[SCM] Packaging for sauerbraten game engine branch, master, updated. upstream/0.0.20090504-23-g8acfbb9

Bruno Kleinert fuddl at tauware.de
Fri Jul 3 10:25:19 UTC 2009


The following commit has been merged in the master branch:
commit 64606184d75d771f6d967ed80237d7d279eab562
Author: Bruno Kleinert <fuddl at tauware.de>
Date:   Fri Jul 3 12:09:41 2009 +0200

    Revert "Imported Upstream version 0.0.20090504"
    
    This reverts commit 1da94a3985fb28bd7dd733508eab20705d902dd9.

diff --git a/Makefile b/Makefile
index ec5d50e..f0b4ec2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,39 +1,18 @@
-CXXFLAGS= -O3 -fomit-frame-pointer
-override CXXFLAGS+= -Wall -fsigned-char
+CXXOPTFLAGS= -O3 -fomit-frame-pointer
+INCLUDES= -Ishared -Iengine -Ifpsgame -Irpggame -Ienet/include -I/usr/X11R6/include `sdl-config --cflags`
+CXXFLAGS= -Wall -fsigned-char $(CXXOPTFLAGS) $(INCLUDES)
 
-PLATFORM= $(shell uname -s)
-PLATFORM_PREFIX= native
+PLATFORM_PREFIX=native
 
-INCLUDES= -Ishared -Iengine -Ifpsgame -Ienet/include
-
-STRIP=
-ifeq (,$(findstring -g,$(CXXFLAGS)))
-ifeq (,$(findstring -pg,$(CXXFLAGS)))
-  STRIP=strip
-endif
-endif
-
-ifneq (,$(findstring MINGW,$(PLATFORM)))
-WINDRES= windres
-CLIENT_INCLUDES= $(INCLUDES) -Iinclude
-CLIENT_LIBS= -mwindows -Llib -lSDL -lSDL_image -lSDL_mixer -lzdll -lopengl32 -lenet -lws2_32 -lwinmm
-else	
-CLIENT_INCLUDES= $(INCLUDES) -I/usr/X11R6/include `sdl-config --cflags`
-CLIENT_LIBS= -Lenet -lenet -L/usr/X11R6/lib `sdl-config --libs` -lSDL_image -lSDL_mixer -lz -lGL
-endif
-ifeq ($(PLATFORM),Linux)
+CLIENT_LIBS= -Lenet -lenet -L/usr/X11R6/lib `sdl-config --libs` -lSDL_image -lSDL_mixer -lz -lGL -lGLU 
+ifeq ($(shell uname -s),Linux)
 CLIENT_LIBS+= -lrt
 endif
 CLIENT_OBJS= \
-	shared/crypto.o \
-	shared/geom.o \
-	shared/stream.o \
 	shared/tools.o \
-	shared/zip.o \
+	shared/geom.o \
 	engine/3dgui.o \
 	engine/bih.o \
-	engine/blend.o \
-	engine/blob.o \
 	engine/client.o	\
 	engine/command.o \
 	engine/console.o \
@@ -46,7 +25,6 @@ CLIENT_OBJS= \
 	engine/main.o \
 	engine/material.o \
 	engine/menus.o \
-	engine/movie.o \
 	engine/normal.o	\
 	engine/octa.o \
 	engine/octaedit.o \
@@ -68,47 +46,14 @@ CLIENT_OBJS= \
 	engine/water.o \
 	engine/world.o \
 	engine/worldio.o \
-	fpsgame/ai.o \
-	fpsgame/client.o \
-	fpsgame/entities.o \
 	fpsgame/fps.o \
-	fpsgame/monster.o \
-	fpsgame/movable.o \
-	fpsgame/render.o \
-	fpsgame/scoreboard.o \
-	fpsgame/server.o \
-	fpsgame/waypoint.o \
-	fpsgame/weapon.o
-ifneq (,$(findstring MINGW,$(PLATFORM)))
-CLIENT_OBJS+= vcpp/SDL_win32_main.o
-endif
-CLIENT_PCH= shared/cube.h.gch engine/engine.h.gch fpsgame/game.h.gch
+	rpggame/rpg.o
 
-ifneq (,$(findstring MINGW,$(PLATFORM)))
-SERVER_INCLUDES= -DSTANDALONE $(INCLUDES) -Iinclude
-SERVER_LIBS= -Llib -lzdll -lenet -lws2_32 -lwinmm
-else
-SERVER_INCLUDES= -DSTANDALONE $(INCLUDES)
 SERVER_LIBS= -Lenet -lenet -lz
-endif
 SERVER_OBJS= \
-	shared/crypto-standalone.o \
-	shared/stream-standalone.o \
 	shared/tools-standalone.o \
-	engine/command-standalone.o \
 	engine/server-standalone.o \
-	fpsgame/server-standalone.o
-MASTER_OBJS= \
-	shared/crypto-standalone.o \
-	shared/stream-standalone.o \
-	shared/tools-standalone.o \
-	engine/command-standalone.o \
-	engine/master-standalone.o
-
-ifeq ($(PLATFORM),SunOS)
-CLIENT_LIBS+= -lsocket -lnsl -lX11
-SERVER_LIBS+= -lsocket -lnsl
-endif
+	fpsgame/fps-standalone.o
 
 default: all
 
@@ -120,332 +65,22 @@ enet/Makefile:
 libenet: enet/Makefile
 	$(MAKE)	-C enet/ all
 
-clean-enet: enet/Makefile
-	$(MAKE) -C enet/ clean
-
-clean:
-	-$(RM) $(CLIENT_PCH) $(CLIENT_OBJS) $(SERVER_OBJS) $(MASTER_OBJS) sauer_client sauer_server sauer_master
-
-%.h.gch: %.h
-	$(CXX) $(CXXFLAGS) -o $@.tmp $(subst .h.gch,.h,$@)
-	mv $@.tmp $@
-
-%-standalone.o: %.cpp
-	$(CXX) $(CXXFLAGS) -c -o $@ $(subst -standalone.o,.cpp,$@)
+clean: enet/Makefile
+	-$(RM) $(SERVER_OBJS) $(CLIENT_OBJS) sauer_server sauer_client
+	$(MAKE)	-C enet/ clean
 
-$(CLIENT_OBJS): CXXFLAGS += $(CLIENT_INCLUDES)
-$(filter shared/%,$(CLIENT_OBJS)): $(filter shared/%,$(CLIENT_PCH))
-$(filter engine/%,$(CLIENT_OBJS)): $(filter engine/%,$(CLIENT_PCH))
-$(filter fpsgame/%,$(CLIENT_OBJS)): $(filter fpsgame/%,$(CLIENT_PCH))
+%-standalone.o:
+	$(CXX) $(CXXFLAGS) -DSTANDALONE -c -o $@ $(subst -standalone.o,.cpp,$@)
 
-$(SERVER_OBJS): CXXFLAGS += $(SERVER_INCLUDES)
-$(filter-out $(SERVER_OBJS),$(MASTER_OBJS)): CXXFLAGS += $(SERVER_INCLUDES)
-
-ifneq (,$(findstring MINGW,$(PLATFORM)))
-vcpp/%.o:
-	$(CXX) $(CXXFLAGS) -c -o $@ $(subst .o,.c,$@) 
-
-client: $(CLIENT_OBJS)
-	$(WINDRES) -I vcpp -i vcpp/sauerbraten.rc -J rc -o vcpp/sauerbraten.res -O coff 
-	$(CXX) $(CXXFLAGS) -o ../bin/sauerbraten.exe vcpp/sauerbraten.res $(CLIENT_OBJS) $(CLIENT_LIBS)
-
-server: $(SERVER_OBJS)
-	$(CXX) $(CXXFLAGS) -o ../bin/sauer_server.exe $(SERVER_OBJS) $(SERVER_LIBS)
-
-master: $(MASTER_OBJS)
-	$(CXX) $(CXXFLAGS) -o ../bin/sauer_master.exe $(MASTER_OBJS) $(SERVER_LIBS)
-
-install: all
-else
 client:	libenet $(CLIENT_OBJS)
 	$(CXX) $(CXXFLAGS) -o sauer_client $(CLIENT_OBJS) $(CLIENT_LIBS)
 
 server:	libenet $(SERVER_OBJS)
 	$(CXX) $(CXXFLAGS) -o sauer_server $(SERVER_OBJS) $(SERVER_LIBS)  
 	
-master: libenet $(MASTER_OBJS)
-	$(CXX) $(CXXFLAGS) -o sauer_master $(MASTER_OBJS) $(SERVER_LIBS)  
-
 install: all
 	cp sauer_client	../bin_unix/$(PLATFORM_PREFIX)_client
 	cp sauer_server	../bin_unix/$(PLATFORM_PREFIX)_server
-ifneq (,$(STRIP))
-	$(STRIP) ../bin_unix/$(PLATFORM_PREFIX)_client
-	$(STRIP) ../bin_unix/$(PLATFORM_PREFIX)_server
-endif
-endif
-
-depend:
-	makedepend -Y -Ishared -Iengine -Ifpsgame $(subst .o,.cpp,$(CLIENT_OBJS))
-	makedepend -a -o.h.gch -Y -Ishared -Iengine -Ifpsgame $(subst .h.gch,.h,$(CLIENT_PCH))
-	makedepend -a -o-standalone.o -Y -Ishared -Iengine -Ifpsgame $(subst -standalone.o,.cpp,$(SERVER_OBJS))
-	makedepend -a -o-standalone.o -Y -Ishared -Iengine -Ifpsgame $(subst -standalone.o,.cpp,$(filter-out $(SERVER_OBJS), $(MASTER_OBJS)))
-
-# DO NOT DELETE
-
-shared/crypto.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-shared/crypto.o: shared/command.h shared/iengine.h shared/igame.h
-shared/geom.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-shared/geom.o: shared/command.h shared/iengine.h shared/igame.h
-shared/stream.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-shared/stream.o: shared/command.h shared/iengine.h shared/igame.h
-shared/tools.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-shared/tools.o: shared/command.h shared/iengine.h shared/igame.h
-shared/zip.o: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-shared/zip.o: shared/command.h shared/iengine.h shared/igame.h
-engine/3dgui.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/3dgui.o: shared/ents.h shared/command.h shared/iengine.h
-engine/3dgui.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/3dgui.o: engine/bih.h engine/texture.h engine/model.h
-engine/3dgui.o: engine/textedit.h
-engine/bih.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/bih.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-engine/bih.o: engine/world.h engine/octa.h engine/lightmap.h engine/bih.h
-engine/bih.o: engine/texture.h engine/model.h
-engine/blend.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/blend.o: shared/ents.h shared/command.h shared/iengine.h
-engine/blend.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/blend.o: engine/bih.h engine/texture.h engine/model.h
-engine/blob.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/blob.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-engine/blob.o: engine/world.h engine/octa.h engine/lightmap.h engine/bih.h
-engine/blob.o: engine/texture.h engine/model.h
-engine/client.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/client.o: shared/ents.h shared/command.h shared/iengine.h
-engine/client.o: shared/igame.h engine/world.h engine/octa.h
-engine/client.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/client.o: engine/model.h
-engine/command.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/command.o: shared/ents.h shared/command.h shared/iengine.h
-engine/command.o: shared/igame.h engine/world.h engine/octa.h
-engine/command.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/command.o: engine/model.h
-engine/console.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/console.o: shared/ents.h shared/command.h shared/iengine.h
-engine/console.o: shared/igame.h engine/world.h engine/octa.h
-engine/console.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/console.o: engine/model.h
-engine/cubeloader.o: engine/engine.h shared/cube.h shared/tools.h
-engine/cubeloader.o: shared/geom.h shared/ents.h shared/command.h
-engine/cubeloader.o: shared/iengine.h shared/igame.h engine/world.h
-engine/cubeloader.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/cubeloader.o: engine/texture.h engine/model.h
-engine/decal.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/decal.o: shared/ents.h shared/command.h shared/iengine.h
-engine/decal.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/decal.o: engine/bih.h engine/texture.h engine/model.h
-engine/dynlight.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/dynlight.o: shared/ents.h shared/command.h shared/iengine.h
-engine/dynlight.o: shared/igame.h engine/world.h engine/octa.h
-engine/dynlight.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/dynlight.o: engine/model.h
-engine/glare.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/glare.o: shared/ents.h shared/command.h shared/iengine.h
-engine/glare.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/glare.o: engine/bih.h engine/texture.h engine/model.h
-engine/glare.o: engine/rendertarget.h
-engine/grass.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/grass.o: shared/ents.h shared/command.h shared/iengine.h
-engine/grass.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/grass.o: engine/bih.h engine/texture.h engine/model.h
-engine/lightmap.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/lightmap.o: shared/ents.h shared/command.h shared/iengine.h
-engine/lightmap.o: shared/igame.h engine/world.h engine/octa.h
-engine/lightmap.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/lightmap.o: engine/model.h
-engine/main.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/main.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-engine/main.o: engine/world.h engine/octa.h engine/lightmap.h engine/bih.h
-engine/main.o: engine/texture.h engine/model.h
-engine/material.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/material.o: shared/ents.h shared/command.h shared/iengine.h
-engine/material.o: shared/igame.h engine/world.h engine/octa.h
-engine/material.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/material.o: engine/model.h
-engine/menus.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/menus.o: shared/ents.h shared/command.h shared/iengine.h
-engine/menus.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/menus.o: engine/bih.h engine/texture.h engine/model.h
-engine/movie.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/movie.o: shared/ents.h shared/command.h shared/iengine.h
-engine/movie.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/movie.o: engine/bih.h engine/texture.h engine/model.h
-engine/normal.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/normal.o: shared/ents.h shared/command.h shared/iengine.h
-engine/normal.o: shared/igame.h engine/world.h engine/octa.h
-engine/normal.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/normal.o: engine/model.h
-engine/octa.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/octa.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-engine/octa.o: engine/world.h engine/octa.h engine/lightmap.h engine/bih.h
-engine/octa.o: engine/texture.h engine/model.h
-engine/octaedit.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/octaedit.o: shared/ents.h shared/command.h shared/iengine.h
-engine/octaedit.o: shared/igame.h engine/world.h engine/octa.h
-engine/octaedit.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/octaedit.o: engine/model.h
-engine/octarender.o: engine/engine.h shared/cube.h shared/tools.h
-engine/octarender.o: shared/geom.h shared/ents.h shared/command.h
-engine/octarender.o: shared/iengine.h shared/igame.h engine/world.h
-engine/octarender.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/octarender.o: engine/texture.h engine/model.h
-engine/physics.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/physics.o: shared/ents.h shared/command.h shared/iengine.h
-engine/physics.o: shared/igame.h engine/world.h engine/octa.h
-engine/physics.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/physics.o: engine/model.h
-engine/pvs.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/pvs.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-engine/pvs.o: engine/world.h engine/octa.h engine/lightmap.h engine/bih.h
-engine/pvs.o: engine/texture.h engine/model.h
-engine/rendergl.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/rendergl.o: shared/ents.h shared/command.h shared/iengine.h
-engine/rendergl.o: shared/igame.h engine/world.h engine/octa.h
-engine/rendergl.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/rendergl.o: engine/model.h
-engine/rendermodel.o: engine/engine.h shared/cube.h shared/tools.h
-engine/rendermodel.o: shared/geom.h shared/ents.h shared/command.h
-engine/rendermodel.o: shared/iengine.h shared/igame.h engine/world.h
-engine/rendermodel.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/rendermodel.o: engine/texture.h engine/model.h engine/ragdoll.h
-engine/rendermodel.o: engine/animmodel.h engine/vertmodel.h
-engine/rendermodel.o: engine/skelmodel.h engine/md2.h engine/md3.h
-engine/rendermodel.o: engine/md5.h engine/obj.h
-engine/renderparticles.o: engine/engine.h shared/cube.h shared/tools.h
-engine/renderparticles.o: shared/geom.h shared/ents.h shared/command.h
-engine/renderparticles.o: shared/iengine.h shared/igame.h engine/world.h
-engine/renderparticles.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/renderparticles.o: engine/texture.h engine/model.h
-engine/renderparticles.o: engine/rendertarget.h engine/depthfx.h
-engine/renderparticles.o: engine/explosion.h engine/lensflare.h
-engine/renderparticles.o: engine/lightning.h
-engine/rendersky.o: engine/engine.h shared/cube.h shared/tools.h
-engine/rendersky.o: shared/geom.h shared/ents.h shared/command.h
-engine/rendersky.o: shared/iengine.h shared/igame.h engine/world.h
-engine/rendersky.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/rendersky.o: engine/texture.h engine/model.h
-engine/rendertext.o: engine/engine.h shared/cube.h shared/tools.h
-engine/rendertext.o: shared/geom.h shared/ents.h shared/command.h
-engine/rendertext.o: shared/iengine.h shared/igame.h engine/world.h
-engine/rendertext.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/rendertext.o: engine/texture.h engine/model.h
-engine/renderva.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/renderva.o: shared/ents.h shared/command.h shared/iengine.h
-engine/renderva.o: shared/igame.h engine/world.h engine/octa.h
-engine/renderva.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/renderva.o: engine/model.h
-engine/server.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/server.o: shared/ents.h shared/command.h shared/iengine.h
-engine/server.o: shared/igame.h engine/world.h engine/octa.h
-engine/server.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/server.o: engine/model.h
-engine/serverbrowser.o: engine/engine.h shared/cube.h shared/tools.h
-engine/serverbrowser.o: shared/geom.h shared/ents.h shared/command.h
-engine/serverbrowser.o: shared/iengine.h shared/igame.h engine/world.h
-engine/serverbrowser.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/serverbrowser.o: engine/texture.h engine/model.h
-engine/shader.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/shader.o: shared/ents.h shared/command.h shared/iengine.h
-engine/shader.o: shared/igame.h engine/world.h engine/octa.h
-engine/shader.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/shader.o: engine/model.h
-engine/shadowmap.o: engine/engine.h shared/cube.h shared/tools.h
-engine/shadowmap.o: shared/geom.h shared/ents.h shared/command.h
-engine/shadowmap.o: shared/iengine.h shared/igame.h engine/world.h
-engine/shadowmap.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/shadowmap.o: engine/texture.h engine/model.h engine/rendertarget.h
-engine/sound.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/sound.o: shared/ents.h shared/command.h shared/iengine.h
-engine/sound.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/sound.o: engine/bih.h engine/texture.h engine/model.h
-engine/texture.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/texture.o: shared/ents.h shared/command.h shared/iengine.h
-engine/texture.o: shared/igame.h engine/world.h engine/octa.h
-engine/texture.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/texture.o: engine/model.h engine/scale.h
-engine/water.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/water.o: shared/ents.h shared/command.h shared/iengine.h
-engine/water.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/water.o: engine/bih.h engine/texture.h engine/model.h
-engine/world.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/world.o: shared/ents.h shared/command.h shared/iengine.h
-engine/world.o: shared/igame.h engine/world.h engine/octa.h engine/lightmap.h
-engine/world.o: engine/bih.h engine/texture.h engine/model.h
-engine/worldio.o: engine/engine.h shared/cube.h shared/tools.h shared/geom.h
-engine/worldio.o: shared/ents.h shared/command.h shared/iengine.h
-engine/worldio.o: shared/igame.h engine/world.h engine/octa.h
-engine/worldio.o: engine/lightmap.h engine/bih.h engine/texture.h
-engine/worldio.o: engine/model.h
-fpsgame/ai.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/ai.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-fpsgame/ai.o: fpsgame/ai.h
-fpsgame/client.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/client.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/client.o: shared/igame.h fpsgame/ai.h fpsgame/capture.h fpsgame/ctf.h
-fpsgame/entities.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/entities.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/entities.o: shared/igame.h fpsgame/ai.h
-fpsgame/fps.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/fps.o: shared/ents.h shared/command.h shared/iengine.h shared/igame.h
-fpsgame/fps.o: fpsgame/ai.h
-fpsgame/monster.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/monster.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/monster.o: shared/igame.h fpsgame/ai.h
-fpsgame/movable.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/movable.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/movable.o: shared/igame.h fpsgame/ai.h
-fpsgame/render.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/render.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/render.o: shared/igame.h fpsgame/ai.h
-fpsgame/scoreboard.o: fpsgame/game.h shared/cube.h shared/tools.h
-fpsgame/scoreboard.o: shared/geom.h shared/ents.h shared/command.h
-fpsgame/scoreboard.o: shared/iengine.h shared/igame.h fpsgame/ai.h
-fpsgame/server.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/server.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/server.o: shared/igame.h fpsgame/ai.h fpsgame/capture.h fpsgame/ctf.h
-fpsgame/server.o: fpsgame/extinfo.h fpsgame/aiman.h
-fpsgame/waypoint.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/waypoint.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/waypoint.o: shared/igame.h fpsgame/ai.h
-fpsgame/weapon.o: fpsgame/game.h shared/cube.h shared/tools.h shared/geom.h
-fpsgame/weapon.o: shared/ents.h shared/command.h shared/iengine.h
-fpsgame/weapon.o: shared/igame.h fpsgame/ai.h
-
-shared/cube.h.gch: shared/tools.h shared/geom.h shared/ents.h
-shared/cube.h.gch: shared/command.h shared/iengine.h shared/igame.h
-engine/engine.h.gch: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-engine/engine.h.gch: shared/command.h shared/iengine.h shared/igame.h
-engine/engine.h.gch: engine/world.h engine/octa.h engine/lightmap.h
-engine/engine.h.gch: engine/bih.h engine/texture.h engine/model.h
-fpsgame/game.h.gch: shared/cube.h shared/tools.h shared/geom.h shared/ents.h
-fpsgame/game.h.gch: shared/command.h shared/iengine.h shared/igame.h
-fpsgame/game.h.gch: fpsgame/ai.h
-
-shared/crypto-standalone.o: shared/cube.h shared/tools.h shared/geom.h
-shared/crypto-standalone.o: shared/ents.h shared/command.h shared/iengine.h
-shared/crypto-standalone.o: shared/igame.h
-shared/stream-standalone.o: shared/cube.h shared/tools.h shared/geom.h
-shared/stream-standalone.o: shared/ents.h shared/command.h shared/iengine.h
-shared/stream-standalone.o: shared/igame.h
-shared/tools-standalone.o: shared/cube.h shared/tools.h shared/geom.h
-shared/tools-standalone.o: shared/ents.h shared/command.h shared/iengine.h
-shared/tools-standalone.o: shared/igame.h
-engine/command-standalone.o: engine/engine.h shared/cube.h shared/tools.h
-engine/command-standalone.o: shared/geom.h shared/ents.h shared/command.h
-engine/command-standalone.o: shared/iengine.h shared/igame.h engine/world.h
-engine/command-standalone.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/command-standalone.o: engine/texture.h engine/model.h
-engine/server-standalone.o: engine/engine.h shared/cube.h shared/tools.h
-engine/server-standalone.o: shared/geom.h shared/ents.h shared/command.h
-engine/server-standalone.o: shared/iengine.h shared/igame.h engine/world.h
-engine/server-standalone.o: engine/octa.h engine/lightmap.h engine/bih.h
-engine/server-standalone.o: engine/texture.h engine/model.h
-fpsgame/server-standalone.o: fpsgame/game.h shared/cube.h shared/tools.h
-fpsgame/server-standalone.o: shared/geom.h shared/ents.h shared/command.h
-fpsgame/server-standalone.o: shared/iengine.h shared/igame.h fpsgame/ai.h
-fpsgame/server-standalone.o: fpsgame/capture.h fpsgame/ctf.h
-fpsgame/server-standalone.o: fpsgame/extinfo.h fpsgame/aiman.h
+	strip ../bin_unix/$(PLATFORM_PREFIX)_client
+	strip ../bin_unix/$(PLATFORM_PREFIX)_server
 
-engine/master-standalone.o: shared/cube.h shared/tools.h shared/geom.h
-engine/master-standalone.o: shared/ents.h shared/command.h shared/iengine.h
-engine/master-standalone.o: shared/igame.h
diff --git a/OCTA_TODO.txt b/OCTA_TODO.txt
new file mode 100644
index 0000000..6d8ee50
--- /dev/null
+++ b/OCTA_TODO.txt
@@ -0,0 +1,90 @@
+- check if ATI fixed fglrx/OQ bug in next version
+
+- make people that do teamkills/damage wait longer?
+
+- fog entities (R, G, B, radius, fog dist),
+     If player inside of radius, changes global fog color and distance to the entity's, interpolated
+     with the outside map fog color depending on distance from center. Use some sort of weighted average
+     if inside multiple fog entities.
+
+- sound entities | http://freesound.iua.upf.edu/
+
+- make sure all bumpmaps are parallax -> finetune each one individually
+
+- audio effect for chat messages | audio for gui events
+
+- social cheating solution
+
+- before a release: update static wiki
+
+- improve smoothness of going up steps ?
+- remove delay before doors start opening ?
+- make nearby closed doors render early so that they can contribute to OC?
+- water physics close to surface
+- add entity looping command?
+
+- LOD: improved lod-remip?
+- "editing not in view" sometimes obstructs editing
+- simultaneous heightmap orientations (all six sides at same time for easier rock/cliff/caves/destructable geom?)
+- multiple cube selections for heightmaps? maybe instead of selections+mode, use textures+Q?
+- heightmap outlines (like sky) instead of selection box
+- context sensitive editing menu: short cuts to ents, lighting, map ops etc. bind to tab. (current menu being more detailed and reference)
+- combine mingw and linux makefiles?
+- need to make selecting cubes/ents while inside a cube easier.... maybe modify raycube/rayent to treat cubes as 'hollow'?
+- entundo more efficeint
+
+- maybe add a way to purge infrequent / not recently used textures? esp now with mapshots.
+
+- reduce maps
+- memory leaks?
+- Mix_LoadMUS() crashes after being called many times?
+
+- death needs to be more obvious (better sound/centerprint) 
+- sparklies very bad on ATI, particularly deathtek / diff skybox
+- crashes when people join?
+- 2 levels of lightlod for huge maps
+- shallow water splashing sound?
+
+- use setbb also for monsters
+- experiment with averaging lightvector based on intensity^N, such that dominant light becomes more dominant without losing smooth transitions | store intensity multiplier in alpha?
+
+- the eyecandy priority list: better bloom, motion blur, volumetric fog (see fog entities idea above), character bumpmaps (likely need skeletal animation to make this memory efficient)...
+- bots (level design placed waypoints?)
+- destructable geom
+
+- better doc on the scripting language | beginners guide | wiki?
+- fake rigid-body sphere only?
+- $ vs @? var stacks? [] for trees/arrays? other improvements? | do "." for concatword so you could do easy (associative) arrays and tree structures?
+- start moving some stuff in their own headers
+- new BAS sounds? | player spawn | sp respawn | menu clicks | specific door sound? | end of level | push button/trigger
+- improve bloom? optimize it.
+- report to ATI: float vs short issue
+- more cube map ports?
+- integrate fmodex (to get both panning and stereo ogg on windows?)
+- 3d sprites?
+- monster spawner ammo - add egg model
+- iconic stats
+- profile for memory usage (devpartner?) | if md2 buffers are a lot, can remove it
+- glowmaps in the texture browser -> messy
+
+- make start of flares etc spawn out further from the player
+- improve on cma render then add to sauer
+- new sounds: impact (diff materials + flesh) | flyby, both instant & projectile 
+
+- all cfg stuff in menu
+- draw not-spawned items in wireframe?
+
+- make wether you are hitting other player clearer - blip sounds like q3?
+- LOD: visibleface(lodcube==true) must not go across VA boundaries... otherwise minor HOM bewteen LOD/non-LOD rendering boundaries
+- LOD: materials & sky currently always rendered at lod0 only, is that ever a problem visually/performance?
+- LOD: make loddistance fov dependent (for models too)
+- a multiplayer player still shows SG rays go thru target | hard to fix, is it really a problem?
+- sound samples for N kills in a row
+- sword?
+- think about doing other permanent powerups besides +10h
+- slowmo as gameplay feature?
+- calclight while playing?
+- use of glBufferData() makes ATI driver crash on SDL_Exit()
+- fix variance in framerate?
+- render entities at distance as sprites
+- blend screen when inside a cube
diff --git a/enet/Doxyfile b/enet/Doxyfile
old mode 100644
new mode 100755
diff --git a/enet/LICENSE b/enet/LICENSE
index 4c57065..9c4fe6b 100644
--- a/enet/LICENSE
+++ b/enet/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2002-2008 Lee Salzman
+Copyright (c) 2002-2007 Lee Salzman
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 
diff --git a/enet/Makefile.am b/enet/Makefile.am
index dc86fde..5b9caf3 100644
--- a/enet/Makefile.am
+++ b/enet/Makefile.am
@@ -1,5 +1,5 @@
 lib_LIBRARIES = libenet.a
 libenet_a_SOURCES = host.c list.c callbacks.c packet.c peer.c protocol.c unix.c win32.c
-INCLUDES = -Iinclude
+INCLUDES = -Iinclude/
 
 SUBDIRS = include
diff --git a/enet/Makefile.in b/enet/Makefile.in
index 019446c..0ba2d8f 100644
--- a/enet/Makefile.in
+++ b/enet/Makefile.in
@@ -1,8 +1,8 @@
-# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# Makefile.in generated by automake 1.9.6 from Makefile.am.
 # @configure_input@
 
 # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
-# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# 2003, 2004, 2005  Free Software Foundation, Inc.
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
@@ -14,11 +14,15 @@
 
 @SET_MAKE@
 
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
 VPATH = @srcdir@
 pkgdatadir = $(datadir)/@PACKAGE@
 pkglibdir = $(libdir)/@PACKAGE@
 pkgincludedir = $(includedir)/@PACKAGE@
+top_builddir = .
 am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+INSTALL = @INSTALL@
 install_sh_DATA = $(install_sh) -c -m 644
 install_sh_PROGRAM = $(install_sh) -c
 install_sh_SCRIPT = $(install_sh) -c
@@ -32,15 +36,15 @@ PRE_UNINSTALL = :
 POST_UNINSTALL = :
 subdir = .
 DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
-	$(srcdir)/Makefile.in $(top_srcdir)/configure depcomp \
-	install-sh missing
+	$(srcdir)/Makefile.in $(top_srcdir)/configure config.guess \
+	config.sub depcomp install-sh missing mkinstalldirs
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/configure.in
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
 am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \
- configure.lineno config.status.lineno
-mkinstalldirs = $(install_sh) -d
+ configure.lineno configure.status.lineno
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
 CONFIG_CLEAN_FILES =
 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
 am__vpath_adj = case $$p in \
@@ -59,7 +63,7 @@ am_libenet_a_OBJECTS = host.$(OBJEXT) list.$(OBJEXT) \
 	callbacks.$(OBJEXT) packet.$(OBJEXT) peer.$(OBJEXT) \
 	protocol.$(OBJEXT) unix.$(OBJEXT) win32.$(OBJEXT)
 libenet_a_OBJECTS = $(am_libenet_a_OBJECTS)
-DEFAULT_INCLUDES = -I. at am__isrc@
+DEFAULT_INCLUDES = -I. -I$(srcdir)
 depcomp = $(SHELL) $(top_srcdir)/depcomp
 am__depfiles_maybe = depfiles
 COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
@@ -70,13 +74,10 @@ SOURCES = $(libenet_a_SOURCES)
 DIST_SOURCES = $(libenet_a_SOURCES)
 RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
 	html-recursive info-recursive install-data-recursive \
-	install-dvi-recursive install-exec-recursive \
-	install-html-recursive install-info-recursive \
-	install-pdf-recursive install-ps-recursive install-recursive \
-	installcheck-recursive installdirs-recursive pdf-recursive \
-	ps-recursive uninstall-recursive
-RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
-  distclean-recursive maintainer-clean-recursive
+	install-exec-recursive install-info-recursive \
+	install-recursive installcheck-recursive installdirs-recursive \
+	pdf-recursive ps-recursive uninstall-info-recursive \
+	uninstall-recursive
 ETAGS = etags
 CTAGS = ctags
 DIST_SUBDIRS = $(SUBDIRS)
@@ -92,6 +93,8 @@ GZIP_ENV = --best
 distuninstallcheck_listfiles = find . -type f -print
 distcleancheck_listfiles = find . -type f -print
 ACLOCAL = @ACLOCAL@
+AMDEP_FALSE = @AMDEP_FALSE@
+AMDEP_TRUE = @AMDEP_TRUE@
 AMTAR = @AMTAR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -111,7 +114,6 @@ ECHO_T = @ECHO_T@
 EGREP = @EGREP@
 EXEEXT = @EXEEXT@
 GREP = @GREP@
-INSTALL = @INSTALL@
 INSTALL_DATA = @INSTALL_DATA@
 INSTALL_PROGRAM = @INSTALL_PROGRAM@
 INSTALL_SCRIPT = @INSTALL_SCRIPT@
@@ -121,7 +123,6 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LTLIBOBJS = @LTLIBOBJS@
 MAKEINFO = @MAKEINFO@
-MKDIR_P = @MKDIR_P@
 OBJEXT = @OBJEXT@
 PACKAGE = @PACKAGE@
 PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
@@ -135,11 +136,9 @@ SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
 VERSION = @VERSION@
-abs_builddir = @abs_builddir@
-abs_srcdir = @abs_srcdir@
-abs_top_builddir = @abs_top_builddir@
-abs_top_srcdir = @abs_top_srcdir@
 ac_ct_CC = @ac_ct_CC@
+am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
+am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -147,7 +146,6 @@ am__tar = @am__tar@
 am__untar = @am__untar@
 bindir = @bindir@
 build_alias = @build_alias@
-builddir = @builddir@
 datadir = @datadir@
 datarootdir = @datarootdir@
 docdir = @docdir@
@@ -171,14 +169,11 @@ program_transform_name = @program_transform_name@
 psdir = @psdir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
-srcdir = @srcdir@
 sysconfdir = @sysconfdir@
 target_alias = @target_alias@
-top_builddir = @top_builddir@
-top_srcdir = @top_srcdir@
 lib_LIBRARIES = libenet.a
 libenet_a_SOURCES = host.c list.c callbacks.c packet.c peer.c protocol.c unix.c win32.c
-INCLUDES = -Iinclude
+INCLUDES = -Iinclude/
 SUBDIRS = include
 all: all-recursive
 
@@ -219,7 +214,7 @@ $(ACLOCAL_M4):  $(am__aclocal_m4_deps)
 	cd $(srcdir) && $(ACLOCAL) $(ACLOCAL_AMFLAGS)
 install-libLIBRARIES: $(lib_LIBRARIES)
 	@$(NORMAL_INSTALL)
-	test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)"
+	test -z "$(libdir)" || $(mkdir_p) "$(DESTDIR)$(libdir)"
 	@list='$(lib_LIBRARIES)'; for p in $$list; do \
 	  if test -f $$p; then \
 	    f=$(am__strip_dir) \
@@ -267,18 +262,19 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote at ./$(DEPDIR)/win32.Po at am__quote@
 
 .c.o:
- at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $<
- at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at am__fastdepCC_TRUE@	if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \
+ at am__fastdepCC_TRUE@	then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(COMPILE) -c $<
 
 .c.obj:
- at am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'`
- at am__fastdepCC_TRUE@	mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po
+ at am__fastdepCC_TRUE@	if $(COMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \
+ at am__fastdepCC_TRUE@	then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
 @AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
 @am__fastdepCC_FALSE@	$(COMPILE) -c `$(CYGPATH_W) '$<'`
+uninstall-info-am:
 
 # This directory's subdirectories are mostly independent; you can cd
 # into them and run `make' without going through this Makefile.
@@ -311,7 +307,8 @@ $(RECURSIVE_TARGETS):
 	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
 	fi; test -z "$$fail"
 
-$(RECURSIVE_CLEAN_TARGETS):
+mostlyclean-recursive clean-recursive distclean-recursive \
+maintainer-clean-recursive:
 	@failcom='exit 1'; \
 	for f in x $$MAKEFLAGS; do \
 	  case $$f in \
@@ -355,8 +352,8 @@ ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	mkid -fID $$unique
 tags: TAGS
 
@@ -381,8 +378,8 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
 	  test -n "$$unique" || unique=$$empty_fix; \
 	  $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
@@ -392,12 +389,13 @@ ctags: CTAGS
 CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 		$(TAGS_FILES) $(LISP)
 	tags=; \
+	here=`pwd`; \
 	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	test -z "$(CTAGS_ARGS)$$tags$$unique" \
 	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
 	     $$tags $$unique
@@ -412,22 +410,23 @@ distclean-tags:
 
 distdir: $(DISTFILES)
 	$(am__remove_distdir)
-	test -d $(distdir) || mkdir $(distdir)
-	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	list='$(DISTFILES)'; \
-	  dist_files=`for file in $$list; do echo $$file; done | \
-	  sed -e "s|^$$srcdirstrip/||;t" \
-	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
-	case $$dist_files in \
-	  */*) $(MKDIR_P) `echo "$$dist_files" | \
-			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
-			   sort -u` ;; \
-	esac; \
-	for file in $$dist_files; do \
+	mkdir $(distdir)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
+	list='$(DISTFILES)'; for file in $$list; do \
+	  case $$file in \
+	    $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
+	    $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
+	  esac; \
 	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
+	  if test "$$dir" != "$$file" && test "$$dir" != "."; then \
+	    dir="/$$dir"; \
+	    $(mkdir_p) "$(distdir)$$dir"; \
+	  else \
+	    dir=''; \
+	  fi; \
 	  if test -d $$d/$$file; then \
-	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
 	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
 	      cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
 	    fi; \
@@ -441,7 +440,7 @@ distdir: $(DISTFILES)
 	list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
 	  if test "$$subdir" = .; then :; else \
 	    test -d "$(distdir)/$$subdir" \
-	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || $(mkdir_p) "$(distdir)/$$subdir" \
 	    || exit 1; \
 	    distdir=`$(am__cd) $(distdir) && pwd`; \
 	    top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
@@ -449,8 +448,6 @@ distdir: $(DISTFILES)
 	      $(MAKE) $(AM_MAKEFLAGS) \
 	        top_distdir="$$top_distdir" \
 	        distdir="$$distdir/$$subdir" \
-		am__remove_distdir=: \
-		am__skip_length_check=: \
 	        distdir) \
 	      || exit 1; \
 	  fi; \
@@ -458,7 +455,7 @@ distdir: $(DISTFILES)
 	-find $(distdir) -type d ! -perm -777 -exec chmod a+rwx {} \; -o \
 	  ! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
 	  ! -type d ! -perm -400 -exec chmod a+r {} \; -o \
-	  ! -type d ! -perm -444 -exec $(install_sh) -c -m a+r {} {} \; \
+	  ! -type d ! -perm -444 -exec $(SHELL) $(install_sh) -c -m a+r {} {} \; \
 	|| chmod -R a+r $(distdir)
 dist-gzip: distdir
 	tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz
@@ -468,10 +465,6 @@ dist-bzip2: distdir
 	tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2
 	$(am__remove_distdir)
 
-dist-lzma: distdir
-	tardir=$(distdir) && $(am__tar) | lzma -9 -c >$(distdir).tar.lzma
-	$(am__remove_distdir)
-
 dist-tarZ: distdir
 	tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z
 	$(am__remove_distdir)
@@ -498,8 +491,6 @@ distcheck: dist
 	  GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\
 	*.tar.bz2*) \
 	  bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\
-	*.tar.lzma*) \
-	  unlzma -c $(distdir).tar.lzma | $(am__untar) ;;\
 	*.tar.Z*) \
 	  uncompress -c $(distdir).tar.Z | $(am__untar) ;;\
 	*.shar.gz*) \
@@ -539,7 +530,7 @@ distcheck: dist
 	$(am__remove_distdir)
 	@(echo "$(distdir) archives ready for distribution: "; \
 	  list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \
-	  sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x'
+	  sed -e '1{h;s/./=/g;p;x;}' -e '$${p;x;}'
 distuninstallcheck:
 	@cd $(distuninstallcheck_dir) \
 	&& test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \
@@ -564,7 +555,7 @@ all-am: Makefile $(LIBRARIES)
 installdirs: installdirs-recursive
 installdirs-am:
 	for dir in "$(DESTDIR)$(libdir)"; do \
-	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	  test -z "$$dir" || $(mkdir_p) "$$dir"; \
 	done
 install: install-recursive
 install-exec: install-exec-recursive
@@ -613,20 +604,12 @@ info-am:
 
 install-data-am:
 
-install-dvi: install-dvi-recursive
-
 install-exec-am: install-libLIBRARIES
 
-install-html: install-html-recursive
-
 install-info: install-info-recursive
 
 install-man:
 
-install-pdf: install-pdf-recursive
-
-install-ps: install-ps-recursive
-
 installcheck-am:
 
 maintainer-clean: maintainer-clean-recursive
@@ -648,26 +631,24 @@ ps: ps-recursive
 
 ps-am:
 
-uninstall-am: uninstall-libLIBRARIES
+uninstall-am: uninstall-info-am uninstall-libLIBRARIES
 
-.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
-	install-strip
+uninstall-info: uninstall-info-recursive
 
-.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
-	all all-am am--refresh check check-am clean clean-generic \
-	clean-libLIBRARIES ctags ctags-recursive dist dist-all \
-	dist-bzip2 dist-gzip dist-lzma dist-shar dist-tarZ dist-zip \
-	distcheck distclean distclean-compile distclean-generic \
+.PHONY: $(RECURSIVE_TARGETS) CTAGS GTAGS all all-am am--refresh check \
+	check-am clean clean-generic clean-libLIBRARIES \
+	clean-recursive ctags ctags-recursive dist dist-all dist-bzip2 \
+	dist-gzip dist-shar dist-tarZ dist-zip distcheck distclean \
+	distclean-compile distclean-generic distclean-recursive \
 	distclean-tags distcleancheck distdir distuninstallcheck dvi \
 	dvi-am html html-am info info-am install install-am \
-	install-data install-data-am install-dvi install-dvi-am \
-	install-exec install-exec-am install-html install-html-am \
+	install-data install-data-am install-exec install-exec-am \
 	install-info install-info-am install-libLIBRARIES install-man \
-	install-pdf install-pdf-am install-ps install-ps-am \
 	install-strip installcheck installcheck-am installdirs \
 	installdirs-am maintainer-clean maintainer-clean-generic \
-	mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \
-	ps ps-am tags tags-recursive uninstall uninstall-am \
+	maintainer-clean-recursive mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-recursive pdf pdf-am ps ps-am \
+	tags tags-recursive uninstall uninstall-am uninstall-info-am \
 	uninstall-libLIBRARIES
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
diff --git a/enet/aclocal.m4 b/enet/aclocal.m4
index eb3b99a..aef181a 100644
--- a/enet/aclocal.m4
+++ b/enet/aclocal.m4
@@ -1,7 +1,7 @@
-# generated automatically by aclocal 1.10.1 -*- Autoconf -*-
+# generated automatically by aclocal 1.9.6 -*- Autoconf -*-
 
 # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-# 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# 2005  Free Software Foundation, Inc.
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
@@ -11,15 +11,7 @@
 # even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 # PARTICULAR PURPOSE.
 
-m4_ifndef([AC_AUTOCONF_VERSION],
-  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
-m4_if(AC_AUTOCONF_VERSION, [2.61],,
-[m4_warning([this file was generated for autoconf 2.61.
-You have another version of autoconf.  It may work, but is not guaranteed to.
-If you have problems, you may need to regenerate the build system entirely.
-To do so, use the procedure documented by the package, typically `autoreconf'.])])
-
-# Copyright (C) 2002, 2003, 2005, 2006, 2007  Free Software Foundation, Inc.
+# Copyright (C) 2002, 2003, 2005  Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -29,31 +21,14 @@ To do so, use the procedure documented by the package, typically `autoreconf'.])
 # ----------------------------
 # Automake X.Y traces this macro to ensure aclocal.m4 has been
 # generated from the m4 files accompanying Automake X.Y.
-# (This private macro should not be called outside this file.)
-AC_DEFUN([AM_AUTOMAKE_VERSION],
-[am__api_version='1.10'
-dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to
-dnl require some minimum version.  Point them to the right macro.
-m4_if([$1], [1.10.1], [],
-      [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl
-])
-
-# _AM_AUTOCONF_VERSION(VERSION)
-# -----------------------------
-# aclocal traces this macro to find the Autoconf version.
-# This is a private macro too.  Using m4_define simplifies
-# the logic in aclocal, which can simply ignore this definition.
-m4_define([_AM_AUTOCONF_VERSION], [])
+AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version="1.9"])
 
 # AM_SET_CURRENT_AUTOMAKE_VERSION
 # -------------------------------
-# Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced.
+# Call AM_AUTOMAKE_VERSION so it can be traced.
 # This function is AC_REQUIREd by AC_INIT_AUTOMAKE.
 AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION],
-[AM_AUTOMAKE_VERSION([1.10.1])dnl
-m4_ifndef([AC_AUTOCONF_VERSION],
-  [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl
-_AM_AUTOCONF_VERSION(AC_AUTOCONF_VERSION)])
+	 [AM_AUTOMAKE_VERSION([1.9.6])])
 
 # AM_AUX_DIR_EXPAND                                         -*- Autoconf -*-
 
@@ -110,14 +85,14 @@ am_aux_dir=`cd $ac_aux_dir && pwd`
 
 # AM_CONDITIONAL                                            -*- Autoconf -*-
 
-# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005, 2006
+# Copyright (C) 1997, 2000, 2001, 2003, 2004, 2005
 # Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
 
-# serial 8
+# serial 7
 
 # AM_CONDITIONAL(NAME, SHELL-CONDITION)
 # -------------------------------------
@@ -126,10 +101,8 @@ AC_DEFUN([AM_CONDITIONAL],
 [AC_PREREQ(2.52)dnl
  ifelse([$1], [TRUE],  [AC_FATAL([$0: invalid condition: $1])],
 	[$1], [FALSE], [AC_FATAL([$0: invalid condition: $1])])dnl
-AC_SUBST([$1_TRUE])dnl
-AC_SUBST([$1_FALSE])dnl
-_AM_SUBST_NOTMAKE([$1_TRUE])dnl
-_AM_SUBST_NOTMAKE([$1_FALSE])dnl
+AC_SUBST([$1_TRUE])
+AC_SUBST([$1_FALSE])
 if $2; then
   $1_TRUE=
   $1_FALSE='#'
@@ -143,14 +116,15 @@ AC_CONFIG_COMMANDS_PRE(
 Usually this means the macro was only invoked conditionally.]])
 fi])])
 
-# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+
+# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005
 # Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
 
-# serial 9
+# serial 8
 
 # There are a few dirty hacks below to avoid letting `AC_PROG_CC' be
 # written in clear, in which case automake, when reading aclocal.m4,
@@ -178,7 +152,6 @@ AC_REQUIRE([AM_DEP_TRACK])dnl
 ifelse([$1], CC,   [depcc="$CC"   am_compiler_list=],
        [$1], CXX,  [depcc="$CXX"  am_compiler_list=],
        [$1], OBJC, [depcc="$OBJC" am_compiler_list='gcc3 gcc'],
-       [$1], UPC,  [depcc="$UPC"  am_compiler_list=],
        [$1], GCJ,  [depcc="$GCJ"  am_compiler_list='gcc3 gcc'],
                    [depcc="$$1"   am_compiler_list=])
 
@@ -244,7 +217,6 @@ AC_CACHE_CHECK([dependency style of $depcc],
        depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
        $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \
          >/dev/null 2>conftest.err &&
-       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
        grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
        grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 &&
        ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
@@ -297,8 +269,7 @@ if test "x$enable_dependency_tracking" != xno; then
   AMDEPBACKSLASH='\'
 fi
 AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno])
-AC_SUBST([AMDEPBACKSLASH])dnl
-_AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl
+AC_SUBST([AMDEPBACKSLASH])
 ])
 
 # Generate code to set up dependency tracking.              -*- Autoconf -*-
@@ -323,9 +294,8 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS],
   # some people rename them; so instead we look at the file content.
   # Grep'ing the first line is not enough: some people post-process
   # each Makefile.in and add a new line on top of each file to say so.
-  # Grep'ing the whole file is not good either: AIX grep has a line
-  # limit of 2048, but all sed's we know have understand at least 4000.
-  if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+  # So let's grep whole file.
+  if grep '^#.*generated by automake' $mf > /dev/null 2>&1; then
     dirpart=`AS_DIRNAME("$mf")`
   else
     continue
@@ -372,14 +342,14 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
 
 # Do all the work for Automake.                             -*- Autoconf -*-
 
-# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
-# 2005, 2006, 2008 Free Software Foundation, Inc.
+# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
+# Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
 
-# serial 13
+# serial 12
 
 # This macro actually does too much.  Some checks are only needed if
 # your package does certain things.  But this isn't really a big deal.
@@ -396,20 +366,16 @@ AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS],
 # arguments mandatory, and then we can depend on a new Autoconf
 # release and drop the old call support.
 AC_DEFUN([AM_INIT_AUTOMAKE],
-[AC_PREREQ([2.60])dnl
+[AC_PREREQ([2.58])dnl
 dnl Autoconf wants to disallow AM_ names.  We explicitly allow
 dnl the ones we care about.
 m4_pattern_allow([^AM_[A-Z]+FLAGS$])dnl
 AC_REQUIRE([AM_SET_CURRENT_AUTOMAKE_VERSION])dnl
 AC_REQUIRE([AC_PROG_INSTALL])dnl
-if test "`cd $srcdir && pwd`" != "`pwd`"; then
-  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
-  # is not polluted with repeated "-I."
-  AC_SUBST([am__isrc], [' -I$(srcdir)'])_AM_SUBST_NOTMAKE([am__isrc])dnl
-  # test to see if srcdir already configured
-  if test -f $srcdir/config.status; then
-    AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
-  fi
+# test to see if srcdir already configured
+if test "`cd $srcdir && pwd`" != "`pwd`" &&
+   test -f $srcdir/config.status; then
+  AC_MSG_ERROR([source directory already configured; run "make distclean" there first])
 fi
 
 # test whether we have cygpath
@@ -429,9 +395,6 @@ m4_ifval([$2],
  AC_SUBST([PACKAGE], [$1])dnl
  AC_SUBST([VERSION], [$2])],
 [_AM_SET_OPTIONS([$1])dnl
-dnl Diagnose old-style AC_INIT with new-style AM_AUTOMAKE_INIT.
-m4_if(m4_ifdef([AC_PACKAGE_NAME], 1)m4_ifdef([AC_PACKAGE_VERSION], 1), 11,,
-  [m4_fatal([AC_INIT should be called with package and version arguments])])dnl
  AC_SUBST([PACKAGE], ['AC_PACKAGE_TARNAME'])dnl
  AC_SUBST([VERSION], ['AC_PACKAGE_VERSION'])])dnl
 
@@ -467,10 +430,6 @@ AC_PROVIDE_IFELSE([AC_PROG_CXX],
                   [_AM_DEPENDENCIES(CXX)],
                   [define([AC_PROG_CXX],
                           defn([AC_PROG_CXX])[_AM_DEPENDENCIES(CXX)])])dnl
-AC_PROVIDE_IFELSE([AC_PROG_OBJC],
-                  [_AM_DEPENDENCIES(OBJC)],
-                  [define([AC_PROG_OBJC],
-                          defn([AC_PROG_OBJC])[_AM_DEPENDENCIES(OBJC)])])dnl
 ])
 ])
 
@@ -484,17 +443,16 @@ AC_PROVIDE_IFELSE([AC_PROG_OBJC],
 # our stamp files there.
 AC_DEFUN([_AC_AM_CONFIG_HEADER_HOOK],
 [# Compute $1's index in $config_headers.
-_am_arg=$1
 _am_stamp_count=1
 for _am_header in $config_headers :; do
   case $_am_header in
-    $_am_arg | $_am_arg:* )
+    $1 | $1:* )
       break ;;
     * )
       _am_stamp_count=`expr $_am_stamp_count + 1` ;;
   esac
 done
-echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count])
+echo "timestamp for $1" >`AS_DIRNAME([$1])`/stamp-h[]$_am_stamp_count])
 
 # Copyright (C) 2001, 2003, 2005  Free Software Foundation, Inc.
 #
@@ -507,7 +465,7 @@ echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_co
 # Define $install_sh.
 AC_DEFUN([AM_PROG_INSTALL_SH],
 [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
-install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"}
+install_sh=${install_sh-"$am_aux_dir/install-sh"}
 AC_SUBST(install_sh)])
 
 # Copyright (C) 2003, 2005  Free Software Foundation, Inc.
@@ -585,14 +543,14 @@ rm -f confinc confmf
 
 # Fake the existence of programs that GNU maintainers use.  -*- Autoconf -*-
 
-# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2004, 2005
+# Copyright (C) 1997, 1999, 2000, 2001, 2003, 2005
 # Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
 
-# serial 5
+# serial 4
 
 # AM_MISSING_PROG(NAME, PROGRAM)
 # ------------------------------
@@ -608,7 +566,6 @@ AC_SUBST($1)])
 # If it does, set am_missing_run to use it, otherwise, to nothing.
 AC_DEFUN([AM_MISSING_HAS_RUN],
 [AC_REQUIRE([AM_AUX_DIR_EXPAND])dnl
-AC_REQUIRE_AUX_FILE([missing])dnl
 test x"${MISSING+set}" = xset || MISSING="\${SHELL} $am_aux_dir/missing"
 # Use eval to expand $SHELL
 if eval "$MISSING --run true"; then
@@ -619,7 +576,7 @@ else
 fi
 ])
 
-# Copyright (C) 2003, 2004, 2005, 2006  Free Software Foundation, Inc.
+# Copyright (C) 2003, 2004, 2005  Free Software Foundation, Inc.
 #
 # This file is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
@@ -627,23 +584,60 @@ fi
 
 # AM_PROG_MKDIR_P
 # ---------------
-# Check for `mkdir -p'.
+# Check whether `mkdir -p' is supported, fallback to mkinstalldirs otherwise.
+#
+# Automake 1.8 used `mkdir -m 0755 -p --' to ensure that directories
+# created by `make install' are always world readable, even if the
+# installer happens to have an overly restrictive umask (e.g. 077).
+# This was a mistake.  There are at least two reasons why we must not
+# use `-m 0755':
+#   - it causes special bits like SGID to be ignored,
+#   - it may be too restrictive (some setups expect 775 directories).
+#
+# Do not use -m 0755 and let people choose whatever they expect by
+# setting umask.
+#
+# We cannot accept any implementation of `mkdir' that recognizes `-p'.
+# Some implementations (such as Solaris 8's) are not thread-safe: if a
+# parallel make tries to run `mkdir -p a/b' and `mkdir -p a/c'
+# concurrently, both version can detect that a/ is missing, but only
+# one can create it and the other will error out.  Consequently we
+# restrict ourselves to GNU make (using the --version option ensures
+# this.)
 AC_DEFUN([AM_PROG_MKDIR_P],
-[AC_PREREQ([2.60])dnl
-AC_REQUIRE([AC_PROG_MKDIR_P])dnl
-dnl Automake 1.8 to 1.9.6 used to define mkdir_p.  We now use MKDIR_P,
-dnl while keeping a definition of mkdir_p for backward compatibility.
-dnl @MKDIR_P@ is magic: AC_OUTPUT adjusts its value for each Makefile.
-dnl However we cannot define mkdir_p as $(MKDIR_P) for the sake of
-dnl Makefile.ins that do not define MKDIR_P, so we do our own
-dnl adjustment using top_builddir (which is defined more often than
-dnl MKDIR_P).
-AC_SUBST([mkdir_p], ["$MKDIR_P"])dnl
-case $mkdir_p in
-  [[\\/$]]* | ?:[[\\/]]*) ;;
-  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
-esac
-])
+[if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then
+  # We used to keeping the `.' as first argument, in order to
+  # allow $(mkdir_p) to be used without argument.  As in
+  #   $(mkdir_p) $(somedir)
+  # where $(somedir) is conditionally defined.  However this is wrong
+  # for two reasons:
+  #  1. if the package is installed by a user who cannot write `.'
+  #     make install will fail,
+  #  2. the above comment should most certainly read
+  #     $(mkdir_p) $(DESTDIR)$(somedir)
+  #     so it does not work when $(somedir) is undefined and
+  #     $(DESTDIR) is not.
+  #  To support the latter case, we have to write
+  #     test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir),
+  #  so the `.' trick is pointless.
+  mkdir_p='mkdir -p --'
+else
+  # On NextStep and OpenStep, the `mkdir' command does not
+  # recognize any option.  It will interpret all options as
+  # directories to create, and then abort because `.' already
+  # exists.
+  for d in ./-p ./--version;
+  do
+    test -d $d && rmdir $d
+  done
+  # $(mkinstalldirs) is defined by Automake if mkinstalldirs exists.
+  if test -f "$ac_aux_dir/mkinstalldirs"; then
+    mkdir_p='$(mkinstalldirs)'
+  else
+    mkdir_p='$(install_sh) -d'
+  fi
+fi
+AC_SUBST([mkdir_p])])
 
 # Helper functions for option handling.                     -*- Autoconf -*-
 
@@ -755,21 +749,9 @@ dnl Don't test for $cross_compiling = yes, because it might be `maybe'.
 if test "$cross_compiling" != no; then
   AC_CHECK_TOOL([STRIP], [strip], :)
 fi
-INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s"
 AC_SUBST([INSTALL_STRIP_PROGRAM])])
 
-# Copyright (C) 2006  Free Software Foundation, Inc.
-#
-# This file is free software; the Free Software Foundation
-# gives unlimited permission to copy and/or distribute it,
-# with or without modifications, as long as this notice is preserved.
-
-# _AM_SUBST_NOTMAKE(VARIABLE)
-# ---------------------------
-# Prevent Automake from outputting VARIABLE = @VARIABLE@ in Makefile.in.
-# This macro is traced by Automake.
-AC_DEFUN([_AM_SUBST_NOTMAKE])
-
 # Check how to create a tarball.                            -*- Autoconf -*-
 
 # Copyright (C) 2004, 2005  Free Software Foundation, Inc.
diff --git a/enet/config.guess b/enet/config.guess
new file mode 100755
index 0000000..77c7cba
--- /dev/null
+++ b/enet/config.guess
@@ -0,0 +1,1441 @@
+#! /bin/sh
+# Attempt to guess a canonical system name.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+
+timestamp='2004-08-13'
+
+# This file is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Originally written by Per Bothner <per at bothner.com>.
+# Please send patches to <config-patches at gnu.org>.  Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# This script attempts to guess a canonical system name similar to
+# config.sub.  If it succeeds, it prints the system name on stdout, and
+# exits with 0.  Otherwise, it exits with 1.
+#
+# The plan is that this can be called by configure scripts if you
+# don't specify an explicit build system type.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION]
+
+Output the configuration name of the system \`$me' is run on.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches at gnu.org>."
+
+version="\
+GNU config.guess ($timestamp)
+
+Originally written by Per Bothner.
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit 0 ;;
+    --version | -v )
+       echo "$version" ; exit 0 ;;
+    --help | --h* | -h )
+       echo "$usage"; exit 0 ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help" >&2
+       exit 1 ;;
+    * )
+       break ;;
+  esac
+done
+
+if test $# != 0; then
+  echo "$me: too many arguments$help" >&2
+  exit 1
+fi
+
+trap 'exit 1' 1 2 15
+
+# CC_FOR_BUILD -- compiler used by this script. Note that the use of a
+# compiler to aid in system detection is discouraged as it requires
+# temporary files to be created and, as you can see below, it is a
+# headache to deal with in a portable fashion.
+
+# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still
+# use `HOST_CC' if defined, but it is deprecated.
+
+# Portable tmp directory creation inspired by the Autoconf team.
+
+set_cc_for_build='
+trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ;
+trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ;
+: ${TMPDIR=/tmp} ;
+ { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } ||
+ { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } ||
+ { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } ||
+ { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ;
+dummy=$tmp/dummy ;
+tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ;
+case $CC_FOR_BUILD,$HOST_CC,$CC in
+ ,,)    echo "int x;" > $dummy.c ;
+	for c in cc gcc c89 c99 ; do
+	  if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then
+	     CC_FOR_BUILD="$c"; break ;
+	  fi ;
+	done ;
+	if test x"$CC_FOR_BUILD" = x ; then
+	  CC_FOR_BUILD=no_compiler_found ;
+	fi
+	;;
+ ,,*)   CC_FOR_BUILD=$CC ;;
+ ,*,*)  CC_FOR_BUILD=$HOST_CC ;;
+esac ;'
+
+# This is needed to find uname on a Pyramid OSx when run in the BSD universe.
+# (ghazi at noc.rutgers.edu 1994-08-24)
+if (test -f /.attbin/uname) >/dev/null 2>&1 ; then
+	PATH=$PATH:/.attbin ; export PATH
+fi
+
+UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown
+UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown
+UNAME_SYSTEM=`(uname -s) 2>/dev/null`  || UNAME_SYSTEM=unknown
+UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown
+
+# Note: order is significant - the case branches are not exclusive.
+
+case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
+    *:NetBSD:*:*)
+	# NetBSD (nbsd) targets should (where applicable) match one or
+	# more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*,
+	# *-*-netbsdecoff* and *-*-netbsd*.  For targets that recently
+	# switched to ELF, *-*-netbsd* would select the old
+	# object file format.  This provides both forward
+	# compatibility and a consistent mechanism for selecting the
+	# object file format.
+	#
+	# Note: NetBSD doesn't particularly care about the vendor
+	# portion of the name.  We always set it to "unknown".
+	sysctl="sysctl -n hw.machine_arch"
+	UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
+	    /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+	case "${UNAME_MACHINE_ARCH}" in
+	    armeb) machine=armeb-unknown ;;
+	    arm*) machine=arm-unknown ;;
+	    sh3el) machine=shl-unknown ;;
+	    sh3eb) machine=sh-unknown ;;
+	    *) machine=${UNAME_MACHINE_ARCH}-unknown ;;
+	esac
+	# The Operating System including object format, if it has switched
+	# to ELF recently, or will in the future.
+	case "${UNAME_MACHINE_ARCH}" in
+	    arm*|i386|m68k|ns32k|sh3*|sparc|vax)
+		eval $set_cc_for_build
+		if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
+			| grep __ELF__ >/dev/null
+		then
+		    # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout).
+		    # Return netbsd for either.  FIX?
+		    os=netbsd
+		else
+		    os=netbsdelf
+		fi
+		;;
+	    *)
+	        os=netbsd
+		;;
+	esac
+	# The OS release
+	# Debian GNU/NetBSD machines have a different userland, and
+	# thus, need a distinct triplet. However, they do not need
+	# kernel version information, so it can be replaced with a
+	# suitable tag, in the style of linux-gnu.
+	case "${UNAME_VERSION}" in
+	    Debian*)
+		release='-gnu'
+		;;
+	    *)
+		release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+		;;
+	esac
+	# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
+	# contains redundant information, the shorter form:
+	# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
+	echo "${machine}-${os}${release}"
+	exit 0 ;;
+    amd64:OpenBSD:*:*)
+	echo x86_64-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    amiga:OpenBSD:*:*)
+	echo m68k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    cats:OpenBSD:*:*)
+	echo arm-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    hp300:OpenBSD:*:*)
+	echo m68k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    luna88k:OpenBSD:*:*)
+    	echo m88k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    mac68k:OpenBSD:*:*)
+	echo m68k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    macppc:OpenBSD:*:*)
+	echo powerpc-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    mvme68k:OpenBSD:*:*)
+	echo m68k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    mvme88k:OpenBSD:*:*)
+	echo m88k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    mvmeppc:OpenBSD:*:*)
+	echo powerpc-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    sgi:OpenBSD:*:*)
+	echo mips64-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    sun3:OpenBSD:*:*)
+	echo m68k-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    *:OpenBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE}
+	exit 0 ;;
+    *:ekkoBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
+	exit 0 ;;
+    macppc:MirBSD:*:*)
+	echo powerppc-unknown-mirbsd${UNAME_RELEASE}
+	exit 0 ;;
+    *:MirBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
+	exit 0 ;;
+    alpha:OSF1:*:*)
+	case $UNAME_RELEASE in
+	*4.0)
+		UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'`
+		;;
+	*5.*)
+	        UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'`
+		;;
+	esac
+	# According to Compaq, /usr/sbin/psrinfo has been available on
+	# OSF/1 and Tru64 systems produced since 1995.  I hope that
+	# covers most systems running today.  This code pipes the CPU
+	# types through head -n 1, so we only detect the type of CPU 0.
+	ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^  The alpha \(.*\) processor.*$/\1/p' | head -n 1`
+	case "$ALPHA_CPU_TYPE" in
+	    "EV4 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV4.5 (21064)")
+		UNAME_MACHINE="alpha" ;;
+	    "LCA4 (21066/21068)")
+		UNAME_MACHINE="alpha" ;;
+	    "EV5 (21164)")
+		UNAME_MACHINE="alphaev5" ;;
+	    "EV5.6 (21164A)")
+		UNAME_MACHINE="alphaev56" ;;
+	    "EV5.6 (21164PC)")
+		UNAME_MACHINE="alphapca56" ;;
+	    "EV5.7 (21164PC)")
+		UNAME_MACHINE="alphapca57" ;;
+	    "EV6 (21264)")
+		UNAME_MACHINE="alphaev6" ;;
+	    "EV6.7 (21264A)")
+		UNAME_MACHINE="alphaev67" ;;
+	    "EV6.8CB (21264C)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8AL (21264B)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.8CX (21264D)")
+		UNAME_MACHINE="alphaev68" ;;
+	    "EV6.9A (21264/EV69A)")
+		UNAME_MACHINE="alphaev69" ;;
+	    "EV7 (21364)")
+		UNAME_MACHINE="alphaev7" ;;
+	    "EV7.9 (21364A)")
+		UNAME_MACHINE="alphaev79" ;;
+	esac
+	# A Pn.n version is a patched version.
+	# A Vn.n version is a released version.
+	# A Tn.n version is a released field test version.
+	# A Xn.n version is an unreleased experimental baselevel.
+	# 1.2 uses "1.2" for uname -r.
+	echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+	exit 0 ;;
+    Alpha\ *:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# Should we change UNAME_MACHINE based on the output of uname instead
+	# of the specific Alpha model?
+	echo alpha-pc-interix
+	exit 0 ;;
+    21064:Windows_NT:50:3)
+	echo alpha-dec-winnt3.5
+	exit 0 ;;
+    Amiga*:UNIX_System_V:4.0:*)
+	echo m68k-unknown-sysv4
+	exit 0;;
+    *:[Aa]miga[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-amigaos
+	exit 0 ;;
+    *:[Mm]orph[Oo][Ss]:*:*)
+	echo ${UNAME_MACHINE}-unknown-morphos
+	exit 0 ;;
+    *:OS/390:*:*)
+	echo i370-ibm-openedition
+	exit 0 ;;
+    *:OS400:*:*)
+        echo powerpc-ibm-os400
+	exit 0 ;;
+    arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*)
+	echo arm-acorn-riscix${UNAME_RELEASE}
+	exit 0;;
+    SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*)
+	echo hppa1.1-hitachi-hiuxmpp
+	exit 0;;
+    Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*)
+	# akee at wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE.
+	if test "`(/bin/universe) 2>/dev/null`" = att ; then
+		echo pyramid-pyramid-sysv3
+	else
+		echo pyramid-pyramid-bsd
+	fi
+	exit 0 ;;
+    NILE*:*:*:dcosx)
+	echo pyramid-pyramid-svr4
+	exit 0 ;;
+    DRS?6000:unix:4.0:6*)
+	echo sparc-icl-nx6
+	exit 0 ;;
+    DRS?6000:UNIX_SV:4.2*:7*)
+	case `/usr/bin/uname -p` in
+	    sparc) echo sparc-icl-nx7 && exit 0 ;;
+	esac ;;
+    sun4H:SunOS:5.*:*)
+	echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit 0 ;;
+    sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*)
+	echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit 0 ;;
+    i86pc:SunOS:5.*:*)
+	echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit 0 ;;
+    sun4*:SunOS:6*:*)
+	# According to config.sub, this is the proper way to canonicalize
+	# SunOS6.  Hard to guess exactly what SunOS6 will be like, but
+	# it's likely to be more like Solaris than SunOS4.
+	echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit 0 ;;
+    sun4*:SunOS:*:*)
+	case "`/usr/bin/arch -k`" in
+	    Series*|S4*)
+		UNAME_RELEASE=`uname -v`
+		;;
+	esac
+	# Japanese Language versions have a version number like `4.1.3-JL'.
+	echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'`
+	exit 0 ;;
+    sun3*:SunOS:*:*)
+	echo m68k-sun-sunos${UNAME_RELEASE}
+	exit 0 ;;
+    sun*:*:4.2BSD:*)
+	UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
+	test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+	case "`/bin/arch`" in
+	    sun3)
+		echo m68k-sun-sunos${UNAME_RELEASE}
+		;;
+	    sun4)
+		echo sparc-sun-sunos${UNAME_RELEASE}
+		;;
+	esac
+	exit 0 ;;
+    aushp:SunOS:*:*)
+	echo sparc-auspex-sunos${UNAME_RELEASE}
+	exit 0 ;;
+    # The situation for MiNT is a little confusing.  The machine name
+    # can be virtually everything (everything which is not
+    # "atarist" or "atariste" at least should have a processor
+    # > m68000).  The system name ranges from "MiNT" over "FreeMiNT"
+    # to the lowercase version "mint" (or "freemint").  Finally
+    # the system name "TOS" denotes a system which is actually not
+    # MiNT.  But MiNT is downward compatible to TOS, so this should
+    # be no problem.
+    atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+	exit 0 ;;
+    atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*)
+	echo m68k-atari-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*)
+        echo m68k-atari-mint${UNAME_RELEASE}
+	exit 0 ;;
+    milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*)
+        echo m68k-milan-mint${UNAME_RELEASE}
+        exit 0 ;;
+    hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*)
+        echo m68k-hades-mint${UNAME_RELEASE}
+        exit 0 ;;
+    *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*)
+        echo m68k-unknown-mint${UNAME_RELEASE}
+        exit 0 ;;
+    m68k:machten:*:*)
+	echo m68k-apple-machten${UNAME_RELEASE}
+	exit 0 ;;
+    powerpc:machten:*:*)
+	echo powerpc-apple-machten${UNAME_RELEASE}
+	exit 0 ;;
+    RISC*:Mach:*:*)
+	echo mips-dec-mach_bsd4.3
+	exit 0 ;;
+    RISC*:ULTRIX:*:*)
+	echo mips-dec-ultrix${UNAME_RELEASE}
+	exit 0 ;;
+    VAX*:ULTRIX*:*:*)
+	echo vax-dec-ultrix${UNAME_RELEASE}
+	exit 0 ;;
+    2020:CLIX:*:* | 2430:CLIX:*:*)
+	echo clipper-intergraph-clix${UNAME_RELEASE}
+	exit 0 ;;
+    mips:*:*:UMIPS | mips:*:*:RISCos)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+#ifdef __cplusplus
+#include <stdio.h>  /* for printf() prototype */
+	int main (int argc, char *argv[]) {
+#else
+	int main (argc, argv) int argc; char *argv[]; {
+#endif
+	#if defined (host_mips) && defined (MIPSEB)
+	#if defined (SYSTYPE_SYSV)
+	  printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_SVR4)
+	  printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0);
+	#endif
+	#if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD)
+	  printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0);
+	#endif
+	#endif
+	  exit (-1);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c \
+	  && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \
+	  && exit 0
+	echo mips-mips-riscos${UNAME_RELEASE}
+	exit 0 ;;
+    Motorola:PowerMAX_OS:*:*)
+	echo powerpc-motorola-powermax
+	exit 0 ;;
+    Motorola:*:4.3:PL8-*)
+	echo powerpc-harris-powermax
+	exit 0 ;;
+    Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*)
+	echo powerpc-harris-powermax
+	exit 0 ;;
+    Night_Hawk:Power_UNIX:*:*)
+	echo powerpc-harris-powerunix
+	exit 0 ;;
+    m88k:CX/UX:7*:*)
+	echo m88k-harris-cxux7
+	exit 0 ;;
+    m88k:*:4*:R4*)
+	echo m88k-motorola-sysv4
+	exit 0 ;;
+    m88k:*:3*:R3*)
+	echo m88k-motorola-sysv3
+	exit 0 ;;
+    AViiON:dgux:*:*)
+        # DG/UX returns AViiON for all architectures
+        UNAME_PROCESSOR=`/usr/bin/uname -p`
+	if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ]
+	then
+	    if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \
+	       [ ${TARGET_BINARY_INTERFACE}x = x ]
+	    then
+		echo m88k-dg-dgux${UNAME_RELEASE}
+	    else
+		echo m88k-dg-dguxbcs${UNAME_RELEASE}
+	    fi
+	else
+	    echo i586-dg-dgux${UNAME_RELEASE}
+	fi
+ 	exit 0 ;;
+    M88*:DolphinOS:*:*)	# DolphinOS (SVR3)
+	echo m88k-dolphin-sysv3
+	exit 0 ;;
+    M88*:*:R3*:*)
+	# Delta 88k system running SVR3
+	echo m88k-motorola-sysv3
+	exit 0 ;;
+    XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3)
+	echo m88k-tektronix-sysv3
+	exit 0 ;;
+    Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD)
+	echo m68k-tektronix-bsd
+	exit 0 ;;
+    *:IRIX*:*:*)
+	echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'`
+	exit 0 ;;
+    ????????:AIX?:[12].1:2)   # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX.
+	echo romp-ibm-aix      # uname -m gives an 8 hex-code CPU id
+	exit 0 ;;              # Note that: echo "'`uname -s`'" gives 'AIX '
+    i*86:AIX:*:*)
+	echo i386-ibm-aix
+	exit 0 ;;
+    ia64:AIX:*:*)
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${UNAME_MACHINE}-ibm-aix${IBM_REV}
+	exit 0 ;;
+    *:AIX:2:3)
+	if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then
+		eval $set_cc_for_build
+		sed 's/^		//' << EOF >$dummy.c
+		#include <sys/systemcfg.h>
+
+		main()
+			{
+			if (!__power_pc())
+				exit(1);
+			puts("powerpc-ibm-aix3.2.5");
+			exit(0);
+			}
+EOF
+		$CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+		echo rs6000-ibm-aix3.2.5
+	elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then
+		echo rs6000-ibm-aix3.2.4
+	else
+		echo rs6000-ibm-aix3.2
+	fi
+	exit 0 ;;
+    *:AIX:*:[45])
+	IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'`
+	if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then
+		IBM_ARCH=rs6000
+	else
+		IBM_ARCH=powerpc
+	fi
+	if [ -x /usr/bin/oslevel ] ; then
+		IBM_REV=`/usr/bin/oslevel`
+	else
+		IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
+	fi
+	echo ${IBM_ARCH}-ibm-aix${IBM_REV}
+	exit 0 ;;
+    *:AIX:*:*)
+	echo rs6000-ibm-aix
+	exit 0 ;;
+    ibmrt:4.4BSD:*|romp-ibm:BSD:*)
+	echo romp-ibm-bsd4.4
+	exit 0 ;;
+    ibmrt:*BSD:*|romp-ibm:BSD:*)            # covers RT/PC BSD and
+	echo romp-ibm-bsd${UNAME_RELEASE}   # 4.3 with uname added to
+	exit 0 ;;                           # report: romp-ibm BSD 4.3
+    *:BOSX:*:*)
+	echo rs6000-bull-bosx
+	exit 0 ;;
+    DPX/2?00:B.O.S.:*:*)
+	echo m68k-bull-sysv3
+	exit 0 ;;
+    9000/[34]??:4.3bsd:1.*:*)
+	echo m68k-hp-bsd
+	exit 0 ;;
+    hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*)
+	echo m68k-hp-bsd4.4
+	exit 0 ;;
+    9000/[34678]??:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	case "${UNAME_MACHINE}" in
+	    9000/31? )            HP_ARCH=m68000 ;;
+	    9000/[34]?? )         HP_ARCH=m68k ;;
+	    9000/[678][0-9][0-9])
+		if [ -x /usr/bin/getconf ]; then
+		    sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
+                    sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
+                    case "${sc_cpu_version}" in
+                      523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
+                      528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+                      532)                      # CPU_PA_RISC2_0
+                        case "${sc_kernel_bits}" in
+                          32) HP_ARCH="hppa2.0n" ;;
+                          64) HP_ARCH="hppa2.0w" ;;
+			  '') HP_ARCH="hppa2.0" ;;   # HP-UX 10.20
+                        esac ;;
+                    esac
+		fi
+		if [ "${HP_ARCH}" = "" ]; then
+		    eval $set_cc_for_build
+		    sed 's/^              //' << EOF >$dummy.c
+
+              #define _HPUX_SOURCE
+              #include <stdlib.h>
+              #include <unistd.h>
+
+              int main ()
+              {
+              #if defined(_SC_KERNEL_BITS)
+                  long bits = sysconf(_SC_KERNEL_BITS);
+              #endif
+                  long cpu  = sysconf (_SC_CPU_VERSION);
+
+                  switch (cpu)
+              	{
+              	case CPU_PA_RISC1_0: puts ("hppa1.0"); break;
+              	case CPU_PA_RISC1_1: puts ("hppa1.1"); break;
+              	case CPU_PA_RISC2_0:
+              #if defined(_SC_KERNEL_BITS)
+              	    switch (bits)
+              		{
+              		case 64: puts ("hppa2.0w"); break;
+              		case 32: puts ("hppa2.0n"); break;
+              		default: puts ("hppa2.0"); break;
+              		} break;
+              #else  /* !defined(_SC_KERNEL_BITS) */
+              	    puts ("hppa2.0"); break;
+              #endif
+              	default: puts ("hppa1.0"); break;
+              	}
+                  exit (0);
+              }
+EOF
+		    (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+		    test -z "$HP_ARCH" && HP_ARCH=hppa
+		fi ;;
+	esac
+	if [ ${HP_ARCH} = "hppa2.0w" ]
+	then
+	    # avoid double evaluation of $set_cc_for_build
+	    test -n "$CC_FOR_BUILD" || eval $set_cc_for_build
+	    if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null
+	    then
+		HP_ARCH="hppa2.0w"
+	    else
+		HP_ARCH="hppa64"
+	    fi
+	fi
+	echo ${HP_ARCH}-hp-hpux${HPUX_REV}
+	exit 0 ;;
+    ia64:HP-UX:*:*)
+	HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'`
+	echo ia64-hp-hpux${HPUX_REV}
+	exit 0 ;;
+    3050*:HI-UX:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#include <unistd.h>
+	int
+	main ()
+	{
+	  long cpu = sysconf (_SC_CPU_VERSION);
+	  /* The order matters, because CPU_IS_HP_MC68K erroneously returns
+	     true for CPU_PA_RISC1_0.  CPU_IS_PA_RISC returns correct
+	     results, however.  */
+	  if (CPU_IS_PA_RISC (cpu))
+	    {
+	      switch (cpu)
+		{
+		  case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break;
+		  case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break;
+		  default: puts ("hppa-hitachi-hiuxwe2"); break;
+		}
+	    }
+	  else if (CPU_IS_HP_MC68K (cpu))
+	    puts ("m68k-hitachi-hiuxwe2");
+	  else puts ("unknown-hitachi-hiuxwe2");
+	  exit (0);
+	}
+EOF
+	$CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0
+	echo unknown-hitachi-hiuxwe2
+	exit 0 ;;
+    9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* )
+	echo hppa1.1-hp-bsd
+	exit 0 ;;
+    9000/8??:4.3bsd:*:*)
+	echo hppa1.0-hp-bsd
+	exit 0 ;;
+    *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*)
+	echo hppa1.0-hp-mpeix
+	exit 0 ;;
+    hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* )
+	echo hppa1.1-hp-osf
+	exit 0 ;;
+    hp8??:OSF1:*:*)
+	echo hppa1.0-hp-osf
+	exit 0 ;;
+    i*86:OSF1:*:*)
+	if [ -x /usr/sbin/sysversion ] ; then
+	    echo ${UNAME_MACHINE}-unknown-osf1mk
+	else
+	    echo ${UNAME_MACHINE}-unknown-osf1
+	fi
+	exit 0 ;;
+    parisc*:Lites*:*:*)
+	echo hppa1.1-hp-lites
+	exit 0 ;;
+    C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*)
+	echo c1-convex-bsd
+        exit 0 ;;
+    C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+        exit 0 ;;
+    C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*)
+	echo c34-convex-bsd
+        exit 0 ;;
+    C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*)
+	echo c38-convex-bsd
+        exit 0 ;;
+    C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*)
+	echo c4-convex-bsd
+        exit 0 ;;
+    CRAY*Y-MP:*:*:*)
+	echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    CRAY*[A-Z]90:*:*:*)
+	echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \
+	| sed -e 's/CRAY.*\([A-Z]90\)/\1/' \
+	      -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \
+	      -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    CRAY*TS:*:*:*)
+	echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    CRAY*T3E:*:*:*)
+	echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    CRAY*SV1:*:*:*)
+	echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    *:UNICOS/mp:*:*)
+	echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
+	exit 0 ;;
+    F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
+	FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
+        echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+        exit 0 ;;
+    5000:UNIX_System_V:4.*:*)
+        FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+        FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+        echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
+	exit 0 ;;
+    i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
+	echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE}
+	exit 0 ;;
+    sparc*:BSD/OS:*:*)
+	echo sparc-unknown-bsdi${UNAME_RELEASE}
+	exit 0 ;;
+    *:BSD/OS:*:*)
+	echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE}
+	exit 0 ;;
+    *:FreeBSD:*:*)
+	echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+	exit 0 ;;
+    i*:CYGWIN*:*)
+	echo ${UNAME_MACHINE}-pc-cygwin
+	exit 0 ;;
+    i*:MINGW*:*)
+	echo ${UNAME_MACHINE}-pc-mingw32
+	exit 0 ;;
+    i*:PW*:*)
+	echo ${UNAME_MACHINE}-pc-pw32
+	exit 0 ;;
+    x86:Interix*:[34]*)
+	echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//'
+	exit 0 ;;
+    [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*)
+	echo i${UNAME_MACHINE}-pc-mks
+	exit 0 ;;
+    i*:Windows_NT*:* | Pentium*:Windows_NT*:*)
+	# How do we know it's Interix rather than the generic POSIX subsystem?
+	# It also conflicts with pre-2.0 versions of AT&T UWIN. Should we
+	# UNAME_MACHINE based on the output of uname instead of i386?
+	echo i586-pc-interix
+	exit 0 ;;
+    i*:UWIN*:*)
+	echo ${UNAME_MACHINE}-pc-uwin
+	exit 0 ;;
+    p*:CYGWIN*:*)
+	echo powerpcle-unknown-cygwin
+	exit 0 ;;
+    prep*:SunOS:5.*:*)
+	echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
+	exit 0 ;;
+    *:GNU:*:*)
+	# the GNU system
+	echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'`
+	exit 0 ;;
+    *:GNU/*:*:*)
+	# other systems with GNU libc and userland
+	echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu
+	exit 0 ;;
+    i*86:Minix:*:*)
+	echo ${UNAME_MACHINE}-pc-minix
+	exit 0 ;;
+    arm*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    cris:Linux:*:*)
+	echo cris-axis-linux-gnu
+	exit 0 ;;
+    ia64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    m32r*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    m68*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    mips:Linux:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#undef CPU
+	#undef mips
+	#undef mipsel
+	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+	CPU=mipsel
+	#else
+	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+	CPU=mips
+	#else
+	CPU=
+	#endif
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+	test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+	;;
+    mips64:Linux:*:*)
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#undef CPU
+	#undef mips64
+	#undef mips64el
+	#if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL)
+	CPU=mips64el
+	#else
+	#if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB)
+	CPU=mips64
+	#else
+	CPU=
+	#endif
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=`
+	test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0
+	;;
+    ppc:Linux:*:*)
+	echo powerpc-unknown-linux-gnu
+	exit 0 ;;
+    ppc64:Linux:*:*)
+	echo powerpc64-unknown-linux-gnu
+	exit 0 ;;
+    alpha:Linux:*:*)
+	case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in
+	  EV5)   UNAME_MACHINE=alphaev5 ;;
+	  EV56)  UNAME_MACHINE=alphaev56 ;;
+	  PCA56) UNAME_MACHINE=alphapca56 ;;
+	  PCA57) UNAME_MACHINE=alphapca56 ;;
+	  EV6)   UNAME_MACHINE=alphaev6 ;;
+	  EV67)  UNAME_MACHINE=alphaev67 ;;
+	  EV68*) UNAME_MACHINE=alphaev68 ;;
+        esac
+	objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null
+	if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi
+	echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC}
+	exit 0 ;;
+    parisc:Linux:*:* | hppa:Linux:*:*)
+	# Look for CPU level
+	case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in
+	  PA7*) echo hppa1.1-unknown-linux-gnu ;;
+	  PA8*) echo hppa2.0-unknown-linux-gnu ;;
+	  *)    echo hppa-unknown-linux-gnu ;;
+	esac
+	exit 0 ;;
+    parisc64:Linux:*:* | hppa64:Linux:*:*)
+	echo hppa64-unknown-linux-gnu
+	exit 0 ;;
+    s390:Linux:*:* | s390x:Linux:*:*)
+	echo ${UNAME_MACHINE}-ibm-linux
+	exit 0 ;;
+    sh64*:Linux:*:*)
+    	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    sh*:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    sparc:Linux:*:* | sparc64:Linux:*:*)
+	echo ${UNAME_MACHINE}-unknown-linux-gnu
+	exit 0 ;;
+    x86_64:Linux:*:*)
+	echo x86_64-unknown-linux-gnu
+	exit 0 ;;
+    i*86:Linux:*:*)
+	# The BFD linker knows what the default object file format is, so
+	# first see if it will tell us. cd to the root directory to prevent
+	# problems with other programs or directories called `ld' in the path.
+	# Set LC_ALL=C to ensure ld outputs messages in English.
+	ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \
+			 | sed -ne '/supported targets:/!d
+				    s/[ 	][ 	]*/ /g
+				    s/.*supported targets: *//
+				    s/ .*//
+				    p'`
+        case "$ld_supported_targets" in
+	  elf32-i386)
+		TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu"
+		;;
+	  a.out-i386-linux)
+		echo "${UNAME_MACHINE}-pc-linux-gnuaout"
+		exit 0 ;;
+	  coff-i386)
+		echo "${UNAME_MACHINE}-pc-linux-gnucoff"
+		exit 0 ;;
+	  "")
+		# Either a pre-BFD a.out linker (linux-gnuoldld) or
+		# one that does not give us useful --help.
+		echo "${UNAME_MACHINE}-pc-linux-gnuoldld"
+		exit 0 ;;
+	esac
+	# Determine whether the default compiler is a.out or elf
+	eval $set_cc_for_build
+	sed 's/^	//' << EOF >$dummy.c
+	#include <features.h>
+	#ifdef __ELF__
+	# ifdef __GLIBC__
+	#  if __GLIBC__ >= 2
+	LIBC=gnu
+	#  else
+	LIBC=gnulibc1
+	#  endif
+	# else
+	LIBC=gnulibc1
+	# endif
+	#else
+	#ifdef __INTEL_COMPILER
+	LIBC=gnu
+	#else
+	LIBC=gnuaout
+	#endif
+	#endif
+	#ifdef __dietlibc__
+	LIBC=dietlibc
+	#endif
+EOF
+	eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=`
+	test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0
+	test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0
+	;;
+    i*86:DYNIX/ptx:4*:*)
+	# ptx 4.0 does uname -s correctly, with DYNIX/ptx in there.
+	# earlier versions are messed up and put the nodename in both
+	# sysname and nodename.
+	echo i386-sequent-sysv4
+	exit 0 ;;
+    i*86:UNIX_SV:4.2MP:2.*)
+        # Unixware is an offshoot of SVR4, but it has its own version
+        # number series starting with 2...
+        # I am not positive that other SVR4 systems won't match this,
+	# I just have to hope.  -- rms.
+        # Use sysv4.2uw... so that sysv4* matches it.
+	echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION}
+	exit 0 ;;
+    i*86:OS/2:*:*)
+	# If we were able to find `uname', then EMX Unix compatibility
+	# is probably installed.
+	echo ${UNAME_MACHINE}-pc-os2-emx
+	exit 0 ;;
+    i*86:XTS-300:*:STOP)
+	echo ${UNAME_MACHINE}-unknown-stop
+	exit 0 ;;
+    i*86:atheos:*:*)
+	echo ${UNAME_MACHINE}-unknown-atheos
+	exit 0 ;;
+	i*86:syllable:*:*)
+	echo ${UNAME_MACHINE}-pc-syllable
+	exit 0 ;;
+    i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*)
+	echo i386-unknown-lynxos${UNAME_RELEASE}
+	exit 0 ;;
+    i*86:*DOS:*:*)
+	echo ${UNAME_MACHINE}-pc-msdosdjgpp
+	exit 0 ;;
+    i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*)
+	UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'`
+	if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then
+		echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL}
+	else
+		echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL}
+	fi
+	exit 0 ;;
+    i*86:*:5:[78]*)
+	case `/bin/uname -X | grep "^Machine"` in
+	    *486*)	     UNAME_MACHINE=i486 ;;
+	    *Pentium)	     UNAME_MACHINE=i586 ;;
+	    *Pent*|*Celeron) UNAME_MACHINE=i686 ;;
+	esac
+	echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}
+	exit 0 ;;
+    i*86:*:3.2:*)
+	if test -f /usr/options/cb.name; then
+		UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name`
+		echo ${UNAME_MACHINE}-pc-isc$UNAME_REL
+	elif /bin/uname -X 2>/dev/null >/dev/null ; then
+		UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')`
+		(/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486
+		(/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \
+			&& UNAME_MACHINE=i586
+		(/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		(/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \
+			&& UNAME_MACHINE=i686
+		echo ${UNAME_MACHINE}-pc-sco$UNAME_REL
+	else
+		echo ${UNAME_MACHINE}-pc-sysv32
+	fi
+	exit 0 ;;
+    pc:*:*:*)
+	# Left here for compatibility:
+        # uname -m prints for DJGPP always 'pc', but it prints nothing about
+        # the processor, so we play safe by assuming i386.
+	echo i386-pc-msdosdjgpp
+        exit 0 ;;
+    Intel:Mach:3*:*)
+	echo i386-pc-mach3
+	exit 0 ;;
+    paragon:*:*:*)
+	echo i860-intel-osf1
+	exit 0 ;;
+    i860:*:4.*:*) # i860-SVR4
+	if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then
+	  echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4
+	else # Add other i860-SVR4 vendors below as they are discovered.
+	  echo i860-unknown-sysv${UNAME_RELEASE}  # Unknown i860-SVR4
+	fi
+	exit 0 ;;
+    mini*:CTIX:SYS*5:*)
+	# "miniframe"
+	echo m68010-convergent-sysv
+	exit 0 ;;
+    mc68k:UNIX:SYSTEM5:3.51m)
+	echo m68k-convergent-sysv
+	exit 0 ;;
+    M680?0:D-NIX:5.3:*)
+	echo m68k-diab-dnix
+	exit 0 ;;
+    M68*:*:R3V[5678]*:*)
+	test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;;
+    3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0)
+	OS_REL=''
+	test -r /etc/.relid \
+	&& OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid`
+	/bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+	  && echo i486-ncr-sysv4.3${OS_REL} && exit 0
+	/bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \
+	  && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;;
+    3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*)
+        /bin/uname -p 2>/dev/null | grep 86 >/dev/null \
+          && echo i486-ncr-sysv4 && exit 0 ;;
+    m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*)
+	echo m68k-unknown-lynxos${UNAME_RELEASE}
+	exit 0 ;;
+    mc68030:UNIX_System_V:4.*:*)
+	echo m68k-atari-sysv4
+	exit 0 ;;
+    TSUNAMI:LynxOS:2.*:*)
+	echo sparc-unknown-lynxos${UNAME_RELEASE}
+	exit 0 ;;
+    rs6000:LynxOS:2.*:*)
+	echo rs6000-unknown-lynxos${UNAME_RELEASE}
+	exit 0 ;;
+    PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*)
+	echo powerpc-unknown-lynxos${UNAME_RELEASE}
+	exit 0 ;;
+    SM[BE]S:UNIX_SV:*:*)
+	echo mips-dde-sysv${UNAME_RELEASE}
+	exit 0 ;;
+    RM*:ReliantUNIX-*:*:*)
+	echo mips-sni-sysv4
+	exit 0 ;;
+    RM*:SINIX-*:*:*)
+	echo mips-sni-sysv4
+	exit 0 ;;
+    *:SINIX-*:*:*)
+	if uname -p 2>/dev/null >/dev/null ; then
+		UNAME_MACHINE=`(uname -p) 2>/dev/null`
+		echo ${UNAME_MACHINE}-sni-sysv4
+	else
+		echo ns32k-sni-sysv
+	fi
+	exit 0 ;;
+    PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort
+                      # says <Richard.M.Bartel at ccMail.Census.GOV>
+        echo i586-unisys-sysv4
+        exit 0 ;;
+    *:UNIX_System_V:4*:FTX*)
+	# From Gerald Hewes <hewes at openmarket.com>.
+	# How about differentiating between stratus architectures? -djm
+	echo hppa1.1-stratus-sysv4
+	exit 0 ;;
+    *:*:*:FTX*)
+	# From seanf at swdc.stratus.com.
+	echo i860-stratus-sysv4
+	exit 0 ;;
+    *:VOS:*:*)
+	# From Paul.Green at stratus.com.
+	echo hppa1.1-stratus-vos
+	exit 0 ;;
+    mc68*:A/UX:*:*)
+	echo m68k-apple-aux${UNAME_RELEASE}
+	exit 0 ;;
+    news*:NEWS-OS:6*:*)
+	echo mips-sony-newsos6
+	exit 0 ;;
+    R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*)
+	if [ -d /usr/nec ]; then
+	        echo mips-nec-sysv${UNAME_RELEASE}
+	else
+	        echo mips-unknown-sysv${UNAME_RELEASE}
+	fi
+        exit 0 ;;
+    BeBox:BeOS:*:*)	# BeOS running on hardware made by Be, PPC only.
+	echo powerpc-be-beos
+	exit 0 ;;
+    BeMac:BeOS:*:*)	# BeOS running on Mac or Mac clone, PPC only.
+	echo powerpc-apple-beos
+	exit 0 ;;
+    BePC:BeOS:*:*)	# BeOS running on Intel PC compatible.
+	echo i586-pc-beos
+	exit 0 ;;
+    SX-4:SUPER-UX:*:*)
+	echo sx4-nec-superux${UNAME_RELEASE}
+	exit 0 ;;
+    SX-5:SUPER-UX:*:*)
+	echo sx5-nec-superux${UNAME_RELEASE}
+	exit 0 ;;
+    SX-6:SUPER-UX:*:*)
+	echo sx6-nec-superux${UNAME_RELEASE}
+	exit 0 ;;
+    Power*:Rhapsody:*:*)
+	echo powerpc-apple-rhapsody${UNAME_RELEASE}
+	exit 0 ;;
+    *:Rhapsody:*:*)
+	echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE}
+	exit 0 ;;
+    *:Darwin:*:*)
+	UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown
+	case $UNAME_PROCESSOR in
+	    *86) UNAME_PROCESSOR=i686 ;;
+	    unknown) UNAME_PROCESSOR=powerpc ;;
+	esac
+	echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
+	exit 0 ;;
+    *:procnto*:*:* | *:QNX:[0123456789]*:*)
+	UNAME_PROCESSOR=`uname -p`
+	if test "$UNAME_PROCESSOR" = "x86"; then
+		UNAME_PROCESSOR=i386
+		UNAME_MACHINE=pc
+	fi
+	echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE}
+	exit 0 ;;
+    *:QNX:*:4*)
+	echo i386-pc-qnx
+	exit 0 ;;
+    NSR-?:NONSTOP_KERNEL:*:*)
+	echo nsr-tandem-nsk${UNAME_RELEASE}
+	exit 0 ;;
+    *:NonStop-UX:*:*)
+	echo mips-compaq-nonstopux
+	exit 0 ;;
+    BS2000:POSIX*:*:*)
+	echo bs2000-siemens-sysv
+	exit 0 ;;
+    DS/*:UNIX_System_V:*:*)
+	echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE}
+	exit 0 ;;
+    *:Plan9:*:*)
+	# "uname -m" is not consistent, so use $cputype instead. 386
+	# is converted to i386 for consistency with other x86
+	# operating systems.
+	if test "$cputype" = "386"; then
+	    UNAME_MACHINE=i386
+	else
+	    UNAME_MACHINE="$cputype"
+	fi
+	echo ${UNAME_MACHINE}-unknown-plan9
+	exit 0 ;;
+    *:TOPS-10:*:*)
+	echo pdp10-unknown-tops10
+	exit 0 ;;
+    *:TENEX:*:*)
+	echo pdp10-unknown-tenex
+	exit 0 ;;
+    KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*)
+	echo pdp10-dec-tops20
+	exit 0 ;;
+    XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*)
+	echo pdp10-xkl-tops20
+	exit 0 ;;
+    *:TOPS-20:*:*)
+	echo pdp10-unknown-tops20
+	exit 0 ;;
+    *:ITS:*:*)
+	echo pdp10-unknown-its
+	exit 0 ;;
+    SEI:*:*:SEIUX)
+        echo mips-sei-seiux${UNAME_RELEASE}
+	exit 0 ;;
+    *:DragonFly:*:*)
+	echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`
+	exit 0 ;;
+    *:*VMS:*:*)
+    	UNAME_MACHINE=`(uname -p) 2>/dev/null`
+	case "${UNAME_MACHINE}" in
+	    A*) echo alpha-dec-vms && exit 0 ;;
+	    I*) echo ia64-dec-vms && exit 0 ;;
+	    V*) echo vax-dec-vms && exit 0 ;;
+	esac
+esac
+
+#echo '(No uname command or uname output not recognized.)' 1>&2
+#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2
+
+eval $set_cc_for_build
+cat >$dummy.c <<EOF
+#ifdef _SEQUENT_
+# include <sys/types.h>
+# include <sys/utsname.h>
+#endif
+main ()
+{
+#if defined (sony)
+#if defined (MIPSEB)
+  /* BFD wants "bsd" instead of "newsos".  Perhaps BFD should be changed,
+     I don't know....  */
+  printf ("mips-sony-bsd\n"); exit (0);
+#else
+#include <sys/param.h>
+  printf ("m68k-sony-newsos%s\n",
+#ifdef NEWSOS4
+          "4"
+#else
+	  ""
+#endif
+         ); exit (0);
+#endif
+#endif
+
+#if defined (__arm) && defined (__acorn) && defined (__unix)
+  printf ("arm-acorn-riscix"); exit (0);
+#endif
+
+#if defined (hp300) && !defined (hpux)
+  printf ("m68k-hp-bsd\n"); exit (0);
+#endif
+
+#if defined (NeXT)
+#if !defined (__ARCHITECTURE__)
+#define __ARCHITECTURE__ "m68k"
+#endif
+  int version;
+  version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`;
+  if (version < 4)
+    printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version);
+  else
+    printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version);
+  exit (0);
+#endif
+
+#if defined (MULTIMAX) || defined (n16)
+#if defined (UMAXV)
+  printf ("ns32k-encore-sysv\n"); exit (0);
+#else
+#if defined (CMU)
+  printf ("ns32k-encore-mach\n"); exit (0);
+#else
+  printf ("ns32k-encore-bsd\n"); exit (0);
+#endif
+#endif
+#endif
+
+#if defined (__386BSD__)
+  printf ("i386-pc-bsd\n"); exit (0);
+#endif
+
+#if defined (sequent)
+#if defined (i386)
+  printf ("i386-sequent-dynix\n"); exit (0);
+#endif
+#if defined (ns32000)
+  printf ("ns32k-sequent-dynix\n"); exit (0);
+#endif
+#endif
+
+#if defined (_SEQUENT_)
+    struct utsname un;
+
+    uname(&un);
+
+    if (strncmp(un.version, "V2", 2) == 0) {
+	printf ("i386-sequent-ptx2\n"); exit (0);
+    }
+    if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */
+	printf ("i386-sequent-ptx1\n"); exit (0);
+    }
+    printf ("i386-sequent-ptx\n"); exit (0);
+
+#endif
+
+#if defined (vax)
+# if !defined (ultrix)
+#  include <sys/param.h>
+#  if defined (BSD)
+#   if BSD == 43
+      printf ("vax-dec-bsd4.3\n"); exit (0);
+#   else
+#    if BSD == 199006
+      printf ("vax-dec-bsd4.3reno\n"); exit (0);
+#    else
+      printf ("vax-dec-bsd\n"); exit (0);
+#    endif
+#   endif
+#  else
+    printf ("vax-dec-bsd\n"); exit (0);
+#  endif
+# else
+    printf ("vax-dec-ultrix\n"); exit (0);
+# endif
+#endif
+
+#if defined (alliant) && defined (i860)
+  printf ("i860-alliant-bsd\n"); exit (0);
+#endif
+
+  exit (1);
+}
+EOF
+
+$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0
+
+# Apollos put the system type in the environment.
+
+test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; }
+
+# Convex versions that predate uname can use getsysinfo(1)
+
+if [ -x /usr/convex/getsysinfo ]
+then
+    case `getsysinfo -f cpu_type` in
+    c1*)
+	echo c1-convex-bsd
+	exit 0 ;;
+    c2*)
+	if getsysinfo -f scalar_acc
+	then echo c32-convex-bsd
+	else echo c2-convex-bsd
+	fi
+	exit 0 ;;
+    c34*)
+	echo c34-convex-bsd
+	exit 0 ;;
+    c38*)
+	echo c38-convex-bsd
+	exit 0 ;;
+    c4*)
+	echo c4-convex-bsd
+	exit 0 ;;
+    esac
+fi
+
+cat >&2 <<EOF
+$0: unable to guess system type
+
+This script, last modified $timestamp, has failed to recognize
+the operating system you are using. It is advised that you
+download the most up to date version of the config scripts from
+
+    ftp://ftp.gnu.org/pub/gnu/config/
+
+If the version you run ($0) is already up to date, please
+send the following data and any information you think might be
+pertinent to <config-patches at gnu.org> in order to provide the needed
+information to handle your system.
+
+config.guess timestamp = $timestamp
+
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null`
+
+hostinfo               = `(hostinfo) 2>/dev/null`
+/bin/universe          = `(/bin/universe) 2>/dev/null`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null`
+/bin/arch              = `(/bin/arch) 2>/dev/null`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null`
+
+UNAME_MACHINE = ${UNAME_MACHINE}
+UNAME_RELEASE = ${UNAME_RELEASE}
+UNAME_SYSTEM  = ${UNAME_SYSTEM}
+UNAME_VERSION = ${UNAME_VERSION}
+EOF
+
+exit 1
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/enet/config.sub b/enet/config.sub
new file mode 100755
index 0000000..ac6de98
--- /dev/null
+++ b/enet/config.sub
@@ -0,0 +1,1552 @@
+#! /bin/sh
+# Configuration validation subroutine script.
+#   Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
+#   2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+
+timestamp='2004-06-24'
+
+# This file is (in principle) common to ALL GNU software.
+# The presence of a machine in this file suggests that SOME GNU software
+# can handle that machine.  It does not imply ALL GNU software can.
+#
+# This file is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Please send patches to <config-patches at gnu.org>.  Submit a context
+# diff and a properly formatted ChangeLog entry.
+#
+# Configuration subroutine to validate and canonicalize a configuration type.
+# Supply the specified configuration type as an argument.
+# If it is invalid, we print an error message on stderr and exit with code 1.
+# Otherwise, we print the canonical config type on stdout and succeed.
+
+# This file is supposed to be the same for all GNU packages
+# and recognize all the CPU types, system types and aliases
+# that are meaningful with *any* GNU software.
+# Each package is responsible for reporting which valid configurations
+# it does not support.  The user should be able to distinguish
+# a failure to support a valid configuration from a meaningless
+# configuration.
+
+# The goal of this file is to map all the various variations of a given
+# machine specification into a single specification in the form:
+#	CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
+# or in some cases, the newer four-part form:
+#	CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
+# It is wrong to echo any other type of specification.
+
+me=`echo "$0" | sed -e 's,.*/,,'`
+
+usage="\
+Usage: $0 [OPTION] CPU-MFR-OPSYS
+       $0 [OPTION] ALIAS
+
+Canonicalize a configuration name.
+
+Operation modes:
+  -h, --help         print this help, then exit
+  -t, --time-stamp   print date of last modification, then exit
+  -v, --version      print version number, then exit
+
+Report bugs and patches to <config-patches at gnu.org>."
+
+version="\
+GNU config.sub ($timestamp)
+
+Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004
+Free Software Foundation, Inc.
+
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
+
+help="
+Try \`$me --help' for more information."
+
+# Parse command line
+while test $# -gt 0 ; do
+  case $1 in
+    --time-stamp | --time* | -t )
+       echo "$timestamp" ; exit 0 ;;
+    --version | -v )
+       echo "$version" ; exit 0 ;;
+    --help | --h* | -h )
+       echo "$usage"; exit 0 ;;
+    -- )     # Stop option processing
+       shift; break ;;
+    - )	# Use stdin as input.
+       break ;;
+    -* )
+       echo "$me: invalid option $1$help"
+       exit 1 ;;
+
+    *local*)
+       # First pass through any local machine types.
+       echo $1
+       exit 0;;
+
+    * )
+       break ;;
+  esac
+done
+
+case $# in
+ 0) echo "$me: missing argument$help" >&2
+    exit 1;;
+ 1) ;;
+ *) echo "$me: too many arguments$help" >&2
+    exit 1;;
+esac
+
+# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any).
+# Here we must recognize all the valid KERNEL-OS combinations.
+maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
+case $maybe_os in
+  nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \
+  kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*)
+    os=-$maybe_os
+    basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
+    ;;
+  *)
+    basic_machine=`echo $1 | sed 's/-[^-]*$//'`
+    if [ $basic_machine != $1 ]
+    then os=`echo $1 | sed 's/.*-/-/'`
+    else os=; fi
+    ;;
+esac
+
+### Let's recognize common machines as not being operating systems so
+### that things like config.sub decstation-3100 work.  We also
+### recognize some manufacturers as not being operating systems, so we
+### can provide default operating systems below.
+case $os in
+	-sun*os*)
+		# Prevent following clause from handling this invalid input.
+		;;
+	-dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \
+	-att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \
+	-unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \
+	-convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\
+	-c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \
+	-harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \
+	-apple | -axis | -knuth | -cray)
+		os=
+		basic_machine=$1
+		;;
+	-sim | -cisco | -oki | -wec | -winbond)
+		os=
+		basic_machine=$1
+		;;
+	-scout)
+		;;
+	-wrs)
+		os=-vxworks
+		basic_machine=$1
+		;;
+	-chorusos*)
+		os=-chorusos
+		basic_machine=$1
+		;;
+ 	-chorusrdb)
+ 		os=-chorusrdb
+		basic_machine=$1
+ 		;;
+	-hiux*)
+		os=-hiuxwe2
+		;;
+	-sco5)
+		os=-sco3.2v5
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco4)
+		os=-sco3.2v4
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2.[4-9]*)
+		os=`echo $os | sed -e 's/sco3.2./sco3.2v/'`
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco3.2v[4-9]*)
+		# Don't forget version if it is 3.2v4 or newer.
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-sco*)
+		os=-sco3.2v2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-udk*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-isc)
+		os=-isc2.2
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-clix*)
+		basic_machine=clipper-intergraph
+		;;
+	-isc*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'`
+		;;
+	-lynx*)
+		os=-lynxos
+		;;
+	-ptx*)
+		basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'`
+		;;
+	-windowsnt*)
+		os=`echo $os | sed -e 's/windowsnt/winnt/'`
+		;;
+	-psos*)
+		os=-psos
+		;;
+	-mint | -mint[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+esac
+
+# Decode aliases for certain CPU-COMPANY combinations.
+case $basic_machine in
+	# Recognize the basic CPU types without company name.
+	# Some are omitted here because they have special meanings below.
+	1750a | 580 \
+	| a29k \
+	| alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \
+	| alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \
+	| am33_2.0 \
+	| arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \
+	| c4x | clipper \
+	| d10v | d30v | dlx | dsp16xx \
+	| fr30 | frv \
+	| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
+	| i370 | i860 | i960 | ia64 \
+	| ip2k | iq2000 \
+	| m32r | m32rle | m68000 | m68k | m88k | mcore \
+	| mips | mipsbe | mipseb | mipsel | mipsle \
+	| mips16 \
+	| mips64 | mips64el \
+	| mips64vr | mips64vrel \
+	| mips64orion | mips64orionel \
+	| mips64vr4100 | mips64vr4100el \
+	| mips64vr4300 | mips64vr4300el \
+	| mips64vr5000 | mips64vr5000el \
+	| mipsisa32 | mipsisa32el \
+	| mipsisa32r2 | mipsisa32r2el \
+	| mipsisa64 | mipsisa64el \
+	| mipsisa64r2 | mipsisa64r2el \
+	| mipsisa64sb1 | mipsisa64sb1el \
+	| mipsisa64sr71k | mipsisa64sr71kel \
+	| mipstx39 | mipstx39el \
+	| mn10200 | mn10300 \
+	| msp430 \
+	| ns16k | ns32k \
+	| openrisc | or32 \
+	| pdp10 | pdp11 | pj | pjl \
+	| powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \
+	| pyramid \
+	| sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \
+	| sh64 | sh64le \
+	| sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv8 | sparcv9 | sparcv9b \
+	| strongarm \
+	| tahoe | thumb | tic4x | tic80 | tron \
+	| v850 | v850e \
+	| we32k \
+	| x86 | xscale | xstormy16 | xtensa \
+	| z8k)
+		basic_machine=$basic_machine-unknown
+		;;
+	m6811 | m68hc11 | m6812 | m68hc12)
+		# Motorola 68HC11/12.
+		basic_machine=$basic_machine-unknown
+		os=-none
+		;;
+	m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k)
+		;;
+
+	# We use `pc' rather than `unknown'
+	# because (1) that's what they normally are, and
+	# (2) the word "unknown" tends to confuse beginning users.
+	i*86 | x86_64)
+	  basic_machine=$basic_machine-pc
+	  ;;
+	# Object if more than one company name word.
+	*-*-*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+	# Recognize the basic CPU types with company name.
+	580-* \
+	| a29k-* \
+	| alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \
+	| alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \
+	| alphapca5[67]-* | alpha64pca5[67]-* | arc-* \
+	| arm-*  | armbe-* | armle-* | armeb-* | armv*-* \
+	| avr-* \
+	| bs2000-* \
+	| c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \
+	| clipper-* | craynv-* | cydra-* \
+	| d10v-* | d30v-* | dlx-* \
+	| elxsi-* \
+	| f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \
+	| h8300-* | h8500-* \
+	| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
+	| i*86-* | i860-* | i960-* | ia64-* \
+	| ip2k-* | iq2000-* \
+	| m32r-* | m32rle-* \
+	| m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \
+	| m88110-* | m88k-* | mcore-* \
+	| mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \
+	| mips16-* \
+	| mips64-* | mips64el-* \
+	| mips64vr-* | mips64vrel-* \
+	| mips64orion-* | mips64orionel-* \
+	| mips64vr4100-* | mips64vr4100el-* \
+	| mips64vr4300-* | mips64vr4300el-* \
+	| mips64vr5000-* | mips64vr5000el-* \
+	| mipsisa32-* | mipsisa32el-* \
+	| mipsisa32r2-* | mipsisa32r2el-* \
+	| mipsisa64-* | mipsisa64el-* \
+	| mipsisa64r2-* | mipsisa64r2el-* \
+	| mipsisa64sb1-* | mipsisa64sb1el-* \
+	| mipsisa64sr71k-* | mipsisa64sr71kel-* \
+	| mipstx39-* | mipstx39el-* \
+	| mmix-* \
+	| msp430-* \
+	| none-* | np1-* | ns16k-* | ns32k-* \
+	| orion-* \
+	| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
+	| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \
+	| pyramid-* \
+	| romp-* | rs6000-* \
+	| sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \
+	| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
+	| sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \
+	| sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \
+	| tahoe-* | thumb-* \
+	| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
+	| tron-* \
+	| v850-* | v850e-* | vax-* \
+	| we32k-* \
+	| x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \
+	| xtensa-* \
+	| ymp-* \
+	| z8k-*)
+		;;
+	# Recognize the various machine names and aliases which stand
+	# for a CPU type and a company and sometimes even an OS.
+	386bsd)
+		basic_machine=i386-unknown
+		os=-bsd
+		;;
+	3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc)
+		basic_machine=m68000-att
+		;;
+	3b*)
+		basic_machine=we32k-att
+		;;
+	a29khif)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+    	abacus)
+		basic_machine=abacus-unknown
+		;;
+	adobe68k)
+		basic_machine=m68010-adobe
+		os=-scout
+		;;
+	alliant | fx80)
+		basic_machine=fx80-alliant
+		;;
+	altos | altos3068)
+		basic_machine=m68k-altos
+		;;
+	am29k)
+		basic_machine=a29k-none
+		os=-bsd
+		;;
+	amd64)
+		basic_machine=x86_64-pc
+		;;
+	amd64-*)
+		basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	amdahl)
+		basic_machine=580-amdahl
+		os=-sysv
+		;;
+	amiga | amiga-*)
+		basic_machine=m68k-unknown
+		;;
+	amigaos | amigados)
+		basic_machine=m68k-unknown
+		os=-amigaos
+		;;
+	amigaunix | amix)
+		basic_machine=m68k-unknown
+		os=-sysv4
+		;;
+	apollo68)
+		basic_machine=m68k-apollo
+		os=-sysv
+		;;
+	apollo68bsd)
+		basic_machine=m68k-apollo
+		os=-bsd
+		;;
+	aux)
+		basic_machine=m68k-apple
+		os=-aux
+		;;
+	balance)
+		basic_machine=ns32k-sequent
+		os=-dynix
+		;;
+	c90)
+		basic_machine=c90-cray
+		os=-unicos
+		;;
+	convex-c1)
+		basic_machine=c1-convex
+		os=-bsd
+		;;
+	convex-c2)
+		basic_machine=c2-convex
+		os=-bsd
+		;;
+	convex-c32)
+		basic_machine=c32-convex
+		os=-bsd
+		;;
+	convex-c34)
+		basic_machine=c34-convex
+		os=-bsd
+		;;
+	convex-c38)
+		basic_machine=c38-convex
+		os=-bsd
+		;;
+	cray | j90)
+		basic_machine=j90-cray
+		os=-unicos
+		;;
+	craynv)
+		basic_machine=craynv-cray
+		os=-unicosmp
+		;;
+	cr16c)
+		basic_machine=cr16c-unknown
+		os=-elf
+		;;
+	crds | unos)
+		basic_machine=m68k-crds
+		;;
+	cris | cris-* | etrax*)
+		basic_machine=cris-axis
+		;;
+	crx)
+		basic_machine=crx-unknown
+		os=-elf
+		;;
+	da30 | da30-*)
+		basic_machine=m68k-da30
+		;;
+	decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn)
+		basic_machine=mips-dec
+		;;
+	decsystem10* | dec10*)
+		basic_machine=pdp10-dec
+		os=-tops10
+		;;
+	decsystem20* | dec20*)
+		basic_machine=pdp10-dec
+		os=-tops20
+		;;
+	delta | 3300 | motorola-3300 | motorola-delta \
+	      | 3300-motorola | delta-motorola)
+		basic_machine=m68k-motorola
+		;;
+	delta88)
+		basic_machine=m88k-motorola
+		os=-sysv3
+		;;
+	dpx20 | dpx20-*)
+		basic_machine=rs6000-bull
+		os=-bosx
+		;;
+	dpx2* | dpx2*-bull)
+		basic_machine=m68k-bull
+		os=-sysv3
+		;;
+	ebmon29k)
+		basic_machine=a29k-amd
+		os=-ebmon
+		;;
+	elxsi)
+		basic_machine=elxsi-elxsi
+		os=-bsd
+		;;
+	encore | umax | mmax)
+		basic_machine=ns32k-encore
+		;;
+	es1800 | OSE68k | ose68k | ose | OSE)
+		basic_machine=m68k-ericsson
+		os=-ose
+		;;
+	fx2800)
+		basic_machine=i860-alliant
+		;;
+	genix)
+		basic_machine=ns32k-ns
+		;;
+	gmicro)
+		basic_machine=tron-gmicro
+		os=-sysv
+		;;
+	go32)
+		basic_machine=i386-pc
+		os=-go32
+		;;
+	h3050r* | hiux*)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	h8300hms)
+		basic_machine=h8300-hitachi
+		os=-hms
+		;;
+	h8300xray)
+		basic_machine=h8300-hitachi
+		os=-xray
+		;;
+	h8500hms)
+		basic_machine=h8500-hitachi
+		os=-hms
+		;;
+	harris)
+		basic_machine=m88k-harris
+		os=-sysv3
+		;;
+	hp300-*)
+		basic_machine=m68k-hp
+		;;
+	hp300bsd)
+		basic_machine=m68k-hp
+		os=-bsd
+		;;
+	hp300hpux)
+		basic_machine=m68k-hp
+		os=-hpux
+		;;
+	hp3k9[0-9][0-9] | hp9[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k2[0-9][0-9] | hp9k31[0-9])
+		basic_machine=m68000-hp
+		;;
+	hp9k3[2-9][0-9])
+		basic_machine=m68k-hp
+		;;
+	hp9k6[0-9][0-9] | hp6[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hp9k7[0-79][0-9] | hp7[0-79][0-9])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k78[0-9] | hp78[0-9])
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893)
+		# FIXME: really hppa2.0-hp
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][13679] | hp8[0-9][13679])
+		basic_machine=hppa1.1-hp
+		;;
+	hp9k8[0-9][0-9] | hp8[0-9][0-9])
+		basic_machine=hppa1.0-hp
+		;;
+	hppa-next)
+		os=-nextstep3
+		;;
+	hppaosf)
+		basic_machine=hppa1.1-hp
+		os=-osf
+		;;
+	hppro)
+		basic_machine=hppa1.1-hp
+		os=-proelf
+		;;
+	i370-ibm* | ibm*)
+		basic_machine=i370-ibm
+		;;
+# I'm not sure what "Sysv32" means.  Should this be sysv3.2?
+	i*86v32)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv32
+		;;
+	i*86v4*)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv4
+		;;
+	i*86v)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-sysv
+		;;
+	i*86sol2)
+		basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'`
+		os=-solaris2
+		;;
+	i386mach)
+		basic_machine=i386-mach
+		os=-mach
+		;;
+	i386-vsta | vsta)
+		basic_machine=i386-unknown
+		os=-vsta
+		;;
+	iris | iris4d)
+		basic_machine=mips-sgi
+		case $os in
+		    -irix*)
+			;;
+		    *)
+			os=-irix4
+			;;
+		esac
+		;;
+	isi68 | isi)
+		basic_machine=m68k-isi
+		os=-sysv
+		;;
+	m88k-omron*)
+		basic_machine=m88k-omron
+		;;
+	magnum | m3230)
+		basic_machine=mips-mips
+		os=-sysv
+		;;
+	merlin)
+		basic_machine=ns32k-utek
+		os=-sysv
+		;;
+	mingw32)
+		basic_machine=i386-pc
+		os=-mingw32
+		;;
+	miniframe)
+		basic_machine=m68000-convergent
+		;;
+	*mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*)
+		basic_machine=m68k-atari
+		os=-mint
+		;;
+	mips3*-*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`
+		;;
+	mips3*)
+		basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown
+		;;
+	monitor)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	morphos)
+		basic_machine=powerpc-unknown
+		os=-morphos
+		;;
+	msdos)
+		basic_machine=i386-pc
+		os=-msdos
+		;;
+	mvs)
+		basic_machine=i370-ibm
+		os=-mvs
+		;;
+	ncr3000)
+		basic_machine=i486-ncr
+		os=-sysv4
+		;;
+	netbsd386)
+		basic_machine=i386-unknown
+		os=-netbsd
+		;;
+	netwinder)
+		basic_machine=armv4l-rebel
+		os=-linux
+		;;
+	news | news700 | news800 | news900)
+		basic_machine=m68k-sony
+		os=-newsos
+		;;
+	news1000)
+		basic_machine=m68030-sony
+		os=-newsos
+		;;
+	news-3600 | risc-news)
+		basic_machine=mips-sony
+		os=-newsos
+		;;
+	necv70)
+		basic_machine=v70-nec
+		os=-sysv
+		;;
+	next | m*-next )
+		basic_machine=m68k-next
+		case $os in
+		    -nextstep* )
+			;;
+		    -ns2*)
+		      os=-nextstep2
+			;;
+		    *)
+		      os=-nextstep3
+			;;
+		esac
+		;;
+	nh3000)
+		basic_machine=m68k-harris
+		os=-cxux
+		;;
+	nh[45]000)
+		basic_machine=m88k-harris
+		os=-cxux
+		;;
+	nindy960)
+		basic_machine=i960-intel
+		os=-nindy
+		;;
+	mon960)
+		basic_machine=i960-intel
+		os=-mon960
+		;;
+	nonstopux)
+		basic_machine=mips-compaq
+		os=-nonstopux
+		;;
+	np1)
+		basic_machine=np1-gould
+		;;
+	nsr-tandem)
+		basic_machine=nsr-tandem
+		;;
+	op50n-* | op60c-*)
+		basic_machine=hppa1.1-oki
+		os=-proelf
+		;;
+	or32 | or32-*)
+		basic_machine=or32-unknown
+		os=-coff
+		;;
+	os400)
+		basic_machine=powerpc-ibm
+		os=-os400
+		;;
+	OSE68000 | ose68000)
+		basic_machine=m68000-ericsson
+		os=-ose
+		;;
+	os68k)
+		basic_machine=m68k-none
+		os=-os68k
+		;;
+	pa-hitachi)
+		basic_machine=hppa1.1-hitachi
+		os=-hiuxwe2
+		;;
+	paragon)
+		basic_machine=i860-intel
+		os=-osf
+		;;
+	pbd)
+		basic_machine=sparc-tti
+		;;
+	pbb)
+		basic_machine=m68k-tti
+		;;
+	pc532 | pc532-*)
+		basic_machine=ns32k-pc532
+		;;
+	pentium | p5 | k5 | k6 | nexgen | viac3)
+		basic_machine=i586-pc
+		;;
+	pentiumpro | p6 | 6x86 | athlon | athlon_*)
+		basic_machine=i686-pc
+		;;
+	pentiumii | pentium2 | pentiumiii | pentium3)
+		basic_machine=i686-pc
+		;;
+	pentium4)
+		basic_machine=i786-pc
+		;;
+	pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*)
+		basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumpro-* | p6-* | 6x86-* | athlon-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*)
+		basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pentium4-*)
+		basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	pn)
+		basic_machine=pn-gould
+		;;
+	power)	basic_machine=power-ibm
+		;;
+	ppc)	basic_machine=powerpc-unknown
+		;;
+	ppc-*)	basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppcle | powerpclittle | ppc-le | powerpc-little)
+		basic_machine=powerpcle-unknown
+		;;
+	ppcle-* | powerpclittle-*)
+		basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64)	basic_machine=powerpc64-unknown
+		;;
+	ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+		basic_machine=powerpc64le-unknown
+		;;
+	ppc64le-* | powerpc64little-*)
+		basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'`
+		;;
+	ps2)
+		basic_machine=i386-ibm
+		;;
+	pw32)
+		basic_machine=i586-unknown
+		os=-pw32
+		;;
+	rom68k)
+		basic_machine=m68k-rom68k
+		os=-coff
+		;;
+	rm[46]00)
+		basic_machine=mips-siemens
+		;;
+	rtpc | rtpc-*)
+		basic_machine=romp-ibm
+		;;
+	s390 | s390-*)
+		basic_machine=s390-ibm
+		;;
+	s390x | s390x-*)
+		basic_machine=s390x-ibm
+		;;
+	sa29200)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	sb1)
+		basic_machine=mipsisa64sb1-unknown
+		;;
+	sb1el)
+		basic_machine=mipsisa64sb1el-unknown
+		;;
+	sei)
+		basic_machine=mips-sei
+		os=-seiux
+		;;
+	sequent)
+		basic_machine=i386-sequent
+		;;
+	sh)
+		basic_machine=sh-hitachi
+		os=-hms
+		;;
+	sh64)
+		basic_machine=sh64-unknown
+		;;
+	sparclite-wrs | simso-wrs)
+		basic_machine=sparclite-wrs
+		os=-vxworks
+		;;
+	sps7)
+		basic_machine=m68k-bull
+		os=-sysv2
+		;;
+	spur)
+		basic_machine=spur-unknown
+		;;
+	st2000)
+		basic_machine=m68k-tandem
+		;;
+	stratus)
+		basic_machine=i860-stratus
+		os=-sysv4
+		;;
+	sun2)
+		basic_machine=m68000-sun
+		;;
+	sun2os3)
+		basic_machine=m68000-sun
+		os=-sunos3
+		;;
+	sun2os4)
+		basic_machine=m68000-sun
+		os=-sunos4
+		;;
+	sun3os3)
+		basic_machine=m68k-sun
+		os=-sunos3
+		;;
+	sun3os4)
+		basic_machine=m68k-sun
+		os=-sunos4
+		;;
+	sun4os3)
+		basic_machine=sparc-sun
+		os=-sunos3
+		;;
+	sun4os4)
+		basic_machine=sparc-sun
+		os=-sunos4
+		;;
+	sun4sol2)
+		basic_machine=sparc-sun
+		os=-solaris2
+		;;
+	sun3 | sun3-*)
+		basic_machine=m68k-sun
+		;;
+	sun4)
+		basic_machine=sparc-sun
+		;;
+	sun386 | sun386i | roadrunner)
+		basic_machine=i386-sun
+		;;
+	sv1)
+		basic_machine=sv1-cray
+		os=-unicos
+		;;
+	symmetry)
+		basic_machine=i386-sequent
+		os=-dynix
+		;;
+	t3e)
+		basic_machine=alphaev5-cray
+		os=-unicos
+		;;
+	t90)
+		basic_machine=t90-cray
+		os=-unicos
+		;;
+	tic54x | c54x*)
+		basic_machine=tic54x-unknown
+		os=-coff
+		;;
+	tic55x | c55x*)
+		basic_machine=tic55x-unknown
+		os=-coff
+		;;
+	tic6x | c6x*)
+		basic_machine=tic6x-unknown
+		os=-coff
+		;;
+	tx39)
+		basic_machine=mipstx39-unknown
+		;;
+	tx39el)
+		basic_machine=mipstx39el-unknown
+		;;
+	toad1)
+		basic_machine=pdp10-xkl
+		os=-tops20
+		;;
+	tower | tower-32)
+		basic_machine=m68k-ncr
+		;;
+	tpf)
+		basic_machine=s390x-ibm
+		os=-tpf
+		;;
+	udi29k)
+		basic_machine=a29k-amd
+		os=-udi
+		;;
+	ultra3)
+		basic_machine=a29k-nyu
+		os=-sym1
+		;;
+	v810 | necv810)
+		basic_machine=v810-nec
+		os=-none
+		;;
+	vaxv)
+		basic_machine=vax-dec
+		os=-sysv
+		;;
+	vms)
+		basic_machine=vax-dec
+		os=-vms
+		;;
+	vpp*|vx|vx-*)
+		basic_machine=f301-fujitsu
+		;;
+	vxworks960)
+		basic_machine=i960-wrs
+		os=-vxworks
+		;;
+	vxworks68)
+		basic_machine=m68k-wrs
+		os=-vxworks
+		;;
+	vxworks29k)
+		basic_machine=a29k-wrs
+		os=-vxworks
+		;;
+	w65*)
+		basic_machine=w65-wdc
+		os=-none
+		;;
+	w89k-*)
+		basic_machine=hppa1.1-winbond
+		os=-proelf
+		;;
+	xps | xps100)
+		basic_machine=xps100-honeywell
+		;;
+	ymp)
+		basic_machine=ymp-cray
+		os=-unicos
+		;;
+	z8k-*-coff)
+		basic_machine=z8k-unknown
+		os=-sim
+		;;
+	none)
+		basic_machine=none-none
+		os=-none
+		;;
+
+# Here we handle the default manufacturer of certain CPU types.  It is in
+# some cases the only manufacturer, in others, it is the most popular.
+	w89k)
+		basic_machine=hppa1.1-winbond
+		;;
+	op50n)
+		basic_machine=hppa1.1-oki
+		;;
+	op60c)
+		basic_machine=hppa1.1-oki
+		;;
+	romp)
+		basic_machine=romp-ibm
+		;;
+	mmix)
+		basic_machine=mmix-knuth
+		;;
+	rs6000)
+		basic_machine=rs6000-ibm
+		;;
+	vax)
+		basic_machine=vax-dec
+		;;
+	pdp10)
+		# there are many clones, so DEC is not a safe bet
+		basic_machine=pdp10-unknown
+		;;
+	pdp11)
+		basic_machine=pdp11-dec
+		;;
+	we32k)
+		basic_machine=we32k-att
+		;;
+	sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele)
+		basic_machine=sh-unknown
+		;;
+	sh64)
+		basic_machine=sh64-unknown
+		;;
+	sparc | sparcv8 | sparcv9 | sparcv9b)
+		basic_machine=sparc-sun
+		;;
+	cydra)
+		basic_machine=cydra-cydrome
+		;;
+	orion)
+		basic_machine=orion-highlevel
+		;;
+	orion105)
+		basic_machine=clipper-highlevel
+		;;
+	mac | mpw | mac-mpw)
+		basic_machine=m68k-apple
+		;;
+	pmac | pmac-mpw)
+		basic_machine=powerpc-apple
+		;;
+	*-unknown)
+		# Make sure to match an already-canonicalized machine name.
+		;;
+	*)
+		echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2
+		exit 1
+		;;
+esac
+
+# Here we canonicalize certain aliases for manufacturers.
+case $basic_machine in
+	*-digital*)
+		basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'`
+		;;
+	*-commodore*)
+		basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'`
+		;;
+	*)
+		;;
+esac
+
+# Decode manufacturer-specific aliases for certain operating systems.
+
+if [ x"$os" != x"" ]
+then
+case $os in
+        # First match some system type aliases
+        # that might get confused with valid system types.
+	# -solaris* is a basic system type, with this one exception.
+	-solaris1 | -solaris1.*)
+		os=`echo $os | sed -e 's|solaris1|sunos4|'`
+		;;
+	-solaris)
+		os=-solaris2
+		;;
+	-svr4*)
+		os=-sysv4
+		;;
+	-unixware*)
+		os=-sysv4.2uw
+		;;
+	-gnu/linux*)
+		os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'`
+		;;
+	# First accept the basic system types.
+	# The portable systems comes first.
+	# Each alternative MUST END IN A *, to match a version number.
+	# -sysv* is not here because it comes later, after sysvr4.
+	-gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \
+	      | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\
+	      | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \
+	      | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
+	      | -aos* \
+	      | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
+	      | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
+	      | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \
+	      | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
+	      | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
+	      | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
+	      | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
+	      | -chorusos* | -chorusrdb* \
+	      | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
+	      | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \
+	      | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
+	      | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
+	      | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
+	      | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
+	      | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
+	      | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly*)
+	# Remember, each alternative MUST END IN *, to match a version number.
+		;;
+	-qnx*)
+		case $basic_machine in
+		    x86-* | i*86-*)
+			;;
+		    *)
+			os=-nto$os
+			;;
+		esac
+		;;
+	-nto-qnx*)
+		;;
+	-nto*)
+		os=`echo $os | sed -e 's|nto|nto-qnx|'`
+		;;
+	-sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \
+	      | -windows* | -osx | -abug | -netware* | -os9* | -beos* \
+	      | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*)
+		;;
+	-mac*)
+		os=`echo $os | sed -e 's|mac|macos|'`
+		;;
+	-linux-dietlibc)
+		os=-linux-dietlibc
+		;;
+	-linux*)
+		os=`echo $os | sed -e 's|linux|linux-gnu|'`
+		;;
+	-sunos5*)
+		os=`echo $os | sed -e 's|sunos5|solaris2|'`
+		;;
+	-sunos6*)
+		os=`echo $os | sed -e 's|sunos6|solaris3|'`
+		;;
+	-opened*)
+		os=-openedition
+		;;
+        -os400*)
+		os=-os400
+		;;
+	-wince*)
+		os=-wince
+		;;
+	-osfrose*)
+		os=-osfrose
+		;;
+	-osf*)
+		os=-osf
+		;;
+	-utek*)
+		os=-bsd
+		;;
+	-dynix*)
+		os=-bsd
+		;;
+	-acis*)
+		os=-aos
+		;;
+	-atheos*)
+		os=-atheos
+		;;
+	-syllable*)
+		os=-syllable
+		;;
+	-386bsd)
+		os=-bsd
+		;;
+	-ctix* | -uts*)
+		os=-sysv
+		;;
+	-nova*)
+		os=-rtmk-nova
+		;;
+	-ns2 )
+		os=-nextstep2
+		;;
+	-nsk*)
+		os=-nsk
+		;;
+	# Preserve the version number of sinix5.
+	-sinix5.*)
+		os=`echo $os | sed -e 's|sinix|sysv|'`
+		;;
+	-sinix*)
+		os=-sysv4
+		;;
+        -tpf*)
+		os=-tpf
+		;;
+	-triton*)
+		os=-sysv3
+		;;
+	-oss*)
+		os=-sysv3
+		;;
+	-svr4)
+		os=-sysv4
+		;;
+	-svr3)
+		os=-sysv3
+		;;
+	-sysvr4)
+		os=-sysv4
+		;;
+	# This must come after -sysvr4.
+	-sysv*)
+		;;
+	-ose*)
+		os=-ose
+		;;
+	-es1800*)
+		os=-ose
+		;;
+	-xenix)
+		os=-xenix
+		;;
+	-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+		os=-mint
+		;;
+	-aros*)
+		os=-aros
+		;;
+	-kaos*)
+		os=-kaos
+		;;
+	-none)
+		;;
+	*)
+		# Get rid of the `-' at the beginning of $os.
+		os=`echo $os | sed 's/[^-]*-//'`
+		echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2
+		exit 1
+		;;
+esac
+else
+
+# Here we handle the default operating systems that come with various machines.
+# The value should be what the vendor currently ships out the door with their
+# machine or put another way, the most popular os provided with the machine.
+
+# Note that if you're going to try to match "-MANUFACTURER" here (say,
+# "-sun"), then you have to tell the case statement up towards the top
+# that MANUFACTURER isn't an operating system.  Otherwise, code above
+# will signal an error saying that MANUFACTURER isn't an operating
+# system, and we'll never get to this point.
+
+case $basic_machine in
+	*-acorn)
+		os=-riscix1.2
+		;;
+	arm*-rebel)
+		os=-linux
+		;;
+	arm*-semi)
+		os=-aout
+		;;
+    c4x-* | tic4x-*)
+        os=-coff
+        ;;
+	# This must come before the *-dec entry.
+	pdp10-*)
+		os=-tops20
+		;;
+	pdp11-*)
+		os=-none
+		;;
+	*-dec | vax-*)
+		os=-ultrix4.2
+		;;
+	m68*-apollo)
+		os=-domain
+		;;
+	i386-sun)
+		os=-sunos4.0.2
+		;;
+	m68000-sun)
+		os=-sunos3
+		# This also exists in the configure program, but was not the
+		# default.
+		# os=-sunos4
+		;;
+	m68*-cisco)
+		os=-aout
+		;;
+	mips*-cisco)
+		os=-elf
+		;;
+	mips*-*)
+		os=-elf
+		;;
+	or32-*)
+		os=-coff
+		;;
+	*-tti)	# must be before sparc entry or we get the wrong os.
+		os=-sysv3
+		;;
+	sparc-* | *-sun)
+		os=-sunos4.1.1
+		;;
+	*-be)
+		os=-beos
+		;;
+	*-ibm)
+		os=-aix
+		;;
+    	*-knuth)
+		os=-mmixware
+		;;
+	*-wec)
+		os=-proelf
+		;;
+	*-winbond)
+		os=-proelf
+		;;
+	*-oki)
+		os=-proelf
+		;;
+	*-hp)
+		os=-hpux
+		;;
+	*-hitachi)
+		os=-hiux
+		;;
+	i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent)
+		os=-sysv
+		;;
+	*-cbm)
+		os=-amigaos
+		;;
+	*-dg)
+		os=-dgux
+		;;
+	*-dolphin)
+		os=-sysv3
+		;;
+	m68k-ccur)
+		os=-rtu
+		;;
+	m88k-omron*)
+		os=-luna
+		;;
+	*-next )
+		os=-nextstep
+		;;
+	*-sequent)
+		os=-ptx
+		;;
+	*-crds)
+		os=-unos
+		;;
+	*-ns)
+		os=-genix
+		;;
+	i370-*)
+		os=-mvs
+		;;
+	*-next)
+		os=-nextstep3
+		;;
+	*-gould)
+		os=-sysv
+		;;
+	*-highlevel)
+		os=-bsd
+		;;
+	*-encore)
+		os=-bsd
+		;;
+	*-sgi)
+		os=-irix
+		;;
+	*-siemens)
+		os=-sysv4
+		;;
+	*-masscomp)
+		os=-rtu
+		;;
+	f30[01]-fujitsu | f700-fujitsu)
+		os=-uxpv
+		;;
+	*-rom68k)
+		os=-coff
+		;;
+	*-*bug)
+		os=-coff
+		;;
+	*-apple)
+		os=-macos
+		;;
+	*-atari*)
+		os=-mint
+		;;
+	*)
+		os=-none
+		;;
+esac
+fi
+
+# Here we handle the case where we know the os, and the CPU type, but not the
+# manufacturer.  We pick the logical manufacturer.
+vendor=unknown
+case $basic_machine in
+	*-unknown)
+		case $os in
+			-riscix*)
+				vendor=acorn
+				;;
+			-sunos*)
+				vendor=sun
+				;;
+			-aix*)
+				vendor=ibm
+				;;
+			-beos*)
+				vendor=be
+				;;
+			-hpux*)
+				vendor=hp
+				;;
+			-mpeix*)
+				vendor=hp
+				;;
+			-hiux*)
+				vendor=hitachi
+				;;
+			-unos*)
+				vendor=crds
+				;;
+			-dgux*)
+				vendor=dg
+				;;
+			-luna*)
+				vendor=omron
+				;;
+			-genix*)
+				vendor=ns
+				;;
+			-mvs* | -opened*)
+				vendor=ibm
+				;;
+			-os400*)
+				vendor=ibm
+				;;
+			-ptx*)
+				vendor=sequent
+				;;
+			-tpf*)
+				vendor=ibm
+				;;
+			-vxsim* | -vxworks* | -windiss*)
+				vendor=wrs
+				;;
+			-aux*)
+				vendor=apple
+				;;
+			-hms*)
+				vendor=hitachi
+				;;
+			-mpw* | -macos*)
+				vendor=apple
+				;;
+			-*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*)
+				vendor=atari
+				;;
+			-vos*)
+				vendor=stratus
+				;;
+		esac
+		basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"`
+		;;
+esac
+
+echo $basic_machine$os
+exit 0
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "timestamp='"
+# time-stamp-format: "%:y-%02m-%02d"
+# time-stamp-end: "'"
+# End:
diff --git a/enet/configure b/enet/configure
index 275154d..908b6ad 100755
--- a/enet/configure
+++ b/enet/configure
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.61 for libenet 9-15-2008.
+# Generated by GNU Autoconf 2.61 for libenet 10-12-2007.
 #
 # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
 # 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
@@ -572,8 +572,8 @@ SHELL=${CONFIG_SHELL-/bin/sh}
 # Identity of this package.
 PACKAGE_NAME='libenet'
 PACKAGE_TARNAME='libenet'
-PACKAGE_VERSION='9-15-2008'
-PACKAGE_STRING='libenet 9-15-2008'
+PACKAGE_VERSION='10-12-2007'
+PACKAGE_STRING='libenet 10-12-2007'
 PACKAGE_BUGREPORT=''
 
 ac_subst_vars='SHELL
@@ -616,7 +616,6 @@ target_alias
 INSTALL_PROGRAM
 INSTALL_SCRIPT
 INSTALL_DATA
-am__isrc
 CYGPATH_W
 PACKAGE
 VERSION
@@ -1169,7 +1168,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures libenet 9-15-2008 to adapt to many kinds of systems.
+\`configure' configures libenet 10-12-2007 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1235,7 +1234,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of libenet 9-15-2008:";;
+     short | recursive ) echo "Configuration of libenet 10-12-2007:";;
    esac
   cat <<\_ACEOF
 
@@ -1319,7 +1318,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-libenet configure 9-15-2008
+libenet configure 10-12-2007
 generated by GNU Autoconf 2.61
 
 Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001,
@@ -1333,7 +1332,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by libenet $as_me 9-15-2008, which was
+It was created by libenet $as_me 10-12-2007, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   $ $0 $@
@@ -1686,8 +1685,7 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
 ac_compiler_gnu=$ac_cv_c_compiler_gnu
 
 
-am__api_version='1.10'
-
+am__api_version="1.9"
 ac_aux_dir=
 for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
   if test -f "$ac_dir/install-sh"; then
@@ -1870,53 +1868,38 @@ else
 echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;}
 fi
 
-{ echo "$as_me:$LINENO: checking for a thread-safe mkdir -p" >&5
-echo $ECHO_N "checking for a thread-safe mkdir -p... $ECHO_C" >&6; }
-if test -z "$MKDIR_P"; then
-  if test "${ac_cv_path_mkdir+set}" = set; then
-  echo $ECHO_N "(cached) $ECHO_C" >&6
+if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then
+  # We used to keeping the `.' as first argument, in order to
+  # allow $(mkdir_p) to be used without argument.  As in
+  #   $(mkdir_p) $(somedir)
+  # where $(somedir) is conditionally defined.  However this is wrong
+  # for two reasons:
+  #  1. if the package is installed by a user who cannot write `.'
+  #     make install will fail,
+  #  2. the above comment should most certainly read
+  #     $(mkdir_p) $(DESTDIR)$(somedir)
+  #     so it does not work when $(somedir) is undefined and
+  #     $(DESTDIR) is not.
+  #  To support the latter case, we have to write
+  #     test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir),
+  #  so the `.' trick is pointless.
+  mkdir_p='mkdir -p --'
 else
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH$PATH_SEPARATOR/opt/sfw/bin
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-  for ac_prog in mkdir gmkdir; do
-	 for ac_exec_ext in '' $ac_executable_extensions; do
-	   { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue
-	   case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #(
-	     'mkdir (GNU coreutils) '* | \
-	     'mkdir (coreutils) '* | \
-	     'mkdir (fileutils) '4.1*)
-	       ac_cv_path_mkdir=$as_dir/$ac_prog$ac_exec_ext
-	       break 3;;
-	   esac
-	 done
-       done
-done
-IFS=$as_save_IFS
-
-fi
-
-  if test "${ac_cv_path_mkdir+set}" = set; then
-    MKDIR_P="$ac_cv_path_mkdir -p"
+  # On NextStep and OpenStep, the `mkdir' command does not
+  # recognize any option.  It will interpret all options as
+  # directories to create, and then abort because `.' already
+  # exists.
+  for d in ./-p ./--version;
+  do
+    test -d $d && rmdir $d
+  done
+  # $(mkinstalldirs) is defined by Automake if mkinstalldirs exists.
+  if test -f "$ac_aux_dir/mkinstalldirs"; then
+    mkdir_p='$(mkinstalldirs)'
   else
-    # As a last resort, use the slow shell script.  Don't cache a
-    # value for MKDIR_P within a source directory, because that will
-    # break other packages using the cache if that directory is
-    # removed, or if the value is a relative name.
-    test -d ./--version && rmdir ./--version
-    MKDIR_P="$ac_install_sh -d"
+    mkdir_p='$(install_sh) -d'
   fi
 fi
-{ echo "$as_me:$LINENO: result: $MKDIR_P" >&5
-echo "${ECHO_T}$MKDIR_P" >&6; }
-
-mkdir_p="$MKDIR_P"
-case $mkdir_p in
-  [\\/$]* | ?:[\\/]*) ;;
-  */*) mkdir_p="\$(top_builddir)/$mkdir_p" ;;
-esac
 
 for ac_prog in gawk mawk nawk awk
 do
@@ -1999,16 +1982,12 @@ else
 fi
 rmdir .tst 2>/dev/null
 
-if test "`cd $srcdir && pwd`" != "`pwd`"; then
-  # Use -I$(srcdir) only when $(srcdir) != ., so that make's output
-  # is not polluted with repeated "-I."
-  am__isrc=' -I$(srcdir)'
-  # test to see if srcdir already configured
-  if test -f $srcdir/config.status; then
-    { { echo "$as_me:$LINENO: error: source directory already configured; run \"make distclean\" there first" >&5
+# test to see if srcdir already configured
+if test "`cd $srcdir && pwd`" != "`pwd`" &&
+   test -f $srcdir/config.status; then
+  { { echo "$as_me:$LINENO: error: source directory already configured; run \"make distclean\" there first" >&5
 echo "$as_me: error: source directory already configured; run \"make distclean\" there first" >&2;}
    { (exit 1); exit 1; }; }
-  fi
 fi
 
 # test whether we have cygpath
@@ -2023,7 +2002,7 @@ fi
 
 # Define the identity of the package.
  PACKAGE=libenet.a
- VERSION=9-15-2008
+ VERSION=10-12-2007
 
 
 cat >>confdefs.h <<_ACEOF
@@ -2051,7 +2030,7 @@ AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"}
 
 MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
 
-install_sh=${install_sh-"\$(SHELL) $am_aux_dir/install-sh"}
+install_sh=${install_sh-"$am_aux_dir/install-sh"}
 
 # Installed binaries are usually stripped using `strip' when the user
 # run `make install-strip'.  However `strip' might not be the right
@@ -2155,7 +2134,7 @@ else
 fi
 
 fi
-INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s"
+INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s"
 
 # We need awk for the "check" target.  The system "awk" is bad on
 # some platforms.
@@ -3141,7 +3120,9 @@ if test "x$enable_dependency_tracking" != xno; then
   am_depcomp="$ac_aux_dir/depcomp"
   AMDEPBACKSLASH='\'
 fi
- if test "x$enable_dependency_tracking" != xno; then
+
+
+if test "x$enable_dependency_tracking" != xno; then
   AMDEP_TRUE=
   AMDEP_FALSE='#'
 else
@@ -3151,6 +3132,7 @@ fi
 
 
 
+
 depcc="$CC"   am_compiler_list=
 
 { echo "$as_me:$LINENO: checking dependency style of $depcc" >&5
@@ -3218,7 +3200,6 @@ else
        depfile=sub/conftest.Po tmpdepfile=sub/conftest.TPo \
        $SHELL ./depcomp $depcc -c -o sub/conftest.${OBJEXT-o} sub/conftest.c \
          >/dev/null 2>conftest.err &&
-       grep sub/conftst1.h sub/conftest.Po > /dev/null 2>&1 &&
        grep sub/conftst6.h sub/conftest.Po > /dev/null 2>&1 &&
        grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 &&
        ${MAKE-make} -s -f confmf > /dev/null 2>&1; then
@@ -3248,7 +3229,9 @@ fi
 echo "${ECHO_T}$am_cv_CC_dependencies_compiler_type" >&6; }
 CCDEPMODE=depmode=$am_cv_CC_dependencies_compiler_type
 
- if
+
+
+if
   test "x$enable_dependency_tracking" != xno \
   && test "$am_cv_CC_dependencies_compiler_type" = gcc3; then
   am__fastdepCC_TRUE=
@@ -4946,7 +4929,7 @@ exec 6>&1
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by libenet $as_me 9-15-2008, which was
+This file was extended by libenet $as_me 10-12-2007, which was
 generated by GNU Autoconf 2.61.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -4993,7 +4976,7 @@ Report bugs to <bug-autoconf at gnu.org>."
 _ACEOF
 cat >>$CONFIG_STATUS <<_ACEOF
 ac_cs_version="\\
-libenet config.status 9-15-2008
+libenet config.status 10-12-2007
 configured by $0, generated by GNU Autoconf 2.61,
   with options \\"`echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`\\"
 
@@ -5004,7 +4987,6 @@ gives unlimited permission to copy, distribute and modify it."
 ac_pwd='$ac_pwd'
 srcdir='$srcdir'
 INSTALL='$INSTALL'
-MKDIR_P='$MKDIR_P'
 _ACEOF
 
 cat >>$CONFIG_STATUS <<\_ACEOF
@@ -5203,7 +5185,6 @@ target_alias!$target_alias$ac_delim
 INSTALL_PROGRAM!$INSTALL_PROGRAM$ac_delim
 INSTALL_SCRIPT!$INSTALL_SCRIPT$ac_delim
 INSTALL_DATA!$INSTALL_DATA$ac_delim
-am__isrc!$am__isrc$ac_delim
 CYGPATH_W!$CYGPATH_W$ac_delim
 PACKAGE!$PACKAGE$ac_delim
 VERSION!$VERSION$ac_delim
@@ -5246,7 +5227,7 @@ LIBOBJS!$LIBOBJS$ac_delim
 LTLIBOBJS!$LTLIBOBJS$ac_delim
 _ACEOF
 
-  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 81; then
+  if test `sed -n "s/.*$ac_delim\$/X/p" conf$$subs.sed | grep -c X` = 80; then
     break
   elif $ac_last_try; then
     { { echo "$as_me:$LINENO: error: could not make $CONFIG_STATUS" >&5
@@ -5473,11 +5454,6 @@ ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
   [\\/$]* | ?:[\\/]* ) ac_INSTALL=$INSTALL ;;
   *) ac_INSTALL=$ac_top_build_prefix$INSTALL ;;
   esac
-  ac_MKDIR_P=$MKDIR_P
-  case $MKDIR_P in
-  [\\/$]* | ?:[\\/]* ) ;;
-  */*) ac_MKDIR_P=$ac_top_build_prefix$MKDIR_P ;;
-  esac
 _ACEOF
 
 cat >>$CONFIG_STATUS <<\_ACEOF
@@ -5531,7 +5507,6 @@ s&@builddir@&$ac_builddir&;t t
 s&@abs_builddir@&$ac_abs_builddir&;t t
 s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
 s&@INSTALL@&$ac_INSTALL&;t t
-s&@MKDIR_P@&$ac_MKDIR_P&;t t
 $ac_datarootdir_hack
 " $ac_file_inputs | sed -f "$tmp/subs-1.sed" >$tmp/out
 
@@ -5566,9 +5541,8 @@ echo "$as_me: executing $ac_file commands" >&6;}
   # some people rename them; so instead we look at the file content.
   # Grep'ing the first line is not enough: some people post-process
   # each Makefile.in and add a new line on top of each file to say so.
-  # Grep'ing the whole file is not good either: AIX grep has a line
-  # limit of 2048, but all sed's we know have understand at least 4000.
-  if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then
+  # So let's grep whole file.
+  if grep '^#.*generated by automake' $mf > /dev/null 2>&1; then
     dirpart=`$as_dirname -- "$mf" ||
 $as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
 	 X"$mf" : 'X\(//\)[^/]' \| \
diff --git a/enet/configure.in b/enet/configure.in
index f0fd703..178a623 100644
--- a/enet/configure.in
+++ b/enet/configure.in
@@ -1,5 +1,5 @@
-AC_INIT(libenet, 9-15-2008)
-AM_INIT_AUTOMAKE(libenet.a, 9-15-2008)
+AC_INIT(libenet, 10-12-2007)
+AM_INIT_AUTOMAKE(libenet.a, 10-12-2007)
 
 AC_PROG_CC
 AC_PROG_RANLIB
diff --git a/enet/depcomp b/enet/depcomp
index e5f9736..11e2d3b 100755
--- a/enet/depcomp
+++ b/enet/depcomp
@@ -1,10 +1,9 @@
 #! /bin/sh
 # depcomp - compile a program generating dependencies as side-effects
 
-scriptversion=2007-03-29.01
+scriptversion=2004-05-31.23
 
-# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007 Free Software
-# Foundation, Inc.
+# Copyright (C) 1999, 2000, 2003, 2004 Free Software Foundation, Inc.
 
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,8 +17,8 @@ scriptversion=2007-03-29.01
 
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
 
 # As a special exception to the GNU General Public License, if you
 # distribute this file as part of a program that contains a
@@ -51,11 +50,11 @@ Environment variables:
 
 Report bugs to <bug-automake at gnu.org>.
 EOF
-    exit $?
+    exit 0
     ;;
   -v | --v*)
     echo "depcomp $scriptversion"
-    exit $?
+    exit 0
     ;;
 esac
 
@@ -92,20 +91,7 @@ gcc3)
 ## gcc 3 implements dependency tracking that does exactly what
 ## we want.  Yay!  Note: for some reason libtool 1.4 doesn't like
 ## it if -MD -MP comes after the -MF stuff.  Hmm.
-## Unfortunately, FreeBSD c89 acceptance of flags depends upon
-## the command line argument order; so add the flags where they
-## appear in depend2.am.  Note that the slowdown incurred here
-## affects only configure: in makefiles, %FASTDEP% shortcuts this.
-  for arg
-  do
-    case $arg in
-    -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;;
-    *)  set fnord "$@" "$arg" ;;
-    esac
-    shift # fnord
-    shift # $arg
-  done
-  "$@"
+  "$@" -MT "$object" -MD -MP -MF "$tmpdepfile"
   stat=$?
   if test $stat -eq 0; then :
   else
@@ -215,39 +201,34 @@ aix)
   # current directory.  Also, the AIX compiler puts `$object:' at the
   # start of each line; $object doesn't have directory information.
   # Version 6 uses the directory in both cases.
-  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
-  test "x$dir" = "x$object" && dir=
-  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
+  stripped=`echo "$object" | sed 's/\(.*\)\..*$/\1/'`
+  tmpdepfile="$stripped.u"
   if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$base.u
-    tmpdepfile3=$dir.libs/$base.u
     "$@" -Wc,-M
   else
-    tmpdepfile1=$dir$base.u
-    tmpdepfile2=$dir$base.u
-    tmpdepfile3=$dir$base.u
     "$@" -M
   fi
   stat=$?
 
+  if test -f "$tmpdepfile"; then :
+  else
+    stripped=`echo "$stripped" | sed 's,^.*/,,'`
+    tmpdepfile="$stripped.u"
+  fi
+
   if test $stat -eq 0; then :
   else
-    rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
+    rm -f "$tmpdepfile"
     exit $stat
   fi
 
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
-  do
-    test -f "$tmpdepfile" && break
-  done
   if test -f "$tmpdepfile"; then
+    outname="$stripped.o"
     # Each line is of the form `foo.o: dependent.h'.
     # Do two passes, one to just change these to
     # `$object: dependent.h' and one to simply `dependent.h:'.
-    sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
-    # That's a tab and a space in the [].
-    sed -e 's,^.*\.[a-z]*:[	 ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
+    sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile"
+    sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile"
   else
     # The sourcefile does not contain any dependencies, so just
     # store a dummy comment line, to avoid errors with the Makefile
@@ -295,46 +276,6 @@ icc)
   rm -f "$tmpdepfile"
   ;;
 
-hp2)
-  # The "hp" stanza above does not work with aCC (C++) and HP's ia64
-  # compilers, which have integrated preprocessors.  The correct option
-  # to use with these is +Maked; it writes dependencies to a file named
-  # 'foo.d', which lands next to the object file, wherever that
-  # happens to be.
-  # Much of this is similar to the tru64 case; see comments there.
-  dir=`echo "$object" | sed -e 's|/[^/]*$|/|'`
-  test "x$dir" = "x$object" && dir=
-  base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
-  if test "$libtool" = yes; then
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir.libs/$base.d
-    "$@" -Wc,+Maked
-  else
-    tmpdepfile1=$dir$base.d
-    tmpdepfile2=$dir$base.d
-    "$@" +Maked
-  fi
-  stat=$?
-  if test $stat -eq 0; then :
-  else
-     rm -f "$tmpdepfile1" "$tmpdepfile2"
-     exit $stat
-  fi
-
-  for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2"
-  do
-    test -f "$tmpdepfile" && break
-  done
-  if test -f "$tmpdepfile"; then
-    sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile"
-    # Add `dependent.h:' lines.
-    sed -ne '2,${; s/^ *//; s/ \\*$//; s/$/:/; p;}' "$tmpdepfile" >> "$depfile"
-  else
-    echo "#dummy" > "$depfile"
-  fi
-  rm -f "$tmpdepfile" "$tmpdepfile2"
-  ;;
-
 tru64)
    # The Tru64 compiler uses -MD to generate dependencies as a side
    # effect.  `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
@@ -346,43 +287,36 @@ tru64)
    base=`echo "$object" | sed -e 's|^.*/||' -e 's/\.o$//' -e 's/\.lo$//'`
 
    if test "$libtool" = yes; then
-      # With Tru64 cc, shared objects can also be used to make a
-      # static library.  This mechanism is used in libtool 1.4 series to
-      # handle both shared and static libraries in a single compilation.
-      # With libtool 1.4, dependencies were output in $dir.libs/$base.lo.d.
-      #
-      # With libtool 1.5 this exception was removed, and libtool now
-      # generates 2 separate objects for the 2 libraries.  These two
-      # compilations output dependencies in $dir.libs/$base.o.d and
-      # in $dir$base.o.d.  We have to check for both files, because
-      # one of the two compilations can be disabled.  We should prefer
-      # $dir$base.o.d over $dir.libs/$base.o.d because the latter is
-      # automatically cleaned when .libs/ is deleted, while ignoring
-      # the former would cause a distcleancheck panic.
-      tmpdepfile1=$dir.libs/$base.lo.d   # libtool 1.4
-      tmpdepfile2=$dir$base.o.d          # libtool 1.5
-      tmpdepfile3=$dir.libs/$base.o.d    # libtool 1.5
-      tmpdepfile4=$dir.libs/$base.d      # Compaq CCC V6.2-504
+      # Dependencies are output in .lo.d with libtool 1.4.
+      # With libtool 1.5 they are output both in $dir.libs/$base.o.d
+      # and in $dir.libs/$base.o.d and $dir$base.o.d.  We process the
+      # latter, because the former will be cleaned when $dir.libs is
+      # erased.
+      tmpdepfile1="$dir.libs/$base.lo.d"
+      tmpdepfile2="$dir$base.o.d"
+      tmpdepfile3="$dir.libs/$base.d"
       "$@" -Wc,-MD
    else
-      tmpdepfile1=$dir$base.o.d
-      tmpdepfile2=$dir$base.d
-      tmpdepfile3=$dir$base.d
-      tmpdepfile4=$dir$base.d
+      tmpdepfile1="$dir$base.o.d"
+      tmpdepfile2="$dir$base.d"
+      tmpdepfile3="$dir$base.d"
       "$@" -MD
    fi
 
    stat=$?
    if test $stat -eq 0; then :
    else
-      rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
+      rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3"
       exit $stat
    fi
 
-   for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" "$tmpdepfile4"
-   do
-     test -f "$tmpdepfile" && break
-   done
+   if test -f "$tmpdepfile1"; then
+      tmpdepfile="$tmpdepfile1"
+   elif test -f "$tmpdepfile2"; then
+      tmpdepfile="$tmpdepfile2"
+   else
+      tmpdepfile="$tmpdepfile3"
+   fi
    if test -f "$tmpdepfile"; then
       sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
       # That's a tab and a space in the [].
@@ -526,8 +460,7 @@ cpp)
   done
 
   "$@" -E |
-    sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \
-       -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
+    sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
     sed '$ s: \\$::' > "$tmpdepfile"
   rm -f "$depfile"
   echo "$object : \\" > "$depfile"
diff --git a/enet/docs/FAQ.dox b/enet/docs/FAQ.dox
old mode 100644
new mode 100755
diff --git a/enet/docs/design.dox b/enet/docs/design.dox
old mode 100644
new mode 100755
diff --git a/enet/docs/install.dox b/enet/docs/install.dox
old mode 100644
new mode 100755
diff --git a/enet/docs/license.dox b/enet/docs/license.dox
old mode 100644
new mode 100755
diff --git a/enet/docs/mainpage.dox b/enet/docs/mainpage.dox
old mode 100644
new mode 100755
diff --git a/enet/docs/tutorial.dox b/enet/docs/tutorial.dox
old mode 100644
new mode 100755
diff --git a/enet/enet.dsp b/enet/enet.dsp
old mode 100644
new mode 100755
diff --git a/enet/host.c b/enet/host.c
index 30eaded..9bab1a8 100644
--- a/enet/host.c
+++ b/enet/host.c
@@ -36,12 +36,9 @@ enet_host_create (const ENetAddress * address, size_t peerCount, enet_uint32 inc
     host -> peers = (ENetPeer *) enet_malloc (peerCount * sizeof (ENetPeer));
     memset (host -> peers, 0, peerCount * sizeof (ENetPeer));
 
-    host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM);
-    if (host -> socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind (host -> socket, address) < 0))
+    host -> socket = enet_socket_create (ENET_SOCKET_TYPE_DATAGRAM, address);
+    if (host -> socket == ENET_SOCKET_NULL)
     {
-       if (host -> socket != ENET_SOCKET_NULL)
-         enet_socket_destroy (host -> socket);
-
        enet_free (host -> peers);
        enet_free (host);
 
diff --git a/enet/include/Makefile.in b/enet/include/Makefile.in
index 4a33aaa..3e26043 100644
--- a/enet/include/Makefile.in
+++ b/enet/include/Makefile.in
@@ -1,8 +1,8 @@
-# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# Makefile.in generated by automake 1.9.6 from Makefile.am.
 # @configure_input@
 
 # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
-# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# 2003, 2004, 2005  Free Software Foundation, Inc.
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
@@ -13,11 +13,15 @@
 # PARTICULAR PURPOSE.
 
 @SET_MAKE@
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
 VPATH = @srcdir@
 pkgdatadir = $(datadir)/@PACKAGE@
 pkglibdir = $(libdir)/@PACKAGE@
 pkgincludedir = $(includedir)/@PACKAGE@
+top_builddir = ..
 am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+INSTALL = @INSTALL@
 install_sh_DATA = $(install_sh) -c -m 644
 install_sh_PROGRAM = $(install_sh) -c
 install_sh_SCRIPT = $(install_sh) -c
@@ -35,24 +39,23 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/configure.in
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
-mkinstalldirs = $(install_sh) -d
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
 CONFIG_CLEAN_FILES =
 SOURCES =
 DIST_SOURCES =
 RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
 	html-recursive info-recursive install-data-recursive \
-	install-dvi-recursive install-exec-recursive \
-	install-html-recursive install-info-recursive \
-	install-pdf-recursive install-ps-recursive install-recursive \
-	installcheck-recursive installdirs-recursive pdf-recursive \
-	ps-recursive uninstall-recursive
-RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive	\
-  distclean-recursive maintainer-clean-recursive
+	install-exec-recursive install-info-recursive \
+	install-recursive installcheck-recursive installdirs-recursive \
+	pdf-recursive ps-recursive uninstall-info-recursive \
+	uninstall-recursive
 ETAGS = etags
 CTAGS = ctags
 DIST_SUBDIRS = $(SUBDIRS)
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
+AMDEP_FALSE = @AMDEP_FALSE@
+AMDEP_TRUE = @AMDEP_TRUE@
 AMTAR = @AMTAR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -72,7 +75,6 @@ ECHO_T = @ECHO_T@
 EGREP = @EGREP@
 EXEEXT = @EXEEXT@
 GREP = @GREP@
-INSTALL = @INSTALL@
 INSTALL_DATA = @INSTALL_DATA@
 INSTALL_PROGRAM = @INSTALL_PROGRAM@
 INSTALL_SCRIPT = @INSTALL_SCRIPT@
@@ -82,7 +84,6 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LTLIBOBJS = @LTLIBOBJS@
 MAKEINFO = @MAKEINFO@
-MKDIR_P = @MKDIR_P@
 OBJEXT = @OBJEXT@
 PACKAGE = @PACKAGE@
 PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
@@ -96,11 +97,9 @@ SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
 VERSION = @VERSION@
-abs_builddir = @abs_builddir@
-abs_srcdir = @abs_srcdir@
-abs_top_builddir = @abs_top_builddir@
-abs_top_srcdir = @abs_top_srcdir@
 ac_ct_CC = @ac_ct_CC@
+am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
+am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -108,7 +107,6 @@ am__tar = @am__tar@
 am__untar = @am__untar@
 bindir = @bindir@
 build_alias = @build_alias@
-builddir = @builddir@
 datadir = @datadir@
 datarootdir = @datarootdir@
 docdir = @docdir@
@@ -132,11 +130,8 @@ program_transform_name = @program_transform_name@
 psdir = @psdir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
-srcdir = @srcdir@
 sysconfdir = @sysconfdir@
 target_alias = @target_alias@
-top_builddir = @top_builddir@
-top_srcdir = @top_srcdir@
 SUBDIRS = enet
 all: all-recursive
 
@@ -170,6 +165,7 @@ $(top_srcdir)/configure:  $(am__configure_deps)
 	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
 $(ACLOCAL_M4):  $(am__aclocal_m4_deps)
 	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+uninstall-info-am:
 
 # This directory's subdirectories are mostly independent; you can cd
 # into them and run `make' without going through this Makefile.
@@ -202,7 +198,8 @@ $(RECURSIVE_TARGETS):
 	  $(MAKE) $(AM_MAKEFLAGS) "$$target-am" || exit 1; \
 	fi; test -z "$$fail"
 
-$(RECURSIVE_CLEAN_TARGETS):
+mostlyclean-recursive clean-recursive distclean-recursive \
+maintainer-clean-recursive:
 	@failcom='exit 1'; \
 	for f in x $$MAKEFLAGS; do \
 	  case $$f in \
@@ -246,8 +243,8 @@ ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	mkid -fID $$unique
 tags: TAGS
 
@@ -272,8 +269,8 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
 	  test -n "$$unique" || unique=$$empty_fix; \
 	  $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
@@ -283,12 +280,13 @@ ctags: CTAGS
 CTAGS: ctags-recursive $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 		$(TAGS_FILES) $(LISP)
 	tags=; \
+	here=`pwd`; \
 	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	test -z "$(CTAGS_ARGS)$$tags$$unique" \
 	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
 	     $$tags $$unique
@@ -302,21 +300,22 @@ distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
 distdir: $(DISTFILES)
-	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	list='$(DISTFILES)'; \
-	  dist_files=`for file in $$list; do echo $$file; done | \
-	  sed -e "s|^$$srcdirstrip/||;t" \
-	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
-	case $$dist_files in \
-	  */*) $(MKDIR_P) `echo "$$dist_files" | \
-			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
-			   sort -u` ;; \
-	esac; \
-	for file in $$dist_files; do \
+	@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
+	list='$(DISTFILES)'; for file in $$list; do \
+	  case $$file in \
+	    $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
+	    $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
+	  esac; \
 	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
+	  if test "$$dir" != "$$file" && test "$$dir" != "."; then \
+	    dir="/$$dir"; \
+	    $(mkdir_p) "$(distdir)$$dir"; \
+	  else \
+	    dir=''; \
+	  fi; \
 	  if test -d $$d/$$file; then \
-	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
 	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
 	      cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
 	    fi; \
@@ -330,7 +329,7 @@ distdir: $(DISTFILES)
 	list='$(DIST_SUBDIRS)'; for subdir in $$list; do \
 	  if test "$$subdir" = .; then :; else \
 	    test -d "$(distdir)/$$subdir" \
-	    || $(MKDIR_P) "$(distdir)/$$subdir" \
+	    || $(mkdir_p) "$(distdir)/$$subdir" \
 	    || exit 1; \
 	    distdir=`$(am__cd) $(distdir) && pwd`; \
 	    top_distdir=`$(am__cd) $(top_distdir) && pwd`; \
@@ -338,8 +337,6 @@ distdir: $(DISTFILES)
 	      $(MAKE) $(AM_MAKEFLAGS) \
 	        top_distdir="$$top_distdir" \
 	        distdir="$$distdir/$$subdir" \
-		am__remove_distdir=: \
-		am__skip_length_check=: \
 	        distdir) \
 	      || exit 1; \
 	  fi; \
@@ -393,20 +390,12 @@ info-am:
 
 install-data-am:
 
-install-dvi: install-dvi-recursive
-
 install-exec-am:
 
-install-html: install-html-recursive
-
 install-info: install-info-recursive
 
 install-man:
 
-install-pdf: install-pdf-recursive
-
-install-ps: install-ps-recursive
-
 installcheck-am:
 
 maintainer-clean: maintainer-clean-recursive
@@ -425,23 +414,21 @@ ps: ps-recursive
 
 ps-am:
 
-uninstall-am:
+uninstall-am: uninstall-info-am
 
-.MAKE: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) install-am \
-	install-strip
+uninstall-info: uninstall-info-recursive
 
-.PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \
-	all all-am check check-am clean clean-generic ctags \
-	ctags-recursive distclean distclean-generic distclean-tags \
+.PHONY: $(RECURSIVE_TARGETS) CTAGS GTAGS all all-am check check-am \
+	clean clean-generic clean-recursive ctags ctags-recursive \
+	distclean distclean-generic distclean-recursive distclean-tags \
 	distdir dvi dvi-am html html-am info info-am install \
-	install-am install-data install-data-am install-dvi \
-	install-dvi-am install-exec install-exec-am install-html \
-	install-html-am install-info install-info-am install-man \
-	install-pdf install-pdf-am install-ps install-ps-am \
+	install-am install-data install-data-am install-exec \
+	install-exec-am install-info install-info-am install-man \
 	install-strip installcheck installcheck-am installdirs \
 	installdirs-am maintainer-clean maintainer-clean-generic \
-	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
-	tags-recursive uninstall uninstall-am
+	maintainer-clean-recursive mostlyclean mostlyclean-generic \
+	mostlyclean-recursive pdf pdf-am ps ps-am tags tags-recursive \
+	uninstall uninstall-am uninstall-info-am
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/enet/include/enet/Makefile.in b/enet/include/enet/Makefile.in
index e787c1f..bbec556 100644
--- a/enet/include/enet/Makefile.in
+++ b/enet/include/enet/Makefile.in
@@ -1,8 +1,8 @@
-# Makefile.in generated by automake 1.10.1 from Makefile.am.
+# Makefile.in generated by automake 1.9.6 from Makefile.am.
 # @configure_input@
 
 # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
-# 2003, 2004, 2005, 2006, 2007, 2008  Free Software Foundation, Inc.
+# 2003, 2004, 2005  Free Software Foundation, Inc.
 # This Makefile.in is free software; the Free Software Foundation
 # gives unlimited permission to copy and/or distribute it,
 # with or without modifications, as long as this notice is preserved.
@@ -14,11 +14,15 @@
 
 @SET_MAKE@
 
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
 VPATH = @srcdir@
 pkgdatadir = $(datadir)/@PACKAGE@
 pkglibdir = $(libdir)/@PACKAGE@
 pkgincludedir = $(includedir)/@PACKAGE@
+top_builddir = ../..
 am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+INSTALL = @INSTALL@
 install_sh_DATA = $(install_sh) -c -m 644
 install_sh_PROGRAM = $(install_sh) -c
 install_sh_SCRIPT = $(install_sh) -c
@@ -37,7 +41,7 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 am__aclocal_m4_deps = $(top_srcdir)/configure.in
 am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
 	$(ACLOCAL_M4)
-mkinstalldirs = $(install_sh) -d
+mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
 CONFIG_CLEAN_FILES =
 SOURCES =
 DIST_SOURCES =
@@ -54,6 +58,8 @@ ETAGS = etags
 CTAGS = ctags
 DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
 ACLOCAL = @ACLOCAL@
+AMDEP_FALSE = @AMDEP_FALSE@
+AMDEP_TRUE = @AMDEP_TRUE@
 AMTAR = @AMTAR@
 AUTOCONF = @AUTOCONF@
 AUTOHEADER = @AUTOHEADER@
@@ -73,7 +79,6 @@ ECHO_T = @ECHO_T@
 EGREP = @EGREP@
 EXEEXT = @EXEEXT@
 GREP = @GREP@
-INSTALL = @INSTALL@
 INSTALL_DATA = @INSTALL_DATA@
 INSTALL_PROGRAM = @INSTALL_PROGRAM@
 INSTALL_SCRIPT = @INSTALL_SCRIPT@
@@ -83,7 +88,6 @@ LIBOBJS = @LIBOBJS@
 LIBS = @LIBS@
 LTLIBOBJS = @LTLIBOBJS@
 MAKEINFO = @MAKEINFO@
-MKDIR_P = @MKDIR_P@
 OBJEXT = @OBJEXT@
 PACKAGE = @PACKAGE@
 PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
@@ -97,11 +101,9 @@ SET_MAKE = @SET_MAKE@
 SHELL = @SHELL@
 STRIP = @STRIP@
 VERSION = @VERSION@
-abs_builddir = @abs_builddir@
-abs_srcdir = @abs_srcdir@
-abs_top_builddir = @abs_top_builddir@
-abs_top_srcdir = @abs_top_srcdir@
 ac_ct_CC = @ac_ct_CC@
+am__fastdepCC_FALSE = @am__fastdepCC_FALSE@
+am__fastdepCC_TRUE = @am__fastdepCC_TRUE@
 am__include = @am__include@
 am__leading_dot = @am__leading_dot@
 am__quote = @am__quote@
@@ -109,7 +111,6 @@ am__tar = @am__tar@
 am__untar = @am__untar@
 bindir = @bindir@
 build_alias = @build_alias@
-builddir = @builddir@
 datadir = @datadir@
 datarootdir = @datarootdir@
 docdir = @docdir@
@@ -133,11 +134,8 @@ program_transform_name = @program_transform_name@
 psdir = @psdir@
 sbindir = @sbindir@
 sharedstatedir = @sharedstatedir@
-srcdir = @srcdir@
 sysconfdir = @sysconfdir@
 target_alias = @target_alias@
-top_builddir = @top_builddir@
-top_srcdir = @top_srcdir@
 libenetincludedir = $(includedir)/enet
 libenetinclude_HEADERS = \
 	types.h \
@@ -182,9 +180,10 @@ $(top_srcdir)/configure:  $(am__configure_deps)
 	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
 $(ACLOCAL_M4):  $(am__aclocal_m4_deps)
 	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+uninstall-info-am:
 install-libenetincludeHEADERS: $(libenetinclude_HEADERS)
 	@$(NORMAL_INSTALL)
-	test -z "$(libenetincludedir)" || $(MKDIR_P) "$(DESTDIR)$(libenetincludedir)"
+	test -z "$(libenetincludedir)" || $(mkdir_p) "$(DESTDIR)$(libenetincludedir)"
 	@list='$(libenetinclude_HEADERS)'; for p in $$list; do \
 	  if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
 	  f=$(am__strip_dir) \
@@ -205,8 +204,8 @@ ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES)
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	mkid -fID $$unique
 tags: TAGS
 
@@ -218,8 +217,8 @@ TAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \
 	  test -n "$$unique" || unique=$$empty_fix; \
 	  $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
@@ -229,12 +228,13 @@ ctags: CTAGS
 CTAGS:  $(HEADERS) $(SOURCES)  $(TAGS_DEPENDENCIES) \
 		$(TAGS_FILES) $(LISP)
 	tags=; \
+	here=`pwd`; \
 	list='$(SOURCES) $(HEADERS)  $(LISP) $(TAGS_FILES)'; \
 	unique=`for i in $$list; do \
 	    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
 	  done | \
-	  $(AWK) '{ files[$$0] = 1; nonempty = 1; } \
-	      END { if (nonempty) { for (i in files) print i; }; }'`; \
+	  $(AWK) '    { files[$$0] = 1; } \
+	       END { for (i in files) print i; }'`; \
 	test -z "$(CTAGS_ARGS)$$tags$$unique" \
 	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
 	     $$tags $$unique
@@ -248,21 +248,22 @@ distclean-tags:
 	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
 
 distdir: $(DISTFILES)
-	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
-	list='$(DISTFILES)'; \
-	  dist_files=`for file in $$list; do echo $$file; done | \
-	  sed -e "s|^$$srcdirstrip/||;t" \
-	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
-	case $$dist_files in \
-	  */*) $(MKDIR_P) `echo "$$dist_files" | \
-			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
-			   sort -u` ;; \
-	esac; \
-	for file in $$dist_files; do \
+	@srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \
+	list='$(DISTFILES)'; for file in $$list; do \
+	  case $$file in \
+	    $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \
+	    $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \
+	  esac; \
 	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
+	  if test "$$dir" != "$$file" && test "$$dir" != "."; then \
+	    dir="/$$dir"; \
+	    $(mkdir_p) "$(distdir)$$dir"; \
+	  else \
+	    dir=''; \
+	  fi; \
 	  if test -d $$d/$$file; then \
-	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
 	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
 	      cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
 	    fi; \
@@ -278,7 +279,7 @@ check: check-am
 all-am: Makefile $(HEADERS)
 installdirs:
 	for dir in "$(DESTDIR)$(libenetincludedir)"; do \
-	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	  test -z "$$dir" || $(mkdir_p) "$$dir"; \
 	done
 install: install-am
 install-exec: install-exec-am
@@ -324,20 +325,12 @@ info-am:
 
 install-data-am: install-libenetincludeHEADERS
 
-install-dvi: install-dvi-am
-
 install-exec-am:
 
-install-html: install-html-am
-
 install-info: install-info-am
 
 install-man:
 
-install-pdf: install-pdf-am
-
-install-ps: install-ps-am
-
 installcheck-am:
 
 maintainer-clean: maintainer-clean-am
@@ -356,21 +349,18 @@ ps: ps-am
 
 ps-am:
 
-uninstall-am: uninstall-libenetincludeHEADERS
-
-.MAKE: install-am install-strip
+uninstall-am: uninstall-info-am uninstall-libenetincludeHEADERS
 
 .PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \
 	ctags distclean distclean-generic distclean-tags distdir dvi \
 	dvi-am html html-am info info-am install install-am \
-	install-data install-data-am install-dvi install-dvi-am \
-	install-exec install-exec-am install-html install-html-am \
+	install-data install-data-am install-exec install-exec-am \
 	install-info install-info-am install-libenetincludeHEADERS \
-	install-man install-pdf install-pdf-am install-ps \
-	install-ps-am install-strip installcheck installcheck-am \
+	install-man install-strip installcheck installcheck-am \
 	installdirs maintainer-clean maintainer-clean-generic \
 	mostlyclean mostlyclean-generic pdf pdf-am ps ps-am tags \
-	uninstall uninstall-am uninstall-libenetincludeHEADERS
+	uninstall uninstall-am uninstall-info-am \
+	uninstall-libenetincludeHEADERS
 
 # Tell versions [3.59,3.63) of GNU make to not export all variables.
 # Otherwise a system limit (for SysV at least) may be exceeded.
diff --git a/enet/include/enet/enet.h b/enet/include/enet/enet.h
index cf7b2d0..14106aa 100644
--- a/enet/include/enet/enet.h
+++ b/enet/include/enet/enet.h
@@ -46,8 +46,7 @@ typedef enum
    ENET_SOCKOPT_NONBLOCK  = 1,
    ENET_SOCKOPT_BROADCAST = 2,
    ENET_SOCKOPT_RCVBUF    = 3,
-   ENET_SOCKOPT_SNDBUF    = 4,
-   ENET_SOCKOPT_REUSEADDR = 5
+   ENET_SOCKOPT_SNDBUF    = 4
 } ENetSocketOption;
 
 enum
@@ -404,9 +403,7 @@ ENET_API void enet_time_set (enet_uint32);
 /** @defgroup socket ENet socket functions
     @{
 */
-ENET_API ENetSocket enet_socket_create (ENetSocketType);
-ENET_API int        enet_socket_bind (ENetSocket, const ENetAddress *);
-ENET_API int        enet_socket_listen (ENetSocket, int);
+ENET_API ENetSocket enet_socket_create (ENetSocketType, const ENetAddress *);
 ENET_API ENetSocket enet_socket_accept (ENetSocket, ENetAddress *);
 ENET_API int        enet_socket_connect (ENetSocket, const ENetAddress *);
 ENET_API int        enet_socket_send (ENetSocket, const ENetAddress *, const ENetBuffer *, size_t);
@@ -414,7 +411,6 @@ ENET_API int        enet_socket_receive (ENetSocket, ENetAddress *, ENetBuffer *
 ENET_API int        enet_socket_wait (ENetSocket, enet_uint32 *, enet_uint32);
 ENET_API int        enet_socket_set_option (ENetSocket, ENetSocketOption, int);
 ENET_API void       enet_socket_destroy (ENetSocket);
-ENET_API int        enet_socketset_select (ENetSocket, ENetSocketSet *, ENetSocketSet *, enet_uint32);
 
 /** @} */
 
diff --git a/enet/include/enet/unix.h b/enet/include/enet/unix.h
index 087015e..b20fecd 100644
--- a/enet/include/enet/unix.h
+++ b/enet/include/enet/unix.h
@@ -6,10 +6,8 @@
 #define __ENET_UNIX_H__
 
 #include <stdlib.h>
-#include <sys/time.h>
 #include <sys/types.h>
 #include <netinet/in.h>
-#include <unistd.h>
 
 typedef int ENetSocket;
 
@@ -34,12 +32,5 @@ typedef struct
 
 #define ENET_API extern
 
-typedef fd_set ENetSocketSet;
-
-#define ENET_SOCKETSET_EMPTY(sockset)          FD_ZERO (& (sockset))
-#define ENET_SOCKETSET_ADD(sockset, socket)    FD_SET (socket, & (sockset))
-#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLEAR (socket, & (sockset))
-#define ENET_SOCKETSET_CHECK(sockset, socket)  FD_ISSET (socket, & (sockset))
-    
 #endif /* __ENET_UNIX_H__ */
 
diff --git a/enet/include/enet/win32.h b/enet/include/enet/win32.h
index 0e1cf0c..310ecd5 100644
--- a/enet/include/enet/win32.h
+++ b/enet/include/enet/win32.h
@@ -46,13 +46,6 @@ typedef struct
 #define ENET_API extern
 #endif /* ENET_DLL */
 
-typedef fd_set ENetSocketSet;
-
-#define ENET_SOCKETSET_EMPTY(sockset)          FD_ZERO (& (sockset))
-#define ENET_SOCKETSET_ADD(sockset, socket)    FD_SET (socket, & (sockset))
-#define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLEAR (socket, & (sockset))
-#define ENET_SOCKETSET_CHECK(sockset, socket)  FD_ISSET (socket, & (sockset))
-
 #endif /* __ENET_WIN32_H__ */
 
 
diff --git a/enet/install-sh b/enet/install-sh
index a5897de..e9de238 100755
--- a/enet/install-sh
+++ b/enet/install-sh
@@ -1,519 +1,251 @@
 #!/bin/sh
-# install - install a program, script, or datafile
-
-scriptversion=2006-12-25.00
-
-# This originates from X11R5 (mit/util/scripts/install.sh), which was
-# later released in X11R6 (xc/config/util/install.sh) with the
-# following copyright and license.
-#
-# Copyright (C) 1994 X Consortium
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to
-# deal in the Software without restriction, including without limitation the
-# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-# sell copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
-# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
-# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC-
-# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 #
-# Except as contained in this notice, the name of the X Consortium shall not
-# be used in advertising or otherwise to promote the sale, use or other deal-
-# ings in this Software without prior written authorization from the X Consor-
-# tium.
+# install - install a program, script, or datafile
+# This comes from X11R5 (mit/util/scripts/install.sh).
 #
+# Copyright 1991 by the Massachusetts Institute of Technology
 #
-# FSF changes to this file are in the public domain.
+# Permission to use, copy, modify, distribute, and sell this software and its
+# documentation for any purpose is hereby granted without fee, provided that
+# the above copyright notice appear in all copies and that both that
+# copyright notice and this permission notice appear in supporting
+# documentation, and that the name of M.I.T. not be used in advertising or
+# publicity pertaining to distribution of the software without specific,
+# written prior permission.  M.I.T. makes no representations about the
+# suitability of this software for any purpose.  It is provided "as is"
+# without express or implied warranty.
 #
 # Calling this script install-sh is preferred over install.sh, to prevent
 # `make' implicit rules from creating a file called install from it
 # when there is no Makefile.
 #
 # This script is compatible with the BSD install script, but was written
-# from scratch.
+# from scratch.  It can only install one file at a time, a restriction
+# shared with many OS's install programs.
 
-nl='
-'
-IFS=" ""	$nl"
 
 # set DOITPROG to echo to test this script
 
 # Don't use :- since 4.3BSD and earlier shells don't like it.
-doit=${DOITPROG-}
-if test -z "$doit"; then
-  doit_exec=exec
+doit="${DOITPROG-}"
+
+
+# put in absolute paths if you don't have them in your path; or use env. vars.
+
+mvprog="${MVPROG-mv}"
+cpprog="${CPPROG-cp}"
+chmodprog="${CHMODPROG-chmod}"
+chownprog="${CHOWNPROG-chown}"
+chgrpprog="${CHGRPPROG-chgrp}"
+stripprog="${STRIPPROG-strip}"
+rmprog="${RMPROG-rm}"
+mkdirprog="${MKDIRPROG-mkdir}"
+
+transformbasename=""
+transform_arg=""
+instcmd="$mvprog"
+chmodcmd="$chmodprog 0755"
+chowncmd=""
+chgrpcmd=""
+stripcmd=""
+rmcmd="$rmprog -f"
+mvcmd="$mvprog"
+src=""
+dst=""
+dir_arg=""
+
+while [ x"$1" != x ]; do
+    case $1 in
+	-c) instcmd="$cpprog"
+	    shift
+	    continue;;
+
+	-d) dir_arg=true
+	    shift
+	    continue;;
+
+	-m) chmodcmd="$chmodprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-o) chowncmd="$chownprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-g) chgrpcmd="$chgrpprog $2"
+	    shift
+	    shift
+	    continue;;
+
+	-s) stripcmd="$stripprog"
+	    shift
+	    continue;;
+
+	-t=*) transformarg=`echo $1 | sed 's/-t=//'`
+	    shift
+	    continue;;
+
+	-b=*) transformbasename=`echo $1 | sed 's/-b=//'`
+	    shift
+	    continue;;
+
+	*)  if [ x"$src" = x ]
+	    then
+		src=$1
+	    else
+		# this colon is to work around a 386BSD /bin/sh bug
+		:
+		dst=$1
+	    fi
+	    shift
+	    continue;;
+    esac
+done
+
+if [ x"$src" = x ]
+then
+	echo "install:	no input file specified"
+	exit 1
 else
-  doit_exec=$doit
+	true
 fi
 
-# Put in absolute file names if you don't have them in your path;
-# or use environment vars.
-
-chgrpprog=${CHGRPPROG-chgrp}
-chmodprog=${CHMODPROG-chmod}
-chownprog=${CHOWNPROG-chown}
-cmpprog=${CMPPROG-cmp}
-cpprog=${CPPROG-cp}
-mkdirprog=${MKDIRPROG-mkdir}
-mvprog=${MVPROG-mv}
-rmprog=${RMPROG-rm}
-stripprog=${STRIPPROG-strip}
-
-posix_glob='?'
-initialize_posix_glob='
-  test "$posix_glob" != "?" || {
-    if (set -f) 2>/dev/null; then
-      posix_glob=
-    else
-      posix_glob=:
-    fi
-  }
-'
-
-posix_mkdir=
+if [ x"$dir_arg" != x ]; then
+	dst=$src
+	src=""
+	
+	if [ -d $dst ]; then
+		instcmd=:
+		chmodcmd=""
+	else
+		instcmd=mkdir
+	fi
+else
 
-# Desired mode of installed file.
-mode=0755
+# Waiting for this to be detected by the "$instcmd $src $dsttmp" command
+# might cause directories to be created, which would be especially bad 
+# if $src (and thus $dsttmp) contains '*'.
 
-chgrpcmd=
-chmodcmd=$chmodprog
-chowncmd=
-mvcmd=$mvprog
-rmcmd="$rmprog -f"
-stripcmd=
+	if [ -f $src -o -d $src ]
+	then
+		true
+	else
+		echo "install:  $src does not exist"
+		exit 1
+	fi
+	
+	if [ x"$dst" = x ]
+	then
+		echo "install:	no destination specified"
+		exit 1
+	else
+		true
+	fi
 
-src=
-dst=
-dir_arg=
-dst_arg=
+# If destination is a directory, append the input filename; if your system
+# does not like double slashes in filenames, you may need to add some logic
 
-copy_on_change=false
-no_target_directory=
+	if [ -d $dst ]
+	then
+		dst="$dst"/`basename $src`
+	else
+		true
+	fi
+fi
 
-usage="\
-Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE
-   or: $0 [OPTION]... SRCFILES... DIRECTORY
-   or: $0 [OPTION]... -t DIRECTORY SRCFILES...
-   or: $0 [OPTION]... -d DIRECTORIES...
+## this sed command emulates the dirname command
+dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`
 
-In the 1st form, copy SRCFILE to DSTFILE.
-In the 2nd and 3rd, copy all SRCFILES to DIRECTORY.
-In the 4th, create DIRECTORIES.
+# Make sure that the destination directory exists.
+#  this part is taken from Noah Friedman's mkinstalldirs script
 
-Options:
-     --help     display this help and exit.
-     --version  display version info and exit.
+# Skip lots of stat calls in the usual case.
+if [ ! -d "$dstdir" ]; then
+defaultIFS='	
+'
+IFS="${IFS-${defaultIFS}}"
 
-  -c            (ignored)
-  -C            install only if different (preserve the last data modification time)
-  -d            create directories instead of installing files.
-  -g GROUP      $chgrpprog installed files to GROUP.
-  -m MODE       $chmodprog installed files to MODE.
-  -o USER       $chownprog installed files to USER.
-  -s            $stripprog installed files.
-  -t DIRECTORY  install into DIRECTORY.
-  -T            report an error if DSTFILE is a directory.
+oIFS="${IFS}"
+# Some sh's can't handle IFS=/ for some reason.
+IFS='%'
+set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
+IFS="${oIFS}"
 
-Environment variables override the default commands:
-  CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG
-  RMPROG STRIPPROG
-"
+pathcomp=''
 
-while test $# -ne 0; do
-  case $1 in
-    -c) ;;
+while [ $# -ne 0 ] ; do
+	pathcomp="${pathcomp}${1}"
+	shift
 
-    -C) copy_on_change=true;;
+	if [ ! -d "${pathcomp}" ] ;
+        then
+		$mkdirprog "${pathcomp}"
+	else
+		true
+	fi
 
-    -d) dir_arg=true;;
+	pathcomp="${pathcomp}/"
+done
+fi
 
-    -g) chgrpcmd="$chgrpprog $2"
-	shift;;
+if [ x"$dir_arg" != x ]
+then
+	$doit $instcmd $dst &&
 
-    --help) echo "$usage"; exit $?;;
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi
+else
 
-    -m) mode=$2
-	case $mode in
-	  *' '* | *'	'* | *'
-'*	  | *'*'* | *'?'* | *'['*)
-	    echo "$0: invalid mode: $mode" >&2
-	    exit 1;;
-	esac
-	shift;;
+# If we're going to rename the final executable, determine the name now.
 
-    -o) chowncmd="$chownprog $2"
-	shift;;
+	if [ x"$transformarg" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		dstfile=`basename $dst $transformbasename | 
+			sed $transformarg`$transformbasename
+	fi
 
-    -s) stripcmd=$stripprog;;
+# don't allow the sed command to completely eliminate the filename
 
-    -t) dst_arg=$2
-	shift;;
+	if [ x"$dstfile" = x ] 
+	then
+		dstfile=`basename $dst`
+	else
+		true
+	fi
 
-    -T) no_target_directory=true;;
+# Make a temp file name in the proper directory.
 
-    --version) echo "$0 $scriptversion"; exit $?;;
+	dsttmp=$dstdir/#inst.$$#
 
-    --)	shift
-	break;;
+# Move or copy the file name to the temp name
 
-    -*)	echo "$0: invalid option: $1" >&2
-	exit 1;;
+	$doit $instcmd $src $dsttmp &&
 
-    *)  break;;
-  esac
-  shift
-done
+	trap "rm -f ${dsttmp}" 0 &&
 
-if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then
-  # When -d is used, all remaining arguments are directories to create.
-  # When -t is used, the destination is already specified.
-  # Otherwise, the last argument is the destination.  Remove it from $@.
-  for arg
-  do
-    if test -n "$dst_arg"; then
-      # $@ is not empty: it contains at least $arg.
-      set fnord "$@" "$dst_arg"
-      shift # fnord
-    fi
-    shift # arg
-    dst_arg=$arg
-  done
-fi
+# and set any options; do chmod last to preserve setuid bits
 
-if test $# -eq 0; then
-  if test -z "$dir_arg"; then
-    echo "$0: no input file specified." >&2
-    exit 1
-  fi
-  # It's OK to call `install-sh -d' without argument.
-  # This can happen when creating conditional directories.
-  exit 0
-fi
+# If any of these fail, we abort the whole thing.  If we want to
+# ignore errors from any of these, just make sure not to ignore
+# errors from the above "$doit $instcmd $src $dsttmp" command.
 
-if test -z "$dir_arg"; then
-  trap '(exit $?); exit' 1 2 13 15
-
-  # Set umask so as not to create temps with too-generous modes.
-  # However, 'strip' requires both read and write access to temps.
-  case $mode in
-    # Optimize common cases.
-    *644) cp_umask=133;;
-    *755) cp_umask=22;;
-
-    *[0-7])
-      if test -z "$stripcmd"; then
-	u_plus_rw=
-      else
-	u_plus_rw='% 200'
-      fi
-      cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;;
-    *)
-      if test -z "$stripcmd"; then
-	u_plus_rw=
-      else
-	u_plus_rw=,u+rw
-      fi
-      cp_umask=$mode$u_plus_rw;;
-  esac
-fi
+	if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi &&
+	if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi &&
+	if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi &&
+	if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi &&
 
-for src
-do
-  # Protect names starting with `-'.
-  case $src in
-    -*) src=./$src;;
-  esac
-
-  if test -n "$dir_arg"; then
-    dst=$src
-    dstdir=$dst
-    test -d "$dstdir"
-    dstdir_status=$?
-  else
-
-    # Waiting for this to be detected by the "$cpprog $src $dsttmp" command
-    # might cause directories to be created, which would be especially bad
-    # if $src (and thus $dsttmp) contains '*'.
-    if test ! -f "$src" && test ! -d "$src"; then
-      echo "$0: $src does not exist." >&2
-      exit 1
-    fi
-
-    if test -z "$dst_arg"; then
-      echo "$0: no destination specified." >&2
-      exit 1
-    fi
-
-    dst=$dst_arg
-    # Protect names starting with `-'.
-    case $dst in
-      -*) dst=./$dst;;
-    esac
+# Now rename the file to the real destination.
 
-    # If destination is a directory, append the input filename; won't work
-    # if double slashes aren't ignored.
-    if test -d "$dst"; then
-      if test -n "$no_target_directory"; then
-	echo "$0: $dst_arg: Is a directory" >&2
-	exit 1
-      fi
-      dstdir=$dst
-      dst=$dstdir/`basename "$src"`
-      dstdir_status=0
-    else
-      # Prefer dirname, but fall back on a substitute if dirname fails.
-      dstdir=`
-	(dirname "$dst") 2>/dev/null ||
-	expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
-	     X"$dst" : 'X\(//\)[^/]' \| \
-	     X"$dst" : 'X\(//\)$' \| \
-	     X"$dst" : 'X\(/\)' \| . 2>/dev/null ||
-	echo X"$dst" |
-	    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\/\)[^/].*/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\/\)$/{
-		   s//\1/
-		   q
-		 }
-		 /^X\(\/\).*/{
-		   s//\1/
-		   q
-		 }
-		 s/.*/./; q'
-      `
-
-      test -d "$dstdir"
-      dstdir_status=$?
-    fi
-  fi
-
-  obsolete_mkdir_used=false
-
-  if test $dstdir_status != 0; then
-    case $posix_mkdir in
-      '')
-	# Create intermediate dirs using mode 755 as modified by the umask.
-	# This is like FreeBSD 'install' as of 1997-10-28.
-	umask=`umask`
-	case $stripcmd.$umask in
-	  # Optimize common cases.
-	  *[2367][2367]) mkdir_umask=$umask;;
-	  .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;;
-
-	  *[0-7])
-	    mkdir_umask=`expr $umask + 22 \
-	      - $umask % 100 % 40 + $umask % 20 \
-	      - $umask % 10 % 4 + $umask % 2
-	    `;;
-	  *) mkdir_umask=$umask,go-w;;
-	esac
-
-	# With -d, create the new directory with the user-specified mode.
-	# Otherwise, rely on $mkdir_umask.
-	if test -n "$dir_arg"; then
-	  mkdir_mode=-m$mode
-	else
-	  mkdir_mode=
-	fi
+	$doit $rmcmd -f $dstdir/$dstfile &&
+	$doit $mvcmd $dsttmp $dstdir/$dstfile 
 
-	posix_mkdir=false
-	case $umask in
-	  *[123567][0-7][0-7])
-	    # POSIX mkdir -p sets u+wx bits regardless of umask, which
-	    # is incompatible with FreeBSD 'install' when (umask & 300) != 0.
-	    ;;
-	  *)
-	    tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$
-	    trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0
-
-	    if (umask $mkdir_umask &&
-		exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1
-	    then
-	      if test -z "$dir_arg" || {
-		   # Check for POSIX incompatibilities with -m.
-		   # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or
-		   # other-writeable bit of parent directory when it shouldn't.
-		   # FreeBSD 6.1 mkdir -m -p sets mode of existing directory.
-		   ls_ld_tmpdir=`ls -ld "$tmpdir"`
-		   case $ls_ld_tmpdir in
-		     d????-?r-*) different_mode=700;;
-		     d????-?--*) different_mode=755;;
-		     *) false;;
-		   esac &&
-		   $mkdirprog -m$different_mode -p -- "$tmpdir" && {
-		     ls_ld_tmpdir_1=`ls -ld "$tmpdir"`
-		     test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1"
-		   }
-		 }
-	      then posix_mkdir=:
-	      fi
-	      rmdir "$tmpdir/d" "$tmpdir"
-	    else
-	      # Remove any dirs left behind by ancient mkdir implementations.
-	      rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null
-	    fi
-	    trap '' 0;;
-	esac;;
-    esac
+fi &&
 
-    if
-      $posix_mkdir && (
-	umask $mkdir_umask &&
-	$doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir"
-      )
-    then :
-    else
-
-      # The umask is ridiculous, or mkdir does not conform to POSIX,
-      # or it failed possibly due to a race condition.  Create the
-      # directory the slow way, step by step, checking for races as we go.
-
-      case $dstdir in
-	/*) prefix='/';;
-	-*) prefix='./';;
-	*)  prefix='';;
-      esac
-
-      eval "$initialize_posix_glob"
-
-      oIFS=$IFS
-      IFS=/
-      $posix_glob set -f
-      set fnord $dstdir
-      shift
-      $posix_glob set +f
-      IFS=$oIFS
-
-      prefixes=
-
-      for d
-      do
-	test -z "$d" && continue
-
-	prefix=$prefix$d
-	if test -d "$prefix"; then
-	  prefixes=
-	else
-	  if $posix_mkdir; then
-	    (umask=$mkdir_umask &&
-	     $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break
-	    # Don't fail if two instances are running concurrently.
-	    test -d "$prefix" || exit 1
-	  else
-	    case $prefix in
-	      *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;;
-	      *) qprefix=$prefix;;
-	    esac
-	    prefixes="$prefixes '$qprefix'"
-	  fi
-	fi
-	prefix=$prefix/
-      done
-
-      if test -n "$prefixes"; then
-	# Don't fail if two instances are running concurrently.
-	(umask $mkdir_umask &&
-	 eval "\$doit_exec \$mkdirprog $prefixes") ||
-	  test -d "$dstdir" || exit 1
-	obsolete_mkdir_used=true
-      fi
-    fi
-  fi
-
-  if test -n "$dir_arg"; then
-    { test -z "$chowncmd" || $doit $chowncmd "$dst"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } &&
-    { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false ||
-      test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1
-  else
-
-    # Make a couple of temp file names in the proper directory.
-    dsttmp=$dstdir/_inst.$$_
-    rmtmp=$dstdir/_rm.$$_
-
-    # Trap to clean up those temp files at exit.
-    trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0
-
-    # Copy the file name to the temp name.
-    (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") &&
-
-    # and set any options; do chmod last to preserve setuid bits.
-    #
-    # If any of these fail, we abort the whole thing.  If we want to
-    # ignore errors from any of these, just make sure not to ignore
-    # errors from the above "$doit $cpprog $src $dsttmp" command.
-    #
-    { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } &&
-    { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } &&
-    { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } &&
-    { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } &&
-
-    # If -C, don't bother to copy if it wouldn't change the file.
-    if $copy_on_change &&
-       old=`LC_ALL=C ls -dlL "$dst"	2>/dev/null` &&
-       new=`LC_ALL=C ls -dlL "$dsttmp"	2>/dev/null` &&
-
-       eval "$initialize_posix_glob" &&
-       $posix_glob set -f &&
-       set X $old && old=:$2:$4:$5:$6 &&
-       set X $new && new=:$2:$4:$5:$6 &&
-       $posix_glob set +f &&
-
-       test "$old" = "$new" &&
-       $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1
-    then
-      rm -f "$dsttmp"
-    else
-      # Rename the file to the real destination.
-      $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null ||
-
-      # The rename failed, perhaps because mv can't rename something else
-      # to itself, or perhaps because mv is so ancient that it does not
-      # support -f.
-      {
-	# Now remove or move aside any old file at destination location.
-	# We try this two ways since rm can't unlink itself on some
-	# systems and the destination file might be busy for other
-	# reasons.  In this case, the final cleanup might fail but the new
-	# file should still install successfully.
-	{
-	  test ! -f "$dst" ||
-	  $doit $rmcmd -f "$dst" 2>/dev/null ||
-	  { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null &&
-	    { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; }
-	  } ||
-	  { echo "$0: cannot unlink or rename $dst" >&2
-	    (exit 1); exit 1
-	  }
-	} &&
-
-	# Now rename the file to the real destination.
-	$doit $mvcmd "$dsttmp" "$dst"
-      }
-    fi || exit 1
-
-    trap '' 0
-  fi
-done
 
-# Local variables:
-# eval: (add-hook 'write-file-hooks 'time-stamp)
-# time-stamp-start: "scriptversion="
-# time-stamp-format: "%:y-%02m-%02d.%02H"
-# time-stamp-end: "$"
-# End:
+exit 0
diff --git a/enet/missing b/enet/missing
index 1c8ff70..64b5f90 100755
--- a/enet/missing
+++ b/enet/missing
@@ -1,9 +1,9 @@
 #! /bin/sh
 # Common stub for a few missing GNU programs while installing.
 
-scriptversion=2006-05-10.23
+scriptversion=2004-09-07.08
 
-# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006
+# Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004
 #   Free Software Foundation, Inc.
 # Originally by Fran,cois Pinard <pinard at iro.umontreal.ca>, 1996.
 
@@ -19,8 +19,8 @@ scriptversion=2006-05-10.23
 
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301, USA.
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
 
 # As a special exception to the GNU General Public License, if you
 # distribute this file as part of a program that contains a
@@ -33,8 +33,6 @@ if test $# -eq 0; then
 fi
 
 run=:
-sed_output='s/.* --output[ =]\([^ ]*\).*/\1/p'
-sed_minuso='s/.* -o \([^ ]*\).*/\1/p'
 
 # In the cases where this matters, `missing' is being run in the
 # srcdir already.
@@ -46,7 +44,7 @@ fi
 
 msg="missing on your system"
 
-case $1 in
+case "$1" in
 --run)
   # Try to run requested program, and just exit if it succeeds.
   run=
@@ -79,7 +77,6 @@ Supported PROGRAM values:
   aclocal      touch file \`aclocal.m4'
   autoconf     touch file \`configure'
   autoheader   touch file \`config.h.in'
-  autom4te     touch the output file, or create a stub one
   automake     touch all \`Makefile.in' files
   bison        create \`y.tab.[ch]', if possible, from existing .[ch]
   flex         create \`lex.yy.c', if possible, from existing .c
@@ -90,12 +87,12 @@ Supported PROGRAM values:
   yacc         create \`y.tab.[ch]', if possible, from existing .[ch]
 
 Send bug reports to <bug-automake at gnu.org>."
-    exit $?
+    exit 0
     ;;
 
   -v|--v|--ve|--ver|--vers|--versi|--versio|--version)
     echo "missing $scriptversion (GNU Automake)"
-    exit $?
+    exit 0
     ;;
 
   -*)
@@ -109,7 +106,7 @@ esac
 # Now exit if we have it, but it failed.  Also exit now if we
 # don't have it and --version was passed (most likely to detect
 # the program).
-case $1 in
+case "$1" in
   lex|yacc)
     # Not GNU programs, they don't have --version.
     ;;
@@ -138,7 +135,7 @@ esac
 
 # If it does not exist, or fails to run (possibly an outdated version),
 # try to emulate it.
-case $1 in
+case "$1" in
   aclocal*)
     echo 1>&2 "\
 WARNING: \`$1' is $msg.  You should only need it if
@@ -167,7 +164,7 @@ WARNING: \`$1' is $msg.  You should only need it if
     test -z "$files" && files="config.h"
     touch_files=
     for f in $files; do
-      case $f in
+      case "$f" in
       *:*) touch_files="$touch_files "`echo "$f" |
 				       sed -e 's/^[^:]*://' -e 's/:.*//'`;;
       *) touch_files="$touch_files $f.in";;
@@ -195,8 +192,8 @@ WARNING: \`$1' is needed, but is $msg.
          You can get \`$1' as part of \`Autoconf' from any GNU
          archive site."
 
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'`
+    test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'`
     if test -f "$file"; then
 	touch $file
     else
@@ -217,25 +214,25 @@ WARNING: \`$1' $msg.  You should only need it if
          in order for those modifications to take effect.  You can get
          \`Bison' from any GNU archive site."
     rm -f y.tab.c y.tab.h
-    if test $# -ne 1; then
+    if [ $# -ne 1 ]; then
         eval LASTARG="\${$#}"
-	case $LASTARG in
+	case "$LASTARG" in
 	*.y)
 	    SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
-	    if test -f "$SRCFILE"; then
+	    if [ -f "$SRCFILE" ]; then
 	         cp "$SRCFILE" y.tab.c
 	    fi
 	    SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
-	    if test -f "$SRCFILE"; then
+	    if [ -f "$SRCFILE" ]; then
 	         cp "$SRCFILE" y.tab.h
 	    fi
 	  ;;
 	esac
     fi
-    if test ! -f y.tab.h; then
+    if [ ! -f y.tab.h ]; then
 	echo >y.tab.h
     fi
-    if test ! -f y.tab.c; then
+    if [ ! -f y.tab.c ]; then
 	echo 'main() { return 0; }' >y.tab.c
     fi
     ;;
@@ -247,18 +244,18 @@ WARNING: \`$1' is $msg.  You should only need it if
          in order for those modifications to take effect.  You can get
          \`Flex' from any GNU archive site."
     rm -f lex.yy.c
-    if test $# -ne 1; then
+    if [ $# -ne 1 ]; then
         eval LASTARG="\${$#}"
-	case $LASTARG in
+	case "$LASTARG" in
 	*.l)
 	    SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
-	    if test -f "$SRCFILE"; then
+	    if [ -f "$SRCFILE" ]; then
 	         cp "$SRCFILE" lex.yy.c
 	    fi
 	  ;;
 	esac
     fi
-    if test ! -f lex.yy.c; then
+    if [ ! -f lex.yy.c ]; then
 	echo 'main() { return 0; }' >lex.yy.c
     fi
     ;;
@@ -270,9 +267,11 @@ WARNING: \`$1' is $msg.  You should only need it if
 	 \`Help2man' package in order for those modifications to take
 	 effect.  You can get \`Help2man' from any GNU archive site."
 
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
-    if test -f "$file"; then
+    file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
+    if test -z "$file"; then
+	file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'`
+    fi
+    if [ -f "$file" ]; then
 	touch $file
     else
 	test -z "$file" || exec >$file
@@ -289,24 +288,11 @@ WARNING: \`$1' is $msg.  You should only need it if
          call might also be the consequence of using a buggy \`make' (AIX,
          DU, IRIX).  You might want to install the \`Texinfo' package or
          the \`GNU make' package.  Grab either from any GNU archive site."
-    # The file to touch is that specified with -o ...
-    file=`echo "$*" | sed -n "$sed_output"`
-    test -z "$file" && file=`echo "$*" | sed -n "$sed_minuso"`
+    file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
     if test -z "$file"; then
-      # ... or it is the one specified with @setfilename ...
-      infile=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
-      file=`sed -n '
-	/^@setfilename/{
-	  s/.* \([^ ]*\) *$/\1/
-	  p
-	  q
-	}' $infile`
-      # ... or it is derived from the source name (dir/f.texi becomes f.info)
-      test -z "$file" && file=`echo "$infile" | sed 's,.*/,,;s,.[^.]*$,,'`.info
+      file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
+      file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
     fi
-    # If the file does not exist, the user really needs makeinfo;
-    # let's fail without touching anything.
-    test -f $file || exit 1
     touch $file
     ;;
 
@@ -324,13 +310,13 @@ WARNING: \`$1' is $msg.  You should only need it if
     fi
     firstarg="$1"
     if shift; then
-	case $firstarg in
+	case "$firstarg" in
 	*o*)
 	    firstarg=`echo "$firstarg" | sed s/o//`
 	    tar "$firstarg" "$@" && exit 0
 	    ;;
 	esac
-	case $firstarg in
+	case "$firstarg" in
 	*h*)
 	    firstarg=`echo "$firstarg" | sed s/h//`
 	    tar "$firstarg" "$@" && exit 0
diff --git a/enet/mkinstalldirs b/enet/mkinstalldirs
new file mode 100755
index 0000000..c492104
--- /dev/null
+++ b/enet/mkinstalldirs
@@ -0,0 +1,40 @@
+#! /bin/sh
+# mkinstalldirs --- make directory hierarchy
+# Author: Noah Friedman <friedman at prep.ai.mit.edu>
+# Created: 1993-05-16
+# Public domain
+
+# $Id: mkinstalldirs,v 1.1 2006/03/07 05:56:13 eihrul Exp $
+
+errstatus=0
+
+for file
+do
+   set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
+   shift
+
+   pathcomp=
+   for d
+   do
+     pathcomp="$pathcomp$d"
+     case "$pathcomp" in
+       -* ) pathcomp=./$pathcomp ;;
+     esac
+
+     if test ! -d "$pathcomp"; then
+        echo "mkdir $pathcomp"
+
+        mkdir "$pathcomp" || lasterr=$?
+
+        if test ! -d "$pathcomp"; then
+  	  errstatus=$lasterr
+        fi
+     fi
+
+     pathcomp="$pathcomp/"
+   done
+done
+
+exit $errstatus
+
+# mkinstalldirs ends here
diff --git a/enet/packet.c b/enet/packet.c
index 7f18e49..e1ae9c4 100644
--- a/enet/packet.c
+++ b/enet/packet.c
@@ -91,28 +91,13 @@ enet_packet_resize (ENetPacket * packet, size_t dataLength)
 static int initializedCRC32 = 0;
 static enet_uint32 crcTable [256];
 
-static enet_uint32 
-reflect_crc (int val, int bits)
-{
-    int result = 0, bit;
-
-    for (bit = 0; bit < bits; bit ++)
-    {
-        if(val & 1) result |= 1 << (bits - 1 - bit); 
-        val >>= 1;
-    }
-
-    return result;
-}
-
-static void 
-initialize_crc32 ()
+static void initialize_crc32 ()
 {
     int byte;
 
     for (byte = 0; byte < 256; ++ byte)
     {
-        enet_uint32 crc = reflect_crc (byte, 8) << 24;
+        enet_uint32 crc = byte << 24;
         int offset;
 
         for(offset = 0; offset < 8; ++ offset)
@@ -123,7 +108,7 @@ initialize_crc32 ()
                 crc <<= 1;
         }
 
-        crcTable [byte] = reflect_crc (crc, 32);
+        crcTable [byte] = crc;
     }
 
     initializedCRC32 = 1;
@@ -143,7 +128,7 @@ enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 
         while (data < dataEnd)
         {
-            crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];        
+            crc = ((crc << 8) | * data ++) ^ crcTable [crc >> 24];        
         }
 
         ++ buffers;
diff --git a/enet/peer.c b/enet/peer.c
index a858eb0..f9d73fc 100644
--- a/enet/peer.c
+++ b/enet/peer.c
@@ -422,7 +422,6 @@ enet_peer_disconnect (ENetPeer * peer, enet_uint32 data)
 
     if (peer -> state == ENET_PEER_STATE_DISCONNECTING ||
         peer -> state == ENET_PEER_STATE_DISCONNECTED ||
-        peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || 
         peer -> state == ENET_PEER_STATE_ZOMBIE)
       return;
 
diff --git a/enet/protocol.c b/enet/protocol.c
index 47bf844..af82fd6 100644
--- a/enet/protocol.c
+++ b/enet/protocol.c
@@ -629,9 +629,6 @@ enet_protocol_handle_throttle_configure (ENetHost * host, ENetPeer * peer, const
 static int
 enet_protocol_handle_disconnect (ENetHost * host, ENetPeer * peer, const ENetProtocol * command)
 {
-    if (peer -> state == ENET_PEER_STATE_ZOMBIE || peer -> state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT)
-      return 0;
-
     enet_peer_reset_queues (peer);
 
     if (peer -> state == ENET_PEER_STATE_CONNECTION_SUCCEEDED)
diff --git a/enet/unix.c b/enet/unix.c
index 1ccdc4c..132ca1a 100644
--- a/enet/unix.c
+++ b/enet/unix.c
@@ -152,41 +152,37 @@ enet_address_get_host (const ENetAddress * address, char * name, size_t nameLeng
     return 0;
 }
 
-int
-enet_socket_bind (ENetSocket socket, const ENetAddress * address)
+ENetSocket
+enet_socket_create (ENetSocketType type, const ENetAddress * address)
 {
+    ENetSocket newSocket = socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
     struct sockaddr_in sin;
 
+    if (newSocket == ENET_SOCKET_NULL)
+      return ENET_SOCKET_NULL;
+
+    if (address == NULL)
+      return newSocket;
+
     memset (& sin, 0, sizeof (struct sockaddr_in));
 
     sin.sin_family = AF_INET;
+    sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
+    sin.sin_addr.s_addr = address -> host;
 
-    if (address != NULL)
+    if (bind (newSocket, 
+              (struct sockaddr *) & sin,
+              sizeof (struct sockaddr_in)) == -1 ||
+        (type == ENET_SOCKET_TYPE_STREAM &&
+          address -> port != ENET_PORT_ANY &&
+          listen (newSocket, SOMAXCONN) == -1))
     {
-       sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
-       sin.sin_addr.s_addr = address -> host;
-    }
-    else
-    {
-       sin.sin_port = 0;
-       sin.sin_addr.s_addr = INADDR_ANY;
-    }
+       close (newSocket);
 
-    return bind (socket,
-                 (struct sockaddr *) & sin,
-                 sizeof (struct sockaddr_in)); 
-}
-
-int 
-enet_socket_listen (ENetSocket socket, int backlog)
-{
-    return listen (socket, backlog < 0 ? SOMAXCONN : backlog);
-}
+       return ENET_SOCKET_NULL;
+    }
 
-ENetSocket
-enet_socket_create (ENetSocketType type)
-{
-    return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
+    return newSocket;
 }
 
 int
@@ -207,10 +203,6 @@ enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value)
             result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int));
             break;
 
-        case ENET_SOCKOPT_REUSEADDR:
-            result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int));
-            break;
-
         case ENET_SOCKOPT_RCVBUF:
             result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int));
             break;
@@ -354,17 +346,6 @@ enet_socket_receive (ENetSocket socket,
 }
 
 int
-enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout)
-{
-    struct timeval timeVal;
-
-    timeVal.tv_sec = timeout / 1000;
-    timeVal.tv_usec = (timeout % 1000) * 1000;
-
-    return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal);
-}
-
-int
 enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout)
 {
 #ifdef HAS_POLL
diff --git a/enet/win32.c b/enet/win32.c
index e1fae23..b8b2aa7 100644
--- a/enet/win32.c
+++ b/enet/win32.c
@@ -100,15 +100,19 @@ enet_address_get_host (const ENetAddress * address, char * name, size_t nameLeng
     return 0;
 }
 
-int
-enet_socket_bind (ENetSocket socket, const ENetAddress * address)
+ENetSocket
+enet_socket_create (ENetSocketType type, const ENetAddress * address)
 {
+    ENetSocket newSocket = socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
     struct sockaddr_in sin;
 
+    if (newSocket == ENET_SOCKET_NULL)
+      return ENET_SOCKET_NULL;
+
     memset (& sin, 0, sizeof (struct sockaddr_in));
 
     sin.sin_family = AF_INET;
-
+    
     if (address != NULL)
     {
        sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
@@ -120,21 +124,20 @@ enet_socket_bind (ENetSocket socket, const ENetAddress * address)
        sin.sin_addr.s_addr = INADDR_ANY;
     }
 
-    return bind (socket,
-                 (struct sockaddr *) & sin,
-                 sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
-}
+    if (bind (newSocket,    
+              (struct sockaddr *) & sin,
+              sizeof (struct sockaddr_in)) == SOCKET_ERROR ||
+        (type == ENET_SOCKET_TYPE_STREAM &&
+          address != NULL &&
+          address -> port != ENET_PORT_ANY &&
+          listen (newSocket, SOMAXCONN) == SOCKET_ERROR))
+    {
+       closesocket (newSocket);
 
-int
-enet_socket_listen (ENetSocket socket, int backlog)
-{
-    return listen (socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0;
-}
+       return ENET_SOCKET_NULL;
+    }
 
-ENetSocket
-enet_socket_create (ENetSocketType type)
-{
-    return socket (PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0);
+    return newSocket;
 }
 
 int
@@ -154,10 +157,6 @@ enet_socket_set_option (ENetSocket socket, ENetSocketOption option, int value)
             result = setsockopt (socket, SOL_SOCKET, SO_BROADCAST, (char *) & value, sizeof (int));
             break;
 
-        case ENET_SOCKOPT_REUSEADDR:
-            result = setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) & value, sizeof (int));
-            break;
-
         case ENET_SOCKOPT_RCVBUF:
             result = setsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) & value, sizeof (int));
             break;
@@ -183,7 +182,7 @@ enet_socket_connect (ENetSocket socket, const ENetAddress * address)
     sin.sin_port = ENET_HOST_TO_NET_16 (address -> port);
     sin.sin_addr.s_addr = address -> host;
 
-    return connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in)) == SOCKET_ERROR ? -1 : 0;
+    return connect (socket, (struct sockaddr *) & sin, sizeof (struct sockaddr_in));
 }
 
 ENetSocket
@@ -296,17 +295,6 @@ enet_socket_receive (ENetSocket socket,
 }
 
 int
-enet_socketset_select (ENetSocket maxSocket, ENetSocketSet * readSet, ENetSocketSet * writeSet, enet_uint32 timeout)
-{
-    struct timeval timeVal;
-
-    timeVal.tv_sec = timeout / 1000;
-    timeVal.tv_usec = (timeout % 1000) * 1000;
-
-    return select (maxSocket + 1, readSet, writeSet, NULL, & timeVal);
-}
-
-int
 enet_socket_wait (ENetSocket socket, enet_uint32 * condition, enet_uint32 timeout)
 {
     fd_set readSet, writeSet;
diff --git a/engine/3dgui.cpp b/engine/3dgui.cpp
index d008b9f..d87f69a 100644
--- a/engine/3dgui.cpp
+++ b/engine/3dgui.cpp
@@ -3,6 +3,7 @@
 // special feature is that its mostly *modeless*: you can use this menu while playing, without turning menus on or off
 // implementationwise, it is *stateless*: it keeps no internal gui structure, hit tests are instant, usage & implementation is greatly simplified
 
+#include "pch.h"
 #include "engine.h"
 
 #include "textedit.h"
@@ -13,9 +14,7 @@ static struct gui *windowhit = NULL;
 
 static float firstx, firsty;
 
-enum {FIELDCOMMIT, FIELDABORT, FIELDEDIT, FIELDSHOW, FIELDKEY};
-
-static int fieldmode = FIELDSHOW; 
+static enum {FIELDCOMMIT, FIELDABORT, FIELDEDIT, FIELDSHOW} fieldmode = FIELDSHOW; 
 static bool fieldsactive = false;
 
 static bool hascursor;
@@ -108,10 +107,10 @@ struct gui : g3d_gui
         if(!name) 
         {
             static string title;
-            formatstring(title)("%d", tpos);
+            s_sprintf(title)("%d", tpos);
             name = title;
         }
-        int w = max(text_width(name) - 2*INSERT, 0);
+        int w = text_width(name) - 2*INSERT;
         if(layoutpass) 
         {  
             ty = max(ty, ysize); 
@@ -123,7 +122,7 @@ struct gui : g3d_gui
             int h = FONTH-2*INSERT,
                 x1 = curx + tx,
                 x2 = x1 + w + ((skinx[3]-skinx[2]) + (skinx[5]-skinx[4]))*SKIN_SCALE,
-                y1 = cury - ((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE-h,
+                y1 = cury - ((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE-h,
                 y2 = cury;
             bool hit = tcurrent && windowhit==this && hitx>=x1 && hity>=y1 && hitx<x2 && hity<y2;
             if(hit && (!guiclicktab || mousebuttons&G3D_DOWN)) 
@@ -132,8 +131,8 @@ struct gui : g3d_gui
                 color = 0xFF0000;
             }
             
-            drawskin(x1-skinx[visible()?2:6]*SKIN_SCALE, y1-skiny[1]*SKIN_SCALE, w, h, visible()?10:19, 9, gui2d ? 1 : 2, light, alpha);
-            text_(name, x1 + (skinx[3]-skinx[2])*SKIN_SCALE - (w ? INSERT : INSERT/2), y1 + (skiny[2]-skiny[1])*SKIN_SCALE - INSERT, tcolor, visible());
+            skin_(x1-skinx[visible()?2:6]*SKIN_SCALE, y1-skiny[1]*SKIN_SCALE, w, h, visible()?10:19, 9);
+            text_(name, x1 + (skinx[3]-skinx[2])*SKIN_SCALE - INSERT, y1 + (skiny[2]-skiny[1])*SKIN_SCALE - INSERT, tcolor, visible());
         }
         tx += w + ((skinx[5]-skinx[4]) + (skinx[3]-skinx[2]))*SKIN_SCALE; 
     }
@@ -240,12 +239,12 @@ struct gui : g3d_gui
         return layout(size+SHADOW, size+SHADOW);
     }
     
-    int texture(Texture *t, float scale, int rotate, int xoff, int yoff, Texture *glowtex, const vec &glowcolor, Texture *layertex)
+    int texture(Texture *t, float scale, int rotate, int xoff, int yoff, Texture *glowtex, const vec &glowcolor)
     {
         autotab();
         if(scale==0) scale = 1;
         int size = (int)(scale*2*FONTH)-SHADOW;
-        if(t!=notexture && visible()) icon_(t, true, true, curx, cury, size, ishit(size+SHADOW, size+SHADOW), rotate, xoff, yoff, glowtex, glowcolor, layertex);
+        if(t!=notexture && visible()) icon_(t, true, true, curx, cury, size, ishit(size+SHADOW, size+SHADOW), rotate, xoff, yoff, glowtex, glowcolor);
         return layout(size+SHADOW, size+SHADOW);
     }
 
@@ -260,7 +259,7 @@ struct gui : g3d_gui
             if(!label)
             {
                 static string s;
-                formatstring(s)("%d", val);
+                s_sprintf(s)("%d", val);
                 label = s;
             }
             int w = text_width(label);
@@ -295,16 +294,6 @@ struct gui : g3d_gui
     }
 
     char *field(const char *name, int color, int length, int height, const char *initval, int initmode)
-    {
-        return field_(name, color, length, height, initval, initmode, FIELDEDIT);
-    }
-
-    char *keyfield(const char *name, int color, int length, int height, const char *initval, int initmode)
-    {
-        return field_(name, color, length, height, initval, initmode, FIELDKEY);
-    }
-
-    char *field_(const char *name, int color, int length, int height, const char *initval, int initmode, int fieldtype = FIELDEDIT)
     {	
         editor *e = useeditor(name, initmode, false, initval); // generate a new editor if necessary
         if(layoutpass)
@@ -339,14 +328,13 @@ struct gui : g3d_gui
             {
                 if(mousebuttons&G3D_DOWN) //mouse request focus
                 {   
-                    if(fieldtype==FIELDKEY) e->clear();
                     useeditor(name, initmode, true); 
                     e->mark(false);
-                    fieldmode = fieldtype;
+                    fieldmode = FIELDEDIT;
                 } 
             }
             bool editing = (fieldmode != FIELDSHOW) && (e==currentfocus());
-            if(hit && editing && (mousebuttons&G3D_PRESSED)!=0 && fieldtype==FIELDEDIT) e->hit(int(floor(hitx-(curx+FONTW/2))), int(floor(hity-cury)), (mousebuttons&G3D_DRAGGED)!=0); //mouse request position
+            if(hit && editing && (mousebuttons&G3D_PRESSED)!=0) e->hit(int(floor(hitx-(curx+FONTW/2))), int(floor(hity-cury)), (mousebuttons&G3D_DRAGGED)!=0); //mouse request position
             if(editing && ((fieldmode==FIELDCOMMIT) || (fieldmode==FIELDABORT) || !hit)) // commit field if user pressed enter or wandered out of focus 
             {
                 if(fieldmode==FIELDCOMMIT || (fieldmode!=FIELDABORT && !hit)) result = e->currentline().text;
@@ -438,7 +426,7 @@ struct gui : g3d_gui
         defaultshader->set();
     }
 
-    void icon_(Texture *t, bool overlaid, bool tiled, int x, int y, int size, bool hit, int rotate = 0, int xoff = 0, int yoff = 0, Texture *glowtex = NULL, const vec &glowcolor = vec(1, 1, 1), Texture *layertex = NULL) 
+    void icon_(Texture *t, bool overlaid, bool tiled, int x, int y, int size, bool hit, int rotate = 0, int xoff = 0, int yoff = 0, Texture *glowtex = NULL, const vec &glowcolor = vec(1, 1, 1)) 
     {
         float xs, ys, xt, yt;
         if(tiled)
@@ -484,8 +472,8 @@ struct gui : g3d_gui
         }
         loopk(4) { tc[k][0] = tc[k][0]/xt - float(xoff)/t->xs; tc[k][1] = tc[k][1]/yt - float(yoff)/t->ys; }
         vec color = hit ? vec(1, 0.5f, 0.5f) : (overlaid ? vec(1, 1, 1) : light);
-        glBindTexture(GL_TEXTURE_2D, t->id);
         glColor3fv(color.v);
+        glBindTexture(GL_TEXTURE_2D, t->id);
         glBegin(GL_QUADS);
         glTexCoord2fv(tc[0]); glVertex2f(x,    y);
         glTexCoord2fv(tc[1]); glVertex2f(x+xs, y);
@@ -494,10 +482,10 @@ struct gui : g3d_gui
         glEnd();
         if(glowtex)
         {
-            glBlendFunc(GL_SRC_ALPHA, GL_ONE);
-            glBindTexture(GL_TEXTURE_2D, glowtex->id);
             if(hit || overlaid) { loopk(3) color[k] *= glowcolor[k]; glColor3fv(color.v); }
             else glColor3fv(glowcolor.v);
+            glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+            glBindTexture(GL_TEXTURE_2D, glowtex->id);
             glBegin(GL_QUADS);
             glTexCoord2fv(tc[0]); glVertex2f(x,    y);
             glTexCoord2fv(tc[1]); glVertex2f(x+xs, y);
@@ -506,24 +494,12 @@ struct gui : g3d_gui
             glEnd();
             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
         }
-        if(layertex)
-        {
-            glBindTexture(GL_TEXTURE_2D, layertex->id);
-            glColor3fv(color.v);
-            glBegin(GL_QUADS);
-            glTexCoord2fv(tc[0]); glVertex2f(x+xs/2, y+ys/2);
-            glTexCoord2fv(tc[1]); glVertex2f(x+xs,   y+ys/2);
-            glTexCoord2fv(tc[2]); glVertex2f(x+xs,   y+ys);
-            glTexCoord2fv(tc[3]); glVertex2f(x+xs/2, y+ys);
-            glEnd();
-        }
-            
         if(tiled) defaultshader->set();
         if(overlaid) 
         {
-            if(!overlaytex) overlaytex = textureload("data/guioverlay.png", 3);
-            glBindTexture(GL_TEXTURE_2D, overlaytex->id);
+            if(!overlaytex) overlaytex = textureload("data/guioverlay.png");
             glColor3fv(light.v);
+            glBindTexture(GL_TEXTURE_2D, overlaytex->id);
             glBegin(GL_QUADS);
             rect_(x, y, xs, ys, 0);
             glEnd();
@@ -534,7 +510,7 @@ struct gui : g3d_gui
     {		
         if(visible())
         {
-            if(!slidertex) slidertex = textureload("data/guislider.png", 3);
+            if(!slidertex) slidertex = textureload("data/guislider.png");
             glEnable(GL_TEXTURE_2D);
             glBindTexture(GL_TEXTURE_2D, slidertex->id);
             glBegin(GL_QUADS);
@@ -556,17 +532,6 @@ struct gui : g3d_gui
         layout(ishorizontal() ? FONTH : 0, ishorizontal() ? 0 : FONTH);
     }
 
-    void textbox(const char *text, int width, int height, int color) 
-    {
-        width *= FONTW;
-        height *= FONTH;
-        int w, h;
-        text_bounds(text, w, h, width);
-        if(h > height) height = h;
-        if(visible()) draw_text(text, curx, cury, color>>16, (color>>8)&0xFF, color&0xFF, 0xFF, -1, width);
-        layout(width, height);
-    }
-
     int button_(const char *text, int color, const char *icon, bool clickable, bool center)
     {
         const int padding = 10;
@@ -584,8 +549,8 @@ struct gui : g3d_gui
         
             if(icon)
             {
-                defformatstring(tname)("packages/icons/%s.jpg", icon);
-                icon_(textureload(tname, 3), false, false, x, cury, ICON_SIZE, clickable && hit);
+                s_sprintfd(tname)("packages/icons/%s.jpg", icon);
+                icon_(textureload(tname), false, false, x, cury, ICON_SIZE, clickable && hit);
                 x += ICON_SIZE;
             }
             if(icon && text) x += padding;
@@ -598,18 +563,18 @@ struct gui : g3d_gui
     static const int skinx[], skiny[];
     static const struct patch { ushort left, right, top, bottom; uchar flags; } patches[];
 
-    static void drawskin(int x, int y, int gapw, int gaph, int start, int n, int passes = 1, const vec &light = vec(1, 1, 1), float alpha = 0.80f)//int vleft, int vright, int vtop, int vbottom, int start, int n) 
+    void skin_(int x, int y, int gapw, int gaph, int start, int n)//int vleft, int vright, int vtop, int vbottom, int start, int n) 
     {
-        if(!skintex) skintex = textureload("data/guiskin.png", 3);
+        if(!skintex) skintex = textureload("data/guiskin.png");
         glBindTexture(GL_TEXTURE_2D, skintex->id);
         int gapx1 = INT_MAX, gapy1 = INT_MAX, gapx2 = INT_MAX, gapy2 = INT_MAX;
         float wscale = 1.0f/(SKIN_W*SKIN_SCALE), hscale = 1.0f/(SKIN_H*SKIN_SCALE);
         
-        loopj(passes)
+        loopj(gui2d ? 1 : 2)
         {	
             bool quads = false;
-            if(passes>1) glDepthFunc(j ? GL_LEQUAL : GL_GREATER);
-            glColor4f(j ? light.x : 1.0f, j ? light.y : 1.0f, j ? light.z : 1.0f, passes<=1 || j ? alpha : alpha/2); //ghost when its behind something in depth
+            if(!gui2d) glDepthFunc(j ? GL_LEQUAL : GL_GREATER);
+            glColor4f(j ? light.x : 1.0f, j ? light.y : 1.0f, j ? light.z : 1.0f, gui2d || j ? 0.80f : 0.35f); //ghost when its behind something in depth
             loopi(n)
             {
                 const patch &p = patches[start+i];
@@ -675,7 +640,7 @@ struct gui : g3d_gui
             if(quads) glEnd();
             else break; //if it didn't happen on the first pass, it won't happen on the second..
         }
-        if(passes>1) glDepthFunc(GL_ALWAYS);
+        if(!gui2d) glDepthFunc(GL_ALWAYS);
     } 
 
     vec origin, scale, *savedorigin;
@@ -685,19 +650,18 @@ struct gui : g3d_gui
 
     static float basescale, maxscale;
     static bool passthrough;
-    static float alpha;
     static vec light;
 
     void adjustscale()
     {
-        int w = xsize + (skinx[2]-skinx[1])*SKIN_SCALE + (skinx[10]-skinx[9])*SKIN_SCALE, h = ysize + (skiny[9]-skiny[7])*SKIN_SCALE;
+        int w = xsize + (skinx[2]-skinx[1])*SKIN_SCALE + (skinx[10]-skinx[9])*SKIN_SCALE, h = ysize + (skiny[8]-skiny[6])*SKIN_SCALE;
         if(tcurrent) h += ((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE + FONTH-2*INSERT;
-        else h += (skiny[6]-skiny[3])*SKIN_SCALE;
+        else h += (skiny[5]-skiny[3])*SKIN_SCALE;
 
         float aspect = float(screen->h)/float(screen->w), fit = 1.0f;
         if(w*aspect*basescale>1.0f) fit = 1.0f/(w*aspect*basescale);
         if(h*basescale*fit>maxscale) fit *= maxscale/(h*basescale*fit);
-        origin = vec(0.5f-((w-xsize)/2 - (skinx[2]-skinx[1])*SKIN_SCALE)*aspect*scale.x*fit, 0.5f + (0.5f*h-(skiny[9]-skiny[7])*SKIN_SCALE)*scale.y*fit, 0);
+        origin = vec(0.5f-((w-xsize)/2 - (skinx[2]-skinx[1])*SKIN_SCALE)*aspect*scale.x*fit, 0.5f + (0.5f*h-(skiny[8]-skiny[6])*SKIN_SCALE)*scale.y*fit, 0);
         scale = vec(aspect*scale.x*fit, scale.y*fit, 1);
     }
 
@@ -709,8 +673,7 @@ struct gui : g3d_gui
             if(allowinput) hascursor = true;
         }
         basescale = initscale;
-        if(layoutpass) scale.x = scale.y = scale.z = min(basescale*(totalmillis-starttime)/300.0f, basescale);
-        alpha = allowinput ? 0.80f : 0.60f;
+        if(layoutpass) scale.x = scale.y = scale.z = basescale*min((totalmillis-starttime)/300.0f, 1.0f);
         passthrough = scale.x<basescale || !allowinput;
         curdepth = -1;
         curlist = -1;
@@ -748,8 +711,8 @@ struct gui : g3d_gui
                 light.mul(1.0f + max(intensity, 0.0f));
             }
 
-            drawskin(curx-skinx[2]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, ysize, 0, 9, gui2d ? 1 : 2, light, alpha);
-            if(!tcurrent) drawskin(curx-skinx[5]*SKIN_SCALE, cury-skiny[6]*SKIN_SCALE, xsize, 0, 9, 1, gui2d ? 1 : 2, light, alpha);
+            skin_(curx-skinx[2]*SKIN_SCALE, cury-skiny[5]*SKIN_SCALE, xsize, ysize, 0, 9);
+            if(!tcurrent) skin_(curx-skinx[5]*SKIN_SCALE, cury-skiny[5]*SKIN_SCALE, xsize, 0, 9, 1);
         }
     }
 
@@ -759,12 +722,12 @@ struct gui : g3d_gui
         {	
             xsize = max(tx, xsize);
             ysize = max(ty, ysize);
-            ysize = max(ysize, (skiny[7]-skiny[6])*SKIN_SCALE);
+            ysize = max(ysize, (skiny[6]-skiny[5])*SKIN_SCALE);
             if(tcurrent) *tcurrent = max(1, min(*tcurrent, tpos));
             if(gui2d) adjustscale();
             if(!windowhit && !passthrough)
             {
-                float dist = 0;
+                int intersects = INTERSECT_MIDDLE;
                 if(gui2d)
                 {
                     hitx = (cursorx - origin.x)/scale.x;
@@ -772,27 +735,23 @@ struct gui : g3d_gui
                 }
                 else
                 {
-                    plane p;
-                    p.toplane(vec(origin).sub(camera1->o).set(2, 0).normalize(), origin);
-                    if(p.rayintersect(camera1->o, camdir, dist) && dist>=0)
-                    {
-                        vec hitpos(camdir);
-                        hitpos.mul(dist).add(camera1->o).sub(origin);
-                        hitx = vec(-p.y, p.x, 0).dot(hitpos)/scale.x;
-                        hity = -hitpos.z/scale.y;
-                    }
+                    vec planenormal = vec(origin).sub(camera1->o).set(2, 0).normalize(), intersectionpoint;
+                    intersects = intersect_plane_line(camera1->o, worldpos, origin, planenormal, intersectionpoint);
+                    vec intersectionvec = vec(intersectionpoint).sub(origin), xaxis(-planenormal.y, planenormal.x, 0);
+                    hitx = xaxis.dot(intersectionvec)/scale.x;
+                    hity = -intersectionvec.z/scale.y;
                 }
                 if((mousebuttons & G3D_PRESSED) && (fabs(hitx-firstx) > 2 || fabs(hity - firsty) > 2)) mousebuttons |= G3D_DRAGGED;
-                if(dist>=0 && hitx>=-xsize/2 && hitx<=xsize/2 && hity<=0)
+                if(intersects>=INTERSECT_MIDDLE && hitx>=-xsize/2 && hitx<=xsize/2 && hity<=0)
                 {
-                    if(hity>=-ysize || (tcurrent && hity>=-ysize-(FONTH-2*INSERT)-((skiny[6]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE && hitx<=tx-xsize/2))
+                    if(hity>=-ysize || (tcurrent && hity>=-ysize-(FONTH-2*INSERT)-((skiny[5]-skiny[1])-(skiny[3]-skiny[2]))*SKIN_SCALE && hitx<=tx-xsize/2))
                         windowhit = this;
                 }
             }
         }
         else
         {
-            if(tcurrent && tx<xsize) drawskin(curx+tx-skinx[5]*SKIN_SCALE, -ysize-skiny[6]*SKIN_SCALE, xsize-tx, FONTH, 9, 1, gui2d ? 1 : 2, light, alpha);
+            if(tcurrent && tx<xsize) skin_(curx+tx-skinx[5]*SKIN_SCALE, -ysize-skiny[5]*SKIN_SCALE, xsize-tx, FONTH, 9, 1);
             glPopMatrix();
         }
         poplist();
@@ -802,25 +761,25 @@ struct gui : g3d_gui
 Texture *gui::skintex = NULL, *gui::overlaytex = NULL, *gui::slidertex = NULL;
 
 //chop skin into a grid
-const int gui::skiny[] = {0, 7, 21, 34, 43, 48, 56, 104, 111, 117, 128},
-          gui::skinx[] = {0, 11, 23, 37, 105, 119, 137, 151, 215, 229, 246, 256};
+const int gui::skiny[] = {0, 7, 21, 34, 48, 56, 104, 111, 116, 128},
+          gui::skinx[] = {0, 11, 23, 37, 105, 119, 137, 151, 215, 229, 245, 256};
 //Note: skinx[3]-skinx[2] = skinx[7]-skinx[6]
 //      skinx[5]-skinx[4] = skinx[9]-skinx[8]		 
 const gui::patch gui::patches[] = 
 { //arguably this data can be compressed - it depends on what else needs to be skinned in the future
-    {1,2,3,6,  0},    // body
-    {2,9,5,6,  0x01},
-    {9,10,3,6, 0},
+    {1,2,3,5,  0},    // body
+    {2,9,4,5,  0x01},
+    {9,10,3,5, 0},
 
-    {1,2,6,7,  0x10},
-    {2,9,6,7,  0x11},
-    {9,10,6,7, 0x10},
+    {1,2,5,6,  0x10},
+    {2,9,5,6,  0x11},
+    {9,10,5,6, 0x10},
 
-    {1,2,7,9,  0},
-    {2,9,7,9,  0x01},
-    {9,10,7,9, 0},
+    {1,2,6,8,  0},
+    {2,9,6,8,  0x01},
+    {9,10,6,8, 0},
 
-    {5,6,3,5, 0x01}, // top
+    {5,6,3,4, 0x01}, // top
 
     {2,3,1,2, 0},    // selected tab
     {3,4,1,2, 0x01},
@@ -828,9 +787,9 @@ const gui::patch gui::patches[] =
     {2,3,2,3, 0x10},
     {3,4,2,3, 0x11},
     {4,5,2,3, 0x10},
-    {2,3,3,5, 0},
-    {3,4,3,5, 0x01},
-    {4,5,3,5, 0},
+    {2,3,3,4, 0},
+    {3,4,3,4, 0x01},
+    {4,5,3,4, 0},
 
     {6,7,1,2, 0},    // deselected tab
     {7,8,1,2, 0x01},
@@ -838,13 +797,13 @@ const gui::patch gui::patches[] =
     {6,7,2,3, 0x10},
     {7,8,2,3, 0x11},
     {8,9,2,3, 0x10},
-    {6,7,3,5, 0},
-    {7,8,3,5, 0x01},
-    {8,9,3,5, 0},
+    {6,7,3,4, 0},
+    {7,8,3,4, 0x01},
+    {8,9,3,4, 0},
 };
 
 vector<gui::list> gui::lists;
-float gui::basescale, gui::maxscale = 1, gui::hitx, gui::hity, gui::alpha;
+float gui::basescale, gui::maxscale = 1, gui::hitx, gui::hity;
 bool gui::passthrough, gui::shouldmergehits = false, gui::shouldautotab = true;
 vec gui::light;
 int gui::curdepth, gui::curlist, gui::xsize, gui::ysize, gui::curx, gui::cury;
@@ -855,28 +814,11 @@ VARP(guipushdist, 1, 4, 64);
 
 bool menukey(int code, bool isdown, int cooked)
 {
-    editor *e = currentfocus();
-    if(fieldmode == FIELDKEY)
-    {
-        switch(code)
-        {
-            case SDLK_ESCAPE:
-                if(isdown) fieldmode = FIELDCOMMIT;
-                return true;
-        }
-        const char *keyname = getkeyname(code);
-        if(keyname && isdown)
-        {
-            if(e->lines.length()!=1 || !e->lines[0].empty()) e->insert(" ");
-            e->insert(keyname);
-        }
-        return true;
-    }
-
     if(code==-1 && g3d_windowhit(isdown, true)) return true;
     else if(code==-3 && g3d_windowhit(isdown, false)) return true;
 
-    if(fieldmode == FIELDSHOW || !e)
+    editor *e = currentfocus();
+    if((fieldmode == FIELDSHOW) || !e)
     {
         if(windowhit) switch(code)
         {
@@ -940,8 +882,6 @@ bool menukey(int code, bool isdown, int cooked)
         case SDLK_DOWN:
         case SDLK_LEFT:
         case SDLK_RIGHT:
-        case SDLK_LSHIFT:
-        case SDLK_RSHIFT:
         case -4:
         case -5:
             break;
@@ -978,7 +918,7 @@ VARNP(gui2d, usegui2d, 0, 1, 1);
 
 void g3d_addgui(g3d_callback *cb, vec &origin, int flags)
 {
-    bool gui2d = flags&GUI_FORCE_2D || (flags&GUI_2D && usegui2d) || mainmenu;
+    bool gui2d = flags&GUI_FORCE_2D || (flags&GUI_2D && usegui2d);
     if(!gui2d && flags&GUI_FOLLOW && useguifollow) origin.z = player->o.z-(player->eyeheight-1);
     gui &g = (gui2d ? guis2d : guis3d).add();
     g.cb = cb;
@@ -1000,11 +940,8 @@ bool g3d_windowhit(bool on, bool act)
     extern int cleargui(int n);
     if(act) 
     {
-        if(actionon || windowhit)
-        {
-            if(on) { firstx = gui::hitx; firsty = gui::hity; }
-            mousebuttons |= (actionon=on) ? G3D_DOWN : G3D_UP;
-        }
+        if(on) { firstx = gui::hitx; firsty = gui::hity; }
+        mousebuttons |= (actionon=on) ? G3D_DOWN : G3D_UP;
     } else if(!on && windowhit) cleargui(1);
     return (guis2d.length() && hascursor) || (windowhit && !windowhit->gui2d);
 }
@@ -1021,9 +958,9 @@ void g3d_render()
     // call all places in the engine that may want to render a gui from here, they call g3d_addgui()
     extern void g3d_texturemenu();
     
-    if(!mainmenu) g3d_texturemenu();
+    g3d_texturemenu();
     g3d_mainmenu();
-    if(!mainmenu) game::g3d_gamemenus();
+    cl->g3d_gamemenus();
 
     guis2d.sort(g3d_sort);
     guis3d.sort(g3d_sort);
@@ -1047,7 +984,6 @@ void g3d_render()
 
     if(guis3d.length())
     {
-        glEnable(GL_DEPTH_TEST);
         glDepthFunc(GL_ALWAYS);
         glDepthMask(GL_FALSE);
 
@@ -1055,7 +991,6 @@ void g3d_render()
 
         glDepthFunc(GL_LESS);
         glDepthMask(GL_TRUE);
-        glDisable(GL_DEPTH_TEST);
     }
 
     if(guis2d.length())
@@ -1069,8 +1004,12 @@ void g3d_render()
         glPushMatrix();
         glLoadIdentity();
 
+        glDisable(GL_DEPTH_TEST);
+
         loopvrev(guis2d) guis2d[i].cb->gui(guis2d[i], false);
 
+        glEnable(GL_DEPTH_TEST);
+
         glMatrixMode(GL_PROJECTION);
         glPopMatrix();
         glMatrixMode(GL_MODELVIEW);
@@ -1093,19 +1032,3 @@ void g3d_render()
     mousebuttons = 0;
 }
 
-void consolebox(int x1, int y1, int x2, int y2)
-{
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glPushMatrix();
-    glTranslatef(x1, y1, 0);
-    float bw = x2 - x1, bh = y2 - y1, aspect = bw/bh, sh = bh, sw = sh*aspect;
-    bw *= float(4*FONTH)/(SKIN_H*SKIN_SCALE);
-    bh *= float(4*FONTH)/(SKIN_H*SKIN_SCALE);
-    sw /= bw + (gui::skinx[2]-gui::skinx[1] + gui::skinx[10]-gui::skinx[9])*SKIN_SCALE;
-    sh /= bh + (gui::skiny[9]-gui::skiny[7] + gui::skiny[6]-gui::skiny[4])*SKIN_SCALE;
-    glScalef(sw, sh, 1);
-    gui::drawskin(-gui::skinx[1]*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), int(bh), 0, 9, 1, vec(1, 1, 1), 0.60f);
-    gui::drawskin((-gui::skinx[1] + gui::skinx[2] - gui::skinx[5])*SKIN_SCALE, -gui::skiny[4]*SKIN_SCALE, int(bw), 0, 9, 1, 1, vec(1, 1, 1), 0.60f);
-    glPopMatrix();
-}
-
diff --git a/engine/animmodel.h b/engine/animmodel.h
index b4f4633..e0640db 100644
--- a/engine/animmodel.h
+++ b/engine/animmodel.h
@@ -1,7 +1,7 @@
-VARFP(lightmodels, 0, 1, 1, preloadmodelshaders());
-VARFP(envmapmodels, 0, 1, 1, preloadmodelshaders());
-VARFP(glowmodels, 0, 1, 1, preloadmodelshaders());
-VARFP(bumpmodels, 0, 1, 1, preloadmodelshaders());
+VARP(lightmodels, 0, 1, 1);
+VARP(envmapmodels, 0, 1, 1);
+VARP(glowmodels, 0, 1, 1);
+VARP(bumpmodels, 0, 1, 1);
 VARP(fullbrightmodels, 0, 0, 200);
 
 struct animmodel : model
@@ -74,10 +74,10 @@ struct animmodel : model
         part *owner;
         Texture *tex, *masks, *envmap, *unlittex, *normalmap;
         Shader *shader;
-        float spec, ambient, glow, specglare, glowglare, fullbright, envmapmin, envmapmax, scrollu, scrollv, alphatest;
-        bool alphablend, cullface;
+        float spec, ambient, glow, specglare, glowglare, fullbright, envmapmin, envmapmax, translucency, scrollu, scrollv, alphatest;
+        bool alphablend;
 
-        skin() : owner(0), tex(notexture), masks(notexture), envmap(NULL), unlittex(NULL), normalmap(NULL), shader(NULL), spec(1.0f), ambient(0.3f), glow(3.0f), specglare(1), glowglare(1), fullbright(0), envmapmin(0), envmapmax(0), scrollu(0), scrollv(0), alphatest(0.9f), alphablend(true), cullface(true) {}
+        skin() : owner(0), tex(notexture), masks(notexture), envmap(NULL), unlittex(NULL), normalmap(NULL), shader(NULL), spec(1.0f), ambient(0.3f), glow(3.0f), specglare(1), glowglare(1), fullbright(0), envmapmin(0), envmapmax(0), translucency(0.5f), scrollu(0), scrollv(0), alphatest(0.9f), alphablend(true) {}
 
         bool multitextured() { return enableglow; }
         bool envmapped() { return hasCM && envmapmax>0 && envmapmodels && (renderpath!=R_FIXEDFUNCTION || maxtmus >= (fogging ? 4 : 3)); }
@@ -91,12 +91,7 @@ struct animmodel : model
             {
                 if(enablelighting) { glDisable(GL_LIGHTING); enablelighting = false; }
             }
-            else if(lightmodels && !enablelighting)
-            {
-                glEnable(GL_LIGHTING); 
-                enablelighting = true;
-                if(!enablerescale) { glEnable(hasRN ? GL_RESCALE_NORMAL_EXT : GL_NORMALIZE); enablerescale = true; }
-            }
+            else if(lightmodels && !enablelighting) { glEnable(GL_LIGHTING); enablelighting = true; }
             int needsfog = -1;
             if(fogging)
             {
@@ -109,9 +104,9 @@ struct animmodel : model
             if(masked)
             {
                 if(enableoverbright) disableoverbright();
-                if(!enableglow) setuptmu(0, "K , C @ T", envmaptmu>=0 && envmapmax>0 ? "Ca * Ta" : NULL);
+                if(!enableglow) setuptmu(0, "K , C @ T", as->anim&ANIM_ENVMAP && envmapmax>0 ? "Ca * Ta" : NULL);
                 int glowscale = glow>2 ? 4 : (glow>1 || mincolor>1 ? 2 : 1);
-                float envmap = envmaptmu>=0 && envmapmax>0 ? 0.2f*envmapmax + 0.8f*envmapmin : 1;
+                float envmap = as->anim&ANIM_ENVMAP && envmapmax>0 ? 0.2f*envmapmax + 0.8f*envmapmin : 1;
                 colortmu(0, glow/glowscale, glow/glowscale, glow/glowscale);
                 if(fullbright) glColor4f(fullbright/glowscale, fullbright/glowscale, fullbright/glowscale, envmap);
                 else if(lightmodels)
@@ -122,18 +117,18 @@ struct animmodel : model
                 else glColor4f(r/glowscale, g/glowscale, b/glowscale, envmap);
 
                 glActiveTexture_(GL_TEXTURE1_ARB);
-                if(!enableglow || (!enableenvmap && envmaptmu>=0 && envmapmax>0) || transparent<1)
+                if(!enableglow || (!enableenvmap && as->anim&ANIM_ENVMAP && envmapmax>0) || as->anim&ANIM_TRANSLUCENT)
                 {
                     if(!enableglow) glEnable(GL_TEXTURE_2D);
-                    if(!(envmaptmu>=0 && envmapmax>0) && transparent<1) colortmu(1, 0, 0, 0, transparent);
-                    setuptmu(1, "P * T", envmaptmu>=0 && envmapmax>0 ? "= Pa" : (transparent<1 ? "Ta * Ka" : "= Ta"));
+                    if(!(as->anim&ANIM_ENVMAP && envmapmax>0) && as->anim&ANIM_TRANSLUCENT) colortmu(1, 0, 0, 0, translucency);
+                    setuptmu(1, "P * T", as->anim&ANIM_ENVMAP && envmapmax>0 ? "= Pa" : (as->anim&ANIM_TRANSLUCENT ? "Ta * Ka" : "= Ta"));
                 }
                 scaletmu(1, glowscale);
 
-                if(envmaptmu>=0 && envmapmax>0 && transparent<1)
+                if(as->anim&ANIM_ENVMAP && envmapmax>0 && as->anim&ANIM_TRANSLUCENT)
                 {
                     glActiveTexture_(GL_TEXTURE0_ARB+envmaptmu);
-                    colortmu(envmaptmu, 0, 0, 0, transparent);
+                    colortmu(envmaptmu, 0, 0, 0, translucency);
                 }
 
                 if(needsfog<0) glActiveTexture_(GL_TEXTURE0_ARB);
@@ -150,13 +145,13 @@ struct animmodel : model
                     if(!enableoverbright) { setuptmu(0, "C * T x 2"); enableoverbright = true; }
                 }
                 else if(enableoverbright) disableoverbright();
-                if(fullbright) glColor4f(fullbright/colorscale, fullbright/colorscale, fullbright, transparent);
+                if(fullbright) glColor4f(fullbright/colorscale, fullbright/colorscale, fullbright, as->anim&ANIM_TRANSLUCENT ? translucency : 1);
                 else if(lightmodels)
                 {
-                    GLfloat material[4] = { 1.0f/colorscale, 1.0f/colorscale, 1.0f/colorscale, transparent };
+                    GLfloat material[4] = { 1.0f/colorscale, 1.0f/colorscale, 1.0f/colorscale, as->anim&ANIM_TRANSLUCENT ? translucency : 1 };
                     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, material);
                 }
-                else glColor4f(r/colorscale, g/colorscale, b/colorscale, transparent);
+                else glColor4f(r/colorscale, g/colorscale, b/colorscale, as->anim&ANIM_TRANSLUCENT ? translucency : 1);
             }
             if(needsfog>=0)
             {
@@ -167,8 +162,10 @@ struct animmodel : model
                     glEnable(GL_TEXTURE_1D);
                     glEnable(GL_TEXTURE_GEN_S);
                     glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-                    setuptmu(fogtmu, "K , P @ Ta", masked && envmaptmu>=0 && envmapmax>0 ? "Ka , Pa @ Ta" : "= Pa");
-                    colortmu(fogtmu, watercolor[0]/255.0f, watercolor[1]/255.0f, watercolor[2]/255.0f, 0);
+                    setuptmu(fogtmu, "K , P @ Ta", masked && as->anim&ANIM_ENVMAP && envmapmax>0 ? "Ka , Pa @ Ta" : "= Pa");
+                    uchar wcol[3];
+                    getwatercolour(wcol);
+                    colortmu(fogtmu, wcol[0]/255.0f, wcol[1]/255.0f, wcol[2]/255.0f, 0);
                     if(!fogtex) createfogtex();
                     glBindTexture(GL_TEXTURE_1D, fogtex);
                 }
@@ -197,95 +194,83 @@ struct animmodel : model
         {
             if(fullbright)
             {
-                glColor4f(fullbright/2, fullbright/2, fullbright/2, transparent);
-                setenvparamf("lightscale", SHPARAM_VERTEX, 2, 0, 2, glow);
-                setenvparamf("lightscale", SHPARAM_PIXEL, 2, 0, 2, glow);
+                glColor4f(fullbright/2, fullbright/2, fullbright/2, as->anim&ANIM_TRANSLUCENT ? translucency : 1);
+                setenvparamf("ambient", SHPARAM_VERTEX, 3, 2, 2, 2, 1);
+                setenvparamf("ambient", SHPARAM_PIXEL, 3, 2, 2, 2, 1);
             }
             else
             {
-                float mincolor = as->anim&ANIM_FULLBRIGHT ? fullbrightmodels/100.0f : 0,
-                      minshade = max(ambient, mincolor);
+                float mincolor = as->anim&ANIM_FULLBRIGHT ? fullbrightmodels/100.0f : 0;
                 glColor4f(max(lightcolor.x, mincolor), 
                           max(lightcolor.y, mincolor),
                           max(lightcolor.z, mincolor),
-                          transparent);
-                setenvparamf("lightscale", SHPARAM_VERTEX, 2, spec, minshade, glow);
-                setenvparamf("lightscale", SHPARAM_PIXEL, 2, spec, minshade, glow);
+                          as->anim&ANIM_TRANSLUCENT ? translucency : 1);
+                setenvparamf("specscale", SHPARAM_PIXEL, 2, spec, spec, spec);
+                float minshade = max(ambient, mincolor);
+                setenvparamf("ambient", SHPARAM_VERTEX, 3, minshade, minshade, minshade, 1);
+                setenvparamf("ambient", SHPARAM_PIXEL, 3, minshade, minshade, minshade, 1);
             }
+            setenvparamf("glowscale", SHPARAM_PIXEL, 4, glow, glow, glow);
             setenvparamf("millis", SHPARAM_VERTEX, 5, lastmillis/1000.0f, lastmillis/1000.0f, lastmillis/1000.0f);
-            if(envmaptmu>=0 && envmapmax>0) setenvparamf("envmapscale", bumpmapped() ? SHPARAM_PIXEL : SHPARAM_VERTEX, 3, envmapmin-envmapmax, envmapmax);
-            if(glaring) setenvparamf("glarescale", SHPARAM_PIXEL, 4, 16*specglare, 4*glowglare);
+            if(glaring) setenvparamf("glarescale", SHPARAM_PIXEL, 7, 16*specglare, 4*glowglare);
         }
 
-        Shader *loadshader(bool shouldenvmap, bool masked)
+        void setshader(mesh *m, const animstate *as, bool masked)
         {
-            #define DOMODELSHADER(name, body) \
-                do { \
+            #define SETMODELSHADER(m, name) \
+                do \
+                { \
                     static Shader *name##shader = NULL; \
-                    if(!name##shader) name##shader = useshaderbyname(#name); \
-                    body; \
-                } while(0)
-            #define LOADMODELSHADER(name) DOMODELSHADER(name, return name##shader)
-            #define SETMODELSHADER(m, name) DOMODELSHADER(name, (m)->setshader(name##shader))
-            if(shader) return shader;
+                    if(!name##shader) name##shader = lookupshaderbyname(#name); \
+                    m->setshader(name##shader); \
+                } \
+                while(0)
+            if(shader) m->setshader(shader);
             else if(bumpmapped())
             {
-                if(shouldenvmap)
+                if(as->anim&ANIM_ENVMAP && envmapmax>0)
                 {
-                    if(lightmodels && !fullbright && (masked || spec>=0.01f)) LOADMODELSHADER(bumpenvmapmodel);
-                    else LOADMODELSHADER(bumpenvmapnospecmodel);
+                    if(lightmodels && !fullbright && (masked || spec>=0.01f)) SETMODELSHADER(m, bumpenvmapmodel);
+                    else SETMODELSHADER(m, bumpenvmapnospecmodel);
+                    setlocalparamf("envmapscale", SHPARAM_PIXEL, 6, envmapmin-envmapmax, envmapmax);
                 }
-                else if(masked && lightmodels && !fullbright) LOADMODELSHADER(bumpmasksmodel);
-                else if(masked && glowmodels) LOADMODELSHADER(bumpmasksnospecmodel);
-                else if(spec>=0.01f && lightmodels && !fullbright) LOADMODELSHADER(bumpmodel);
-                else LOADMODELSHADER(bumpnospecmodel);
+                else if(masked && lightmodels && !fullbright) SETMODELSHADER(m, bumpmasksmodel);
+                else if(masked && glowmodels) SETMODELSHADER(m, bumpmasksnospecmodel);
+                else if(spec>=0.01f && lightmodels && !fullbright) SETMODELSHADER(m, bumpmodel);
+                else SETMODELSHADER(m, bumpnospecmodel);
             }
-            else if(shouldenvmap)
+            else if(as->anim&ANIM_ENVMAP && envmapmax>0)
             {
-                if(lightmodels && !fullbright && (masked || spec>=0.01f)) LOADMODELSHADER(envmapmodel);
-                else LOADMODELSHADER(envmapnospecmodel);
+                if(lightmodels && !fullbright && (masked || spec>=0.01f)) SETMODELSHADER(m, envmapmodel);
+                else SETMODELSHADER(m, envmapnospecmodel);
+                setlocalparamf("envmapscale", SHPARAM_VERTEX, 6, envmapmin-envmapmax, envmapmax);
             }
-            else if(masked && lightmodels && !fullbright) LOADMODELSHADER(masksmodel);
-            else if(masked && glowmodels) LOADMODELSHADER(masksnospecmodel);
-            else if(spec>=0.01f && lightmodels && !fullbright) LOADMODELSHADER(stdmodel);
-            else LOADMODELSHADER(nospecmodel);
-        }
-
-        void preloadshader()
-        {
-            bool shouldenvmap = envmapped();
-            loadshader(shouldenvmap, masks!=notexture && !(masks->type&Texture::STUB) && (lightmodels || glowmodels || shouldenvmap));
-        }
- 
-        void setshader(mesh *m, const animstate *as, bool masked)
-        {
-            m->setshader(loadshader(envmaptmu>=0 && envmapmax>0, masked));
+            else if(masked && lightmodels && !fullbright) SETMODELSHADER(m, masksmodel);
+            else if(masked && glowmodels) SETMODELSHADER(m, masksnospecmodel);
+            else if(spec>=0.01f && lightmodels && !fullbright) SETMODELSHADER(m, stdmodel);
+            else SETMODELSHADER(m, nospecmodel);
         }
 
         void bind(mesh *b, const animstate *as)
         {
-            if(!cullface && enablecullface) { glDisable(GL_CULL_FACE); enablecullface = false; }
-            else if(cullface && !enablecullface) { glEnable(GL_CULL_FACE); enablecullface = true; }
-
             if(as->anim&ANIM_NOSKIN)
             {
                 if(enablealphatest) { glDisable(GL_ALPHA_TEST); enablealphatest = false; }
-                if(enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; }
+                if(!(as->anim&ANIM_SHADOW) && enablealphablend) { glDisable(GL_BLEND); enablealphablend = false; }
                 if(enableglow) disableglow();
                 if(enableenvmap) disableenvmap();
                 if(enablelighting) { glDisable(GL_LIGHTING); enablelighting = false; }
-                if(enablerescale) { glDisable(hasRN ? GL_RESCALE_NORMAL_EXT : GL_NORMALIZE); enablerescale = false; }
                 if(enablefog) disablefog(true);
                 if(shadowmapping) SETMODELSHADER(b, shadowmapcaster);
-                else /*if(as->anim&ANIM_SHADOW)*/ SETMODELSHADER(b, notexturemodel);
+                else /*if(as->anim&ANIM_SHADOW)*/ SETMODELSHADER(b, dynshadow); // this shader also gets used with color mask disabled
                 return;
             }
             Texture *s = bumpmapped() && unlittex ? unlittex : tex, 
-                    *m = masks->type&Texture::STUB ? notexture : masks, 
+                    *m = masks->type==Texture::STUB ? notexture : masks, 
                     *n = bumpmapped() ? normalmap : NULL;
             if((renderpath==R_FIXEDFUNCTION || !lightmodels) &&
                (!glowmodels || (renderpath==R_FIXEDFUNCTION && fogging && maxtmus<=2)) &&
-               (!envmapmodels || envmaptmu<0 || envmapmax<=0))
+               (!envmapmodels || !(as->anim&ANIM_ENVMAP) || envmapmax<=0))
                 m = notexture;
             if(renderpath==R_FIXEDFUNCTION) setuptmus(as, m!=notexture);
             else
@@ -306,7 +291,7 @@ struct animmodel : model
                 glBindTexture(GL_TEXTURE_2D, n->id);
                 glActiveTexture_(GL_TEXTURE0_ARB);
             }
-            if(s->bpp==4)
+            if(s->bpp==32)
             {
                 if(alphablend)
                 {
@@ -332,7 +317,7 @@ struct animmodel : model
             else
             {
                 if(enablealphatest) { glDisable(GL_ALPHA_TEST); enablealphatest = false; }
-                if(enablealphablend && transparent>=1) { glDisable(GL_BLEND); enablealphablend = false; }
+                if(enablealphablend && !(as->anim&ANIM_TRANSLUCENT)) { glDisable(GL_BLEND); enablealphablend = false; }
             }
             if(m!=lastmasks && m!=notexture)
             {
@@ -341,7 +326,7 @@ struct animmodel : model
                 if(!enableglow) glActiveTexture_(GL_TEXTURE0_ARB);
                 lastmasks = m;
             }
-            if((renderpath!=R_FIXEDFUNCTION || m!=notexture) && envmaptmu>=0 && envmapmax>0)
+            if((renderpath!=R_FIXEDFUNCTION || m!=notexture) && as->anim&ANIM_ENVMAP && envmapmax>0)
             {
                 GLuint emtex = envmap ? envmap->id : closestenvmaptex;
                 if(!enableenvmap || lastenvmaptex!=emtex)
@@ -360,7 +345,6 @@ struct animmodel : model
                             glEnable(GL_TEXTURE_GEN_R);
                         }
                         enableenvmap = true;
-                        if(!enablerescale) { glEnable(hasRN ? GL_RESCALE_NORMAL_EXT : GL_NORMALIZE); enablerescale = true; }
                     }
                     if(lastenvmaptex!=emtex) { glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, emtex); lastenvmaptex = emtex; }
                     glActiveTexture_(GL_TEXTURE0_ARB);
@@ -387,12 +371,22 @@ struct animmodel : model
             DELETEA(name);
         }
 
+        virtual mesh *allocate() = 0;
+        virtual mesh *copy()
+        {
+            mesh &m = *allocate();
+            if(name) m.name = newstring(name);
+            m.noclip = noclip;
+            return &m;
+        }
+            
+        virtual void scaleverts(const vec &transdiff, float scalediff) {}        
         virtual void calcbb(int frame, vec &bbmin, vec &bbmax, const matrix3x4 &m) {}
         virtual void gentris(int frame, Texture *tex, vector<BIH::tri> *out, const matrix3x4 &m) {}
 
         virtual void setshader(Shader *s) 
         { 
-            if(glaring) s->setvariant(0, 2);
+            if(glaring) s->variant(0, 2)->set();
             else s->set(); 
         }
     };
@@ -418,7 +412,7 @@ struct animmodel : model
         }            
 
         virtual int findtag(const char *name) { return -1; }
-        virtual void concattagtransform(part *p, int frame, int i, const matrix3x4 &m, matrix3x4 &n) {}
+        virtual void concattagtransform(int frame, int i, const matrix3x4 &m, matrix3x4 &n) {}
 
         void calcbb(int frame, vec &bbmin, vec &bbmax, const matrix3x4 &m)
         {
@@ -435,8 +429,41 @@ struct animmodel : model
         bool hasframes(int i, int n) const { return i>=0 && i+n<=totalframes(); }
         int clipframes(int i, int n) const { return min(n, totalframes() - i); }
 
+        virtual meshgroup *allocate() = 0;
+        virtual meshgroup *copy()
+        {
+            meshgroup &group = *allocate();
+            group.name = newstring(name);
+            loopv(meshes) group.meshes.add(meshes[i]->copy())->group = &group;
+            group.scale = scale;
+            group.translate = translate;
+            return &group;
+        }
+       
+        virtual void scaletags(const vec &transdiff, float scalediff) {}
+ 
+        meshgroup *scaleverts(float nscale, const vec &ntranslate)
+        {
+            if(nscale==scale && ntranslate==translate) { shared++; return this; }
+            else if(next || shared)
+            {
+                if(!next) next = copy();
+                return next->scaleverts(nscale, ntranslate);
+            }
+            float scalediff = nscale/scale;
+            vec transdiff(ntranslate);
+            transdiff.sub(translate);
+            transdiff.mul(scale);
+            loopv(meshes) meshes[i]->scaleverts(transdiff, scalediff);
+            scaletags(transdiff, scalediff);
+            scale = nscale;
+            translate = ntranslate;
+            shared++;
+            return this;
+        }
+
         virtual void cleanup() {}
-        virtual void render(const animstate *as, float pitch, const vec &axis, dynent *d, part *p) {}
+        virtual void render(const animstate *as, float pitch, const vec &axis, part *p) {}
     };
 
     virtual meshgroup *loadmeshes(char *name, va_list args) { return NULL; }
@@ -460,10 +487,9 @@ struct animmodel : model
     {
         part *p;
         int tag, anim, basetime;
-        vec *pos;
-        glmatrixf matrix;
+        GLfloat matrix[16];
 
-        linkedpart() : p(NULL), tag(-1), anim(-1), basetime(0), pos(NULL) {}
+        linkedpart() : p(NULL), tag(-1), anim(-1), basetime(0) {}
     };
 
     struct part
@@ -476,9 +502,8 @@ struct animmodel : model
         vector<animspec> *anims[MAXANIMPARTS];
         int numanimparts;
         float pitchscale, pitchoffset, pitchmin, pitchmax;
-        vec translate;
 
-        part() : meshes(NULL), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0), translate(0, 0, 0)
+        part() : meshes(NULL), numanimparts(1), pitchscale(1), pitchoffset(0), pitchmin(0), pitchmax(0) 
         {
             loopk(MAXANIMPARTS) anims[k] = NULL;
         }
@@ -494,53 +519,41 @@ struct animmodel : model
 
         void calcbb(int frame, vec &bbmin, vec &bbmax, const matrix3x4 &m)
         {
-            matrix3x4 t = m;
-            t.translate(translate);
-            t.scale(model->scale);
-            meshes->calcbb(frame, bbmin, bbmax, t);
+            meshes->calcbb(frame, bbmin, bbmax, m);
             loopv(links)
             {
                 matrix3x4 n;
-                meshes->concattagtransform(this, frame, links[i].tag, m, n);
+                meshes->concattagtransform(frame, links[i].tag, m, n);
                 links[i].p->calcbb(frame, bbmin, bbmax, n);
             }
         }
 
         void gentris(int frame, vector<BIH::tri> *tris, const matrix3x4 &m)
         {
-            matrix3x4 t = m;
-            t.translate(translate);
-            t.scale(model->scale);
-            meshes->gentris(frame, skins, tris, t);
+            meshes->gentris(frame, skins, tris, m);
             loopv(links)
             {
                 matrix3x4 n;
-                meshes->concattagtransform(this, frame, links[i].tag, m, n);
+                meshes->concattagtransform(frame, links[i].tag, m, n);
                 links[i].p->gentris(frame, tris, n);
             }
         }
 
-        bool link(part *p, const char *tag, int anim = -1, int basetime = 0, vec *pos = NULL)
+        bool link(part *p, const char *tag, int anim = -1, int basetime = 0)
         {
-            int i = meshes ? meshes->findtag(tag) : -1;
-            if(i<0)
-            {
-                loopv(links) if(links[i].p && links[i].p->link(p, tag, anim, basetime, pos)) return true;
-                return false;
-            }
+            int i = meshes->findtag(tag);
+            if(i<0) return false;
             linkedpart &l = links.add();
             l.p = p;
             l.tag = i;
             l.anim = anim;
             l.basetime = basetime;
-            l.pos = pos;
             return true;
         }
 
         bool unlink(part *p)
         {
             loopvrev(links) if(links[i].p==p) { links.remove(i, 1); return true; }
-            loopv(links) if(links[i].p && links[i].p->unlink(p)) return true;
             return false;
         }
 
@@ -560,24 +573,36 @@ struct animmodel : model
             }
         }
 
-        void preloadshaders()
-        {
-            loopv(skins) skins[i].preloadshader();
-        }
-
         virtual void getdefaultanim(animinfo &info, int anim, uint varseed, dynent *d)
         {
             info.frame = 0;
             info.range = 1;
         }
 
-        bool calcanim(int animpart, int anim, int basetime, int basetime2, dynent *d, int interp, animinfo &info)
+        void getanimspeed(animinfo &info, dynent *d)
+        {
+            switch(info.anim&ANIM_INDEX)
+            {
+                case ANIM_FORWARD:
+                case ANIM_BACKWARD:
+                case ANIM_LEFT:
+                case ANIM_RIGHT:
+                case ANIM_SWIM:
+                    info.speed = 5500.0f/d->maxspeed;
+                    break;
+
+                default:
+                    info.speed = 100.0f;
+                    break;
+            }
+        }
+
+        bool calcanim(int animpart, int anim, float speed, int basetime, dynent *d, int interp, animinfo &info)
         {
-            uint varseed = uint((size_t)d);
+            uint varseed = uint(basetime + (int)(size_t)d);
             info.anim = anim;
-            info.basetime = basetime;
             info.varseed = varseed;
-            info.speed = anim&ANIM_SETSPEED ? basetime2 : 100.0f;
+            info.speed = speed;
             if((anim&ANIM_INDEX)==ANIM_ALL)
             {
                 info.frame = 0;
@@ -589,18 +614,17 @@ struct animmodel : model
                 if(anims[animpart])
                 {
                     vector<animspec> &primary = anims[animpart][anim&ANIM_INDEX];
-                    if(primary.length()) spec = &primary[uint(varseed + basetime)%primary.length()];
-                    if((anim>>ANIM_SECONDARY)&(ANIM_INDEX|ANIM_DIR))
+                    if(primary.length()) spec = &primary[varseed%primary.length()];
+                    if((anim>>ANIM_SECONDARY)&ANIM_INDEX)
                     {
                         vector<animspec> &secondary = anims[animpart][(anim>>ANIM_SECONDARY)&ANIM_INDEX];
                         if(secondary.length())
                         {
-                            animspec &spec2 = secondary[uint(varseed + basetime2)%secondary.length()];
+                            animspec &spec2 = secondary[varseed%secondary.length()];
                             if(!spec || spec2.priority > spec->priority)
                             {
                                 spec = &spec2;
                                 info.anim >>= ANIM_SECONDARY;
-                                info.basetime = basetime2;
                             }
                         }
                     }
@@ -611,15 +635,17 @@ struct animmodel : model
                     info.range = spec->range;
                     if(spec->speed>0) info.speed = 1000.0f/spec->speed;
                 }
-                else getdefaultanim(info, anim, uint(varseed + info.basetime), d);
+                else getdefaultanim(info, anim, varseed, d);
             }
+            if(info.speed<=0) getanimspeed(info, d);
 
             info.anim &= (1<<ANIM_SECONDARY)-1;
             info.anim |= anim&ANIM_FLAGS;
-            if(info.anim&(ANIM_LOOP|ANIM_START|ANIM_END))
+            info.basetime = basetime;
+            if(info.anim&(ANIM_LOOP|ANIM_START|ANIM_END) && (anim>>ANIM_SECONDARY)&ANIM_INDEX)
             {
                 info.anim &= ~ANIM_SETTIME;
-                if(!info.basetime) info.basetime = -((int)(size_t)d&0xFFF);
+                info.basetime = -((int)(size_t)d&0xFFF);
             }
             if(info.anim&(ANIM_START|ANIM_END))
             {
@@ -636,12 +662,7 @@ struct animmodel : model
             if(d && interp>=0)
             {
                 animinterpinfo &ai = d->animinterp[interp];
-                if(d->ragdoll && !(anim&ANIM_RAGDOLL)) 
-                {
-                    ai.prev.range = ai.cur.range = 0;
-                    ai.lastswitch = -1;
-                }
-                else if(ai.lastmodel!=this || ai.lastswitch<0 || lastmillis-d->lastrendered>animationinterpolationtime)
+                if(ai.lastmodel!=this || ai.lastswitch<0 || lastmillis-d->lastrendered>animationinterpolationtime)
                 {
                     ai.prev = ai.cur = info;
                     ai.lastswitch = lastmillis-animationinterpolationtime*2;
@@ -658,6 +679,46 @@ struct animmodel : model
             return true;
         }
 
+        void calcnormal(GLfloat *m, vec &dir)
+        {
+            vec n(dir);
+            dir.x = n.x*m[0] + n.y*m[1] + n.z*m[2];
+            dir.y = n.x*m[4] + n.y*m[5] + n.z*m[6];
+            dir.z = n.x*m[8] + n.y*m[9] + n.z*m[10];
+        }
+
+        void calcplane(GLfloat *m, plane &p)
+        {
+            p.offset += p.x*m[12] + p.y*m[13] + p.z*m[14];
+            calcnormal(m, p);
+        }
+
+        void calcvertex(GLfloat *m, vec &pos)
+        {
+            vec p(pos);
+
+            p.x -= m[12];
+            p.y -= m[13];
+            p.z -= m[14];
+
+#if 0
+            // This is probably overkill, since just about any transformations this encounters will be orthogonal matrices 
+            // where their inverse is simply the transpose.
+            int a = fabs(m[0])>fabs(m[1]) && fabs(m[0])>fabs(m[2]) ? 0 : (fabs(m[1])>fabs(m[2]) ? 1 : 2), b = (a+1)%3, c = (a+2)%3;
+            float a1 = m[a], a2 = m[a+4], a3 = m[a+8],
+                  b1 = m[b], b2 = m[b+4], b3 = m[b+8],
+                  c1 = m[c], c2 = m[c+4], c3 = m[c+8];
+
+            pos.z = (p[c] - c1*p[a]/a1 - (c2 - c1*a2/a1)*(p[b] - b1*p[a]/a1)/(b2 - b1*a2/a1)) / (c3 - c1*a3/a1 - (c2 - c1*a2/a1)*(b3 - b1*a3/a1)/(b2 - b1*a2/a1));
+            pos.y = (p[b] - b1*p[a]/a1 - (b3 - b1*a3/a1)*pos.z)/(b2 - b1*a2/a1);
+            pos.x = (p[a] - a2*pos.y - a3*pos.z)/a1;
+#else
+            pos.x = p.x*m[0] + p.y*m[1] + p.z*m[2];
+            pos.y = p.x*m[4] + p.y*m[5] + p.z*m[6];
+            pos.z = p.x*m[8] + p.y*m[9] + p.z*m[10];
+#endif
+        }
+
         float calcpitchaxis(int anim, float pitch, vec &axis, vec &dir, vec &campos, plane &fogplane)
         {
             float angle = pitchscale*pitch + pitchoffset;
@@ -677,19 +738,19 @@ struct animmodel : model
             return angle;
         }
 
-        void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, dynent *d, const vec &dir, const vec &campos, const plane &fogplane)
+        void render(int anim, float speed, int basetime, float pitch, const vec &axis, dynent *d, const vec &dir, const vec &campos, const plane &fogplane)
         {
             animstate as[MAXANIMPARTS];
-            render(anim, basetime, basetime2, pitch, axis, d, dir, campos, fogplane, as);
+            render(anim, speed, basetime, pitch, axis, d, dir, campos, fogplane, as);
         }
 
-        void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, dynent *d, const vec &dir, const vec &campos, const plane &fogplane, animstate *as)
+        void render(int anim, float speed, int basetime, float pitch, const vec &axis, dynent *d, const vec &dir, const vec &campos, const plane &fogplane, animstate *as)
         {
             if(!(anim&ANIM_REUSE)) loopi(numanimparts)
             {
                 animinfo info;
                 int interp = d && index+numanimparts<=MAXANIMPARTS ? index+i : -1;
-                if(!calcanim(i, anim, basetime, basetime2, d, interp, info)) return;
+                if(!calcanim(i, anim, speed, basetime, d, interp, info)) return;
                 animstate &p = as[i];
                 p.owner = this;
                 p.anim = info.anim;
@@ -706,52 +767,49 @@ struct animmodel : model
                 }
             }
 
+            if(!model->cullface && enablecullface) { glDisable(GL_CULL_FACE); enablecullface = false; }
+            else if(model->cullface && !enablecullface) { glEnable(GL_CULL_FACE); enablecullface = true; }
+
             vec raxis(axis), rdir(dir), rcampos(campos);
             plane rfogplane(fogplane);
             float pitchamount = calcpitchaxis(anim, pitch, raxis, rdir, rcampos, rfogplane);
             if(pitchamount)
             {
-                ++matrixpos;
-                matrixstack[matrixpos] = matrixstack[matrixpos-1];
-                matrixstack[matrixpos].rotate(pitchamount*RAD, axis);
-            }
-
-            if(!(anim&ANIM_NORENDER))
-            {
                 glPushMatrix();
-                glMultMatrixf(matrixstack[matrixpos].v);
-                if(model->scale!=1) glScalef(model->scale, model->scale, model->scale);
-                if(!translate.iszero()) glTranslatef(translate.x, translate.y, translate.z);
-                if(renderpath!=R_FIXEDFUNCTION && envmaptmu>=0)
+                glRotatef(pitchamount, axis.x, axis.y, axis.z);
+                if(renderpath!=R_FIXEDFUNCTION && anim&ANIM_ENVMAP)
                 {
                     glMatrixMode(GL_TEXTURE);
-                    glLoadMatrixf(matrixstack[matrixpos].v);
+                    glPushMatrix();
+                    glRotatef(pitchamount, axis.x, axis.y, axis.z);
                     glMatrixMode(GL_MODELVIEW);
                 }
             }
 
-            if(!(anim&(ANIM_NOSKIN|ANIM_NORENDER)))
+            if(!(anim&ANIM_NOSKIN))
             {
                 if(renderpath!=R_FIXEDFUNCTION)
                 {
-                    if(fogging) setfogplane(plane(rfogplane).translate(translate).scale(model->scale));
+                    if(fogging) setfogplane(rfogplane);
                     setenvparamf("direction", SHPARAM_VERTEX, 0, rdir.x, rdir.y, rdir.z);
-                    vec ocampos(rcampos);
-                    ocampos.div(model->scale).sub(translate);
-                    setenvparamf("camera", SHPARAM_VERTEX, 1, ocampos.x, ocampos.y, ocampos.z, 1);
+                    setenvparamf("camera", SHPARAM_VERTEX, 1, rcampos.x, rcampos.y, rcampos.z, 1);
                 }
                 else
                 {
-                    if(fogging) refractfogplane = plane(rfogplane).translate(translate).scale(model->scale);
+                    if(fogging) refractfogplane = rfogplane;
+                    if(lightmodels) 
+                    {
+                        loopv(skins) if(!skins[i].fullbright)
+                        {
+                            GLfloat pos[4] = { rdir.x*1000, rdir.y*1000, rdir.z*1000, 0 };
+                            glLightfv(GL_LIGHT0, GL_POSITION, pos);
+                            break;
+                        }
+                    }
                 }
             }
 
-            meshes->render(as, pitch, axis, d, this);
-
-            if(!(anim&ANIM_NORENDER))
-            {
-                glPopMatrix();
-            }
+            meshes->render(as, pitch, axis, this);
 
             if(!(anim&ANIM_REUSE)) 
             {
@@ -759,41 +817,52 @@ struct animmodel : model
                 {
                     linkedpart &link = links[i];
 
-                    matrixpos++;
-                    matrixstack[matrixpos].mul(matrixstack[matrixpos-1], link.matrix);
-
-                    if(link.pos) *link.pos = matrixstack[matrixpos].gettranslation();
-
-                    if(!link.p)
-                    {
-                        matrixpos--;
-                        continue;
-                    }
-
                     vec naxis(raxis), ndir(rdir), ncampos(rcampos);
                     plane nfogplane(rfogplane);
-                    link.matrix.invertnormal(naxis);
-                    if(!(anim&(ANIM_NOSKIN|ANIM_NORENDER)))
+                    calcnormal(link.matrix, naxis);
+                    if(!(anim&ANIM_NOSKIN))
                     {
-                        link.matrix.invertnormal(ndir);
-                        link.matrix.invertvertex(ncampos);
-                        link.matrix.invertplane(nfogplane);
+                        calcnormal(link.matrix, ndir);
+                        calcvertex(link.matrix, ncampos);
+                        calcplane(link.matrix, nfogplane);
                     }
 
-                    int nanim = anim, nbasetime = basetime, nbasetime2 = basetime2;
+                    glPushMatrix();
+                    glMultMatrixf(link.matrix);
+                    if(renderpath!=R_FIXEDFUNCTION && anim&ANIM_ENVMAP)
+                    {
+                        glMatrixMode(GL_TEXTURE);
+                        glPushMatrix();
+                        glMultMatrixf(link.matrix);
+                        glMatrixMode(GL_MODELVIEW);
+                    }
+                    int nanim = anim, nbasetime = basetime;
                     if(link.anim>=0)
                     {
                         nanim = link.anim | (anim&ANIM_FLAGS);
                         nbasetime = link.basetime;
-                        nbasetime2 = 0;
                     }
-                    link.p->render(nanim, nbasetime, nbasetime2, pitch, naxis, d, ndir, ncampos, nfogplane);
-
-                    matrixpos--;
+                    link.p->render(nanim, speed, nbasetime, pitch, naxis, d, ndir, ncampos, nfogplane);
+                    if(renderpath!=R_FIXEDFUNCTION && anim&ANIM_ENVMAP)
+                    {
+                        glMatrixMode(GL_TEXTURE);
+                        glPopMatrix();
+                        glMatrixMode(GL_MODELVIEW);
+                    }
+                    glPopMatrix();
                 }
             }
 
-            if(pitchamount) matrixpos--;
+            if(pitchamount)
+            {
+                glPopMatrix();
+                if(renderpath!=R_FIXEDFUNCTION && anim&ANIM_ENVMAP)
+                {
+                    glMatrixMode(GL_TEXTURE);
+                    glPopMatrix();
+                    glMatrixMode(GL_MODELVIEW);
+                }
+            }
         }
 
         void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0)
@@ -822,29 +891,22 @@ struct animmodel : model
 
     virtual int linktype(animmodel *m) const { return LINK_TAG; }
 
-    void render(int anim, int basetime, int basetime2, float pitch, const vec &axis, dynent *d, modelattach *a, const vec &dir, const vec &campos, const plane &fogplane)
+    void render(int anim, float speed, int basetime, float pitch, const vec &axis, dynent *d, modelattach *a, const vec &dir, const vec &campos, const plane &fogplane)
     {
         if(!loaded) return;
 
-        int numtags = 0;
         if(a)
         {
             int index = parts.last()->index + parts.last()->numanimparts;
-            for(int i = 0; a[i].tag; i++)
+            for(int i = 0; a[i].name; i++)
             {
-                numtags++;
-
                 animmodel *m = (animmodel *)a[i].m;
-                if(!m || !m->loaded)
-                {
-                    if(a[i].pos) link(NULL, a[i].tag, 0, 0, a[i].pos);
-                    continue;
-                }
+                if(!m || !m->loaded) continue;
                 part *p = m->parts[0];
                 switch(linktype(m))
                 {
                     case LINK_TAG:
-                        p->index = link(p, a[i].tag, a[i].anim, a[i].basetime, a[i].pos) ? index : -1;
+                        p->index = link(p, a[i].tag, a[i].anim, a[i].basetime) ? index : -1;
                         break;
 
                     case LINK_COOP:
@@ -859,16 +921,12 @@ struct animmodel : model
         }
 
         animstate as[MAXANIMPARTS];
-        parts[0]->render(anim, basetime, basetime2, pitch, axis, d, dir, campos, fogplane, as);
+        parts[0]->render(anim, speed, basetime, pitch, axis, d, dir, campos, fogplane, as);
 
-        if(a) for(int i = numtags-1; i >= 0; i--)
+        if(a) for(int i = 0; a[i].name; i++)
         {
             animmodel *m = (animmodel *)a[i].m;
-            if(!m || !m->loaded)
-            {
-                if(a[i].pos) unlink(NULL);
-                continue;
-            }
+            if(!m || !m->loaded) continue;
             part *p = m->parts[0];
             switch(linktype(m))
             {
@@ -878,113 +936,107 @@ struct animmodel : model
                     break;
 
                 case LINK_COOP:
-                    p->render(anim, basetime, basetime2, pitch, axis, d, dir, campos, fogplane);
+                    p->render(anim, speed, basetime, pitch, axis, d, dir, campos, fogplane);
                     p->index = 0;
                     break;
 
                 case LINK_REUSE:
-                    p->render(anim | ANIM_REUSE, basetime, basetime2, pitch, axis, d, dir, campos, fogplane, as); 
+                    p->render(anim | ANIM_REUSE, speed, basetime, pitch, axis, d, dir, campos, fogplane, as); 
                     break;
             }
         }
     }
 
-    void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, const vec &color, const vec &dir, float trans)
+    void render(int anim, float speed, int basetime, const vec &o, float yaw, float pitch, dynent *d, modelattach *a, const vec &color, const vec &dir)
     {
         if(!loaded) return;
 
         vec rdir, campos;
         plane fogplane;
 
-        yaw += offsetyaw + spinyaw*lastmillis/1000.0f;
-        pitch += offsetpitch + spinpitch*lastmillis/1000.0f;
-
-        matrixpos = 0;
-        matrixstack[0].identity();
-        if(!d || !d->ragdoll || anim&ANIM_RAGDOLL)
-        {
-            matrixstack[0].translate(o);
-            matrixstack[0].rotate_around_z((yaw+180)*RAD);
-        }
-        else pitch = 0;
-
-        if(anim&ANIM_NORENDER)
-        {
-            render(anim, basetime, basetime2, pitch, vec(0, -1, 0), d, a, rdir, campos, fogplane);
-            if(d) d->lastrendered = lastmillis;
-            return;
-        }
+        yaw += offsetyaw + spin*lastmillis/1000.0f;
+        pitch += offsetpitch;
 
         if(!(anim&ANIM_NOSKIN))
         {
-            if(renderpath==R_FIXEDFUNCTION && lightmodels)
-            {
-                GLfloat pos[4] = { dir.x*1000, dir.y*1000, dir.z*1000, 0 };
-                glLightfv(GL_LIGHT0, GL_POSITION, pos);
-            }
-                
-            transparent = trans;
+            fogplane = plane(0, 0, 1, o.z-reflectz);
+
             lightcolor = color;
-            
-            fogplane = plane(0, 0, 1, -reflectz);
+
             rdir = dir;
+            rdir.rotate_around_z((-yaw-180.0f)*RAD);
+
             campos = camera1->o;
-            if(!d || !d->ragdoll || anim&ANIM_RAGDOLL) 
-            {
-                fogplane.offset += o.z;
-                rdir.rotate_around_z((-yaw-180.0f)*RAD);
-                campos.sub(o);
-                campos.rotate_around_z((-yaw-180.0f)*RAD);
-            }
+            campos.sub(o);
+            campos.rotate_around_z((-yaw-180.0f)*RAD);
 
-            if(envmapped()) envmaptmu = 2;
-            else if(a) for(int i = 0; a[i].tag; i++) if(a[i].m && a[i].m->envmapped())
+            if(envmapped()) anim |= ANIM_ENVMAP;
+            else if(a) for(int i = 0; a[i].name; i++) if(a[i].m && a[i].m->envmapped())
             {
-                envmaptmu = 2;
+                anim |= ANIM_ENVMAP;
                 break;
             }
-            if(envmaptmu>=0) closestenvmaptex = lookupenvmap(closestenvmap(o));
+            if(anim&ANIM_ENVMAP) closestenvmaptex = lookupenvmap(closestenvmap(o));
         }
 
-        if(envmaptmu>=0 && renderpath==R_FIXEDFUNCTION)
+        if(anim&ANIM_ENVMAP)
         {
-            if(fogging) envmaptmu = 3;
-
-            glActiveTexture_(GL_TEXTURE0_ARB+envmaptmu);
-            setuptmu(envmaptmu, "T , P @ Pa", transparent<1 ? "= Ka" : NULL);
-
-            glmatrixf mmtrans = mvmatrix;
-            if(reflecting) mmtrans.reflectz(reflectz);
-            mmtrans.transpose();
-
+            envmaptmu = 2;
+            if(renderpath==R_FIXEDFUNCTION)
+            {
+                if(fogging) envmaptmu = 3;
+                glActiveTexture_(GL_TEXTURE0_ARB+envmaptmu);
+            }
             glMatrixMode(GL_TEXTURE);
-            glLoadMatrixf(mmtrans.v);
+            if(renderpath==R_FIXEDFUNCTION)
+            {
+                setuptmu(envmaptmu, "T , P @ Pa", anim&ANIM_TRANSLUCENT ? "= Ka" : NULL);
+
+                GLfloat mm[16], mmtrans[16];
+                glGetFloatv(GL_MODELVIEW_MATRIX, mm);
+                loopi(4) // transpose modelview (mmtrans[4*i+j] = mm[4*j+i])
+                {
+                    GLfloat x = mm[i], y = mm[4+i], z = mm[8+i], w = mm[12+i];
+                    mmtrans[4*i] = x;
+                    mmtrans[4*i+1] = y;
+                    mmtrans[4*i+2] = z;
+                    mmtrans[4*i+3] = w;
+                }
+                glLoadMatrixf(mmtrans);
+            }
+            else
+            {
+                glLoadIdentity();
+                glTranslatef(o.x, o.y, o.z);
+                glRotatef(yaw+180, 0, 0, 1);
+            }
             glMatrixMode(GL_MODELVIEW);
-            glActiveTexture_(GL_TEXTURE0_ARB);
+            if(renderpath==R_FIXEDFUNCTION) glActiveTexture_(GL_TEXTURE0_ARB);
         }
 
-        if(transparent<1)
+        glPushMatrix();
+        glTranslatef(o.x, o.y, o.z);
+        glRotatef(yaw+180, 0, 0, 1);
+
+        if(anim&ANIM_TRANSLUCENT)
         {
-            if(alphadepth)
-            {
-                glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
-                render(anim|ANIM_NOSKIN, basetime, basetime2, pitch, vec(0, -1, 0), d, a, rdir, campos, fogplane);
-                glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE);
+            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+            render(anim|ANIM_NOSKIN, speed, basetime, pitch, vec(0, -1, 0), d, a, rdir, campos, fogplane);
+            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE);
 
-                glDepthFunc(GL_LEQUAL);
-            }
+            glDepthFunc(GL_LEQUAL);
+        }
 
-            if(!enablealphablend)
-            {
-                glEnable(GL_BLEND);
-                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-                enablealphablend = true;
-            }
+        if(anim&(ANIM_TRANSLUCENT|ANIM_SHADOW) && !enablealphablend)
+        {
+            glEnable(GL_BLEND);
+            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+            enablealphablend = true;
         }
 
-        render(anim, basetime, basetime2, pitch, vec(0, -1, 0), d, a, rdir, campos, fogplane);
+        render(anim, speed, basetime, pitch, vec(0, -1, 0), d, a, rdir, campos, fogplane);
 
-        if(envmaptmu>=0)
+        if(anim&ANIM_ENVMAP)
         {
             if(renderpath==R_FIXEDFUNCTION) glActiveTexture_(GL_TEXTURE0_ARB+envmaptmu);
             glMatrixMode(GL_TEXTURE);
@@ -993,7 +1045,9 @@ struct animmodel : model
             if(renderpath==R_FIXEDFUNCTION) glActiveTexture_(GL_TEXTURE0_ARB);
         }
 
-        if(transparent<1 && alphadepth) glDepthFunc(GL_LESS);
+        if(anim&ANIM_TRANSLUCENT) glDepthFunc(GL_LESS);
+
+        glPopMatrix();
 
         if(d) d->lastrendered = lastmillis;
     }
@@ -1023,9 +1077,18 @@ struct animmodel : model
 
     void initmatrix(matrix3x4 &m)
     {
-        m.identity();
-        if(offsetyaw) m.rotate_around_z(offsetyaw*RAD);
-        if(offsetpitch) m.rotate_around_y(-offsetpitch*RAD);
+        if(offsetyaw)
+        {
+            m.rotate(offsetyaw*RAD, vec(0, 0, 1));
+            if(offsetpitch)
+            {
+                matrix3x4 n;
+                n.rotate(offsetpitch*RAD, vec(0, -1, 0));
+                m.mul(n);
+            }
+        }
+        else if(offsetpitch) m.rotate(offsetpitch*RAD, vec(0, -1, 0));
+        else m.identity();
     }
 
     void gentris(int frame, vector<BIH::tri> *tris)
@@ -1045,16 +1108,16 @@ struct animmodel : model
         return bih;
     }
 
-    bool link(part *p, const char *tag, int anim = -1, int basetime = 0, vec *pos = NULL)
+    bool link(part *p, const char *tag, int anim = -1, int basetime = 0)
     {
-        if(parts.empty()) return false;
-        return parts[0]->link(p, tag, anim, basetime, pos);
+        loopv(parts) if(parts[i]->link(p, tag, anim, basetime)) return true;
+        return false;
     }
 
     bool unlink(part *p)
     {
-        if(parts.empty()) return false;
-        return parts[0]->unlink(p);
+        loopv(parts) if(parts[i]->unlink(p)) return true;
+        return false;
     }
 
     bool envmapped()
@@ -1068,11 +1131,6 @@ struct animmodel : model
         return true;
     }
 
-    void preloadshaders()
-    {
-        loopv(parts) parts[i]->preloadshaders();
-    }
-
     void setshader(Shader *shader)
     {
         if(parts.empty()) loaddefaultparts();
@@ -1135,16 +1193,16 @@ struct animmodel : model
         loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].alphablend = alphablend;
     }
 
-    void setfullbright(float fullbright)
+    void settranslucency(float translucency)
     {
         if(parts.empty()) loaddefaultparts();
-        loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].fullbright = fullbright;
+        loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].translucency = translucency;
     }
 
-    void setcullface(bool cullface)
+    void setfullbright(float fullbright)
     {
         if(parts.empty()) loaddefaultparts();
-        loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].cullface = cullface;
+        loopv(parts) loopvj(parts[i]->skins) parts[i]->skins[j].fullbright = fullbright;
     }
 
     void calcbb(int frame, vec &center, vec &radius)
@@ -1161,26 +1219,24 @@ struct animmodel : model
         center.add(radius);
     }
 
-    static bool enabletc, enablemtc, enablealphatest, enablealphablend, enableenvmap, enableglow, enableoverbright, enablelighting, enablelight0, enablecullface, enablefog, enablenormals, enabletangents, enablebones, enablerescale;
+    static bool enabletc, enablemtc, enablealphatest, enablealphablend, enableenvmap, enableglow, enableoverbright, enablelighting, enablelight0, enablecullface, enablefog, enabletangents, enablebones;
     static vec lightcolor;
     static plane refractfogplane;
-    static float transparent, lastalphatest;
-    static void *lastvbuf, *lasttcbuf, *lastmtcbuf, *lastnbuf, *lastxbuf, *lastbbuf, *lastsdata, *lastbdata;
+    static float lastalphatest;
+    static void *lastvbuf, *lasttcbuf, *lastmtcbuf, *lastnbuf, *lastbbuf, *lastsdata, *lastbdata;
     static GLuint lastebuf, lastenvmaptex, closestenvmaptex;
     static Texture *lasttex, *lastmasks, *lastnormalmap;
-    static int envmaptmu, fogtmu, matrixpos;
-    static glmatrixf matrixstack[64];
+    static int envmaptmu, fogtmu;
 
     void startrender()
     {
-        enabletc = enablemtc = enablealphatest = enablealphablend = enableenvmap = enableglow = enableoverbright = enablelighting = enablefog = enablenormals = enabletangents = enablebones = enablerescale = false;
+        enabletc = enablemtc = enablealphatest = enablealphablend = enableenvmap = enableglow = enableoverbright = enablelighting = enablefog = enabletangents = enablebones = false;
         enablecullface = true;
         lastalphatest = -1;
-        lastvbuf = lasttcbuf = lastmtcbuf = lastxbuf = lastnbuf = lastbbuf = lastsdata = lastbdata = NULL;
+        lastvbuf = lasttcbuf = lastmtcbuf = lastnbuf = lastbbuf = lastsdata = lastbdata = NULL;
         lastebuf = lastenvmaptex = closestenvmaptex = 0;
         lasttex = lastmasks = lastnormalmap = NULL;
         envmaptmu = fogtmu = -1;
-        transparent = 1;
 
         if(renderpath==R_FIXEDFUNCTION && lightmodels && !enablelight0)
         {
@@ -1218,16 +1274,11 @@ struct animmodel : model
     static void disabletc()
     {
         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+        glDisableClientState(GL_NORMAL_ARRAY);
         if(enablemtc) disablemtc();
         enabletc = false;
     }
 
-    static void disablenormals()
-    {
-        glDisableClientState(GL_NORMAL_ARRAY);
-        enablenormals = false;
-    }
-
     static void disablevbo()
     {
         if(hasVBO)
@@ -1237,10 +1288,9 @@ struct animmodel : model
         }
         glDisableClientState(GL_VERTEX_ARRAY);
         if(enabletc) disabletc();
-        if(enablenormals) disablenormals();
         if(enabletangents) disabletangents();
         if(enablebones) disablebones();
-        lastvbuf = lasttcbuf = lastmtcbuf = lastxbuf = lastnbuf = lastbbuf = NULL;
+        lastvbuf = lasttcbuf = lastmtcbuf = lastnbuf = lastbbuf = NULL;
         lastebuf = 0;
     }
 
@@ -1299,7 +1349,6 @@ struct animmodel : model
         if(enableoverbright) disableoverbright();
         if(enablelighting) glDisable(GL_LIGHTING);
         if(lastenvmaptex) disableenvmap(true);
-        if(enablerescale) glDisable(hasRN ? GL_RESCALE_NORMAL_EXT : GL_NORMALIZE);
         if(!enablecullface) glEnable(GL_CULL_FACE);
         if(fogtmu>=0) disablefog(true);
     }
@@ -1307,14 +1356,12 @@ struct animmodel : model
 
 bool animmodel::enabletc = false, animmodel::enablemtc = false, animmodel::enablealphatest = false, animmodel::enablealphablend = false,
      animmodel::enableenvmap = false, animmodel::enableglow = false, animmodel::enableoverbright = false, animmodel::enablelighting = false, animmodel::enablelight0 = false, animmodel::enablecullface = true,
-     animmodel::enablefog = false, animmodel::enablenormals = false, animmodel::enabletangents = false, animmodel::enablebones = false, animmodel::enablerescale = false;
+     animmodel::enablefog = false, animmodel::enabletangents = false, animmodel::enablebones = false;
 vec animmodel::lightcolor;
 plane animmodel::refractfogplane;
-float animmodel::transparent = 1, animmodel::lastalphatest = -1;
-void *animmodel::lastvbuf = NULL, *animmodel::lasttcbuf = NULL, *animmodel::lastmtcbuf = NULL, *animmodel::lastnbuf = NULL, *animmodel::lastxbuf = NULL, *animmodel::lastbbuf = NULL, *animmodel::lastsdata = NULL, *animmodel::lastbdata = NULL;
+float animmodel::lastalphatest = -1;
+void *animmodel::lastvbuf = NULL, *animmodel::lasttcbuf = NULL, *animmodel::lastmtcbuf = NULL, *animmodel::lastnbuf = NULL, *animmodel::lastbbuf = NULL, *animmodel::lastsdata = NULL, *animmodel::lastbdata = NULL;
 GLuint animmodel::lastebuf = 0, animmodel::lastenvmaptex = 0, animmodel::closestenvmaptex = 0;
 Texture *animmodel::lasttex = NULL, *animmodel::lastmasks = NULL, *animmodel::lastnormalmap = NULL;
-int animmodel::envmaptmu = -1, animmodel::fogtmu = -1, animmodel::matrixpos = 0;
-glmatrixf animmodel::matrixstack[64];
-
+int animmodel::envmaptmu = -1, animmodel::fogtmu = -1;
 
diff --git a/engine/bih.cpp b/engine/bih.cpp
index 994085b..bb6e258 100644
--- a/engine/bih.cpp
+++ b/engine/bih.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 bool BIH::triintersect(tri &t, const vec &o, const vec &ray, float maxdist, float &dist, int mode, tri *noclip)
@@ -17,8 +18,13 @@ bool BIH::triintersect(tri &t, const vec &o, const vec &ray, float maxdist, floa
     float f = t.c.dot(q) / det;
     if(f < 0 || f > maxdist) return false;
     if(!(mode&RAY_SHADOW) && &t >= noclip) return false;
-    if(t.tex && (mode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY && (t.tex->alphamask || (loadalphamask(t.tex), t.tex->alphamask)))
+    if(t.tex && (mode&RAY_ALPHAPOLY)==RAY_ALPHAPOLY)
     {
+        if(!t.tex->alphamask)
+        {
+            loadalphamask(t.tex); 
+            if(!t.tex->alphamask) { dist = f; return true; }
+        }
         int si = clamp(int(t.tex->xs * (t.tc[0] + u*(t.tc[2] - t.tc[0]) + v*(t.tc[4] - t.tc[0]))), 0, t.tex->xs-1),
             ti = clamp(int(t.tex->ys * (t.tc[1] + u*(t.tc[3] - t.tc[1]) + v*(t.tc[5] - t.tc[1]))), 0, t.tex->ys-1);
         if(!(t.tex->alphamask[ti*((t.tex->xs+7)/8) + si/8] & (1<<(si%8)))) return false;
@@ -265,11 +271,11 @@ static inline void yawray(vec &o, vec &ray, float angle)
     angle *= RAD;
     float c = cosf(angle), s = sinf(angle),
           ox = o.x, oy = o.y,
-          rx = ray.x, ry = ray.y;
+          rx = ox+ray.x, ry = oy+ray.y;
     o.x = ox*c - oy*s;
     o.y = oy*c + ox*s;
-    ray.x = rx*c - ry*s;
-    ray.y = ry*c + rx*s;
+    ray.x = rx*c - ry*s - o.x;
+    ray.y = ry*c + rx*s - o.y;
     ray.normalize();
 }
 
diff --git a/engine/blend.cpp b/engine/blend.cpp
deleted file mode 100644
index 15a5260..0000000
--- a/engine/blend.cpp
+++ /dev/null
@@ -1,717 +0,0 @@
-#include "engine.h"
-
-enum
-{
-    BM_BRANCH = 0,
-    BM_SOLID,
-    BM_IMAGE
-};
-
-struct BlendMapBranch;
-struct BlendMapSolid;
-struct BlendMapImage;
-
-struct BlendMapNode
-{
-    union
-    {
-        BlendMapBranch *branch;
-        BlendMapSolid *solid;
-        BlendMapImage *image;
-    };
-
-    void cleanup(int type);
-    void splitsolid(uchar &type, uchar val);
-};
-
-struct BlendMapBranch
-{
-    uchar type[4];
-    BlendMapNode children[4];
-
-    ~BlendMapBranch()
-    {
-        loopi(4) children[i].cleanup(type[i]);
-    }
-};
-
-struct BlendMapSolid
-{
-    uchar val;
-
-    BlendMapSolid(uchar val) : val(val) {}
-};
-
-#define BM_SCALE 1
-#define BM_IMAGE_SIZE 64
-
-struct BlendMapImage
-{
-    uchar data[BM_IMAGE_SIZE*BM_IMAGE_SIZE];
-};
-
-void BlendMapNode::cleanup(int type)
-{
-    switch(type)
-    {
-        case BM_BRANCH: delete branch; break;
-        case BM_IMAGE: delete image; break;
-    }
-}
-
-#define DEFBMSOLIDS(n) n, n+1, n+2, n+3, n+4, n+5, n+6, n+7, n+8, n+9, n+10, n+11, n+12, n+13, n+14, n+15
-
-static BlendMapSolid bmsolids[256] = 
-{
-    DEFBMSOLIDS(0x00), DEFBMSOLIDS(0x10), DEFBMSOLIDS(0x20), DEFBMSOLIDS(0x30),
-    DEFBMSOLIDS(0x40), DEFBMSOLIDS(0x50), DEFBMSOLIDS(0x60), DEFBMSOLIDS(0x70),
-    DEFBMSOLIDS(0x80), DEFBMSOLIDS(0x90), DEFBMSOLIDS(0xA0), DEFBMSOLIDS(0xB0),
-    DEFBMSOLIDS(0xC0), DEFBMSOLIDS(0xD0), DEFBMSOLIDS(0xE0), DEFBMSOLIDS(0xF0),
-};
-
-void BlendMapNode::splitsolid(uchar &type, uchar val)
-{
-    cleanup(type);
-    type = BM_BRANCH;
-    branch = new BlendMapBranch;
-    loopi(4)
-    {
-        branch->type[i] = BM_SOLID;
-        branch->children[i].solid = &bmsolids[val];
-    }
-}
-
-struct BlendMapRoot : BlendMapNode
-{
-    uchar type;
-
-    BlendMapRoot() : type(BM_SOLID) { solid = &bmsolids[0xFF]; }
-    BlendMapRoot(uchar type, const BlendMapNode &node) : BlendMapNode(node), type(type) {}
-
-    void cleanup() { BlendMapNode::cleanup(type); }
-};
-
-static BlendMapRoot blendmap, curbm;
-static int curbmscale;
-static ivec curbmo;
-
-bool setblendmaporigin(const ivec &o, int size)
-{
-    if(blendmap.type!=BM_BRANCH)
-    {
-        curbm = blendmap;
-        curbmscale = worldscale-BM_SCALE;
-        curbmo = ivec(0, 0, 0);
-        return curbm.solid!=&bmsolids[0xFF];
-    }
-
-    BlendMapBranch *bm = blendmap.branch;
-    int bmscale = worldscale-BM_SCALE, bmsize = 1<<bmscale,
-        x = o.x>>BM_SCALE, y = o.y>>BM_SCALE,
-        x1 = max(x-1, 0), y1 = max(y-1, 0),
-        x2 = min(((o.x + size + (1<<BM_SCALE)-1)>>BM_SCALE) + 1, bmsize),
-        y2 = min(((o.y + size + (1<<BM_SCALE)-1)>>BM_SCALE) + 1, bmsize),
-        diff = (x1^x2)|(y1^y2);
-    if(diff < bmsize) while(!(diff&(1<<(bmscale-1))))
-    {
-        bmscale--;
-        int n = (((y1>>bmscale)&1)<<1) | ((x1>>bmscale)&1);
-        if(bm->type[n]!=BM_BRANCH)
-        {
-            curbm = BlendMapRoot(bm->type[n], bm->children[n]);
-            curbmscale = bmscale;
-            curbmo = ivec(x1&(~0U<<bmscale), y1&(~0U<<bmscale), 0);
-            return curbm.solid!=&bmsolids[0xFF];
-        }
-        bm = bm->children[n].branch;
-    }
-
-    curbm.type = BM_BRANCH;
-    curbm.branch = bm;
-    curbmscale = bmscale;
-    curbmo = ivec(x1&(~0U<<bmscale), y1&(~0U<<bmscale), 0);
-    return true;
-}
-
-bool hasblendmap()
-{
-    return curbm.solid!=&bmsolids[0xFF];
-}
-
-static uchar lookupblendmap(int x, int y, BlendMapBranch *bm, int bmscale)
-{
-    for(;;)
-    {
-        bmscale--;
-        int n = (((y>>bmscale)&1)<<1) | ((x>>bmscale)&1);
-        switch(bm->type[n])
-        {
-            case BM_SOLID: return bm->children[n].solid->val;
-            case BM_IMAGE: return bm->children[n].image->data[(y&((1<<bmscale)-1))*BM_IMAGE_SIZE + (x&((1<<bmscale)-1))];
-        }
-        bm = bm->children[n].branch;
-    }
-}
-    
-uchar lookupblendmap(const vec &pos)
-{
-    if(curbm.type==BM_SOLID) return curbm.solid->val;
-    
-    uchar vals[4], *val = vals;
-    float bx = pos.x/(1<<BM_SCALE) - 0.5f, by = pos.y/(1<<BM_SCALE) - 0.5f;
-    int ix = (int)floor(bx), iy = (int)floor(by),
-        rx = ix-curbmo.x, ry = iy-curbmo.y;
-    loop(vy, 2) loop(vx, 2)
-    {
-        int cx = clamp(rx+vx, 0, (1<<curbmscale)-1), cy = clamp(ry+vy, 0, (1<<curbmscale)-1);
-        if(curbm.type==BM_IMAGE)
-            *val++ = curbm.image->data[cy*BM_IMAGE_SIZE + cx];
-        else *val++ = lookupblendmap(cx, cy, curbm.branch, curbmscale);
-    }
-    float fx = bx - ix, fy = by - iy;
-    return uchar((1-fy)*((1-fx)*vals[0] + fx*vals[1]) +
-                 fy*((1-fx)*vals[2] + fx*vals[3]));
-}
-
-static void fillblendmap(uchar &type, BlendMapNode &node, int size, uchar val, int x1, int y1, int x2, int y2)
-{
-    if(max(x1, y1) <= 0 && min(x2, y2) >= size)
-    {
-        node.cleanup(type);
-        type = BM_SOLID;
-        node.solid = &bmsolids[val];
-        return;
-    }
-
-    if(type==BM_BRANCH)
-    {
-        size /= 2;
-        if(y1 < size)
-        {
-            if(x1 < size) fillblendmap(node.branch->type[0], node.branch->children[0], size, val,
-                                        x1, y1, min(x2, size), min(y2, size));
-            if(x2 > size) fillblendmap(node.branch->type[1], node.branch->children[1], size, val, 
-                                        max(x1-size, 0), y1, x2-size, min(y2, size));
-        }
-        if(y2 > size)
-        {
-            if(x1 < size) fillblendmap(node.branch->type[2], node.branch->children[2], size, val,
-                                        x1, y1-size, min(x2, size), y2-size);
-            if(x2 > size) fillblendmap(node.branch->type[3], node.branch->children[3], size, val,
-                                        max(x1-size, 0), y1-size, x2-size, y2-size);
-        }
-        loopi(4) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val!=val) return;
-        node.cleanup(type);
-        type = BM_SOLID;
-        node.solid = &bmsolids[val];
-        return;
-    }             
-    else if(type==BM_SOLID)
-    {
-        uchar oldval = node.solid->val;
-        if(oldval==val) return;
-
-        if(size > BM_IMAGE_SIZE)
-        {
-            node.splitsolid(type, oldval);
-            fillblendmap(type, node, size, val, x1, y1, x2, y2);
-            return;
-        }
- 
-        type = BM_IMAGE;
-        node.image = new BlendMapImage;
-        memset(node.image->data, oldval, sizeof(node.image->data));
-    }
-    
-    uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
-    loopi(y2-y1)
-    {
-        memset(dst, val, x2-x1);
-        dst += BM_IMAGE_SIZE;
-    }
-}
-
-void fillblendmap(int x, int y, int w, int h, uchar val)
-{
-    int bmsize = worldsize>>BM_SCALE,
-        x1 = clamp(x, 0, bmsize),
-        y1 = clamp(y, 0, bmsize),
-        x2 = clamp(x+w, 0, bmsize),
-        y2 = clamp(y+h, 0, bmsize);
-    if(max(x1, y1) >= bmsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
-    fillblendmap(blendmap.type, blendmap, bmsize, val, x1, y1, x2, y2);
-}
-
-static void optimizeblendmap(uchar &type, BlendMapNode &node)
-{
-    switch(type)
-    {
-        case BM_IMAGE:
-        {
-            uint val = node.image->data[0];
-            val |= val<<8;
-            val |= val<<16;
-            for(uint *data = (uint *)node.image->data, *end = &data[sizeof(node.image->data)/sizeof(uint)]; data < end; data++) 
-                if(*data != val) return;
-            node.cleanup(type);
-            type = BM_SOLID;
-            node.solid = &bmsolids[val&0xFF];
-            break;
-        }
-        case BM_BRANCH:
-        {
-            loopi(4) optimizeblendmap(node.branch->type[i], node.branch->children[i]);
-            if(node.branch->type[3]!=BM_SOLID) return;
-            uint val = node.branch->children[3].solid->val;
-            loopi(3) if(node.branch->type[i]!=BM_SOLID || node.branch->children[i].solid->val != val) return;
-            node.cleanup(type);
-            type = BM_SOLID;
-            node.solid = &bmsolids[val];
-            break;
-        }
-    }
-}
-
-void optimizeblendmap()
-{
-    optimizeblendmap(blendmap.type, blendmap);
-}
-
-VARF(blendpaintmode, 0, 0, 5,
-{
-    if(!blendpaintmode) stoppaintblendmap();
-});
-
-static void blitblendmap(uchar &type, BlendMapNode &node, int bmx, int bmy, int bmsize, uchar *src, int sx, int sy, int sw, int sh)
-{
-    if(type==BM_BRANCH)
-    {
-        bmsize /= 2;
-        if(sy < bmy + bmsize)
-        {
-            if(sx < bmx + bmsize) blitblendmap(node.branch->type[0], node.branch->children[0], bmx, bmy, bmsize, src, sx, sy, sw, sh);
-            if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[1], node.branch->children[1], bmx+bmsize, bmy, bmsize, src, sx, sy, sw, sh);
-        }
-        if(sy + sh > bmy + bmsize)
-        {
-            if(sx < bmx + bmsize) blitblendmap(node.branch->type[2], node.branch->children[2], bmx, bmy+bmsize, bmsize, src, sx, sy, sw, sh);
-            if(sx + sw > bmx + bmsize) blitblendmap(node.branch->type[3], node.branch->children[3], bmx+bmsize, bmy+bmsize, bmsize, src, sx, sy, sw, sh);
-        }
-        return;
-    }
-    if(type==BM_SOLID)
-    {
-        uchar val = node.solid->val;
-        if(bmsize > BM_IMAGE_SIZE)
-        {
-            node.splitsolid(type, val);
-            blitblendmap(type, node, bmx, bmy, bmsize, src, sx, sy, sw, sh);
-            return;
-        }
-
-        type = BM_IMAGE;
-        node.image = new BlendMapImage;
-        memset(node.image->data, val, sizeof(node.image->data));
-    }
-
-    int x1 = clamp(sx - bmx, 0, bmsize), y1 = clamp(sy - bmy, 0, bmsize),
-        x2 = clamp(sx+sw - bmx, 0, bmsize), y2 = clamp(sy+sh - bmy, 0, bmsize);
-    uchar *dst = &node.image->data[y1*BM_IMAGE_SIZE + x1];
-    src += max(bmy - sy, 0)*sw + max(bmx - sx, 0);
-    loopi(y2-y1)
-    {
-        switch(blendpaintmode)
-        {
-            case 1:
-                memcpy(dst, src, x2 - x1);
-                break;
-
-            case 2:
-                loopi(x2 - x1) dst[i] = min(dst[i], src[i]); 
-                break;
-
-            case 3:
-                loopi(x2 - x1) dst[i] = max(dst[i], src[i]);
-                break;
-
-            case 4:
-                loopi(x2 - x1) dst[i] = min(dst[i], uchar(0xFF - src[i]));
-                break;
-
-            case 5:
-                loopi(x2 - x1) dst[i] = max(dst[i], uchar(0xFF - src[i]));
-                break;
-        }
-        dst += BM_IMAGE_SIZE;
-        src += sw;
-    } 
-}
-
-void blitblendmap(uchar *src, int sx, int sy, int sw, int sh)
-{
-    int bmsize = worldsize>>BM_SCALE;
-    if(max(sx, sy) >= bmsize || min(sx+sw, sy+sh) <= 0 || min(sw, sh) <= 0) return;
-    blitblendmap(blendmap.type, blendmap, 0, 0, bmsize, src, sx, sy, sw, sh);
-}
-        
-void resetblendmap()
-{
-    blendmap.cleanup();
-    blendmap.type = BM_SOLID;
-    blendmap.solid = &bmsolids[0xFF];
-}
-
-void enlargeblendmap()
-{
-    if(blendmap.type == BM_SOLID) return;
-    BlendMapBranch *branch = new BlendMapBranch;
-    branch->type[0] = blendmap.type;
-    branch->children[0] = blendmap;
-    loopi(3)
-    {
-        branch->type[i+1] = BM_SOLID;
-        branch->children[i+1].solid = &bmsolids[0xFF];
-    }
-    blendmap.type = BM_BRANCH;
-    blendmap.branch = branch;
-}
-
-struct BlendBrush
-{
-    char *name;
-    int w, h;
-    uchar *data;
-    GLuint tex;
-   
-    BlendBrush(const char *name, int w, int h) :
-      name(newstring(name)), w(w), h(h), data(new uchar[w*h]), tex(0)
-    {}
-
-    ~BlendBrush()
-    {
-        cleanup();
-        delete[] name;
-        if(data) delete[] data;
-    }
-
-    void cleanup()
-    {
-        if(tex) { glDeleteTextures(1, &tex); tex = 0; }
-    }
-
-    void gentex()
-    {
-        if(!tex) glGenTextures(1, &tex);
-        uchar *buf = new uchar[2*(w+2)*(h+2)];
-        memset(buf, 0, 2*(w+2));
-        uchar *dst = &buf[2*(w+2)], *src = data;
-        loopi(h)
-        {
-            *dst++ = 0; 
-            *dst++ = 0;
-            loopj(w)
-            {
-                *dst++ = 255 - *src;
-                *dst++ = 255 - *src++;
-            }
-            *dst++ = 0;
-            *dst++ = 0;
-        }
-        memset(dst, 0, 2*(w+2));
-        createtexture(tex, w+2, h+2, buf, 3, 1, GL_LUMINANCE_ALPHA);
-        delete[] buf;
-    }
-    
-    void reorient(bool flipx, bool flipy, bool swapxy)
-    {
-        uchar *rdata = new uchar[w*h];
-        int stridex = 1, stridey = 1;
-        if(swapxy) stridex *= h; else stridey *= w;
-        uchar *src = data, *dst = rdata;
-        if(flipx) { dst += (w-1)*stridex; stridex = -stridex; }
-        if(flipy) { dst += (h-1)*stridey; stridey = -stridey; }
-        loopi(h)
-        {
-            uchar *curdst = dst;
-            loopj(w) 
-            {
-                *curdst = *src++;
-                curdst += stridex;
-            }
-            dst += stridey;
-        }
-        if(swapxy) swap(w, h);
-        delete[] data;
-        data = rdata;
-        if(tex) gentex();
-    } 
-};
-
-static vector<BlendBrush *> brushes;
-static int curbrush = -1;
-
-void cleanupblendmap()
-{
-    loopv(brushes) brushes[i]->cleanup();
-}
-
-void clearblendbrushes()
-{
-    while(brushes.length()) delete brushes.pop();
-    curbrush = -1;
-}
-
-void delblendbrush(const char *name)
-{
-    loopv(brushes) if(!strcmp(brushes[i]->name, name)) 
-    {
-        delete brushes[i];
-        brushes.remove(i--);
-    }
-    curbrush = brushes.empty() ? -1 : clamp(curbrush, 0, brushes.length()-1);
-}
-
-void addblendbrush(const char *name, const char *imgname)
-{
-    delblendbrush(name);
-
-    ImageData s;
-    if(!loadimage(imgname, s)) { conoutf(CON_ERROR, "could not load blend brush image %s", imgname); return; }
-    if(max(s.w, s.h) > (1<<12))
-    {
-        conoutf(CON_ERROR, "blend brush image size exceeded %dx%d pixels: %s", 1<<12, 1<<12, imgname);
-        return;
-    }
-    
-    BlendBrush *brush = new BlendBrush(name, s.w, s.h);
-
-    uchar *dst = brush->data, *srcrow = s.data;
-    loopi(s.h)
-    {
-        for(uchar *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; src += s.bpp)
-            *dst++ = src[0];
-        srcrow += s.pitch;
-    }
-
-    brushes.add(brush);
-    if(curbrush < 0) curbrush = 0;
-    else if(curbrush >= brushes.length()) curbrush = brushes.length()-1;
-
-}
-
-void nextblendbrush(int *dir)
-{
-    curbrush += *dir < 0 ? -1 : 1;
-    if(brushes.empty()) curbrush = -1;
-    else if(!brushes.inrange(curbrush)) curbrush = *dir < 0 ? brushes.length()-1 : 0;
-}
-
-void setblendbrush(const char *name)
-{
-    loopv(brushes) if(!strcmp(brushes[i]->name, name)) { curbrush = i; break; }
-}
-
-void getblendbrushname(int *n)
-{
-    result(brushes.inrange(*n) ? brushes[*n]->name : "");
-}
-
-void curblendbrush()
-{
-    intret(curbrush);
-}
-
-COMMAND(clearblendbrushes, "");
-COMMAND(delblendbrush, "s");
-COMMAND(addblendbrush, "ss");
-COMMAND(nextblendbrush, "i");
-COMMAND(setblendbrush, "s");
-COMMAND(getblendbrushname, "i");
-COMMAND(curblendbrush, "");
-
-bool canpaintblendmap(bool brush = true, bool sel = false, bool msg = true)
-{
-    if(noedit(!sel, msg)) return false;
-    if(!blendpaintmode)
-    {
-        if(msg) conoutf(CON_ERROR, "operation only allowed in blend paint mode");
-        return false;
-    }
-    if(brush && !brushes.inrange(curbrush))
-    {
-        if(msg) conoutf(CON_ERROR, "no blend brush selected");
-        return false;
-    }
-    return true;
-}
-
-void rotateblendbrush(int *val)
-{
-    if(!canpaintblendmap()) return;
-    
-    int numrots = clamp(*val, 1, 5);
-    BlendBrush *brush = brushes[curbrush];
-    brush->reorient(numrots>=2 && numrots<=4, numrots<=2 || numrots==5, (numrots&5)==1);
-}
-
-COMMAND(rotateblendbrush, "i");
-
-void paintblendmap(bool msg)
-{
-    if(!canpaintblendmap(true, false, msg)) return;
-
-    BlendBrush *brush = brushes[curbrush];
-    int x = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->w),
-        y = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*brush->h);
-    blitblendmap(brush->data, x, y, brush->w, brush->h);
-    previewblends(ivec((x-1)<<BM_SCALE, (y-1)<<BM_SCALE, 0),
-                  ivec((brush->w+2)<<BM_SCALE, (brush->h+2)<<BM_SCALE, worldsize));
-}
-
-VAR(paintblendmapdelay, 1, 500, 3000);
-VAR(paintblendmapinterval, 1, 30, 3000);
-
-int paintingblendmap = 0, lastpaintblendmap = 0;
-
-void stoppaintblendmap()
-{
-    paintingblendmap = 0;
-    lastpaintblendmap = 0;
-}
-
-void trypaintblendmap()
-{
-    if(!paintingblendmap || totalmillis - paintingblendmap < paintblendmapdelay) return;
-    if(lastpaintblendmap)
-    {
-        int diff = totalmillis - lastpaintblendmap;
-        if(diff < paintblendmapinterval) return;
-        lastpaintblendmap = (diff - diff%paintblendmapinterval) + lastpaintblendmap;
-    }
-    else lastpaintblendmap = totalmillis;
-    paintblendmap(false);
-}
-
-ICOMMAND(paintblendmap, "D", (int *isdown),
-{
-    if(*isdown)
-    {
-        if(!paintingblendmap) { paintblendmap(true); paintingblendmap = totalmillis; }
-    }
-    else stoppaintblendmap();
-});
-    
-void clearblendmapsel()
-{
-    if(noedit(false)) return;
-    extern selinfo sel;
-    int x1 = sel.o.x>>BM_SCALE, y1 = sel.o.y>>BM_SCALE,
-        x2 = (sel.o.x+sel.s.x*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE,
-        y2 = (sel.o.y+sel.s.y*sel.grid+(1<<BM_SCALE)-1)>>BM_SCALE;
-    fillblendmap(x1, y1, x2-x1, y2-y1, 0xFF);
-    previewblends(ivec(x1<<BM_SCALE, y1<<BM_SCALE, 0),
-                  ivec((x2-x1)<<BM_SCALE, (y2-y1)<<BM_SCALE, worldsize));
-}
-
-COMMAND(clearblendmapsel, "");
-
-void showblendmap()
-{
-    if(noedit(true)) return;
-    previewblends(ivec(0, 0, 0), ivec(worldsize, worldsize, worldsize));
-}
-
-COMMAND(showblendmap, "");
-COMMAND(optimizeblendmap, "");
-ICOMMAND(clearblendmap, "", (),
-{
-    if(noedit(true)) return;
-    resetblendmap();
-    showblendmap();
-});
-
-void renderblendbrush()
-{
-    if(!blendpaintmode || !brushes.inrange(curbrush)) return;
-
-    BlendBrush *brush = brushes[curbrush];
-    int x1 = (int)floor(clamp(worldpos.x, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*(brush->w+2)) << BM_SCALE,
-        y1 = (int)floor(clamp(worldpos.y, 0.0f, float(worldsize))/(1<<BM_SCALE) - 0.5f*(brush->h+2)) << BM_SCALE,
-        x2 = x1 + ((brush->w+2) << BM_SCALE),
-        y2 = y1 + ((brush->h+2) << BM_SCALE);
-
-    if(max(x1, y1) >= worldsize || min(x2, y2) <= 0 || x1>=x2 || y1>=y2) return;
-
-    if(!brush->tex) brush->gentex();
-    renderblendbrush(brush->tex, x1, y1, x2 - x1, y2 - y1);
-}
-
-bool loadblendmap(stream *f, uchar &type, BlendMapNode &node)
-{
-    type = f->getchar();
-    switch(type)
-    {
-        case BM_SOLID:
-        {
-            int val = f->getchar();
-            if(val<0 || val>0xFF) return false;
-            node.solid = &bmsolids[val];
-            break;
-        }
-
-        case BM_IMAGE:
-            node.image = new BlendMapImage;
-            if(f->read(node.image->data, sizeof(node.image->data)) != sizeof(node.image->data))
-                return false;
-            break;
-
-        case BM_BRANCH:
-            node.branch = new BlendMapBranch;
-            loopi(4) { node.branch->type[i] = BM_SOLID; node.branch->children[i].solid = &bmsolids[0xFF]; }
-            loopi(4) if(!loadblendmap(f, node.branch->type[i], node.branch->children[i]))
-                return false;
-            break;
-
-        default:
-            type = BM_SOLID;
-            node.solid = &bmsolids[0xFF];
-            return false;
-    }
-    return true;
-}
-
-bool loadblendmap(stream *f, int info)
-{
-    resetblendmap();
-    return loadblendmap(f, blendmap.type, blendmap);
-}
-
-void saveblendmap(stream *f, uchar type, BlendMapNode &node)
-{
-    f->putchar(type);
-    switch(type)
-    {
-        case BM_SOLID:
-            f->putchar(node.solid->val);
-            break;
-        
-        case BM_IMAGE:
-            f->write(node.image->data, sizeof(node.image->data));
-            break;
-
-        case BM_BRANCH:
-            loopi(4) saveblendmap(f, node.branch->type[i], node.branch->children[i]);
-            break;
-    }
-}
-
-void saveblendmap(stream *f)
-{
-    saveblendmap(f, blendmap.type, blendmap);
-}
-
-uchar shouldsaveblendmap()
-{
-    return blendmap.solid!=&bmsolids[0xFF] ? 1 : 0;
-}
-    
diff --git a/engine/blob.cpp b/engine/blob.cpp
deleted file mode 100644
index 73e1723..0000000
--- a/engine/blob.cpp
+++ /dev/null
@@ -1,596 +0,0 @@
-#include "engine.h"
-
-VARNP(blobs, showblobs, 0, 1, 1);
-VARFP(blobintensity, 0, 60, 100, resetblobs());
-VARFP(blobheight, 1, 32, 128, resetblobs());
-VARFP(blobfadelow, 1, 8, 32, resetblobs());
-VARFP(blobfadehigh, 1, 8, 32, resetblobs());
-VARFP(blobmargin, 0, 1, 16, resetblobs());
-
-VAR(dbgblob, 0, 0, 1);
-
-struct blobinfo
-{
-    vec o;
-    float radius;
-    int millis;
-    uint startindex, endindex;
-    ushort startvert, endvert;
-};
-
-struct blobvert
-{
-    vec pos;
-    float u, v;
-    bvec color;
-    uchar alpha;
-};
-
-struct blobrenderer
-{
-    const char *texname;
-    Texture *tex;
-    blobinfo **cache;
-    int cachesize;
-    blobinfo *blobs;
-    int maxblobs, startblob, endblob;
-    blobvert *verts;
-    int maxverts, startvert, endvert, availverts;
-    ushort *indexes;
-    int maxindexes, startindex, endindex, availindexes;
-    
-    blobinfo *lastblob, *flushblob;
-
-    vec blobmin, blobmax;
-    ivec bborigin, bbsize;
-    float blobalphalow, blobalphahigh;
-    uchar blobalpha;
-
-    blobrenderer(const char *texname)
-      : texname(texname), tex(NULL),
-        cache(NULL), cachesize(0),
-        blobs(NULL), maxblobs(0), startblob(0), endblob(0),
-        verts(NULL), maxverts(0), startvert(0), endvert(0), availverts(0),
-        indexes(NULL), maxindexes(0), startindex(0), endindex(0), availindexes(0),
-        lastblob(NULL)
-    {}
-
-    void init(int tris)
-    {
-        if(cache)
-        {
-            DELETEA(cache);
-            cachesize = 0;
-        }
-        if(blobs)
-        {
-            DELETEA(blobs);
-            maxblobs = startblob = endblob = 0;
-        }
-        if(verts)
-        {
-            DELETEA(verts);
-            maxverts = startvert = endvert = availverts = 0;
-        }
-        if(indexes)
-        {
-            DELETEA(indexes);
-            maxindexes = startindex = endindex = availindexes = 0;
-        }
-        if(!tris) return;
-        tex = textureload(texname, 3);
-        cachesize = tris/2;
-        cache = new blobinfo *[cachesize];
-        memset(cache, 0, cachesize * sizeof(blobinfo *));
-        maxblobs = tris/2;
-        blobs = new blobinfo[maxblobs];
-        memset(blobs, 0, maxblobs * sizeof(blobinfo));
-        maxindexes = tris*3 + 3;
-        availindexes = maxindexes - 3;
-        indexes = new ushort[maxindexes];
-        maxverts = min(tris*3/2 + 1, (1<<16)-1);
-        availverts = maxverts - 1;
-        verts = new blobvert[maxverts];
-    }
-
-    bool freeblob()
-    {
-        blobinfo &b = blobs[startblob];
-        if(&b == lastblob) return false;
-
-        startblob++;
-        if(startblob >= maxblobs) startblob = 0;
-
-        startvert = b.endvert;
-        if(startvert>=maxverts) startvert = 0;
-        availverts += b.endvert - b.startvert;
-
-        startindex = b.endindex;
-        if(startindex>=maxindexes) startindex = 0;
-        availindexes += b.endindex - b.startindex;
-
-        b.millis = 0;
-
-        return true;
-    }
-
-    blobinfo &newblob(const vec &o, float radius)
-    {
-        blobinfo &b = blobs[endblob];
-        int next = endblob + 1;
-        if(next>=maxblobs) next = 0;
-        if(next==startblob) 
-        {
-            lastblob = &b;
-            freeblob();
-        }
-        endblob = next;
-        b.o = o;
-        b.radius = radius;
-        b.millis = totalmillis;
-        b.startindex = b.endindex = endindex;
-        b.startvert = b.endvert = endvert;
-        lastblob = &b;
-        return b;
-    }
-
-    void clearblobs()
-    {
-        startblob = endblob = 0;
-        startvert = endvert = 0;
-        availverts = maxverts - 1;
-        startindex = endindex = 0;
-        availindexes = maxindexes - 3;
-    }
-    
-   
-    template<int C>
-    static int split(const vec *in, int numin, float val, vec *out)
-    {
-        int numout = 0;
-        const vec *n = in;
-        float c = (*n)[C];
-        loopi(numin-1)
-        {
-            const vec &p = *n++;
-            float pc = c;
-            c = (*n)[C];
-            out[numout++] = p;
-            if(pc < val ? c > val : pc > val && c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
-        }
-        float ic = (*in)[C];
-        out[numout++] = *n;
-        if(c < val ? ic > val : c > val && ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
-        return numout;
-    }
-
-    template<int C>
-    static int clipabove(const vec *in, int numin, float val, vec *out)
-    {
-        int numout = 0;
-        const vec *n = in;
-        float c = (*n)[C];
-        loopi(numin-1)
-        {
-            const vec &p = *n++;
-            float pc = c;
-            c = (*n)[C];
-            if(pc >= val) 
-            {
-                out[numout++] = p;
-                if(pc > val && c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
-            }
-            else if(c > val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
-        }
-        float ic = (*in)[C];
-        if(c >= val)
-        {   
-            out[numout++] = *n;
-            if(c > val && ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
-        }
-        else if(ic > val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
-        return numout;
-    }
-
-    template<int C>
-    static int clipbelow(const vec *in, int numin, float val, vec *out)
-    {
-        int numout = 0;
-        const vec *n = in;
-        float c = (*n)[C];
-        loopi(numin-1)
-        {
-            const vec &p = *n++;
-            float pc = c;
-            c = (*n)[C];
-            if(pc <= val)   
-            {
-                out[numout++] = p;
-                if(pc < val && c > val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
-            }
-            else if(c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
-        }
-        float ic = (*in)[C];
-        if(c <= val)
-        { 
-            out[numout++] = *n;
-            if(c < val && ic > val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
-        }
-        else if(ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
-        return numout;
-    }
-
-    void dupblob()
-    {
-        if(lastblob->startvert >= lastblob->endvert) 
-        {
-            lastblob->startindex = lastblob->endindex = endindex;
-            lastblob->startvert = lastblob->endvert = endvert;
-            return; 
-        }
-        blobinfo &b = newblob(lastblob->o, lastblob->radius);
-        b.millis = -1;
-    }
-
-    inline int addvert(const vec &pos)
-    {
-        blobvert &v = verts[endvert];
-        v.pos = pos;
-        v.u = (pos.x - blobmin.x) / (blobmax.x - blobmin.x);
-        v.v = (pos.y - blobmin.y) / (blobmax.y - blobmin.y);
-        v.color = bvec(255, 255, 255);
-        if(pos.z < blobmin.z + blobfadelow) v.alpha = uchar(blobalphalow * (pos.z - blobmin.z));
-        else if(pos.z > blobmax.z - blobfadehigh) v.alpha = uchar(blobalphahigh * (blobmax.z - pos.z));
-        else v.alpha = blobalpha;
-        return endvert++;
-    }
-
-    void addtris(const vec *v, int numv)
-    {
-        if(endvert != int(lastblob->endvert) || endindex != int(lastblob->endindex)) dupblob();
-        for(const vec *cur = &v[2], *end = &v[numv];;)
-        {
-            int limit = maxverts - endvert - 2;
-            if(limit <= 0)
-            {
-                while(availverts < limit+2) if(!freeblob()) return;
-                availverts -= limit+2;
-                lastblob->endvert = maxverts;
-                endvert = 0;
-                dupblob();
-                limit = maxverts - 2;
-            }
-            limit = min(int(end - cur), min(limit, (maxindexes - endindex)/3));
-            while(availverts < limit+2) if(!freeblob()) return;
-            while(availindexes < limit*3) if(!freeblob()) return;
-
-            int i1 = addvert(v[0]), i2 = addvert(cur[-1]);
-            loopk(limit)
-            {
-                indexes[endindex++] = i1;
-                indexes[endindex++] = i2;
-                i2 = addvert(*cur++);
-                indexes[endindex++] = i2; 
-            }
-
-            availverts -= endvert - lastblob->endvert;
-            availindexes -= endindex - lastblob->endindex;
-            lastblob->endvert = endvert;
-            lastblob->endindex = endindex;
-            if(endvert >= maxverts) endvert = 0;
-            if(endindex >= maxindexes) endindex = 0;
-
-            if(cur >= end) break;
-            dupblob();
-        }
-    }
-
-    void genflattris(cube &cu, int orient, vec *v, uint overlap)
-    {
-        int dim = dimension(orient);
-        float c = v[fv[orient][0]][dim];
-        if(c < blobmin[dim] || c > blobmax[dim]) return;
-        #define CLIPSIDE(flag, clip, val, check) \
-            if(overlap&(flag)) \
-            { \
-                vec *in = v; \
-                v = in==v1 ? v2 : v1; \
-                numv = clip(in, numv, val, v); \
-                check \
-            }
-        static vec v1[16], v2[16];
-        loopk(4) v1[k] = v[fv[orient][k]];
-        v = v1;
-        int numv = 4;
-        overlap &= ~(3<<(2*dim));
-        CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) return; });
-        CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) return; });
-        CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) return; });
-        CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) return; });
-        CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) return; });
-        CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) return; });
-        if(dim!=2)
-        {
-            CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
-            CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
-        }
-
-        addtris(v, numv);
-    }
-
-    void genslopedtris(cube &cu, int orient, vec *v, uint overlap)
-    {
-        int convexity = faceconvexity(cu, orient), order = convexity < 0 ? 1 : 0;
-        const vec &p0 = v[fv[orient][0 + order]],
-                  &p1 = v[fv[orient][1 + order]],
-                  &p2 = v[fv[orient][2 + order]],
-                  &p3 = v[fv[orient][(3 + order)&3]];
-        if(p0 == p2) return;
-        static vec v1[16], v2[16];
-        if(p0 != p1 && p1 != p2)
-        {
-            if((p1.x - p0.x)*(p2.y - p0.y) - (p1.y - p0.y)*(p2.x - p0.x) < 0) goto nexttri;
-            v1[0] = p0; v1[1] = p1; v1[2] = p2;
-            int numv = 3;
-            if(!convexity && p0 != p3 && p2 != p3) { v1[3] = p3; numv = 4; } 
-            v = v1;
-            CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) goto nexttri; });
-            CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
-            CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
-
-            addtris(v, numv);
-        }
-        else convexity = 1;
-    nexttri:
-        if(convexity && p0 != p3 && p2 != p3)
-        {
-            if((p2.x - p0.x)*(p3.y - p0.y) - (p2.y - p0.y)*(p3.x - p0.x) < 0) return;
-            v1[0] = p0; v1[1] = p2; v1[2] = p3;
-            int numv = 3;
-            v = v1;
-            CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) return; });
-            CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) return; });
-            CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) return; });
-            CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) return; });
-            CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) return; });
-            CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) return; });
-            CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
-            CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
-
-            addtris(v, numv);
-        }
-    } 
-
-    int checkoverlap(const ivec &o, int size)
-    {
-        int overlap = 0;
-        if(o.x < blobmin.x) overlap |= 1<<0;
-        if(o.x + size > blobmax.x) overlap |= 1<<1;
-        if(o.y < blobmin.y) overlap |= 1<<2;
-        if(o.y + size > blobmax.y) overlap |= 1<<3;
-        if(o.z < blobmin.z) overlap |= 1<<4;
-        if(o.z + size > blobmax.z) overlap |= 1<<5;
-        if(o.z < blobmin.z + blobfadelow && o.z + size > blobmin.z + blobfadelow) overlap |= 1<<6;
-        if(o.z < blobmax.z - blobfadehigh && o.z + size > blobmax.z - blobfadehigh) overlap |= 1<<7;
-        return overlap;
-    }
-
-    void gentris(cube *cu, const ivec &o, int size, uchar *vismasks = NULL, uchar avoid = 1<<O_BOTTOM)
-    {
-        loopoctabox(o, size, bborigin, bbsize)
-        {
-            ivec co(i, o.x, o.y, o.z, size);
-            if(cu[i].children)
-            {
-                uchar visclip = cu[i].vismask & cu[i].clipmask & ~avoid;
-                if(visclip)
-                {
-                    uint overlap = checkoverlap(co, size);
-                    uchar vertused = fvmasks[visclip];
-                    vec v[8];
-                    loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, true);
-                    loopj(6) if(visclip&(1<<j)) genflattris(cu[i], j, v, overlap);
-                }
-                if(cu[i].vismask & ~avoid) gentris(cu[i].children, co, size>>1, cu[i].vismasks, avoid | visclip);
-            }
-            else if(vismasks)
-            {
-                uchar vismask = vismasks[i] & ~avoid;
-                if(!vismask) continue;
-                uint overlap = checkoverlap(co, size);
-                uchar vertused = fvmasks[vismask];
-                bool solid = cu[i].ext && isclipped(cu[i].ext->material&MATF_VOLUME);
-                vec v[8];
-                loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, solid);
-                loopj(6) if(vismask&(1<<j))
-                {
-                    if(solid || (flataxisface(cu[i], j) && faceedges(cu[i], j)==F_SOLID)) genflattris(cu[i], j, v, overlap);
-                    else genslopedtris(cu[i], j, v, overlap);
-                }
-            }
-            else
-            {
-                bool solid = cu[i].ext && isclipped(cu[i].ext->material&MATF_VOLUME);
-                uchar vismask = 0;
-                loopj(6) if(!(avoid&(1<<j)) && (solid ? visiblematerial(cu[i], j, co.x, co.y, co.z, size)==MATSURF_VISIBLE : cu[i].texture[j]!=DEFAULT_SKY && visibleface(cu[i], j, co.x, co.y, co.z, size))) vismask |= 1<<j;
-                if(!vismask) continue;
-                uint overlap = checkoverlap(co, size);
-                uchar vertused = fvmasks[vismask];
-                vec v[8];
-                loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, solid);
-                loopj(6) if(vismask&(1<<j)) 
-                {
-                    if(solid || (flataxisface(cu[i], j) && faceedges(cu[i], j)==F_SOLID)) genflattris(cu[i], j, v, overlap);
-                    else genslopedtris(cu[i], j, v, overlap);
-                }
-            }
-        }
-    }
-
-    blobinfo *addblob(const vec &o, float radius, float fade)
-    {
-        lastblob = &blobs[endblob];
-        blobinfo &b = newblob(o, radius);
-        blobmin = blobmax = o;
-        blobmin.x -= radius;
-        blobmin.y -= radius;
-        blobmin.z -= blobheight + blobfadelow;
-        blobmax.x += radius;
-        blobmax.y += radius;
-        blobmax.z += blobfadehigh;
-        (bborigin = blobmin).sub(2);
-        (bbsize = blobmax).sub(blobmin).add(4);
-        float scale =  fade*blobintensity*255/100.0f;
-        blobalphalow = scale / blobfadelow;
-        blobalphahigh = scale / blobfadehigh;
-        blobalpha = uchar(scale);
-        gentris(worldroot, ivec(0, 0, 0), worldsize>>1);
-        return b.millis >= 0 ? &b : NULL;
-    } 
-
-    static void setuprenderstate()
-    {
-        if(renderpath!=R_FIXEDFUNCTION && fogging) setfogplane(1, reflectz);
-
-        foggedshader->set();
-
-        enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
-
-        glDepthMask(GL_FALSE);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        if(!dbgblob) glEnable(GL_BLEND);
-
-        glEnableClientState(GL_VERTEX_ARRAY);
-        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-        glEnableClientState(GL_COLOR_ARRAY);
-    }
-
-    static void cleanuprenderstate()
-    {
-        glDisableClientState(GL_VERTEX_ARRAY);
-        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-        glDisableClientState(GL_COLOR_ARRAY);
-
-        glDepthMask(GL_TRUE);
-        glDisable(GL_BLEND);
-
-        disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
-    }
-
-    static int lastreset;
-
-    static void reset()
-    {
-        lastreset = totalmillis;
-    }
-
-    static blobrenderer *lastrender;
-
-    void fadeblob(blobinfo *b, float fade)
-    {
-        float minz = b->o.z - (blobheight + blobfadelow), maxz = b->o.z + blobfadehigh,
-              scale = fade*blobintensity*255/100.0f, scalelow = scale / blobfadelow, scalehigh = scale / blobfadehigh;
-        uchar alpha = uchar(scale); 
-        b->millis = totalmillis;
-        do
-        {
-            if(b->endvert - b->startvert >= 3) for(blobvert *v = &verts[b->startvert], *end = &verts[b->endvert]; v < end; v++)
-            {
-                float z = v->pos.z;
-                if(z < minz + blobfadelow) v->alpha = uchar(scalelow * (z - minz));
-                else if(z > maxz - blobfadehigh) v->alpha = uchar(scalehigh * (maxz - z));
-                else v->alpha = alpha;
-            }
-            int offset = b - &blobs[0] + 1;
-            if(offset >= maxblobs) offset = 0;
-            if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset];
-            else break;
-        } while(b->millis < 0);
-    }
-
-    void renderblob(const vec &o, float radius, float fade)
-    {
-        if(lastrender != this)
-        {
-            if(!lastrender) 
-            {
-                if(!blobs) initblobs();
-
-                setuprenderstate();
-            }
-            glVertexPointer(3, GL_FLOAT, sizeof(blobvert), &verts->pos);
-            glTexCoordPointer(2, GL_FLOAT, sizeof(blobvert), &verts->u);
-            glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(blobvert), &verts->color);
-            if(!lastrender || lastrender->tex != tex) glBindTexture(GL_TEXTURE_2D, tex->id);
-            lastrender = this;
-        }
-    
-        union { int i; float f; } ox, oy;
-        ox.f = o.x; oy.f = o.y;
-        uint hash = uint(ox.i^~oy.i^(INT_MAX-oy.i)^uint(radius));
-        hash %= cachesize;
-        blobinfo *b = cache[hash];
-        if(!b || b->millis <= lastreset || b->o!=o || b->radius!=radius)
-        {
-            b = addblob(o, radius, fade);
-            cache[hash] = b;
-            if(!b) return;
-        }
-        else if(fade < 1 && b->millis < totalmillis) fadeblob(b, fade); 
-        do
-        {
-            if(b->endvert - b->startvert >= 3)
-            {
-                if(hasDRE) glDrawRangeElements_(GL_TRIANGLES, b->startvert, b->endvert-1, b->endindex - b->startindex, GL_UNSIGNED_SHORT, &indexes[b->startindex]);
-                else glDrawElements(GL_TRIANGLES, b->endindex - b->startindex, GL_UNSIGNED_SHORT, &indexes[b->startindex]);
-                xtravertsva += b->endvert - b->startvert;
-            }
-            int offset = b - &blobs[0] + 1;
-            if(offset >= maxblobs) offset = 0; 
-            if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset];
-            else break; 
-        } while(b->millis < 0);
-    }
-};
-
-int blobrenderer::lastreset = 0;
-blobrenderer *blobrenderer::lastrender = NULL;
-
-VARFP(blobstattris, 128, 4096, 1<<16, initblobs(BLOB_STATIC));
-VARFP(blobdyntris, 128, 4096, 1<<16, initblobs(BLOB_DYNAMIC));
-
-static blobrenderer blobs[] = 
-{
-    blobrenderer("packages/particles/blob.png"),
-    blobrenderer("packages/particles/blob.png")
-};
-
-void initblobs(int type)
-{
-    if(type < 0 || (type==BLOB_STATIC && blobs[BLOB_STATIC].blobs)) blobs[BLOB_STATIC].init(showblobs ? blobstattris : 0);
-    if(type < 0 || (type==BLOB_DYNAMIC && blobs[BLOB_DYNAMIC].blobs)) blobs[BLOB_DYNAMIC].init(showblobs ? blobdyntris : 0);
-}
-
-void resetblobs()
-{
-    blobrenderer::lastreset = totalmillis;
-}
-
-void renderblob(int type, const vec &o, float radius, float fade)
-{
-    if(!showblobs) return;
-    if(refracting < 0 && o.z - blobheight - blobfadelow >= reflectz) return;
-    blobs[type].renderblob(o, radius + blobmargin, fade);
-}
-
-void flushblobs()
-{
-    if(blobrenderer::lastrender) blobrenderer::cleanuprenderstate();
-    blobrenderer::lastrender = NULL;
-}
-
diff --git a/engine/client.cpp b/engine/client.cpp
index c61d99b..0a1ce8d 100644
--- a/engine/client.cpp
+++ b/engine/client.cpp
@@ -1,5 +1,6 @@
 // client.cpp, mostly network related client game code
 
+#include "pch.h"
 #include "engine.h"
 
 ENetHost *clienthost = NULL;
@@ -8,9 +9,9 @@ int connmillis = 0, connattempts = 0, discmillis = 0;
 
 bool multiplayer(bool msg)
 {
-    bool val = curpeer || hasnonlocalclients(); 
-    if(val && msg) conoutf(CON_ERROR, "operation not available in multiplayer");
-    return val;
+    // check not correct on listen server?
+    if(curpeer && msg) conoutf(CON_ERROR, "operation not available in multiplayer");
+    return curpeer!=NULL;
 }
 
 void setrate(int rate)
@@ -34,17 +35,9 @@ void throttle()
     enet_peer_throttle_configure(curpeer, throttle_interval*1000, throttle_accel, throttle_decel);
 }
 
-bool isconnected(bool attempt)
-{
-    return curpeer || (attempt && connpeer);
-}
-
-ICOMMAND(isconnected, "i", (int *attempt), intret(isconnected(*attempt > 0) ? 1 : 0));
-
 void abortconnect()
 {
     if(!connpeer) return;
-    game::connectfail();
     if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer);
     connpeer = NULL;
     if(curpeer) return;
@@ -52,7 +45,7 @@ void abortconnect()
     clienthost = NULL;
 }
 
-void connectserv(const char *servername, int serverport, const char *serverpassword)
+void connects(char *servername)
 {   
     if(connpeer)
     {
@@ -60,15 +53,13 @@ void connectserv(const char *servername, int serverport, const char *serverpassw
         abortconnect();
     }
 
-    if(serverport <= 0) serverport = server::serverport();
-
     ENetAddress address;
-    address.port = serverport;
+    address.port = sv->serverport();
 
     if(servername)
     {
-        addserver(servername, serverport, serverpassword[0] ? serverpassword : NULL);
-        conoutf("attempting to connect to %s:%d", servername, serverport);
+        addserver(servername);
+        conoutf("attempting to connect to %s", servername);
         if(!resolverwait(servername, &address))
         {
             conoutf("\f3could not resolve server %s", servername);
@@ -85,18 +76,22 @@ void connectserv(const char *servername, int serverport, const char *serverpassw
 
     if(clienthost)
     {
-        connpeer = enet_host_connect(clienthost, &address, game::numchannels()); 
+        connpeer = enet_host_connect(clienthost, &address, cc->numchannels()); 
         enet_host_flush(clienthost);
         connmillis = totalmillis;
         connattempts = 0;
-
-        game::connectattempt(servername ? servername : "", serverpassword ? serverpassword : "", address);
     }
     else conoutf("\f3could not connect to server");
 }
 
-void disconnect(bool async, bool cleanup)
+void lanconnect()
 {
+    connects(0);
+}
+
+void disconnect(int onlyclean, int async)
+{
+    bool cleanup = onlyclean!=0;
     if(curpeer) 
     {
         if(!discmillis)
@@ -113,14 +108,19 @@ void disconnect(bool async, bool cleanup)
         curpeer = NULL;
         discmillis = 0;
         conoutf("disconnected");
-        game::gamedisconnect(cleanup);
-        mainmenu = 1;
+        cleanup = true;
+    }
+    if(cleanup)
+    {
+        cc->gamedisconnect();
+        localdisconnect();
     }
     if(!connpeer && clienthost)
     {
         enet_host_destroy(clienthost);
         clienthost = NULL;
     }
+    if(!onlyclean) { localconnect(); cc->gameconnect(false); }
 }
 
 void trydisconnect()
@@ -129,42 +129,54 @@ void trydisconnect()
     {
         conoutf("aborting connection attempt");
         abortconnect();
+        return;
     }
-    else if(curpeer)
+    if(!curpeer)
     {
-        conoutf("attempting to disconnect...");
-        disconnect(!discmillis);
+        conoutf("not connected");
+        return;
     }
-    else conoutf("not connected");
+    conoutf("attempting to disconnect...");
+    disconnect(0, !discmillis);
 }
 
-ICOMMAND(connect, "sis", (char *name, int *port, char *pw), connectserv(name, *port, pw));
-ICOMMAND(lanconnect, "is", (int *port, char *pw), connectserv(NULL, *port, pw));
+COMMANDN(connect, connects, "s");
+COMMAND(lanconnect, "");
 COMMANDN(disconnect, trydisconnect, "");
-ICOMMAND(localconnect, "", (), { if(!isconnected() && !haslocalclients()) localconnect(); });
-ICOMMAND(localdisconnect, "", (), { if(haslocalclients()) localdisconnect(); });
 
-void sendclientpacket(ENetPacket *packet, int chan)
+int lastupdate = -1000;
+
+void sendpackettoserv(ENetPacket *packet, int chan)
 {
     if(curpeer) enet_peer_send(curpeer, chan, packet);
     else localclienttoserver(chan, packet);
 }
 
-void flushclient()
+void c2sinfo(dynent *d, int rate)                     // send update to the server
 {
+    if(totalmillis-lastupdate<rate) return;    // don't update faster than 30fps
+    lastupdate = totalmillis;
+    ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0);
+    ucharbuf p(packet->data, packet->dataLength);
+    bool reliable = false;
+    int chan = cc->sendpacketclient(p, reliable, d);
+    if(!p.length()) { enet_packet_destroy(packet); return; }
+    if(reliable) packet->flags = ENET_PACKET_FLAG_RELIABLE;
+    enet_packet_resize(packet, p.length());
+    sendpackettoserv(packet, chan);
     if(clienthost) enet_host_flush(clienthost);
 }
 
-void neterr(const char *s, bool disc)
+void neterr(const char *s)
 {
     conoutf(CON_ERROR, "\f3illegal network message (%s)", s);
-    if(disc) disconnect();
+    disconnect();
 }
 
-void localservertoclient(int chan, ENetPacket *packet)   // processes any updates from the server
+void localservertoclient(int chan, uchar *buf, int len)   // processes any updates from the server
 {
-    packetbuf p(packet);
-    game::parsepacketclient(chan, p);
+    ucharbuf p(buf, len);
+    cc->parsepacketclient(chan, p);
 }
 
 void clientkeepalive() { if(clienthost) enet_host_service(clienthost, NULL, 0); }
@@ -189,19 +201,18 @@ void gets2c()           // get updates from the server
     switch(event.type)
     {
         case ENET_EVENT_TYPE_CONNECT:
-            disconnect(false, false); 
-            localdisconnect(false);
+            disconnect(1); 
             curpeer = connpeer;
             connpeer = NULL;
             conoutf("connected to server");
             throttle();
             if(rate) setrate(rate);
-            game::gameconnect(true);
+            cc->gameconnect(true);
             break;
          
         case ENET_EVENT_TYPE_RECEIVE:
             if(discmillis) conoutf("attempting to disconnect...");
-            else localservertoclient(event.channelID, event.packet);
+            else localservertoclient(event.channelID, event.packet->data, (int)event.packet->dataLength);
             enet_packet_destroy(event.packet);
             break;
 
diff --git a/engine/command.cpp b/engine/command.cpp
index 22619b6..4ab0b01 100644
--- a/engine/command.cpp
+++ b/engine/command.cpp
@@ -1,6 +1,7 @@
 // command.cpp: implements the parsing and execution of a tiny script language which
 // is largely backwards compatible with the quake console language.
 
+#include "pch.h"
 #include "engine.h"
 
 char *exchangestr(char *o, const char *n) { delete[] o; return newstring(n); }
@@ -30,38 +31,32 @@ void clear_command()
     if(idents) idents->clear();
 }
 
-void clearoverride(ident &i)
+void clearoverrides()
 {
-    if(i.override==NO_OVERRIDE) return;
-    switch(i.type)
-    {
-        case ID_ALIAS:
-            if(i.action[0])
+    enumerate(*idents, ident, i,
+        if(i.override!=NO_OVERRIDE)
+        {
+            switch(i.type)
             {
-                if(i.action != i.isexecuting) delete[] i.action;
-                i.action = newstring("");
+                case ID_ALIAS: 
+                    if(i.action[0]) i.action = exchangestr(i.action, ""); 
+                    break;
+                case ID_VAR: 
+                    *i.storage.i = i.overrideval.i;
+                    i.changed();
+                    break;
+                case ID_FVAR:
+                    *i.storage.f = i.overrideval.f;
+                    i.changed();
+                    break;
+                case ID_SVAR:
+                    delete[] *i.storage.s;
+                    *i.storage.s = i.overrideval.s;
+                    i.changed();
+                    break;
             }
-            break;
-        case ID_VAR:
-            *i.storage.i = i.overrideval.i;
-            i.changed();
-            break;
-        case ID_FVAR:
-            *i.storage.f = i.overrideval.f;
-            i.changed();
-            break;
-        case ID_SVAR:
-            delete[] *i.storage.s;
-            *i.storage.s = i.overrideval.s;
-            i.changed();
-            break;
-    }
-    i.override = NO_OVERRIDE;
-}
-
-void clearoverrides()
-{
-    enumerate(*idents, ident, i, clearoverride(i));
+            i.override = NO_OVERRIDE;
+        });
 }
 
 void pushident(ident &id, char *val)
@@ -90,7 +85,7 @@ ident *newident(const char *name)
     if(!id)
     {
         ident init(ID_ALIAS, newstring(name), newstring(""), persistidents ? IDF_PERSIST : 0);
-        id = &idents->access(init.name, init);
+        id = idents->access(init.name, &init);
     }
     return id;
 }
@@ -111,17 +106,8 @@ void pop(char *name)
     if(id) popident(*id);
 }
 
-void resetvar(char *name)
-{
-    ident *id = idents->access(name);
-    if(!id) return;
-    if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
-    else clearoverride(*id);
-}
-
 COMMAND(push, "ss");
 COMMAND(pop, "s");
-COMMAND(resetvar, "s");
 
 void aliasa(const char *name, char *action)
 {
@@ -130,7 +116,7 @@ void aliasa(const char *name, char *action)
     {
         ident b(ID_ALIAS, newstring(name), action, persistidents ? IDF_PERSIST : 0);
         if(overrideidents) b.override = OVERRIDDEN;
-        idents->access(b.name, b);
+        idents->access(b.name, &b);
     }
     else if(b->type != ID_ALIAS)
     {
@@ -164,15 +150,15 @@ int variable(const char *name, int min, int cur, int max, int *storage, void (*f
 {
     if(!idents) idents = new identtable;
     ident v(ID_VAR, name, min, cur, max, storage, (void *)fun, flags);
-    idents->access(name, v);
+    idents->access(name, &v);
     return cur;
 }
 
-float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), int flags)
+float fvariable(const char *name, float cur, float *storage, void (*fun)(), int flags)
 {
     if(!idents) idents = new identtable;
-    ident v(ID_FVAR, name, min, cur, max, storage, (void *)fun, flags);
-    idents->access(name, v);
+    ident v(ID_FVAR, name, cur, storage, (void *)fun, flags);
+    idents->access(name, &v);
     return cur;
 }
 
@@ -180,7 +166,7 @@ char *svariable(const char *name, const char *cur, char **storage, void (*fun)()
 {
     if(!idents) idents = new identtable;
     ident v(ID_SVAR, name, newstring(cur), storage, (void *)fun, flags);
-    idents->access(name, v);
+    idents->access(name, &v);
     return v.val.s;
 }
 
@@ -188,44 +174,22 @@ char *svariable(const char *name, const char *cur, char **storage, void (*fun)()
     ident *id = idents->access(name); \
     if(!id || id->type!=vartype) return retval;
 #define GETVAR(id, name, retval) _GETVAR(id, ID_VAR, name, retval)
-#define OVERRIDEVAR(errorval, saveval, resetval, clearval) \
-    if(overrideidents || id->flags&IDF_OVERRIDE) \
-    { \
-        if(id->flags&IDF_PERSIST) \
-        { \
-            conoutf(CON_ERROR, "cannot override persistent variable %s", id->name); \
-            errorval; \
-        } \
-        if(id->override==NO_OVERRIDE) { saveval; id->override = OVERRIDDEN; } \
-        else { clearval; } \
-    } \
-    else \
-    { \
-        if(id->override!=NO_OVERRIDE) { resetval; id->override = NO_OVERRIDE; } \
-        clearval; \
-    }
-
-void setvar(const char *name, int i, bool dofunc, bool doclamp)
-{
+void setvar(const char *name, int i, bool dofunc) 
+{ 
     GETVAR(id, name, );
-    OVERRIDEVAR(return, id->overrideval.i = *id->storage.i, , )
-    if(doclamp) *id->storage.i = clamp(i, id->minval, id->maxval);
-    else *id->storage.i = i;
+    *id->storage.i = clamp(i, id->minval, id->maxval); 
     if(dofunc) id->changed();
-}
-void setfvar(const char *name, float f, bool dofunc, bool doclamp)
+} 
+void setfvar(const char *name, float f, bool dofunc)
 {
     _GETVAR(id, ID_FVAR, name, );
-    OVERRIDEVAR(return, id->overrideval.f = *id->storage.f, , );
-    if(doclamp) *id->storage.f = clamp(f, id->minvalf, id->maxvalf);
-    else *id->storage.f = f;
+    *id->storage.f = f;
     if(dofunc) id->changed();
 }
 void setsvar(const char *name, const char *str, bool dofunc)
 {
     _GETVAR(id, ID_SVAR, name, );
-    OVERRIDEVAR(return, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
-    *id->storage.s = newstring(str);
+    *id->storage.s = exchangestr(*id->storage.s, str);
     if(dofunc) id->changed();
 }
 int getvar(const char *name) 
@@ -269,14 +233,14 @@ bool addcommand(const char *name, void (*fun)(), const char *narg)
 {
     if(!idents) idents = new identtable;
     ident c(ID_COMMAND, name, narg, (void *)fun);
-    idents->access(name, c);
+    idents->access(name, &c);
     return false;
 }
 
 void addident(const char *name, ident *id)
 {
     if(!idents) idents = new identtable;
-    idents->access(name, *id);
+    idents->access(name, id);
 }
 
 static vector<vector<char> *> wordbufs;
@@ -311,44 +275,6 @@ void parsemacro(const char *&p, int level, vector<char> &wordbuf)
     while(*alias) wordbuf.add(*alias++);
 }
 
-const char *parsestring(const char *p)
-{
-    for(; *p; p++) switch(*p) 
-    {
-        case '\r':
-        case '\n':
-        case '\"':
-            return p;
-        case '^':
-            if(*++p) break;
-            return p;
-    }
-    return p;
-}
-
-int escapestring(char *dst, const char *src, const char *end)
-{
-    char *start = dst;
-    while(src < end)
-    {
-        int c = *src++;
-        if(c == '^')
-        {
-            if(src >= end) break;
-            int e = *src++;
-            switch(e)
-            {
-                case 'n': *dst++ = '\n'; break;
-                case 't': *dst++ = '\t'; break;
-                case 'f': *dst++ = '\f'; break;
-                default: *dst++ = e; break;
-            }            
-        }
-        else *dst++ = c;
-    }
-    return dst - start;
-}
-
 char *parseexp(const char *&p, int right)          // parse any nested set of () or []
 {
     if(bufnest++>=wordbufs.length()) wordbufs.add(new vector<char>);
@@ -357,37 +283,36 @@ char *parseexp(const char *&p, int right)          // parse any nested set of ()
     for(int brak = 1; brak; )
     {
         int c = *p++;
-        switch(c)
+        if(c=='\r') continue;               // hack
+        if(left=='[' && c=='@')
         {
-            case '\r': continue;
-            case '@': 
-                if(left == '[') { parsemacro(p, brak, wordbuf); continue; }
-                break;
-            case '\"':
-            {
-                wordbuf.add(c);
-                const char *end = parsestring(p);
-                wordbuf.put(p, end - p);
-                p = end;
-                if(*p=='\"') wordbuf.add(*p++);
-                continue;
-            }
-            case '/':
-                if(*p=='/')
-                {
-                    p += strcspn(p, "\n\0");
-                    continue;
-                }
-                break;
-            case '\0':
-                p--;
-                conoutf(CON_ERROR, "missing \"%c\"", right);
-                wordbuf.setsize(0); 
-                bufnest--;
-                return NULL; 
-        } 
+            parsemacro(p, brak, wordbuf);
+            continue;
+        }
+        if(c=='\"')
+        {
+            wordbuf.add(c);
+            const char *end = p+strcspn(p, "\"\r\n\0");
+            while(p < end) wordbuf.add(*p++);
+            if(*p=='\"') wordbuf.add(*p++);
+            continue;
+        }
+        if(c=='/' && *p=='/')
+        {
+            p += strcspn(p, "\n\0");
+            continue;
+        }
+            
         if(c==left) brak++;
         else if(c==right) brak--;
+        else if(!c) 
+        { 
+            p--;
+            conoutf(CON_ERROR, "missing \"%c\"", right);
+            wordbuf.setsize(0); 
+            bufnest--;
+            return NULL; 
+        }
         wordbuf.add(c);
     }
     wordbuf.pop();
@@ -413,8 +338,8 @@ char *lookup(char *n)                           // find value of ident reference
     ident *id = idents->access(n+1);
     if(id) switch(id->type)
     {
-        case ID_VAR: { defformatstring(t)("%d", *id->storage.i); return exchangestr(n, t); }
-        case ID_FVAR: return exchangestr(n, floatstr(*id->storage.f));
+        case ID_VAR: { s_sprintfd(t)("%d", *id->storage.i); return exchangestr(n, t); }
+        case ID_FVAR: { s_sprintfd(t)("%f", *id->storage.f); return exchangestr(n, t); }
         case ID_SVAR: return exchangestr(n, *id->storage.s);
         case ID_ALIAS: return exchangestr(n, id->action);
     }
@@ -422,7 +347,7 @@ char *lookup(char *n)                           // find value of ident reference
     return n;
 }
 
-char *parseword(const char *&p, int arg, int &infix)                       // parse single argument, including expressions
+char *parseword(const char *&p)                       // parse single argument, including expressions
 {
     for(;;)
     {
@@ -433,10 +358,9 @@ char *parseword(const char *&p, int arg, int &infix)                       // pa
     if(*p=='\"')
     {
         p++;
-        const char *end = parsestring(p);
-        char *s = newstring(end - p);
-        s[escapestring(s, p, end)] = '\0';
-        p = end;
+        const char *word = p;
+        p += strcspn(p, "\"\r\n\0");
+        char *s = newstring(word, p-word);
         if(*p=='\"') p++;
         return s;
     }
@@ -451,10 +375,6 @@ char *parseword(const char *&p, int arg, int &infix)                       // pa
         p += 2;
     }
     if(p-word==0) return NULL;
-    if(arg==1 && p-word==1) switch(*word)
-    {
-        case '=': infix = *word; break;
-    }
     char *s = newstring(word, p-word);
     if(*s=='$') return lookup(s);               // substitute variables
     return s;
@@ -476,15 +396,12 @@ char *conc(char **w, int n, bool space)
 
 VARN(numargs, _numargs, 0, 0, 25);
 
-static inline bool isinteger(char *c)
-{
-    return isdigit(c[0]) || ((c[0]=='+' || c[0]=='-' || c[0]=='.') && isdigit(c[1]));
-}
-
 #define parseint(s) strtol((s), NULL, 0)
 
 char *commandret = NULL;
 
+extern const char *addreleaseaction(const char *s);
+
 char *executeret(const char *p)               // all evaluation happens here, recursively
 {
     const int MAXWORDS = 25;                    // limit, remove
@@ -493,12 +410,12 @@ char *executeret(const char *p)               // all evaluation happens here, re
     #define setretval(v) { char *rv = v; if(rv) retval = rv; }
     for(bool cont = true; cont;)                // for each ; seperated statement
     {
-        int numargs = MAXWORDS, infix = 0;
+        int numargs = MAXWORDS;
         loopi(MAXWORDS)                         // collect all argument values
         {
             w[i] = (char *)"";
             if(i>numargs) continue;
-            char *s = parseword(p, i, infix);   // parse and evaluate exps
+            char *s = parseword(p);             // parse and evaluate exps
             if(s) w[i] = s;
             else numargs = i;
         }
@@ -510,22 +427,17 @@ char *executeret(const char *p)               // all evaluation happens here, re
         
         DELETEA(retval);
 
-        if(infix)
+        if(w[1][0]=='=' && !w[1][1])
         {
-            switch(infix)
-            {
-                case '=':    
-                    aliasa(c, numargs>2 ? w[2] : newstring(""));
-                    w[2] = NULL;
-                    break;
-            }
+            aliasa(c, numargs>2 ? w[2] : newstring(""));
+            w[2] = NULL;
         }
         else
         {     
             ident *id = idents->access(c);
             if(!id)
             {
-                if(!isinteger(c))
+                if(!isdigit(*c) && ((*c!='+' && *c!='-' && *c!='.') || !isdigit(c[1]))) 
                     conoutf(CON_ERROR, "unknown command: %s", c);
                 setretval(newstring(c));
             }
@@ -548,9 +460,7 @@ char *executeret(const char *p)               // all evaluation happens here, re
                         case 's':                                 v[n] = w[++wn];     n++; break;
                         case 'i': nstor[n].i = parseint(w[++wn]); v[n] = &nstor[n].i; n++; break;
                         case 'f': nstor[n].f = atof(w[++wn]);     v[n] = &nstor[n].f; n++; break;
-#ifndef STANDALONE
                         case 'D': nstor[n].i = addreleaseaction(id->name) ? 1 : 0; v[n] = &nstor[n].i; n++; break;
-#endif
                         case 'V': v[n++] = w+1; nstor[n].i = numargs-1; v[n] = &nstor[n].i; n++; break;
                         case 'C': if(!cargs) cargs = conc(w+1, numargs-1, true); v[n++] = cargs; break;
                         default: fatal("builtin declared with illegal type");
@@ -575,84 +485,50 @@ char *executeret(const char *p)               // all evaluation happens here, re
                 }
 
                 case ID_VAR:                        // game defined variables 
-                    if(numargs <= 1) 
-                    {
-                        if(id->flags&IDF_HEX && id->maxval==0xFFFFFF) 
-                            conoutf("%s = 0x%.6X (%d, %d, %d)", c, *id->storage.i, (*id->storage.i>>16)&0xFF, (*id->storage.i>>8)&0xFF, *id->storage.i&0xFF);
-                        else
-                            conoutf(id->flags&IDF_HEX ? "%s = 0x%X" : "%s = %d", c, *id->storage.i);      // var with no value just prints its current value
-                    }
-                    else if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
-#ifndef STANDALONE
-                    else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
-#else
+                    if(numargs <= 1) conoutf("%s = %d", c, *id->storage.i);      // var with no value just prints its current value
+                    else if(id->minval>id->maxval) conoutf(CON_ERROR, "variable %s is read-only", id->name);
                     else
-#endif
                     {
-                        OVERRIDEVAR(break, id->overrideval.i = *id->storage.i, , )
+                        #define OVERRIDEVAR(saveval, resetval) \
+                            if(overrideidents || id->flags&IDF_OVERRIDE) \
+                            { \
+                                if(id->flags&IDF_PERSIST) \
+                                { \
+                                    conoutf(CON_ERROR, "cannot override persistent variable %s", id->name); \
+                                    break; \
+                                } \
+                                if(id->override==NO_OVERRIDE) { saveval; id->override = OVERRIDDEN; } \
+                            } \
+                            else if(id->override!=NO_OVERRIDE) { resetval; id->override = NO_OVERRIDE; }
+                        OVERRIDEVAR(id->overrideval.i = *id->storage.i, )
                         int i1 = parseint(w[1]);
-                        if(id->flags&IDF_HEX && numargs > 2)
-                        {
-                            i1 <<= 16;
-                            i1 |= parseint(w[2])<<8;    
-                            i1 |= parseint(w[3]);
-                        }
                         if(i1<id->minval || i1>id->maxval)
                         {
                             i1 = i1<id->minval ? id->minval : id->maxval;                // clamp to valid range
-                            conoutf(CON_ERROR,
-                                id->flags&IDF_HEX ?
-                                    (id->minval <= 255 ? "valid range for %s is %d..0x%X" : "valid range for %s is 0x%X..0x%X") :
-                                    "valid range for %s is %d..%d", 
-                                id->name, id->minval, id->maxval);
+                            conoutf(CON_ERROR, "valid range for %s is %d..%d", id->name, id->minval, id->maxval);
                         }
                         *id->storage.i = i1;
                         id->changed();                                             // call trigger function if available
-#ifndef STANDALONE
-                        if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
-#endif
                     }
                     break;
                   
                 case ID_FVAR:
-                    if(numargs <= 1) conoutf("%s = %s", c, floatstr(*id->storage.f));
-                    else if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
-#ifndef STANDALONE
-                    else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
-#else
+                    if(numargs <= 1) conoutf("%s = %f", c, *id->storage.f);
                     else
-#endif
                     {
-                        OVERRIDEVAR(break, id->overrideval.f = *id->storage.f, , );
-                        float f1 = atof(w[1]);
-                        if(f1<id->minvalf || f1>id->maxvalf)
-                        {
-                            f1 = f1<id->minvalf ? id->minvalf : id->maxvalf;                // clamp to valid range
-                            conoutf(CON_ERROR, "valid range for %s is %s..%s", id->name, floatstr(id->minvalf), floatstr(id->maxvalf));
-                        }
-                        *id->storage.f = f1;
+                        OVERRIDEVAR(id->overrideval.f = *id->storage.f, );
+                        *id->storage.f = atof(w[1]);
                         id->changed();
-#ifndef STANDALONE
-                        if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
-#endif
                     }
                     break;
  
                 case ID_SVAR:
                     if(numargs <= 1) conoutf(strchr(*id->storage.s, '"') ? "%s = [%s]" : "%s = \"%s\"", c, *id->storage.s);
-                    else if(id->flags&IDF_READONLY) conoutf(CON_ERROR, "variable %s is read-only", id->name);
-#ifndef STANDALONE
-                    else if(!(id->flags&IDF_OVERRIDE) || overrideidents || game::allowedittoggle())
-#else
                     else
-#endif
                     {
-                        OVERRIDEVAR(break, id->overrideval.s = *id->storage.s, delete[] id->overrideval.s, delete[] *id->storage.s);
+                        OVERRIDEVAR(id->overrideval.s = *id->storage.s, delete[] id->overrideval.s);
                         *id->storage.s = newstring(w[1]);
                         id->changed();
-#ifndef STANDALONE
-                        if(id->flags&IDF_OVERRIDE && !overrideidents) game::vartrigger(id);
-#endif
                     }
                     break;
                         
@@ -663,7 +539,7 @@ char *executeret(const char *p)               // all evaluation happens here, re
                     {
                         if(i > argids.length())
                         {
-                            defformatstring(argname)("arg%d", i);
+                            s_sprintfd(argname)("arg%d", i);
                             argids.add(newident(argname));
                         }
                         pushident(*argids[i-1], w[i]); // set any arguments as (global) arg values so functions can access them
@@ -695,104 +571,60 @@ int execute(const char *p)
     return i;
 }
 
-bool execfile(const char *cfgfile, bool msg)
+bool execfile(const char *cfgfile)
 {
     string s;
-    copystring(s, cfgfile);
+    s_strcpy(s, cfgfile);
     char *buf = loadfile(path(s), NULL);
-    if(!buf) 
-    {
-        if(msg) conoutf(CON_ERROR, "could not read \"%s\"", cfgfile);
-        return false;
-    }
+    if(!buf) return false;
     execute(buf);
     delete[] buf;
     return true;
 }
 
-#ifndef STANDALONE
-static int sortidents(ident **x, ident **y)
+void exec(const char *cfgfile)
 {
-    return strcmp((*x)->name, (*y)->name);
-}
-
-void writeescapedstring(stream *f, const char *s)
-{
-    f->putchar('"');
-    for(; *s; s++) switch(*s)
-    {
-        case '\n': f->write("^n", 2); break;
-        case '\t': f->write("^t", 2); break;
-        case '\f': f->write("^f", 2); break;
-        case '"': f->write("^\"", 2); break;
-        default: f->putchar(*s); break;
-    }
-    f->putchar('"');
+    if(!execfile(cfgfile)) conoutf(CON_ERROR, "could not read \"%s\"", cfgfile);
 }
 
 void writecfg()
 {
-    stream *f = openfile(path(game::savedconfig(), true), "w");
+    FILE *f = openfile(path(cl->savedconfig(), true), "w");
     if(!f) return;
-    f->printf("// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", game::defaultconfig(), game::autoexec());
-    game::writeclientinfo(f);
-    f->printf("\n");
+    fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// delete this file to have %s overwrite these settings\n// modify settings in game, or put settings in %s to override anything\n\n", cl->defaultconfig(), cl->autoexec());
+    cc->writeclientinfo(f);
+    fprintf(f, "\n");
     writecrosshairs(f);
-    vector<ident *> ids;
-    enumerate(*idents, ident, id, ids.add(&id));
-    ids.sort(sortidents);
-    loopv(ids)
-    {
-        ident &id = *ids[i];
+    enumerate(*idents, ident, id,
         if(id.flags&IDF_PERSIST) switch(id.type)
         {
-            case ID_VAR: f->printf("%s %d\n", id.name, *id.storage.i); break;
-            case ID_FVAR: f->printf("%s %s\n", id.name, floatstr(*id.storage.f)); break;
-            case ID_SVAR: f->printf("%s ", id.name); writeescapedstring(f, *id.storage.s); f->putchar('\n'); break;
+            case ID_VAR: fprintf(f, "%s %d\n", id.name, *id.storage.i); break;
+            case ID_FVAR: fprintf(f, "%s %f\n", id.name, *id.storage.f); break;
+            case ID_SVAR: fprintf(f, "%s [%s]\n", id.name, *id.storage.s); break;
         }
-    }
-    f->printf("\n");
+    );
+    fprintf(f, "\n");
     writebinds(f);
-    f->printf("\n");
-    loopv(ids)
-    {
-        ident &id = *ids[i];
+    fprintf(f, "\n");
+    enumerate(*idents, ident, id,
         if(id.type==ID_ALIAS && id.flags&IDF_PERSIST && id.override==NO_OVERRIDE && !strstr(id.name, "nextmap_") && id.action[0])
         {
-            f->printf("\"%s\" = [%s]\n", id.name, id.action);
+            fprintf(f, "\"%s\" = [%s]\n", id.name, id.action);
         }
-    }
-    f->printf("\n");
+    );
+    fprintf(f, "\n");
     writecompletions(f);
-    delete f;
+    fclose(f);
 }
 
 COMMAND(writecfg, "");
-#endif
 
 // below the commands that implement a small imperative language. thanks to the semantics of
 // () and [] expressions, any control construct can be defined trivially.
 
-void intret(int v) { defformatstring(b)("%d", v); commandret = newstring(b); }
-
-const char *floatstr(float v)
-{
-    static int n = 0;
-    static string t[3];
-    n = (n + 1)%3;
-    formatstring(t[n])(v==int(v) ? "%.1f" : "%.7g", v);
-    return t[n];
-}
-
-void floatret(float v)
-{
-    commandret = newstring(floatstr(v));
-}
+void intret(int v) { s_sprintfd(b)("%d", v); commandret = newstring(b); }
 
-#undef ICOMMANDNAME
-#define ICOMMANDNAME(name) _stdcmd
-
-ICOMMAND(if, "sss", (char *cond, char *t, char *f), commandret = executeret(cond[0] && (!isinteger(cond) || parseint(cond)) ? t : f));
+ICOMMAND(if, "sss", (char *cond, char *t, char *f), commandret = executeret(cond[0]!='0' ? t : f));
 ICOMMAND(loop, "sis", (char *var, int *n, char *body), 
 {
     if(*n<=0) return;
@@ -893,7 +725,7 @@ void getalias_(char *s)
     result(getalias(s));
 }
 
-ICOMMAND(exec, "s", (char *file), execfile(file));
+COMMAND(exec, "s");
 COMMAND(concat, "C");
 COMMAND(result, "s");
 COMMAND(concatword, "V");
@@ -903,93 +735,35 @@ COMMAND(substr, "sii");
 COMMAND(listlen, "s");
 COMMANDN(getalias, getalias_, "s");
 
-void looplist(const char *var, const char *list, const char *body, bool search)
-{
-    ident *id = newident(var);
-    if(id->type!=ID_ALIAS) { if(search) intret(-1); return; }
-    int n = 0;
-    for(const char *s = list;;)
-    {
-        whitespaceskip;
-        if(!*s) { if(search) intret(-1); break; }
-        const char *start = s;
-        elementskip;
-        const char *end = s;
-        if(*start=='"') { start++; if(end[-1]=='"') --end; }
-        char *val = newstring(start, end-start);
-        if(n++) aliasa(id->name, val);
-        else pushident(*id, val);
-        if(execute(body) && search) { intret(n-1); break; }
-    }
-    if(n) popident(*id);
-}
+void add  (int *a, int *b) { intret(*a + *b); }          COMMANDN(+, add, "ii");
+void mul  (int *a, int *b) { intret(*a * *b); }          COMMANDN(*, mul, "ii");
+void sub  (int *a, int *b) { intret(*a - *b); }          COMMANDN(-, sub, "ii");
+void divi (int *a, int *b) { intret(*b ? *a / *b : 0); } COMMANDN(div, divi, "ii");
+void mod  (int *a, int *b) { intret(*b ? *a % *b : 0); } COMMAND(mod, "ii");
+void equal(int *a, int *b) { intret((int)(*a == *b)); }  COMMANDN(=, equal, "ii");
+void nequal(int *a, int *b) { intret((int)(*a != *b)); } COMMANDN(!=, nequal, "ii");
+void lt   (int *a, int *b) { intret((int)(*a < *b)); }   COMMANDN(<, lt, "ii");
+void gt   (int *a, int *b) { intret((int)(*a > *b)); }   COMMANDN(>, gt, "ii");
+void lte  (int *a, int *b) { intret((int)(*a <= *b)); } COMMANDN(<=, lte, "ii");
+void gte  (int *a, int *b) { intret((int)(*a >= *b)); } COMMANDN(>=, gte, "ii");
+void xora (int *a, int *b) { intret(*a ^ *b); }          COMMANDN(^, xora, "ii");
+void nota (int *a)         { intret(*a == 0); }          COMMANDN(!, nota, "i");
+void mina (int *a, int *b) { intret(min(*a, *b)); }      COMMANDN(min, mina, "ii");
+void maxa (int *a, int *b) { intret(max(*a, *b)); }      COMMANDN(max, maxa, "ii");
 
-ICOMMAND(listfind, "sss", (char *var, char *list, char *body), looplist(var, list, body, true));
-ICOMMAND(looplist, "sss", (char *var, char *list, char *body), looplist(var, list, body, false));
-ICOMMAND(loopfiles, "ssss", (char *var, char *dir, char *ext, char *body),
-{
-    ident *id = newident(var);
-    if(id->type!=ID_ALIAS) return;
-    vector<char *> files;
-    listfiles(dir, ext[0] ? ext : NULL, files);
-    loopv(files)
-    {
-        char *file = files[i];
-        bool redundant = false;
-        loopj(i) if(!strcmp(files[j], file)) { redundant = true; break; }
-        if(redundant) { delete[] file; continue; }
-        if(i) aliasa(id->name, file);
-        else pushident(*id, file);
-        execute(body);
-    }
-    if(files.length()) popident(*id);
-});
+void anda (char *a, char *b) { intret(execute(a)!=0 && execute(b)!=0); }
+void ora  (char *a, char *b) { intret(execute(a)!=0 || execute(b)!=0); }
+
+COMMANDN(&&, anda, "ss");
+COMMANDN(||, ora, "ss");
+
+void rndn(int *a)          { intret(*a>0 ? rnd(*a) : 0); }  COMMANDN(rnd, rndn, "i");
+
+void strcmpa(char *a, char *b) { intret(strcmp(a,b)==0); }  COMMANDN(strcmp, strcmpa, "ss");
+
+ICOMMAND(echo, "C", (char *s), conoutf(CON_ECHO, "\f1%s", s));
 
-ICOMMAND(+, "ii", (int *a, int *b), intret(*a + *b));
-ICOMMAND(*, "ii", (int *a, int *b), intret(*a * *b));
-ICOMMAND(-, "ii", (int *a, int *b), intret(*a - *b));
-ICOMMAND(+f, "ff", (float *a, float *b), floatret(*a + *b));
-ICOMMAND(*f, "ff", (float *a, float *b), floatret(*a * *b));
-ICOMMAND(-f, "ff", (float *a, float *b), floatret(*a - *b));
-ICOMMAND(=, "ii", (int *a, int *b), intret((int)(*a == *b)));
-ICOMMAND(!=, "ii", (int *a, int *b), intret((int)(*a != *b)));
-ICOMMAND(<, "ii", (int *a, int *b), intret((int)(*a < *b)));
-ICOMMAND(>, "ii", (int *a, int *b), intret((int)(*a > *b)));
-ICOMMAND(<=, "ii", (int *a, int *b), intret((int)(*a <= *b)));
-ICOMMAND(>=, "ii", (int *a, int *b), intret((int)(*a >= *b)));
-ICOMMAND(=f, "ff", (float *a, float *b), intret((int)(*a == *b)));
-ICOMMAND(!=f, "ff", (float *a, float *b), intret((int)(*a != *b)));
-ICOMMAND(<f, "ff", (float *a, float *b), intret((int)(*a < *b)));
-ICOMMAND(>f, "ff", (float *a, float *b), intret((int)(*a > *b)));
-ICOMMAND(<=f, "ff", (float *a, float *b), intret((int)(*a <= *b)));
-ICOMMAND(>=f, "ff", (float *a, float *b), intret((int)(*a >= *b)));
-ICOMMAND(^, "ii", (int *a, int *b), intret(*a ^ *b));
-ICOMMAND(!, "i", (int *a), intret(*a == 0));
-ICOMMAND(&, "ii", (int *a, int *b), intret(*a & *b));
-ICOMMAND(|, "ii", (int *a, int *b), intret(*a | *b));
-ICOMMAND(~, "i", (int *a), intret(~*a));
-ICOMMAND(^~, "ii", (int *a, int *b), intret(*a ^ ~*b));
-ICOMMAND(&~, "ii", (int *a, int *b), intret(*a & ~*b));
-ICOMMAND(|~, "ii", (int *a, int *b), intret(*a | ~*b));
-ICOMMAND(<<, "ii", (int *a, int *b), intret(*a << *b));
-ICOMMAND(>>, "ii", (int *a, int *b), intret(*a >> *b));
-ICOMMAND(&&, "ss", (char *a, char *b), intret(execute(a)!=0 && execute(b)!=0));
-ICOMMAND(||, "ss", (char *a, char *b), intret(execute(a)!=0 || execute(b)!=0));
-
-ICOMMAND(div, "ii", (int *a, int *b), intret(*b ? *a / *b : 0));
-ICOMMAND(mod, "ii", (int *a, int *b), intret(*b ? *a % *b : 0));
-ICOMMAND(divf, "ff", (float *a, float *b), floatret(*b ? *a / *b : 0));
-ICOMMAND(modf, "ff", (float *a, float *b), floatret(*b ? fmod(*a, *b) : 0));
-ICOMMAND(min, "ii", (int *a, int *b), intret(min(*a, *b)));
-ICOMMAND(max, "ii", (int *a, int *b), intret(max(*a, *b)));
-ICOMMAND(minf, "ff", (float *a, float *b), floatret(min(*a, *b)));
-ICOMMAND(maxf, "ff", (float *a, float *b), floatret(max(*a, *b)));
-
-ICOMMAND(rnd, "ii", (int *a, int *b), intret(*a - *b > 0 ? rnd(*a - *b) + *b : *b));
-ICOMMAND(strcmp, "ss", (char *a, char *b), intret(strcmp(a,b)==0));
-ICOMMAND(echo, "C", (char *s), conoutf("\f1%s", s));
-ICOMMAND(strstr, "ss", (char *a, char *b), { char *s = strstr(a, b); intret(s ? s-a : -1); });
-ICOMMAND(strlen, "s", (char *s), intret(strlen(s)));
+void strstra(char *a, char *b) { char *s = strstr(a, b); intret(s ? s-a : -1); } COMMANDN(strstr, strstra, "ss");
 
 char *strreplace(const char *s, const char *oldval, const char *newval)
 {
@@ -1014,14 +788,13 @@ char *strreplace(const char *s, const char *oldval, const char *newval)
     }
 }
 
-ICOMMAND(strreplace, "sss", (char *s, char *o, char *n), commandret = strreplace(s, o, n));
+void strreplacea(char *s, char *o, char *n) { commandret = strreplace(s, o, n); } COMMANDN(strreplace, strreplacea, "sss");
 
-#ifndef STANDALONE
 struct sleepcmd
 {
     int millis;
     char *command;
-    bool override, persist;
+    bool override;
 };
 vector<sleepcmd> sleepcmds;
 
@@ -1031,29 +804,21 @@ void addsleep(int *msec, char *cmd)
     s.millis = *msec+lastmillis;
     s.command = newstring(cmd);
     s.override = overrideidents;
-    s.persist = persistidents;
 }
 
 COMMANDN(sleep, addsleep, "is");
 
 void checksleep(int millis)
 {
-    if(sleepcmds.empty()) return;
     loopv(sleepcmds)
     {
         sleepcmd &s = sleepcmds[i];
         if(s.millis && millis>s.millis)
         {
             char *cmd = s.command; // execute might create more sleep commands
-            s.command = NULL;
-            bool waspersisting = persistidents, wasoverriding = overrideidents;
-            persistidents = s.persist;
-            overrideidents = s.override;
             execute(cmd);
-            persistidents = waspersisting;
-            overrideidents = wasoverriding;
             delete[] cmd;
-            if(sleepcmds.inrange(i) && !sleepcmds[i].command) sleepcmds.remove(i--);
+            sleepcmds.remove(i--);
         }
     }
 }
@@ -1061,7 +826,7 @@ void checksleep(int millis)
 void clearsleep(bool clearoverrides)
 {
     int len = 0;
-    loopv(sleepcmds) if(sleepcmds[i].command) 
+    loopv(sleepcmds) 
     {
         if(clearoverrides && !sleepcmds[i].override) sleepcmds[len++] = sleepcmds[i];
         else delete[] sleepcmds[i].command;
@@ -1075,5 +840,4 @@ void clearsleep_(int *clearoverrides)
 }
 
 COMMANDN(clearsleep, clearsleep_, "i");
-#endif
 
diff --git a/engine/console.cpp b/engine/console.cpp
index e1ee8c3..d5b76bb 100644
--- a/engine/console.cpp
+++ b/engine/console.cpp
@@ -1,36 +1,37 @@
 // console.cpp: the console buffer, its display, and command line control
 
+#include "pch.h"
 #include "engine.h"
 
 struct cline { char *line; int type, outtime; };
 vector<cline> conlines;
 
-int commandmillis = -1;
+bool saycommandon = false;
 string commandbuf;
 char *commandaction = NULL, *commandprompt = NULL;
 int commandpos = -1;
 
 VARFP(maxcon, 10, 200, 1000, { while(conlines.length() > maxcon) delete[] conlines.pop().line; });
 
-#define CONSTRLEN 512
-
 void conline(int type, const char *sf)        // add a line to the console buffer
 {
     cline cl;
-    cl.line = conlines.length()>maxcon ? conlines.pop().line : newstring("", CONSTRLEN-1);   // constrain the buffer size
+    cl.line = conlines.length()>maxcon ? conlines.pop().line : newstringbuf("");   // constrain the buffer size
     cl.type = type;
     cl.outtime = totalmillis;                       // for how long to keep line on screen
     conlines.insert(0, cl);
-    copystring(cl.line, sf, CONSTRLEN);
+    s_strcpy(cl.line, sf);
 }
 
+#define CONSPAD (FONTH/3)
+
 void conoutfv(int type, const char *fmt, va_list args)
 {
-    static char buf[CONSTRLEN];
-    vformatstring(buf, fmt, args, sizeof(buf));
-    conline(type, buf);
-    filtertext(buf, buf);
-    puts(buf);
+    string sf, sp;
+    formatstring(sf, fmt, args);
+    filtertext(sp, sf);
+    puts(sp);
+    conline(type, sf);
 }
 
 void conoutf(const char *fmt, ...)
@@ -55,19 +56,17 @@ COMMAND(toggleconsole, "");
 
 int rendercommand(int x, int y, int w)
 {
-    if(commandmillis < 0) return 0;
+    if(!saycommandon) return 0;
 
-    defformatstring(s)("%s %s", commandprompt ? commandprompt : ">", commandbuf);
+    s_sprintfd(s)("%s %s", commandprompt ? commandprompt : ">", commandbuf);
     int width, height;
     text_bounds(s, width, height, w);
-    y -= height;
+    y-= height-FONTH;
     draw_text(s, x, y, 0xFF, 0xFF, 0xFF, 0xFF, (commandpos>=0) ? (commandpos+1+(commandprompt?strlen(commandprompt):1)) : strlen(s), w);
     return height;
 }
 
 void blendbox(int x1, int y1, int x2, int y2, bool border)
-;
-#if 0
 {
     notextureshader->set();
 
@@ -99,54 +98,56 @@ void blendbox(int x1, int y1, int x2, int y2, bool border)
 
     defaultshader->set();
 }
-#endif
 
 VARP(consize, 0, 5, 100);
-VARP(miniconsize, 0, 5, 100);
-VARP(miniconwidth, 0, 40, 100);
 VARP(confade, 0, 30, 60);
-VARP(miniconfade, 0, 30, 60);
 VARP(fullconsize, 0, 75, 100);
-HVARP(confilter, 0, 0x7FFFFFF, 0x7FFFFFF);
-HVARP(fullconfilter, 0, 0x7FFFFFF, 0x7FFFFFF);
-HVARP(miniconfilter, 0, 0, 0x7FFFFFF);
+VARP(confilter, 0, 0xFFFFFF, 0xFFFFFF);
+VARP(fullconfilter, 0, 0xFFFFFF, 0xFFFFFF);
 
-int conskip = 0, miniconskip = 0;
+int conskip = 0;
 
-void setconskip(int &skip, int filter, int n)
+void setconskip(int *n)
 {
-    int offset = abs(n), dir = n < 0 ? -1 : 1;
-    skip = clamp(skip, 0, conlines.length()-1);
-    while(offset)
+    int filter = fullconsole ? fullconfilter : confilter,
+        skipped = abs(*n),
+        dir = *n < 0 ? -1 : 1;
+    conskip = clamp(conskip, 0, conlines.length()-1);
+    while(skipped)
     {
-        skip += dir;
-        if(!conlines.inrange(skip))
+        conskip += dir;
+        if(!conlines.inrange(conskip))
         {
-            skip = clamp(skip, 0, conlines.length()-1);
+            conskip = clamp(conskip, 0, conlines.length()-1);
             return;
         }
-        if(conlines[skip].type&filter) --offset;
+        if(conlines[conskip].type&filter) --skipped;
     }
 }
 
-ICOMMAND(conskip, "i", (int *n), setconskip(conskip, fullconsole ? fullconfilter : confilter, *n));
-ICOMMAND(miniconskip, "i", (int *n), setconskip(miniconskip, miniconfilter, *n));
+COMMANDN(conskip, setconskip, "i");
 
-int drawconlines(int conskip, int confade, int conwidth, int conheight, int conoff, int filter, int y = 0, int dir = 1)
+int renderconsole(int w, int h)                   // render buffer taking into account time & scrolling
 {
+    int conheight = min(fullconsole ? ((h*3*fullconsize/100)/FONTH)*FONTH : (FONTH*consize), h*3 - 2*CONSPAD - 2*FONTH/3),
+        conwidth = w*3 - 2*CONSPAD - 2*FONTH/3,
+        filter = fullconsole ? fullconfilter : confilter;
+    
+    if(fullconsole) blendbox(CONSPAD, CONSPAD, conwidth+CONSPAD+2*FONTH/3, conheight+CONSPAD+2*FONTH/3, true);
+    
     int numl = conlines.length(), offset = min(conskip, numl);
-
-    if(confade)
+    
+    if(!fullconsole && confade)
     {
-        if(!conskip)
+        if(!conskip) 
         {
             numl = 0;
             loopvrev(conlines) if(totalmillis-conlines[i].outtime < confade*1000) { numl = i+1; break; }
-        }
+        } 
         else offset--;
     }
-
-    int totalheight = 0;
+   
+    int y = 0;
     loopi(numl) //determine visible height
     {
         // shuffle backwards to fill if necessary
@@ -155,38 +156,21 @@ int drawconlines(int conskip, int confade, int conwidth, int conheight, int cono
         char *line = conlines[idx].line;
         int width, height;
         text_bounds(line, width, height, conwidth);
-        if(totalheight + height > conheight) { numl = i; if(offset == idx) ++offset; break; }
-        totalheight += height;
+        y += height;
+        if(y > conheight) { numl = i; if(offset == idx) ++offset; break; }
     }
-    if(dir > 0) y = conoff;
+    y = CONSPAD+FONTH/3;
     loopi(numl)
     {
-        int idx = offset + (dir > 0 ? numl-i-1 : i);
+        int idx = offset + numl-i-1;
         if(!(conlines[idx].type&filter)) continue;
         char *line = conlines[idx].line;
+        draw_text(line, CONSPAD+FONTH/3, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth);
         int width, height;
         text_bounds(line, width, height, conwidth);
-        if(dir <= 0) y -= height; 
-        draw_text(line, conoff, y, 0xFF, 0xFF, 0xFF, 0xFF, -1, conwidth);
-        if(dir > 0) y += height;
+        y += height;
     }
-    return y+conoff;
-}
-
-int renderconsole(int w, int h, int abovehud)                   // render buffer taking into account time & scrolling
-{
-    int conpad = fullconsole ? 0 : FONTH/4,
-        conoff = fullconsole ? FONTH : FONTH/3,
-        conheight = min(fullconsole ? ((h*fullconsize/100)/FONTH)*FONTH : FONTH*consize, h - 2*(conpad + conoff)),
-        conwidth = w - 2*(conpad + conoff) - (fullconsole ? 0 : game::clipconsole(w, h));
-    
-    extern void consolebox(int x1, int y1, int x2, int y2);
-    if(fullconsole) consolebox(conpad, conpad, conwidth+conpad+2*conoff, conheight+conpad+2*conoff);
-    
-    int y = drawconlines(conskip, fullconsole ? 0 : confade, conwidth, conheight, conpad+conoff, fullconsole ? fullconfilter : confilter);
-    if(!fullconsole && (miniconsize && miniconwidth))
-        drawconlines(miniconskip, miniconfade, (miniconwidth*(w - 2*(conpad + conoff)))/100, min(FONTH*miniconsize, abovehud - y), conpad+conoff, miniconfilter, abovehud, -1);
-    return fullconsole ? conheight + 2*(conpad + conoff) : y;
+    return fullconsole ? (2*CONSPAD+conheight+2*FONTH/3) : (y+CONSPAD+FONTH/3);
 }
 
 // keymap is defined externally in keymap.cfg
@@ -206,62 +190,50 @@ struct keym
     char *actions[NUMACTIONS];
     bool pressed;
 
-    keym() : code(-1), name(NULL), pressed(false) { loopi(NUMACTIONS) actions[i] = newstring(""); }
+    keym() : code(-1), name(NULL), pressed(false) { memset(actions, 0, sizeof(actions)); }
     ~keym() { DELETEA(name); loopi(NUMACTIONS) DELETEA(actions[i]); }
 };
 
-hashtable<int, keym> keyms(128);
+vector<keym> keyms;                                 
 
-void keymap(int *code, char *key)
+void keymap(char *code, char *key)
 {
     if(overrideidents) { conoutf(CON_ERROR, "cannot override keymap %s", code); return; }
-    keym &km = keyms[*code];
-    km.code = *code;
-    DELETEA(km.name);
+    keym &km = keyms.add();
+    km.code = atoi(code);
     km.name = newstring(key);
+    loopi(keym::NUMACTIONS) km.actions[i] = newstring("");
 }
     
-COMMAND(keymap, "is");
+COMMAND(keymap, "ss");
 
 keym *keypressed = NULL;
 char *keyaction = NULL;
 
-const char *getkeyname(int code)
-{
-    keym *km = keyms.access(code);
-    return km ? km->name : NULL;
-}
-
-void searchbinds(char *action, int type)
-{
-    vector<char> names;
-    enumerate(keyms, keym, km,
-    {
-        if(!strcmp(km.actions[type], action))
-        {
-            if(names.length()) names.add(' ');
-            names.put(km.name, strlen(km.name));
-        }
-    });
-    names.add('\0');
-    result(names.getbuf());
-}
-
 keym *findbind(char *key)
 {
-    enumerate(keyms, keym, km,
-    {
-        if(!strcasecmp(km.name, key)) return &km;
-    });
+    loopv(keyms) if(!strcasecmp(keyms[i].name, key)) return &keyms[i];
     return NULL;
 }   
     
-void getbind(char *key, int type)
+void getbind(char *key)
 {
     keym *km = findbind(key);
-    result(km ? km->actions[type] : "");
+    result(km ? km->actions[keym::ACTION_DEFAULT] : "");
 }   
 
+void getspecbind(char *key)
+{
+    keym *km = findbind(key);
+    result(km ? km->actions[keym::ACTION_SPECTATOR] : "");
+}
+
+void geteditbind(char *key)
+{
+    keym *km = findbind(key);
+    result(km ? km->actions[keym::ACTION_EDITING] : "");
+}  
+
 void bindkey(char *key, char *action, int state, const char *cmd)
 {
     if(overrideidents) { conoutf(CON_ERROR, "cannot override %s \"%s\"", cmd, key); return; }
@@ -269,38 +241,42 @@ void bindkey(char *key, char *action, int state, const char *cmd)
     if(!km) { conoutf(CON_ERROR, "unknown key \"%s\"", key); return; }
     char *&binding = km->actions[state];
     if(!keypressed || keyaction!=binding) delete[] binding;
-    // trim white-space to make searchbinds more reliable
-    while(isspace(*action)) action++;
-    int len = strlen(action);
-    while(len>0 && isspace(action[len-1])) len--;
-    binding = newstring(action, len);
+    binding = newstring(action);
 }
 
-ICOMMAND(bind,     "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_DEFAULT, "bind"));
-ICOMMAND(specbind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_SPECTATOR, "specbind"));
-ICOMMAND(editbind, "ss", (char *key, char *action), bindkey(key, action, keym::ACTION_EDITING, "editbind"));
-ICOMMAND(getbind,     "s", (char *key), getbind(key, keym::ACTION_DEFAULT));
-ICOMMAND(getspecbind, "s", (char *key), getbind(key, keym::ACTION_SPECTATOR));
-ICOMMAND(geteditbind, "s", (char *key), getbind(key, keym::ACTION_EDITING));
-ICOMMAND(searchbinds,     "s", (char *action), searchbinds(action, keym::ACTION_DEFAULT));
-ICOMMAND(searchspecbinds, "s", (char *action), searchbinds(action, keym::ACTION_SPECTATOR));
-ICOMMAND(searcheditbinds, "s", (char *action), searchbinds(action, keym::ACTION_EDITING));
-
-void inputcommand(char *init, char *action = NULL, char *prompt = NULL) // turns input to the command line on or off
-{
-    commandmillis = init ? totalmillis : -1;
-    SDL_EnableUNICODE(commandmillis >= 0 ? 1 : 0);
-    if(!editmode) keyrepeat(commandmillis >= 0);
-    copystring(commandbuf, init ? init : "");
+void bindnorm(char *key, char *action) { bindkey(key, action, keym::ACTION_DEFAULT, "bind"); }
+void bindspec(char *key, char *action) { bindkey(key, action, keym::ACTION_SPECTATOR, "specbind"); }
+void bindedit(char *key, char *action) { bindkey(key, action, keym::ACTION_EDITING, "editbind"); }
+
+COMMANDN(bind,     bindnorm, "ss");
+COMMANDN(specbind, bindspec, "ss");
+COMMANDN(editbind, bindedit, "ss");
+COMMAND(getbind, "s");
+COMMAND(getspecbind, "s");
+COMMAND(geteditbind, "s");
+
+void saycommand(char *init)                         // turns input to the command line on or off
+{
+    SDL_EnableUNICODE(saycommandon = (init!=NULL));
+    if(!editmode) keyrepeat(saycommandon);
+    s_strcpy(commandbuf, init ? init : "");
     DELETEA(commandaction);
     DELETEA(commandprompt);
     commandpos = -1;
-    if(action && action[0]) commandaction = newstring(action);
-    if(prompt && prompt[0]) commandprompt = newstring(prompt);
 }
 
-ICOMMAND(saycommand, "C", (char *init), inputcommand(init));
+void inputcommand(char *init, char *action, char *prompt)
+{
+    saycommand(init);
+    if(action[0]) commandaction = newstring(action);
+    if(prompt[0]) commandprompt = newstring(prompt);
+}
+
+void mapmsg(char *s) { s_strncpy(hdr.maptitle, s, 128); }
+
+COMMAND(saycommand, "C");
 COMMAND(inputcommand, "sss");
+COMMAND(mapmsg, "s");
 
 #if !defined(WIN32) && !defined(__APPLE__)
 #include <X11/Xlib.h>
@@ -313,7 +289,7 @@ void pasteconsole()
     if(!IsClipboardFormatAvailable(CF_TEXT)) return; 
     if(!OpenClipboard(NULL)) return;
     char *cb = (char *)GlobalLock(GetClipboardData(CF_TEXT));
-    concatstring(commandbuf, cb);
+    s_strcat(commandbuf, cb);
     GlobalUnlock(cb);
     CloseClipboard();
     #elif defined(__APPLE__)
@@ -358,7 +334,7 @@ struct hline
 
     void restore()
     {
-        copystring(commandbuf, buf);
+        s_strcpy(commandbuf, buf);
         if(commandpos >= (int)strlen(commandbuf)) commandpos = -1;
         DELETEA(commandaction);
         DELETEA(commandprompt);
@@ -388,7 +364,7 @@ struct hline
             execute(action);
         }
         else if(buf[0]=='/') execute(buf+1);
-        else game::toserver(buf);
+        else cc->toserver(buf);
     }
 };
 vector<hline *> history;
@@ -396,7 +372,7 @@ int histpos = 0;
 
 void history_(int *n)
 {
-    static bool inhistory = false;
+    static bool inhistory = true;
     if(!inhistory && history.inrange(*n))
     {
         inhistory = true;
@@ -444,12 +420,7 @@ void execbind(keym &k, bool isdown)
     }
     if(isdown)
     {
-        int state = keym::ACTION_DEFAULT;
-        if(!mainmenu)
-        {
-            if(editmode) state = keym::ACTION_EDITING;
-            else if(player->state==CS_SPECTATOR) state = keym::ACTION_SPECTATOR;
-        }
+        int state = editmode ? keym::ACTION_EDITING : (player->state==CS_SPECTATOR ? keym::ACTION_SPECTATOR : keym::ACTION_DEFAULT);
         char *&action = k.actions[state][0] ? k.actions[state] : k.actions[keym::ACTION_DEFAULT];
         keyaction = action;
         keypressed = &k;
@@ -565,13 +536,13 @@ void consolekey(int code, bool isdown, int cooked)
                 else h = history.last();
             }
             histpos = history.length();
-            inputcommand(NULL);
+            saycommand(NULL);
             if(h) h->run();
         }
         else if(code==SDLK_ESCAPE)
         {
             histpos = history.length();
-            inputcommand(NULL);
+            saycommand(NULL);
         }
     }
 }
@@ -580,38 +551,27 @@ extern bool menukey(int code, bool isdown, int cooked);
 
 void keypress(int code, bool isdown, int cooked)
 {
-    keym *haskey = keyms.access(code);
+    keym *haskey = NULL;
+    loopv(keyms) if(keyms[i].code==code) { haskey = &keyms[i]; break; }        
     if(haskey && haskey->pressed) execbind(*haskey, isdown); // allow pressed keys to release
     else if(!menukey(code, isdown, cooked)) // 3D GUI mouse button intercept   
     {
-        if(commandmillis >= 0) consolekey(code, isdown, cooked);
+        if(saycommandon) consolekey(code, isdown, cooked);
         else if(haskey) execbind(*haskey, isdown);
     }
 }
 
 void clear_console()
 {
-    keyms.clear();
+    keyms.setsize(0);
 }
 
-static int sortbinds(keym **x, keym **y)
+void writebinds(FILE *f)
 {
-    return strcmp((*x)->name, (*y)->name);
-}
-
-void writebinds(stream *f)
-{
-    static const char *cmds[3] = { "bind", "specbind", "editbind" };
-    vector<keym *> binds;
-    enumerate(keyms, keym, km, binds.add(&km));
-    binds.sort(sortbinds);
-    loopj(3)
+    loopv(keyms) loopj(3)
     {
-        loopv(binds)
-        {
-            keym &km = *binds[i];
-            if(*km.actions[j]) f->printf("%s \"%s\" [%s]\n", cmds[j], km.name, km.actions[j]);
-        }
+        static const char *cmds[3] = { "bind", "specbind", "editbind" };
+        if(*keyms[i].actions[j]) fprintf(f, "%s \"%s\" [%s]\n", cmds[j], keyms[i].name, keyms[i].actions[j]);
     }
 }
 
@@ -633,22 +593,9 @@ struct filesval
     int type;
     char *dir, *ext;
     vector<char *> files;
-    int millis;
     
-    filesval(int type, const char *dir, const char *ext) : type(type), dir(newstring(dir)), ext(ext && ext[0] ? newstring(ext) : NULL), millis(-1) {}
-    ~filesval() { DELETEA(dir); DELETEA(ext); files.deletecontentsa(); }
-
-    static int comparefiles(char **x, char **y) { return strcmp(*x, *y); }
-
-    void update()
-    {
-        if(type!=FILES_DIR || millis >= commandmillis) return;
-        files.deletecontentsa();        
-        listfiles(dir, ext, files);
-        files.sort(comparefiles); 
-        loopv(files) if(i && !strcmp(files[i], files[i-1])) delete[] files.remove(i--);
-        millis = totalmillis;
-    }
+    filesval(int type, const char *dir, const char *ext) : type(type), dir(newstring(dir)), ext(ext && ext[0] ? newstring(ext) : NULL) {}
+    ~filesval() { DELETEA(dir); DELETEA(ext); loopv(files) DELETEA(files[i]); files.setsize(0); }
 };
 
 static inline bool htcmp(const fileskey &x, const fileskey &y)
@@ -725,9 +672,9 @@ void complete(char *s)
     if(*s!='/')
     {
         string t;
-        copystring(t, s);
-        copystring(s, "/");
-        concatstring(s, t);
+        s_strcpy(t, s);
+        s_strcpy(s, "/");
+        s_strcat(s, t);
     }
     if(!s[1]) return;
     if(!completesize) { completesize = (int)strlen(s)-1; lastcomplete[0] = '\0'; }
@@ -739,7 +686,7 @@ void complete(char *s)
         if(end)
         {
             string command;
-            copystring(command, s+1, min(size_t(end-s), sizeof(command)));
+            s_strncpy(command, s+1, min(size_t(end-s), sizeof(command)));
             filesval **hasfiles = completions.access(command);
             if(hasfiles) f = *hasfiles;
         }
@@ -747,13 +694,13 @@ void complete(char *s)
 
     const char *nextcomplete = NULL;
     string prefix;
-    copystring(prefix, "/");
+    s_strcpy(prefix, "/");
     if(f) // complete using filenames
     {
         int commandsize = strchr(s, ' ')+1-s;
-        copystring(prefix, s, min(size_t(commandsize+1), sizeof(prefix)));
-        f->update();
-        loopv(f->files)
+        s_strncpy(prefix, s, min(size_t(commandsize+1), sizeof(prefix)));
+        if(f->type==FILES_DIR && f->files.empty()) listfiles(f->dir, f->ext, f->files);
+        loopi(f->files.length())
         {
             if(strncmp(f->files[i], s+commandsize, completesize+1-commandsize)==0 &&
                strcmp(f->files[i], lastcomplete) > 0 && (!nextcomplete || strcmp(f->files[i], nextcomplete) < 0))
@@ -762,6 +709,7 @@ void complete(char *s)
     }
     else // complete using command names
     {
+        extern hashtable<const char *, ident> *idents;
         enumerate(*idents, ident, id,
             if(strncmp(id.name, s+1, completesize)==0 &&
                strcmp(id.name, lastcomplete) > 0 && (!nextcomplete || strcmp(id.name, nextcomplete) < 0))
@@ -770,29 +718,19 @@ void complete(char *s)
     }
     if(nextcomplete)
     {
-        copystring(s, prefix);
-        concatstring(s, nextcomplete);
-        copystring(lastcomplete, nextcomplete);
+        s_strcpy(s, prefix);
+        s_strcat(s, nextcomplete);
+        s_strcpy(lastcomplete, nextcomplete);
     }
     else lastcomplete[0] = '\0';
 }
 
-static int sortcompletions(char **x, char **y)
-{
-    return strcmp(*x, *y);
-}
-
-void writecompletions(stream *f)
+void writecompletions(FILE *f)
 {
-    vector<char *> cmds;
-    enumeratekt(completions, char *, k, filesval *, v, { if(v) cmds.add(k); });
-    cmds.sort(sortcompletions);
-    loopv(cmds)
-    {
-        char *k = cmds[i];
-        filesval *v = completions[k];
-        if(v->type==FILES_LIST) f->printf("listcomplete \"%s\" [%s]\n", k, v->dir);
-        else f->printf("complete \"%s\" \"%s\" \"%s\"\n", k, v->dir, v->ext ? v->ext : "*");
-    }
+    enumeratekt(completions, char *, k, filesval *, v,
+        if(!v) continue;
+        if(v->type==FILES_LIST) fprintf(f, "listcomplete \"%s\" [%s]\n", k, v->dir);
+        else fprintf(f, "complete \"%s\" \"%s\" \"%s\"\n", k, v->dir, v->ext ? v->ext : "*");
+    );
 }
 
diff --git a/engine/cubeloader.cpp b/engine/cubeloader.cpp
index b7a1504..4480724 100644
--- a/engine/cubeloader.cpp
+++ b/engine/cubeloader.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 VAR(importcuberemip, 0, 1024, 2048);
@@ -62,37 +63,28 @@ struct cubeloader
         if(ce.type>=ET_PARTICLES) ce.type++; 
         if(ce.type>=ET_SOUND) ce.type++;
         if(ce.type>=ET_SPOTLIGHT) ce.type++;
-        extentity &e = *entities::newentity();
-        entities::getents().add(&e);
+        extentity &e = *et->newentity();
+        et->getents().add(&e);
         e.type = ce.type;
         e.spawned = false;
         e.inoctanode = false;
-        e.o = vec(ce.x*4+worldsize/4, ce.y*4+worldsize/4, ce.z*4+worldsize/2);
+        e.o = vec(ce.x*4+hdr.worldsize/4, ce.y*4+hdr.worldsize/4, (ce.z+ce.attr3)*4+hdr.worldsize/2);
         e.light.color = vec(1, 1, 1);
         e.light.dir = vec(0, 0, 1);
         e.attr1 = ce.attr1;
         e.attr2 = ce.attr2;
-        switch(e.type)
+        if(e.type == ET_MAPMODEL) e.attr3 = e.attr4 = 0;
+        else
         {
-            case ET_MAPMODEL:
-                e.o.z += ce.attr3*4;
-                e.attr3 = e.attr4 = 0;
-                break;
-            case ET_LIGHT:
-                e.attr1 *= 4;
-                if(!ce.attr3 && !ce.attr4) { e.attr3 = e.attr4 = e.attr2; break; }
-                // fall through
-            default:
-                e.attr3 = ce.attr3;
-                e.attr4 = ce.attr4;
-                break;
+            e.attr3 = ce.attr3;
+            e.attr4 = ce.attr4;
         }
         e.attr5 = 0;
     }
 
     cube &getcube(int x, int y, int z)
     {
-        return lookupcube(x*4+worldsize/4, y*4+worldsize/4, z*4+worldsize/2, 4);
+        return lookupcube(x*4+hdr.worldsize/4, y*4+hdr.worldsize/4, z*4+hdr.worldsize/2, 4);
     }
 
     int neighbours(c_sqr &t)
@@ -234,9 +226,9 @@ struct cubeloader
             }
             if((progress++&0x7F)==0)
             {
-                float bar = float((y1-y0+2)*(x-x0+1) + y-y0+1) / float((y1-y0+2)*(x1-x0+2));
-                defformatstring(text)("creating cubes... %d%%", int(bar*100));
-                renderprogress(bar, text);
+                float bar2 = float((y1-y0+2)*(x-x0+1) + y-y0+1) / float((y1-y0+2)*(x1-x0+2));
+                s_sprintfd(text2)("%d%%", int(bar2*100));
+                show_out_of_renderloop_progress(0, "creating cubes...", bar2, text2);
             }
         }
     }
@@ -245,13 +237,13 @@ struct cubeloader
     {
         int loadingstart = SDL_GetTicks();
         string pakname, cgzname;
-        formatstring(pakname)("cube/%s", mname);
-        formatstring(cgzname)("packages/%s.cgz", pakname);
-        stream *f = opengzfile(path(cgzname), "rb");
+        s_sprintf(pakname)("cube/%s", mname);
+        s_sprintf(cgzname)("packages/%s.cgz", pakname);
+        gzFile f = opengzfile(path(cgzname), "rb9");
         if(!f) { conoutf(CON_ERROR, "could not read cube map %s", cgzname); return; }
         c_header hdr;
-        f->read(&hdr, sizeof(c_header)-sizeof(int)*16);
-        lilswap(&hdr.version, 4);
+        gzread(f, &hdr, sizeof(c_header)-sizeof(int)*16);
+        endianswap(&hdr.version, sizeof(int), 4);
         bool mod = false;
         if(strncmp(hdr.head, "CUBE", 4)) 
         { 
@@ -264,17 +256,16 @@ struct cubeloader
             }
         }
         else if(hdr.version>5) mod = true;
-        if(hdr.version>5 && !mod) { conoutf(CON_ERROR, "map %s requires a newer version of the Cube 1 importer", cgzname); gzclose(f); return; }
-        if(!haslocalclients()) game::forceedit("");
-        emptymap(12, true, NULL);
+        if(hdr.version>5 && !mod) { conoutf(CON_ERROR, "map %s requires a newer version of the cube 1 importer", cgzname); gzclose(f); return; }
+        emptymap(12, true);
         freeocta(worldroot);
         worldroot = newcubes(F_SOLID);
-        defformatstring(cs)("importing %s", cgzname);
-        renderbackground(cs);
+        s_sprintfd(cs)("importing %s", cgzname);
+        computescreen(cs);
         if(hdr.version>=4)
         {
-            f->read(&hdr.waterlevel, sizeof(int)*16);
-            lilswap(&hdr.waterlevel, 16);
+            gzread(f, &hdr.waterlevel, sizeof(int)*16);
+            endianswap(&hdr.waterlevel, sizeof(int), 16);
         }
         else
         {
@@ -284,9 +275,9 @@ struct cubeloader
         else loopi(hdr.numents)
         {
             c_persistent_entity e;
-            f->read(&e, sizeof(c_persistent_entity));
-            lilswap(&e.x, 4);
-            if(i < MAXENTS) create_ent(e);
+            gzread(f, &e, sizeof(c_persistent_entity));
+            endianswap(&e, sizeof(short), 4);
+            create_ent(e);
         }
         ssize = 1<<hdr.sfactor;
         world = new c_sqr[ssize*ssize];
@@ -294,12 +285,12 @@ struct cubeloader
         loopk(ssize*ssize)
         {
             c_sqr *s = &world[k];
-            int type = f->getchar();
+            int type = gzgetc(f);
             switch(type)
             {
                 case 255:
                 {
-                    int n = f->getchar();
+                    int n = gzgetc(f);
                     for(int i = 0; i<n; i++, k++) memcpy(&world[k], t, sizeof(c_sqr));
                     k--;
                     break;
@@ -307,16 +298,16 @@ struct cubeloader
                 case 254: // only in MAPVERSION<=2
                 {
                     memcpy(s, t, sizeof(c_sqr));
-                    f->getchar();
-                    f->getchar();
+                    gzgetc(f);
+                    gzgetc(f);
                     break;
                 }
                 case C_SOLID:
                 {
                     s->type = C_SOLID;
-                    s->wtex = f->getchar();
-                    s->vdelta = f->getchar();
-                    if(hdr.version<=2) { f->getchar(); f->getchar(); }
+                    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;
@@ -328,41 +319,39 @@ struct cubeloader
                 {
                     if(type<0 || type>=C_MAXTYPE)
                     {
-                        defformatstring(t)("%d @ %d", type, k);
+                        s_sprintfd(t)("%d @ %d", type, k);
                         fatal("while reading map: type out of range: ", t);
                     }
                     s->type = type;
-                    s->floor = f->getchar();
-                    s->ceil = f->getchar();
+                    s->floor = gzgetc(f);
+                    s->ceil = gzgetc(f);
                     if(s->floor>=s->ceil) s->floor = s->ceil-1;  // for pre 12_13
-                    s->wtex = f->getchar();
-                    s->ftex = f->getchar();
-                    s->ctex = f->getchar();
-                    if(hdr.version<=2) { f->getchar(); f->getchar(); }
-                    s->vdelta = f->getchar();
-                    s->utex = (hdr.version>=2) ? f->getchar() : s->wtex;
-                    if(hdr.version>=5) f->getchar();
+                    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;
+                    if(hdr.version>=5) gzgetc(f);
                     s->type = type;
                 }
             }
             t = s;
         }
-        delete f;
+        gzclose(f);
 
         string cfgname;
-        formatstring(cfgname)("packages/cube/%s.cfg", mname);
-        overrideidents = true;
-        execfile("packages/cube/package.cfg");
-        execfile(path(cfgname));
-        overrideidents = false;
+        s_sprintf(cfgname)("packages/cube/%s.cfg", mname);
+        exec("packages/cube/package.cfg");
+        exec(path(cfgname));
         create_cubes();
         mpremip(true);
         clearlights();
         allchanged();
-        loopv(entities::getents()) if(entities::getents()[i]->type!=ET_LIGHT) dropenttofloor(entities::getents()[i]);
+        loopv(et->getents()) if(et->getents()[i]->type!=ET_LIGHT) dropenttofloor(et->getents()[i]);
         entitiesinoctanodes();
         conoutf("read cube map %s (%.1f seconds)", cgzname, (SDL_GetTicks()-loadingstart)/1000.0f);
-        startmap(NULL);
+        startmap(pakname);
     }
 };
 
diff --git a/engine/decal.cpp b/engine/decal.cpp
index 117af60..a6275a6 100644
--- a/engine/decal.cpp
+++ b/engine/decal.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 struct decalvert
@@ -21,8 +22,7 @@ enum
     DF_ROTATE     = 1<<1,
     DF_INVMOD     = 1<<2,
     DF_OVERBRIGHT = 1<<3,
-    DF_ADD        = 1<<4,
-    DF_SATURATE   = 1<<5
+    DF_ADD        = 1<<4
 };
 
 VARFP(maxdecaltris, 1, 1024, 16384, initdecals());
@@ -63,7 +63,7 @@ struct decalrenderer
         }
         decals = new decalinfo[tris];
         maxdecals = tris;
-        tex = textureload(texname, 3);
+        tex = textureload(texname);
         maxverts = tris*3 + 3;
         availverts = maxverts - 3; 
         verts = new decalvert[maxverts];
@@ -243,7 +243,9 @@ struct decalrenderer
             if(renderpath!=R_FIXEDFUNCTION)
             {
                 glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); 
-                SETSHADER(overbrightdecal);
+                static Shader *overbrightdecalshader = NULL;
+                if(!overbrightdecalshader) overbrightdecalshader = lookupshaderbyname("overbrightdecal");
+                overbrightdecalshader->set();
             }
             else if(hasTE)
             {
@@ -257,13 +259,7 @@ struct decalrenderer
             if(flags&DF_INVMOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
             else if(flags&DF_ADD) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
             else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-            if(flags&DF_SATURATE)
-            {
-                if(renderpath!=R_FIXEDFUNCTION) SETSHADER(saturatedecal);
-                else if(hasTE) setuptmu(0, "C * T x 2");
-            }
-            else foggedshader->set();
+            foggedshader->set();
         }
 
         glBindTexture(GL_TEXTURE_2D, tex->id);
@@ -282,7 +278,7 @@ struct decalrenderer
         xtravertsva += count;
 
         if(flags&(DF_ADD|DF_INVMOD|DF_OVERBRIGHT)) glFogfv(GL_FOG_COLOR, oldfogc);
-        if(flags&(DF_OVERBRIGHT|DF_SATURATE) && hasTE) resettmu(0);
+        if(flags&DF_OVERBRIGHT && hasTE) resettmu(0);
     }
 
     decalinfo &newdecal()
@@ -346,7 +342,7 @@ struct decalrenderer
         }
 
         ushort dstart = endvert;
-        gendecaltris(worldroot, ivec(0, 0, 0), worldsize>>1);
+        gendecaltris(worldroot, ivec(0, 0, 0), hdr.worldsize>>1);
         if(dbgdec)
         {
             int nverts = endvert < dstart ? endvert + maxverts - dstart : endvert - dstart;
@@ -510,13 +506,13 @@ void cleardecals()
 
 VARNP(decals, showdecals, 0, 1, 1);
 
-void renderdecals(bool mainpass)
+void renderdecals(int time)
 {
     bool rendered = false;
     loopi(sizeof(decals)/sizeof(decals[0]))
     {
         decalrenderer &d = decals[i];
-        if(mainpass)
+        if(time)
         {
             d.clearfadeddecals();
             d.fadeindecals();
diff --git a/engine/depthfx.h b/engine/depthfx.h
deleted file mode 100644
index ec1203f..0000000
--- a/engine/depthfx.h
+++ /dev/null
@@ -1,208 +0,0 @@
-// eye space depth texture for soft particles, done at low res then blurred to prevent ugly jaggies
-VARP(depthfxfpscale, 1, 1<<12, 1<<16);
-VARP(depthfxscale, 1, 1<<6, 1<<8);
-VARP(depthfxblend, 1, 16, 64);
-VARP(depthfxpartblend, 1, 8, 64);
-VAR(depthfxmargin, 0, 16, 64);
-VAR(depthfxbias, 0, 1, 64);
-
-extern void cleanupdepthfx();
-VARFP(fpdepthfx, 0, 0, 1, cleanupdepthfx());
-VARFP(depthfxprecision, 0, 0, 1, cleanupdepthfx());
-VARP(depthfxemuprecision, 0, 1, 1);
-VARFP(depthfxsize, 6, 7, 12, cleanupdepthfx());
-VARP(depthfx, 0, 1, 1);
-VARP(depthfxparts, 0, 1, 1);
-VARFP(depthfxrect, 0, 0, 1, cleanupdepthfx());
-VARFP(depthfxfilter, 0, 1, 1, cleanupdepthfx());
-VARP(blurdepthfx, 0, 1, 7);
-VARP(blurdepthfxsigma, 1, 50, 200);
-VAR(depthfxscissor, 0, 2, 2);
-VAR(debugdepthfx, 0, 0, 1);
-
-#define MAXDFXRANGES 4
-
-void *depthfxowners[MAXDFXRANGES];
-float depthfxranges[MAXDFXRANGES];
-int numdepthfxranges = 0;
-vec depthfxmin(1e16f, 1e16f, 1e16f), depthfxmax(1e16f, 1e16f, 1e16f);
-
-static struct depthfxtexture : rendertarget
-{
-    const GLenum *colorformats() const
-    {
-        static const GLenum colorfmts[] = { GL_FLOAT_RG16_NV, GL_RGB16F_ARB, GL_RGB16, GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE };
-        return &colorfmts[hasTF && hasFBO ? (fpdepthfx ? (hasNVFB && texrect() && !filter() ? 0 : 1) : (depthfxprecision ? 2 : 3)) : 3];
-    }
-
-    float eyedepth(const vec &p) const
-    {
-        return max(-mvmatrix.transformz(p), 0.0f);
-    }
-
-    void addscissorvert(const vec &v, float &sx1, float &sy1, float &sx2, float &sy2)
-    {
-        float w = mvpmatrix.transformw(v),
-              x = mvpmatrix.transformx(v) / w,
-              y = mvpmatrix.transformy(v) / w;
-        sx1 = min(sx1, x);
-        sy1 = min(sy1, y);
-        sx2 = max(sx2, x);
-        sy2 = max(sy2, y);
-    }
-
-    bool addscissorbox(const vec &center, float size)
-    {
-        float sx1, sy1, sx2, sy2;
-        calcspherescissor(center, size, sx1, sy1, sx2, sy2);
-        return addblurtiles(sx1, sy1, sx2, sy2);
-    }
-
-    bool addscissorbox(const vec &bbmin, const vec &bbmax)
-    {
-        float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
-        loopi(8)
-        {
-            vec v(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, i&4 ? bbmax.z : bbmin.z);
-            addscissorvert(v, sx1, sy1, sx2, sy2);
-        }
-        return addblurtiles(sx1, sy1, sx2, sy2);
-    }
-
-    bool screenview() const { return depthfxrect!=0; }
-    bool texrect() const { return depthfxrect && hasTR; }
-    bool filter() const { return depthfxfilter!=0; }
-    bool highprecision() const { return colorfmt==GL_FLOAT_RG16_NV || colorfmt==GL_RGB16F_ARB || colorfmt==GL_RGB16; }
-    bool emulatehighprecision() const { return depthfxemuprecision && !depthfxfilter; }
-
-    bool shouldrender()
-    {
-        extern void finddepthfxranges();
-        finddepthfxranges();
-        return (numdepthfxranges && scissorx1 < scissorx2 && scissory1 < scissory2) || debugdepthfx;
-    }
-
-    bool dorender()
-    {
-        glClearColor(1, 1, 1, 1);
-        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-
-        depthfxing = true;
-        refracting = -1;
-
-        extern void renderdepthobstacles(const vec &bbmin, const vec &bbmax, float scale, float *ranges, int numranges);
-        float scale = depthfxscale;
-        float *ranges = depthfxranges;
-        int numranges = numdepthfxranges;
-        if(highprecision())
-        {
-            scale = depthfxfpscale;
-            ranges = NULL;
-            numranges = 0;
-        }
-        else if(emulatehighprecision())
-        {
-            scale = depthfxfpscale;
-            ranges = NULL;
-            numranges = -3;
-        }
-        renderdepthobstacles(depthfxmin, depthfxmax, scale, ranges, numranges);
-
-        refracting = 0;
-        depthfxing = false;
-
-        return numdepthfxranges > 0;
-    }
-
-    void dodebug(int w, int h)
-    {
-        if(numdepthfxranges > 0)
-        {
-            glColor3f(0, 1, 0);
-            debugscissor(w, h, true);
-            glColor3f(0, 0, 1);
-            debugblurtiles(w, h, true);
-            glColor3f(1, 1, 1);
-        }
-    }
-} depthfxtex;
-
-void cleanupdepthfx()
-{
-    depthfxtex.cleanup(true);
-}
-
-void viewdepthfxtex()
-{
-    if(!depthfx) return;
-    depthfxtex.debug();
-}
-
-bool depthfxing = false;
-
-bool binddepthfxtex()
-{
-    if(renderpath!=R_FIXEDFUNCTION && !reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0)        
-    {
-        glActiveTexture_(GL_TEXTURE2_ARB);
-        glBindTexture(depthfxtex.target, depthfxtex.rendertex);
-        glActiveTexture_(GL_TEXTURE0_ARB);
-
-        if(depthfxtex.target==GL_TEXTURE_RECTANGLE_ARB)
-            setenvparamf("depthfxview", SHPARAM_VERTEX, 6, 0.5f*depthfxtex.vieww, 0.5f*depthfxtex.viewh);
-        else
-            setenvparamf("depthfxview", SHPARAM_VERTEX, 6, 0.5f*float(depthfxtex.vieww)/depthfxtex.texw, 0.5f*float(depthfxtex.viewh)/depthfxtex.texh);
-        return true;
-    }
-    return false;
-}
-
-void binddepthfxparams(float blend, float minblend = 0, bool allow = true, void *owner = NULL)
-{
-    if(renderpath!=R_FIXEDFUNCTION && !reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0)
-    {
-        float scale = 0, offset = -1, texscale = 0;
-        if(!depthfxtex.highprecision())
-        {
-            float select[4] = { 0, 0, 0, 0 };
-            if(!depthfxtex.emulatehighprecision())
-            {
-                loopi(numdepthfxranges) if(depthfxowners[i]==owner)
-                {
-                    select[i] = float(depthfxscale)/blend;
-                    scale = 1.0f/blend;
-                    offset = -float(depthfxranges[i] - depthfxbias)/blend;
-                    break;
-                }
-            }
-            else if(allow)
-            {
-                select[0] = float(depthfxfpscale)/blend;
-                select[1] = select[0]/256;
-                select[2] = select[1]/256;
-                scale = 1.0f/blend;
-                offset = 0;
-            }
-            setlocalparamfv("depthfxselect", SHPARAM_PIXEL, 6, select);
-        }
-        else if(allow)
-        {
-            scale = 1.0f/blend;
-            offset = 0;
-            texscale = float(depthfxfpscale)/blend;
-        }
-        setlocalparamf("depthfxparams", SHPARAM_VERTEX, 5, scale, offset, texscale, minblend);
-        setlocalparamf("depthfxparams", SHPARAM_PIXEL, 5, scale, offset, texscale, minblend);
-    }
-}
-
-void drawdepthfxtex()
-{
-    if(!depthfx || renderpath==R_FIXEDFUNCTION) return;
-
-    // Apple/ATI bug - fixed-function fog state can force software fallback even when fragment program is enabled
-    glDisable(GL_FOG);
-    depthfxtex.render(1<<depthfxsize, 1<<depthfxsize, blurdepthfx, blurdepthfxsigma/100.0f);
-    glEnable(GL_FOG);
-}
-
diff --git a/engine/dynlight.cpp b/engine/dynlight.cpp
index b96dbe4..17e0e52 100644
--- a/engine/dynlight.cpp
+++ b/engine/dynlight.cpp
@@ -1,6 +1,6 @@
+#include "pch.h"
 #include "engine.h"
 
-VARP(ffdynlights, 0, min(5, DYNLIGHTMASK), DYNLIGHTMASK);
 VARP(maxdynlights, 0, min(3, MAXDYNLIGHTS), MAXDYNLIGHTS);
 VARP(dynlightdist, 0, 1024, 10000);
 
@@ -10,7 +10,6 @@ struct dynlight
     float radius, initradius, curradius, dist;
     vec color, initcolor, curcolor;
     int fade, peak, expire, flags;
-    physent *owner;
 
     void calcradius()
     {
@@ -19,7 +18,7 @@ struct dynlight
             int remaining = expire - lastmillis;
             if(flags&DL_EXPAND)
                 curradius = initradius + (radius - initradius) * (1.0f - remaining/float(fade + peak));
-            else if(!(flags&DL_FLASH) && remaining > fade)
+            else if(remaining > fade)
                 curradius = initradius + (radius - initradius) * (1.0f - float(remaining - fade)/peak);
             else if(flags&DL_SHRINK)
                 curradius = (radius*remaining)/fade;
@@ -39,7 +38,7 @@ struct dynlight
         }
 
         float intensity = 1.0f;
-        if(fade)
+        if(!(flags&DL_FLASH) && fade)
         {
             int fading = expire - lastmillis;
             if(fading < fade) intensity = float(fading)/fade;
@@ -53,10 +52,9 @@ struct dynlight
 vector<dynlight> dynlights;
 vector<dynlight *> closedynlights;
 
-void adddynlight(const vec &o, float radius, const vec &color, int fade, int peak, int flags, float initradius, const vec &initcolor, physent *owner)
+void adddynlight(const vec &o, float radius, const vec &color, int fade, int peak, int flags, float initradius, const vec &initcolor)
 {
-    if(renderpath==R_FIXEDFUNCTION ? !ffdynlights || maxtmus<3 : !maxdynlights) return;
-    if(o.dist(camera1->o) > dynlightdist) return;
+    if(renderpath==R_FIXEDFUNCTION || o.dist(camera1->o) > dynlightdist) return;
 
     int insert = 0, expire = fade + peak + lastmillis;
     loopvrev(dynlights) if(expire>=dynlights[i].expire) { insert = i+1; break; }
@@ -70,7 +68,6 @@ void adddynlight(const vec &o, float radius, const vec &color, int fade, int pea
     d.peak = peak;
     d.expire = expire;
     d.flags = flags;
-    d.owner = owner;
     dynlights.insert(insert, d);
 }
 
@@ -82,20 +79,14 @@ void cleardynlights()
     else if(faded>0) dynlights.remove(0, faded);
 }
 
-void removetrackeddynlights(physent *owner)
-{
-    loopvrev(dynlights) if(owner ? dynlights[i].owner == owner : dynlights[i].owner != NULL) dynlights.remove(i);
-}
-
 void updatedynlights()
 {
     cleardynlights();
-    game::adddynlights();
+    cl->adddynlights();
 
     loopv(dynlights)
     {
         dynlight &d = dynlights[i];
-        if(d.owner) game::dynlighttrack(d.owner, d.o);
         d.calcradius();
         d.calccolor();
     }
@@ -104,7 +95,7 @@ void updatedynlights()
 int finddynlights()
 {
     closedynlights.setsizenodelete(0);
-    if(renderpath==R_FIXEDFUNCTION ? !ffdynlights || maxtmus<3 : !maxdynlights) return 0;
+    if(!maxdynlights) return 0;
     physent e;
     e.type = ENT_CAMERA;
     loopvj(dynlights)
@@ -114,13 +105,9 @@ int finddynlights()
         d.dist = camera1->o.dist(d.o) - d.curradius;
         if(d.dist > dynlightdist || isvisiblesphere(d.curradius, d.o) >= VFC_FOGGED || pvsoccluded(d.o, 2*int(d.curradius+1))) 
             continue;
-        if(reflecting || refracting > 0)
-        {
-            if(d.o.z + d.curradius < reflectz) continue;
-        }
-        else if(refracting < 0 && d.o.z - d.curradius > reflectz) continue;
+
         e.o = d.o;
-        e.radius = e.xradius = e.yradius = e.eyeheight = e.aboveeye = d.curradius;
+        e.radius = e.eyeheight = e.aboveeye = d.curradius;
         if(collide(&e, vec(0, 0, 0), 0, false)) continue;
 
         int insert = 0;
@@ -128,21 +115,9 @@ int finddynlights()
         closedynlights.insert(insert, &d);
         if(closedynlights.length() >= DYNLIGHTMASK) break;
     }
-    if(renderpath==R_FIXEDFUNCTION && closedynlights.length() > ffdynlights)
-        closedynlights.setsizenodelete(ffdynlights);
     return closedynlights.length();
 }
 
-bool getdynlight(int n, vec &o, float &radius, vec &color)
-{
-    if(!closedynlights.inrange(n)) return false;
-    dynlight &d = *closedynlights[n];
-    o = d.o;
-    radius = d.curradius;
-    color = d.curcolor;
-    return true;
-}
-
 void dynlightreaching(const vec &target, vec &color, vec &dir)
 {
     vec dyncolor(0, 0, 0);//, dyndir(0, 0, 0);
@@ -202,8 +177,8 @@ int setdynlights(vtxarray *va, const ivec &vaorigin)
     static string vertexparams[MAXDYNLIGHTS] = { "" }, pixelparams[MAXDYNLIGHTS] = { "" };
     if(!*vertexparams[0]) loopi(MAXDYNLIGHTS)
     {
-        formatstring(vertexparams[i])("dynlight%dpos", i);
-        formatstring(pixelparams[i])("dynlight%dcolor", i);
+        s_sprintf(vertexparams[i])("dynlight%dpos", i);
+        s_sprintf(pixelparams[i])("dynlight%dcolor", i);
     }
 
     int index = 0;
diff --git a/engine/engine.h b/engine/engine.h
index 11608d2..b1dd13d 100644
--- a/engine/engine.h
+++ b/engine/engine.h
@@ -1,9 +1,11 @@
-#ifndef __ENGINE_H__
-#define __ENGINE_H__
-
 #include "cube.h"
+#include "iengine.h"
+#include "igame.h"
 
-#ifndef STANDALONE
+extern igameclient     *cl;
+extern igameserver     *sv;
+extern iclientcom      *cc;
+extern icliententities *et;
 
 #include "world.h"
 #include "octa.h"
@@ -51,25 +53,12 @@ extern PFNGLFRAMEBUFFERTEXTURE2DEXTPROC    glFramebufferTexture2D_;
 extern PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbuffer_;
 extern PFNGLGENERATEMIPMAPEXTPROC          glGenerateMipmap_;
 
-// GL_EXT_framebuffer_blit
-#ifndef GL_EXT_framebuffer_blit
-#define GL_READ_FRAMEBUFFER_EXT           0x8CA8
-#define GL_DRAW_FRAMEBUFFER_EXT           0x8CA9
-#define GL_DRAW_FRAMEBUFFER_BINDING_EXT   0x8CA6
-#define GL_READ_FRAMEBUFFER_BINDING_EXT   0x8CAA
-typedef void (APIENTRYP PFNGLBLITFRAMEBUFFEREXTPROC) (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
-#endif
-extern PFNGLBLITFRAMEBUFFEREXTPROC         glBlitFramebuffer_;
-
 // GL_EXT_draw_range_elements
 extern PFNGLDRAWRANGEELEMENTSEXTPROC glDrawRangeElements_;
 
 // GL_EXT_blend_minmax
 extern PFNGLBLENDEQUATIONEXTPROC glBlendEquation_;
 
-// GL_EXT_blend_color
-extern PFNGLBLENDCOLOREXTPROC glBlendColor_;
-
 // GL_EXT_multi_draw_arrays
 extern PFNGLMULTIDRAWARRAYSEXTPROC   glMultiDrawArrays_;
 extern PFNGLMULTIDRAWELEMENTSEXTPROC glMultiDrawElements_;
@@ -82,21 +71,11 @@ extern PFNGLMULTIDRAWELEMENTSEXTPROC glMultiDrawElements_;
 #define GL_DEPTH24_STENCIL8_EXT 0x88F0
 #endif
 
-// GL_ARB_texture_compression
-extern PFNGLCOMPRESSEDTEXIMAGE3DARBPROC    glCompressedTexImage3D_;
-extern PFNGLCOMPRESSEDTEXIMAGE2DARBPROC    glCompressedTexImage2D_;
-extern PFNGLCOMPRESSEDTEXIMAGE1DARBPROC    glCompressedTexImage1D_;
-extern PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC glCompressedTexSubImage3D_;
-extern PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC glCompressedTexSubImage2D_;
-extern PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC glCompressedTexSubImage1D_;
-extern PFNGLGETCOMPRESSEDTEXIMAGEARBPROC   glGetCompressedTexImage_;
-
 extern dynent *player;
 extern physent *camera1;                // special ent that acts as camera, same object as player1 in FPS mode
 
-extern int worldscale, worldsize;
-extern int mapversion;
-extern char *maptitle;
+extern header hdr;                      // current map header
+extern int worldscale;
 extern vector<ushort> texmru;
 extern int xtraverts, xtravertsva;
 extern int curtexnum;
@@ -104,8 +83,11 @@ extern const ivec cubecoords[8];
 extern const ushort fv[6][4];
 extern const uchar fvmasks[64];
 extern const uchar faceedgesidx[6][4];
-extern bool inbetweenframes, renderedframe;
+extern bool inbetweenframes;
 
+extern int curtime;                     // current frame time
+extern int lastmillis;                  // last time
+extern int totalmillis;                 // total elapsed time
 extern SDL_Surface *screen;
 extern int zpass, glowpass;
 
@@ -134,20 +116,18 @@ struct font
 extern font *curfont;
 
 // texture
-extern int hwtexsize, hwcubetexsize, hwmaxaniso, maxtexsize;
+extern int hwtexsize, hwcubetexsize, hwmaxaniso;
 
 extern Texture *textureload(const char *name, int clamp = 0, bool mipit = true, bool msg = true);
-extern int texalign(void *data, int w, int bpp);
-extern void cleanuptexture(Texture *t);
 extern void loadalphamask(Texture *t);
-extern void loadlayermasks();
-extern Texture *cubemapload(const char *name, bool mipit = true, bool msg = true, bool transient = false);
+extern GLuint cubemapfromsky(int size);
+extern Texture *cubemapload(const char *name, bool mipit = true, bool msg = true);
 extern void drawcubemap(int size, const vec &o, float yaw, float pitch, const cubemapside &side);
-extern Slot &lookupmaterialslot(int slot, bool load = true);
-extern Slot &lookuptexture(int slot, bool load = true);
+extern Slot    &lookuptexture(int tex, bool load = true);
 extern void loadshaders();
-extern void createtexture(int tnum, int w, int h, void *pixels, int clamp, int filter, GLenum component = GL_RGB, GLenum target = GL_TEXTURE_2D, int pw = 0, int ph = 0, int pitch = 0, bool resize = true);
-extern void renderpostfx();
+extern Shader  *lookupshader(int slot);
+extern void createtexture(int tnum, int w, int h, void *pixels, int clamp, bool mipit, GLenum component = GL_RGB, GLenum target = GL_TEXTURE_2D, bool compress = false, bool filter = true);
+extern void renderfullscreenshader(int w, int h);
 extern void initenvmaps();
 extern void genenvmaps();
 extern ushort closestenvmap(const vec &o);
@@ -156,11 +136,10 @@ extern GLuint lookupenvmap(ushort emid);
 extern GLuint lookupenvmap(Slot &slot);
 extern bool reloadtexture(Texture &tex);
 extern bool reloadtexture(const char *name);
-extern void setuptexcompress();
 
 // shadowmap
 
-extern int shadowmap, shadowmapcasters;
+extern int shadowmap;
 extern bool shadowmapping;
 
 extern bool isshadowmapcaster(const vec &o, float rad);
@@ -178,8 +157,8 @@ extern void clearpvs();
 extern bool pvsoccluded(const ivec &bborigin, const ivec &bbsize);
 extern bool waterpvsoccluded(int height);
 extern void setviewcell(const vec &p);
-extern void savepvs(stream *f);
-extern void loadpvs(stream *f, int numpvs);
+extern void savepvs(gzFile f);
+extern void loadpvs(gzFile f);
 extern int getnumviewcells();
 
 static inline bool pvsoccluded(const ivec &bborigin, int size)
@@ -188,30 +167,23 @@ static inline bool pvsoccluded(const ivec &bborigin, int size)
 }
 
 // rendergl
-extern bool hasVBO, hasDRE, hasOQ, hasTR, hasFBO, hasDS, hasTF, hasBE, hasBC, hasCM, hasNP2, hasTC, hasTE, hasMT, hasD3, hasAF, hasVP2, hasVP3, hasPP, hasMDA, hasTE3, hasTE4, hasVP, hasFP, hasGLSL, hasGM, hasNVFB, hasSGIDT, hasSGISH, hasDT, hasSH, hasNVPCF, hasRN, hasPBO, hasFBB;
-extern int hasstencil;
+extern bool hasVBO, hasDRE, hasOQ, hasTR, hasFBO, hasDS, hasTF, hasBE, hasCM, hasNP2, hasTC, hasTE, hasMT, hasD3, hasstencil, hasAF, hasVP2, hasVP3, hasPP, hasMDA, hasTE3, hasTE4, hasVP, hasFP, hasGLSL, hasGM, hasNVFB;
 
 extern bool envmapping, renderedgame;
-extern glmatrixf mvmatrix, projmatrix, mvpmatrix, invmvmatrix, invmvpmatrix;
-extern bvec fogcolor;
+extern GLfloat mvmatrix[16], projmatrix[16], mvpmatrix[16], invmvmatrix[16];
 
 extern void gl_checkextensions();
 extern void gl_init(int w, int h, int bpp, int depth, int fsaa);
 extern void cleangl();
-extern void rendergame(bool mainpass = false);
+extern void rendergame();
 extern void invalidatepostfx();
 extern void gl_drawframe(int w, int h);
-extern void gl_drawmainmenu(int w, int h);
 extern void enablepolygonoffset(GLenum type);
 extern void disablepolygonoffset(GLenum type);
-extern void calcspherescissor(const vec &center, float size, float &sx1, float &sy1, float &sx2, float &sy2);
-extern int pushscissor(float sx1, float sy1, float sx2, float sy2);
-extern void popscissor();
 extern void setfogplane(const plane &p, bool flush = false);
 extern void setfogplane(float scale = 0, float z = 0, bool flush = false, float fadescale = 0, float fadeoffset = 0);
-extern void recomputecamera();
 extern void findorientation();
-extern void writecrosshairs(stream *f);
+extern void writecrosshairs(FILE *f);
 
 // renderextras
 extern void render3dbox(vec &o, float tofloor, float toceil, float xradius, float yradius = 0);
@@ -229,6 +201,7 @@ extern void validatec(cube *c, int size);
 extern bool isvalidcube(cube &c);
 extern cube &lookupcube(int tx, int ty, int tz, int tsize = 0);
 extern cube &neighbourcube(int x, int y, int z, int size, int rsize, int orient);
+extern int lookupmaterial(const vec &o);
 extern void newclipplanes(cube &c);
 extern void freeclipplanes(cube &c);
 extern void forcemip(cube &c);
@@ -246,14 +219,12 @@ extern bool flataxisface(cube &c, int orient);
 extern int genclipplane(cube &c, int i, vec *v, plane *clip);
 extern void genclipplanes(cube &c, int x, int y, int z, int size, clipplanes &p);
 extern bool visibleface(cube &c, int orient, int x, int y, int z, int size, uchar mat = MAT_AIR, uchar nmat = MAT_AIR, uchar matmask = MATF_VOLUME);
-extern int visibletris(cube &c, int orient, int x, int y, int z, int size);
 extern int visibleorient(cube &c, int orient);
 extern bool threeplaneintersect(plane &pl1, plane &pl2, plane &pl3, vec &dest);
-extern void genvectorvert(const ivec &p, cube &c, ivec &v);
 extern void freemergeinfo(cube &c);
 extern void genmergedverts(cube &cu, int orient, const ivec &co, int size, const mergeinfo &m, vvec *vv, plane *p = NULL);
 extern int calcmergedsize(int orient, const ivec &co, int size, const mergeinfo &m, const vvec *vv);
-extern void invalidatemerges(cube &c, bool msg);
+extern void invalidatemerges(cube &c);
 extern void calcmerges();
 
 struct cubeface : mergeinfo
@@ -280,12 +251,12 @@ static inline uchar octantrectangleoverlap(const ivec &c, int size, const ivec &
 
 static inline bool insideworld(const vec &o)
 {
-    return o.x>=0 && o.x<worldsize && o.y>=0 && o.y<worldsize && o.z>=0 && o.z<worldsize;
+    return o.x>=0 && o.x<hdr.worldsize && o.y>=0 && o.y<hdr.worldsize && o.z>=0 && o.z<hdr.worldsize;
 }
 
 static inline bool insideworld(const ivec &o)
 {
-    return uint(o.x)<uint(worldsize) && uint(o.y)<uint(worldsize) && uint(o.z)<uint(worldsize);
+    return uint(o.x)<uint(hdr.worldsize) && uint(o.y)<uint(hdr.worldsize) && uint(o.z)<uint(hdr.worldsize);
 }
 
 // ents
@@ -296,16 +267,13 @@ extern void pasteundoents(undoblock *u);
 
 // octaedit
 extern void cancelsel();
-extern void rendertexturepanel(int w, int h);
+extern void render_texture_panel(int w, int h);
 extern void addundo(undoblock *u);
-extern void commitchanges(bool force = false);
-extern void rendereditcursor();
-extern void tryedit();
 
 // octarender
 extern void octarender();
 extern void allchanged(bool load = false);
-extern void clearvas(cube *c);
+extern void vaclearc(cube *c);
 extern vtxarray *newva(int x, int y, int z, int size);
 extern void destroyva(vtxarray *va, bool reparent = true);
 extern bool readva(vtxarray *va, ushort *&edata, uchar *&vdata);
@@ -348,7 +316,6 @@ extern void updatedynlights();
 extern int finddynlights();
 extern void calcdynlightmask(vtxarray *va);
 extern int setdynlights(vtxarray *va, const ivec &vaorigin);
-extern bool getdynlight(int n, vec &o, float &radius, vec &color);
 
 // material
 
@@ -369,15 +336,15 @@ extern int refracting;
 extern bool reflecting, fading, fogging;
 extern float reflectz;
 extern int reflectdist, vertwater, refractfog, waterrefract, waterreflect, waterfade, caustics, waterfallrefract, waterfog, lavafog;
-extern bvec watercolor, waterfallcolor, lavacolor;
 
 extern void cleanreflections();
 extern void queryreflections();
 extern void drawreflections();
 extern void renderwater();
 extern void renderlava(materialsurface &m, Texture *tex, float scale);
-extern void loadcaustics(bool force = false);
-extern void preloadwatershaders(bool force = false);
+extern void getwatercolour(uchar *wcol);
+extern void getwaterfallcolour(uchar *fcol);
+extern void getlavacolour(uchar *lcol);
 
 // glare
 extern bool glaring;
@@ -391,32 +358,32 @@ extern bool depthfxing;
 extern void drawdepthfxtex();
 
 // server
-extern vector<const char *> gameargs;
+extern vector<char *> gameargs;
 
-extern void initserver(bool listen, bool dedicated);
+extern void initserver(bool dedicated);
 extern void cleanupserver();
-extern void serverslice(bool dedicated, uint timeout);
+extern void serverslice(uint timeout);
 
-extern ENetSocket connectmaster();
+extern uchar *retrieveservers(uchar *buf, int buflen);
 extern void localclienttoserver(int chan, ENetPacket *);
 extern void localconnect();
 extern bool serveroption(char *opt);
 
 // serverbrowser
 extern bool resolverwait(const char *name, ENetAddress *address);
-extern int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress &address);
-extern void addserver(const char *name, int port = 0, const char *password = NULL, bool keep = false);
+extern int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &address);
+extern void addserver(char *servername);
+extern char *getservername(int n);
 extern void writeservercfg();
 
 // client
-extern void localdisconnect(bool cleanup = true);
-extern void localservertoclient(int chan, ENetPacket *packet);
-extern void connectserv(const char *servername, int port, const char *serverpassword);
+extern void localdisconnect();
+extern void localservertoclient(int chan, uchar *buf, int len);
+extern void connects(char *servername);
 extern void abortconnect();
 extern void clientkeepalive();
 
 // command
-extern hashtable<const char *, ident> *idents;
 extern bool overrideidents, persistidents;
 
 extern void explodelist(const char *s, vector<char *> &elems);
@@ -428,17 +395,8 @@ extern void checksleep(int millis);
 extern void clearsleep(bool clearoverrides = true);
 
 // console
-extern void keypress(int code, bool isdown, int cooked);
-extern int rendercommand(int x, int y, int w);
-extern int renderconsole(int w, int h, int abovehud);
-extern void conoutf(const char *s, ...);
-extern void conoutf(int type, const char *s, ...);
-extern void resetcomplete();
-extern void complete(char *s);
-const char *getkeyname(int code);
-extern const char *addreleaseaction(const char *s);
-extern void writebinds(stream *f);
-extern void writecompletions(stream *f);
+extern void writebinds(FILE *f);
+extern void writecompletions(FILE *f);
 
 // main
 enum
@@ -447,8 +405,6 @@ enum
     INIT_LOAD,
     INIT_RESET
 };
-extern int initing;
-
 enum
 {
     CHANGE_GFX   = 1<<0,
@@ -458,13 +414,8 @@ extern bool initwarning(const char *desc, int level = INIT_RESET, int type = CHA
 
 extern void pushevent(const SDL_Event &e);
 extern bool interceptkey(int sym);
-
-extern float loadprogress;
-extern void renderbackground(const char *caption = NULL, Texture *mapshot = NULL, const char *mapname = NULL, const char *mapinfo = NULL, bool restore = false, bool force = false);
-extern void renderprogress(float bar, const char *text, GLuint tex = 0, bool background = false);
-
-extern void getfps(int &fps, int &bestdiff, int &worstdiff);
-extern void swapbuffers();
+extern void computescreen(const char *text = NULL, Texture *t = NULL, const char *overlaytext = NULL);
+extern void show_out_of_renderloop_progress(float bar1, const char *text1, float bar2 = 0, const char *text2 = NULL, GLuint tex = 0);
 
 // menu
 extern void menuprocess();
@@ -488,7 +439,7 @@ enum
     TRIG_DISAPPEAR  = 1<<3,
     TRIG_AUTO_RESET = 1<<4,
     TRIG_RUMBLE     = 1<<5,
-    TRIG_LOCKED     = 1<<6
+    TRIG_LOCKED     = 1<<6,
 };
 
 #define NUMTRIGGERTYPES 16
@@ -497,8 +448,6 @@ extern int triggertypes[NUMTRIGGERTYPES];
 
 #define checktriggertype(type, flag) (triggertypes[(type) & (NUMTRIGGERTYPES-1)] & (flag))
 
-extern vector<int> outsideents;
-
 extern void entitiesinoctanodes();
 extern void attachentities();
 extern void freeoctaentities(cube &c);
@@ -515,36 +464,16 @@ extern void loadskin(const char *dir, const char *altdir, Texture *&skin, Textur
 extern mapmodelinfo &getmminfo(int i);
 extern void startmodelquery(occludequery *query);
 extern void endmodelquery();
-extern void preloadmodelshaders();
 
 // renderparticles
 extern void particleinit();
 extern void clearparticles();
-extern void clearparticleemitters();
-extern void seedparticles();
-extern void updateparticles();
-extern void renderparticles(bool mainpass = false);
-extern bool printparticles(extentity &e, char *buf);
+extern void entity_particles();
 
 // decal
 extern void initdecals();
 extern void cleardecals();
-extern void renderdecals(bool mainpass = false);
-
-// blob
-
-enum
-{
-    BLOB_STATIC = 0,
-    BLOB_DYNAMIC
-};
-
-extern int showblobs;
-
-extern void initblobs(int type = -1);
-extern void resetblobs();
-extern void renderblob(int type, const vec &o, float radius, float fade = 1);
-extern void flushblobs();
+extern void renderdecals(int time);
 
 // rendersky
 extern int explicitsky;
@@ -556,52 +485,17 @@ extern bool limitsky();
 // 3dgui
 extern void g3d_render();
 extern bool g3d_windowhit(bool on, bool act);
-
-// menus
-extern int mainmenu;
-
-extern void clearmainmenu();
 extern void g3d_mainmenu();
 
 // sound
-extern void clearmapsounds();
 extern void checkmapsounds();
-extern void updatesounds();
+extern void clearmapsounds();
+extern void updatevol();
 
 extern void initmumble();
 extern void closemumble();
 extern void updatemumble();
 
 // grass
-extern void generategrass();
 extern void rendergrass();
 
-// blendmap
-extern int blendpaintmode;
-
-extern bool setblendmaporigin(const ivec &o, int size);
-extern bool hasblendmap();
-extern uchar lookupblendmap(const vec &pos);
-extern void resetblendmap();
-extern void enlargeblendmap();
-extern void optimizeblendmap();
-extern void stoppaintblendmap();
-extern void trypaintblendmap();
-extern void renderblendbrush(GLuint tex, float x, float y, float w, float h);
-extern void renderblendbrush();
-extern bool loadblendmap(stream *f, int info);
-extern void saveblendmap(stream *f);
-extern uchar shouldsaveblendmap();
-
-// recorder
-
-namespace recorder
-{
-    void stop();
-    void capture();
-}
-
-#endif
-
-#endif
-
diff --git a/engine/explosion.h b/engine/explosion.h
index 5947eaa..883d4e4 100644
--- a/engine/explosion.h
+++ b/engine/explosion.h
@@ -1,3 +1,184 @@
+// eye space depth texture for soft particles, done at low res then blurred to prevent ugly jaggies
+VARP(depthfxfpscale, 1, 1<<12, 1<<16);
+VARP(depthfxscale, 1, 1<<6, 1<<8);
+VARP(depthfxblend, 1, 16, 64);
+VAR(depthfxmargin, 0, 16, 64);
+VAR(depthfxbias, 0, 1, 64);
+
+extern void cleanupdepthfx();
+VARFP(fpdepthfx, 0, 0, 1, cleanupdepthfx());
+VARFP(depthfxprecision, 0, 0, 1, cleanupdepthfx());
+VARP(depthfxemuprecision, 0, 1, 1);
+VARFP(depthfxsize, 6, 7, 12, cleanupdepthfx());
+VARP(depthfx, 0, 1, 1);
+VARFP(depthfxrect, 0, 0, 1, cleanupdepthfx());
+VARFP(depthfxfilter, 0, 1, 1, cleanupdepthfx());
+VARP(blurdepthfx, 0, 1, 7);
+VARP(blurdepthfxsigma, 1, 50, 200);
+VAR(depthfxscissor, 0, 2, 2);
+VAR(debugdepthfx, 0, 0, 1);
+
+#define MAXDFXRANGES 4
+
+void *depthfxowners[MAXDFXRANGES];
+float depthfxranges[MAXDFXRANGES];
+int numdepthfxranges = 0;
+vec depthfxmin(1e16f, 1e16f, 1e16f), depthfxmax(1e16f, 1e16f, 1e16f);
+
+static struct depthfxtexture : rendertarget
+{
+    const GLenum *colorformats() const
+    {
+        static const GLenum colorfmts[] = { GL_FLOAT_RG16_NV, GL_RGB16F_ARB, GL_RGB16, GL_RGBA, GL_RGBA8, GL_RGB, GL_RGB8, GL_FALSE };
+        return &colorfmts[hasTF && hasFBO ? (fpdepthfx ? (hasNVFB && texrect() && !filter() ? 0 : 1) : (depthfxprecision ? 2 : 3)) : 3];
+    }
+
+    float eyedepth(const vec &p) const
+    {
+        return max(-(p.x*mvmatrix[2] + p.y*mvmatrix[6] + p.z*mvmatrix[10] + mvmatrix[14]), 0.0f);
+    }
+
+    void addscissorvert(const vec &v, float &sx1, float &sy1, float &sx2, float &sy2)
+    {
+        float w = v.x*mvpmatrix[3] + v.y*mvpmatrix[7] + v.z*mvpmatrix[11] + mvpmatrix[15],
+              x = (v.x*mvpmatrix[0] + v.y*mvpmatrix[4] + v.z*mvpmatrix[8] + mvpmatrix[12]) / w,
+              y = (v.x*mvpmatrix[1] + v.y*mvpmatrix[5] + v.z*mvpmatrix[9] + mvpmatrix[13]) / w;
+        sx1 = min(sx1, x);
+        sy1 = min(sy1, y);
+        sx2 = max(sx2, x);
+        sy2 = max(sy2, y);
+    }
+
+    bool addscissorbox(const vec &center, float size)
+    {
+        extern float fovy, aspect;
+        vec e(center.x*mvmatrix[0] + center.y*mvmatrix[4] + center.z*mvmatrix[8] + mvmatrix[12],
+              center.x*mvmatrix[1] + center.y*mvmatrix[5] + center.z*mvmatrix[9] + mvmatrix[13],
+              center.x*mvmatrix[2] + center.y*mvmatrix[6] + center.z*mvmatrix[10] + mvmatrix[14]);
+        float zz = e.z*e.z, xx = e.x*e.x, yy = e.y*e.y, rr = size*size,
+              dx = zz*(xx + zz) - rr*zz, dy = zz*(yy + zz) - rr*zz,
+              focaldist = 1.0f/tan(fovy*0.5f*RAD),
+              left = -1, right = 1, bottom = -1, top = 1;
+        #define CHECKPLANE(c, dir, focaldist, low, high) \
+        do { \
+            float nc = (size*e.c dir drt)/(c##c + zz), \
+                  nz = (size - nc*e.c)/e.z, \
+                  pz = (c##c + zz - rr)/(e.z - nz/nc*e.c); \
+            if(pz < 0) \
+            { \
+                float c = nz*(focaldist)/nc, \
+                      pc = -pz*nz/nc; \
+                if(pc < e.c) low = c; \
+                else if(pc > e.c) high = c; \
+            } \
+        } while(0)
+        if(dx > 0)
+        {
+            float drt = sqrt(dx);
+            CHECKPLANE(x, -, focaldist/aspect, left, right);
+            CHECKPLANE(x, +, focaldist/aspect, left, right);
+        }
+        if(dy > 0)
+        {
+            float drt = sqrt(dy);
+            CHECKPLANE(y, -, focaldist, bottom, top);
+            CHECKPLANE(y, +, focaldist, bottom, top);
+        }
+        return addblurtiles(left, bottom, right, top);
+    }
+
+    bool addscissorbox(const vec &bbmin, const vec &bbmax)
+    {
+        float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
+        loopi(8)
+        {
+            vec v(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, i&4 ? bbmax.z : bbmin.z);
+            addscissorvert(v, sx1, sy1, sx2, sy2);
+        }
+        return addblurtiles(sx1, sy1, sx2, sy2);
+    }
+
+    bool screenview() const { return depthfxrect!=0; }
+    bool texrect() const { return depthfxrect && hasTR; }
+    bool filter() const { return depthfxfilter!=0; }
+    bool highprecision() const { return colorfmt==GL_FLOAT_RG16_NV || colorfmt==GL_RGB16F_ARB || colorfmt==GL_RGB16; }
+    bool emulatehighprecision() const { return depthfxemuprecision && !depthfxfilter; }
+
+    bool shouldrender()
+    {
+        extern void finddepthfxranges();
+        finddepthfxranges();
+        return (numdepthfxranges && scissorx1 < scissorx2 && scissory1 < scissory2) || debugdepthfx;
+    }
+
+    bool dorender()
+    {
+        glClearColor(1, 1, 1, 1);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+        depthfxing = true;
+        refracting = -1;
+
+        extern void renderdepthobstacles(const vec &bbmin, const vec &bbmax, float scale, float *ranges, int numranges);
+        float scale = depthfxscale;
+        float *ranges = depthfxranges;
+        int numranges = numdepthfxranges;
+        if(highprecision())
+        {
+            scale = depthfxfpscale;
+            ranges = NULL;
+            numranges = 0;
+        }
+        else if(emulatehighprecision())
+        {
+            scale = depthfxfpscale;
+            ranges = NULL;
+            numranges = -3;
+        }
+        renderdepthobstacles(depthfxmin, depthfxmax, scale, ranges, numranges);
+
+        refracting = 0;
+        depthfxing = false;
+
+        return numdepthfxranges > 0;
+    }
+
+    void dodebug(int w, int h)
+    {
+        if(numdepthfxranges > 0)
+        {
+            glColor3f(0, 1, 0);
+            debugscissor(w, h, true);
+            glColor3f(0, 0, 1);
+            debugblurtiles(w, h, true);
+            glColor3f(1, 1, 1);
+        }
+    }
+} depthfxtex;
+
+void cleanupdepthfx()
+{
+    depthfxtex.cleanup(true);
+}
+
+void viewdepthfxtex()
+{
+    if(!depthfx) return;
+    depthfxtex.debug();
+}
+
+bool depthfxing = false;
+
+void drawdepthfxtex()
+{
+    if(!depthfx || renderpath==R_FIXEDFUNCTION) return;
+
+    // Apple/ATI bug - fixed-function fog state can force software fallback even when fragment program is enabled
+    glDisable(GL_FOG);
+    depthfxtex.render(1<<depthfxsize, 1<<depthfxsize, blurdepthfx, blurdepthfxsigma/100.0f);
+    glEnable(GL_FOG);
+}
+
 //cache our unit hemisphere
 static GLushort *hemiindices = NULL;
 static vec *hemiverts = NULL;
@@ -81,7 +262,7 @@ static GLuint createexpmodtex(int size, float minval)
     }
     GLuint tex = 0;
     glGenTextures(1, &tex);
-    createtexture(tex, size, size, data, 3, 2, GL_ALPHA);
+    createtexture(tex, size, size, data, 3, true, GL_ALPHA);
     delete[] data;
     return tex;
 }
@@ -219,6 +400,8 @@ static void setupexplosion()
                     if(explosion2d) SETSHADER(explosion2dsoft8rect); else SETSHADER(explosion3dsoft8rect);
                 }
                 else if(explosion2d) SETSHADER(explosion2dsoftrect); else SETSHADER(explosion3dsoftrect);
+
+                setlocalparamf("depthfxview", SHPARAM_VERTEX, 6, 0.5f*depthfxtex.vieww, 0.5f*depthfxtex.viewh);
             }
             else
             {
@@ -227,7 +410,13 @@ static void setupexplosion()
                     if(explosion2d) SETSHADER(explosion2dsoft8); else SETSHADER(explosion3dsoft8);
                 }
                 else if(explosion2d) SETSHADER(explosion2dsoft); else SETSHADER(explosion3dsoft);
+
+                setlocalparamf("depthfxview", SHPARAM_VERTEX, 6, 0.5f*float(depthfxtex.vieww)/depthfxtex.texw, 0.5f*float(depthfxtex.viewh)/depthfxtex.texh);
             }
+
+            glActiveTexture_(GL_TEXTURE2_ARB);
+            glBindTexture(depthfxtex.target, depthfxtex.rendertex);
+            glActiveTexture_(GL_TEXTURE0_ARB);
         }
         else if(explosion2d) SETSHADER(explosion2d); else SETSHADER(explosion3d);
     }
@@ -395,7 +584,7 @@ static const float WOBBLE = 1.25f;
 struct fireballrenderer : listrenderer
 {
     fireballrenderer(int type)
-        : listrenderer("packages/particles/explosion.jpg", type)
+        : listrenderer("packages/particles/explosion.jpg", type, 0, 0)
     {}
 
     void startrender()
@@ -467,12 +656,6 @@ struct fireballrenderer : listrenderer
         return numranges;
     }
 
-    void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity)
-    {
-        pe.maxfade = max(pe.maxfade, fade);
-        pe.extendbb(o, (size+1+pe.ent->attr2)*WOBBLE); 
-    }
-
     void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts, uchar *color)
     {
         float pmax = p->val,
@@ -523,7 +706,41 @@ struct fireballrenderer : listrenderer
         {
             setlocalparamf("center", SHPARAM_VERTEX, 0, o.x, o.y, o.z);
             setlocalparamf("animstate", SHPARAM_VERTEX, 1, size, psize, pmax, float(lastmillis));
-            binddepthfxparams(depthfxblend, inside ? blend/(2*255.0f) : 0, 2*(p->size + pmax)*WOBBLE >= depthfxblend, p);
+            if(!glaring && !reflecting && !refracting && depthfx && depthfxtex.rendertex && numdepthfxranges>0)
+            {
+                float scale = 0, offset = -1, texscale = 0;
+                if(!depthfxtex.highprecision())
+                {
+                    float select[4] = { 0, 0, 0, 0 };
+                    if(!depthfxtex.emulatehighprecision())
+                    {
+                        loopi(numdepthfxranges) if(depthfxowners[i]==p)
+                        {
+                            select[i] = float(depthfxscale)/depthfxblend;
+                            scale = 1.0f/depthfxblend;
+                            offset = -float(depthfxranges[i] - depthfxbias)/depthfxblend;
+                            break;
+                        }
+                    }
+                    else if(2*(p->size + pmax)*WOBBLE >= depthfxblend)
+                    {
+                        select[0] = float(depthfxfpscale)/depthfxblend;
+                        select[1] = select[0]/256;
+                        select[2] = select[1]/256;
+                        scale = 1.0f/depthfxblend;
+                        offset = 0;
+                    }
+                    setlocalparamfv("depthfxselect", SHPARAM_PIXEL, 6, select);
+                }
+                else if(2*(p->size + pmax)*WOBBLE >= depthfxblend)
+                {
+                    scale = 1.0f/depthfxblend;
+                    offset = 0;
+                    texscale = float(depthfxfpscale)/depthfxblend;
+                }
+                setlocalparamf("depthfxparams", SHPARAM_VERTEX, 5, scale, offset, texscale, inside ? blend/(2*255.0f) : 0);
+                setlocalparamf("depthfxparams", SHPARAM_PIXEL, 5, scale, offset, texscale, inside ? blend/(2*255.0f) : 0);
+            }
         }
 
         glRotatef(lastmillis/7.0f, -rotdir.x, rotdir.y, -rotdir.z);
@@ -535,3 +752,17 @@ struct fireballrenderer : listrenderer
 };
 static fireballrenderer fireballs(PT_FIREBALL|PT_GLARE), noglarefireballs(PT_FIREBALL);
 
+void finddepthfxranges()
+{
+    depthfxmin = vec(1e16f, 1e16f, 1e16f);
+    depthfxmax = vec(0, 0, 0);
+    numdepthfxranges = fireballs.finddepthfxranges(depthfxowners, depthfxranges, MAXDFXRANGES, depthfxmin, depthfxmax);
+    loopk(3)
+    {
+        depthfxmin[k] -= depthfxmargin;
+        depthfxmax[k] += depthfxmargin;
+    }
+
+    if(depthfxscissor<2 && numdepthfxranges>0) depthfxtex.addscissorbox(depthfxmin, depthfxmax);
+}
+
diff --git a/engine/glare.cpp b/engine/glare.cpp
index 0232e44..d691269 100644
--- a/engine/glare.cpp
+++ b/engine/glare.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 #include "rendertarget.h"
 
@@ -38,12 +39,14 @@ void drawglaretex()
     glaretex.render(1<<glaresize, 1<<glaresize, blurglare, blurglaresigma/100.0f);
 }
 
-FVARP(glarescale, 0, 1, 8);
+FVARP(glarescale, 1);
 
 void addglare()
 {
     if(!glare || renderpath==R_FIXEDFUNCTION) return;
 
+    glDisable(GL_DEPTH_TEST);
+    
     glEnable(GL_BLEND);
     glBlendFunc(GL_ONE, GL_ONE);
 
@@ -63,5 +66,7 @@ void addglare()
     glEnd();
 
     glDisable(GL_BLEND);
+
+    glEnable(GL_DEPTH_TEST);
 }
      
diff --git a/engine/grass.cpp b/engine/grass.cpp
index e8bd345..ba0292c 100644
--- a/engine/grass.cpp
+++ b/engine/grass.cpp
@@ -1,304 +1,439 @@
+#include "pch.h"
 #include "engine.h"
 
-VARP(grass, 0, 0, 1);
-VAR(dbggrass, 0, 0, 1);
-VARP(grassdist, 0, 256, 10000);
-FVARP(grasstaper, 0, 0.2, 1);
-FVARP(grassstep, 0.5, 2, 8);
-VARP(grassheight, 1, 4, 64);
+VARP(grassanimdist, 0, 500, 10000);
+VARP(grassdist, 0, 500, 10000);
+VARP(grassfalloff, 0, 100, 1000);
 
-#define NUMGRASSWEDGES 8
+VAR(grasswidth, 1, 6, 64);
+VAR(grassheight, 1, 8, 64);
 
-static struct grasswedge
-{
-    vec dir, edge1, edge2;
-    plane bound1, bound2;
-
-    grasswedge(int i) :
-      dir(2*M_PI*(i+0.5f)/float(NUMGRASSWEDGES), 0),
-      edge1(vec(2*M_PI*i/float(NUMGRASSWEDGES), 0).div(cos(M_PI/NUMGRASSWEDGES))),
-      edge2(vec(2*M_PI*(i+1)/float(NUMGRASSWEDGES), 0).div(cos(M_PI/NUMGRASSWEDGES))),
-      bound1(vec(2*M_PI*(i/float(NUMGRASSWEDGES) - 0.5f), 0), 0),
-      bound2(vec(2*M_PI*((i+1)/float(NUMGRASSWEDGES) + 0.5f), 0), 0)
-    {}
-} grasswedges[NUMGRASSWEDGES] = { 0, 1, 2, 3, 4, 5, 6, 7 };
-
-struct grassvert
+void resetgrasssamples()
 {
-    vec pos;
-    uchar color[4];
-    float u, v, lmu, lmv;
-};
+    extern vector<vtxarray *> valist;
+    loopv(valist)
+    {
+        vtxarray *va = valist[i];
+        DELETEP(va->grasssamples);
+    }
+}
 
-static vector<grassvert> grassverts;
+VARF(grassgrid, 1, 6, 32, resetgrasssamples());
 
-struct grassgroup
+void gengrasssample(vtxarray *va, const vec &o, float tu, float tv, LightMap *lm)
 {
-    const grasstri *tri;
-    float dist;
-    int tex, lmtex, offset, numquads;
-};
-
-static vector<grassgroup> grassgroups;
-
-#define NUMGRASSOFFSETS 32
+    grasssample &g = va->grasssamples->add();
 
-static float grassoffsets[NUMGRASSOFFSETS] = { -1 }, grassanimoffsets[NUMGRASSOFFSETS];
-static int lastgrassanim = -1;
+    g.x = ushort(o.x-va->o.x) | GRASS_SAMPLE;
+    g.y = ushort(o.y-va->o.y);
+    g.z = ushort(4*(o.z-va->o.z));
 
-VARR(grassanimmillis, 0, 3000, 60000);
-FVARR(grassanimscale, 0, 0.03f, 1);
-
-static void animategrass()
-{
-    loopi(NUMGRASSOFFSETS) grassanimoffsets[i] = grassanimscale*sinf(2*M_PI*(grassoffsets[i] + lastmillis/float(grassanimmillis)));
-    lastgrassanim = lastmillis;
+    if(lm)
+    {
+        tu = min(tu, LM_PACKW-0.01f);
+        tv = min(tv, LM_PACKH-0.01f);
+        memcpy(g.color, &lm->data[3*(int(tv)*LM_PACKW + int(tu))], 3);
+    }
+    else loopk(3) g.color[k] = hdr.ambient;
 }
 
-static inline bool clipgrassquad(const grasstri &g, vec &p1, vec &p2)
+bool gengrassheader(vtxarray *va, const vec *v)
 {
-    loopi(g.numv)
-    {
-        float dist1 = g.e[i].dist(p1), dist2 = g.e[i].dist(p2);
-        if(dist1 <= 0)
-        {
-            if(dist2 <= 0) return false;
-            p1.add(vec(p2).sub(p1).mul(dist1 / (dist1 - dist2)));
-        }
-        else if(dist2 <= 0)
-            p2.add(vec(p1).sub(p2).mul(dist2 / (dist2 - dist1)));
-    }
+    vec center = v[0];
+    center.add(v[1]);
+    center.add(v[2]);
+    center.div(3);
+
+    float r1 = center.dist(v[0]),
+          r2 = center.dist(v[1]),
+          r3 = center.dist(v[2]),
+          radius = min(r1, min(r2, r3));
+    if(radius < grassgrid*2) return false;
+
+    grassbounds &g = *(grassbounds *)&va->grasssamples->add();
+    g.x = ushort(center.x-va->o.x) | GRASS_BOUNDS;
+    g.y = ushort(center.y-va->o.y);
+    g.z = ushort(4*(center.z-va->o.z));
+    g.radius = ushort(radius + grasswidth);
+    g.numsamples = 0;
     return true;
 }
 
-VARR(grassscale, 1, 2, 64);
- 
-static void gengrassquads(grassgroup *&group, const grasswedge &w, const grasstri &g, Texture *tex)
+void gengrasssamples(vtxarray *va, const vec *v, float *tc, LightMap *lm)
 {
-    float t = camera1->o.dot(w.dir);
-    int tstep = int(ceil(t/grassstep));
-    float tstart = tstep*grassstep, tfrac = tstart - t;
-
-    float t1 = w.dir.dot(g.v[0]), t2 = w.dir.dot(g.v[1]), t3 = w.dir.dot(g.v[2]),
-          tmin = min(t1, min(t2, t3)),
-          tmax = max(t1, max(t2, t3));
-    if(g.numv>3)
+    int u, l, r;
+    if(v[1].y < v[0].y) u = v[1].y < v[2].y ? 1 : 2;
+    else u = v[0].y < v[2].y ? 0 : 2;
+    l = (u+2)%3;
+    r = (u+1)%3;
+    if(v[l].x > v[r].x) swap(l, r);
+    if(v[u].y == v[l].y)
     {
-        float t4 = w.dir.dot(g.v[3]);
-        tmin = min(tmin, t4);
-        tmax = max(tmax, t4);
+        if(v[l].x <= v[u].x) swap(u, l);
+        swap(l, r);
     }
- 
-    if(tmax < tstart || tmin > t + grassdist) return;
-
-    int minstep = max(int(ceil(tmin/grassstep)) - tstep, 1),
-        maxstep = int(floor(min(tmax, t + grassdist)/grassstep)) - tstep,
-        numsteps = maxstep - minstep + 1;
-
-    float texscale = (grassscale*tex->ys)/float(grassheight*tex->xs), animscale = grassheight*texscale;
-    vec tc;
-    tc.cross(g.surface, w.dir).mul(texscale);
-
-    int color = tstep + maxstep;
-    if(color < 0) color = NUMGRASSOFFSETS - (-color)%NUMGRASSOFFSETS;
-    color += numsteps + NUMGRASSOFFSETS - numsteps%NUMGRASSOFFSETS;
+    vec o1 = v[u], dl = v[l];
+    dl.sub(o1);
+    if(dl.x==0 && dl.y==0) return;
+    float endl = v[l].y,
+          ls = tc[2*u], lt = tc[2*u+1],
+          lds = tc[2*l] - ls, ldt = tc[2*l+1] - lt;
+
+    vec o2, dr;
+    float endr, rs, rt, rds, rdt;
+    if(v[u].y==v[r].y)
+    {
+        if(v[u].x==v[r].x) return;
+        o2 = v[r];
+        dr = v[l];
+        dr.sub(o2);
+        endr = v[l].y;
+
+        rs = tc[2*r];
+        rt = tc[2*r+1];
+        rds = tc[2*l] - rs;
+        rdt = tc[2*l+1] - rt;
+    }
+    else
+    {
+        o2 = v[u];
+        dr = v[r];
+        dr.sub(o2);
+        endr = v[r].y;
+        rs = ls;
+        rt = lt;
+        rds = tc[2*r] - rs;
+        rdt = tc[2*r+1] - rt;
+    }
+    if(dr.y==0 && (dr.x==0 || dl.y==0)) return;
+    if(dr.x==0 && dl.x==0) return;
+    
+    bool header = false;
+    int numsamples = 0;
+    float dy = grassgrid - fmodf(o1.y, grassgrid);
+    for(;;)
+    {
+        if(endl > o1.y) dy = min(dy, endl - o1.y);
+        if(endr > o2.y) dy = min(dy, endr - o2.y);
+
+        o1.y += dy;
+        o1.x += dl.x * dy/dl.y;
+        o1.z += dl.z * dy/dl.y;
+        ls += lds * dy/dl.y;
+        lt += ldt * dy/dl.y;
+
+        o2.y += dy;
+        o2.x += dr.x * dy/dr.y;
+        o2.z += dr.z * dy/dr.y;
+        rs += rds * dy/dr.y;
+        rt += rdt * dy/dr.y;
+
+        if(o1.y <= endl && o2.y <= endr && fmod(o1.y, grassgrid) < 0.01f)
+        {
+            vec p = o1, dp = o2;
+            dp.sub(o1);
+            float s = ls, t = lt,
+                  ds = rs - ls, dt = rt - lt;
+            float dx = grassgrid - fmodf(o1.x, grassgrid);
+            if(o1.x==o2.x && dx==grassgrid)
+            {
+                if(!numsamples++) header = gengrassheader(va, v);
+                gengrasssample(va, p, s, t, lm);
+            }
+            else while(!header || numsamples<USHRT_MAX)
+            {
+                p.x += dx;
+                p.y += dp.y * dx/dp.x;
+                p.z += dp.z * dx/dp.x;
+                s += ds * dx/dp.x;
+                t += dt * dx/dp.x;
 
-    float taperdist = grassdist*grasstaper,
-          taperscale = 1.0f / (grassdist - taperdist);
+                if(p.x > o2.x) break;
 
-    for(int i = maxstep; i >= minstep; i--, color--)
-    {
-        float dist = i*grassstep + tfrac;
-        vec p1 = vec(w.edge1).mul(dist).add(camera1->o),
-            p2 = vec(w.edge2).mul(dist).add(camera1->o);
-        p1.z = g.surface.zintersect(p1);
-        p2.z = g.surface.zintersect(p2);
+                if(!numsamples++) header = gengrassheader(va, v);
+                gengrasssample(va, p, s, t, lm);
 
-        if(!clipgrassquad(g, p1, p2)) continue;
+                dx = grassgrid;
+            }
+            if(header && numsamples>=USHRT_MAX) break;
+        }
 
-        if(!group)
+        if(o1.y >= endl)
         {
-            group = &grassgroups.add();
-            group->tri = &g;
-            group->tex = tex->id;
-            group->lmtex = lightmaptexs.inrange(g.lmid) ? lightmaptexs[g.lmid].id : notexture->id;
-            group->offset = grassverts.length();
-            group->numquads = 0;
-            if(lastgrassanim!=lastmillis) animategrass();
+            if(v[r].y <= endl) break;
+            dl = v[r];
+            dl.sub(v[l]);
+            endl = v[r].y;
+            lds = tc[2*r] - tc[2*l];
+            ldt = tc[2*r+1] - tc[2*l+1];
+
+            dy = grassgrid - fmod(o1.y, grassgrid);
+            continue;
         }
-  
-        group->numquads++;
- 
-        float offset = grassoffsets[color%NUMGRASSOFFSETS],
-              animoffset = animscale*grassanimoffsets[color%NUMGRASSOFFSETS],
-              tc1 = tc.dot(p1) + offset, tc2 = tc.dot(p2) + offset,
-              lm1u = g.tcu.dot(p1), lm1v = g.tcv.dot(p1),
-              lm2u = g.tcu.dot(p2), lm2v = g.tcv.dot(p2),
-              fade = dist > taperdist ? (grassdist - dist)*taperscale : 1,
-              height = grassheight * fade;
-        uchar color[4] = { 255, 255, 255, uchar(fade*255) };
-
-        #define GRASSVERT(n, tcv, modify) { \
-            grassvert &gv = grassverts.add(); \
-            gv.pos = p##n; \
-            memcpy(gv.color, color, sizeof(color)); \
-            gv.u = tc##n; gv.v = tcv; \
-            gv.lmu = lm##n##u; gv.lmv = lm##n##v; \
-            modify; \
+
+        if(o2.y >= endr)
+        {
+            if(v[l].y <= endr) break;
+            dr = v[l];
+            dr.sub(v[r]);
+            endr = v[l].y;
+            rds = tc[2*l] - tc[2*r];
+            rdt = tc[2*l+1] - tc[2*r+1];
+
+            dy = grassgrid - fmod(o1.y, grassgrid);
+            continue;
         }
-    
-        GRASSVERT(2, 0, { gv.pos.z += height; gv.u += animoffset; });
-        GRASSVERT(1, 0, { gv.pos.z += height; gv.u += animoffset; });
-        GRASSVERT(1, 1, );
-        GRASSVERT(2, 1, );
+
+        dy = grassgrid;
     }
-}             
+    if(header)
+    {
+        grassbounds &g = *(grassbounds *)&(*va->grasssamples)[va->grasssamples->length() - numsamples - 1];
+        g.numsamples = numsamples;
+    } 
+}
 
-static void gengrassquads(vtxarray *va)
+void gengrasssamples(vtxarray *va)
 {
+    if(va->grasssamples) return;
+    va->grasssamples = new vector<grasssample>;
+    int lasttex = -1;
     loopv(*va->grasstris)
     {
         grasstri &g = (*va->grasstris)[i];
-        if(isvisiblesphere(g.radius, g.center) >= VFC_FOGGED) continue;
-        float dist = g.center.dist(camera1->o);
-        if(dist - g.radius > grassdist) continue;
-            
-        Slot &s = lookuptexture(g.texture, false);
-        if(!s.grasstex) 
+        if(g.texture != lasttex)
         {
-            if(!s.autograss) continue;
-            s.grasstex = textureload(s.autograss, 2);
+            grasstexture &t = *(grasstexture *)&va->grasssamples->add();
+            t.x = GRASS_TEXTURE;
+            t.texture = g.texture;
+            lasttex = g.texture;
         }
-
-        grassgroup *group = NULL;
-        loopi(NUMGRASSWEDGES)
+        vec v[4];
+        float tc[8];
+        static int remap[4] = { 1, 2, 0, 3 };
+        loopk(4)
         {
-            grasswedge &w = grasswedges[i];    
-            if(w.bound1.dist(g.center) > g.radius || w.bound2.dist(g.center) > g.radius) continue;
-            gengrassquads(group, w, g, s.grasstex);
+            int j = remap[k];
+            v[k] = g.v[j].tovec(va->o);
+            if(g.surface)
+            {
+                tc[2*k] = float(g.surface->x + (g.surface->texcoords[j*2] / 255.0f) * (g.surface->w - 1) + 0.5f);
+                tc[2*k+1] = float(g.surface->y + (g.surface->texcoords[j*2 + 1] / 255.0f) * (g.surface->h - 1) + 0.5f);
+            }
         }
-        if(group) group->dist = dist;
+        LightMap *lm = g.surface && g.surface->lmid >= LMID_RESERVED ? &lightmaps[g.surface->lmid-LMID_RESERVED] : NULL;
+        gengrasssamples(va, v, tc, lm);
+        gengrasssamples(va, &v[1], &tc[2], lm);
     }
 }
 
-static inline int comparegrassgroups(const grassgroup *x, const grassgroup *y)
-{
-    if(x->dist > y->dist) return -1;
-    else if(x->dist < y->dist) return 1;
-    else return 0;
-}
+VAR(grasstest, 0, 0, 3);
 
-void generategrass()
-{
-    if(!grass || !grassdist) return;
+static Texture *grasstex = NULL;
 
-    grassgroups.setsizenodelete(0);
-    grassverts.setsizenodelete(0);
+VARP(grasslod, 0, 25, 1000);
 
-    if(grassoffsets[0] < 0) loopi(NUMGRASSOFFSETS) grassoffsets[i] = rnd(0x1000000)/float(0x1000000);
+VARP(grasslodz, 0, 150, 10000);
 
-    loopi(NUMGRASSWEDGES)
-    {
-        grasswedge &w = grasswedges[i];
-        w.bound1.offset = -camera1->o.dot(w.bound1);
-        w.bound2.offset = -camera1->o.dot(w.bound2);
-    }
+float loddist(const vec &o)
+{
+    float dx = o.x - camera1->o.x, dy = o.y - camera1->o.y, dz = camera1->o.z - o.z;
+    float dist = sqrt(dx*dx + dy*dy);
+    dist -= grasslodz/100.0f * max(dz, 0.0f);
+    return max(dist, 0.0f);
+}
 
-    extern vtxarray *visibleva;
-    for(vtxarray *va = visibleva; va; va = va->next)
-    {
-        if(!va->grasstris || va->occluded >= OCCLUDE_GEOM) continue;
-        if(va->distance > grassdist) continue;
-        if(reflecting || refracting>0 ? va->o.z+va->size<reflectz : va->o.z>=reflectz) continue;
-        gengrassquads(va);
-    }
+VAR(grassrand, 0, 30, 90);
 
-    grassgroups.sort(comparegrassgroups);
-}
+VARP(grasssamples, 0, 50, 10000);
 
-void rendergrass()
+VARP(grassbillboard, 0, 1, 100);
+VARP(grassbbcorrect, 0, 1, 1);
+VARP(grasstaper, 0, 200, 10000);
+
+void rendergrasssample(const grasssample &g, const vec &o, float dist, int seed, float height, int numsamples)
 {
-    if(!grass || !grassdist || grassgroups.empty() || dbggrass) return;
+    if(grasstest>2) return;
 
-    glDisable(GL_CULL_FACE);
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-    glDepthMask(GL_FALSE);
+    if(seed >= 2*numsamples) return;
 
-    SETSHADER(grass);
+    vec up(0, 0, 1), right(seed%2, (seed+1)%2, 0);
+    float width = grasswidth;
+    if(numsamples<=grassbillboard)
+    {
+        if(seed%2) return;
+        right = camright;
+        if(grassrand) right.rotate_around_z((detrnd((size_t)&g * (seed + 1), 2*grassrand)-grassrand)*RAD);
+        if(grassbbcorrect) 
+        {
+            if(fabs(right.x) > fabs(right.y)) width *= sqrt(right.y*right.y/(right.x*right.x) + 1);
+            else width *= sqrt(right.x*right.x/(right.y*right.y) + 1);
+        }
+    }
+    else if(grassrand) right.rotate_around_z((detrnd((size_t)&g * (seed + 1), 2*grassrand)-grassrand)*RAD);
 
-    glEnableClientState(GL_VERTEX_ARRAY);
-    glVertexPointer(3, GL_FLOAT, sizeof(grassvert), grassverts[0].pos.v);
+    vec b1 = right;
+    b1.mul(-0.5f*width);
+    b1.add(o);
+    b1[seed%2] += (seed/2 * grassgrid) / float(numsamples) - grassgrid/2.0f;
 
-    glEnableClientState(GL_COLOR_ARRAY);
-    glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(grassvert), grassverts[0].color);
+    vec b2 = right;
+    b2.mul(width);
+    b2.add(b1);
 
-    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-    glTexCoordPointer(2, GL_FLOAT, sizeof(grassvert), &grassverts[0].u);
+    vec t1 = b1, t2 = b2;
+    t1.z += grassheight * height;
+    t2.z += grassheight * height;
 
-    if(renderpath!=R_FIXEDFUNCTION || maxtmus>=2)
+    float w1 = 0, w2 = 0;
+    if(grasstest>0) t1 = t2 = b1;
+    else if(dist < grassanimdist)
     {
-        glActiveTexture_(GL_TEXTURE1_ARB);
-        glClientActiveTexture_(GL_TEXTURE1_ARB);
-        glEnable(GL_TEXTURE_2D);
-        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-        glTexCoordPointer(2, GL_FLOAT, sizeof(grassvert), &grassverts[0].lmu);
-        if(renderpath==R_FIXEDFUNCTION) setuptmu(1, "P * T x 2"); 
-        glClientActiveTexture_(GL_TEXTURE0_ARB);
-        glActiveTexture_(GL_TEXTURE0_ARB);
+        w1 = detrnd((size_t)&g * (seed + 1)*7, 360)*RAD + t1.x*0.4f + t1.y*0.5f;
+        w1 += lastmillis*0.0015f;
+        w1 = sinf(w1);
+        vec d1(1.0f, 1.0f, 0.5f);
+        d1.mul(grassheight/4.0f * w1);
+        t1.add(d1);
+
+        w2 = detrnd((size_t)&g * (seed + 1)*11, 360)*RAD + t2.x*0.55f + t2.y*0.45f;
+        w2 += lastmillis*0.0015f;
+        w2 = sinf(w2);
+        vec d2(0.4f, 0.4f, 0.2f);
+        d2.mul(grassheight/4.0f * w2);
+        t2.add(d2);
     }
 
-    int texid = -1, lmtexid = -1;
-    loopv(grassgroups)
+    if(grasstest>1) return;
+
+    extern int fullbright;
+    if(nolights || (fullbright && editmode)) glColor3ub(128, 128, 128);
+    else glColor3ubv(g.color);
+    float offset = detrnd((size_t)&g * (seed + 1)*13, grasstex->xs)/float(grasstex->xs);
+    glTexCoord2f(offset, 1); glVertex3fv(b1.v);
+    glTexCoord2f(offset, 0); glVertex3fv(t1.v);
+    glTexCoord2f(offset + float(grasswidth)*64.0f/grasstex->xs, 0); glVertex3fv(t2.v);
+    glTexCoord2f(offset + float(grasswidth)*64.0f/grasstex->xs, 1); glVertex3fv(b2.v);
+    xtraverts += 4;
+}
+
+void rendergrasssamples(vtxarray *va, const vec &dir)
+{
+    if(!va->grasssamples) return;
+    loopv(*va->grasssamples)
     {
-        grassgroup &g = grassgroups[i];
+        grasssample &g = (*va->grasssamples)[i];
 
-        if(reflecting || refracting)
+        vec o((g.x&~GRASS_TYPE)+va->o.x, g.y+va->o.y, g.z/4.0f+va->o.z), tograss;
+        switch(g.x&GRASS_TYPE)
         {
-            if(refracting < 0 ?
-                min(g.tri->numv>3 ? min(g.tri->v[0].z, g.tri->v[3].z) : g.tri->v[0].z, min(g.tri->v[1].z, g.tri->v[2].z)) > reflectz :
-                max(g.tri->numv>3 ? max(g.tri->v[0].z, g.tri->v[3].z) : g.tri->v[0].z, max(g.tri->v[1].z, g.tri->v[2].z)) + grassheight < reflectz) 
-                continue;
-            if(isvisiblesphere(g.tri->radius, g.tri->center) >= VFC_FOGGED) continue;
-        }
+            case GRASS_BOUNDS:
+            {
+                grassbounds &b = *(grassbounds *)&g;
+                if(reflecting || refracting>0 ? o.z+b.radius<reflectz : o.z-b.radius>=reflectz)
+                {
+                    i += b.numsamples;
+                    continue;
+                }
+                float dist = o.dist(camera1->o, tograss);
+                if(dist > grassdist + b.radius || (dir.dot(tograss)<0 && dist > b.radius + 2*(grassgrid + player->eyeheight)))
+                    i += b.numsamples;
+                break;
+            }
 
-        if(texid != g.tex)
-        {
-            glBindTexture(GL_TEXTURE_2D, g.tex);
-            texid = g.tex;
-        }
-        if(lmtexid != g.lmtex)
-        {
-            if(renderpath!=R_FIXEDFUNCTION || maxtmus>=2)
+            case GRASS_TEXTURE:
             {
-                glActiveTexture_(GL_TEXTURE1_ARB);
-                glBindTexture(GL_TEXTURE_2D, g.lmtex);
-                glActiveTexture_(GL_TEXTURE0_ARB);
+                grasstexture &t = *(grasstexture *)&g;
+                Slot &s = lookuptexture(t.texture, false);
+                if(!s.grasstex || s.grasstex!=grasstex)
+                {
+                    glEnd();
+                    if(!s.grasstex) s.grasstex = textureload(s.autograss, 2);
+                    glBindTexture(GL_TEXTURE_2D, s.grasstex->id);
+                    glBegin(GL_QUADS);
+                    grasstex = s.grasstex;
+                }
+                break;
             }
-            lmtexid = g.lmtex;
-        }
 
-        glDrawArrays(GL_QUADS, g.offset, 4*g.numquads);
-        xtravertsva += 4*g.numquads;
+            case GRASS_SAMPLE:
+            {
+                if(reflecting || refracting>0 ? o.z+grassheight<=reflectz : o.z>=reflectz) continue;
+                float dist = o.dist(camera1->o, tograss);
+                if(dist > grassdist || (dir.dot(tograss)<0 && dist > grasswidth/2 + 2*(grassgrid + player->eyeheight))) continue;
+
+                float ld = loddist(o);
+                int numsamples = int(grasssamples/100.0f*max(grassgrid - ld/grasslod, 100.0f/grasssamples));
+                float height = 1 - (dist + grasstaper - grassdist) / (grasstaper ? grasstaper : 1);
+                height = min(height, 1.0f);
+                loopj(2*numsamples)
+                {
+                    rendergrasssample(g, o, dist, j, height, numsamples);
+                }
+                break;
+            }
+        }
     }
+}
 
-    glDisableClientState(GL_VERTEX_ARRAY);
-    glDisableClientState(GL_COLOR_ARRAY);
-    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+VAR(grassblend, 0, 0, 100);
 
-    if(renderpath!=R_FIXEDFUNCTION || maxtmus>=2)
+void setupgrass()
+{
+    glDisable(GL_CULL_FACE);
+    glEnable(GL_ALPHA_TEST);
+    glAlphaFunc(GL_GREATER, grassblend ? grassblend/100.0f : 0.6f);
+    if(grassblend)
     {
-        glActiveTexture_(GL_TEXTURE1_ARB);
-        glClientActiveTexture_(GL_TEXTURE1_ARB);
-        if(renderpath==R_FIXEDFUNCTION) resettmu(1);
-        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
-        glDisable(GL_TEXTURE_2D);
-        glClientActiveTexture_(GL_TEXTURE0_ARB);
-        glActiveTexture_(GL_TEXTURE0_ARB);
+        glEnable(GL_BLEND);
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     }
 
-    glDisable(GL_BLEND);
-    glDepthMask(GL_TRUE);
+    static Shader *grassshader = NULL;
+    if(!grassshader) grassshader = lookupshaderbyname("grass");
+    grassshader->set();
+
+    grasstex = NULL;
+
+    setuptmu(0, "C * T x 2");
+
+    glBegin(GL_QUADS);
+}
+
+void cleanupgrass()
+{
+    glEnd();
+
+    resettmu(0);
+
+    defaultshader->set();
+
+    if(grassblend) glDisable(GL_BLEND);
+    glDisable(GL_ALPHA_TEST);
     glEnable(GL_CULL_FACE);
 }
 
+VARP(grass, 0, 0, 1);
+
+void rendergrass()
+{
+    if(!grass || !grasssamples || !grassdist) return;
+
+    vec dir;
+    vecfromyawpitch(camera1->yaw, 0, 1, 0, dir);
+
+    int rendered = 0;
+    extern vtxarray *visibleva;
+    for(vtxarray *va = visibleva; va; va = va->next)
+    {
+        if(!va->grasstris || va->occluded >= OCCLUDE_GEOM) continue;
+        if(va->distance > grassdist) continue;
+        if(reflecting || refracting>0 ? va->o.z+va->size<reflectz : va->o.z>=reflectz) continue;
+        if(!va->grasssamples) gengrasssamples(va);
+        if(!rendered++) setupgrass();
+        rendergrasssamples(va, dir);
+    }
+
+    if(rendered) cleanupgrass();
+}
+
diff --git a/engine/lensflare.h b/engine/lensflare.h
index 971b6a4..711da66 100644
--- a/engine/lensflare.h
+++ b/engine/lensflare.h
@@ -39,7 +39,7 @@ struct flarerenderer : partrenderer
     flare *flares;
 
     flarerenderer(const char *texname, int maxflares)
-        : partrenderer(texname, PT_FLARE), maxflares(maxflares), shinetime(0)
+        : partrenderer(texname, PT_FLARE, 0, 0), maxflares(maxflares), shinetime(0)
     {
         flares = new flare[maxflares];
     }
@@ -94,7 +94,7 @@ struct flarerenderer : partrenderer
 
         if(editmode || !flarelights) return;
 
-        const vector<extentity *> &ents = entities::getents();
+        const vector<extentity *> &ents = et->getents();
         vec viewdir;
         vecfromyawpitch(camera1->yaw, camera1->pitch, 1, 0, viewdir);
         extern const vector<int> &checklightcache(int x, int y);
@@ -137,7 +137,8 @@ struct flarerenderer : partrenderer
 
     void render()
     {
-        if(renderpath!=R_FIXEDFUNCTION || !fogging) glDisable(GL_FOG);
+        bool fog = glIsEnabled(GL_FOG)==GL_TRUE;
+        if(fog) glDisable(GL_FOG);
         defaultshader->set();
         glDisable(GL_DEPTH_TEST);
         if(!tex) tex = textureload(texname);
@@ -177,11 +178,11 @@ struct flarerenderer : partrenderer
         }
         glEnd();
         glEnable(GL_DEPTH_TEST);
-        if(renderpath!=R_FIXEDFUNCTION || !fogging) glEnable(GL_FOG);
+        if(fog) glEnable(GL_FOG);
     }
 
     //square per round hole - use addflare(..) instead
-    particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity = 0) { return NULL; }
+    particle *addpart(const vec &o, const vec &d, int fade, int color, float size) { return NULL; }
 };
 static flarerenderer flares("packages/particles/lensflares.png", 64);
 
diff --git a/engine/lightmap.cpp b/engine/lightmap.cpp
index 40bc32d..d8b7765 100644
--- a/engine/lightmap.cpp
+++ b/engine/lightmap.cpp
@@ -1,41 +1,39 @@
+#include "pch.h"
 #include "engine.h"
 
 vector<LightMap> lightmaps;
 
-VARR(lightprecision, 1, 32, 1024);
-VARR(lighterror, 1, 8, 16);
-VARR(bumperror, 1, 3, 16);
-VARR(lightlod, 0, 0, 10);
-bvec ambientcolor(0x19, 0x19, 0x19), skylightcolor(0, 0, 0);
-HVARFR(ambient, 1, 0x191919, 0xFFFFFF, 
-{
-    if(ambient <= 255) ambient |= (ambient<<8) | (ambient<<16);
-    ambientcolor = bvec((ambient>>16)&0xFF, (ambient>>8)&0xFF, ambient&0xFF);
-});
-HVARFR(skylight, 0, 0, 0xFFFFFF, 
-{
-    if(skylight <= 255) skylight |= (skylight<<8) | (skylight<<16);
-    skylightcolor = bvec((skylight>>16)&0xFF, (skylight>>8)&0xFF, skylight&0xFF);
-});
+VARF(lightprecision, 1, 32, 1024, hdr.mapprec = lightprecision);
+VARF(lighterror, 1, 8, 16, hdr.maple = lighterror);
+VARF(bumperror, 1, 3, 16, hdr.mapbe = bumperror);
+VARF(lightlod, 0, 0, 10, hdr.mapllod = lightlod);
+VARF(worldlod, 0, 0, 1,  hdr.mapwlod = worldlod);
+VARF(ambient, 1, 25, 64, hdr.ambient = ambient);
 
-static surfaceinfo brightsurfaces[6] =
+void skylight(char *r, char *g, char *b)
 {
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-    {{0}, 0, 0, 0, 0, LMID_BRIGHT, LAYER_TOP},
-};
+	if(!r[0])
+	{
+		s_sprintfd(s)("%d %d %d", hdr.skylight[0], hdr.skylight[1], hdr.skylight[2]);
+		result(s);
+	}
+	else
+	{
+		hdr.skylight[0] = strtol(r, NULL, 0);
+		hdr.skylight[1] = g[0] ? strtol(g, NULL, 0) : hdr.skylight[0];
+		hdr.skylight[2] = b[0] ? strtol(b, NULL, 0) : hdr.skylight[1];
+	}
+}
+
+COMMAND(skylight, "sss");
 
 // quality parameters, set by the calclight arg
-VARN(lmshadows, lmshadows_, 0, 2, 2);
-VARN(lmaa, lmaa_, 0, 3, 3);
-static int lmshadows = 2, lmaa = 3;
+int shadows = 1;
+int mmshadows = 0;
+int aalights = 3;
 
-static Slot *lmslot = NULL;
-static int lmtype, lmbpp, lmorient, lmrotate;
-static uchar lm[4*LM_MAXW*LM_MAXH];
+static int lmtype, lmorient, lmrotate;
+static uchar lm[3*LM_MAXW*LM_MAXH];
 static vec lm_ray[LM_MAXW*LM_MAXH];
 static int lm_w, lm_h;
 static vector<const extentity *> lights1, lights2;
@@ -58,29 +56,29 @@ void show_calclight_progress()
 {
     int lumels = curlumels;
     loopv(lightmaps) lumels += lightmaps[i].lumels;
-    float bar1 = float(progress) / float(allocnodes);
+    float bar1 = float(progress) / float(allocnodes),
+          bar2 = lightmaps.length() ? float(lumels) / float(lightmaps.length() * LM_PACKW * LM_PACKH) : 0;
           
-    defformatstring(text1)("%d%% using %d textures", int(bar1 * 100), lightmaps.length());
+    s_sprintfd(text1)("%d%%", int(bar1 * 100));
+    s_sprintfd(text2)("%d textures %d%% utilized", lightmaps.length(), int(bar2 * 100));
 
     if(LM_PACKW <= hwtexsize && !progresstex) 
     {
         glGenTextures(1, &progresstex); 
-        createtexture(progresstex, LM_PACKW, LM_PACKH, NULL, 3, 1, GL_RGB);
+        createtexture(progresstex, LM_PACKW, LM_PACKH, NULL, 3, false, GL_RGB);
     }
     // only update once a sec (4 * 250 ms ticks) to not kill performance
     if(progresstex && !calclight_canceled) 
     {
-        loopvrev(lightmaps) if((lightmaps[i].type&LM_TYPE)!=LM_BUMPMAP1)
+        loopvrev(lightmaps) if(lightmaps[i].type==LM_DIFFUSE || lightmaps[i].type==LM_BUMPMAP0)
         {
             if(progresstexticks++ % 4) break;
             glBindTexture(GL_TEXTURE_2D, progresstex);
-            glPixelStorei(GL_UNPACK_ALIGNMENT, texalign(lightmaps[i].data, LM_PACKW, lightmaps[i].bpp));
-            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LM_PACKW, LM_PACKH, 
-                            lightmaps[i].type&LM_ALPHA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, lightmaps[i].data);
+            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, LM_PACKW, LM_PACKH, GL_RGB, GL_UNSIGNED_BYTE, lightmaps[i].data);
             break;
         }
     }
-    renderprogress(bar1, text1, progresstexticks ? progresstex : 0);
+    show_out_of_renderloop_progress(bar1, text1, bar2, text2, progresstexticks ? progresstex : 0);
 }
 
 #define CHECK_PROGRESS(exit) CHECK_CALCLIGHT_PROGRESS(exit, show_calclight_progress)
@@ -123,39 +121,34 @@ bool PackNode::insert(ushort &tx, ushort &ty, ushort tw, ushort th)
 
 bool LightMap::insert(ushort &tx, ushort &ty, uchar *src, ushort tw, ushort th)
 {
-    if((type&LM_TYPE) != LM_BUMPMAP1 && !packroot.insert(tx, ty, tw, th))
+    if(type != LM_BUMPMAP1 && !packroot.insert(tx, ty, tw, th))
         return false;
 
-    copy(tx, ty, src, tw, th);
-    return true;
-}
-
-void LightMap::copy(ushort tx, ushort ty, uchar *src, ushort tw, ushort th)
-{
-    uchar *dst = data + bpp * tx + ty * bpp * LM_PACKW;
+    uchar *dst = data + 3 * tx + ty * 3 * LM_PACKW;
     loopi(th)
     {
-        memcpy(dst, src, bpp * tw);
-        dst += bpp * LM_PACKW;
-        src += bpp * tw;
+        memcpy(dst, src, 3 * tw);
+        dst += 3 * LM_PACKW;
+        src += 3 * tw;
     }
     ++lightmaps;
     lumels += tw * th;
+    return true;
 }
 
 void insert_unlit(int i)
 {
     LightMap &l = lightmaps[i];
-    if((l.type&LM_TYPE) == LM_BUMPMAP1)
+    if(l.type != LM_DIFFUSE && l.type != LM_BUMPMAP0)
     {
         l.unlitx = l.unlity = -1;
         return;
     }
     ushort x, y;
-    uchar unlit[4] = { ambientcolor[0], ambientcolor[1], ambientcolor[2], 255 };
+    uchar unlit[3] = { hdr.ambient, hdr.ambient, hdr.ambient };
     if(l.insert(x, y, unlit, 1, 1))
     {
-        if((l.type&LM_TYPE) == LM_BUMPMAP0)
+        if(l.type == LM_BUMPMAP0)
         {
             bvec front(128, 128, 255);
             ASSERT(lightmaps[i+1].insert(x, y, front.v, 1, 1));
@@ -165,43 +158,30 @@ void insert_unlit(int i)
     }
 }
 
-void insert_lightmap(ushort &x, ushort &y, uchar &lmid)
+void insert_lightmap(int type, ushort &x, ushort &y, ushort &lmid)
 {
     loopv(lightmaps)
     {
-        if(lightmaps[i].type == lmtype && lightmaps[i].insert(x, y, lm, lm_w, lm_h))
+        if(lightmaps[i].type == type && lightmaps[i].insert(x, y, lm, lm_w, lm_h))
         {
             lmid = i + LMID_RESERVED;
-            if((lmtype&LM_TYPE) == LM_BUMPMAP0) ASSERT(lightmaps[i+1].insert(x, y, (uchar *)lm_ray, lm_w, lm_h));
+            if(type == LM_BUMPMAP0) ASSERT(lightmaps[i+1].insert(x, y, (uchar *)lm_ray, lm_w, lm_h));
             return;
         }
     }
 
     lmid = lightmaps.length() + LMID_RESERVED;
     LightMap &l = lightmaps.add();
-    l.type = lmtype;
-    l.bpp = lmbpp;
-    l.data = new uchar[lmbpp*LM_PACKW*LM_PACKH];
-    memset(l.data, 0, lmbpp*LM_PACKW*LM_PACKH);
+    l.type = type;
     ASSERT(l.insert(x, y, lm, lm_w, lm_h));
-    if((lmtype&LM_TYPE) == LM_BUMPMAP0)
+    if(type == LM_BUMPMAP0)
     {
         LightMap &r = lightmaps.add();
-        r.type = LM_BUMPMAP1 | (lmtype&~LM_TYPE);
-        r.bpp = 3;
-        r.data = new uchar[3*LM_PACKW*LM_PACKH];
-        memset(r.data, 0, 3*LM_PACKW*LM_PACKH);
+        r.type = LM_BUMPMAP1;
         ASSERT(r.insert(x, y, (uchar *)lm_ray, lm_w, lm_h));
     }
 }
 
-void copy_lightmap(surfaceinfo &surface)
-{
-    lightmaps[surface.lmid-LMID_RESERVED].copy(surface.x, surface.y, lm, lm_w, lm_h);
-    if((lmtype&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED))
-        lightmaps[surface.lmid+1-LMID_RESERVED].copy(surface.x, surface.y, (uchar *)lm_ray, lm_w, lm_h);
-}
-
 struct compresskey 
 { 
     ushort x, y, lmid;
@@ -224,20 +204,21 @@ static inline bool htcmp(const compresskey &x, const compresskey &y)
     if(lm_w != y.w || lm_h != y.h) return false;
     LightMap &ylm = lightmaps[y.lmid - LMID_RESERVED];
     if(lmtype != ylm.type) return false;
-    const uchar *xcolor = lm, *ycolor = ylm.data + lmbpp*(y.x + y.y*LM_PACKW);
+    const uchar *xcolor = lm, *ycolor = ylm.data + 3*(y.x + y.y*LM_PACKW);
     loopi(lm_h)
     {
-        if(memcmp(xcolor, ycolor, lmbpp*lm_w)) return false;
-        xcolor += lmbpp*lm_w;
-        ycolor += lmbpp*LM_PACKW;
+        loopj(lm_w)
+        {
+            loopk(3) if(*xcolor++ != *ycolor++) return false;
+        }
+        ycolor += 3*(LM_PACKW - y.w);
     }
-    if((lmtype&LM_TYPE) != LM_BUMPMAP0) return true;
+    if(lmtype != LM_BUMPMAP0) return true;
     const bvec *xdir = (bvec *)lm_ray, *ydir = (bvec *)lightmaps[y.lmid+1 - LMID_RESERVED].data;
     loopi(lm_h)
     {
-        if(memcmp(xdir, ydir, lm_w*sizeof(bvec))) return false;
-        xdir += lm_w;
-        ydir += LM_PACKW;
+        loopj(lm_w) if(*xdir++ != *ydir++) return false;
+        ydir += LM_PACKW - y.w;
     }
     return true;
 }
@@ -249,7 +230,7 @@ static inline uint hthash(const compresskey &k)
     loopi(lm_w*lm_h)
     {
        hash ^= (color[0] + (color[1] << 8) + (color[2] << 16));
-       color += lmbpp;
+       color += 3;
     }
     return hash;  
 }
@@ -258,14 +239,14 @@ static hashtable<compresskey, compressval> compressed;
 
 VAR(lightcompress, 0, 3, 6);
 
-bool pack_lightmap(surfaceinfo &surface) 
+void pack_lightmap(int type, surfaceinfo &surface) 
 {
     if((int)lm_w <= lightcompress && (int)lm_h <= lightcompress)
     {
         compressval *val = compressed.access(compresskey());
         if(!val)
         {
-            insert_lightmap(surface.x, surface.y, surface.lmid);
+            insert_lightmap(type, surface.x, surface.y, surface.lmid);
             compressed[surface] = surface;
         }
         else
@@ -273,62 +254,11 @@ bool pack_lightmap(surfaceinfo &surface)
             surface.x = val->x;
             surface.y = val->y;
             surface.lmid = val->lmid;
-            return false;
         }
     }
-    else insert_lightmap(surface.x, surface.y, surface.lmid);
-    return true;
+    else insert_lightmap(type, surface.x, surface.y, surface.lmid);
 }
 
-void update_lightmap(const surfaceinfo &surface)
-{
-    if(max(LM_PACKW, LM_PACKH) > hwtexsize) return;
-
-    LightMap &lm = lightmaps[surface.lmid-LMID_RESERVED];
-    if(lm.tex < 0)
-    {
-        lm.offsetx = lm.offsety = 0;
-        lm.tex = lightmaptexs.length();
-        LightMapTexture &tex = lightmaptexs.add();
-        tex.type = renderpath==R_FIXEDFUNCTION ? (lm.type&~LM_TYPE) | LM_DIFFUSE : lm.type;
-        tex.w = LM_PACKW;
-        tex.h = LM_PACKH;
-        tex.unlitx = lm.unlitx;
-        tex.unlity = lm.unlity;
-        glGenTextures(1, &tex.id);
-        createtexture(tex.id, tex.w, tex.h, NULL, 3, 1, tex.type&LM_ALPHA ? GL_RGBA : GL_RGB);
-        if(renderpath!=R_FIXEDFUNCTION && (lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED))
-        {
-            LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED];
-            lm2.offsetx = lm2.offsety = 0;
-            lm2.tex = lightmaptexs.length();
-            LightMapTexture &tex2 = lightmaptexs.add();
-            tex2.type = (lm.type&~LM_TYPE) | LM_BUMPMAP0;
-            tex2.w = LM_PACKW;
-            tex2.h = LM_PACKH;
-            tex2.unlitx = lm2.unlitx;
-            tex2.unlity = lm2.unlity;
-            glGenTextures(1, &tex2.id);
-            createtexture(tex2.id, tex2.w, tex2.h, NULL, 3, 1, GL_RGB);
-        }
-    }
-
-    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
-    glPixelStorei(GL_UNPACK_ROW_LENGTH, LM_PACKW);
-
-    glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm.tex].id);
-    glTexSubImage2D(GL_TEXTURE_2D, 0, lm.offsetx + surface.x, lm.offsety + surface.y, surface.w, surface.h, lm.type&LM_ALPHA ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, &lm.data[(surface.y*LM_PACKW + surface.x)*lm.bpp]);
-    if(renderpath!=R_FIXEDFUNCTION && (lm.type&LM_TYPE)==LM_BUMPMAP0 && lightmaps.inrange(surface.lmid+1-LMID_RESERVED))
-    {
-        LightMap &lm2 = lightmaps[surface.lmid+1-LMID_RESERVED];
-        glBindTexture(GL_TEXTURE_2D, lightmaptexs[lm2.tex].id);
-        glTexSubImage2D(GL_TEXTURE_2D, 0, lm2.offsetx + surface.x, lm2.offsety + surface.y, surface.w, surface.h, GL_RGB, GL_UNSIGNED_BYTE, &lm2.data[(surface.y*LM_PACKW + surface.x)*3]);
-    }
- 
-    glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
-}
- 
-        
 void generate_lumel(const float tolerance, const vector<const extentity *> &lights, const vec &target, const vec &normal, vec &sample, int x, int y)
 {
     vec avgray(0, 0, 0);
@@ -357,13 +287,13 @@ void generate_lumel(const float tolerance, const vector<const extentity *> &ligh
             if(spotatten <= 0) continue;
             attenuation *= spotatten;
         }
-        if(lmshadows && mag)
+        if(shadows && mag)
         {
-            float dist = shadowray(light.o, ray, mag - tolerance, RAY_SHADOW | (lmshadows > 1 ? RAY_ALPHAPOLY : 0));
+            float dist = shadowray(light.o, ray, mag - tolerance, RAY_SHADOW | (mmshadows > 1 ? RAY_ALPHAPOLY : (mmshadows ? RAY_POLY : 0)));
             if(dist < mag - tolerance) continue;
         }
         float intensity;
-        switch(lmtype&LM_TYPE)
+        switch(lmtype)
         {
             case LM_BUMPMAP0: 
                 intensity = attenuation; 
@@ -377,7 +307,7 @@ void generate_lumel(const float tolerance, const vector<const extentity *> &ligh
         g += intensity * float(light.attr3);
         b += intensity * float(light.attr4);
     }
-    switch(lmtype&LM_TYPE)
+    switch(lmtype)
     {
         case LM_BUMPMAP0:
             if(avgray.iszero()) break;
@@ -391,16 +321,16 @@ void generate_lumel(const float tolerance, const vector<const extentity *> &ligh
             lm_ray[y*lm_w+x].add(vec(S.dot(avgray), T.dot(avgray), normal.dot(avgray)));
             break;
     }
-    sample.x = min(255.0f, max(r, float(ambientcolor[0])));
-    sample.y = min(255.0f, max(g, float(ambientcolor[1])));
-    sample.z = min(255.0f, max(b, float(ambientcolor[2])));
+    sample.x = min(255.0f, max(r, float(ambient)));
+    sample.y = min(255.0f, max(g, float(ambient)));
+    sample.z = min(255.0f, max(b, float(ambient)));
 }
 
 bool lumel_sample(const vec &sample, int aasample, int stride)
 {
-    if(sample.x >= int(ambientcolor[0])+1 || sample.y >= int(ambientcolor[1])+1 || sample.z >= int(ambientcolor[2])+1) return true;
+    if(sample.x >= ambient+1 || sample.y >= ambient+1 || sample.z >= ambient+1) return true;
 #define NCHECK(n) \
-    if((n).x >= int(ambientcolor[0])+1 || (n).y >= int(ambientcolor[1])+1 || (n).z >= int(ambientcolor[2])+1) \
+    if((n).x >= ambient+1 || (n).y >= ambient+1 || (n).z >= ambient+1) \
         return true;
     const vec *n = &sample - stride - aasample;
     NCHECK(n[0]); NCHECK(n[aasample]); NCHECK(n[2*aasample]);
@@ -411,7 +341,9 @@ bool lumel_sample(const vec &sample, int aasample, int stride)
     return false;
 }
 
-void calcskylight(const vec &o, const vec &normal, float tolerance, uchar *skylight, int flags = RAY_ALPHAPOLY, extentity *t = NULL)
+VAR(mmskylight, 0, 1, 1);
+
+void calcskylight(const vec &o, const vec &normal, float tolerance, uchar *skylight, int mmshadows = 1, extentity *t = NULL)
 {
     static const vec rays[17] =
     {
@@ -441,15 +373,10 @@ void calcskylight(const vec &o, const vec &normal, float tolerance, uchar *skyli
     int hit = 0;
     loopi(17) if(normal.dot(rays[i])>=0)
     {
-        if(shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, RAY_SHADOW | flags, t)>1e15f) hit++;
+        if(shadowray(vec(rays[i]).mul(tolerance).add(o), rays[i], 1e16f, RAY_SHADOW | (!mmskylight || !mmshadows ? 0 : (mmshadows > 1 ? RAY_ALPHAPOLY : RAY_POLY)), t)>1e15f) hit++;
     }
 
-    loopk(3) skylight[k] = uchar(ambientcolor[k] + (max(skylightcolor[k], ambientcolor[k]) - ambientcolor[k])*hit/17.0f);
-}
-
-static inline bool hasskylight()
-{
-    return skylightcolor[0]>ambientcolor[0] || skylightcolor[1]>ambientcolor[1] || skylightcolor[2]>ambientcolor[2];
+    loopk(3) skylight[k] = uchar(ambient + (max(int(hdr.skylight[k]), ambient) - ambient)*hit/17.0f);
 }
 
 VARR(blurlms, 0, 0, 2);
@@ -457,7 +384,7 @@ VARR(blurskylight, 0, 0, 2);
 
 void blurlightmap(int n)
 {
-    static uchar blur[4*LM_MAXW*LM_MAXH];
+    static uchar blur[3*LM_MAXW*LM_MAXH];
     static const int matrix3x3[9] =
     {
         1, 2, 1,
@@ -475,73 +402,28 @@ void blurlightmap(int n)
     };
     static const int matrix5x5sum = 52;
     uchar *src = lm, *dst = blur;
-    int stride = lmbpp*lm_w;
-    loop(y, lm_h) loop(x, lm_w) 
+    int stride = 3*lm_w;
+
+    loop(y, lm_h) loop(x, lm_w) loopk(3)
     {
-        loopk(3)
+        int c = *src, val = 0; 
+        const int *m = n>1 ? matrix5x5 : matrix3x3;
+        for(int t = -n; t<=n; t++) for(int s = -n; s<=n; s++, m++)
         {
-            int c = *src, val = 0; 
-            const int *m = n>1 ? matrix5x5 : matrix3x3;
-            for(int t = -n; t<=n; t++) for(int s = -n; s<=n; s++, m++)
-            {
-                val += *m * (x+s>=0 && x+s<lm_w && y+t>=0 && y+t<lm_h ? src[t*stride+lmbpp*s] : c);
-            }
-            *dst++ = val/(n>1 ? matrix5x5sum : matrix3x3sum);
-            src++;
+            val += *m * (x+s>=0 && x+s<lm_w && y+t>=0 && y+t<lm_h ? src[t*stride+3*s] : c);
         }
-        if(lmtype&LM_ALPHA) *dst++ = *src++;
+        *dst++ = val/(n>1 ? matrix5x5sum : matrix3x3sum);
+        src++;
     }
-    memcpy(lm, blur, lmbpp*lm_w*lm_h);
+    memcpy(lm, blur, 3*lm_w*lm_h);
 }
 
-static inline void generate_alpha(float tolerance, const vec &pos, uchar &alpha)
-{
-    alpha = lookupblendmap(pos);
-    if(lmslot->layermask)
-    {
-        static const int sdim[] = { 1, 0, 0 }, tdim[] = { 2, 2, 1 };
-        int dim = dimension(lmorient);
-        float k = 8.0f/lmslot->scale,
-              s = (pos[sdim[dim]] * k - lmslot->xoffset) / lmslot->layermaskscale,
-              t = (pos[tdim[dim]] * (dim <= 1 ? -k : k) - lmslot->yoffset) / lmslot->layermaskscale;
-        if((lmrotate&5)==1) swap(s, t);
-        if(lmrotate>=2 && lmrotate<=4) s = -s;
-        if((lmrotate>=1 && lmrotate<=2) || lmrotate==5) t = -t;
-        const ImageData &mask = *lmslot->layermask;
-        int mx = int(floor(s))%mask.w, my = int(floor(t))%mask.h;
-        if(mx < 0) mx += mask.w;
-        if(my < 0) my += mask.h;
-        uchar maskval = mask.data[mask.bpp*(mx + 1) - 1 + mask.pitch*my];
-        switch(lmslot->layermaskmode)
-        {
-            case 2: alpha = min(alpha, maskval); break;
-            case 3: alpha = max(alpha, maskval); break;
-            case 4: alpha = min(alpha, uchar(0xFF - maskval)); break;
-            case 5: alpha = max(alpha, uchar(0xFF - maskval)); break;
-            default: alpha = maskval; break;
-        }
-    }
-}
-        
 VAR(edgetolerance, 1, 4, 8);
 VAR(adaptivesample, 0, 1, 1);
 
-enum
-{
-    NO_SURFACE = 0,
-    SURFACE_AMBIENT_BOTTOM,
-    SURFACE_AMBIENT_TOP,
-    SURFACE_LIGHTMAP_BOTTOM,
-    SURFACE_LIGHTMAP_TOP,
-    SURFACE_LIGHTMAP_BLEND 
-};
-
-#define SURFACE_AMBIENT SURFACE_AMBIENT_BOTTOM
-#define SURFACE_LIGHTMAP SURFACE_LIGHTMAP_BOTTOM
-
-int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpvert *lv, int numv, const vec &ustep, const vec &vstep)
+bool generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpvert *lv, int numv, const vec &ustep, const vec &vstep)
 {
-    static uchar mincolor[4], maxcolor[4];
+    static uchar mincolor[3], maxcolor[3];
     static float aacoords[8][2] =
     {
         {0.0f, 0.0f},
@@ -562,17 +444,17 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
 
     if(y1 == 0)
     {
-        memset(mincolor, 255, sizeof(mincolor));
-        memset(maxcolor, 0, sizeof(maxcolor));
-        if((lmtype&LM_TYPE) == LM_BUMPMAP0) memset(lm_ray, 0, sizeof(lm_ray));
+        memset(mincolor, 255, 3);
+        memset(maxcolor, 0, 3);
+        if(lmtype == LM_BUMPMAP0) memset(lm_ray, 0, sizeof(lm_ray));
     }
 
-    static vec samples[4*(LM_MAXW+1)*(LM_MAXH+1)];
+    static vec samples [4*(LM_MAXW+1)*(LM_MAXH+1)];
 
-    int aasample = min(1 << lmaa, 4);
+    int aasample = min(1 << aalights, 4);
     int stride = aasample*(lm_w+1);
     vec *sample = &samples[stride*y1];
-    uchar *skylight = &lm[lmbpp*lm_w*y1];
+    uchar *skylight = &lm[3*lm_w*y1];
     lerpbounds start, end;
     initlerpbounds(lv, numv, start, end);
     for(int y = y1; y < y2; ++y, v.add(vstep)) 
@@ -581,18 +463,17 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
         lerpnormal(y, lv, numv, start, end, normal, nstep);
         
         vec u(v);
-        for(int x = 0; x < lm_w; ++x, u.add(ustep), normal.add(nstep), skylight += lmbpp) 
+        for(int x = 0; x < lm_w; ++x, u.add(ustep), normal.add(nstep), skylight += 3) 
         {
-            CHECK_PROGRESS(return NO_SURFACE);
+            CHECK_PROGRESS(return false);
             generate_lumel(tolerance, lights, u, vec(normal).normalize(), *sample, x, y);
-            if(hasskylight())
+            if(hdr.skylight[0]>ambient || hdr.skylight[1]>ambient || hdr.skylight[2]>ambient)
             {
-                if((lmtype&LM_TYPE)==LM_BUMPMAP0 || !adaptivesample || sample->x<skylightcolor[0] || sample->y<skylightcolor[1] || sample->z<skylightcolor[2])
-                    calcskylight(u, normal, tolerance, skylight, lmshadows > 1 ? RAY_ALPHAPOLY : 0);
-                else loopk(3) skylight[k] = max(skylightcolor[k], ambientcolor[k]);
+                if(lmtype==LM_BUMPMAP0 || !adaptivesample || sample->x<hdr.skylight[0] || sample->y<hdr.skylight[1] || sample->z<hdr.skylight[2])
+                    calcskylight(u, normal, tolerance, skylight, mmshadows);
+                else loopk(3) skylight[k] = max(int(hdr.skylight[k]), ambient);
             }
-            else loopk(3) skylight[k] = ambientcolor[k];
-            if(lmtype&LM_ALPHA) generate_alpha(tolerance, u, skylight[3]);
+            else loopk(3) skylight[k] = ambient;
             sample += aasample;
         }
         sample += aasample;
@@ -623,7 +504,7 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
                 n.normalize();
                 loopi(aasample-1)
                     generate_lumel(EDGE_TOLERANCE(i+1) * tolerance, lights, vec(u).add(offsets[i+1]), n, *sample++, x, y);
-                if(lmaa == 3) 
+                if(aalights == 3) 
                 {
                     loopi(4)
                     {
@@ -654,7 +535,7 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
 
             for(int x = 0; x <= lm_w; ++x, v.add(ustep), normal.add(nstep))
             {
-                CHECK_PROGRESS(return NO_SURFACE);
+                CHECK_PROGRESS(return false);
                 vec n(normal);
                 n.normalize();
                 generate_lumel(edgetolerance * tolerance, lights, vec(v).add(offsets[1]), n, sample[1], min(x, lm_w-1), lm_h-1);
@@ -664,13 +545,13 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
             } 
         }
 
-        if(hasskylight())
+        if(hdr.skylight[0]>ambient || hdr.skylight[1]>ambient || hdr.skylight[2]>ambient)
         {
             if(blurskylight && (lm_w>1 || lm_h>1)) blurlightmap(blurskylight);
         }
         sample = samples;
-        float weight = 1.0f / (1.0f + 4.0f*lmaa),
-              cweight = weight * (lmaa == 3 ? 5.0f : 1.0f);
+        float weight = 1.0f / (1.0f + 4.0f*aalights),
+              cweight = weight * (aalights == 3 ? 5.0f : 1.0f);
         uchar *lumel = lm;
         vec *ray = lm_ray;
         bvec minray(255, 255, 255), maxray(0, 0, 0);
@@ -706,20 +587,16 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
                     mincolor[k] = min(mincolor[k], lumel[k]);
                     maxcolor[k] = max(maxcolor[k], lumel[k]);
                 }
-                if(lmtype&LM_ALPHA)
-                {
-                    mincolor[3] = min(mincolor[3], lumel[3]);
-                    maxcolor[3] = max(maxcolor[3], lumel[3]);
-                }
-                if((lmtype&LM_TYPE) == LM_BUMPMAP0)
+
+                if(lmtype == LM_BUMPMAP0)
                 {
                     bvec &n = ((bvec *)lm_ray)[ray-lm_ray];
                     if(ray->iszero()) n = bvec(128, 128, 255);
                     else
                     {
-                        // bias the normals towards the amount of ambient/skylight in the lumel 
-                        // this is necessary to prevent the light values in shaders from dropping too far below the skylight (to the ambient) if N.L is small 
                         ray->normalize();
+                        // bias the normals towards the amount of ambient/skylight in the lumel
+                        // this is necessary to prevent the light values in shaders from dropping too far below the skylight (to the ambient) if N.L is small
                         int l = max(r, max(g, b)), a = max(ar, max(ag, ab));
                         ray->mul(max(l-a, 0));
                         ray->z += a;
@@ -730,75 +607,38 @@ int generate_lightmap(float lpu, int y1, int y2, const vec &origin, const lerpve
                         minray[k] = min(minray[k], n[k]);
                         maxray[k] = max(maxray[k], n[k]);
                     }
-                    ray++;
                 }
-                lumel += lmbpp;
+                lumel += 3;
+                ray++;
             }
             sample += aasample;
         }
         if(int(maxcolor[0]) - int(mincolor[0]) <= lighterror &&
            int(maxcolor[1]) - int(mincolor[1]) <= lighterror &&
-           int(maxcolor[2]) - int(mincolor[2]) <= lighterror &&
-           mincolor[3] >= maxcolor[3])
+           int(maxcolor[2]) - int(mincolor[2]) <= lighterror)
         {
             uchar color[3];
             loopk(3) color[k] = (int(maxcolor[k]) + int(mincolor[k])) / 2;
-            if(color[0] <= int(ambientcolor[0]) + lighterror && 
-               color[1] <= int(ambientcolor[1]) + lighterror && 
-               color[2] <= int(ambientcolor[2]) + lighterror &&
-               (maxcolor[3]==0 || mincolor[3]==255))
-                return mincolor[3]==255 ? SURFACE_AMBIENT_TOP : SURFACE_AMBIENT_BOTTOM;
-            if((lmtype&LM_TYPE) != LM_BUMPMAP0 || 
+            if(color[0] <= ambient + lighterror && 
+               color[1] <= ambient + lighterror && 
+               color[2] <= ambient + lighterror)
+                return false;
+            if(lmtype != LM_BUMPMAP0 || 
                 (int(maxray.x) - int(minray.x) <= bumperror &&
                  int(maxray.y) - int(minray.z) <= bumperror &&
                  int(maxray.z) - int(minray.z) <= bumperror))
-
             {
                 memcpy(lm, color, 3);
-                if(lmtype&LM_ALPHA) lm[3] = mincolor[3];
-                if((lmtype&LM_TYPE) == LM_BUMPMAP0) 
-                {
-                    loopk(3) ((bvec *)lm_ray)[0][k] = uchar((int(maxray[k])+int(minray[k]))/2);
-                }
+                if(lmtype == LM_BUMPMAP0) loopk(3) ((bvec *)lm_ray)[0][k] = uchar((int(maxray[k])+int(minray[k]))/2);
                 lm_w = 1;
                 lm_h = 1;
             }
         }
         if(blurlms && (lm_w>1 || lm_h>1)) blurlightmap(blurlms);
     }
-    if(mincolor[3]==255) return SURFACE_LIGHTMAP_TOP;
-    else if(maxcolor[3]==0) return SURFACE_LIGHTMAP_BOTTOM;
-    else return SURFACE_LIGHTMAP_BLEND;
+    return true;
 }
 
-int preview_lightmap_alpha(float lpu, int y1, int y2, const vec &origin, const vec &ustep, const vec &vstep)
-{
-    extern int fullbrightlevel;
-    float tolerance = 0.5 / lpu;
-    uchar *dst = &lm[4*lm_w*y1];
-    vec v = origin;
-    uchar minalpha = 255, maxalpha = 0;
-    for(int y = y1; y < y2; ++y, v.add(vstep))
-    {
-        vec u(v);
-        for(int x = 0; x < lm_w; ++x, u.add(ustep), dst += 4)
-        {
-            loopk(3) dst[k] = fullbrightlevel;        
-            generate_alpha(tolerance, u, dst[3]);
-            minalpha = min(minalpha, dst[3]);
-            maxalpha = max(maxalpha, dst[3]);
-        }
-    }
-    if(y2 == lm_h)
-    {
-        if(minalpha==255) return SURFACE_AMBIENT_TOP;
-        if(maxalpha==0) return SURFACE_AMBIENT_BOTTOM;
-        if(minalpha==maxalpha) lm_w = lm_h = 1;    
-        loopi(lm_w*lm_h) ((bvec *)lm_ray)[i] = bvec(128, 128, 255);
-    }
-    return SURFACE_LIGHTMAP_BLEND;
-}        
-
 void clear_lmids(cube *c)
 {
     loopi(8)
@@ -826,7 +666,7 @@ VARF(lightcachesize, 4, 6, 12, clearlightcache());
 
 void clearlightcache(int e)
 {
-    if(e < 0 || !entities::getents()[e]->attr1)
+    if(e < 0 || !et->getents()[e]->attr1)
     {
         for(lightcacheentry *lce = lightcache; lce < &lightcache[LIGHTCACHESIZE]; lce++)
         {
@@ -836,10 +676,10 @@ void clearlightcache(int e)
     }
     else
     {
-        const extentity &light = *entities::getents()[e];
+        const extentity &light = *et->getents()[e];
         int radius = light.attr1;
-        for(int x = int(max(light.o.x-radius, 0.0f))>>lightcachesize, ex = int(min(light.o.x+radius, worldsize-1.0f))>>lightcachesize; x <= ex; x++)
-        for(int y = int(max(light.o.y-radius, 0.0f))>>lightcachesize, ey = int(min(light.o.y+radius, worldsize-1.0f))>>lightcachesize; y <= ey; y++)
+        for(int x = int(max(light.o.x-radius, 0.0f))>>lightcachesize, ex = int(min(light.o.x+radius, hdr.worldsize-1.0f))>>lightcachesize; x <= ex; x++)
+        for(int y = int(max(light.o.y-radius, 0.0f))>>lightcachesize, ey = int(min(light.o.y+radius, hdr.worldsize-1.0f))>>lightcachesize; y <= ey; y++)
         {
             lightcacheentry &lce = lightcache[LIGHTCACHEHASH(x, y)];
             if(lce.x != x || lce.y != y) continue;
@@ -858,24 +698,18 @@ const vector<int> &checklightcache(int x, int y)
 
     lce.lights.setsize(0);
     int csize = 1<<lightcachesize, cx = x<<lightcachesize, cy = y<<lightcachesize;
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         const extentity &light = *ents[i];
-        switch(light.type)
+        if(light.type != ET_LIGHT) continue;
+
+        int radius = light.attr1;
+        if(radius > 0)
         {
-            case ET_LIGHT:
-            {
-                int radius = light.attr1;
-                if(radius > 0)
-                {
-                    if(light.o.x + radius < cx || light.o.x - radius > cx + csize ||
-                       light.o.y + radius < cy || light.o.y - radius > cy + csize)
-                        continue;
-                }
-                break;
-            }
-            default: continue;
+            if(light.o.x + radius < cx || light.o.x - radius > cx + csize ||
+               light.o.y + radius < cy || light.o.y - radius > cy + csize)
+                continue;
         }
         lce.lights.add(i);
     }
@@ -941,36 +775,30 @@ static inline void addlight(const extentity &light, int cx, int cy, int cz, int
     if(plane2) lights2.add(&light);
 } 
 
-bool find_lights(int cx, int cy, int cz, int size, const vec *v, const vec *n, const vec *n2, const Slot &slot)
+bool find_lights(int cx, int cy, int cz, int size, const vec *v, const vec *n, const vec *n2)
 {
     lights1.setsize(0);
     lights2.setsize(0);
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     if(size <= 1<<lightcachesize)
     {
         const vector<int> &lights = checklightcache(cx, cy);
         loopv(lights)
         {
             const extentity &light = *ents[lights[i]];
-            switch(light.type)
-            {
-                case ET_LIGHT: addlight(light, cx, cy, cz, size, v, n, n2); break;
-            }
+            addlight(light, cx, cy, cz, size, v, n, n2);
         }
     }
     else loopv(ents)
     {
         const extentity &light = *ents[i];
-        switch(light.type)
-        {
-            case ET_LIGHT: addlight(light, cx, cy, cz, size, v, n, n2); break;
-        }
+        if(light.type != ET_LIGHT) continue;
+        addlight(light, cx, cy, cz, size, v, n, n2);
     }
-    if(slot.layer && (setblendmaporigin(ivec(cx, cy, cz), size) || slot.layermask)) return true;
-    return lights1.length() || lights2.length() || hasskylight();
+    return lights1.length() || lights2.length() || hdr.skylight[0]>ambient || hdr.skylight[1]>ambient || hdr.skylight[2]>ambient;
 }
 
-int setup_surface(plane planes[2], const vec *p, const vec *n, const vec *n2, uchar texcoords[8], bool preview = false)
+bool setup_surface(plane planes[2], const vec *p, const vec *n, const vec *n2, uchar texcoords[8])
 {
     vec u, v, s, t;
     float umin(0.0f), umax(0.0f),
@@ -1047,29 +875,14 @@ int setup_surface(plane planes[2], const vec *p, const vec *n, const vec *n2, uc
     ustep.mul((umax - umin) / (lm_w - 1));
     uint split = vl * lm_h / (vl + tl);
     vstep.mul((vmax - vmin) / (split - 1));
-    int surftype = NO_SURFACE;
-    if(preview)
-    {
-        if(!n2) surftype = preview_lightmap_alpha(lpu, 0, lm_h, origin1, ustep, vstep);
-        else
-        {
-            origin2 = p[0];
-            origin2.add(uo);
-            vec tstep(t);
-            tstep.mul(tmax / (lm_h - split - 1));
-
-            surftype = preview_lightmap_alpha(lpu, 0, split, origin1, ustep, vstep);
-            if(surftype<SURFACE_LIGHTMAP) return surftype;
-            surftype = preview_lightmap_alpha(lpu, split, lm_h, origin2, ustep, tstep);
-        }
-    }
-    else if(!n2)
+    if(!n2)
     {
         lerpvert lv[4];
         int numv = 4;
         calclerpverts(origin1, p, n, ustep, vstep, lv, numv);
 
-        surftype = generate_lightmap(lpu, 0, lm_h, origin1, lv, numv, ustep, vstep);
+        if(!generate_lightmap(lpu, 0, lm_h, origin1, lv, numv, ustep, vstep))
+            return false;
     }
     else
     {
@@ -1085,11 +898,10 @@ int setup_surface(plane planes[2], const vec *p, const vec *n, const vec *n2, uc
         calclerpverts(origin1, p1, n, ustep, vstep, lv1, numv1);
         calclerpverts(origin2, p2, n2, ustep, tstep, lv2, numv2);
 
-        surftype = generate_lightmap(lpu, 0, split, origin1, lv1, numv1, ustep, vstep);
-        if(surftype<SURFACE_LIGHTMAP) return surftype;
-        surftype = generate_lightmap(lpu, split, lm_h, origin2, lv2, numv2, ustep, tstep);
+        if(!generate_lightmap(lpu, 0, split, origin1, lv1, numv1, ustep, vstep) ||
+           !generate_lightmap(lpu, split, lm_h, origin2, lv2, numv2, ustep, tstep))
+            return false;
     }
-    if(surftype<SURFACE_LIGHTMAP) return surftype;
 
     #define CALCVERT(origin, u, v, offset, vert) \
     { \
@@ -1116,22 +928,7 @@ int setup_surface(plane planes[2], const vec *p, const vec *n, const vec *n2, uc
         float tscale = 255.0f / float(tmax - tmin) * float(lm_h - split) / float(lm_h);
         CALCVERT(origin2, u, t, toffset, 3)
     }
-    return surftype;
-}
-
-void removelmalpha()
-{
-    if(!(lmtype&LM_ALPHA)) return;
-    for(uchar *dst = lm, *src = lm, *end = &src[lm_w*lm_h*4];
-        src < end;
-        dst += 3, src += 4)
-    {
-        dst[0] = src[0];
-        dst[1] = src[1];
-        dst[2] = src[2];
-    }
-    lmtype &= ~LM_ALPHA;
-    lmbpp = 3;
+    return true;
 }
 
 void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
@@ -1146,18 +943,11 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
         freenormals(c);
     }
     vvec vvecs[8];
+    bool usefaces[6];
+    int vertused = calcverts(c, cx, cy, cz, size, vvecs, usefaces);
     vec verts[8];
-    int vertused = 0, usefaces[6];
-    loopi(6) if((usefaces[i] = visibletris(c, i, cx, cy, cz, size))) vertused |= fvmasks[1<<i];
-    loopi(8) if(vertused&(1<<i)) 
-    {
-        calcvert(c, cx, cy, cz, size, vvecs[i], i);
-        verts[i] = vvecs[i].tovec(cx, cy, cz);
-    }
-
+    loopi(8) if(vertused&(1<<i)) verts[i] = vvecs[i].tovec(cx, cy, cz);
     int mergeindex = 0;
-    surfaceinfo surfaces[12];
-    int numsurfs = 0;
     loopi(6) if(usefaces[i])
     {
         CHECK_PROGRESS(return);
@@ -1167,11 +957,8 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
         vec v[4], n[4], n2[3];
         int numplanes;
 
-        Slot &slot = lookuptexture(c.texture[i], false),
-             *layer = slot.layer ? &lookuptexture(slot.layer, false) : NULL;
-        Shader *shader = slot.shader;
-        int shadertype = shader->type;
-        if(layer) shadertype |= layer->shader->type;
+        Slot &slot = lookuptexture(c.texture[i], false);
+        Shader *shader = slot.shader ? slot.shader : defaultshader;
         if(c.ext && c.ext->merged&(1<<i))
         {
             if(!(c.ext->mergeorigin&(1<<i))) continue;
@@ -1190,9 +977,9 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
                 findnormal(mo, mv[j], planes[0], n[j]);
             }
 
-            if(!find_lights(mo.x, mo.y, mo.z, 1<<msz, v, n, NULL, slot))
+            if(!find_lights(mo.x, mo.y, mo.z, 1<<msz, v, n, NULL))
             {
-                if(!(shadertype&(SHADER_NORMALSLMS | SHADER_ENVMAP))) continue;
+                if(!(shader->type&(SHADER_NORMALSLMS | SHADER_ENVMAP))) continue;
             }
         }
         else
@@ -1203,20 +990,14 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
             vec avg;
             if(numplanes >= 2)
             {
-                if(!(usefaces[i]&1)) { planes[0] = planes[1]; numplanes--; }
-                else if(!(usefaces[i]&2)) numplanes--;
-                else
-                {
-                    avg = planes[0];
-                    avg.add(planes[1]);
-                    avg.normalize();
-                }
+                avg = planes[0];
+                avg.add(planes[1]);
+                avg.normalize();
             }
 
-            int order = usefaces[i]&4 || faceconvexity(c, i)<0 ? 1 : 0;
             loopj(4)
             {
-                int index = fv[i][(j+order)&3];
+                int index = faceverts(c, i, j);
                 const vvec &vv = vvecs[index];
                 v[j] = verts[index];
                 if(numplanes < 2 || j == 1) findnormal(ivec(cx, cy, cz), vv, planes[0], n[j]);
@@ -1229,16 +1010,17 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
                 }
             }
 
-            if(!(usefaces[i]&1)) { v[1] = v[0]; n[1] = n[0]; }
-            if(!(usefaces[i]&2)) { v[3] = v[0]; n[3] = n[0]; }
-
-            if(!find_lights(cx, cy, cz, size, v, n, numplanes > 1 ? n2 : NULL, slot))
+            if(!find_lights(cx, cy, cz, size, v, n, numplanes > 1 ? n2 : NULL))
             {
-                if(!(shadertype&(SHADER_NORMALSLMS | SHADER_ENVMAP))) continue;
+                if(!(shader->type&(SHADER_NORMALSLMS | SHADER_ENVMAP))) continue;
             }
         }
-        if(shadertype&(SHADER_NORMALSLMS | SHADER_ENVMAP))
+        lmtype = LM_DIFFUSE;
+        lmorient = i;
+        lmrotate = slot.rotation;
+        if(shader->type&(SHADER_NORMALSLMS | SHADER_ENVMAP))
         {
+            if(shader->type&SHADER_NORMALSLMS) lmtype = LM_BUMPMAP0;
             newnormals(c);
             surfacenormals *cn = c.ext->normals;
             cn[i].normals[0] = bvec(n[0]);
@@ -1246,90 +1028,19 @@ void setup_surfaces(cube &c, int cx, int cy, int cz, int size)
             cn[i].normals[2] = bvec(n[2]);
             cn[i].normals[3] = bvec(numplanes < 2 ? n[3] : n2[2]);
         }
-        if(lights1.empty() && lights2.empty() && (!layer || (!hasblendmap() && !slot.layermask)) && !hasskylight()) continue;
-
+        if(lights1.empty() && lights2.empty() && hdr.skylight[0]<=ambient && hdr.skylight[1]<=ambient && hdr.skylight[2]<=ambient) continue;
         uchar texcoords[8];
+        if(!setup_surface(planes, v, n, numplanes >= 2 ? n2 : NULL, texcoords))
+            continue;
 
-        lmslot = &slot;
-        lmtype = shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE;
-        if(layer) lmtype |= LM_ALPHA;
-        lmbpp = lmtype&LM_ALPHA ? 4 : 3;
-        lmorient = i;
-        lmrotate = slot.rotation;
-        int surftype = setup_surface(planes, v, n, numplanes >= 2 ? n2 : NULL, texcoords);
-        switch(surftype)
-        {
-            case SURFACE_LIGHTMAP_BOTTOM:
-                if((shader->type^layer->shader->type)&SHADER_NORMALSLMS ||
-                   (shader->type&SHADER_NORMALSLMS && slot.rotation!=layer->rotation))
-                    break;
-                // fall through
-            case SURFACE_LIGHTMAP_BLEND:
-            case SURFACE_LIGHTMAP_TOP:
-            {
-                CHECK_PROGRESS(return);
-                if(!numsurfs) { numsurfs = 6; memset(surfaces, 0, sizeof(surfaces)); }
-                surfaceinfo &surface = surfaces[i];
-                surface.w = lm_w;
-                surface.h = lm_h;
-                if(surftype==SURFACE_LIGHTMAP_BLEND) surface.layer = LAYER_TOP|LAYER_BLEND;
-                else
-                {
-                    if(surftype==SURFACE_LIGHTMAP_BOTTOM) surface.layer = LAYER_BOTTOM;
-                    if(lmtype&LM_ALPHA) removelmalpha();
-                } 
-                memcpy(surface.texcoords, texcoords, 8);
-                pack_lightmap(surface);
-                if(surftype!=SURFACE_LIGHTMAP_BLEND) continue;
-                if((shader->type^layer->shader->type)&SHADER_NORMALSLMS ||
-                   (shader->type&SHADER_NORMALSLMS && slot.rotation!=layer->rotation)) 
-                    break;
-                surfaces[numsurfs] = surface;
-                surfaces[numsurfs++].layer = LAYER_BOTTOM;
-                continue;
-            }
-
-            case SURFACE_AMBIENT_BOTTOM:
-                if(layer)
-                {
-                    if(!numsurfs) { numsurfs = 6; memset(surfaces, 0, sizeof(surfaces)); }
-                    surfaces[i].layer = LAYER_BOTTOM;
-                }
-                continue;
-
-            default: continue;
-        }
-
-        lmslot = layer;
-        lmtype = layer->shader->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE;
-        lmbpp = 3;
-        lmrotate = layer->rotation;
-        switch(setup_surface(planes, v, n, numplanes >= 2 ? n2 : NULL, texcoords))
-        {
-            case SURFACE_LIGHTMAP_TOP:
-            {
-                CHECK_PROGRESS(return);
-                if(!numsurfs) { numsurfs = 6; memset(surfaces, 0, sizeof(surfaces)); }
-                surfaceinfo &surface = surfaces[surftype==SURFACE_LIGHTMAP_BLEND ? numsurfs++ : i];
-                surface.w = lm_w;
-                surface.h = lm_h;
-                surface.layer = LAYER_BOTTOM;
-                memcpy(surface.texcoords, texcoords, 8);
-                pack_lightmap(surface);
-                break;
-            }
-
-            case SURFACE_AMBIENT_TOP:
-            {
-                if(!numsurfs) { numsurfs = 6; memset(surfaces, 0, sizeof(surfaces)); }
-                surfaceinfo &surface = surfaces[surftype==SURFACE_LIGHTMAP_BLEND ? numsurfs++ : i];
-                memset(&surface, 0, sizeof(surface));
-                surface.layer = LAYER_BOTTOM;
-                break;
-            }
-        }
+        CHECK_PROGRESS(return);
+        newsurfaces(c);
+        surfaceinfo &surface = c.ext->surfaces[i];
+        surface.w = lm_w;
+        surface.h = lm_h;
+        memcpy(surface.texcoords, texcoords, 8);
+        pack_lightmap(lmtype, surface);
     }
-    if(numsurfs) newsurfaces(c, surfaces, numsurfs);
 }
 
 void generate_lightmaps(cube *c, int cx, int cy, int cz, int size)
@@ -1348,182 +1059,6 @@ void generate_lightmaps(cube *c, int cx, int cy, int cz, int size)
     } 
 }
 
-bool previewblends(cube &c, const ivec &co, int size)
-{
-    if(isempty(c)) return false;
-
-    int usefaces[6];
-    int vertused = 0;
-    loopi(6) if((usefaces[i] = lookuptexture(c.texture[i], false).layer ? visibletris(c, i, co.x, co.y, co.z, size) : 0))
-        vertused |= fvmasks[1<<i];
-    if(!vertused) return false;
-
-    if(!setblendmaporigin(co, size))
-    {
-        if(!c.ext || !c.ext->surfaces || c.ext->surfaces==brightsurfaces) return false;
-        bool blends = false;
-        loopi(6) if(c.ext->surfaces[i].layer&LAYER_BLEND || c.ext->surfaces[i].layer==LAYER_BOTTOM)
-        {
-            surfaceinfo &surface = c.ext->surfaces[i];
-            memset(&surface, 0, sizeof(surfaceinfo));
-            surface.lmid = LMID_BRIGHT;
-            surface.layer = LAYER_TOP;
-            blends = true;
-        }
-        return blends;
-    }
-
-    vec verts[8];
-    loopi(8) if(vertused&(1<<i)) 
-    {
-        vvec vv;
-        calcvert(c, co.x, co.y, co.z, size, vv, i);
-        verts[i] = vv.tovec(co);
-    }
-
-    surfaceinfo surfaces[12], *srcsurfaces = c.ext && c.ext->surfaces && c.ext->surfaces!=brightsurfaces ? c.ext->surfaces : NULL;
-    int numsurfs = srcsurfaces ? 6 : 0, numsrcsurfs = srcsurfaces ? 6 : 0;
-    if(srcsurfaces) memcpy(surfaces, srcsurfaces, 6*sizeof(surfaceinfo));
-    else 
-    {
-        memset(surfaces, 0, 6*sizeof(surfaceinfo));
-        loopi(6) surfaces[i].lmid = LMID_BRIGHT;
-    }
-    loopi(6)
-    {
-        if(surfaces[i].layer&LAYER_BLEND) 
-        {
-            if(!usefaces[i]) 
-            {
-                surfaces[numsurfs++] = srcsurfaces[numsrcsurfs++];
-                continue;
-            }
-            numsrcsurfs++;
-        }
-        else if(!usefaces[i]) continue;
-
-        plane planes[2];
-        int numplanes = genclipplane(c, i, verts, planes);
-        if(!numplanes) continue;
-
-        Slot &slot = lookuptexture(c.texture[i], false),
-             &layer = lookuptexture(slot.layer, false);
-        Shader *shader = slot.shader;
-        int shadertype = shader->type | layer.shader->type;
-            
-        int order = usefaces[i]&4 || faceconvexity(c, i)<0 ? 1 : 0;
-        vec v[4];
-        loopk(4) v[k] = verts[fv[i][(k+order)&3]];
-        if(!(usefaces[i]&1)) { v[1] = v[0]; if(numplanes>1) { planes[0] = planes[1]; --numplanes; } }
-        if(!(usefaces[i]&2)) { v[3] = v[0]; if(numplanes>1) --numplanes; }
-
-        static const vec n[4] = { vec(0, 0, 1), vec(0, 0, 1), vec(0, 0, 1), vec(0, 0, 1) };
-        uchar texcoords[8];
-
-        lmslot = &slot;
-        lmtype = shadertype&SHADER_NORMALSLMS ? LM_BUMPMAP0|LM_ALPHA : LM_DIFFUSE|LM_ALPHA;
-        lmbpp = 4;
-        lmorient = i;
-        lmrotate = slot.rotation;
-        int surftype = setup_surface(planes, v, n, numplanes >= 2 ? n : NULL, texcoords, true);
-        switch(surftype)
-        {
-            case SURFACE_AMBIENT_TOP:
-                if(srcsurfaces) 
-                {
-                    memset(&surfaces[i], 0, sizeof(surfaceinfo));
-                    surfaces[i].lmid = LMID_BRIGHT;
-                }
-                continue;
-
-            case SURFACE_AMBIENT_BOTTOM:
-                if(!numsurfs) numsurfs = 6;
-                if(srcsurfaces) 
-                {
-                    memset(&surfaces[i], 0, sizeof(surfaceinfo));
-                    surfaces[i].lmid = LMID_BRIGHT;
-                }
-                surfaces[i].layer = LAYER_BOTTOM;
-                continue;
-
-            case SURFACE_LIGHTMAP_BLEND:
-            {
-                if(!numsurfs) numsurfs = 6;
-                surfaceinfo &surface = surfaces[i];
-                if(surface.w==lm_w && surface.h==lm_h && 
-                   surface.layer==(LAYER_TOP|LAYER_BLEND) && 
-                   !memcmp(surface.texcoords, texcoords, 8) &&
-                   lightmaps.inrange(surface.lmid-LMID_RESERVED) &&
-                   lightmaps[surface.lmid-LMID_RESERVED].type==lmtype)           
-                {
-                    copy_lightmap(surface);
-                    update_lightmap(surface);
-                    surfaces[numsurfs] = surface;
-                    surfaces[numsurfs++].layer = LAYER_BOTTOM;
-                    continue;
-                }
-                surface.w = lm_w;
-                surface.h = lm_h;
-                surface.layer = LAYER_TOP|LAYER_BLEND;
-                memcpy(surface.texcoords, texcoords, 8);
-                if(pack_lightmap(surface)) update_lightmap(surface);
-                surfaces[numsurfs] = surface;
-                surfaces[numsurfs++].layer = LAYER_BOTTOM;
-                continue;
-            }
-        }
-    }
-    if(numsurfs>numsrcsurfs) 
-    {
-        freesurfaces(c);
-        newsurfaces(c, surfaces, numsurfs);
-        return true;
-    }
-    else if(numsurfs!=numsrcsurfs || memcmp(srcsurfaces, surfaces, numsurfs*sizeof(surfaceinfo))) 
-    {
-        if(!numsurfs) brightencube(c);
-        else memcpy(srcsurfaces, surfaces, numsurfs*sizeof(surfaceinfo));
-        return true;
-    }
-    else return false;
-}
-
-static bool previewblends(cube *c, const ivec &co, int size, const ivec &bo, const ivec &bs)
-{
-    bool changed = false;
-    loopoctabox(co, size, bo, bs)
-    {
-        ivec o(i, co.x, co.y, co.z, size);
-        cubeext *ext = c[i].ext;
-        if(ext && ext->va && ext->va->hasmerges)
-        {
-            destroyva(ext->va);
-            ext->va = NULL;
-            invalidatemerges(c[i], true);
-            changed = true;
-        }
-        if(c[i].children ? previewblends(c[i].children, o, size/2, bo, bs) : previewblends(c[i], o, size))  
-        {
-            changed = true;
-            if(ext && ext->va)
-            {
-                int hasmerges = ext->va->hasmerges;
-                destroyva(ext->va);
-                ext->va = NULL;
-                if(hasmerges) invalidatemerges(c[i], true);
-            }
-        }
-    }
-    return changed;
-}
-
-void previewblends(const ivec &bo, const ivec &bs)
-{
-    loadlayermasks();
-    if(previewblends(worldroot, ivec(0, 0, 0), worldsize/2, bo, bs))
-        commitchanges(true);
-}
-                            
 void cleanuplightmaps()
 {
     loopv(lightmaps)
@@ -1553,9 +1088,12 @@ bool setlightmapquality(int quality)
 {
     switch(quality)
     {
-        case  1: lmshadows = 2; lmaa = 3; break;
-        case  0: lmshadows = lmshadows_; lmaa = lmaa_; break;
-        case -1: lmshadows = 1; lmaa = 0; break;
+        case  3: shadows = 1; aalights = 3; mmshadows = 2; break;
+        case  2: shadows = 1; aalights = 3; mmshadows = 1; break;
+        case  1: shadows = 1; aalights = 3; mmshadows = 0; break;
+        case  0: shadows = 1; aalights = 2; mmshadows = 0; break;
+        case -1: shadows = 1; aalights = 1; mmshadows = 0; break;
+        case -2: shadows = 0; aalights = 0; mmshadows = 0; break;
         default: return false;
     }
     return true;
@@ -1565,13 +1103,11 @@ void calclight(int *quality)
 {
     if(!setlightmapquality(*quality))
     {
-        conoutf(CON_ERROR, "valid range for calclight quality is -1..1"); 
+        conoutf(CON_ERROR, "valid range for calclight quality is -2..3"); 
         return;
     }
-    renderbackground("computing lightmaps... (esc to abort)");
+    computescreen("computing lightmaps... (esc to abort)");
     mpremip(true);
-    optimizeblendmap();
-    loadlayermasks();
     resetlightmaps();
     clear_lmids(worldroot);
     curlumels = 0;
@@ -1583,7 +1119,7 @@ void calclight(int *quality)
     Uint32 start = SDL_GetTicks();
     calcnormals();
     show_calclight_progress();
-    generate_lightmaps(worldroot, 0, 0, 0, worldsize >> 1);
+    generate_lightmaps(worldroot, 0, 0, 0, hdr.worldsize >> 1);
     clearnormals();
     Uint32 end = SDL_GetTicks();
     if(timer) SDL_RemoveTimer(timer);
@@ -1597,7 +1133,7 @@ void calclight(int *quality)
     }
     if(!editmode) compressed.clear();
     initlights();
-    renderbackground("lighting done...");
+    computescreen("lighting done...");
     allchanged();
     if(calclight_canceled)
         conoutf("calclight aborted");
@@ -1618,11 +1154,10 @@ void patchlight(int *quality)
     if(noedit(true)) return;
     if(!setlightmapquality(*quality))
     {
-        conoutf(CON_ERROR, "valid range for patchlight quality is -1..1"); 
+        conoutf(CON_ERROR, "valid range for patchlight quality is -2..3"); 
         return;
     }
-    renderbackground("patching lightmaps... (esc to abort)");
-    loadlayermasks();
+    computescreen("patching lightmaps... (esc to abort)");
     cleanuplightmaps();
     progress = 0;
     progresstexticks = 0;
@@ -1636,11 +1171,11 @@ void patchlight(int *quality)
     calclight_canceled = false;
     check_calclight_progress = false;
     SDL_TimerID timer = SDL_AddTimer(250, calclight_timer, NULL);
-    if(patchnormals) renderprogress(0, "computing normals...");
+    if(patchnormals) show_out_of_renderloop_progress(0, "computing normals...");
     Uint32 start = SDL_GetTicks();
     if(patchnormals) calcnormals();
     show_calclight_progress();
-    generate_lightmaps(worldroot, 0, 0, 0, worldsize >> 1);
+    generate_lightmaps(worldroot, 0, 0, 0, hdr.worldsize >> 1);
     if(patchnormals) clearnormals();
     Uint32 end = SDL_GetTicks();
     if(timer) SDL_RemoveTimer(timer);
@@ -1650,7 +1185,7 @@ void patchlight(int *quality)
         lumels += lightmaps[i].lumels;
     }
     initlights();
-    renderbackground("lighting done...");
+    computescreen("lighting done...");
     allchanged();
     if(calclight_canceled)
         conoutf("patchlight aborted");
@@ -1669,7 +1204,7 @@ void setfullbrightlevel(int fullbrightlevel)
     if(lightmaptexs.length() > LMID_BRIGHT)
     {
         uchar bright[3] = { fullbrightlevel, fullbrightlevel, fullbrightlevel };
-        createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1);
+        createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, false);
     }
     initlights();
 }
@@ -1713,10 +1248,10 @@ static void rotatenormals(cube *c)
         loopj(6) if(lightmaps.inrange(ch.ext->surfaces[j].lmid+1-LMID_RESERVED))
         {
             Slot &slot = lookuptexture(ch.texture[j], false);
-            if(!slot.rotation) continue;
+            if(!slot.rotation || !slot.shader || !(slot.shader->type&SHADER_NORMALSLMS))
+                continue;
             surfaceinfo &surface = ch.ext->surfaces[j];
             LightMap &lmlv = lightmaps[surface.lmid+1-LMID_RESERVED];
-            if((lmlv.type&LM_TYPE)!=LM_BUMPMAP1) continue;
             rotatenormals(lmlv, surface.x, surface.y, surface.w, surface.h, slot.rotation < 4 ? 4-slot.rotation : slot.rotation);
         }
     }
@@ -1740,13 +1275,12 @@ static void convertlightmap(LightMap &lmc, LightMap &lmlv, uchar *dst, size_t st
                 r = (int(c[0]) * z) / 255,
                 g = (int(c[1]) * z) / 255,
                 b = (int(c[2]) * z) / 255;
-            dstrow[0] = max(r, int(ambientcolor[0]));
-            dstrow[1] = max(g, int(ambientcolor[1]));
-            dstrow[2] = max(b, int(ambientcolor[2]));
-            if(lmc.bpp==4) dstrow[3] = c[3];
-            c += lmc.bpp;
+            dstrow[0] = max(r, ambient);
+            dstrow[1] = max(g, ambient);
+            dstrow[2] = max(b, ambient);
+            c += 3;
             lv++;
-            dstrow += lmc.bpp;
+            dstrow += 3;
         }
         dst += stride;
     }
@@ -1757,8 +1291,8 @@ static void copylightmap(LightMap &lm, uchar *dst, size_t stride)
     const uchar *c = lm.data;
     loopi(LM_PACKH)
     {
-        memcpy(dst, c, lm.bpp*LM_PACKW);
-        c += lm.bpp*LM_PACKW;
+        memcpy(dst, c, 3*LM_PACKW);
+        c += 3*LM_PACKW;
         dst += stride;
     }
 }
@@ -1773,66 +1307,64 @@ void genreservedlightmaptexs()
         tex.type = renderpath != R_FIXEDFUNCTION && lightmaptexs.length()&1 ? LM_DIFFUSE : LM_BUMPMAP1;
         glGenTextures(1, &tex.id);
     }
-    uchar unlit[3] = { ambientcolor[0], ambientcolor[1], ambientcolor[2] };
-    createtexture(lightmaptexs[LMID_AMBIENT].id, 1, 1, unlit, 0, 1);
+    uchar unlit[3] = { ambient, ambient, ambient };
+    createtexture(lightmaptexs[LMID_AMBIENT].id, 1, 1, unlit, 0, false);
     bvec front(128, 128, 255);
-    createtexture(lightmaptexs[LMID_AMBIENT1].id, 1, 1, &front, 0, 1);
-    uchar bright[3] = { fullbrightlevel, fullbrightlevel, fullbrightlevel };
-    createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, 1);
-    createtexture(lightmaptexs[LMID_BRIGHT1].id, 1, 1, &front, 0, 1);
+    createtexture(lightmaptexs[LMID_AMBIENT1].id, 1, 1, &front, 0, false);
+    uchar bright[3] = { 128, 128, 128 };
+    createtexture(lightmaptexs[LMID_BRIGHT].id, 1, 1, bright, 0, false);
+    createtexture(lightmaptexs[LMID_BRIGHT1].id, 1, 1, &front, 0, false);
     uchar dark[3] = { 0, 0, 0 };
-    createtexture(lightmaptexs[LMID_DARK].id, 1, 1, dark, 0, 1);
-    createtexture(lightmaptexs[LMID_DARK1].id, 1, 1, &front, 0, 1);
+    createtexture(lightmaptexs[LMID_DARK].id, 1, 1, dark, 0, false);
+    createtexture(lightmaptexs[LMID_DARK1].id, 1, 1, &front, 0, false);
 }
 
 static void findunlit(int i)
 {
     LightMap &lm = lightmaps[i];
     if(lm.unlitx>=0) return;
-    else if((lm.type&LM_TYPE)==LM_BUMPMAP0)
+    else if(lm.type==LM_BUMPMAP0)
     {
-        if(i+1>=lightmaps.length() || (lightmaps[i+1].type&LM_TYPE)!=LM_BUMPMAP1) return;
+        if(i+1>=lightmaps.length() || lightmaps[i+1].type!=LM_BUMPMAP1) return;
     }
-    else if((lm.type&LM_TYPE)!=LM_DIFFUSE) return;
+    else if(lm.type!=LM_DIFFUSE) return;
     uchar *data = lm.data;
     loop(y, 2) loop(x, LM_PACKW)
     {
         if(!data[0] && !data[1] && !data[2])
         {
-            memcpy(data, ambientcolor.v, 3);
-            if((lm.type&LM_TYPE)==LM_BUMPMAP0) ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] = bvec(128, 128, 255);
+            data[0] = data[1] = data[2] = hdr.ambient;
+            if(lm.type==LM_BUMPMAP0) ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] = bvec(128, 128, 255);
             lm.unlitx = x;
             lm.unlity = y;
             return;
         }
-        if(data[0]==ambientcolor[0] && data[1]==ambientcolor[1] && data[2]==ambientcolor[2])
+        if(data[0]==hdr.ambient && data[1]==hdr.ambient && data[2]==hdr.ambient)
         {
-            if((lm.type&LM_TYPE)!=LM_BUMPMAP0 || ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] == bvec(128, 128, 255))
+            if(lm.type!=LM_BUMPMAP0 || ((bvec *)lightmaps[i+1].data)[y*LM_PACKW + x] == bvec(128, 128, 255))
             {
                 lm.unlitx = x;
                 lm.unlity = y;
                 return;
             }
         }
-        data += lm.bpp;
+        data += 3;
     }
 }
 
 VARF(roundlightmaptex, 0, 4, 16, { cleanuplightmaps(); initlights(); allchanged(); });
 VARF(batchlightmaps, 0, 4, 256, { cleanuplightmaps(); initlights(); allchanged(); });
 
-void genlightmaptexs(int flagmask, int flagval)
+void genlightmaptexs()
 {
     if(lightmaptexs.length() < LMID_RESERVED) genreservedlightmaptexs();
 
-    int remaining[3] = { 0, 0, 0 }, total = 0; 
+    int remaining[3] = { 0, 0, 0 }; 
     loopv(lightmaps) 
     {
         LightMap &lm = lightmaps[i];
-        if(lm.tex >= 0 || (lm.type&flagmask)!=flagval) continue;
-        int type = lm.type&LM_TYPE;
-        remaining[type]++; 
-        total++;
+        if(lm.tex >= 0) continue;
+        remaining[lm.type]++; 
         if(lm.unlitx < 0) findunlit(i);
     }
 
@@ -1842,18 +1374,19 @@ void genlightmaptexs(int flagmask, int flagval)
         remaining[LM_BUMPMAP0] = remaining[LM_BUMPMAP1] = 0;
     }
 
+    extern int maxtexsize;
     int sizelimit = (maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize)/max(LM_PACKW, LM_PACKH);
     sizelimit = min(batchlightmaps, sizelimit*sizelimit);
-    while(total)
+    while(remaining[LM_DIFFUSE] || remaining[LM_BUMPMAP0] || remaining[LM_BUMPMAP1])
     {
         int type = LM_DIFFUSE;
         LightMap *firstlm = NULL;
         loopv(lightmaps)
         {
             LightMap &lm = lightmaps[i];
-            if(lm.tex >= 0 || (lm.type&flagmask) != flagval) continue;
-            if(renderpath != R_FIXEDFUNCTION) type = lm.type&LM_TYPE;
-            else if((lm.type&LM_TYPE) == LM_BUMPMAP1) continue;
+            if(lm.tex >= 0) continue;
+            if(renderpath != R_FIXEDFUNCTION) type = lm.type;
+            else if(lm.type != LM_DIFFUSE && lm.type != LM_BUMPMAP0) continue;
             firstlm = &lm; 
             break; 
         }
@@ -1861,30 +1394,27 @@ void genlightmaptexs(int flagmask, int flagval)
         int used = 0, uselimit = min(remaining[type], sizelimit);
         do used++; while((1<<used) <= uselimit);
         used--;
-        int oldval = remaining[type];
         remaining[type] -= 1<<used;
         if(remaining[type] && (2<<used) <= min(roundlightmaptex, sizelimit))
         {
             remaining[type] -= min(remaining[type], 1<<used);
             used++;
         }
-        total -= oldval - remaining[type];
         LightMapTexture &tex = lightmaptexs.add();
-        tex.type = firstlm->type;
+        tex.type = type;
         tex.w = LM_PACKW<<((used+1)/2);
         tex.h = LM_PACKH<<(used/2);
-        int bpp = firstlm->bpp;
-        uchar *data = used || (renderpath == R_FIXEDFUNCTION && (firstlm->type&LM_TYPE) == LM_BUMPMAP0 && convertlms) ? 
-            new uchar[bpp*tex.w*tex.h] : 
+        uchar *data = used || (renderpath == R_FIXEDFUNCTION && firstlm->type == LM_BUMPMAP0 && convertlms) ? 
+            new uchar[3*tex.w*tex.h] : 
             NULL;
         int offsetx = 0, offsety = 0;
         loopv(lightmaps)
         {
             LightMap &lm = lightmaps[i];
-            if(lm.tex >= 0 || (lm.type&flagmask) != flagval || 
-               (renderpath==R_FIXEDFUNCTION ? 
-                (lm.type&LM_TYPE) == LM_BUMPMAP1 : 
-                (lm.type&LM_TYPE) != type))
+            if(lm.tex >= 0 ||
+               (renderpath == R_FIXEDFUNCTION ? 
+                    lm.type != LM_DIFFUSE && lm.type != LM_BUMPMAP0 : 
+                    lm.type != type))
                 continue;
 
             lm.tex = lightmaptexs.length()-1;
@@ -1898,9 +1428,9 @@ void genlightmaptexs(int flagmask, int flagval)
 
             if(data)
             {
-                if(renderpath == R_FIXEDFUNCTION && (lm.type&LM_TYPE) == LM_BUMPMAP0 && convertlms)
-                    convertlightmap(lm, lightmaps[i+1], &data[bpp*(offsety*tex.w + offsetx)], bpp*tex.w);
-                else copylightmap(lm, &data[bpp*(offsety*tex.w + offsetx)], bpp*tex.w);
+                if(renderpath == R_FIXEDFUNCTION && lm.type == LM_BUMPMAP0 && convertlms)
+                    convertlightmap(lm, lightmaps[i+1], &data[3*(offsety*tex.w + offsetx)], 3*tex.w);
+                else copylightmap(lm, &data[3*(offsety*tex.w + offsetx)], 3*tex.w);
             }
 
             offsetx += LM_PACKW;
@@ -1909,7 +1439,7 @@ void genlightmaptexs(int flagmask, int flagval)
         }
         
         glGenTextures(1, &tex.id);
-        createtexture(tex.id, tex.w, tex.h, data ? data : firstlm->data, 3, 1, bpp==4 ? GL_RGBA : GL_RGB);
+        createtexture(tex.id, tex.w, tex.h, data ? data : firstlm->data, 3, false);
         if(data) delete[] data;
     }        
 }
@@ -1919,7 +1449,7 @@ bool brightengeom = false;
 void clearlights()
 {
     clearlightcache();
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
@@ -1928,15 +1458,14 @@ void clearlights()
     }
     if(nolights) return;
 
-    genlightmaptexs(LM_ALPHA, 0);
-    genlightmaptexs(LM_ALPHA, LM_ALPHA);
+    genlightmaptexs();
     brightengeom = true;
 }
 
 void lightent(extentity &e, float height)
 {
     if(e.type==ET_LIGHT) return;
-    float ambient = 0.0f;
+    float ambient = hdr.ambient/255.0f;
     if(e.type==ET_MAPMODEL)
     {
         model *m = loadmodel(NULL, e.attr2);
@@ -1949,7 +1478,7 @@ void lightent(extentity &e, float height)
 
 void updateentlighting()
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents) lightent(*ents[i]);
 }
 
@@ -1963,8 +1492,7 @@ void initlights()
 
     clearlightcache();
     updateentlighting();
-    genlightmaptexs(LM_ALPHA, 0);
-    genlightmaptexs(LM_ALPHA, LM_ALPHA);
+    genlightmaptexs();
     brightengeom = false;
 }
 
@@ -1978,7 +1506,7 @@ void lightreaching(const vec &target, vec &color, vec &dir, extentity *t, float
     }
 
     color = dir = vec(0, 0, 0);
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     const vector<int> &lights = checklightcache(int(target.x), int(target.y));
     loopv(lights)
     {
@@ -2020,15 +1548,15 @@ void lightreaching(const vec &target, vec &color, vec &dir, extentity *t, float
         else dir.add(vec(e.o).sub(target).mul(intensity/mag));
     }
 
-    if(t && hasskylight())
+    if(t && (hdr.skylight[0]>ambient || hdr.skylight[1]>ambient || hdr.skylight[2]>ambient))
     {
         uchar skylight[3];
-        calcskylight(target, vec(0, 0, 0), 0.5f, skylight, RAY_POLY, t);
+        calcskylight(target, vec(0, 0, 0), 0.5f, skylight, 1, t);
         loopk(3) color[k] = min(1.5f, max(max(skylight[k]/255.0f, ambient), color[k]));
     }
     else loopk(3)
     {
-        float skylight = 0.75f*max(max(skylightcolor[k], ambientcolor[k])/255.0f, ambient) + 0.25f*max(ambientcolor[k]/255.0f, ambient);
+        float skylight = 0.75f*max(hdr.skylight[k]/255.0f, ambient) + 0.25f*ambient;
         color[k] = min(1.5f, max(skylight, color[k]));
     }
     if(dir.iszero()) dir = vec(0, 0, 1);
@@ -2037,7 +1565,7 @@ void lightreaching(const vec &target, vec &color, vec &dir, extentity *t, float
 
 entity *brightestlight(const vec &target, const vec &dir)
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     const vector<int> &lights = checklightcache(int(target.x), int(target.y));
     extentity *brightest = NULL;
     float bintensity = 0;
@@ -2077,6 +1605,16 @@ entity *brightestlight(const vec &target, const vec &dir)
     return brightest;
 }
 
+static surfaceinfo brightsurfaces[6] =
+{
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+    {{0}, 0, 0, 0, 0, LMID_BRIGHT},
+};
+
 void brightencube(cube &c)
 {
     if(c.ext && c.ext->surfaces)
@@ -2087,13 +1625,13 @@ void brightencube(cube &c)
     ext(c).surfaces = brightsurfaces;
 }
         
-void newsurfaces(cube &c, const surfaceinfo *surfs, int numsurfs)
+void newsurfaces(cube &c)
 {
     if(!c.ext) newcubeext(c);
     if(!c.ext->surfaces || c.ext->surfaces==brightsurfaces)
     {
-        c.ext->surfaces = new surfaceinfo[numsurfs];
-        memcpy(c.ext->surfaces, surfs, numsurfs*sizeof(surfaceinfo));
+        c.ext->surfaces = new surfaceinfo[6];
+        memset(c.ext->surfaces, 0, 6*sizeof(surfaceinfo));
     }
 }
 
@@ -2108,14 +1646,24 @@ void freesurfaces(cube &c)
 
 void dumplms()
 {
-    loopv(lightmaps)
+    SDL_Surface *temp;
+    if((temp = SDL_CreateRGBSurface(SDL_SWSURFACE, LM_PACKW, LM_PACKH, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0)))
     {
-        ImageData temp(LM_PACKW, LM_PACKH, lightmaps[i].bpp, lightmaps[i].data);
-        const char *map = game::getclientmap(), *name = strrchr(map, '/');
-        defformatstring(buf)("lightmap_%s_%d.png", name ? name+1 : map, i);
-        savepng(buf, temp, true);
+        loopv(lightmaps)
+        {
+            for(int idx = 0; idx<LM_PACKH; idx++)
+            {
+                uchar *dest = (uchar *)temp->pixels+temp->pitch*idx;
+                memcpy(dest, lightmaps[i].data+3*LM_PACKW*(LM_PACKH-1-idx), 3*LM_PACKW);
+            }
+            char *map = cl->getclientmap(), *name = strrchr(map, '/');
+            s_sprintfd(buf)("lightmap_%s_%d.bmp", name ? name+1 : map, i);
+            SDL_SaveBMP(temp, buf);
+        }
+        SDL_FreeSurface(temp);
     }
 }
 
 COMMAND(dumplms, "");
 
+
diff --git a/engine/lightmap.h b/engine/lightmap.h
index 5d3af09..06382d2 100644
--- a/engine/lightmap.h
+++ b/engine/lightmap.h
@@ -28,35 +28,21 @@ struct PackNode
     bool insert(ushort &tx, ushort &ty, ushort tw, ushort th);
 };
 
-enum 
-{ 
-    LM_DIFFUSE = 0, 
-    LM_BUMPMAP0, 
-    LM_BUMPMAP1, 
-    LM_TYPE = 0x0F,
-
-    LM_ALPHA = 1<<4,  
-    LM_FLAGS = 0xF0 
-};
+enum { LM_DIFFUSE = 0, LM_BUMPMAP0, LM_BUMPMAP1 };
 
 struct LightMap
 {
-    int type, bpp, tex, offsetx, offsety;
+    int type, tex, offsetx, offsety;
     PackNode packroot;
     uint lightmaps, lumels;
     int unlitx, unlity; 
-    uchar *data;
+    uchar data[3 * LM_PACKW * LM_PACKH];
 
     LightMap()
-     : type(LM_DIFFUSE), bpp(3), tex(-1), offsetx(-1), offsety(-1),
-       lightmaps(0), lumels(0), unlitx(-1), unlity(-1),
-       data(NULL)
+     : type(LM_DIFFUSE), tex(-1), offsetx(-1), offsety(-1),
+       lightmaps(0), lumels(0), unlitx(-1), unlity(-1)
     {
-    }
-
-    ~LightMap()
-    {
-        if(data) delete[] data;
+        memset(data, 0, sizeof(data));
     }
 
     void finalize()
@@ -65,7 +51,6 @@ struct LightMap
         packroot.available = 0;
     }
 
-    void copy(ushort tx, ushort ty, uchar *src, ushort tw, ushort th);
     bool insert(ushort &tx, ushort &ty, uchar *src, ushort tw, ushort th);
 };
 
@@ -86,16 +71,13 @@ extern vector<LightMapTexture> lightmaptexs;
 
 enum { LMID_AMBIENT = 0, LMID_AMBIENT1, LMID_BRIGHT, LMID_BRIGHT1, LMID_DARK, LMID_DARK1, LMID_RESERVED };
 
-extern bvec ambientcolor, skylightcolor;
-
 extern void clearlights();
 extern void initlights();
 extern void clearlightcache(int e = -1);
 extern void resetlightmaps();
-extern void newsurfaces(cube &c, const surfaceinfo *surfs, int numsurfs);
+extern void newsurfaces(cube &c);
 extern void freesurfaces(cube &c);
 extern void brightencube(cube &c);
-extern void previewblends(const ivec &bo, const ivec &bs);
 
 struct lerpvert
 {
diff --git a/engine/lightning.h b/engine/lightning.h
index ec90cdc..98b133a 100644
--- a/engine/lightning.h
+++ b/engine/lightning.h
@@ -63,7 +63,7 @@ static void renderlightning(const vec &o, const vec &d, float sz, float tx, floa
 struct lightningrenderer : listrenderer
 {
     lightningrenderer()
-        : listrenderer("packages/particles/lightning.jpg", PT_LIGHTNING|PT_TRACK|PT_GLARE)
+        : listrenderer("packages/particles/lightning.jpg", PT_LIGHTNING|PT_TRACK|PT_GLARE, 0, 0)
     {}
 
     void startrender()
@@ -81,13 +81,6 @@ struct lightningrenderer : listrenderer
         setuplightning();
     }
 
-    void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity)
-    {
-        pe.maxfade = max(pe.maxfade, fade);
-        pe.extendbb(o, size);
-        pe.extendbb(d, size);
-    }
-
     void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts, uchar *color)
     {
         blend = min(blend<<2, 255);
diff --git a/engine/main.cpp b/engine/main.cpp
index 5f78d37..cfbd28b 100644
--- a/engine/main.cpp
+++ b/engine/main.cpp
@@ -1,14 +1,12 @@
 // main.cpp: initialisation & main loop
 
+#include "pch.h"
 #include "engine.h"
 
 void cleanup()
 {
-    recorder::stop();
     cleanupserver();
     SDL_ShowCursor(1);
-    SDL_WM_GrabInput(SDL_GRAB_OFF);
-    SDL_SetGamma(1, 1, 1);
     freeocta(worldroot);
     extern void clear_command(); clear_command();
     extern void clear_console(); clear_console();
@@ -23,8 +21,7 @@ void quit()                     // normal exit
     writeinitcfg();
     writeservercfg();
     abortconnect();
-    disconnect();
-    localdisconnect();
+    disconnect(1);
     writecfg();
     cleanup();
     exit(EXIT_SUCCESS);
@@ -37,19 +34,14 @@ void fatal(const char *s, ...)    // failure exit
 
     if(errors <= 2) // print up to one extra recursive error
     {
-        defvformatstring(msg,s,s);
+        s_sprintfdlv(msg,s,s);
         puts(msg);
 
         if(errors <= 1) // avoid recursion
         {
-            if(SDL_WasInit(SDL_INIT_VIDEO))
-            {
-                SDL_ShowCursor(1);
-                SDL_WM_GrabInput(SDL_GRAB_OFF);
-                SDL_SetGamma(1, 1, 1);
-            }
+            SDL_ShowCursor(1);
             #ifdef WIN32
-                MessageBox(NULL, msg, "Cube 2: Sauerbraten fatal error", MB_OK|MB_SYSTEMMODAL);
+                MessageBox(NULL, msg, "sauerbraten fatal error", MB_OK|MB_SYSTEMMODAL);
             #endif
             SDL_Quit();
         }
@@ -60,11 +52,12 @@ void fatal(const char *s, ...)    // failure exit
 
 SDL_Surface *screen = NULL;
 
-int curtime = 0, totalmillis = 1, lastmillis = 1;
+int curtime;
+int totalmillis = 0, lastmillis = 0;
 
 dynent *player = NULL;
 
-int initing = NOT_INITING;
+static int initing = NOT_INITING;
 static bool restoredinits = false;
 
 bool initwarning(const char *desc, int level, int type)
@@ -77,48 +70,69 @@ bool initwarning(const char *desc, int level, int type)
     return false;
 }
 
-#define SCR_MINW 320
-#define SCR_MINH 200
-#define SCR_MAXW 10000
-#define SCR_MAXH 10000
-#define SCR_DEFAULTW 1024
-#define SCR_DEFAULTH 768
-VARF(scr_w, SCR_MINW, -1, SCR_MAXW, initwarning("screen resolution"));
-VARF(scr_h, SCR_MINH, -1, SCR_MAXH, initwarning("screen resolution"));
+VARF(scr_w, 320, 1024, 10000, initwarning("screen resolution"));
+VARF(scr_h, 200, 768, 10000, initwarning("screen resolution"));
 VARF(colorbits, 0, 0, 32, initwarning("color depth"));
 VARF(depthbits, 0, 0, 32, initwarning("depth-buffer precision"));
-VARF(stencilbits, 0, 0, 32, initwarning("stencil-buffer precision"));
+VARF(stencilbits, 0, 1, 32, initwarning("stencil-buffer precision"));
 VARF(fsaa, -1, -1, 16, initwarning("anti-aliasing"));
 VARF(vsync, -1, -1, 1, initwarning("vertical sync"));
 
 void writeinitcfg()
 {
     if(!restoredinits) return;
-    stream *f = openfile("init.cfg", "w");
+    FILE *f = openfile("init.cfg", "w");
     if(!f) return;
-    f->printf("// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
+    fprintf(f, "// automatically written on exit, DO NOT MODIFY\n// modify settings in game\n");
     extern int fullscreen;
-    f->printf("fullscreen %d\n", fullscreen);
-    f->printf("scr_w %d\n", scr_w);
-    f->printf("scr_h %d\n", scr_h);
-    f->printf("colorbits %d\n", colorbits);
-    f->printf("depthbits %d\n", depthbits);
-    f->printf("stencilbits %d\n", stencilbits);
-    f->printf("fsaa %d\n", fsaa);
-    f->printf("vsync %d\n", vsync);
+    fprintf(f, "fullscreen %d\n", fullscreen);
+    fprintf(f, "scr_w %d\n", scr_w);
+    fprintf(f, "scr_h %d\n", scr_h);
+    fprintf(f, "colorbits %d\n", colorbits);
+    fprintf(f, "depthbits %d\n", depthbits);
+    fprintf(f, "stencilbits %d\n", stencilbits);
+    fprintf(f, "fsaa %d\n", fsaa);
+    fprintf(f, "vsync %d\n", vsync);
     extern int useshaders, shaderprecision;
-    f->printf("shaders %d\n", useshaders);
-    f->printf("shaderprecision %d\n", shaderprecision);
+    fprintf(f, "shaders %d\n", useshaders);
+    fprintf(f, "shaderprecision %d\n", shaderprecision);
     extern int soundchans, soundfreq, soundbufferlen;
-    f->printf("soundchans %d\n", soundchans);
-    f->printf("soundfreq %d\n", soundfreq);
-    f->printf("soundbufferlen %d\n", soundbufferlen);
-    delete f;
+    fprintf(f, "soundchans %d\n", soundchans);
+    fprintf(f, "soundfreq %d\n", soundfreq);
+    fprintf(f, "soundbufferlen %d\n", soundbufferlen);
+    fclose(f);
+}
+
+void screenshot(char *filename)
+{
+    SDL_Surface *image = SDL_CreateRGBSurface(SDL_SWSURFACE, screen->w, screen->h, 24, 0x0000FF, 0x00FF00, 0xFF0000, 0);
+    if(!image) return;
+    uchar *tmp = new uchar[screen->w*screen->h*3];
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
+    glReadPixels(0, 0, screen->w, screen->h, GL_RGB, GL_UNSIGNED_BYTE, tmp);
+    uchar *dst = (uchar *)image->pixels;
+    loopi(screen->h)
+    {
+        memcpy(dst, &tmp[3*screen->w*(screen->h-i-1)], 3*screen->w);
+        endianswap(dst, 3, screen->w);
+        dst += image->pitch;
+    }
+    delete[] tmp;
+    if(!filename[0])
+    {
+        static string buf;
+        s_sprintf(buf)("screenshot_%d.bmp", lastmillis);
+        filename = buf;
+    }
+    else path(filename);
+    SDL_SaveBMP(image, findfile(filename, "wb"));
+    SDL_FreeSurface(image);
 }
 
+COMMAND(screenshot, "s");
 COMMAND(quit, "");
 
-static void getbackgroundres(int &w, int &h)
+static void getcomputescreenres(int &w, int &h)
 {
     float wk = 1, hk = 1;
     if(w < 1024) wk = 1024.0f/w;
@@ -128,196 +142,122 @@ static void getbackgroundres(int &w, int &h)
     h = int(ceil(h*hk));
 }
 
-string backgroundcaption = "";
-Texture *backgroundmapshot = NULL;
-string backgroundmapname = "";
-char *backgroundmapinfo = NULL;
-
-void restorebackground()
+void computescreen(const char *text, Texture *t, const char *overlaytext)
 {
-    if(renderedframe) return;
-    renderbackground(backgroundcaption[0] ? backgroundcaption : NULL, backgroundmapshot, backgroundmapname[0] ? backgroundmapname : NULL, backgroundmapinfo, true);
-}
-
-void renderbackground(const char *caption, Texture *mapshot, const char *mapname, const char *mapinfo, bool restore, bool force)
-{
-    if(!inbetweenframes && !force) return;
-
-    stopsounds(); // stop sounds while loading
- 
     int w = screen->w, h = screen->h;
-    getbackgroundres(w, h);
+    getcomputescreenres(w, h);
     gettextres(w, h);
-
+    glEnable(GL_BLEND);
+    glEnable(GL_TEXTURE_2D);
+    glDisable(GL_DEPTH_TEST);
+    glDisable(GL_CULL_FACE);
+    glClearColor(0.15f, 0.15f, 0.15f, 1);
+    glColor3f(1, 1, 1);
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     glOrtho(0, w, h, 0, -1, 1);
     glMatrixMode(GL_MODELVIEW);
     glLoadIdentity();
-
     defaultshader->set();
-    glEnable(GL_TEXTURE_2D);
-
-    static int lastupdate = -1, lastw = -1, lasth = -1;
-    static float backgroundu = 0, backgroundv = 0, detailu = 0, detailv = 0;
-    static int numdecals = 0;
-    static struct decal { float x, y, size; int side; } decals[12];
-    if((renderedframe && !mainmenu && lastupdate != lastmillis) || lastw != w || lasth != h)
+    loopi(2)
     {
-        lastupdate = lastmillis;
-        lastw = w;
-        lasth = h;
-
-        backgroundu = rndscale(1);
-        backgroundv = rndscale(1);
-        detailu = rndscale(1);
-        detailv = rndscale(1);
-        numdecals = sizeof(decals)/sizeof(decals[0]);
-        numdecals = numdecals/3 + rnd((numdecals*2)/3 + 1);
-        float maxsize = min(w, h)/16.0f;
-        loopi(numdecals)
-        {
-            decal d = { rndscale(w), rndscale(h), maxsize/2 + rndscale(maxsize/2), rnd(2) };
-            decals[i] = d;
-        }
-    }
-    else if(lastupdate != lastmillis) lastupdate = lastmillis;
-
-    loopi(restore ? 1 : 2)
-    {
-        glColor3f(1, 1, 1);
-        settexture("data/background.png", 0);
-        float bu = w*0.67f/256.0f + backgroundu, bv = h*0.67f/256.0f + backgroundv;
-        glBegin(GL_QUADS);
-        glTexCoord2f(0,  0);  glVertex2f(0, 0);
-        glTexCoord2f(bu, 0);  glVertex2f(w, 0);
-        glTexCoord2f(bu, bv); glVertex2f(w, h);
-        glTexCoord2f(0,  bv); glVertex2f(0, h);
-        glEnd();
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        glEnable(GL_BLEND);
-        settexture("data/background_detail.png", 0);
-        float du = w*0.8f/512.0f + detailu, dv = h*0.8f/512.0f + detailv;
-        glBegin(GL_QUADS);
-        glTexCoord2f(0,  0);  glVertex2f(0, 0);
-        glTexCoord2f(du, 0);  glVertex2f(w, 0);
-        glTexCoord2f(du, dv); glVertex2f(w, h);
-        glTexCoord2f(0,  dv); glVertex2f(0, h);
-        glEnd();
-        settexture("data/background_decal.png", 3);
-        glBegin(GL_QUADS);
-        loopj(numdecals)
-        {
-            float hsz = decals[j].size, hx = clamp(decals[j].x, hsz, w-hsz), hy = clamp(decals[j].y, hsz, h-hsz), side = decals[j].side;
-            glTexCoord2f(side,   0); glVertex2f(hx-hsz, hy-hsz);
-            glTexCoord2f(1-side, 0); glVertex2f(hx+hsz, hy-hsz);
-            glTexCoord2f(1-side, 1); glVertex2f(hx+hsz, hy+hsz);
-            glTexCoord2f(side,   1); glVertex2f(hx-hsz, hy+hsz);
-        }
-        glEnd();
-        float lh = 0.5f*min(w, h), lw = lh*2,
-              lx = 0.5f*(w - lw), ly = 0.5f*(h*0.5f - lh);
-        settexture((maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize) >= 1024 && (screen->w > 1280 || screen->h > 800) ? "data/logo_1024.png" : "data/logo.png", 3);
-        glBegin(GL_QUADS);
-        glTexCoord2f(0, 0); glVertex2f(lx,    ly);
-        glTexCoord2f(1, 0); glVertex2f(lx+lw, ly);
-        glTexCoord2f(1, 1); glVertex2f(lx+lw, ly+lh);
-        glTexCoord2f(0, 1); glVertex2f(lx,    ly+lh);
-        glEnd();
-
-        if(caption)
+        glClear(GL_COLOR_BUFFER_BIT);
+        if(text)
         {
-            int tw = text_width(caption);
-            float tsz = 0.04f*min(w, h)/FONTH,
-                  tx = 0.5f*(w - tw*tsz), ty = h - 0.075f*1.5f*min(w, h) - 1.25f*FONTH*tsz;
             glPushMatrix();
-            glTranslatef(tx, ty, 0);
-            glScalef(tsz, tsz, 1);
-            draw_text(caption, 0, 0);
+            glScalef(1/3.0f, 1/3.0f, 1);
+            draw_text(text, 70, 2*FONTH + FONTH/2);
             glPopMatrix();
         }
-        if(mapshot || mapname)
+        if(t)
         {
-            int infowidth = 12*FONTH;
-            float sz = 0.35f*min(w, h), msz = (0.75f*min(w, h) - sz)/(infowidth + FONTH), x = 0.5f*(w-sz), y = ly+lh - sz/15;
-            if(mapinfo)
-            {
-                int mw, mh;
-                text_bounds(mapinfo, mw, mh, infowidth);
-                x -= 0.5f*(mw*msz + FONTH*msz);
+            glDisable(GL_BLEND);
+            glBindTexture(GL_TEXTURE_2D, t->id);
+#if 0
+            int x = (w-640)/2, y = (h-320)/2;
+            glBegin(GL_TRIANGLE_FAN);
+            glTexCoord2f(0.5f, 0.5f); glVertex2f(x+640/2.0f, y+320/2.0f);
+            loopj(64+1) 
+            { 
+                float c = 0.5f+0.5f*cosf(2*M_PI*j/64.0f), s = 0.5f+0.5f*sinf(2*M_PI*j/64.0f);
+                glTexCoord2f(c, 320.0f/640.0f*(s-0.5f)+0.5f);
+                glVertex2f(x+640*c, y+320*s);
             }
-            if(mapshot && mapshot!=notexture)
-            {
-                glBindTexture(GL_TEXTURE_2D, mapshot->id);
-                glBegin(GL_QUADS);
-                glTexCoord2f(0, 0); glVertex2f(x,    y);
-                glTexCoord2f(1, 0); glVertex2f(x+sz, y);
-                glTexCoord2f(1, 1); glVertex2f(x+sz, y+sz);
-                glTexCoord2f(0, 1); glVertex2f(x,    y+sz);
-                glEnd();
-            }
-            else
-            {
-                int qw, qh;
-                text_bounds("?", qw, qh);
-                float qsz = sz*0.5f/max(qw, qh);
-                glPushMatrix();
-                glTranslatef(x + 0.5f*(sz - qw*qsz), y + 0.5f*(sz - qh*qsz), 0);
-                glScalef(qsz, qsz, 1);
-                draw_text("?", 0, 0);
-                glPopMatrix();
-                glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-            }        
-            settexture("data/mapshot_frame.png", 3);
+#else
+            int sz = 256, x = (w-sz)/2, y = min(384, h-256);
             glBegin(GL_QUADS);
             glTexCoord2f(0, 0); glVertex2f(x,    y);
             glTexCoord2f(1, 0); glVertex2f(x+sz, y);
             glTexCoord2f(1, 1); glVertex2f(x+sz, y+sz);
             glTexCoord2f(0, 1); glVertex2f(x,    y+sz);
+#endif
             glEnd();
-            if(mapname)
-            {
-                int tw = text_width(mapname);
-                float tsz = sz/(8*FONTH),
-                      tx = 0.9f*sz - tw*tsz, ty = 0.9f*sz - FONTH*tsz;
-                if(tx < 0.1f*sz) { tsz = 0.1f*sz/tw; tx = 0.1f; }
-                glPushMatrix();
-                glTranslatef(x+tx, y+ty, 0);
-                glScalef(tsz, tsz, 1);
-                draw_text(mapname, 0, 0);
-                glPopMatrix();
-            }
-            if(mapinfo)
-            {
-                glPushMatrix();
-                glTranslatef(x+sz+FONTH*msz, y, 0);
-                glScalef(msz, msz, 1);
-                draw_text(mapinfo, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, -1, infowidth);
-                glPopMatrix();
-            }
+            glEnable(GL_BLEND);
+        }
+        if(overlaytext)
+        {
+            int sz = 256, x = (w-sz)/2, y = min(384, h-256), tw = text_width(overlaytext);
+            int tx = t && tw < sz*2 - FONTH/3 ? 
+                        2*(x + sz) - tw - FONTH/3 : 
+                        2*(x + sz/2) - tw/2, 
+                     ty = t ? 
+                        2*(y + sz) - FONTH*4/3 :
+                        2*(y + sz/2) - FONTH/2; 
+            glPushMatrix();
+            glScalef(1/2.0f, 1/2.0f, 1);
+            draw_text(overlaytext, tx, ty);
+            glPopMatrix();
         }
-        glDisable(GL_BLEND);
-        if(!restore) swapbuffers();
+        int x = (w-512)/2, y = 128;
+        settexture("data/sauer_logo_512_256a.png");
+        glBegin(GL_QUADS);
+        glTexCoord2f(0, 0); glVertex2f(x,     y);
+        glTexCoord2f(1, 0); glVertex2f(x+512, y);
+        glTexCoord2f(1, 1); glVertex2f(x+512, y+256);
+        glTexCoord2f(0, 1); glVertex2f(x,     y+256);
+        glEnd();
+        SDL_GL_SwapBuffers();
     }
+    glDisable(GL_BLEND);
     glDisable(GL_TEXTURE_2D);
+    glEnable(GL_DEPTH_TEST);
+    glEnable(GL_CULL_FACE);
+}
 
-    if(!restore)
+static void bar(float bar, int w, int o, float r, float g, float b)
+{
+    int side = 2*FONTH;
+    float x1 = side, x2 = min(bar, 1.0f)*(w*3-2*side)+side;
+    float y1 = o*FONTH;
+    glColor3f(r, g, b);
+    glBegin(GL_TRIANGLE_STRIP);
+    loopk(10)
     {
-        renderedframe = false;
-        copystring(backgroundcaption, caption ? caption : "");
-        backgroundmapshot = mapshot;
-        copystring(backgroundmapname, mapname ? mapname : "");
-        if(mapinfo != backgroundmapinfo)
-        {
-            DELETEA(backgroundmapinfo);
-            if(mapinfo) backgroundmapinfo = newstring(mapinfo);
-        }
+       float c = cosf(M_PI/2 + k/9.0f*M_PI), s = 1 + sinf(M_PI/2 + k/9.0f*M_PI);
+       glVertex2f(x2 - c*FONTH, y1 + s*FONTH);
+       glVertex2f(x1 + c*FONTH, y1 + s*FONTH);
     }
-}
+    glEnd();
 
-float loadprogress = 0;
+#if 0
+    glColor3f(0.3f, 0.3f, 0.3f);
+    glBegin(GL_LINE_LOOP);
+    loopk(10)
+    {
+        float c = cosf(M_PI/2 + k/9.0f*M_PI), s = 1 + sinf(M_PI/2 + k/9.0f*M_PI);
+        glVertex2f(x1 + c*FONTH, y1 + s*FONTH);
+    }
+    loopk(10)
+    {
+        float c = cosf(M_PI/2 + k/9.0f*M_PI), s = 1 - sinf(M_PI/2 + k/9.0f*M_PI);
+        glVertex2f(x2 - c*FONTH, y1 + s*FONTH);
+    }
+    glEnd();
+#endif
+}
 
-void renderprogress(float bar, const char *text, GLuint tex, bool background)   // also used during loading
+void show_out_of_renderloop_progress(float bar1, const char *text1, float bar2, const char *text2, GLuint tex)   // also used during loading
 {
     if(!inbetweenframes) return;
 
@@ -327,112 +267,67 @@ void renderprogress(float bar, const char *text, GLuint tex, bool background)
     interceptkey(SDLK_UNKNOWN); // keep the event queue awake to avoid 'beachball' cursor
     #endif
 
-    extern int sdl_backingstore_bug;
-    if(background || sdl_backingstore_bug > 0) restorebackground();
-
     int w = screen->w, h = screen->h;
-    getbackgroundres(w, h);
+    getcomputescreenres(w, h);
     gettextres(w, h);
 
-    glMatrixMode(GL_PROJECTION);
+    glDisable(GL_DEPTH_TEST);
+    glMatrixMode(GL_MODELVIEW);
     glPushMatrix();
     glLoadIdentity();
-    glOrtho(0, w, h, 0, -1, 1);
-    glMatrixMode(GL_MODELVIEW);
+    glMatrixMode(GL_PROJECTION);
     glPushMatrix();
     glLoadIdentity();
+    glOrtho(0, w*3, h*3, 0, -1, 1);
+    notextureshader->set();
 
-    glEnable(GL_TEXTURE_2D);
-    defaultshader->set();
-    glColor3f(1, 1, 1);
-
-    float fh = 0.075f*min(w, h), fw = fh*10,
-          fx = renderedframe ? w - fw - fh/4 : 0.5f*(w - fw), 
-          fy = renderedframe ? fh/4 : h - fh*1.5f,
-          fu1 = 0/512.0f, fu2 = 511/512.0f,
-          fv1 = 0/64.0f, fv2 = 52/64.0f;
-    settexture("data/loading_frame.png", 3);
-    glBegin(GL_QUADS);
-    glTexCoord2f(fu1, fv1); glVertex2f(fx,    fy);
-    glTexCoord2f(fu2, fv1); glVertex2f(fx+fw, fy);
-    glTexCoord2f(fu2, fv2); glVertex2f(fx+fw, fy+fh);
-    glTexCoord2f(fu1, fv2); glVertex2f(fx,    fy+fh);
-    glEnd();
+    glLineWidth(3);
 
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-    float bw = fw*(511 - 2*17)/511.0f, bh = fh*20/52.0f,
-          bx = fx + fw*17/511.0f, by = fy + fh*16/52.0f,
-          bv1 = 0/32.0f, bv2 = 20/32.0f,
-          su1 = 0/32.0f, su2 = 7/32.0f, sw = fw*7/511.0f,
-          eu1 = 23/32.0f, eu2 = 30/32.0f, ew = fw*7/511.0f,
-          mw = bw - sw - ew,
-          ex = bx+sw + max(mw*bar, fw*7/511.0f);
-    if(bar > 0)
+    if(text1)
     {
-        settexture("data/loading_bar.png", 3);
-        glBegin(GL_QUADS);
-        glTexCoord2f(su1, bv1); glVertex2f(bx,    by);
-        glTexCoord2f(su2, bv1); glVertex2f(bx+sw, by);
-        glTexCoord2f(su2, bv2); glVertex2f(bx+sw, by+bh);
-        glTexCoord2f(su1, bv2); glVertex2f(bx,    by+bh);
-
-        glTexCoord2f(su2, bv1); glVertex2f(bx+sw, by);
-        glTexCoord2f(eu1, bv1); glVertex2f(ex,    by);
-        glTexCoord2f(eu1, bv2); glVertex2f(ex,    by+bh);
-        glTexCoord2f(su2, bv2); glVertex2f(bx+sw, by+bh);
-
-        glTexCoord2f(eu1, bv1); glVertex2f(ex,    by);
-        glTexCoord2f(eu2, bv1); glVertex2f(ex+ew, by);
-        glTexCoord2f(eu2, bv2); glVertex2f(ex+ew, by+bh);
-        glTexCoord2f(eu1, bv2); glVertex2f(ex,    by+bh);
-        glEnd();
+        bar(1, w, 4, 0, 0, 0.8f);
+        if(bar1>0) bar(bar1, w, 4, 0, 0.5f, 1);
     }
 
-    if(text)
+    if(bar2>0)
     {
-        int tw = text_width(text);
-        float tsz = bh*0.8f/FONTH;
-        if(tw*tsz > mw) tsz = mw/tw;
-        glPushMatrix();
-        glTranslatef(bx+sw, by + (bh - FONTH*tsz)/2, 0);
-        glScalef(tsz, tsz, 1);
-        draw_text(text, 0, 0);
-        glPopMatrix();
+        bar(1, w, 6, 0.5f, 0, 0);
+        bar(bar2, w, 6, 0.75f, 0, 0);
     }
 
+    glLineWidth(1);
+
+    glEnable(GL_BLEND);
+    glEnable(GL_TEXTURE_2D);
+    defaultshader->set();
+
+    if(text1) draw_text(text1, 2*FONTH, 4*FONTH + FONTH/2);
+    if(bar2>0) draw_text(text2, 2*FONTH, 6*FONTH + FONTH/2);
+    
     glDisable(GL_BLEND);
 
     if(tex)
     {
         glBindTexture(GL_TEXTURE_2D, tex);
-        float sz = 0.35f*min(w, h), x = 0.5f*(w-sz), y = 0.5f*min(w, h) - sz/15;
+        int sz = 256, x = (w-sz)/2, y = min(384, h-256);
+        sz *= 3;
+        x *= 3;
+        y *= 3;
         glBegin(GL_QUADS);
         glTexCoord2f(0, 0); glVertex2f(x,    y);
         glTexCoord2f(1, 0); glVertex2f(x+sz, y);
         glTexCoord2f(1, 1); glVertex2f(x+sz, y+sz);
         glTexCoord2f(0, 1); glVertex2f(x,    y+sz);
         glEnd();
-
-        glEnable(GL_BLEND);
-        settexture("data/mapshot_frame.png", 3);
-        glBegin(GL_QUADS);
-        glTexCoord2f(0, 0); glVertex2f(x,    y);
-        glTexCoord2f(1, 0); glVertex2f(x+sz, y);
-        glTexCoord2f(1, 1); glVertex2f(x+sz, y+sz);
-        glTexCoord2f(0, 1); glVertex2f(x,    y+sz);
-        glEnd();
-        glDisable(GL_BLEND);
     }
 
     glDisable(GL_TEXTURE_2D);
 
-    glMatrixMode(GL_PROJECTION);
     glPopMatrix();
     glMatrixMode(GL_MODELVIEW);
     glPopMatrix();
-    swapbuffers();
+    glEnable(GL_DEPTH_TEST);
+    SDL_GL_SwapBuffers();
 }
 
 void setfullscreen(bool enable)
@@ -461,14 +356,14 @@ void screenres(int *w, int *h)
     if(initing >= INIT_RESET)
     {
 #endif
-        scr_w = clamp(*w, SCR_MINW, SCR_MAXW);
-        scr_h = clamp(*h, SCR_MINH, SCR_MAXH);
+        scr_w = *w;
+        scr_h = *h;
 #if defined(WIN32) || defined(__APPLE__)
         initwarning("screen resolution");
 #else
         return;
     }
-    SDL_Surface *surf = SDL_SetVideoMode(clamp(*w, SCR_MINW, SCR_MAXW), clamp(*h, SCR_MINH, SCR_MAXH), 0, SDL_OPENGL|(screen->flags&SDL_FULLSCREEN ? SDL_FULLSCREEN : SDL_RESIZABLE));
+    SDL_Surface *surf = SDL_SetVideoMode(*w, *h, 0, SDL_OPENGL|SDL_RESIZABLE|(screen->flags&SDL_FULLSCREEN));
     if(!surf) return;
     screen = surf;
     scr_w = screen->w;
@@ -497,81 +392,26 @@ void resetgamma()
 	SDL_SetGamma(f, f, f);
 }
 
-static int moderatio(int w, int h)
-{
-    w *= 3*4*5;
-    return w%h ? 0 : w/h;
-}
-
-static int moderatio(SDL_Rect *mode)
-{
-    return moderatio(mode->w, mode->h);
-}
-
-VAR(dbgmodes, 0, 0, 1);
-
-int desktopw = 0, desktoph = 0;
-
 void setupscreen(int &usedcolorbits, int &useddepthbits, int &usedfsaa)
 {
     int flags = SDL_RESIZABLE;
     #if defined(WIN32) || defined(__APPLE__)
     flags = 0;
     #endif
-    if(fullscreen) flags = SDL_FULLSCREEN;
+    if(fullscreen) flags |= SDL_FULLSCREEN;
     SDL_Rect **modes = SDL_ListModes(NULL, SDL_OPENGL|flags);
     if(modes && modes!=(SDL_Rect **)-1)
     {
-        int widest = -1, best = -1;
+        bool hasmode = false;
         for(int i = 0; modes[i]; i++)
         {
-            if(dbgmodes) conoutf(CON_DEBUG, "mode[%d]: %d x %d", i, modes[i]->w, modes[i]->h);
-            if(widest < 0 || modes[i]->w > modes[widest]->w || (modes[i]->w == modes[widest]->w && modes[i]->h > modes[widest]->h)) 
-                widest = i; 
-        }
-        int ratio = desktopw > 0 && desktoph > 0 ? moderatio(desktopw, desktoph) : moderatio(modes[widest]);
-        if((scr_w < 0 || scr_h < 0) && ratio > 0)
-        {
-            int w = scr_w, h = scr_h;
-            if(w < 0 && h < 0) { w = SCR_DEFAULTW; h = SCR_DEFAULTH; }
-            for(int i = 0; modes[i]; i++) if(moderatio(modes[i]) == ratio)
-            {
-                if(w <= modes[i]->w && h <= modes[i]->h && (best < 0 || modes[i]->w < modes[best]->w))
-                    best = i;
-            }
-        } 
-        if(best < 0)
-        {
-            int w = scr_w, h = scr_h;
-            if(w < 0 && h < 0) { w = SCR_DEFAULTW; h = SCR_DEFAULTH; }
-            else if(w < 0) w = (h*SCR_DEFAULTW)/SCR_DEFAULTH;
-            else if(h < 0) h = (w*SCR_DEFAULTH)/SCR_DEFAULTW;
-            for(int i = 0; modes[i]; i++)
-            {
-                if(w <= modes[i]->w && h <= modes[i]->h && (best < 0 || modes[i]->w < modes[best]->w || (modes[i]->w == modes[best]->w && modes[i]->h < modes[best]->h)))
-                    best = i;
-            }
-        }
-        if(flags&SDL_FULLSCREEN)
-        {
-            if(best >= 0) { scr_w = modes[best]->w; scr_h = modes[best]->h; }
-            else if(desktopw > 0 && desktoph > 0) { scr_w = desktopw; scr_h = desktoph; }
-            else if(widest >= 0) { scr_w = modes[widest]->w; scr_h = modes[widest]->h; } 
+            if(scr_w <= modes[i]->w && scr_h <= modes[i]->h) { hasmode = true; break; }
         }
-        else if(best < 0)
-        { 
-            scr_w = min(scr_w >= 0 ? scr_w : (scr_h >= 0 ? (scr_h*SCR_DEFAULTW)/SCR_DEFAULTH : SCR_DEFAULTW), (int)modes[widest]->w); 
-            scr_h = min(scr_h >= 0 ? scr_h : (scr_w >= 0 ? (scr_w*SCR_DEFAULTH)/SCR_DEFAULTW : SCR_DEFAULTH), (int)modes[widest]->h);
-        }
-        if(dbgmodes) conoutf(CON_DEBUG, "selected %d x %d", scr_w, scr_h);
+        if(!hasmode) { scr_w = modes[0]->w; scr_h = modes[0]->h; }
     }
-    if(scr_w < 0 && scr_h < 0) { scr_w = SCR_DEFAULTW; scr_h = SCR_DEFAULTH; }
-    else if(scr_w < 0) scr_w = (scr_h*SCR_DEFAULTW)/SCR_DEFAULTH;
-    else if(scr_h < 0) scr_h = (scr_w*SCR_DEFAULTH)/SCR_DEFAULTW;
-
     bool hasbpp = true;
-    if(colorbits)
-        hasbpp = SDL_VideoModeOK(scr_w, scr_h, colorbits, SDL_OPENGL|flags)==colorbits;
+    if(colorbits && modes)
+        hasbpp = SDL_VideoModeOK(modes!=(SDL_Rect **)-1 ? modes[0]->w : scr_w, modes!=(SDL_Rect **)-1 ? modes[0]->h : scr_h, colorbits, SDL_OPENGL|flags)==colorbits;
 
     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 #if SDL_VERSION_ATLEAST(1, 2, 11)
@@ -601,10 +441,9 @@ void setupscreen(int &usedcolorbits, int &useddepthbits, int &usedfsaa)
         if(depthbits) SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config&1 ? depthbits : 16);
         if(stencilbits)
         {
-            hasstencil = config&2 ? stencilbits : 0;
-            SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, hasstencil);
+            SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config&2 ? 1 : 0);
+            hasstencil = (config&2)!=0;
         }
-        else hasstencil = 0;
         if(fsaa>0)
         {
             SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, config&4 ? 1 : 0);
@@ -640,14 +479,13 @@ void resetgl()
 {
     clearchanges(CHANGE_GFX);
 
-    renderbackground("resetting OpenGL");
+    computescreen("resetting OpenGL");
 
     extern void cleanupva();
     extern void cleanupparticles();
     extern void cleanupmodels();
     extern void cleanuptextures();
     extern void cleanuplightmaps();
-    extern void cleanupblendmap();
     extern void cleanshadowmap();
     extern void cleanreflections();
     extern void cleanupglare();
@@ -659,7 +497,6 @@ void resetgl()
     cleanupmodels();
     cleanuptextures();
     cleanuplightmaps();
-    cleanupblendmap();
     cleanshadowmap();
     cleanreflections();
     cleanupglare();
@@ -678,18 +515,11 @@ void resetgl()
     extern void reloadshaders();
     inbetweenframes = false;
     if(!reloadtexture(*notexture) ||
-       !reloadtexture("data/logo.png") ||
-       !reloadtexture("data/logo_1024.png") || 
-       !reloadtexture("data/background.png") ||
-       !reloadtexture("data/background_detail.png") ||
-       !reloadtexture("data/background_decal.png") ||
-       !reloadtexture("data/mapshot_frame.png") ||
-       !reloadtexture("data/loading_frame.png") ||
-       !reloadtexture("data/loading_bar.png"))
+       !reloadtexture("data/sauer_logo_512_256a.png")) 
         fatal("failed to reload core texture");
     reloadfonts();
     inbetweenframes = true;
-    renderbackground("initializing...");
+    computescreen("initializing...");
 	resetgamma();
     reloadshaders();
     reloadtextures();
@@ -716,18 +546,19 @@ void pushevent(const SDL_Event &e)
 
 bool interceptkey(int sym)
 {
-    static int lastintercept = SDLK_UNKNOWN;
-    int len = lastintercept == sym ? events.length() : 0;
     SDL_Event event;
-    while(SDL_PollEvent(&event)) switch(event.type)
+    while(SDL_PollEvent(&event))
     {
-        case SDL_MOUSEMOTION: break;
-        default: pushevent(event); break;
-    }
-    lastintercept = sym;
-    if(sym != SDLK_UNKNOWN) for(int i = len; i < events.length(); i++)
-    {
-        if(events[i].type == SDL_KEYDOWN && events[i].key.keysym.sym == sym) { events.remove(i); return true; }
+        switch(event.type)
+        {
+        case SDL_KEYDOWN:
+            if(event.key.keysym.sym == sym)
+                return true;
+
+        default:
+            pushevent(event);
+            break;
+        }
     }
     return false;
 }
@@ -792,34 +623,26 @@ void checkinput()
         }
     }
 }
-
-void swapbuffers()
-{
-    recorder::capture();
-    SDL_GL_SwapBuffers();
-}
  
 VARF(gamespeed, 10, 100, 1000, if(multiplayer()) gamespeed = 100);
 
 VARF(paused, 0, 0, 1, if(multiplayer()) paused = 0);
 
-VAR(mainmenufps, 0, 60, 1000);
 VARP(maxfps, 0, 200, 1000);
 
 void limitfps(int &millis, int curmillis)
 {
-    int limit = mainmenu && mainmenufps ? (maxfps ? min(maxfps, mainmenufps) : mainmenufps) : maxfps;
-    if(!limit) return;
+    if(!maxfps) return;
     static int fpserror = 0;
-    int delay = 1000/limit - (millis-curmillis);
+    int delay = 1000/maxfps - (millis-curmillis);
     if(delay < 0) fpserror = 0;
     else
     {
-        fpserror += 1000%limit;
-        if(fpserror >= limit)
+        fpserror += 1000%maxfps;
+        if(fpserror >= maxfps)
         {
             ++delay;
-            fpserror -= limit;
+            fpserror -= maxfps;
         }
         if(delay > 0)
         {
@@ -836,7 +659,7 @@ void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep)
     EXCEPTION_RECORD *er = ep->ExceptionRecord;
     CONTEXT *context = ep->ContextRecord;
     string out, t;
-    formatstring(out)("Cube 2: Sauerbraten Win32 Exception: 0x%x [0x%x]\n\n", er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
+    s_sprintf(out)("Sauerbraten Win32 Exception: 0x%x [0x%x]\n\n", er->ExceptionCode, er->ExceptionCode==EXCEPTION_ACCESS_VIOLATION ? er->ExceptionInformation[1] : -1);
     STACKFRAME sf = {{context->Eip, 0, AddrModeFlat}, {}, {context->Ebp, 0, AddrModeFlat}, {context->Esp, 0, AddrModeFlat}, 0};
     SymInitialize(GetCurrentProcess(), NULL, TRUE);
 
@@ -848,8 +671,8 @@ void stackdumper(unsigned int type, EXCEPTION_POINTERS *ep)
         if(SymGetSymFromAddr(GetCurrentProcess(), (DWORD)sf.AddrPC.Offset, &off, &si.sym) && SymGetLineFromAddr(GetCurrentProcess(), (DWORD)sf.AddrPC.Offset, &off, &li))
         {
             char *del = strrchr(li.FileName, '\\');
-            formatstring(t)("%s - %s [%d]\n", si.sym.Name, del ? del + 1 : li.FileName, li.LineNumber);
-            concatstring(out, t);
+            s_sprintf(t)("%s - %s [%d]\n", si.sym.Name, del ? del + 1 : li.FileName, li.LineNumber);
+            s_strcat(out, t);
         }
     }
     fatal(out);
@@ -898,7 +721,7 @@ void getfps_(int *raw)
 
 COMMANDN(getfps, getfps_, "i");
 
-bool inbetweenframes = false, renderedframe = true;
+bool inbetweenframes = false;
 
 static bool findarg(int argc, char **argv, const char *str)
 {
@@ -922,7 +745,7 @@ int main(int argc, char **argv)
     #endif
     #endif
 
-    int dedicated = 0;
+    bool dedicated = false;
     char *load = NULL, *initscript = NULL;
 
     #define log(s) puts("init: " s)
@@ -934,10 +757,10 @@ int main(int argc, char **argv)
         {
             case 'q': printf("Using home directory: %s\n", &argv[i][2]); sethomedir(&argv[i][2]); break;
             case 'k': printf("Adding package directory: %s\n", &argv[i][2]); addpackagedir(&argv[i][2]); break;
-            case 'r': execfile(argv[i][2] ? &argv[i][2] : "init.cfg", false); restoredinits = true; break;
-            case 'd': dedicated = atoi(&argv[i][2]); if(dedicated<=0) dedicated = 2; break;
-            case 'w': scr_w = clamp(atoi(&argv[i][2]), SCR_MINW, SCR_MAXW); if(!findarg(argc, argv, "-h")) scr_h = -1; break;
-            case 'h': scr_h = clamp(atoi(&argv[i][2]), SCR_MINH, SCR_MAXH); if(!findarg(argc, argv, "-w")) scr_w = -1; break;
+            case 'r': execfile(argv[i][2] ? &argv[i][2] : "init.cfg"); restoredinits = true; break;
+            case 'd': dedicated = true; break;
+            case 'w': scr_w = atoi(&argv[i][2]); if(scr_w<320) scr_w = 320; if(!findarg(argc, argv, "-h")) scr_h = (scr_w*3)/4; break;
+            case 'h': scr_h = atoi(&argv[i][2]); if(scr_h<200) scr_h = 200; if(!findarg(argc, argv, "-w")) scr_w = (scr_h*4)/3; break;
             case 'z': depthbits = atoi(&argv[i][2]); break;
             case 'b': colorbits = atoi(&argv[i][2]); break;
             case 'a': fsaa = atoi(&argv[i][2]); break;
@@ -983,28 +806,17 @@ int main(int argc, char **argv)
 
     if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|SDL_INIT_AUDIO|par)<0) fatal("Unable to initialize SDL: %s", SDL_GetError());
 
-    log("net");
+    log("enet");
     if(enet_initialize()<0) fatal("Unable to initialise network module");
-    atexit(enet_deinitialize);
-    enet_time_set(0);
 
-    log("game");
-    game::parseoptions(gameargs);
-    initserver(dedicated>0, dedicated>1);  // never returns if dedicated
-    game::initclient();
+    initserver(dedicated);  // never returns if dedicated
 
     log("video: mode");
-    const SDL_VideoInfo *video = SDL_GetVideoInfo();
-    if(video) 
-    {
-        desktopw = video->current_w;
-        desktoph = video->current_h;
-    }
     int usedcolorbits = 0, useddepthbits = 0, usedfsaa = 0;
     setupscreen(usedcolorbits, useddepthbits, usedfsaa);
 
     log("video: misc");
-    SDL_WM_SetCaption("Cube 2: Sauerbraten", NULL);
+    SDL_WM_SetCaption("sauerbraten engine", NULL);
     keyrepeat(false);
     SDL_ShowCursor(0);
 
@@ -1016,12 +828,12 @@ int main(int argc, char **argv)
 
     log("console");
     persistidents = false;
-    if(!execfile("data/stdlib.cfg", false)) fatal("cannot find data files (you are running from the wrong folder, try .bat file in the main folder)");   // this is the first file we load.
-    if(!execfile("data/font.cfg", false)) fatal("cannot find font definitions");
+    if(!execfile("data/stdlib.cfg")) fatal("cannot find data files (you are running from the wrong folder, try .bat file in the main folder)");   // this is the first file we load.
+    if(!execfile("data/font.cfg")) fatal("cannot find font definitions");
     if(!setfont("default")) fatal("no default font specified");
 
+    computescreen("initializing...");
     inbetweenframes = true;
-    renderbackground("initializing...");
 
     log("gl: effects");
     loadshaders();
@@ -1029,48 +841,42 @@ int main(int argc, char **argv)
     initdecals();
 
     log("world");
-    camera1 = player = game::iterdynents(0);
-    emptymap(0, true, NULL, false);
+    camera1 = player = cl->iterdynents(0);
+    emptymap(0, true);
 
     log("sound");
     initsound();
 
     log("cfg");
-    execfile("data/keymap.cfg");
-    execfile("data/stdedit.cfg");
-    execfile("data/menus.cfg");
-    execfile("data/sounds.cfg");
-    execfile("data/brush.cfg");
-    execfile("mybrushes.cfg", false);
-    if(game::savedservers()) execfile(game::savedservers(), false);
+    exec("data/keymap.cfg");
+    exec("data/stdedit.cfg");
+    exec("data/menus.cfg");
+    exec("data/sounds.cfg");
+    exec("data/brush.cfg");
+    execfile("mybrushes.cfg");
+    if(cl->savedservers()) execfile(cl->savedservers());
     
     persistidents = true;
     
     initing = INIT_LOAD;
-    if(!execfile(game::savedconfig(), false)) execfile(game::defaultconfig());
-    execfile(game::autoexec(), false);
+    if(!execfile(cl->savedconfig())) exec(cl->defaultconfig());
+    execfile(cl->autoexec());
     initing = NOT_INITING;
 
     persistidents = false;
 
     string gamecfgname;
-    copystring(gamecfgname, "data/game_");
-    concatstring(gamecfgname, game::gameident());
-    concatstring(gamecfgname, ".cfg");
-    execfile(gamecfgname);
-    
-    game::loadconfigs();
+    s_strcpy(gamecfgname, "data/game_");
+    s_strcat(gamecfgname, cl->gameident());
+    s_strcat(gamecfgname, ".cfg");
+    exec(gamecfgname);
 
     persistidents = true;
 
-    if(execfile("once.cfg", false)) remove(findfile("once.cfg", "rb"));
-
-    if(load)
-    {
-        log("localconnect");
-        //localconnect();
-        game::changemap(load);
-    }
+    log("localconnect");
+    localconnect();
+    cc->gameconnect(false);
+    cc->changemap(load ? load : cl->defaultmap());
 
     if(initscript) execute(initscript);
 
@@ -1088,7 +894,7 @@ int main(int argc, char **argv)
         if(millis<totalmillis) millis = totalmillis;
         limitfps(millis, totalmillis);
         int elapsed = millis-totalmillis;
-        if(multiplayer(false)) curtime = game::ispaused() ? 0 : elapsed;
+        if(multiplayer(false)) curtime = elapsed;
         else
         {
             static int timeerr = 0;
@@ -1096,34 +902,35 @@ int main(int argc, char **argv)
             curtime = scaledtime/100;
             timeerr = scaledtime%100;
             if(curtime>200) curtime = 200;
-            if(paused || game::ispaused()) curtime = 0;
+            if(paused) curtime = 0;
         }
-        lastmillis += curtime;
-        totalmillis = millis;
 
         checkinput();
+
+        if(lastmillis) cl->updateworld(worldpos, curtime, lastmillis);
+       
         menuprocess();
-        tryedit();
 
-        if(lastmillis) game::updateworld();
+        lastmillis += curtime;
+        totalmillis = millis;
 
         checksleep(lastmillis);
 
-        serverslice(false, 0);
+        serverslice(0);
 
         if(frames) updatefpshistory(elapsed);
         frames++;
 
         // miscellaneous general game effects
-        recomputecamera();
-        updateparticles();
-        updatesounds();
+        findorientation();
+        entity_particles();
+        updatevol();
+        checkmapsounds();
 
         inbetweenframes = false;
-        if(mainmenu) gl_drawmainmenu(screen->w, screen->h);
-        else gl_drawframe(screen->w, screen->h);
-        swapbuffers();
-        renderedframe = inbetweenframes = true;
+        if(frames>2) gl_drawframe(screen->w, screen->h);
+        SDL_GL_SwapBuffers();
+        inbetweenframes = true;
     }
     
     ASSERT(0);   
diff --git a/engine/master.cpp b/engine/master.cpp
deleted file mode 100644
index aeec652..0000000
--- a/engine/master.cpp
+++ /dev/null
@@ -1,631 +0,0 @@
-#include "cube.h"
-#include <signal.h>
-#include <enet/time.h>
-
-#define INPUT_LIMIT 4096
-#define OUTPUT_LIMIT (64*1024)
-#define CLIENT_TIME (3*60*1000)
-#define AUTH_TIME (60*1000)
-#define AUTH_LIMIT 100
-#define CLIENT_LIMIT 8192
-#define DUP_LIMIT 16
-#define PING_TIME 3000
-#define PING_RETRY 5
-#define KEEPALIVE_TIME (65*60*1000)
-#define SERVER_LIMIT (10*1024)
-
-FILE *logfile = NULL;
-
-struct userinfo
-{
-    char *name;
-    void *pubkey;
-};
-hashtable<char *, userinfo> users;
-
-void adduser(char *name, char *pubkey)
-{
-    name = newstring(name);
-    userinfo &u = users[name];
-    u.name = name;
-    u.pubkey = parsepubkey(pubkey);
-}
-COMMAND(adduser, "ss");
-
-void clearusers()
-{
-    enumerate(users, userinfo, u, { delete[] u.name; freepubkey(u.pubkey); });
-    users.clear();
-}
-COMMAND(clearusers, "");
-
-struct baninfo
-{
-    enet_uint32 ip, mask;
-};
-vector<baninfo> bans, servbans;
-
-void clearbans()
-{
-    bans.setsize(0);
-    servbans.setsize(0);
-}
-COMMAND(clearbans, "");
-
-void addban(vector<baninfo> &bans, const char *name)
-{
-    uchar ip[sizeof(enet_uint32)], mask[sizeof(enet_uint32)];
-    memset(ip, 0, sizeof(ip));
-    memset(mask, 0, sizeof(mask));
-    loopi(4)
-    {
-        char *end = NULL;
-        int n = strtol(name, &end, 10);
-        if(!end) break;
-        if(end > name) { ip[i] = n; mask[i] = 0xFF; } 
-        name = end;
-        while(*name && *name++ != '.');
-    }
-    baninfo &ban = bans.add();
-    ban.ip = *(enet_uint32 *)ip; 
-    ban.mask = *(enet_uint32 *)mask;
-}
-ICOMMAND(ban, "s", (char *name), addban(bans, name));
-ICOMMAND(servban, "s", (char *name), addban(servbans, name));
-
-bool checkban(vector<baninfo> &bans, enet_uint32 host)
-{
-    loopv(bans) if((host & bans[i].mask) == bans[i].ip) return true;
-    return false;
-}
-
-struct authreq
-{
-    enet_uint32 reqtime; 
-    uint id;
-    void *answer;
-};
-
-struct gameserver
-{
-    ENetAddress address;
-    string ip;
-    int port, numpings;
-    enet_uint32 lastping, lastpong;
-};
-vector<gameserver *> gameservers;
-
-struct gameserverlist
-{
-    vector<char> buf;
-    int refs;
-
-    gameserverlist() : refs(0) {}
-
-    const char *getbuf() { return buf.getbuf(); }
-    int length() { return buf.length(); }
-    void purge();
-};
-vector<gameserverlist *> gameserverlists;
-bool updateserverlist = true;
-
-struct client
-{
-    ENetAddress address;
-    ENetSocket socket;
-    char input[INPUT_LIMIT];
-    gameserverlist *list;
-    vector<char> output;
-    int inputpos, outputpos;
-    enet_uint32 connecttime, lastinput;
-    int servport;
-    vector<authreq> authreqs;
-
-    client() : list(NULL), inputpos(0), outputpos(0), servport(-1) {}
-};  
-vector<client *> clients;
-
-ENetSocket serversocket = ENET_SOCKET_NULL;
-
-time_t starttime;
-enet_uint32 servtime = 0;
-
-void fatal(const char *fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-    vfprintf(logfile, fmt, args);
-    fputc('\n', logfile);
-    va_end(args);
-    exit(EXIT_FAILURE);
-}
-
-void conoutfv(int type, const char *fmt, va_list args)
-{
-    vfprintf(logfile, fmt, args);
-    fputc('\n', logfile);
-}
-
-void conoutf(const char *fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-    conoutfv(CON_INFO, fmt, args);
-    va_end(args);
-}
-
-void conoutf(int type, const char *fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-    conoutfv(type, fmt, args);
-    va_end(args);
-}
-
-void purgeclient(int n)
-{
-    client &c = *clients[n];
-    if(c.list) c.list->purge();
-    enet_socket_destroy(c.socket);
-    delete clients[n];
-    clients.remove(n);
-}
-
-void output(client &c, const char *msg, int len = 0)
-{
-    if(!len) len = strlen(msg);
-    c.output.put(msg, len);
-}
-
-void outputf(client &c, const char *fmt, ...)
-{
-    string msg;
-    va_list args;
-    va_start(args, fmt);
-    vformatstring(msg, fmt, args);
-    va_end(args);
-
-    output(c, msg);
-}
-
-ENetSocket pingsocket = ENET_SOCKET_NULL;
-
-bool setuppingsocket()
-{
-    if(pingsocket != ENET_SOCKET_NULL) return true;
-    pingsocket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
-    if(pingsocket == ENET_SOCKET_NULL) return false;
-    enet_socket_set_option(pingsocket, ENET_SOCKOPT_NONBLOCK, 1);
-    return true;
-}
-
-void setupserver(int port, const char *ip = NULL)
-{
-    ENetAddress address;
-    address.host = ENET_HOST_ANY;
-    address.port = port;
-
-    if(ip)
-    {
-        if(enet_address_set_host(&address, ip)<0)
-            fatal("failed to resolve server address: %s", ip);
-    }
-    serversocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM);
-    if(serversocket==ENET_SOCKET_NULL || 
-       enet_socket_set_option(serversocket, ENET_SOCKOPT_REUSEADDR, 1) < 0 ||
-       enet_socket_bind(serversocket, &address) < 0 ||
-       enet_socket_listen(serversocket, -1) < 0)
-        fatal("failed to create server socket");
-    if(enet_socket_set_option(serversocket, ENET_SOCKOPT_NONBLOCK, 1)<0)
-        fatal("failed to make server socket non-blocking");
-    if(!setuppingsocket())
-        fatal("failed to create ping socket");
-
-    enet_time_set(0);
-    
-    starttime = time(NULL);
-    char *ct = ctime(&starttime);
-    if(strchr(ct, '\n')) *strchr(ct, '\n') = '\0';
-    conoutf("*** Starting master server on %s %d at %s ***", ip ? ip : "localhost", port, ct);
-}
-
-void genserverlist()
-{
-    if(!updateserverlist) return;
-    while(gameserverlists.length() && gameserverlists.last()->refs<=0)
-        delete gameserverlists.pop();
-    gameserverlist *l = new gameserverlist;
-    loopv(gameservers)
-    {
-        gameserver &s = *gameservers[i];
-        if(!s.lastpong) continue;
-        defformatstring(cmd)("addserver %s %d\n", s.ip, s.port);
-        l->buf.put(cmd, strlen(cmd));
-    }
-    l->buf.add('\0');
-    gameserverlists.add(l);
-    updateserverlist = false;
-}
-
-void addgameserver(client &c)
-{
-    if(gameservers.length() >= SERVER_LIMIT) return;
-    loopv(gameservers)
-    {
-        gameserver &s = *gameservers[i];
-        if(s.address.host == c.address.host && s.port == c.servport)
-        {
-            s.lastping = 0;
-            s.numpings = 0;
-            return;
-        }
-    }
-    string hostname;
-    if(enet_address_get_host_ip(&c.address, hostname, sizeof(hostname)) < 0)
-    {
-        outputf(c, "failreg failed resolving ip\n");
-        return;
-    }     
-    gameserver &s = *gameservers.add(new gameserver);
-    s.address.host = c.address.host;
-    s.address.port = c.servport+1;
-    copystring(s.ip, hostname);
-    s.port = c.servport;
-    s.numpings = 0;
-    s.lastping = s.lastpong = 0;
-}
-
-void servermessage(gameserver &s, const char *msg)
-{
-    loopv(clients)
-    {
-        client &c = *clients[i];    
-        if(s.address.host == c.address.host && s.port == c.servport)
-        {
-            outputf(c, msg);
-            return;
-        }
-    }
-}
-
-void checkserverpongs()
-{
-    ENetBuffer buf;
-    ENetAddress addr;
-    static uchar pong[MAXTRANS];
-    for(;;)
-    {
-        buf.data = pong;
-        buf.dataLength = sizeof(pong);
-        int len = enet_socket_receive(pingsocket, &addr, &buf, 1);
-        if(len <= 0) break; 
-        loopv(gameservers)
-        {
-            gameserver &s = *gameservers[i];
-            if(s.address.host == addr.host && s.address.port == addr.port)
-            {
-                if(s.lastping && (!s.lastpong || ENET_TIME_GREATER(s.lastping, s.lastpong)))
-                    servermessage(s, "succreg\n");
-                if(!s.lastpong) updateserverlist = true;
-                s.lastpong = servtime ? servtime : 1;
-                break;
-            }
-        }
-    }
-}
-
-void bangameservers()
-{
-    loopvrev(gameservers) if(checkban(servbans, gameservers[i]->address.host))
-    {
-        delete gameservers.remove(i);
-        updateserverlist = true;
-    }
-}
-
-void checkgameservers()
-{
-    ENetBuffer buf;
-    loopv(gameservers)
-    {
-        gameserver &s = *gameservers[i];
-        if(s.lastping && s.lastpong && ENET_TIME_LESS_EQUAL(s.lastping, s.lastpong))
-        {
-            if(ENET_TIME_DIFFERENCE(servtime, s.lastpong) > KEEPALIVE_TIME)
-            {
-                delete gameservers.remove(i--);
-                updateserverlist = true;
-            }
-        }
-        else if(!s.lastping || ENET_TIME_DIFFERENCE(servtime, s.lastping) > PING_TIME)
-        {
-            if(s.numpings >= PING_RETRY)
-            {
-                servermessage(s, "failreg failed pinging server\n");
-                delete gameservers.remove(i--);
-                updateserverlist = true;
-            }
-            else
-            {
-                static const uchar ping[] = { 1 };
-                buf.data = (void *)ping;
-                buf.dataLength = sizeof(ping);
-                s.numpings++;
-                s.lastping = servtime ? servtime : 1;
-                enet_socket_send(pingsocket, &s.address, &buf, 1);
-            }
-        }
-    }
-}
-
-void gameserverlist::purge()
-{
-    refs = max(refs - 1, 0);
-    if(refs<=0 && gameserverlists.last()!=this)
-    {
-        gameserverlists.removeobj(this);
-        delete this;
-    }
-}
-
-void purgeauths(client &c)
-{
-    int expired = 0;
-    loopv(c.authreqs)
-    {
-        if(ENET_TIME_DIFFERENCE(servtime, c.authreqs[i].reqtime) >= AUTH_TIME) 
-        {
-            outputf(c, "failauth %u\n", c.authreqs[i].id);
-            freechallenge(c.authreqs[i].answer);
-            expired = i + 1;
-        }
-        else break;
-    }
-    if(expired > 0) c.authreqs.remove(0, expired);
-}
-
-void reqauth(client &c, uint id, char *name)
-{
-    purgeauths(c);
-    
-    time_t t = time(NULL);
-    char *ct = ctime(&t);
-    if(ct) 
-    { 
-        char *newline = strchr(ct, '\n');
-        if(newline) *newline = '\0'; 
-    }
-    string ip;
-    if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-");
-    conoutf("%s: attempting \"%s\" as %u from %s", ct ? ct : "-", name, id, ip);
-
-    userinfo *u = users.access(name);
-    if(!u)
-    {
-        outputf(c, "failauth %u\n", id);
-        return;
-    }
-
-    if(c.authreqs.length() >= AUTH_LIMIT)
-    {
-        outputf(c, "failauth %u\n", c.authreqs[0].id);
-        freechallenge(c.authreqs[0].answer);
-        c.authreqs.remove(0);
-    }
-
-    authreq &a = c.authreqs.add();
-    a.reqtime = servtime;
-    a.id = id;
-    uint seed[3] = { starttime, servtime, randomMT() };
-    static vector<char> buf;
-    buf.setsizenodelete(0);
-    a.answer = genchallenge(u->pubkey, seed, sizeof(seed), buf);
-
-    outputf(c, "chalauth %u %s\n", id, buf.getbuf());
-}
-
-void confauth(client &c, uint id, const char *val)
-{
-    purgeauths(c);
-
-    loopv(c.authreqs) if(c.authreqs[i].id == id)
-    {
-        string ip;
-        if(enet_address_get_host_ip(&c.address, ip, sizeof(ip)) < 0) copystring(ip, "-");
-        if(checkchallenge(val, c.authreqs[i].answer))
-        {
-            outputf(c, "succauth %u\n", id);
-            conoutf("succeeded %u from %s", id, ip);
-        }    
-        else 
-        {
-            outputf(c, "failauth %u\n", id);
-            conoutf("failed %u from %s", id, ip);
-        }
-        freechallenge(c.authreqs[i].answer);
-        c.authreqs.remove(i--);
-        return;
-    }
-    outputf(c, "failauth %u\n", id);
-}
-
-bool checkclientinput(client &c)
-{
-    if(c.inputpos<0) return true;
-    char *end = (char *)memchr(c.input, '\n', c.inputpos);
-    while(end)
-    {
-        *end++ = '\0';
-        c.lastinput = servtime;
-
-        int port;
-        uint id;
-        string user, val;
-        if(!strncmp(c.input, "list", 4) && (!c.input[4] || isspace(c.input[4])))
-        {
-            genserverlist();
-            if(gameserverlists.empty()) return false;
-            c.list = gameserverlists.last();
-            c.list->refs++;
-            c.outputpos = 0;
-            return true;
-        }
-        else if(sscanf(c.input, "regserv %d", &port) == 1)
-        {
-            if(checkban(servbans, c.address.host)) return false;
-            if(port < 0 || port + 1 < 0 || (c.servport >= 0 && port != c.servport)) outputf(c, "failreg invalid port\n");
-            else
-            {
-                c.servport = port;
-                addgameserver(c);
-            }
-        }
-        else if(sscanf(c.input, "reqauth %u %100s", &id, user) == 2)
-        {
-            reqauth(c, id, user);
-        }
-        else if(sscanf(c.input, "confauth %u %100s", &id, val) == 2)
-        {
-            confauth(c, id, val);
-        }
-        c.inputpos = &c.input[c.inputpos] - end;
-        memmove(c.input, end, c.inputpos);
-
-        end = (char *)memchr(c.input, '\n', c.inputpos);
-    }
-    return c.inputpos<(int)sizeof(c.input);
-}
-
-ENetSocketSet readset, writeset;
-
-void checkclients()
-{
-    ENetSocketSet readset, writeset;
-    ENetSocket maxsock = max(serversocket, pingsocket);
-    ENET_SOCKETSET_EMPTY(readset);
-    ENET_SOCKETSET_EMPTY(writeset);
-    ENET_SOCKETSET_ADD(readset, serversocket);
-    ENET_SOCKETSET_ADD(readset, pingsocket);
-    loopv(clients)
-    {
-        client &c = *clients[i];
-        if(c.list || c.outputpos < c.output.length()) ENET_SOCKETSET_ADD(writeset, c.socket);
-        else ENET_SOCKETSET_ADD(readset, c.socket);
-        maxsock = max(maxsock, c.socket);
-    }
-    if(enet_socketset_select(maxsock, &readset, &writeset, 1000)<=0) return;
-
-    if(ENET_SOCKETSET_CHECK(readset, pingsocket)) checkserverpongs();
-    if(ENET_SOCKETSET_CHECK(readset, serversocket))
-    {
-        ENetAddress address;
-        ENetSocket clientsocket = enet_socket_accept(serversocket, &address);
-        if(clients.length()>=CLIENT_LIMIT || checkban(bans, address.host)) enet_socket_destroy(clientsocket);
-        else if(clientsocket!=ENET_SOCKET_NULL)
-        {
-            int dups = 0, oldest = -1;
-            loopv(clients) if(clients[i]->address.host == address.host) 
-            {
-                dups++;
-                if(oldest<0 || clients[i]->connecttime < clients[oldest]->connecttime) oldest = i;
-            }
-            if(dups >= DUP_LIMIT) purgeclient(oldest);
-                
-            client *c = new client;
-            c->address = address;
-            c->socket = clientsocket;
-            c->connecttime = servtime;
-            c->lastinput = servtime;
-            clients.add(c);
-        }
-    }
-
-    loopv(clients)
-    {
-        client &c = *clients[i];
-        if((c.list || c.outputpos < c.output.length()) && ENET_SOCKETSET_CHECK(writeset, c.socket))
-        {
-            const char *data = c.list ? c.list->getbuf() : c.output.getbuf();
-            int len = c.list ? c.list->length() : c.output.length();
-            ENetBuffer buf;
-            buf.data = (void *)&data[c.outputpos];
-            buf.dataLength = len-c.outputpos;
-            int res = enet_socket_send(c.socket, NULL, &buf, 1);
-            if(res>=0) 
-            {
-                c.outputpos += res;
-                if(c.outputpos>=len)
-                {
-                    if(c.list) { purgeclient(i--); continue; }
-                    c.output.setsizenodelete(0);
-                    c.outputpos = 0;
-                }
-            }
-            else { purgeclient(i--); continue; }
-        }
-        if(ENET_SOCKETSET_CHECK(readset, c.socket))
-        {
-            ENetBuffer buf;
-            buf.data = &c.input[c.inputpos];
-            buf.dataLength = sizeof(c.input) - c.inputpos;
-            int res = enet_socket_receive(c.socket, NULL, &buf, 1);
-            if(res>0)
-            {
-                c.inputpos += res;
-                c.input[min(c.inputpos, (int)sizeof(c.input)-1)] = '\0';
-                if(!checkclientinput(c)) { purgeclient(i--); continue; }
-            }
-            else { purgeclient(i--); continue; }
-        }
-        if(c.output.length() > OUTPUT_LIMIT) { purgeclient(i--); continue; }
-        if(ENET_TIME_DIFFERENCE(servtime, c.lastinput) >= CLIENT_TIME) { purgeclient(i--); continue; }
-    }
-}
-
-void banclients()
-{
-    loopvrev(clients) if(checkban(bans, clients[i]->address.host)) purgeclient(i);
-}
-        
-volatile bool reloadcfg = true;
-
-void reloadsignal(int signum)
-{
-    reloadcfg = true;
-}
-
-int main(int argc, char **argv)
-{
-    const char *dir = "", *ip = NULL;
-    int port = 28787;
-    if(argc>=2) dir = argv[1];
-    if(argc>=3) port = atoi(argv[2]);
-    if(argc>=4) ip = argv[3];
-    defformatstring(logname)("%smaster.log", dir);
-    defformatstring(cfgname)("%smaster.cfg", dir);
-    path(logname);
-    path(cfgname);
-    logfile = fopen(logname, "a");
-    if(!logfile) logfile = stdout;
-    setvbuf(logfile, NULL, _IOLBF, 0);
-    signal(SIGUSR1, reloadsignal);
-    setupserver(port, ip);
-    for(;;) 
-    {
-        if(reloadcfg)
-        {
-            conoutf("reloading master.cfg");
-            execfile(cfgname);
-            bangameservers();
-            banclients();
-            reloadcfg = false;
-        }
-
-        servtime = enet_time_get();
-        checkclients();
-        checkgameservers();
-    }
-
-    return EXIT_SUCCESS;
-}
-
diff --git a/engine/material.cpp b/engine/material.cpp
index 2858c10..610c18f 100644
--- a/engine/material.cpp
+++ b/engine/material.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 struct QuadNode
@@ -133,7 +134,7 @@ struct material
     {"glass", MAT_GLASS},
     {"noclip", MAT_NOCLIP},
     {"lava", MAT_LAVA},
-    {"gameclip", MAT_GAMECLIP},
+    {"aiclip", MAT_AICLIP},
     {"death", MAT_DEATH}
 };
 
@@ -304,7 +305,7 @@ int optimizematsurfs(materialsurface *matbuf, int matsurfs)
          }
          else if(cur-start>=4)
          {
-            QuadNode vmats(0, 0, worldsize);
+            QuadNode vmats(0, 0, hdr.worldsize);
             loopi(cur-start) vmats.insert(start[i].o[C[dim]], start[i].o[R[dim]], start[i].csize);
             vmats.genmatsurfs(start->material, start->orient, start->o[dim], matbuf);
          }
@@ -417,17 +418,11 @@ void setupmaterials(int start, int len)
     }
     if(hasmat&(1<<MAT_WATER))
     {
-        loadcaustics(true);
-        preloadwatershaders(true);
-        lookupmaterialslot(MAT_WATER);
+        extern void loadcaustics();
+        loadcaustics();
+        lookuptexture(-MAT_WATER);
     }
-    if(hasmat&(1<<MAT_LAVA)) 
-    {
-        useshaderbyname("lava");
-        useshaderbyname("lavaglare");
-        lookupmaterialslot(MAT_LAVA);
-    }
-    if(hasmat&(1<<MAT_GLASS)) useshaderbyname("glass");
+    if(hasmat&(1<<MAT_LAVA)) lookuptexture(-MAT_LAVA);
 }
 
 VARP(showmat, 0, 1, 1);
@@ -486,7 +481,7 @@ void sortmaterials(vector<materialsurface *> &vismats)
             materialsurface &m = va->matbuf[i];
             if(!editmode || !showmat)
             {
-                if(m.material==MAT_WATER && (m.orient==O_TOP || (refracting<0 && reflectz>worldsize))) continue;
+                if(m.material==MAT_WATER && (m.orient==O_TOP || (refracting<0 && reflectz>hdr.worldsize))) continue;
                 if(m.material&MAT_EDIT) continue;
                 if(glaring && m.material!=MAT_LAVA) continue;
             }
@@ -512,13 +507,13 @@ void rendermatgrid(vector<materialsurface *> &vismats)
             lastmat = curmat;
             switch(curmat)
             {
-                case MAT_WATER:    glColor3ub( 0,  0, 85); break; // blue
-                case MAT_CLIP:     glColor3ub(85,  0,  0); break; // red
-                case MAT_GLASS:    glColor3ub( 0, 85, 85); break; // cyan
-                case MAT_NOCLIP:   glColor3ub( 0, 85,  0); break; // green
-                case MAT_LAVA:     glColor3ub(85, 40,  0); break; // orange
-                case MAT_GAMECLIP: glColor3ub(85, 85,  0); break; // yellow
-                case MAT_DEATH:    glColor3ub(40, 40, 40); break; // black
+                case MAT_WATER:  glColor3ub( 0,  0, 85); break; // blue
+                case MAT_CLIP:   glColor3ub(85,  0,  0); break; // red
+                case MAT_GLASS:  glColor3ub( 0, 85, 85); break; // cyan
+                case MAT_NOCLIP: glColor3ub( 0, 85,  0); break; // green
+                case MAT_LAVA:   glColor3ub(85, 40,  0); break; // orange
+                case MAT_AICLIP: glColor3ub(85, 85,  0); break; // yellow
+                case MAT_DEATH:  glColor3ub(40, 40, 40); break; // black
             }
         }
         drawmaterial(m.orient, m.o.x, m.o.y, m.o.z, m.csize, m.rsize, -0.1f);
@@ -551,7 +546,7 @@ void drawglass(int orient, int x, int y, int z, int csize, int rsize, float offs
     xtraverts += 4;
 }
 
-VARFP(waterfallenv, 0, 1, 1, preloadwatershaders());
+VARP(waterfallenv, 0, 1, 1);
 
 void rendermaterials()
 {
@@ -561,10 +556,11 @@ void rendermaterials()
 
     glDisable(GL_CULL_FACE);
 
-    Slot &wslot = lookupmaterialslot(MAT_WATER), &lslot = lookupmaterialslot(MAT_LAVA);
-    uchar wcol[4] = { watercolor[0], watercolor[1], watercolor[2], 192 }, 
-          wfcol[4] = { waterfallcolor[0], waterfallcolor[1], waterfallcolor[2], 192 };
-    if(!wfcol[0] && !wfcol[1] && !wfcol[2]) memcpy(wfcol, wcol, 3);
+    Slot &wslot = lookuptexture(-MAT_WATER), &lslot = lookuptexture(-MAT_LAVA);
+    uchar wcol[4], wfcol[4];
+    getwatercolour(wcol);
+    getwaterfallcolour(wfcol);
+    wcol[3] = wfcol[3] = 192;
     int lastorient = -1, lastmat = -1;
     GLenum textured = GL_TEXTURE_2D;
     bool begin = false, depth = true, blended = false, overbright = false, usedcamera = false, usedwaterfall = false;
@@ -795,13 +791,13 @@ void rendermaterials()
                     }
                     switch(curmat&~MAT_EDIT)
                     {
-                        case MAT_WATER:    glColor3ub(255, 128,   0); break; // blue
-                        case MAT_CLIP:     glColor3ub(  0, 255, 255); break; // red
-                        case MAT_GLASS:    glColor3ub(255,   0,   0); break; // cyan
-                        case MAT_NOCLIP:   glColor3ub(255,   0, 255); break; // green
-                        case MAT_LAVA:     glColor3ub(  0, 128, 255); break; // orange
-                        case MAT_GAMECLIP: glColor3ub(  0,   0, 255); break; // yellow
-                        case MAT_DEATH:    glColor3ub(192, 192, 192); break; // black
+                        case MAT_WATER:  glColor3ub(255, 128,   0); break; // blue
+                        case MAT_CLIP:   glColor3ub(  0, 255, 255); break; // red
+                        case MAT_GLASS:  glColor3ub(255,   0,   0); break; // cyan
+                        case MAT_NOCLIP: glColor3ub(255,   0, 255); break; // green
+                        case MAT_LAVA:   glColor3ub(  0, 128, 255); break; // orange
+                        case MAT_AICLIP: glColor3ub(  0,   0, 255); break; // yellow
+                        case MAT_DEATH:  glColor3ub(192, 192, 192); break; // black
                     }   
                     break;
                 }
diff --git a/engine/md2.h b/engine/md2.h
index bf4b928..579fde8 100644
--- a/engine/md2.h
+++ b/engine/md2.h
@@ -126,16 +126,16 @@ struct md2 : vertmodel
         
         bool load(char *filename)
         {
-            stream *file = openfile(filename, "rb");
+            FILE *file = openfile(filename, "rb");
             if(!file) return false;
 
             md2_header header;
-            file->read(&header, sizeof(md2_header));
-            lilswap(&header.magic, sizeof(md2_header)/sizeof(int));
+            fread(&header, sizeof(md2_header), 1, file);
+            endianswap(&header, sizeof(int), sizeof(md2_header)/sizeof(int));
 
             if(header.magic!=844121161 || header.version!=8) 
             {
-                delete file;
+                fclose(file);
                 return false;
             }
           
@@ -148,9 +148,9 @@ struct md2 : vertmodel
             meshes.add(&m);
 
             int *glcommands = new int[header.numglcommands];
-            file->seek(header.offsetglcommands, SEEK_SET); 
-            int numglcommands = file->read(glcommands, header.numglcommands*sizeof(int))/sizeof(int);
-            lilswap(glcommands, numglcommands);
+            fseek(file, header.offsetglcommands, SEEK_SET); 
+            int numglcommands = fread(glcommands, sizeof(int), header.numglcommands, file);
+            endianswap(glcommands, sizeof(int), numglcommands);
             if(numglcommands < header.numglcommands) memset(&glcommands[numglcommands], 0, (header.numglcommands-numglcommands)*sizeof(int));
 
             vector<tcvert> tcgen;
@@ -175,11 +175,11 @@ struct md2 : vertmodel
             loopi(header.numframes)
             {
                 md2_frame frame;
-                file->seek(frame_offset, SEEK_SET);
-                file->read(&frame, sizeof(md2_frame));
-                lilswap(frame.scale, 6);
+                fseek(file, frame_offset, SEEK_SET);
+                fread(&frame, sizeof(md2_frame), 1, file);
+                endianswap(&frame, sizeof(float), 6);
 
-                file->read(tmpverts, header.numvertices*sizeof(md2_vertex));
+                fread(tmpverts, sizeof(md2_vertex), header.numvertices, file);
                 loopj(m.numverts)
                 {
                     const md2_vertex &v = tmpverts[vgen[j]];
@@ -194,7 +194,7 @@ struct md2 : vertmodel
             }
             delete[] tmpverts;
 
-            delete file;
+            fclose(file);
 
             return true;
         }
@@ -208,8 +208,8 @@ struct md2 : vertmodel
             //                      D    D    D    D    D    D    A   P   I   R,  E    J   T   W    FO  SA  GS  GI
             static int _frame[] = { 178, 184, 190, 183, 189, 197, 46, 54, 0,  40, 162, 67, 95, 112, 72, 84, 7,  6 };
             static int _range[] = { 6,   6,   8,   1,   1,   1,   8,  4,  40, 6,  1,   1,  17, 11,  12, 11, 18, 1 };
-            //                      DE DY I  F  B  L  R  H1 H2 H3 H4 H5 H6 H7 A1 A2 A3 A4 A5 A6 A7 PA J   SI SW ED  LA  T   WI  LO  GI  GS
-            static int animfr[] = { 5, 2, 8, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 6, 6, 6, 6, 6, 6, 6, 7, 11, 8, 9, 10, 14, 12, 13, 15, 17, 16 };
+            //                      DE DY I  F  B  L  R  PU SH PA J   SI SW ED  LA  T   WI  LO  GS  GI
+            static int animfr[] = { 5, 2, 8, 9, 9, 9, 9, 6, 6, 7, 11, 8, 9, 10, 14, 12, 13, 15, 16, 17 };
             
             anim &= ANIM_INDEX;
             if((size_t)anim >= sizeof(animfr)/sizeof(animfr[0]))
@@ -219,25 +219,28 @@ struct md2 : vertmodel
                 return;
             }
             int n = animfr[anim];
-            switch(anim)
-            {
-                case ANIM_DYING:
-                case ANIM_DEAD:
-                    n -= varseed%3;
-                    break;
-                case ANIM_FORWARD:
-                case ANIM_BACKWARD:
-                case ANIM_LEFT:
-                case ANIM_RIGHT:
-                case ANIM_SWIM:
-                    info.speed = 5500.0f/d->maxspeed;
-                    break;
-            }
+            if(anim==ANIM_DYING || anim==ANIM_DEAD) n -= varseed%3;
             info.frame = _frame[n];
             info.range = _range[n];
         }
     };
 
+    void extendbb(int frame, vec &center, vec &radius, modelattach &a)
+    {
+        vec acenter, aradius;
+        a.m->boundbox(frame, acenter, aradius);
+        vec bbmin, bbmax;
+        loopi(3)
+        {
+            bbmin[i] = min(acenter[i]-aradius[i], center[i]-radius[i]); 
+            bbmax[i] = max(acenter[i]+aradius[i], center[i]+radius[i]);
+        }
+        radius = bbmax;
+        radius.sub(bbmin).mul(0.5f);
+        center = bbmin;
+        center.add(radius);
+    }
+
     meshgroup *loadmeshes(char *name, va_list args)
     {
         md2meshgroup *group = new md2meshgroup;
@@ -253,11 +256,11 @@ struct md2 : vertmodel
         mdl.model = this;
         mdl.index = 0;
         const char *pname = parentdir(loadname);
-        defformatstring(name1)("packages/models/%s/tris.md2", loadname);
+        s_sprintfd(name1)("packages/models/%s/tris.md2", loadname);
         mdl.meshes = sharemeshes(path(name1));
         if(!mdl.meshes)
         {
-            defformatstring(name2)("packages/models/%s/tris.md2", pname);    // try md2 in parent folder (vert sharing)
+            s_sprintfd(name2)("packages/models/%s/tris.md2", pname);    // try md2 in parent folder (vert sharing)
             mdl.meshes = sharemeshes(path(name2));
             if(!mdl.meshes) return false;
         }
@@ -267,19 +270,15 @@ struct md2 : vertmodel
         if(tex==notexture) conoutf("could not load model skin for %s", name1);
         loadingmd2 = this;
         persistidents = false;
-        defformatstring(name3)("packages/models/%s/md2.cfg", loadname);
-        if(!execfile(name3, false))
+        s_sprintfd(name3)("packages/models/%s/md2.cfg", loadname);
+        if(!execfile(name3))
         {
-            formatstring(name3)("packages/models/%s/md2.cfg", pname);
-            execfile(name3, false);
+            s_sprintf(name3)("packages/models/%s/md2.cfg", pname);
+            execfile(name3);
         }
         persistidents = true;
         loadingmd2 = 0;
-        scale /= 4;
-        translate.y = -translate.y;
-        parts[0]->translate = translate;
-        loopv(parts) parts[i]->meshes->shared++;
-        preloadshaders();
+        loopv(parts) parts[i]->meshes = parts[i]->meshes->scaleverts(scale/4.0f, i ? vec(0, 0, 0) : vec(translate.x, -translate.y, translate.z));
         return loaded = true;
     }
 };
diff --git a/engine/md3.h b/engine/md3.h
index 6f91e71..4a092ca 100644
--- a/engine/md3.h
+++ b/engine/md3.h
@@ -58,15 +58,15 @@ struct md3 : vertmodel
     {
         bool load(char *path)
         {
-            stream *f = openfile(path, "rb");
+            FILE *f = openfile(path, "rb");
             if(!f) return false;
             md3header header;
-            f->read(&header, sizeof(md3header));
-            lilswap(&header.version, 1);
-            lilswap(&header.flags, 9);
+            fread(&header, sizeof(md3header), 1, f);
+            endianswap(&header.version, sizeof(int), 1);
+            endianswap(&header.flags, sizeof(int), 9);
             if(strncmp(header.id, "IDP3", 4) != 0 || header.version != 15) // header check
             { 
-                delete f;
+                fclose(f);
                 conoutf("md3: corrupted header"); 
                 return false; 
             }
@@ -74,6 +74,43 @@ struct md3 : vertmodel
             name = newstring(path);
 
             numframes = header.numframes;
+            numtags = header.numtags;        
+            if(numtags)
+            {
+                tags = new tag[numframes*numtags];
+                fseek(f, header.ofs_tags, SEEK_SET);
+                md3tag tag;
+                
+                loopi(header.numframes*header.numtags) 
+                {
+                    fread(&tag, sizeof(md3tag), 1, f);
+                    endianswap(&tag.pos, sizeof(float), 12);
+                    if(tag.name[0] && i<header.numtags) tags[i].name = newstring(tag.name);
+                    matrix3x4 &m = tags[i].transform;
+                    tag.pos.y *= -1;
+                    // undo the -y
+                    loopj(3) tag.rotation[1][j] *= -1;
+                    // then restore it
+                    loopj(3) tag.rotation[j][1] *= -1;
+                    m.X.w = tag.pos.x;
+                    m.Y.w = tag.pos.y;
+                    m.Z.w = tag.pos.z;
+                    loopj(3) 
+                    {
+                        m.X[j] = tag.rotation[j][0];
+                        m.Y[j] = tag.rotation[j][1];
+                        m.Z[j] = tag.rotation[j][2];
+                    }
+#if 0
+                    tags[i].pos = vec(tag.pos.x, -tag.pos.y, tag.pos.z);
+                    memcpy(tags[i].transform, tag.rotation, sizeof(tag.rotation));
+                    // undo the -y
+                    loopj(3) tags[i].transform[1][j] *= -1;
+                    // then restore it
+                    loopj(3) tags[i].transform[j][1] *= -1;
+#endif
+                }
+            }
 
             int mesh_offset = header.ofs_meshes;
             loopi(header.nummeshes)
@@ -83,36 +120,36 @@ struct md3 : vertmodel
                 meshes.add(&m);
 
                 md3meshheader mheader;
-                f->seek(mesh_offset, SEEK_SET);
-                f->read(&mheader, sizeof(md3meshheader));
-                lilswap(&mheader.flags, 10); 
+                fseek(f, mesh_offset, SEEK_SET);
+                fread(&mheader, sizeof(md3meshheader), 1, f);
+                endianswap(&mheader.flags, sizeof(int), 10); 
 
                 m.name = newstring(mheader.name);
                
                 m.numtris = mheader.numtriangles; 
                 m.tris = new tri[m.numtris];
-                f->seek(mesh_offset + mheader.ofs_triangles, SEEK_SET);
+                fseek(f, mesh_offset + mheader.ofs_triangles, SEEK_SET);
                 loopj(m.numtris)
                 {
                     md3triangle tri;
-                    f->read(&tri, sizeof(md3triangle)); // read the triangles
-                    lilswap(tri.vertexindices, 3);
+                    fread(&tri, sizeof(md3triangle), 1, f); // read the triangles
+                    endianswap(&tri, sizeof(int), 3);
                     loopk(3) m.tris[j].vert[k] = (ushort)tri.vertexindices[k];
                 }
 
                 m.numverts = mheader.numvertices;
                 m.tcverts = new tcvert[m.numverts];
-                f->seek(mesh_offset + mheader.ofs_uv , SEEK_SET); 
-                f->read(m.tcverts, m.numverts*2*sizeof(float)); // read the UV data
-                lilswap(&m.tcverts[0].u, 2*m.numverts);
+                fseek(f, mesh_offset + mheader.ofs_uv , SEEK_SET); 
+                fread(m.tcverts, 2*sizeof(float), m.numverts, f); // read the UV data
+                endianswap(m.tcverts, sizeof(float), 2*m.numverts);
                 
                 m.verts = new vert[numframes*m.numverts];
-                f->seek(mesh_offset + mheader.ofs_vertices, SEEK_SET); 
+                fseek(f, mesh_offset + mheader.ofs_vertices, SEEK_SET); 
                 loopj(numframes*m.numverts)
                 {
                     md3vertex v;
-                    f->read(&v, sizeof(md3vertex)); // read the vertices
-                    lilswap(v.vertex, 4);
+                    fread(&v, sizeof(md3vertex), 1, f); // read the vertices
+                    endianswap(&v, sizeof(short), 4);
 
                     m.verts[j].pos.x = v.vertex[0]/64.0f;
                     m.verts[j].pos.y = -v.vertex[1]/64.0f;
@@ -128,49 +165,20 @@ struct md3 : vertmodel
                 mesh_offset += mheader.meshsize;
             }
 
-            numtags = header.numtags;
-            if(numtags)
-            {
-                tags = new tag[numframes*numtags];
-                f->seek(header.ofs_tags, SEEK_SET);
-                md3tag tag;
-
-                loopi(header.numframes*header.numtags)
-                {
-                    f->read(&tag, sizeof(md3tag));
-                    lilswap(&tag.pos.x, 12);
-                    if(tag.name[0] && i<header.numtags) tags[i].name = newstring(tag.name);
-                    matrix3x4 &m = tags[i].transform;
-                    tag.pos.y *= -1;
-                    // undo the -y
-                    loopj(3) tag.rotation[1][j] *= -1;
-                    // then restore it
-                    loopj(3) tag.rotation[j][1] *= -1;
-                    m.a.w = tag.pos.x;
-                    m.b.w = tag.pos.y;
-                    m.c.w = tag.pos.z;
-                    loopj(3)
-                    {
-                        m.a[j] = tag.rotation[j][0];
-                        m.b[j] = tag.rotation[j][1];
-                        m.c[j] = tag.rotation[j][2];
-                    }
-#if 0
-                    tags[i].pos = vec(tag.pos.x, -tag.pos.y, tag.pos.z);
-                    memcpy(tags[i].transform, tag.rotation, sizeof(tag.rotation));
-                    // undo the -y
-                    loopj(3) tags[i].transform[1][j] *= -1;
-                    // then restore it
-                    loopj(3) tags[i].transform[j][1] *= -1;
-#endif
-                }
-            }
-
-            delete f;
+            fclose(f);
             return true;
         }
     };
     
+    void extendbb(int frame, vec &center, vec &radius, modelattach &a)
+    {
+        vec acenter, aradius;
+        a.m->boundbox(frame, acenter, aradius);
+        float margin = 2*max(aradius.x, max(aradius.y, aradius.z));
+        radius.x += margin;
+        radius.y += margin;
+    }   
+
     meshgroup *loadmeshes(char *name, va_list args)
     {
         md3meshgroup *group = new md3meshgroup;
@@ -185,11 +193,11 @@ struct md3 : vertmodel
         parts.add(&mdl);
         mdl.model = this;
         mdl.index = 0;
-        defformatstring(name1)("packages/models/%s/tris.md3", loadname);
+        s_sprintfd(name1)("packages/models/%s/tris.md3", loadname);
         mdl.meshes = sharemeshes(path(name1));
         if(!mdl.meshes)
         {
-            defformatstring(name2)("packages/models/%s/tris.md3", pname);    // try md3 in parent folder (vert sharing)
+            s_sprintfd(name2)("packages/models/%s/tris.md3", pname);    // try md3 in parent folder (vert sharing)
             mdl.meshes = sharemeshes(path(name2));
             if(!mdl.meshes) return false;
         }
@@ -203,12 +211,12 @@ struct md3 : vertmodel
     bool load()
     {
         if(loaded) return true;
-        formatstring(md3dir)("packages/models/%s", loadname);
-        defformatstring(cfgname)("packages/models/%s/md3.cfg", loadname);
+        s_sprintf(md3dir)("packages/models/%s", loadname);
+        s_sprintfd(cfgname)("packages/models/%s/md3.cfg", loadname);
 
         loadingmd3 = this;
         persistidents = false;
-        if(execfile(cfgname, false) && parts.length()) // configured md3, will call the md3* commands below
+        if(execfile(cfgname) && parts.length()) // configured md3, will call the md3* commands below
         {
             persistidents = true;
             loadingmd3 = NULL;
@@ -220,11 +228,7 @@ struct md3 : vertmodel
             loadingmd3 = NULL;
             if(!loaddefaultparts()) return false;
         }
-        scale /= 4;
-        translate.y = -translate.y;
-        parts[0]->translate = translate;
-        loopv(parts) parts[i]->meshes->shared++;
-        preloadshaders();
+        loopv(parts) parts[i]->meshes = parts[i]->meshes->scaleverts(scale/4.0f, i ? vec(0, 0, 0) : vec(translate.x, -translate.y, translate.z));
         return loaded = true;
     }
 };
@@ -232,7 +236,7 @@ struct md3 : vertmodel
 void md3load(char *model)
 {   
     if(!loadingmd3) { conoutf("not loading an md3"); return; }
-    defformatstring(filename)("%s/%s", md3dir, model);
+    s_sprintfd(filename)("%s/%s", md3dir, model);
     md3::part &mdl = *new md3::part;
     loadingmd3->parts.add(&mdl);
     mdl.model = loadingmd3;
@@ -283,7 +287,7 @@ void md3skin(char *meshname, char *tex, char *masks, float *envmapmax, float *en
         s.tex = textureload(makerelpath(md3dir, tex), 0, true, false);
         if(*masks)
         {
-            s.masks = textureload(makerelpath(md3dir, masks, NULL, "<ffmask:25>"), 0, true, false);
+            s.masks = textureload(makerelpath(md3dir, masks, "<ffmask:25>"), 0, true, false);
             s.envmapmax = *envmapmax;
             s.envmapmin = *envmapmin;
         }
@@ -329,11 +333,6 @@ void md3alphablend(char *meshname, int *blend)
     loopmd3skins(meshname, s, s.alphablend = *blend!=0);
 }
 
-void md3cullface(char *meshname, int *cullface)
-{
-    loopmd3skins(meshname, s, s.cullface = *cullface!=0);
-}
-
 void md3envmap(char *meshname, char *envmap)
 {
     Texture *tex = cubemapload(envmap);
@@ -348,6 +347,11 @@ void md3bumpmap(char *meshname, char *normalmap, char *skin)
     loopmd3skins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
 }
 
+void md3translucent(char *meshname, float *translucency)
+{
+    loopmd3skins(meshname, s, s.translucency = *translucency);
+}
+
 void md3fullbright(char *meshname, float *fullbright)
 {
     loopmd3skins(meshname, s, s.fullbright = *fullbright);
@@ -396,9 +400,9 @@ COMMAND(md3glow, "si");
 COMMAND(md3glare, "sff");
 COMMAND(md3alphatest, "sf");
 COMMAND(md3alphablend, "si");
-COMMAND(md3cullface, "si");
 COMMAND(md3envmap, "ss");
 COMMAND(md3bumpmap, "sss");
+COMMAND(md3translucent, "sf");
 COMMAND(md3fullbright, "sf");
 COMMAND(md3shader, "ss");
 COMMAND(md3scroll, "sff");
diff --git a/engine/md5.h b/engine/md5.h
index a4a1272..b583c24 100644
--- a/engine/md5.h
+++ b/engine/md5.h
@@ -29,16 +29,6 @@ struct md5hierarchy
     int parent, flags, start;
 };
 
-struct md5adjustment
-{
-    float yaw, pitch, roll;
-    vec translate;
-
-    md5adjustment(float yaw, float pitch, float roll, const vec &translate) : yaw(yaw), pitch(pitch), roll(roll), translate(translate) {}
-};
-
-vector<md5adjustment> md5adjustments;
-
 struct md5 : skelmodel
 {
     md5(const char *name) : skelmodel(name) {}
@@ -95,15 +85,34 @@ struct md5 : skelmodel
             }
         }
 
-        void load(stream *f, char *buf, size_t bufsize)
+        void buildnorms(bool areaweight = true)
+        {
+            loopi(numverts) verts[i].norm = vec(0, 0, 0);
+            loopi(numtris)
+            {
+                tri &t = tris[i];
+                vert &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]];
+                vec norm;
+                norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos)); 
+                if(!areaweight) norm.normalize();
+                v1.norm.add(norm);
+                v2.norm.add(norm);
+                v3.norm.add(norm);
+            }
+            loopi(numverts) verts[i].norm.normalize();
+        }
+
+        void load(FILE *f, char *buf, size_t bufsize)
         {
             md5weight w;
             md5vert v;
             tri t;
             int index;
 
-            while(f->getline(buf, bufsize) && buf[0]!='}')
+            for(;;)
             {
+                fgets(buf, bufsize, f);
+                if(buf[0]=='}' || feof(f)) break;
                 if(strstr(buf, "// meshes:"))
                 {
                     char *start = strchr(buf, ':')+1;
@@ -167,38 +176,42 @@ struct md5 : skelmodel
         {
         }
 
-        bool loadmd5mesh(const char *filename, float smooth)
+        bool loadmd5mesh(const char *filename)
         {
-            stream *f = openfile(filename, "r");
+            FILE *f = openfile(filename, "r");
             if(!f) return false;
 
             char buf[512];
             vector<md5joint> basejoints;
-            while(f->getline(buf, sizeof(buf)))
+            for(;;)
             {
+                fgets(buf, sizeof(buf), f);
+                if(feof(f)) break;
                 int tmp;
                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
                 {
-                    if(tmp!=10) { delete f; return false; }
+                    if(tmp!=10) { fclose(f); return false; }
                 }
                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
                 {
-                    if(tmp<1) { delete f; return false; }
+                    if(tmp<1) { fclose(f); return false; }
                     if(skel->numbones>0) continue;
                     skel->numbones = tmp;
                     skel->bones = new boneinfo[skel->numbones];
                 }
                 else if(sscanf(buf, " numMeshes %d", &tmp)==1)
                 {
-                    if(tmp<1) { delete f; return false; }
+                    if(tmp<1) { fclose(f); return false; }
                 }
                 else if(strstr(buf, "joints {"))
                 {
                     string name;
                     int parent;
                     md5joint j;
-                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
+                    for(;;)
                     {
+                        fgets(buf, sizeof(buf), f);
+                        if(buf[0]=='}' || feof(f)) break;
                         if(sscanf(buf, " %s %d ( %f %f %f ) ( %f %f %f )",
                             name, &parent, &j.pos.x, &j.pos.y, &j.pos.z,
                             &j.orient.x, &j.orient.y, &j.orient.z)==8)
@@ -219,7 +232,7 @@ struct md5 : skelmodel
                             basejoints.add(j);
                         }
                     }
-                    if(basejoints.length()!=skel->numbones) { delete f; return false; }
+                    if(basejoints.length()!=skel->numbones) { fclose(f); return false; }
                 }
                 else if(strstr(buf, "mesh {"))
                 {
@@ -246,14 +259,13 @@ struct md5 : skelmodel
             {
                 md5mesh &m = *(md5mesh *)meshes[i];
                 m.buildverts(basejoints);
-                if(smooth <= 1) m.smoothnorms(smooth);
-                else m.buildnorms();
+                m.buildnorms();
                 m.cleanup();
             }
             
             sortblendcombos();
 
-            delete f;
+            fclose(f);
             return true;
         }
 
@@ -262,7 +274,7 @@ struct md5 : skelmodel
             skelanimspec *sa = skel->findskelanim(filename);
             if(sa) return sa;
 
-            stream *f = openfile(filename, "r");
+            FILE *f = openfile(filename, "r");
             if(!f) return NULL;
 
             vector<md5hierarchy> hierarchy;
@@ -271,34 +283,43 @@ struct md5 : skelmodel
             float *animdata = NULL;
             dualquat *animbones = NULL;
             char buf[512];
-            while(f->getline(buf, sizeof(buf)))
+            for(;;)
             {
+                fgets(buf, sizeof(buf), f);
+                if(feof(f)) break;
                 int tmp;
                 if(sscanf(buf, " MD5Version %d", &tmp)==1)
                 {
-                    if(tmp!=10) { delete f; return NULL; }
+                    if(tmp!=10) { fclose(f); return NULL; }
                 }
                 else if(sscanf(buf, " numJoints %d", &tmp)==1)
                 {
-                    if(tmp!=skel->numbones) { delete f; return NULL; }
+                    if(tmp!=skel->numbones) { fclose(f); return NULL; }
                 }
                 else if(sscanf(buf, " numFrames %d", &animframes)==1)
                 {
-                    if(animframes<1) { delete f; return NULL; }
+                    if(animframes<1) { fclose(f); return NULL; }
                 }
                 else if(sscanf(buf, " frameRate %d", &tmp)==1);
                 else if(sscanf(buf, " numAnimatedComponents %d", &animdatalen)==1)
                 {
-                    if(animdatalen>0) animdata = new float[animdatalen];
+                    if(animdatalen<1) { fclose(f); return NULL; }
+                    animdata = new float[animdatalen];
                 }
                 else if(strstr(buf, "bounds {"))
                 {
-                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}');
+                    for(;;)
+                    {
+                        fgets(buf, sizeof(buf), f);
+                        if(buf[0]=='}' || feof(f)) break;
+                    }
                 }
                 else if(strstr(buf, "hierarchy {"))
                 {
-                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
+                    for(;;)
                     {
+                        fgets(buf, sizeof(buf), f);
+                        if(buf[0]=='}' || feof(f)) break;
                         md5hierarchy h;
                         if(sscanf(buf, " %s %d %d %d", h.name, &h.parent, &h.flags, &h.start)==4)
                             hierarchy.add(h);
@@ -306,8 +327,10 @@ struct md5 : skelmodel
                 }
                 else if(strstr(buf, "baseframe {"))
                 {
-                    while(f->getline(buf, sizeof(buf)) && buf[0]!='}')
+                    for(;;)
                     {
+                        fgets(buf, sizeof(buf), f);
+                        if(buf[0]=='}' || feof(f)) break;
                         md5joint j;
                         if(sscanf(buf, " ( %f %f %f ) ( %f %f %f )", &j.pos.x, &j.pos.y, &j.pos.z, &j.orient.x, &j.orient.y, &j.orient.z)==6)
                         {
@@ -318,7 +341,7 @@ struct md5 : skelmodel
                             basejoints.add(j);
                         }
                     }
-                    if(basejoints.length()!=skel->numbones) { delete f; return NULL; }
+                    if(basejoints.length()!=skel->numbones) { fclose(f); return NULL; }
                     animbones = new dualquat[(skel->numframes+animframes)*skel->numbones];
                     if(skel->bones)
                     {
@@ -336,22 +359,15 @@ struct md5 : skelmodel
                 }
                 else if(sscanf(buf, " frame %d", &tmp)==1)
                 {
-                    for(int numdata = 0; f->getline(buf, sizeof(buf)) && buf[0]!='}';)
-                    {
-                        for(char *src = buf, *next = src; numdata < animdatalen; numdata++, src = next)
-                        {
-                            animdata[numdata] = strtod(src, &next);
-                            if(next <= src) break;
-                        }
-                    }
+                    loopi(animdatalen) fscanf(f, "%f", &animdata[i]);
                     dualquat *frame = &animbones[tmp*skel->numbones];
                     loopv(basejoints)
                     {
                         md5hierarchy &h = hierarchy[i];
                         md5joint j = basejoints[i];
-                        if(h.start < animdatalen && h.flags)
+                        float *jdata = &animdata[h.start];
+                        if(h.flags)
                         {
-                            float *jdata = &animdata[h.start];
                             if(h.flags&1) j.pos.x = *jdata++;
                             if(h.flags&2) j.pos.y = -*jdata++;
                             if(h.flags&4) j.pos.z = *jdata++;
@@ -362,24 +378,16 @@ struct md5 : skelmodel
                             /*if(memcmp(&j, &basejoints[i], sizeof(j))) usedjoints[i] = 1; */
                         }
                         frame[i] = dualquat(j.orient, j.pos);
-                        frame[i].fixantipodal(skel->framebones[i]);
 #if 0
                         if(h.parent<0) frame[i] = dualquat(j.orient, j.pos); 
                         else (frame[i] = frame[h.parent]).mul(dualquat(j.orient, j.pos));
 #endif
                     }
-                    loopv(md5adjustments)
-                    {
-                        if(md5adjustments[i].yaw) frame[i].mulorient(quat(vec(0, 0, 1), md5adjustments[i].yaw*RAD));
-                        if(md5adjustments[i].pitch) frame[i].mulorient(quat(vec(0, -1, 0), md5adjustments[i].pitch*RAD));
-                        if(md5adjustments[i].roll) frame[i].mulorient(quat(vec(-1, 0, 0), md5adjustments[i].roll*RAD));
-                        if(!md5adjustments[i].translate.iszero()) frame[i].translate(md5adjustments[i].translate);
-                    }
                 }    
             }
 
             DELETEA(animdata);
-            delete f;
+            fclose(f);
 
 #if 0
             vector<dualquat> invbase;
@@ -413,21 +421,30 @@ struct md5 : skelmodel
             return sa;
         }
 
-        bool load(const char *meshfile, float smooth)
+        bool load(const char *meshfile)
         {
             name = newstring(meshfile);
 
-            if(!loadmd5mesh(meshfile, smooth)) return false;
+            if(!loadmd5mesh(meshfile)) return false;
             
             return true;
         }
     };            
 
+    void extendbb(int frame, vec &center, vec &radius, modelattach &a)
+    {
+        vec acenter, aradius;
+        a.m->boundbox(frame, acenter, aradius);
+        float margin = 2*max(aradius.x, max(aradius.y, aradius.z));
+        radius.x += margin;
+        radius.y += margin;
+    }
+
     meshgroup *loadmeshes(char *name, va_list args)
     {
         md5meshgroup *group = new md5meshgroup;
         group->shareskeleton(va_arg(args, char *));
-        if(!group->load(name, va_arg(args, double))) { delete group; return NULL; }
+        if(!group->load(name)) { delete group; return NULL; }
         return group;
     }
 
@@ -438,16 +455,15 @@ struct md5 : skelmodel
         mdl.model = this;
         mdl.index = 0;
         mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
-        md5adjustments.setsizenodelete(0);
         const char *fname = loadname + strlen(loadname);
         do --fname; while(fname >= loadname && *fname!='/' && *fname!='\\');
         fname++;
-        defformatstring(meshname)("packages/models/%s/%s.md5mesh", loadname, fname);
-        mdl.meshes = sharemeshes(path(meshname), NULL, 0);
+        s_sprintfd(meshname)("packages/models/%s/%s.md5mesh", loadname, fname);
+        mdl.meshes = sharemeshes(path(meshname));
         if(!mdl.meshes) return false;
         mdl.initanimparts();
         mdl.initskins();
-        defformatstring(animname)("packages/models/%s/%s.md5anim", loadname, fname);
+        s_sprintfd(animname)("packages/models/%s/%s.md5anim", loadname, fname);
         ((md5meshgroup *)mdl.meshes)->loadmd5anim(path(animname));
         return true;
     }
@@ -455,57 +471,44 @@ struct md5 : skelmodel
     bool load()
     {
         if(loaded) return true;
-        formatstring(md5dir)("packages/models/%s", loadname);
-        defformatstring(cfgname)("packages/models/%s/md5.cfg", loadname);
+        s_sprintf(md5dir)("packages/models/%s", loadname);
+        s_sprintfd(cfgname)("packages/models/%s/md5.cfg", loadname);
 
         loadingmd5 = this;
         persistidents = false;
-        if(execfile(cfgname, false) && parts.length()) // configured md5, will call the md5* commands below
+        if(execfile(cfgname) && parts.length()) // configured md5, will call the md5* commands below
         {
             persistidents = true;
             loadingmd5 = NULL;
             loopv(parts) if(!parts[i]->meshes) return false;
         }
-        else // md5 without configuration, try default tris and skin 
+        else if(!loaddefaultparts()) // md5 without configuration, try default tris and skin
         {
             persistidents = true;
-            if(!loaddefaultparts()) 
-            {
-                loadingmd5 = NULL;
-                return false;
-            }
             loadingmd5 = NULL;
+            return false;
         }
-        scale /= 4;
-        parts[0]->translate = translate;
+        loadingmd5 = NULL;
         loopv(parts) 
         {
             skelpart *p = (skelpart *)parts[i];
             p->endanimparts();
-            p->meshes->shared++;
+            p->meshes = p->meshes->scaleverts(scale/4.0f, i ? vec(0, 0, 0) : vec(translate.x, translate.y, translate.z));
         }
-        preloadshaders();
         return loaded = true;
     }
 };
 
-void setmd5dir(char *name)
-{
-    if(!loadingmd5) { conoutf("not loading an md5"); return; }
-    formatstring(md5dir)("packages/models/%s", name);
-}
-    
-void md5load(char *meshfile, char *skelname, float *smooth)
+void md5load(char *meshfile, char *skelname)
 {
     if(!loadingmd5) { conoutf("not loading an md5"); return; }
-    defformatstring(filename)("%s/%s", md5dir, meshfile);
+    s_sprintfd(filename)("%s/%s", md5dir, meshfile);
     md5::skelpart &mdl = *new md5::skelpart;
     loadingmd5->parts.add(&mdl);
     mdl.model = loadingmd5;
     mdl.index = loadingmd5->parts.length()-1;
     mdl.pitchscale = mdl.pitchoffset = mdl.pitchmin = mdl.pitchmax = 0;
-    md5adjustments.setsizenodelete(0);
-    mdl.meshes = loadingmd5->sharemeshes(path(filename), skelname[0] ? skelname : NULL, double(*smooth > 0 ? cos(clamp(*smooth, 0.0f, 90.0f)*RAD) : 2));
+    mdl.meshes = loadingmd5->sharemeshes(path(filename), skelname[0] ? skelname : NULL);
     if(!mdl.meshes) conoutf("could not load %s", filename); // ignore failure
     else 
     {
@@ -570,18 +573,6 @@ void md5pitch(char *name, float *pitchscale, float *pitchoffset, float *pitchmin
     }
 }
 
-void md5adjust(char *name, float *yaw, float *pitch, float *roll, float *tx, float *ty, float *tz)
-{
-    if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; }
-    md5::part &mdl = *loadingmd5->parts.last();
-
-    if(!name[0]) return;
-    int i = mdl.meshes ? ((md5::skelmeshgroup *)mdl.meshes)->skel->findbone(name) : -1;
-    if(i < 0) {  conoutf("could not find bone %s to adjust", name); return; }
-    while(!md5adjustments.inrange(i)) md5adjustments.add(md5adjustment(0, 0, 0, vec(0, 0, 0)));
-    md5adjustments[i] = md5adjustment(*yaw, *pitch, *roll, vec(*tx/4, *ty/4, *tz/4));
-}
- 
 #define loopmd5meshes(meshname, m, body) \
     if(!loadingmd5 || loadingmd5->parts.empty()) { conoutf("not loading an md5"); return; } \
     md5::part &mdl = *loadingmd5->parts.last(); \
@@ -603,7 +594,7 @@ void md5skin(char *meshname, char *tex, char *masks, float *envmapmax, float *en
         s.tex = textureload(makerelpath(md5dir, tex), 0, true, false);
         if(*masks)
         {
-            s.masks = textureload(makerelpath(md5dir, masks, NULL, "<ffmask:25>"), 0, true, false);
+            s.masks = textureload(makerelpath(md5dir, masks, "<ffmask:25>"), 0, true, false);
             s.envmapmax = *envmapmax;
             s.envmapmin = *envmapmin;
         }
@@ -649,11 +640,6 @@ void md5alphablend(char *meshname, int *blend)
     loopmd5skins(meshname, s, s.alphablend = *blend!=0);
 }
 
-void md5cullface(char *meshname, int *cullface)
-{
-    loopmd5skins(meshname, s, s.cullface = *cullface!=0);
-}
-
 void md5envmap(char *meshname, char *envmap)
 {
     Texture *tex = cubemapload(envmap);
@@ -668,6 +654,11 @@ void md5bumpmap(char *meshname, char *normalmap, char *skin)
     loopmd5skins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
 }
 
+void md5translucent(char *meshname, float *translucency)
+{
+    loopmd5skins(meshname, s, s.translucency = *translucency);
+}
+
 void md5fullbright(char *meshname, float *fullbright)
 {
     loopmd5skins(meshname, s, s.fullbright = *fullbright);
@@ -692,9 +683,8 @@ void md5anim(char *anim, char *animfile, float *speed, int *priority)
     if(anims.empty()) conoutf("could not find animation %s", anim);
     else 
     {
+        s_sprintfd(filename)("%s/%s", md5dir, animfile);
         md5::part *p = loadingmd5->parts.last();
-        if(!p->meshes) return;
-        defformatstring(filename)("%s/%s", md5dir, animfile);
         md5::skelanimspec *sa = ((md5::md5meshgroup *)p->meshes)->loadmd5anim(path(filename));
         if(!sa) conoutf("could not load md5anim file %s", filename);
         else loopv(anims)
@@ -739,11 +729,9 @@ void md5noclip(char *meshname, int *noclip)
     loopmd5meshes(meshname, m, m.noclip = *noclip!=0);
 }
 
-COMMANDN(md5dir, setmd5dir, "s");
-COMMAND(md5load, "ssf");
+COMMAND(md5load, "ss");
 COMMAND(md5tag, "ss");
 COMMAND(md5pitch, "sffff");
-COMMAND(md5adjust, "sffffff");
 COMMAND(md5skin, "sssff");
 COMMAND(md5spec, "si");
 COMMAND(md5ambient, "si");
@@ -751,9 +739,9 @@ COMMAND(md5glow, "si");
 COMMAND(md5glare, "sff");
 COMMAND(md5alphatest, "sf");
 COMMAND(md5alphablend, "si");
-COMMAND(md5cullface, "si");
 COMMAND(md5envmap, "ss");
 COMMAND(md5bumpmap, "sss");
+COMMAND(md5translucent, "sf");
 COMMAND(md5fullbright, "sf");
 COMMAND(md5shader, "ss");
 COMMAND(md5scroll, "sff");
diff --git a/engine/menus.cpp b/engine/menus.cpp
index baa422c..3c430d2 100644
--- a/engine/menus.cpp
+++ b/engine/menus.cpp
@@ -1,5 +1,6 @@
 // menus.cpp: ingame menu system (also used for scores and serverlist)
 
+#include "pch.h"
 #include "engine.h"
 
 #define GUI_TITLE_COLOR  0xFFDD88
@@ -13,9 +14,9 @@ static g3d_gui *cgui = NULL;
 
 struct menu : g3d_callback
 {
-    char *name, *header, *contents, *onclear;
+    char *name, *header, *contents;
 
-    menu() : name(NULL), header(NULL), contents(NULL), onclear(NULL) {}
+    menu() : name(NULL), header(NULL), contents(NULL) {}
 
     void gui(g3d_gui &g, bool firstpass)
     {
@@ -27,10 +28,7 @@ struct menu : g3d_callback
         cgui = NULL;
     }
 
-    virtual void clear() 
-    {
-        DELETEA(onclear);
-    }
+    virtual void clear() {}
 };
 
 static hashtable<const char *, menu> guis;
@@ -99,26 +97,18 @@ void showgui(const char *name)
     else restoregui(pos);
 }
 
-int cleargui(int n)
+int cleargui(int n = 0)
 {
     int clear = guistack.length();
-    if(mainmenu && !isconnected(true) && clear > 0 && guistack[0]->name && !strcmp(guistack[0]->name, "main")) 
-    {
-        clear--;
-        if(!clear) return 1;
-    }
     if(n>0) clear = min(clear, n);
     loopi(clear) popgui(); 
     if(!guistack.empty()) restoregui(guistack.length()-1);
     return clear;
 }
 
-void guionclear(char *action)
+void cleargui_(int *n)
 {
-    if(guistack.empty()) return;
-    menu *m = guistack.last();
-    DELETEA(m->onclear);
-    if(action[0]) m->onclear = newstring(action);
+    intret(cleargui(*n));
 }
 
 void guistayopen(char *contents)
@@ -179,20 +169,6 @@ void guiimage(char *path, char *action, float *scale, int *overlaid, char *alt)
     }
 }
 
-void guicolor(int *color)
-{
-    if(cgui) 
-    {   
-        defformatstring(desc)("0x%06X", *color);
-        cgui->text(desc, *color, NULL);
-    }
-}
-
-void guitextbox(char *text, int *width, int *height, int *color)
-{
-    if(cgui && text[0]) cgui->textbox(text, *width ? *width : 12, *height ? *height : 1, *color ? *color : 0xFFFFFF);
-}
-
 void guitext(char *name, char *icon)
 {
     if(cgui) cgui->text(name, icon[0] ? GUI_BUTTON_COLOR : GUI_TEXT_COLOR, icon[0] ? icon : "info");
@@ -223,10 +199,10 @@ static void updateval(char *var, int val, char *onchange)
         case ID_VAR:
         case ID_FVAR:
         case ID_SVAR:
-            formatstring(assign)("%s %d", var, val);
+            s_sprintf(assign)("%s %d", var, val);
             break;
         case ID_ALIAS: 
-            formatstring(assign)("%s = %d", var, val);
+            s_sprintf(assign)("%s = %d", var, val);
             break;
         default:
             return;
@@ -271,7 +247,7 @@ void guilistslider(char *var, char *list, char *onchange)
     if(vals.empty()) return;
     int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset;
     loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; }
-    defformatstring(label)("%d", val);
+    s_sprintfd(label)("%d", val);
     cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, label);
     if(offset != oldoffset) updateval(var, vals[offset], onchange);
 }
@@ -315,7 +291,7 @@ void guifield(char *var, int *maxlength, char *onchange)
     if(result) 
     {
         alias(var, result);
-        if(onchange[0]) executelater.add(newstring(onchange));
+        if(onchange[0]) execute(onchange);
     }
 }
 
@@ -327,21 +303,7 @@ void guieditor(char *name, int *maxlength, int *height, int *mode)
     //returns a non-NULL pointer (the currentline) when the user commits, could then manipulate via text* commands
 }
 
-//-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width
-void guikeyfield(char *var, int *maxlength, char *onchange)
-{
-    if(!cgui) return;
-    const char *initval = "";
-    ident *id = getident(var);
-    if(id && id->type==ID_ALIAS) initval = id->action;
-    char *result = cgui->keyfield(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : -8, 0, initval);
-    if(result)
-    {
-        alias(var, result);
-        if(onchange[0]) execute(onchange);
-    }
-}
-
+COMMAND(guieditor, "siii");
 //use text<action> to do more...
 
 
@@ -373,13 +335,14 @@ void newgui(char *name, char *contents, char *header)
 
 void guiservers()
 {
-    extern char *showservers(g3d_gui *cgui);
+    extern const char *showservers(g3d_gui *cgui);
     if(cgui) 
     {
-        char *command = showservers(cgui);
-        if(command)
+        const char *name = showservers(cgui);
+        if(name)
         {
-            executelater.add(command);
+            s_sprintfd(connect)("connect %s", name);
+            executelater.add(newstring(connect));
             if(shouldclearmenu) clearlater = true;
         }
     }
@@ -389,9 +352,8 @@ COMMAND(newgui, "sss");
 COMMAND(guibutton, "sss");
 COMMAND(guitext, "ss");
 COMMAND(guiservers, "s");
-ICOMMAND(cleargui, "i", (int *n), intret(cleargui(*n)));
+COMMANDN(cleargui, cleargui_, "i");
 COMMAND(showgui, "s");
-COMMAND(guionclear, "s");
 COMMAND(guistayopen, "s");
 COMMAND(guinoautotab, "s");
 
@@ -406,10 +368,6 @@ COMMAND(guibitfield, "ssis");
 COMMAND(guicheckbox, "ssiis");
 COMMAND(guitab, "s");
 COMMAND(guifield, "sis");
-COMMAND(guikeyfield, "sis");
-COMMAND(guieditor, "siii");
-COMMAND(guicolor, "i");
-COMMAND(guitextbox, "siii");
 
 struct change
 {
@@ -446,22 +404,19 @@ static struct applymenu : menu
 
     void clear()
     {
-        menu::clear();
         needsapply.setsize(0);
     }
 } applymenu;
 
 VARP(applydialog, 0, 1, 1);
 
-static bool processingmenu = false;
-
 void addchange(const char *desc, int type)
 {
     if(!applydialog) return;
     loopv(needsapply) if(!strcmp(needsapply[i].desc, desc)) return;
     needsapply.add(change(type, desc));
     if(needsapply.length() && guistack.find(&applymenu) < 0)
-        pushgui(&applymenu, processingmenu ? max(guistack.length()-1, 0) : -1);
+        pushgui(&applymenu, 0);
 }
 
 void clearchanges(int type)
@@ -479,42 +434,14 @@ void clearchanges(int type)
 
 void menuprocess()
 {
-    processingmenu = true;
-    int wasmain = mainmenu, level = guistack.length();
+    int level = guistack.length();
     loopv(executelater) execute(executelater[i]);
     executelater.deletecontentsa();
-    if(wasmain > mainmenu || clearlater)
+    if(clearlater)
     {
-        if(wasmain > mainmenu || level==guistack.length()) 
-        {
-            loopvrev(guistack)
-            {
-                menu *m = guistack[i];
-                if(m->onclear) 
-                {
-                    char *action = m->onclear;
-                    m->onclear = NULL;
-                    execute(action);
-                    delete[] action;
-                }
-            }
-            cleargui(level); 
-        }
+        if(level==guistack.length()) loopi(level) popgui();
         clearlater = false;
     }
-    if(mainmenu && !isconnected(true) && guistack.empty()) showgui("main");
-    processingmenu = false;
-}
-
-VAR(mainmenu, 1, 1, 0);
-
-void clearmainmenu()
-{
-    if(mainmenu && (isconnected() || haslocalclients()))
-    {
-        mainmenu = 0;
-        if(!processingmenu) cleargui();
-    }
 }
 
 void g3d_mainmenu()
@@ -522,7 +449,7 @@ void g3d_mainmenu()
     if(!guistack.empty()) 
     {   
         extern int usegui2d;
-        if(!mainmenu && !usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui();
+        if(!usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui();
         else g3d_addgui(guistack.last(), menupos, GUI_2D | GUI_FOLLOW);
     }
 }
diff --git a/engine/model.h b/engine/model.h
index 6f04f98..d8f2f6f 100644
--- a/engine/model.h
+++ b/engine/model.h
@@ -2,19 +2,20 @@ enum { MDL_MD2 = 1, MDL_MD3, MDL_MD5, MDL_OBJ };
 
 struct model
 {
-    float spinyaw, spinpitch, offsetyaw, offsetpitch;
-    bool collide, ellipsecollide, shadow, alphadepth;
+    float spin, offsetyaw, offsetpitch;
+    bool collide, ellipsecollide, cullface, shadow;
     float scale;
     vec translate;
     BIH *bih;
-    vec bbcenter, bbradius, bbextend;
+    vec bbcenter, bbradius;
     float eyeheight, collideradius, collideheight;
     int batch;
 
-    model() : spinyaw(0), spinpitch(0), offsetyaw(0), offsetpitch(0), collide(true), ellipsecollide(false), shadow(true), alphadepth(true), scale(1.0f), translate(0, 0, 0), bih(0), bbcenter(0, 0, 0), bbradius(0, 0, 0), bbextend(0, 0, 0), eyeheight(0.9f), collideradius(0), collideheight(0), batch(-1) {}
+    model() : spin(0), offsetyaw(0), offsetpitch(0), collide(true), ellipsecollide(false), cullface(true), shadow(true), scale(1.0f), translate(0, 0, 0), bih(0), bbcenter(0, 0, 0), bbradius(0, 0, 0), eyeheight(0.9f), collideradius(0), collideheight(0), batch(-1) {}
     virtual ~model() { DELETEP(bih); }
     virtual void calcbb(int frame, vec &center, vec &radius) = 0;
-    virtual void render(int anim, int basetime, int basetime2, const vec &o, float yaw, float pitch, dynent *d, modelattach *a = NULL, const vec &color = vec(0, 0, 0), const vec &dir = vec(0, 0, 0), float transparent = 1) = 0;
+    virtual void extendbb(int frame, vec &center, vec &radius, modelattach &a) {}
+    virtual void render(int anim, float speed, int basetime, const vec &o, float yaw, float pitch, dynent *d, modelattach *a = NULL, const vec &color = vec(0, 0, 0), const vec &dir = vec(0, 0, 0)) = 0;
     virtual bool load() = 0;
     virtual char *name() = 0;
     virtual int type() const = 0;
@@ -29,16 +30,15 @@ struct model
     virtual void setglare(float specglare, float glowglare) {}
     virtual void setalphatest(float alpha) {}
     virtual void setalphablend(bool blend) {}
+    virtual void settranslucency(float translucency) {}
     virtual void setfullbright(float fullbright) {}
-    virtual void setcullface(bool cullface) {}
 
-    virtual void preloadshaders() {}
     virtual void cleanup() {}
 
     virtual void startrender() {}
     virtual void endrender() {}
 
-    void boundbox(int frame, vec &center, vec &radius)
+    void boundbox(int frame, vec &center, vec &radius, modelattach *a = NULL)
     {
         if(frame) calcbb(frame, center, radius);
         else
@@ -47,7 +47,7 @@ struct model
             center = bbcenter;
             radius = bbradius;
         }
-        radius.add(bbextend);
+        if(a) for(int i = 0; a[i].name; i++) if(a[i].m) extendbb(frame, center, radius, a[i]);
     }
 
     void collisionbox(int frame, vec &center, vec &radius)
@@ -64,10 +64,10 @@ struct model
         }
     }
 
-    float boundsphere(int frame, vec &center)
+    float boundsphere(int frame, vec &center, modelattach *a = NULL)
     {
         vec radius;
-        boundbox(frame, center, radius);
+        boundbox(frame, center, radius, a);
         return radius.magnitude();
     }
 
diff --git a/engine/movie.cpp b/engine/movie.cpp
deleted file mode 100644
index 2f6de02..0000000
--- a/engine/movie.cpp
+++ /dev/null
@@ -1,1023 +0,0 @@
-// records video to uncompressed avi files (will split across multiple files once size exceeds 1Gb)
-// - people should post process the files because they will get large very rapidly
-
-
-// Feedback on playing videos:
-//   quicktime - ok
-//   vlc - ok
-//   xine - ok
-//   mplayer - ok
-//   totem - ok
-//   avidemux - ok - 3Apr09-RockKeyman:had to swap UV channels as it showed up blue
-//   kino - ok
-
-#include "engine.h"
-#include "SDL_mixer.h"
-
-VAR(dbgmovie, 0, 0, 1);
-
-struct aviindexentry
-{
-    int type, size;
-    long offset;
-};
-
-struct aviwriter
-{
-    stream *f;
-    uchar *yuv;
-    uint videoframes;
-    uint filesequence;    
-    const uint videow, videoh, videofps;
-    string filename;
-    uint physvideoframes;
-    uint physsoundbytes;
-    
-    int soundfrequency, soundchannels;
-    Uint16 soundformat;
-    
-    vector<aviindexentry> index;
-    
-    long fileframesoffset, filevideooffset, filesoundoffset;
-    
-    enum { MAX_CHUNK_DEPTH = 16 };
-    long chunkoffsets[MAX_CHUNK_DEPTH];
-    int chunkdepth;
-    
-    aviindexentry makeindex(int type, int size)
-    {
-        aviindexentry entry;
-        entry.offset = f->tell() - chunkoffsets[chunkdepth]; // as its relative to movi;
-        entry.type = type;
-        entry.size = size;
-        return entry;
-    }
-    
-    double filespaceguess() 
-    {
-        return double(physvideoframes)*double(videow * videoh * 3 / 2) + double(physsoundbytes + index.length()*16 + 500);
-    }
-       
-    void startchunk(const char *fcc)
-    {
-        f->write(fcc, 4);
-        const uint size = 0;
-        f->write(&size, 4);
-        chunkoffsets[++chunkdepth] = f->tell();
-    }
-    
-    void listchunk(const char *fcc, const char *lfcc)
-    {
-        startchunk(fcc);
-        f->write(lfcc, 4);
-    }
-    
-    void endchunk()
-    {
-        assert(chunkdepth >= 0);
-        uint size = f->tell() - chunkoffsets[chunkdepth];
-        f->seek(chunkoffsets[chunkdepth] - 4, SEEK_SET);
-        f->putlil(size);
-        f->seek(0, SEEK_END);
-        if(size & 1) f->putchar(0x00);
-        --chunkdepth;
-    }
-
-    void writechunk(const char *fcc, const void *data, uint len) // simplify startchunk()/endchunk() to avoid f->seek()
-    {
-        f->write(fcc, 4);
-        f->putlil(len);
-        f->write(data, len);
-        if(len & 1) f->putchar(0x00);
-    }
-    
-    void close()
-    {
-        if(!f) return;
-        assert(chunkdepth == 1);
-        endchunk(); // LIST movi
-        
-        startchunk("idx1");
-        loopv(index)
-        {
-            aviindexentry &entry = index[i];
-            // printf("%3d %s %08x\n", i, (entry.type==1)?"s":"v", entry.offset);
-            f->write(entry.type?"01wb":"00dc", 4); // chunkid
-            f->putlil<uint>(0x10); // flags - KEYFRAME
-            f->putlil<uint>(entry.offset); // offset (relative to movi)
-            f->putlil<uint>(entry.size); // size
-        }
-        endchunk();
-
-        endchunk(); // RIFF AVI
-
-        uint soundframes = 0;
-        uint videoframes = 0;
-        long lastoffset = 0;
-        loopv(index)
-        {
-            aviindexentry &entry = index[i];
-            if(entry.type) soundframes++;
-            else if(entry.offset != lastoffset) 
-            { 
-                lastoffset = entry.offset; 
-                videoframes++;
-            }
-        }
-        if(dbgmovie) conoutf(CON_DEBUG, "fileframes: sound=%d, video=%d+%d(dups)\n", soundframes, videoframes, index.length()-(soundframes+videoframes));
-
-        f->seek(fileframesoffset, SEEK_SET);
-        f->putlil(index.length()-soundframes); // videoframes including duplicates        
-        f->seek(filevideooffset, SEEK_SET);
-        f->putlil(videoframes);
-        if(soundframes > 0)
-        {
-            f->seek(filesoundoffset, SEEK_SET);
-            f->putlil(soundframes);
-        }
-        f->seek(0, SEEK_END);
-        
-        DELETEP(f);
-    }
-    
-    aviwriter(const char *name, uint w, uint h, uint fps, bool sound) : f(NULL), yuv(NULL), videoframes(0), filesequence(0), videow(w&~1), videoh(h&~1), videofps(fps), physvideoframes(0), physsoundbytes(0), soundfrequency(0),soundchannels(0),soundformat(0)
-    {
-        copystring(filename, name);
-        path(filename);
-        if(!strrchr(filename, '.')) concatstring(filename, ".avi");
-        
-        extern bool nosound; // sound.cpp
-        if(sound && !nosound) 
-        {
-            Mix_QuerySpec(&soundfrequency, &soundformat, &soundchannels);
-            const char *desc;
-            switch(soundformat)
-            {
-                case AUDIO_U8:     desc = "u8"; break;
-                case AUDIO_S8:     desc = "s8"; break;
-                case AUDIO_U16LSB: desc = "u16l"; break;
-                case AUDIO_U16MSB: desc = "u16b"; break;
-                case AUDIO_S16LSB: desc = "s16l"; break;
-                case AUDIO_S16MSB: desc = "s16b"; break;
-                default:           desc = "unkn";
-            }
-            if(dbgmovie) conoutf(CON_DEBUG, "soundspec: %dhz %s x %d", soundfrequency, desc, soundchannels);
-        }
-    }
-    
-    ~aviwriter()
-    {
-        close();
-        if(yuv) delete [] yuv;
-    }
-    
-    bool open()
-    {
-        close();
-        string seqfilename;
-        if(filesequence == 0) copystring(seqfilename, filename);
-        else
-        {
-            if(filesequence >= 999) return false;
-            char *ext = strrchr(filename, '.');
-            if(filesequence == 1) 
-            {
-                string oldfilename;
-                copystring(oldfilename, findfile(filename, "wb"));
-                *ext = '\0';
-                conoutf("movie now recording to multiple: %s_XXX.%s files", filename, ext+1);
-                formatstring(seqfilename)("%s_%03d.%s", filename, 0, ext+1);
-                rename(oldfilename, findfile(seqfilename, "wb"));
-            }
-            *ext = '\0';
-            formatstring(seqfilename)("%s_%03d.%s", filename, filesequence, ext+1);
-            *ext = '.';
-        }
-        filesequence++;
-        f = openfile(seqfilename, "wb");
-        if(!f) return false;
-        
-        index.setsize(0);
-        chunkdepth = -1;
-        
-        listchunk("RIFF", "AVI ");
-        
-        listchunk("LIST", "hdrl");
-        
-        startchunk("avih");
-        f->putlil<uint>(1000000 / videofps); // microsecsperframe
-        f->putlil<uint>(0); // maxbytespersec
-        f->putlil<uint>(0); // reserved
-        f->putlil<uint>(0x10 | 0x20); // flags - hasindex|mustuseindex
-        fileframesoffset = f->tell();
-        f->putlil<uint>(0); // totalvideoframes
-        f->putlil<uint>(0); // initialframes
-        f->putlil<uint>(soundfrequency > 0 ? 2 : 1); // streams
-        f->putlil<uint>(0); // buffersize
-        f->putlil<uint>(videow); // video width
-        f->putlil<uint>(videoh); // video height
-        loopi(4) f->putlil<uint>(0); // reserved
-        endchunk(); // avih
-        
-        listchunk("LIST", "strl");
-        
-        startchunk("strh");
-        f->write("vids", 4); // fcctype
-        f->write("I420", 4); // fcchandler
-        f->putlil<uint>(0); // flags
-        f->putlil<uint>(0); // priority
-        f->putlil<uint>(0); // initialframes
-        f->putlil<uint>(1); // scale
-        f->putlil<uint>(videofps); // rate
-        f->putlil<uint>(0); // start
-        filevideooffset = f->tell();
-        f->putlil<uint>(0); // length
-        f->putlil<uint>(videow*videoh*3/2); // suggested buffersize
-        f->putlil<uint>(0); // quality
-        f->putlil<uint>(0); // samplesize
-        f->putlil<ushort>(0); // frame left
-        f->putlil<ushort>(0); // frame top
-        f->putlil<ushort>(videow); // frame right
-        f->putlil<ushort>(videoh); // frame bottom
-        endchunk(); // strh
-        
-        startchunk("strf");
-        f->putlil<uint>(40); //headersize
-        f->putlil<uint>(videow); // width
-        f->putlil<uint>(videoh); // height
-        f->putlil<ushort>(3); // planes
-        f->putlil<ushort>(12); // bitcount
-        f->write("I420", 4); // compression
-        f->putlil<uint>(videow*videoh*3/2); // imagesize
-        f->putlil<uint>(0); // xres
-        f->putlil<uint>(0); // yres;
-        f->putlil<uint>(0); // colorsused
-        f->putlil<uint>(0); // colorsrequired
-        endchunk(); // strf
-        
-        endchunk(); // LIST strl
-                
-        if(soundfrequency > 0)
-        {
-            const int bps = (soundformat==AUDIO_U8 || soundformat == AUDIO_S8) ? 1 : 2;
-            
-            listchunk("LIST", "strl");
-            
-            startchunk("strh");
-            f->write("auds", 4); // fcctype
-            f->putlil<uint>(1); // fcchandler - normally 4cc, but audio is a special case
-            f->putlil<uint>(0); // flags
-            f->putlil<uint>(0); // priority
-            f->putlil<uint>(0); // initialframes
-            f->putlil<uint>(1); // scale
-            f->putlil<uint>(soundfrequency); // rate
-            f->putlil<uint>(0); // start
-            filesoundoffset = f->tell();
-            f->putlil<uint>(0); // length
-            f->putlil<uint>(soundfrequency*bps*soundchannels/2); // suggested buffer size (this is a half second)
-            f->putlil<uint>(0); // quality
-            f->putlil<uint>(bps*soundchannels); // samplesize
-            f->putlil<ushort>(0); // frame left
-            f->putlil<ushort>(0); // frame top
-            f->putlil<ushort>(0); // frame right
-            f->putlil<ushort>(0); // frame bottom
-            endchunk(); // strh
-            
-            startchunk("strf");
-            f->putlil<ushort>(1); // format (uncompressed PCM)
-            f->putlil<ushort>(soundchannels); // channels
-            f->putlil<uint>(soundfrequency); // sampleframes per second
-            f->putlil<uint>(soundfrequency*bps*soundchannels); // average bytes per second
-            f->putlil<ushort>(bps*soundchannels); // block align <-- guess
-            f->putlil<ushort>(bps*8); // bits per sample
-            f->putlil<ushort>(0); // size
-            endchunk(); //strf
-            
-            endchunk(); // LIST strl
-        }
-        
-        listchunk("LIST", "INFO");
-        
-        const char *software = "Cube 2: Sauerbraten";
-        writechunk("ISFT", software, strlen(software)+1);
-        
-        endchunk(); // LIST INFO
-        
-        endchunk(); // LIST hdrl
-        
-        listchunk("LIST", "movi");
-        
-        return true;
-    }
-  
-    static inline void boxsample(const uchar *src, const uint stride, 
-                                 const uint area, const uint w, uint h, 
-                                 const uint xlow, const uint xhigh, const uint ylow, const uint yhigh,
-                                 uint &bdst, uint &gdst, uint &rdst)
-    {
-        const uchar *end = &src[w<<2];
-        uint bt = 0, gt = 0, rt = 0;
-        for(const uchar *cur = &src[4]; cur < end; cur += 4)
-        {
-            bt += cur[0];
-            gt += cur[1];
-            rt += cur[2];
-        }
-        bt = ylow*(bt + ((src[0]*xlow + end[0]*xhigh)>>12));
-        gt = ylow*(gt + ((src[1]*xlow + end[1]*xhigh)>>12));
-        rt = ylow*(rt + ((src[2]*xlow + end[2]*xhigh)>>12));
-        if(h) 
-        {
-            for(src += stride, end += stride; --h; src += stride, end += stride)  
-            {
-                uint b = 0, g = 0, r = 0;
-                for(const uchar *cur = &src[4]; cur < end; cur += 4)
-                {
-                    b += cur[0];
-                    g += cur[1];
-                    r += cur[2];
-                }
-                bt += (b<<12) + src[0]*xlow + end[0]*xhigh;
-                gt += (g<<12) + src[1]*xlow + end[1]*xhigh;
-                rt += (r<<12) + src[2]*xlow + end[2]*xhigh;
-            }
-            uint b = 0, g = 0, r = 0;
-            for(const uchar *cur = &src[4]; cur < end; cur += 4)
-            {
-                b += cur[0];
-                g += cur[1];
-                r += cur[2];
-            }
-            bt += yhigh*(b + ((src[0]*xlow + end[0]*xhigh)>>12));
-            gt += yhigh*(g + ((src[1]*xlow + end[1]*xhigh)>>12));
-            rt += yhigh*(r + ((src[2]*xlow + end[2]*xhigh)>>12));
-        }
-        bdst = (bt*area)>>24;
-        gdst = (gt*area)>>24;
-        rdst = (rt*area)>>24;
-    }
- 
-    void scaleyuv(const uchar *pixels, uint srcw, uint srch)
-    {
-        const int flip = -1;
-        const uint planesize = videow * videoh;
-        if(!yuv) yuv = new uchar[(planesize*3)/2];
-        uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
-        const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
-        if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
-
-        const uint stride = srcw<<2;
-        srcw &= ~1;
-        srch &= ~1;
-        const uint wfrac = (srcw<<12)/videow, hfrac = (srch<<12)/videoh, 
-                   area = ((unsigned long long int)planesize<<12)/(srcw*srch + 1),
-                   dw = videow*wfrac, dh = videoh*hfrac;
-  
-        for(uint y = 0; y < dh;)
-        {
-            uint yn = y + hfrac - 1, yi = y>>12, h = (yn>>12) - yi, ylow = ((yn|(-int(h)>>24))&0xFFFU) + 1 - (y&0xFFFU), yhigh = (yn&0xFFFU) + 1;
-            y += hfrac;
-            uint y2n = y + hfrac - 1, y2i = y>>12, h2 = (y2n>>12) - y2i, y2low = ((y2n|(-int(h2)>>24))&0xFFFU) + 1 - (y&0xFFFU), y2high = (y2n&0xFFFU) + 1;
-            y += hfrac;
-
-            const uchar *src = &pixels[yi*stride], *src2 = &pixels[y2i*stride];
-            uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
-            for(uint x = 0; x < dw;)
-            {
-                uint xn = x + wfrac - 1, xi = x>>12, w = (xn>>12) - xi, xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU), xhigh = (xn&0xFFFU) + 1;
-                x += wfrac;
-                uint x2n = x + wfrac - 1, x2i = x>>12, w2 = (x2n>>12) - x2i, x2low = ((w2+0xFFFU)&0x1000U) - (x&0xFFFU), x2high = (x2n&0xFFFU) + 1;
-                x += wfrac;
-
-                uint b1, g1, r1, b2, g2, r2, b3, g3, r3, b4, g4, r4;
-                boxsample(&src[xi<<2], stride, area, w, h, xlow, xhigh, ylow, yhigh, b1, g1, r1);
-                boxsample(&src[x2i<<2], stride, area, w2, h, x2low, x2high, ylow, yhigh, b2, g2, r2);
-                boxsample(&src2[xi<<2], stride, area, w, h2, xlow, xhigh, y2low, y2high, b3, g3, r3);
-                boxsample(&src2[x2i<<2], stride, area, w2, h2, x2low, x2high, y2low, y2high, b4, g4, r4);
-
-                // 0.299*R + 0.587*G + 0.114*B
-                *ydst++ = (1225*r1 + 2404*g1 + 467*b1)>>12;
-                *ydst++ = (1225*r2 + 2404*g2 + 467*b2)>>12;
-                *ydst2++ = (1225*r3 + 2404*g3 + 467*b3)>>12;
-                *ydst2++ = (1225*r4 + 2404*g4 + 467*b4)>>12;
-
-                const uint b = b1 + b2 + b3 + b4,
-                           g = g1 + g2 + g3 + g4,
-                           r = r1 + r2 + r3 + r4;
-                // U = 0.500 + 0.500*B - 0.169*R - 0.331*G
-                // V = 0.500 + 0.500*R - 0.419*G - 0.081*B
-                // note: weights here are scaled by 1<<10, as opposed to 1<<12, since r/g/b are already *4
-                *udst++ = ((128<<12) + 512*b - 173*r - 339*g)>>12;
-                *vdst++ = ((128<<12) + 512*r - 429*g - 83*b)>>12;
-            }
-
-            yplane += 2*ystride;
-            uplane += uvstride;
-            vplane += uvstride;
-        }
-    }
-
-    void encodeyuv(const uchar *pixels)
-    {
-        const int flip = -1;
-        const uint planesize = videow * videoh;
-        if(!yuv) yuv = new uchar[(planesize*3)/2];
-        uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
-        const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
-        if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
-
-        const uint stride = videow<<2;
-        const uchar *src = pixels, *yend = src + videoh*stride;
-        while(src < yend)    
-        {
-            const uchar *src2 = src + stride, *xend = src2;
-            uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
-            while(src < xend)
-            {
-                const uint b1 = src[0], g1 = src[1], r1 = src[2],
-                           b2 = src[4], g2 = src[5], r2 = src[6],
-                           b3 = src2[0], g3 = src2[1], r3 = src2[2],
-                           b4 = src2[4], g4 = src2[5], r4 = src2[6];
-
-                // 0.299*R + 0.587*G + 0.114*B
-                *ydst++ = (1225*r1 + 2404*g1 + 467*b1)>>12;
-                *ydst++ = (1225*r2 + 2404*g2 + 467*b2)>>12;
-                *ydst2++ = (1225*r3 + 2404*g3 + 467*b3)>>12;
-                *ydst2++ = (1225*r4 + 2404*g4 + 467*b4)>>12;
-
-                const uint b = b1 + b2 + b3 + b4,
-                           g = g1 + g2 + g3 + g4,
-                           r = r1 + r2 + r3 + r4;
-                // U = 0.500 + 0.500*B - 0.169*R - 0.331*G
-                // V = 0.500 + 0.500*R - 0.419*G - 0.081*B
-                // note: weights here are scaled by 1<<10, as opposed to 1<<12, since r/g/b are already *4
-                *udst++ = ((128<<12) + 512*b - 173*r - 339*g)>>12;
-                *vdst++ = ((128<<12) + 512*r - 429*g - 83*b)>>12;
-
-                src += 8;
-                src2 += 8; 
-            }
-            src = src2;
-            yplane += 2*ystride;
-            uplane += uvstride;
-            vplane += uvstride;
-        }
-    }
-
-    void compressyuv(const uchar *pixels)
-    {
-        const int flip = -1;
-        const uint planesize = videow * videoh;
-        if(!yuv) yuv = new uchar[(planesize*3)/2];
-        uchar *yplane = yuv, *uplane = yuv + planesize, *vplane = yuv + planesize + planesize/4;
-        const int ystride = flip*int(videow), uvstride = flip*int(videow)/2;
-        if(flip < 0) { yplane -= int(videoh-1)*ystride; uplane -= int(videoh/2-1)*uvstride; vplane -= int(videoh/2-1)*uvstride; }
-
-        const uint stride = videow<<2;
-        const uchar *src = pixels, *yend = src + videoh*stride;
-        while(src < yend)
-        {
-            const uchar *src2 = src + stride, *xend = src2;
-            uchar *ydst = yplane, *ydst2 = yplane + ystride, *udst = uplane, *vdst = vplane;
-            while(src < xend)
-            {
-                *ydst++ = src[0];
-                *ydst++ = src[4];
-                *ydst2++ = src2[0];
-                *ydst2++ = src2[4];
- 
-                *udst++ = (uint(src[1]) + uint(src[5]) + uint(src2[1]) + uint(src2[5])) >> 2;
-                *vdst++ = (uint(src[2]) + uint(src[6]) + uint(src2[2]) + uint(src2[6])) >> 2;
-
-                src += 8;
-                src2 += 8;
-            }
-            src = src2;
-            yplane += 2*ystride;
-            uplane += uvstride;
-            vplane += uvstride;
-        }
-    }
-
-    void writesound(uchar *data, uint framesize)
-    {
-        // do conversion in-place to little endian format
-        // note that xoring by half the range yields the same bit pattern as subtracting the range regardless of signedness
-        // ... so can toggle signedness just by xoring the high byte with 0x80
-        switch(soundformat)
-        {
-            case AUDIO_U8:
-                for(uchar *dst = data, *end = &data[framesize]; dst < end; dst++) *dst ^= 0x80;
-                break;
-            case AUDIO_S8:
-                break;
-            case AUDIO_U16LSB:
-                for(uchar *dst = &data[1], *end = &data[framesize]; dst < end; dst += 2) *dst ^= 0x80;
-                break;
-            case AUDIO_U16MSB:
-                for(ushort *dst = (ushort *)data, *end = (ushort *)&data[framesize]; dst < end; dst++)
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
-                    *dst = endianswap(*dst) ^ 0x0080;
-#else
-                    *dst = endianswap(*dst) ^ 0x8000;
-#endif
-                break;
-            case AUDIO_S16LSB:
-                break;
-            case AUDIO_S16MSB:
-                endianswap((short *)data, framesize/2);
-                break;
-        }
-        
-        index.add(makeindex(1, framesize));
-    
-        writechunk("01wb", data, framesize);
-        physsoundbytes += framesize;
-    }
-   
-
-    enum
-    {
-        VID_RGB = 0,
-        VID_YUV,
-        VID_YUV420
-    };
-
-    bool writevideoframe(const uchar *pixels, uint srcw, uint srch, int format, uint frame)
-    {
-        if(frame < videoframes) return true;
-        
-        switch(format)
-        {
-            case VID_RGB: 
-                if(srcw != videow || srch != videoh) scaleyuv(pixels, srcw, srch);
-                else encodeyuv(pixels);
-                break;
-            case VID_YUV:
-                compressyuv(pixels);
-                break;
-        }
-
-        const uint framesize = (videow * videoh * 3) / 2;
-        if(f->tell() + framesize > 1000*1000*1000 && !open()) return false; // check for overflow of 1Gb limit
-
-        aviindexentry entry = makeindex(0, framesize);
-        int vpos = index.length(), vnum = frame + 1 - videoframes;
-        loopi(vnum) index.add(entry);
-        
-        if(vnum > 1) // experimental - detect sequence of sound frames that precede this sequence of video - interleave the sound
-        {
-            int snum = 0;
-            while(vpos > snum && index[vpos-snum-1].type == 1) snum++;
-            if(snum > 1)
-            {
-                if(dbgmovie) conoutf(CON_DEBUG, "movie: interleaving sound=%d x%d video=%d x%d\n", vpos-snum, snum, vpos, vnum);
-                int frac = 0, pos = index.length();
-                loopi(snum)
-                {
-                    for(frac += vnum; frac >= snum; frac -= snum) index[--pos] = entry;
-                    index[--pos] = index[vpos-1-i]; 
-                }
-            }
-        }
-        
-        videoframes = frame + 1;
-
-        writechunk("00dc", format == VID_YUV420 ? pixels : yuv, framesize);
-        physvideoframes++;
-
-        return true;
-    }
-    
-};
-
-VAR(movieaccelblit, 0, 0, 1);
-VAR(movieaccelyuv, 0, 1, 1);
-VARP(movieaccel, 0, 1, 1);
-VARP(moviesync, 0, 0, 1);
-
-namespace recorder
-{
-    static enum { REC_OK = 0, REC_USERHALT, REC_TOOSLOW, REC_FILERROR } state = REC_OK;
-    
-    static aviwriter *file = NULL;
-    static int starttime = 0;
-    
-    static int stats[1000];
-    static int statsindex = 0;
-    static uint dps = 0; // dropped frames per sample
-    
-    enum { MAXSOUNDBUFFERS = 32 }; // sounds queue up until there is a video frame, so at low fps you'll need a bigger queue
-    struct soundbuffer
-    {
-        uchar *sound;
-        uint size, maxsize;
-        uint frame;
-        
-        soundbuffer() : sound(NULL), maxsize(0) {}
-        ~soundbuffer() { cleanup(); }
-        
-        void load(uchar *stream, uint len, uint fnum)
-        {
-            if(len > maxsize)
-            {
-                DELETEA(sound);
-                sound = new uchar[len];
-                maxsize = len;
-            }
-            size = len;
-            frame = fnum;
-            memcpy(sound, stream, len);
-        }
-        
-        void cleanup() { DELETEA(sound); maxsize = 0; }
-    };
-    static queue<soundbuffer, MAXSOUNDBUFFERS> soundbuffers;
-    static SDL_mutex *soundlock = NULL;
-    
-    enum { MAXVIDEOBUFFERS = 2 }; // double buffer
-    struct videobuffer 
-    {
-        uchar *video;
-        uint w, h, bpp, frame;
-        int format;
-
-        videobuffer() : video(NULL){}
-        ~videobuffer() { cleanup(); }
-
-        void init(int nw, int nh, int nbpp)
-        {
-            DELETEA(video);
-            w = nw;
-            h = nh;
-            bpp = nbpp;
-            video = new uchar[w*h*bpp];
-            format = -1;
-        }
-         
-        void cleanup() { DELETEA(video); }
-    };
-    static queue<videobuffer, MAXVIDEOBUFFERS> videobuffers;
-    static uint lastframe = ~0U;
-
-    static GLuint scalefb = 0, scaletex[2] = { 0, 0 };
-    static uint scalew = 0, scaleh = 0;
-    static GLuint encodefb = 0, encoderb = 0;
-
-    static SDL_Thread *thread = NULL;
-    static SDL_mutex *videolock = NULL;
-    static SDL_cond *shouldencode = NULL, *shouldread = NULL;
-
-    bool isrecording() { return file != NULL; }
-    
-    int videoencoder(void *data) // runs on a separate thread
-    {
-        for(int numvid = 0, numsound = 0;;)
-        {   
-            SDL_LockMutex(videolock);
-            for(; numvid > 0; numvid--) videobuffers.remove();
-            SDL_CondSignal(shouldread);
-            while(videobuffers.empty() && state == REC_OK) SDL_CondWait(shouldencode, videolock);
-            if(state != REC_OK) { SDL_UnlockMutex(videolock); break; }
-            videobuffer &m = videobuffers.removing();
-            numvid++;
-            SDL_UnlockMutex(videolock);
-            
-            if(file->soundfrequency > 0)
-            {
-                // chug data from lock protected buffer to avoid holding lock while writing to file
-                SDL_LockMutex(soundlock);
-                for(; numsound > 0; numsound--) soundbuffers.remove();
-                for(; numsound < soundbuffers.length(); numsound++)
-                {
-                    soundbuffer &s = soundbuffers.removing(numsound);
-                    if(s.frame > m.frame) break; // sync with video
-                }
-                SDL_UnlockMutex(soundlock);
-                loopi(numsound)
-                {
-                    soundbuffer &s = soundbuffers.removing(i);
-                    file->writesound(s.sound, s.size);
-                }
-            }
-            
-            int duplicates = m.frame - (int)file->videoframes + 1;
-            if(duplicates > 0) // determine how many frames have been dropped over the sample window
-            {
-                dps -= stats[statsindex];
-                stats[statsindex] = duplicates-1;
-                dps += stats[statsindex];
-                statsindex = (statsindex+1)%file->videofps;
-            }
-            //printf("frame %d->%d (%d dps): sound = %d bytes\n", file->videoframes, nextframenum, dps, m.soundlength);
-            if(dps > file->videofps) state = REC_TOOSLOW;
-            else if(!file->writevideoframe(m.video, m.w, m.h, m.format, m.frame)) state = REC_FILERROR;
-            
-            m.frame = ~0U;
-        }
-        
-        return 0;
-    }
-    
-    void soundencoder(void *udata, Uint8 *stream, int len) // callback occurs on a separate thread
-    {
-        SDL_LockMutex(soundlock);
-        if(soundbuffers.full()) state = REC_TOOSLOW;
-        else if(state == REC_OK)
-        {
-            uint nextframe = ((totalmillis - starttime)*file->videofps)/1000;
-            soundbuffer &s = soundbuffers.add();
-            s.load((uchar *)stream, len, nextframe);
-        }
-        SDL_UnlockMutex(soundlock);
-    }
-    
-    void start(const char *filename, int videofps, int videow, int videoh, bool sound) 
-    {
-        if(file) return;
-        
-        int fps, bestdiff, worstdiff;
-        getfps(fps, bestdiff, worstdiff);
-        if(videofps > fps) conoutf(CON_WARN, "frame rate may be too low to capture at %d fps", videofps);
-        
-        if(videow%2) videow += 1;
-        if(videoh%2) videoh += 1;
-
-        file = new aviwriter(filename, videow, videoh, videofps, sound);
-        if(!file->open()) 
-        { 
-            conoutf(CON_ERROR, "unable to create file %s", filename);
-            DELETEP(file);
-            return;
-        }
-        conoutf("movie recording to: %s %dx%d @ %dfps%s", file->filename, file->videow, file->videoh, file->videofps, (file->soundfrequency>0)?" + sound":"");
-        
-        useshaderbyname("moviergb");
-        useshaderbyname("movieyuv");
-        useshaderbyname("moviey");
-        useshaderbyname("movieu");
-        useshaderbyname("moviev");
-
-        starttime = totalmillis;
-        loopi(file->videofps) stats[i] = 0;
-        statsindex = 0;
-        dps = 0;
-        
-        lastframe = ~0U;
-        videobuffers.clear();
-        loopi(MAXVIDEOBUFFERS)
-        {
-            uint w = screen->w, h = screen->w;
-            videobuffers.data[i].init(w, h, 4);
-            videobuffers.data[i].frame = ~0U;
-        }
-        
-        soundbuffers.clear();
-        
-        soundlock = SDL_CreateMutex();
-        videolock = SDL_CreateMutex();
-        shouldencode = SDL_CreateCond();
-        shouldread = SDL_CreateCond();
-        thread = SDL_CreateThread(videoencoder, NULL); 
-        if(file->soundfrequency > 0) Mix_SetPostMix(soundencoder, NULL);
-    }
-    
-    void stop()
-    {
-        if(!file) return;
-        if(state == REC_OK) state = REC_USERHALT;
-        if(file->soundfrequency > 0) Mix_SetPostMix(NULL, NULL);
-        
-        SDL_LockMutex(videolock); // wakeup thread enough to kill it
-        SDL_CondSignal(shouldencode);
-        SDL_UnlockMutex(videolock);
-        
-        SDL_WaitThread(thread, NULL); // block until thread is finished
-
-        if(scalefb) { glDeleteFramebuffers_(1, &scalefb); scalefb = 0; }
-        if(scaletex[0] || scaletex[1]) { glDeleteTextures(2, scaletex); memset(scaletex, 0, sizeof(scaletex)); }
-        scalew = scaleh = 0;
-        if(encodefb) { glDeleteFramebuffers_(1, &encodefb); encodefb = 0; }
-        if(encoderb) { glDeleteRenderbuffers_(1, &encoderb); encoderb = 0; }
-
-        loopi(MAXVIDEOBUFFERS) videobuffers.data[i].cleanup();
-        loopi(MAXSOUNDBUFFERS) soundbuffers.data[i].cleanup();
-
-        SDL_DestroyMutex(soundlock);
-        SDL_DestroyMutex(videolock);
-        SDL_DestroyCond(shouldencode);
-        SDL_DestroyCond(shouldread);
-
-        soundlock = videolock = NULL;
-        shouldencode = shouldread = NULL;
-        thread = NULL;
- 
-        static const char *mesgs[] = { "ok", "stopped", "computer too slow", "file error"};
-        conoutf("movie recording halted: %s, %d frames", mesgs[state], file->videoframes);
-        
-        DELETEP(file);
-        state = REC_OK;
-    }
-  
-    void drawquad(float tw, float th, float x, float y, float w, float h)
-    {
-        glBegin(GL_QUADS);
-        glTexCoord2f(0,  0);  glVertex2f(x,   y);
-        glTexCoord2f(tw, 0);  glVertex2f(x+w, y);
-        glTexCoord2f(tw, th); glVertex2f(x+w, y+h);
-        glTexCoord2f(0,  th); glVertex2f(x,   y+h);
-        glEnd();
-    }
-
-    void readbuffer(videobuffer &m, uint nextframe)
-    {
-        bool accelyuv = movieaccelyuv && renderpath!=R_FIXEDFUNCTION && !(m.w%8),
-             usefbo = movieaccel && hasFBO && hasTR && file->videow <= (uint)screen->w && file->videoh <= (uint)screen->h && (accelyuv || file->videow < (uint)screen->w || file->videoh < (uint)screen->h);
-        uint w = screen->w, h = screen->h;
-        if(usefbo) { w = file->videow; h = file->videoh; }
-        if(w != m.w || h != m.h) m.init(w, h, 4);
-        m.format = aviwriter::VID_RGB;
-        m.frame = nextframe;
-
-        glPixelStorei(GL_PACK_ALIGNMENT, texalign(m.video, m.w, 4));
-        if(usefbo)
-        {
-            uint tw = screen->w, th = screen->h;
-            if(hasFBB && movieaccelblit) { tw = max(tw/2, m.w); th = max(th/2, m.h); }
-            if(tw != scalew || th != scaleh)
-            {
-                if(!scalefb) glGenFramebuffers_(1, &scalefb);
-                loopi(2)
-                {
-                    if(!scaletex[i]) glGenTextures(1, &scaletex[i]);
-                    createtexture(scaletex[i], tw, th, NULL, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE_ARB);
-                }
-                scalew = tw;
-                scaleh = th;
-            }
-            if(accelyuv && (!encodefb || !encoderb))
-            {
-                if(!encodefb) glGenFramebuffers_(1, &encodefb);
-                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, encodefb);
-                if(!encoderb) glGenRenderbuffers_(1, &encoderb);
-                glBindRenderbuffer_(GL_RENDERBUFFER_EXT, encoderb);
-                glRenderbufferStorage_(GL_RENDERBUFFER_EXT, GL_RGBA, (m.w*3)/8, m.h);
-                glFramebufferRenderbuffer_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, encoderb);
-                glBindRenderbuffer_(GL_RENDERBUFFER_EXT, 0);
-                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
-            }
-                     
-            if(tw < (uint)screen->w || th < (uint)screen->h)
-            {
-                glBindFramebuffer_(GL_READ_FRAMEBUFFER_EXT, 0);
-                glBindFramebuffer_(GL_DRAW_FRAMEBUFFER_EXT, scalefb);
-                glFramebufferTexture2D_(GL_DRAW_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, scaletex[0], 0);
-                glBlitFramebuffer_(0, 0, screen->w, screen->h, 0, 0, tw, th, GL_COLOR_BUFFER_BIT, GL_LINEAR);
-                glBindFramebuffer_(GL_DRAW_FRAMEBUFFER_EXT, 0);
-            }
-            else
-            {
-                glBindTexture(GL_TEXTURE_RECTANGLE_ARB, scaletex[0]);
-                glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, 0, 0, screen->w, screen->h);
-            }
-
-            if(tw > m.w || th > m.h || (!accelyuv && renderpath != R_FIXEDFUNCTION && tw >= m.w && th >= m.h))
-            {
-                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, scalefb);
-                glViewport(0, 0, tw, th);
-                glColor3f(1, 1, 1);
-                glMatrixMode(GL_PROJECTION);
-                glLoadIdentity();
-                glOrtho(0, tw, 0, th, -1, 1);
-                glMatrixMode(GL_MODELVIEW);
-                glLoadIdentity();
-                glEnable(GL_TEXTURE_RECTANGLE_ARB);
-                do
-                {
-                    glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, scaletex[1], 0);
-                    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, scaletex[0]);
-                    uint dw = max(tw/2, m.w), dh = max(th/2, m.h);
-                    if(dw == m.w && dh == m.h && !accelyuv && renderpath != R_FIXEDFUNCTION) { SETSHADER(movieyuv); m.format = aviwriter::VID_YUV; }
-                    else SETSHADER(moviergb);
-                    drawquad(tw, th, 0, 0, dw, dh);
-                    tw = dw;
-                    th = dh;
-                    swap(scaletex[0], scaletex[1]);
-                } while(tw > m.w || th > m.h);
-                glDisable(GL_TEXTURE_RECTANGLE_ARB);
-            }
-            if(accelyuv)
-            {
-                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, encodefb); 
-                glViewport(0, 0, (m.w*3)/8, m.h);
-                glColor3f(1, 1, 1);
-                glMatrixMode(GL_PROJECTION);
-                glLoadIdentity();
-                glOrtho(0, (m.w*3)/8, m.h, 0, -1, 1);
-                glMatrixMode(GL_MODELVIEW);
-                glLoadIdentity();
-                glEnable(GL_TEXTURE_RECTANGLE_ARB);
-                glBindTexture(GL_TEXTURE_RECTANGLE_ARB, scaletex[0]); 
-                SETSHADER(moviey); drawquad(m.w, m.h, 0, 0, m.w/4, m.h);
-                SETSHADER(moviev); drawquad(m.w, m.h, m.w/4, 0, m.w/8, m.h/2);
-                SETSHADER(movieu); drawquad(m.w, m.h, m.w/4, m.h/2, m.w/8, m.h/2);
-                glDisable(GL_TEXTURE_RECTANGLE_ARB);
-                const uint planesize = m.w * m.h;
-                glPixelStorei(GL_PACK_ALIGNMENT, texalign(m.video, m.w/4, 4)); 
-                glReadPixels(0, 0, m.w/4, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
-                glPixelStorei(GL_PACK_ALIGNMENT, texalign(&m.video[planesize], m.w/8, 4));
-                glReadPixels(m.w/4, 0, m.w/8, m.h/2, GL_BGRA, GL_UNSIGNED_BYTE, &m.video[planesize]);
-                glPixelStorei(GL_PACK_ALIGNMENT, texalign(&m.video[planesize + planesize/4], m.w/8, 4));
-                glReadPixels(m.w/4, m.h/2, m.w/8, m.h/2, GL_BGRA, GL_UNSIGNED_BYTE, &m.video[planesize + planesize/4]);
-                m.format = aviwriter::VID_YUV420;
-            }
-            else
-            {
-                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, scalefb);
-                glReadPixels(0, 0, m.w, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
-            }
-            glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
-            glViewport(0, 0, screen->w, screen->h);
-
-        }
-        else glReadPixels(0, 0, m.w, m.h, GL_BGRA, GL_UNSIGNED_BYTE, m.video);
-    }
- 
-    bool readbuffer()
-    {
-        if(!file) return false;
-        if(state != REC_OK)
-        {
-            stop();
-            return false;
-        }
-        SDL_LockMutex(videolock);
-        if(moviesync && videobuffers.full()) SDL_CondWait(shouldread, videolock);
-        uint nextframe = ((totalmillis - starttime)*file->videofps)/1000;
-        if(!videobuffers.full() && (lastframe == ~0U || nextframe > lastframe))
-        {
-            videobuffer &m = videobuffers.adding();
-            SDL_UnlockMutex(videolock);
-            readbuffer(m, nextframe);
-            SDL_LockMutex(videolock);
-            lastframe = nextframe;
-            videobuffers.add();
-            SDL_CondSignal(shouldencode);
-        }
-        SDL_UnlockMutex(videolock);
-        return true;
-    }
-
-    void drawhud()
-    {
-        int w = screen->w, h = screen->h;
-
-        gettextres(w, h);
-
-        glMatrixMode(GL_PROJECTION);
-        glLoadIdentity();
-        glOrtho(0, w, h, 0, -1, 1);
-        glMatrixMode(GL_MODELVIEW);
-        glLoadIdentity();
-
-        glEnable(GL_BLEND);
-        glEnable(GL_TEXTURE_2D);
-        defaultshader->set();
-
-        glPushMatrix();
-        glScalef(1/3.0f, 1/3.0f, 1);
-    
-        double totalsize = file->filespaceguess();
-        const char *unit = "KB";
-        if(totalsize >= 1e9) { totalsize /= 1e9; unit = "GB"; }
-        else if(totalsize >= 1e6) { totalsize /= 1e6; unit = "MB"; }
-        else totalsize /= 1e3;
-
-        float quality = 1.0f - float(dps)/float(dps+file->videofps); // strictly speaking should lock to read dps - 1.0=perfect, 0.5=half of frames are beingdropped
-        draw_textf("recorded %.1f%s %d%%", w*3-10*FONTH, h*3-FONTH-FONTH*3/2, totalsize, unit, int(quality*100)); 
-
-        glPopMatrix();
-
-        glDisable(GL_TEXTURE_2D);
-        glDisable(GL_BLEND);
-    }
-
-    void capture()
-    {
-        if(readbuffer()) drawhud();
-    }
-}
-
-VARP(moview, 0, 320, 10000);
-VARP(movieh, 0, 240, 10000);
-VARP(moviefps, 1, 24, 1000);
-VARP(moviesound, 0, 1, 1);
-
-void movie(char *name)
-{
-    if(name[0] == '\0') recorder::stop();
-    else if(!recorder::isrecording()) recorder::start(name, moviefps, moview ? moview : screen->w, movieh ? movieh : screen->h, moviesound!=0);
-}
-
-COMMAND(movie, "s");
-
diff --git a/engine/normal.cpp b/engine/normal.cpp
index 5aad35c..81dab13 100644
--- a/engine/normal.cpp
+++ b/engine/normal.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 struct normal
@@ -20,9 +21,9 @@ struct nkey
 
 struct nval
 {
-    int flat, normals;
+    int normals;
 
-    nval() : flat(0), normals(-1) {}
+    nval() : normals(-1) {}
 };
 
 static inline bool htcmp(const nkey &x, const nkey &y)
@@ -38,11 +39,11 @@ static inline uint hthash(const nkey &k)
 hashtable<nkey, nval> normalgroups(1<<16);
 vector<normal> normals;
 
-VARR(lerpangle, 0, 44, 180);
+VARF(lerpangle, 0, 44, 180, hdr.lerpangle = lerpangle);
 
 static float lerpthreshold = 0;
 
-static void addnormal(const ivec &origin, const vvec &offset, const vec &surface)
+void addnormal(const ivec &origin, const vvec &offset, const vec &surface)
 {
     nkey key(origin, offset);
     nval &val = normalgroups[key];
@@ -52,27 +53,14 @@ static void addnormal(const ivec &origin, const vvec &offset, const vec &surface
     val.normals = normals.length()-1;
 }
 
-static void addnormal(const ivec &origin, const vvec &offset, int axis)
-{
-    nkey key(origin, offset);
-    nval &val = normalgroups[key];
-    val.flat += 1<<(4*axis);
-}
-
 void findnormal(const ivec &origin, const vvec &offset, const vec &surface, vec &v)
 {
     nkey key(origin, offset);
-    const nval *val = normalgroups.access(key);
+    nval *val = normalgroups.access(key);
     if(!val) { v = surface; return; }
 
     v = vec(0, 0, 0);
     int total = 0;
-    if(surface.x >= lerpthreshold) { int n = (val->flat>>4)&0xF; v.x += n; total += n; }
-    else if(surface.x <= -lerpthreshold) { int n = val->flat&0xF; v.x -= n; total += n; }
-    if(surface.y >= lerpthreshold) { int n = (val->flat>>12)&0xF; v.y += n; total += n; }
-    else if(surface.y <= -lerpthreshold) { int n = (val->flat>>8)&0xF; v.y -= n; total += n; }
-    if(surface.z >= lerpthreshold) { int n = (val->flat>>20)&0xF; v.z += n; total += n; }
-    else if(surface.z <= -lerpthreshold) { int n = (val->flat>>16)&0xF; v.z -= n; total += n; }
     for(int cur = val->normals; cur >= 0;)
     {
         normal &o = normals[cur];
@@ -87,15 +75,15 @@ void findnormal(const ivec &origin, const vvec &offset, const vec &surface, vec
     else if(!total) v = surface;
 }
 
-VARR(lerpsubdiv, 0, 2, 4);
-VARR(lerpsubdivsize, 4, 4, 128);
+VAR(lerpsubdiv, 0, 2, 4);
+VAR(lerpsubdivsize, 4, 4, 128);
 
 static uint progress = 0;
 
 void show_calcnormals_progress()
 {
     float bar1 = float(progress) / float(allocnodes);
-    renderprogress(bar1, "computing normals...");
+    show_out_of_renderloop_progress(bar1, "computing normals...");
 }
 
 #define CHECK_PROGRESS(exit) CHECK_CALCLIGHT_PROGRESS(exit, show_calcnormals_progress)
@@ -124,12 +112,8 @@ void addnormals(cube &c, const ivec &o, int size)
         if(c.texture[i] == DEFAULT_SKY) continue;
 
         plane planes[2];
-        int numplanes = 0;
-        if(!flataxisface(c, i))
-        {
-            numplanes = genclipplane(c, i, verts, planes);
-            if(!numplanes) continue;
-        }
+        int numplanes = genclipplane(c, i, verts, planes);
+        if(!numplanes) continue;
         int subdiv = 0;
         if(lerpsubdiv && size > lerpsubdivsize) // && faceedges(c, i) == F_SOLID)
         {
@@ -149,21 +133,6 @@ void addnormals(cube &c, const ivec &o, int size)
         {
             const vvec &v = vvecs[idxs[j]], &vn = vvecs[idxs[(j+1)%4]];
             if(v==vn) continue;
-            if(!numplanes)
-            {
-                addnormal(o, v, i);
-                if(subdiv < 2) continue;
-                ivec dv;
-                loopk(3) dv[k] = (int(vn[k]) - int(v[k])) / subdiv;
-                if(dv.iszero()) continue;
-                vvec vs(v);
-                loopk(subdiv - 1)
-                {
-                    vs.add(dv);
-                    addnormal(o, vs, i);
-                }
-                continue;
-            }
             const vec &cur = numplanes < 2 || j == 1 ? planes[0] : (j == 3 ? planes[1] : avg);
             addnormal(o, v, cur);
             if(subdiv < 2) continue;
@@ -198,7 +167,7 @@ void calcnormals()
     if(!lerpangle) return;
     lerpthreshold = cos(lerpangle*RAD) - 1e-5f; 
     progress = 1;
-    loopi(8) addnormals(worldroot[i], ivec(i, 0, 0, 0, worldsize/2), worldsize/2);
+    loopi(8) addnormals(worldroot[i], ivec(i, 0, 0, 0, hdr.worldsize/2), hdr.worldsize/2);
 }
 
 void clearnormals()
diff --git a/engine/obj.h b/engine/obj.h
index 575b358..b457854 100644
--- a/engine/obj.h
+++ b/engine/obj.h
@@ -29,7 +29,7 @@ struct obj : vertmodel
             int len = strlen(filename);
             if(len < 4 || strcasecmp(&filename[len-4], ".obj")) return false;
 
-            stream *file = openfile(filename, "rb");
+            FILE *file = openfile(filename, "rb");
             if(!file) return false;
 
             name = newstring(filename);
@@ -71,13 +71,14 @@ struct obj : vertmodel
                     curmesh->tris = new tri[tris.length()]; \
                     memcpy(curmesh->tris, tris.getbuf(), tris.length()*sizeof(tri)); \
                 } \
-                if(attrib[2].empty()) curmesh->buildnorms(); \
             } while(0)
 
             string meshname = "";
             vertmesh *curmesh = NULL;
-            while(file->getline(buf, sizeof(buf)))
+            for(;;)
             {
+                fgets(buf, sizeof(buf), file);
+                if(feof(file)) break;
                 char *c = buf;
                 while(isspace(*c)) c++;
                 switch(*c)
@@ -95,7 +96,7 @@ struct obj : vertmodel
                         char *name = c;
                         size_t namelen = strlen(name);
                         while(namelen > 0 && isspace(name[namelen-1])) namelen--;
-                        copystring(meshname, name, min(namelen+1, sizeof(meshname)));
+                        s_strncpy(meshname, name, min(namelen+1, sizeof(meshname)));
 
                         if(curmesh) FLUSHMESH;
                         curmesh = NULL;
@@ -127,21 +128,19 @@ struct obj : vertmodel
                                 *index = verts.length();
                                 vert &v = verts.add();
                                 v.pos = vkey.x < 0 ? vec(0, 0, 0) : attrib[0][vkey.x];
-                                v.pos = vec(v.pos.z, -v.pos.x, v.pos.y);
                                 v.norm = vkey.z < 0 ? vec(0, 0, 0) : attrib[2][vkey.z];
-                                v.norm = vec(v.norm.z, -v.norm.x, v.norm.y);
                                 tcvert &tcv = tcverts.add();
                                 if(vkey.y < 0) tcv.u = tcv.v = 0;
-                                else { tcv.u = attrib[1][vkey.y].x; tcv.v = 1-attrib[1][vkey.y].y; }
+                                else { tcv.u = attrib[1][vkey.y].x; tcv.v = attrib[1][vkey.y].y; }
                             }
                             if(v0 < 0) v0 = *index;
                             else if(v1 < 0) v1 = *index;
                             else
                             {
                                 tri &t = tris.add();
-                                t.vert[0] = ushort(*index);
+                                t.vert[0] = ushort(v0);
                                 t.vert[1] = ushort(v1);
-                                t.vert[2] = ushort(v0);
+                                t.vert[2] = ushort(*index);
                                 v1 = *index;
                             }
                         }
@@ -152,7 +151,7 @@ struct obj : vertmodel
 
             if(curmesh) FLUSHMESH;
 
-            delete file;
+            fclose(file);
 
             return true;
         }
@@ -172,11 +171,11 @@ struct obj : vertmodel
         mdl.model = this;
         mdl.index = 0;
         const char *pname = parentdir(loadname);
-        defformatstring(name1)("packages/models/%s/tris.obj", loadname);
+        s_sprintfd(name1)("packages/models/%s/tris.obj", loadname);
         mdl.meshes = sharemeshes(path(name1));
         if(!mdl.meshes)
         {
-            defformatstring(name2)("packages/models/%s/tris.obj", pname);    // try obj in parent folder (vert sharing)
+            s_sprintfd(name2)("packages/models/%s/tris.obj", pname);    // try obj in parent folder (vert sharing)
             mdl.meshes = sharemeshes(path(name2));
             if(!mdl.meshes) return false;
         }
@@ -190,12 +189,12 @@ struct obj : vertmodel
     bool load()
     { 
         if(loaded) return true;
-        formatstring(objdir)("packages/models/%s", loadname);
-        defformatstring(cfgname)("packages/models/%s/obj.cfg", loadname);
+        s_sprintf(objdir)("packages/models/%s", loadname);
+        s_sprintfd(cfgname)("packages/models/%s/obj.cfg", loadname);
 
         loadingobj = this;
         persistidents = false;
-        if(execfile(cfgname, false) && parts.length()) // configured obj, will call the obj* commands below
+        if(execfile(cfgname) && parts.length()) // configured obj, will call the obj* commands below
         {
             persistidents = true;
             loadingobj = NULL;
@@ -207,11 +206,7 @@ struct obj : vertmodel
             loadingobj = NULL;
             if(!loaddefaultparts()) return false;
         }
-        scale /= 4;
-        translate.y = -translate.y;
-        parts[0]->translate = translate;
-        loopv(parts) parts[i]->meshes->shared++;
-        preloadshaders();
+        loopv(parts) parts[i]->meshes = parts[i]->meshes->scaleverts(scale/4.0f, i ? vec(0, 0, 0) : vec(translate.x, -translate.y, translate.z));
         return loaded = true;
     }
 };
@@ -219,7 +214,7 @@ struct obj : vertmodel
 void objload(char *model)
 {
     if(!loadingobj) { conoutf("not loading an obj"); return; }
-    defformatstring(filename)("%s/%s", objdir, model);
+    s_sprintfd(filename)("%s/%s", objdir, model);
     obj::part &mdl = *new obj::part;
     loadingobj->parts.add(&mdl);
     mdl.model = loadingobj;
@@ -270,7 +265,7 @@ void objskin(char *meshname, char *tex, char *masks, float *envmapmax, float *en
         s.tex = textureload(makerelpath(objdir, tex), 0, true, false);
         if(*masks)
         {
-            s.masks = textureload(makerelpath(objdir, masks, NULL, "<ffmask:25>"), 0, true, false);
+            s.masks = textureload(makerelpath(objdir, masks, "<ffmask:25>"), 0, true, false);
             s.envmapmax = *envmapmax;
             s.envmapmin = *envmapmin;
         }
@@ -316,11 +311,6 @@ void objalphablend(char *meshname, int *blend)
     loopobjskins(meshname, s, s.alphablend = *blend!=0);
 }
 
-void objcullface(char *meshname, int *cullface)
-{
-    loopobjskins(meshname, s, s.cullface = *cullface!=0);
-}
-
 void objenvmap(char *meshname, char *envmap)
 {
     Texture *tex = cubemapload(envmap);
@@ -335,6 +325,11 @@ void objbumpmap(char *meshname, char *normalmap, char *skin)
     loopobjskins(meshname, s, { s.unlittex = skintex; s.normalmap = normalmaptex; m.calctangents(); });
 }
 
+void objtranslucent(char *meshname, float *translucency)
+{
+    loopobjskins(meshname, s, s.translucency = *translucency);
+}
+
 void objfullbright(char *meshname, float *fullbright)
 {
     loopobjskins(meshname, s, s.fullbright = *fullbright);
@@ -364,9 +359,9 @@ COMMAND(objglow, "si");
 COMMAND(objglare, "sff");
 COMMAND(objalphatest, "sf");
 COMMAND(objalphablend, "si");
-COMMAND(objcullface, "si");
 COMMAND(objenvmap, "ss");
 COMMAND(objbumpmap, "sss");
+COMMAND(objtranslucent, "sf");
 COMMAND(objfullbright, "sf");
 COMMAND(objshader, "ss");
 COMMAND(objscroll, "sff");
diff --git a/engine/octa.cpp b/engine/octa.cpp
index 2d0cad6..d6e36a5 100644
--- a/engine/octa.cpp
+++ b/engine/octa.cpp
@@ -1,5 +1,6 @@
 // core world management routines
 
+#include "pch.h"
 #include "engine.h"
 
 cube *worldroot = newcubes(F_SOLID);
@@ -158,7 +159,7 @@ ivec lu;
 int lusize;
 cube &lookupcube(int tx, int ty, int tz, int tsize)
 {
-    int size = worldsize;
+    int size = hdr.worldsize;
     int x = 0, y = 0, z = 0;
     cube *c = worldroot;
     for(;;)
@@ -361,7 +362,7 @@ bool subdividecube(cube &c, bool fullcheck, bool brighten)
         }
     }
 
-    validatec(ch, worldsize);
+    validatec(ch, hdr.worldsize);
     if(fullcheck) loopi(8) if(!isvalidcube(ch[i])) // not so good...
     {
         emptyfaces(ch[i]);
@@ -405,9 +406,11 @@ bool remip(cube &c, int x, int y, int z, int size)
         subdividecube(c);
         ch = c.children;
     }
-    else if((remipprogress++&0xFFF)==1) renderprogress(float(remipprogress)/remiptotal, "remipping...");
+    else if((remipprogress++&0x7FF)==1) show_out_of_renderloop_progress(float(remipprogress)/remiptotal, "remipping...");
 
     bool perfect = true;
+    uchar mat = ch[0].ext ? ch[0].ext->material : MAT_AIR;
+
     loopi(8)
     {
         ivec o(i, x, y, z, size);
@@ -421,27 +424,6 @@ bool remip(cube &c, int x, int y, int z, int size)
     if(!perfect) return false;
     if(size<<1 > VVEC_INT_MASK+1) return false;
 
-    uchar mat = MAT_AIR;
-    loopi(8)
-    {
-        mat = ch[i].ext ? ch[i].ext->material : MAT_AIR;
-        if((mat&MATF_CLIP) == MAT_NOCLIP)
-        {
-            if(i > 0) return false;
-            while(++i < 8) if(!ch[i].ext || ch[i].ext->material != mat) return false;
-            break;
-        }
-        else if(!isentirelysolid(ch[i]))
-        {
-            while(++i < 8)
-            {
-                uchar omat = ch[i].ext ? ch[i].ext->material : MAT_AIR;
-                if(isentirelysolid(ch[i]) ? (omat&MATF_CLIP) == MAT_NOCLIP : mat != omat) return false;
-            }
-            break;
-        }
-    }
-
     cube n = c;
     forcemip(n);
     n.children = NULL;
@@ -454,7 +436,8 @@ bool remip(cube &c, int x, int y, int z, int size)
     {
         if(ch[i].faces[0] != nh[i].faces[0] ||
            ch[i].faces[1] != nh[i].faces[1] ||
-           ch[i].faces[2] != nh[i].faces[2])
+           ch[i].faces[2] != nh[i].faces[2] ||
+           (ch[i].ext ? ch[i].ext->material : MAT_AIR) != mat)
             { freeocta(nh); return false; }
 
         if(isempty(ch[i]) && isempty(nh[i])) continue;
@@ -485,13 +468,13 @@ bool remip(cube &c, int x, int y, int z, int size)
 void mpremip(bool local)
 {
     extern selinfo sel;
-    if(local) game::edittrigger(sel, EDIT_REMIP);
+    if(local) cl->edittrigger(sel, EDIT_REMIP);
     remipprogress = 1;
     remiptotal = allocnodes;
     loopi(8)
     {
-        ivec o(i, 0, 0, 0, worldsize>>1);
-        remip(worldroot[i], o.x, o.y, o.z, worldsize>>2);
+        ivec o(i, 0, 0, 0, hdr.worldsize>>1);
+        remip(worldroot[i], o.x, o.y, o.z, hdr.worldsize>>2);
     }
     calcmerges();
     if(!local) allchanged();
@@ -759,25 +742,6 @@ static inline void genfacevecs(cube &c, int orient, const ivec &pos, int size, b
     }
 }
 
-static inline int genoppositefacevecs(cube &c, int orient, const ivec &pos, int size, facevec *fvecs)
-{
-    int dim = dimension(orient), coord = dimcoord(orient), touching = 0;
-    const ushort *fvo = fv[orient];
-    loopi(4)
-    {
-        const ivec &cc = cubecoords[fvo[coord ? i : 3 - i]];
-        if(edgeval(c, cc, dim, cc[dim]) == coord*8)
-        {
-            int x = edgeval(c, cc, C[dim], cc[C[dim]]),
-                y = edgeval(c, cc, R[dim], cc[R[dim]]);
-            facevec &f = fvecs[touching++];
-            f.x = x*size/(8>>VVEC_FRAC)+(pos[C[dim]]<<VVEC_FRAC);
-            f.y = y*size/(8>>VVEC_FRAC)+(pos[R[dim]]<<VVEC_FRAC);
-        }
-    }
-    return touching;
-}
-
 static inline int clipfacevecy(const facevec &o, const facevec &dir, int cx, int cy, int size, facevec &r)
 {
     if(dir.x >= 0)
@@ -832,12 +796,12 @@ static inline int clipfacevec(const facevec &o, const facevec &dir, int cx, int
     return r;
 }
 
-static inline bool insideface(const facevec *p, int nump, const facevec *o, int numo)
+static inline bool insideface(const facevec *p, int nump, const facevec *o)
 {
     int bounds = 0;
-    loopi(numo)
+    loopi(4)
     {
-        const facevec &cur = o[i], &next = o[i+1 < numo ? i+1 : 0];
+        const facevec &cur = o[i], &next = o[(i+1)%4];
         if(cur == next) continue;
         facevec dir(next.x-cur.x, next.y-cur.y);
         int offset = dir.x*cur.y - dir.y*cur.x;
@@ -862,7 +826,7 @@ static inline int clipfacevecs(const facevec *o, int cx, int cy, int size, facev
         r += clipfacevec(cur, dir, cx, cy, size, &rvecs[r]);
     }
     facevec corner[4] = {facevec(cx, cy), facevec(cx+size, cy), facevec(cx+size, cy+size), facevec(cx, cy+size)};
-    loopi(4) if(insideface(&corner[i], 1, o, 4)) rvecs[r++] = corner[i];
+    loopi(4) if(insideface(&corner[i], 1, o)) rvecs[r++] = corner[i];
     ASSERT(r <= 8);
     return r;
 }
@@ -889,10 +853,10 @@ static inline bool occludesface(cube &c, int orient, const ivec &o, int size, co
          facevec cf[8];
          int numc = clipfacevecs(vf, o[C[dim]], o[R[dim]], size, cf);
          if(numc < 3) return true;
-         if(isempty(c)) return false;
+         if(isempty(c) || !touchingface(c, orient)) return false;
          facevec of[4];
-         int numo = genoppositefacevecs(c, orient, o, size, of);
-         return numo >= 3 && insideface(cf, numc, of, numo);
+         genfacevecs(c, orient, o, size, false, of);
+         return insideface(cf, numc, of);
     }
 
     size >>= 1;
@@ -926,16 +890,16 @@ bool visibleface(cube &c, int orient, int x, int y, int z, int size, uchar mat,
         if(nmat != MAT_AIR && o.ext && (o.ext->material&matmask) == nmat) return true;
         if(isentirelysolid(o)) return false;
         if(mat != MAT_AIR && o.ext && ((o.ext->material&matmask) == mat || (isliquid(mat) && (o.ext->material&MATF_VOLUME) == MAT_GLASS))) return false;
-        if(isempty(o)) return true;
-        if(touchingface(o, opposite(orient)) && faceedges(o, opposite(orient)) == F_SOLID) return false;
+        if(isempty(o) || !touchingface(o, opposite(orient))) return true;
+        if(faceedges(o, opposite(orient)) == F_SOLID) return false;
 
         ivec vo(x, y, z);
         vo.mask(VVEC_INT_MASK);
         lu.mask(VVEC_INT_MASK);
         facevec cf[4], of[4];
         genfacevecs(c, orient, vo, size, mat != MAT_AIR, cf);
-        int numo = genoppositefacevecs(o, opposite(orient), lu, lusize, of);
-        return numo < 3 || !insideface(cf, 4, of, numo);
+        genfacevecs(o, opposite(orient), lu, lusize, false, of);
+        return !insideface(cf, 4, of);
     }
 
     ivec vo(x, y, z);
@@ -946,100 +910,6 @@ bool visibleface(cube &c, int orient, int x, int y, int z, int size, uchar mat,
     return !occludesface(o, opposite(orient), lu, lusize, vo, size, mat, nmat, matmask, cf);
 }
 
-// more expensive version that checks both triangles of a face independently
-int visibletris(cube &c, int orient, int x, int y, int z, int size)
-{
-    int dim = dimension(orient), coord = dimcoord(orient);
-    uint face = c.faces[dim];
-    if(coord) face = (face&0xF0F0F0F0)^0x80808080;
-    else face &= 0x0F0F0F0F;
-
-    int notouch = 0;
-    if(face&0xFF) notouch++;
-    if(face&0xFF00) notouch++;
-    if(face&0xFF0000) notouch++;
-    if(face&0xFF000000) notouch++; 
-    if(notouch>=2) return 3;
-
-    if(collapsedface(faceedges(c, orient))) return 0;
-
-    cube &o = neighbourcube(x, y, z, size, -size, orient);
-    if(&o==&c) return 0;
-
-    ivec vo(x, y, z);
-    vo.mask(VVEC_INT_MASK);
-    lu.mask(VVEC_INT_MASK);
-    facevec cf[4], of[4];
-    int opp = opposite(orient), numo = 0;
-    if(lusize > size || (lusize == size && !o.children))
-    {
-        if(isempty(o)) return 3;
-        if(!notouch && (isentirelysolid(o) || (touchingface(o, opp) && faceedges(o, opp) == F_SOLID))) return 0;
-
-        genfacevecs(c, orient, vo, size, false, cf);
-        numo = genoppositefacevecs(o, opp, lu, lusize, of);
-        if(numo < 3) return 3;
-        if(!notouch && insideface(cf, 4, of, numo)) return 0; 
-    }
-    else
-    {
-        genfacevecs(c, orient, vo, size, false, cf);
-        if(!notouch && occludesface(o, opp, lu, lusize, vo, size, MAT_AIR, MAT_AIR, MATF_VOLUME, cf)) return 0;
-    }
-
-    static const int trimasks[2][2] = { { 0x7, 0xD }, { 0xE, 0xB } };
-    static const int triverts[2][2][2][3] =
-    { // order
-        { // coord
-            { { 1, 2, 3 }, { 0, 1, 3 } }, // verts
-            { { 0, 1, 2 }, { 0, 2, 3 } }
-        },
-        { // coord
-            { { 0, 1, 2 }, { 3, 0, 2 } }, // verts
-            { { 1, 2, 3 }, { 1, 3, 0 } }
-        }
-    };
-
-    int convex = faceconvexity(c, orient),
-        order = convex < 0 ? 1 : 0,
-        vis = 3, 
-        touching = 0;
-    loopi(4)
-    {
-        const ivec &cc = cubecoords[fv[orient][i]];
-        if(edgeval(c, cc, dim, cc[dim]) == coord*8) touching |= 1<<i;
-    }
-    facevec tf[4];
-    
-    for(;;)
-    {
-        loopi(2) if((touching&trimasks[order][i])==trimasks[order][i])
-        {    
-            const int *verts = triverts[order][coord][i];
-            int v1 = verts[0], v2 = verts[1], v3 = verts[2];
-            if(cf[v1]==cf[v2] || cf[v2]==cf[v3] || cf[v3]==cf[v1]) { notouch = 1; continue; }
-            tf[0] = cf[v1]; tf[1] = cf[v2]; tf[2] = cf[v3];
-            if(!numo)
-            {
-                tf[3] = cf[v3];
-                if(!occludesface(o, opp, lu, lusize, vo, size, MAT_AIR, MAT_AIR, MATF_VOLUME, tf)) continue;
-            }
-            else if(!insideface(tf, 3, of, numo)) continue;
-            return vis & ~(1<<i);
-        }
-        if(notouch || vis&4) break;
-        if(c.ext && c.ext->surfaces) // compat for old lightmaps that can't be reordered
-        {
-            const uchar *tc = c.ext->surfaces[orient].texcoords;
-            if((tc[0]!=tc[6] || tc[1]!=tc[7]) && (tc[0]!=tc[2] || tc[1]!=tc[3])) break;
-        }
-        vis |= 4;
-        order++;
-    }
-    
-    return 3;
-}
-
 void calcvert(cube &c, int x, int y, int z, int size, vvec &v, int i, bool solid)
 {
     ivec vv;
@@ -1378,9 +1248,9 @@ VAR(maxmerge, 0, 6, VVEC_INT-1);
 
 static int genmergeprogress = 0; 
 
-void genmergeinfo(cube *c = worldroot, const ivec &o = ivec(0, 0, 0), int size = worldsize>>1)
+void genmergeinfo(cube *c = worldroot, const ivec &o = ivec(0, 0, 0), int size = hdr.worldsize>>1)
 {
-    if((genmergeprogress++&0xFFF)==0) renderprogress(float(genmergeprogress)/allocnodes, "merging surfaces...");
+    if((genmergeprogress++&0x7FF)==0) show_out_of_renderloop_progress(float(genmergeprogress)/allocnodes, "merging surfaces...");
     loopi(8)
     {
         ivec co(i, o.x, o.y, o.z, size);
@@ -1489,7 +1359,7 @@ int calcmergedsize(int orient, const ivec &co, int size, const mergeinfo &m, con
     return bits-VVEC_FRAC;
 }
 
-static void invalidatemerges(cube &c)
+void invalidatemerges(cube &c)
 {   
     if(c.ext)
     {
@@ -1510,18 +1380,6 @@ static void invalidatemerges(cube &c)
     if(c.children) loopi(8) invalidatemerges(c.children[i]);
 }
 
-static int invalidatedmerges = 0;
-
-void invalidatemerges(cube &c, bool msg)
-{
-    if(msg && invalidatedmerges!=totalmillis)
-    {
-        renderprogress(0, "invalidating merged surfaces...");
-        invalidatedmerges = totalmillis;
-    }
-    invalidatemerges(c);
-}
- 
 void calcmerges()
 {
     genmergeprogress = 0;
diff --git a/engine/octa.h b/engine/octa.h
index 1401458..c3f5e90 100644
--- a/engine/octa.h
+++ b/engine/octa.h
@@ -1,18 +1,8 @@
 // 6-directional octree heightfield map format
 
-enum
-{
-    LAYER_TOP = 0,
-    LAYER_BOTTOM,
-
-    LAYER_BLEND = 1<<1
-};
-
 struct elementset
 {
-    ushort texture;
-    uchar lmid, layer;
-    ushort envmap;
+    ushort texture, lmid, envmap;
     ushort length[6];
     ushort minvert[6], maxvert[6];
 };
@@ -47,8 +37,7 @@ struct surfaceinfo
 {
     uchar texcoords[8];
     uchar w, h;
-    ushort x, y;
-    uchar lmid, layer;
+    ushort x, y, lmid;
 };
 
 struct surfacenormals
@@ -58,13 +47,36 @@ struct surfacenormals
 
 struct grasstri
 {
-    vec v[4];
-    int numv;
-    vec4 tcu, tcv;
-    plane surface, e[4];
-    vec center;
-    float radius;
-    ushort texture, lmid;
+    vvec v[4];
+    surfaceinfo *surface;
+    ushort texture;
+};
+
+enum
+{
+    GRASS_SAMPLE  = 0<<14,
+    GRASS_BOUNDS  = 1<<14,
+    GRASS_TEXTURE = 2<<14,
+
+    GRASS_TYPE = 3<<14
+};
+
+struct grassbounds
+{
+    ushort x, y, z;
+    ushort radius, numsamples;
+};
+
+struct grasssample
+{
+    ushort x, y, z;
+    uchar color[3], reserved;
+};
+
+struct grasstexture
+{
+    ushort x, y, z;
+    ushort texture, reserved;
 };
 
 struct occludequery
@@ -81,7 +93,7 @@ struct octaentities
     vector<int> mapmodels;
     vector<int> other;
     occludequery *query;
-    octaentities *next, *rnext;
+    octaentities *next;
     int distance;
     ivec o;
     int size;
@@ -120,7 +132,7 @@ struct vtxarray
     ushort minvert, maxvert; // DRE info
     elementset *eslist;      // List of element indices sets (range) per texture
     materialsurface *matbuf; // buffer of material surfaces
-    int verts, tris, texs, blends, texmask, sky, explicitsky, skyfaces, skyclip, matsurfs, distance;
+    int verts, tris, texs, texmask, sky, explicitsky, skyfaces, skyclip, matsurfs, distance;
     double skyarea;
     ivec o;
     int size;                // location and size of cube.
@@ -132,6 +144,7 @@ struct vtxarray
     occludequery *query, *rquery;
     vector<octaentities *> *mapmodels;
     vector<grasstri> *grasstris;
+    vector<grasssample> *grasssamples;
     int hasmerges;
     uint dynlightmask;
     bool shadowed;
diff --git a/engine/octaedit.cpp b/engine/octaedit.cpp
index 581ae75..267413e 100644
--- a/engine/octaedit.cpp
+++ b/engine/octaedit.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 extern int outline;
@@ -93,7 +94,7 @@ VARF(gridpower, 3-VVEC_FRAC, 3, VVEC_INT-1,
 {
     if(dragging) return;
     gridsize = 1<<gridpower;
-    if(gridsize>=worldsize) gridsize = worldsize/2;
+    if(gridsize>=hdr.worldsize) gridsize = hdr.worldsize/2;
     cancelsel();
 });
 
@@ -122,7 +123,7 @@ void cancelsel()
 void toggleedit(bool force)
 {
     if(player->state!=CS_ALIVE && player->state!=CS_DEAD && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion
-    if(!editmode && !game::allowedittoggle()) return;         // not in most multiplayer modes
+    if(!editmode && !cc->allowedittoggle()) return;         // not in most multiplayer modes
     if(!(editmode = !editmode))
     {
         player->state = player->editstate;
@@ -131,22 +132,21 @@ void toggleedit(bool force)
     }
     else
     {
-        game::resetgamestate();
+        cl->resetgamestate();
         player->editstate = player->state;
         player->state = CS_EDITING;
     }
     cancelsel();
-    stoppaintblendmap();
     keyrepeat(editmode);
     editing = entediting = editmode;
     extern int fullbright;
     if(fullbright) initlights();
-    if(!force) game::edittoggled(editmode);
+    if(!force) cc->edittoggled(editmode);
 }
 
-bool noedit(bool view, bool msg)
+bool noedit(bool view)
 {
-    if(!editmode) { if(msg) conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; }
+    if(!editmode) { conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; }
     if(view || haveselent()) return false;
     float r = 1.0f;
     vec o, s;
@@ -156,7 +156,7 @@ bool noedit(bool view, bool msg)
     o.add(s);
     r = float(max(s.x, max(s.y, s.z)));
     bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE);
-    if(!viewable && msg) conoutf(CON_ERROR, "selection not in view");
+    if(!viewable) conoutf(CON_ERROR, "selection not in view");
     return !viewable;
 }
 
@@ -277,27 +277,32 @@ inline bool isheightmap(int orient, int d, bool empty, cube *c);
 extern void entdrag(const vec &ray);
 extern bool hoveringonent(int ent, int orient);
 extern void renderentselection(const vec &o, const vec &ray, bool entmoving);
-extern float rayent(const vec &o, const vec &ray, float radius, int mode, int size, int &orient, int &ent);
+extern float rayent(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size, int &orient, int &ent);
 
 VAR(gridlookup, 0, 0, 1);
 VAR(passthroughcube, 0, 1, 1);
 
-void rendereditcursor()
+void cursorupdate()
 {
     if(sel.grid == 0) sel.grid = gridsize;
 
+    vec target(worldpos);
+    if(!insideworld(target)) loopi(3) 
+        target[i] = max(min(target[i], float(hdr.worldsize)), 0.0f);
+    vec ray(target);
+    ray.sub(player->o).normalize();
     int d   = dimension(sel.orient),
         od  = dimension(orient),
         odc = dimcoord(orient);
 
-    bool hidecursor = g3d_windowhit(true, false) || blendpaintmode, hovering = false;
+    bool hidecursor = g3d_windowhit(true, false), hovering = false;
     hmapsel = false;
            
     if(moving)
     {       
         ivec e;
         static vec v, handle;
-        editmoveplane(sel.o.tovec(), camdir, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, v, !havesel);
+        editmoveplane(sel.o.tovec(), ray, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, v, !havesel);
         if(!havesel)
         {
             v.add(handle);
@@ -312,22 +317,22 @@ void rendereditcursor()
     else 
     if(entmoving)
     {
-        entdrag(camdir);       
+        entdrag(ray);       
     }
     else
     {  
+        vec v;
         ivec w;
         float sdist = 0, wdist = 0, t;
         int entorient = 0, ent = -1;
        
-        wdist = rayent(player->o, camdir, 1e16f, 
-                       (editmode && showmat ? RAY_EDITMAT : 0)   // select cubes first
-                       | (!dragging && entediting ? RAY_ENTS : 0)
-                       | RAY_SKIPFIRST 
-                       | (passthroughcube==1 ? RAY_PASS : 0), gridsize, entorient, ent);
+        wdist = rayent(player->o, ray, v, 0, (editmode && showmat ? RAY_EDITMAT : 0)   // select cubes first
+                                           | (!dragging && entediting ? RAY_ENTS : 0)
+                                           | RAY_SKIPFIRST 
+                                           | (passthroughcube==1 ? RAY_PASS : 0), gridsize, entorient, ent);
      
         if((havesel || dragging) && !passthroughsel && !hmapedit)     // now try selecting the selection
-            if(rayrectintersect(sel.o.tovec(), vec(sel.s.tovec()).mul(sel.grid), player->o, camdir, sdist, orient))
+            if(rayrectintersect(sel.o.tovec(), vec(sel.s.tovec()).mul(sel.grid), player->o, ray, sdist, orient))
             {   // and choose the nearest of the two
                 if(sdist < wdist) 
                 {
@@ -335,7 +340,7 @@ void rendereditcursor()
                     ent   = -1;
                 }
             }
-       
+
         if((hovering = hoveringonent(hidecursor ? -1 : ent, entorient)))
         {
            if(!havesel) 
@@ -346,28 +351,22 @@ void rendereditcursor()
         }
         else 
         {
-            vec w = vec(camdir).mul(wdist+0.05f).add(player->o);
-            if(!insideworld(w))
-            {
-                loopi(3) wdist = min(wdist, ((camdir[i] > 0 ? worldsize : 0) - player->o[i]) / camdir[i]);
-                w = vec(camdir).mul(wdist-0.05f).add(player->o);
-                if(!insideworld(w))
-                {
-                    wdist = 0;
-                    loopi(3) w[i] = clamp(player->o[i], 0.0f, float(worldsize));
-                }
-            }
-            cube *c = &lookupcube(int(w.x), int(w.y), int(w.z));            
+       
+            v = ray;
+            v.mul(wdist+0.1f);
+            v.add(player->o);
+            w = v;
+            cube *c = &lookupcube(w.x, w.y, w.z);            
             if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize;
             int mag = lusize / gridsize;
-            normalizelookupcube(int(w.x), int(w.y), int(w.z));
-            if(sdist == 0 || sdist > wdist) rayrectintersect(lu.tovec(), vec(gridsize), player->o, camdir, t=0, orient); // just getting orient     
+            normalizelookupcube(w.x, w.y, w.z);
+            if(sdist == 0 || sdist > wdist) rayrectintersect(lu.tovec(), vec(gridsize), player->o, ray, t=0, orient); // just getting orient     
             cur = lu;
-            cor = vec(w).mul(2).div(gridsize);
+            cor = vec(v).mul(2).div(gridsize);
             od = dimension(orient);
             d = dimension(sel.orient);
             
-            if(hmapedit==1 && dimcoord(horient) == (camdir[dimension(horient)]<0))
+            if(hmapedit==1 && dimcoord(horient) == (ray[dimension(horient)]<0))
             {
                 hmapsel = isheightmap(horient, dimension(horient), false, c);     
                 if(hmapsel)
@@ -414,7 +413,7 @@ void rendereditcursor()
 
             sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2;
             selchildcount = 0;
-            countselchild(worldroot, ivec(0, 0, 0), worldsize/2);
+            countselchild(worldroot, ivec(0, 0, 0), hdr.worldsize/2);
             if(mag>1 && selchildcount==1) selchildcount = -mag;
         }
     }
@@ -424,7 +423,7 @@ void rendereditcursor()
     
     // cursors    
 
-    renderentselection(player->o, camdir, entmoving!=0);
+    renderentselection(player->o, ray, entmoving!=0);
 
     enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
 
@@ -465,16 +464,9 @@ void rendereditcursor()
     glDisable(GL_BLEND);
 }
 
-void tryedit()
-{
-    extern int hidehud;
-    if(!editmode || hidehud || mainmenu) return;
-    if(blendpaintmode) trypaintblendmap();
-}
-
 //////////// ready changes to vertex arrays ////////////
 
-static bool haschanged = false;
+static bool invalidatedmerges = false, haschanged = false;
 
 void readychanges(block3 &b, cube *c, const ivec &cor, int size)
 {
@@ -488,7 +480,15 @@ void readychanges(block3 &b, cube *c, const ivec &cor, int size)
                 int hasmerges = c[i].ext->va->hasmerges;
                 destroyva(c[i].ext->va);
                 c[i].ext->va = NULL;
-                if(hasmerges) invalidatemerges(c[i], true); 
+                if(hasmerges) 
+                {
+                    if(!invalidatedmerges)
+                    {
+                        show_out_of_renderloop_progress(0, "invalidating merged surfaces...");
+                        invalidatedmerges = true;
+                    }
+                    invalidatemerges(c[i]);
+                }
             }
             freeoctaentities(c[i]);
             c[i].ext->tjoints = -1;
@@ -507,10 +507,11 @@ void readychanges(block3 &b, cube *c, const ivec &cor, int size)
     }
 }
 
-void commitchanges(bool force)
+void commitchanges()
 {
-    if(!force && !haschanged) return;
+    if(!haschanged) return;
     haschanged = false;
+    invalidatedmerges = false;
 
     extern vector<vtxarray *> valist;
     int oldlen = valist.length();
@@ -521,7 +522,6 @@ void commitchanges(bool force)
     setupmaterials(oldlen);
     invalidatepostfx();
     updatevabbs();
-    resetblobs();
 }
 
 void changed(const block3 &sel, bool commit = true)
@@ -534,7 +534,7 @@ void changed(const block3 &sel, bool commit = true)
     {
         b.o[i] -= 1;
         b.s[i] += 2;
-        readychanges(b, worldroot, ivec(0, 0, 0), worldsize/2);
+        readychanges(b, worldroot, ivec(0, 0, 0), hdr.worldsize/2);
         b.o[i] += 1;
         b.s[i] -= 2;
     }
@@ -586,7 +586,7 @@ void freeblock(block3 *b, bool alloced = true)
 
 void selgridmap(selinfo &sel, int *g)                           // generates a map of the cube sizes at each grid point
 {
-    loopxyz(sel, -sel.grid, (*g++ = lusize, (void)c));
+    loopxyz(sel, -sel.grid, (*g++ = lusize, c));
 }
 
 void freeundo(undoblock *u)
@@ -707,11 +707,9 @@ void addundo(undoblock *u)
     pruneundos(undomegs<<20);
 }
 
-VARP(nompedit, 0, 1, 1);
-
 void makeundoex(selinfo &s)
 {
-    if(nompedit && multiplayer(false)) return;
+    if(multiplayer(false)) return;
     undoblock *u = newundocube(s);
     addundo(u);
 }
@@ -725,7 +723,7 @@ void makeundo()                        // stores state of selected cubes before
 
 void swapundo(undolist &a, undolist &b, const char *s)
 {
-    if(noedit() || (nompedit && multiplayer())) return;
+    if(noedit() || multiplayer()) return;
     if(a.empty()) { conoutf(CON_WARN, "nothing more to %s", s); return; }	
 	int ts = a.last->timestamp;
     selinfo l = sel;
@@ -776,7 +774,7 @@ void freeeditinfo(editinfo *&e)
 
 void mpcopy(editinfo *&e, selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_COPY);
+    if(local) cl->edittrigger(sel, EDIT_COPY);
     if(e==NULL) e = new editinfo;
     if(e->copy) freeblock(e->copy);
     e->copy = NULL;
@@ -787,7 +785,7 @@ void mpcopy(editinfo *&e, selinfo &sel, bool local)
 void mppaste(editinfo *&e, selinfo &sel, bool local)
 {
     if(e==NULL) return;
-    if(local) game::edittrigger(sel, EDIT_PASTE);
+    if(local) cl->edittrigger(sel, EDIT_PASTE);
     if(e->copy)
     {
         sel.s = e->copy->s;
@@ -1038,7 +1036,7 @@ namespace hmap
         if(biasup)
             pullhmap(0, >, <, 1, 0, -);
         else
-            pullhmap(worldsize, <, >, 0, 8, +);     
+            pullhmap(hdr.worldsize, <, >, 0, 8, +);     
    
         cube **c  = cmap[x][y];
         int e[2][2];
@@ -1130,7 +1128,7 @@ namespace hmap
         bool paintme = paintbrush;
         int cx = (sel.corner&1 ? 0 : -1);
         int cy = (sel.corner&2 ? 0 : -1);
-        hws= (worldsize>>gridpower);
+        hws= (hdr.worldsize>>gridpower);
         gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2;
         gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2;
         gz = (cur[D[d]] >> gridpower);
@@ -1156,7 +1154,7 @@ namespace hmap
             bnx = min(nx, brushmaxx-1);
             bny = min(ny, brushmaxy-1);   
         }
-        nz = worldsize-gridsize;
+        nz = hdr.worldsize-gridsize;
         mz = 0;
         hundo.s = ivec(d,1,1,5);
         hundo.orient = sel.orient;
@@ -1184,7 +1182,7 @@ namespace hmap
 }
 
 void edithmap(int dir, int mode) {    
-    if((nompedit && multiplayer()) || !hmapsel || gridsize < 8) return;    
+    if(multiplayer() || !hmapsel || gridsize < 8) return;    
     hmap::run(dir, mode);        
 }
 
@@ -1234,12 +1232,12 @@ void mpeditface(int dir, int mode, selinfo &sel, bool local)
     int seldir = dc ? -dir : dir;
 
     if(local)
-        game::edittrigger(sel, EDIT_FACE, dir, mode);
+        cl->edittrigger(sel, EDIT_FACE, dir, mode);
 
     if(mode==1)
     {
         int h = sel.o[d]+dc*sel.grid;
-        if(((dir>0) == dc && h<=0) || ((dir<0) == dc && h>=worldsize)) return;
+        if(((dir>0) == dc && h<=0) || ((dir<0) == dc && h>=hdr.worldsize)) return;
         if(dir<0) sel.o[d] += sel.grid * seldir;
     }
 
@@ -1327,7 +1325,7 @@ void pushsel(int *dir)
 
 void mpdelcube(selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_DELCUBE);
+    if(local) cl->edittrigger(sel, EDIT_DELCUBE);
     loopselxyz(discardchildren(c); emptyfaces(c));
 }
 
@@ -1343,7 +1341,7 @@ COMMAND(delcube, "");
 
 /////////// texture editing //////////////////
 
-int curtexindex = -1, lasttex = 0, lasttexmillis = -1;
+int curtexindex = -1, lasttex = 0;
 int texpaneltimer = 0;
 vector<ushort> texmru;
 
@@ -1383,7 +1381,7 @@ void mpedittex(int tex, int allfaces, selinfo &sel, bool local)
 {
     if(local)
     {
-        game::edittrigger(sel, EDIT_TEX, tex, allfaces);
+        cl->edittrigger(sel, EDIT_TEX, tex, allfaces);
         if(allfaces || !(repsel == sel)) reptex = -1;
         repsel = sel;
     }
@@ -1400,15 +1398,11 @@ void filltexlist()
     }
 }
 
-void edittex(int i, bool save = true)
+void edittex(int i)
 {
-    lasttex = i;
-    lasttexmillis = totalmillis;
-    if(save) 
-    {
-        loopvj(texmru) if(texmru[j]==lasttex) { curtexindex = j; break; }
-    }
-    mpedittex(i, allfaces, sel, true);
+    curtexindex = i = min(max(i, 0), curtexnum-1);
+    int t = lasttex = texmru[i];    
+    mpedittex(t, allfaces, sel, true);
 }
 
 void edittex_(int *dir)
@@ -1417,8 +1411,7 @@ void edittex_(int *dir)
     filltexlist();
     texpaneltimer = 5000;
     if(!(lastsel==sel)) tofronttex();
-    curtexindex = clamp(curtexindex<0 ? 0 : curtexindex+*dir, 0, curtexnum-1);
-    edittex(texmru[curtexindex], false);
+    edittex(curtexindex<0 ? 0 : curtexindex+*dir);
 }
 
 void gettex()
@@ -1473,7 +1466,7 @@ void replacetexcube(cube &c, int oldtex, int newtex)
 
 void mpreplacetex(int oldtex, int newtex, selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_REPLACE, oldtex, newtex);
+    if(local) cl->edittrigger(sel, EDIT_REPLACE, oldtex, newtex);
     loopi(8) replacetexcube(worldroot[i], oldtex, newtex);
     allchanged();
 }
@@ -1539,7 +1532,7 @@ void rotatecube(cube &c, int d)   // rotates cube clockwise. see pics in cvs for
 
 void mpflip(selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_FLIP);
+    if(local) cl->edittrigger(sel, EDIT_FLIP);
     int zs = sel.s[dimension(sel.orient)];
     makeundo();
     loopxy(sel)
@@ -1563,7 +1556,7 @@ void flip()
 
 void mprotate(int cw, selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_ROTATE, cw);
+    if(local) cl->edittrigger(sel, EDIT_ROTATE, cw);
     int d = dimension(sel.orient);
     if(!dimcoord(sel.orient)) cw = -cw;
     int m = sel.s[C[d]] < sel.s[R[d]] ? C[d] : R[d];
@@ -1592,66 +1585,44 @@ void rotate(int *cw)
 COMMAND(flip, "");
 COMMAND(rotate, "i");
 
-void setmat(cube &c, uchar mat, uchar matmask, uchar filtermat, uchar filtermask)
+void setmat(cube &c, uchar mat, uchar matmask)
 {
     if(c.children)
-        loopi(8) setmat(c.children[i], mat, matmask, filtermat, filtermask);
-    else if(((c.ext ? c.ext->material : MAT_AIR)&filtermask) == filtermat)
+        loopi(8) setmat(c.children[i], mat, matmask);
+    else if(mat!=MAT_AIR) 
     {
-        if(mat!=MAT_AIR) 
-        {
-            cubeext &e = ext(c);
-            e.material &= matmask;
-            e.material |= mat;
-        }
-        else if(c.ext) c.ext->material = MAT_AIR;
+        cubeext &e = ext(c);
+        e.material &= matmask;
+        e.material |= mat;
     }
+    else if(c.ext) c.ext->material = MAT_AIR;
 }
 
-void mpeditmat(int matid, int filter, selinfo &sel, bool local)
+void mpeditmat(int matid, selinfo &sel, bool local)
 {
-    if(local) game::edittrigger(sel, EDIT_MAT, matid, filter);
+    if(local) cl->edittrigger(sel, EDIT_MAT, matid);
 
-    uchar matmask = matid&MATF_VOLUME ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : ~matid), 
-          filtermat = filter < 0 ? 0 : filter,
-          filtermask = filter < 0 ? 0 : (filter&MATF_VOLUME ? MATF_VOLUME : (filter&MATF_CLIP ? MATF_CLIP : filter));
+    uchar matmask = matid&MATF_VOLUME ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : 0xFF);
     if(isclipped(matid&MATF_VOLUME)) matid |= MAT_CLIP;
     if(isdeadly(matid&MATF_VOLUME)) matid |= MAT_DEATH;
-    if(matid < 0 && filter >= 0)
-    {
-        matid = 0;
-        matmask = filtermask;
-        if(isclipped(filter&MATF_VOLUME)) matmask &= ~MATF_CLIP; 
-        if(isdeadly(filter&MATF_VOLUME)) matmask &= ~MAT_DEATH; 
-    }
-    loopselxyz(setmat(c, matid, matmask, filtermat, filtermask));
+    loopselxyz(setmat(c, matid, matmask));
 }
 
-void editmat(char *name, char *filtername)
+void editmat(char *name)
 {
     if(noedit()) return;
-    int filter = -1;
-    if(filtername[0])
-    {
-        filter = findmaterial(filtername);
-        if(filter < 0) { conoutf(CON_ERROR, "unknown material \"%s\"", filtername); return; }
-    }
-    int id = -1;
-    if(name[0] || filter < 0)
-    {
-        id = findmaterial(name);
-        if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; }
-    }
-    mpeditmat(id, filter, sel, true);
+    int id = findmaterial(name);
+    if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; }
+    mpeditmat(id, sel, true);
 }
 
-COMMAND(editmat, "ss");
+COMMAND(editmat, "s");
 
-#define TEXGUI_WIDTH 10
-#define TEXGUI_HEIGHT 7
+#define TEXTURE_WIDTH 10
+#define TEXTURE_HEIGHT 7
 extern int menudistance, menuautoclose;
 
-VARP(thumbtime, 0, 50, 1000);
+VAR(thumbtime, 0, 50, 1000);
 
 static int lastthumbnail = 0;
 
@@ -1661,42 +1632,36 @@ struct texturegui : g3d_callback
 {
     bool menuon;
     vec menupos;
-    int menustart, menutab;
-   
-    texturegui() : menustart(-1) {} 
-
+    int menustart;
+    
     void gui(g3d_gui &g, bool firstpass)
     {
-        int origtab = menutab, numtabs = max((curtexnum + TEXGUI_WIDTH*TEXGUI_HEIGHT - 1)/(TEXGUI_WIDTH*TEXGUI_HEIGHT), 1);
+        int menutab = 1+curtexindex/(TEXTURE_WIDTH*TEXTURE_HEIGHT);        
+        int origtab = menutab;
         g.start(menustart, 0.04f, &menutab);
-        loopi(numtabs)
+        loopi(1+curtexnum/(TEXTURE_WIDTH*TEXTURE_HEIGHT))
         {   
-            g.tab(!i ? "Textures" : NULL, 0xAAFFAA);
-            if(i+1 != origtab) continue; //don't load textures on non-visible tabs!
-            loopj(TEXGUI_HEIGHT) 
+            g.tab((i==0)?"Textures":NULL, 0xAAFFAA);
+            if(i != origtab-1) continue; //don't load textures on non-visible tabs!
+            loopj(TEXTURE_HEIGHT) 
             {
                 g.pushlist();
-                loopk(TEXGUI_WIDTH) 
+                loopk(TEXTURE_WIDTH) 
                 {
-                    int ti = (i*TEXGUI_HEIGHT+j)*TEXGUI_WIDTH+k;
+                    int ti = (i*TEXTURE_HEIGHT+j)*TEXTURE_WIDTH+k;
                     if(ti<curtexnum) 
                     {
-                        Texture *tex = notexture, *glowtex = NULL, *layertex = NULL;
-                        Slot &slot = lookuptexture(ti, false);
+                        Texture *tex = notexture, *glowtex = NULL;
+                        Slot &slot = lookuptexture(texmru[ti], false);
                         if(slot.sts.empty()) continue;
                         else if(slot.loaded) 
                         {
                             tex = slot.sts[0].t;
                             if(slot.texmask&(1<<TEX_GLOW)) { loopv(slot.sts) if(slot.sts[i].type==TEX_GLOW) { glowtex = slot.sts[i].t; break; } }
-                            if(slot.layer)
-                            {
-                                Slot &layer = lookuptexture(slot.layer);
-                                if(!layer.sts.empty()) layertex = layer.sts[0].t;
-                            }
                         }
                         else if(slot.thumbnail) tex = slot.thumbnail;
-                        else if(totalmillis-lastthumbnail>=thumbtime) { tex = loadthumbnail(slot); lastthumbnail = totalmillis; }
-                        if(g.texture(tex, 1.0, slot.rotation, slot.xoffset, slot.yoffset, glowtex, slot.glowcolor, layertex)&G3D_UP && (slot.loaded || tex!=notexture)) 
+                        else if(lastmillis-lastthumbnail>=thumbtime) { tex = loadthumbnail(slot); lastthumbnail = lastmillis; }
+                        if(g.texture(tex, 1.0, slot.rotation, slot.xoffset, slot.yoffset, glowtex, slot.glowcolor)&G3D_UP && (slot.loaded || tex!=notexture)) 
                             edittex(ti);
                     }
                     else
@@ -1706,24 +1671,19 @@ struct texturegui : g3d_callback
             }
         }
         g.end();
+        if(origtab != menutab) curtexindex = (menutab-1)*TEXTURE_WIDTH*TEXTURE_HEIGHT;
     }
 
     void showtextures(bool on)
     {
-        if(on != menuon && (menuon = on)) 
-        { 
-            if(menustart <= lasttexmillis) menutab = 1+clamp(lasttex, 0, curtexnum-1)/(TEXGUI_WIDTH*TEXGUI_HEIGHT);
-            menupos = menuinfrontofplayer(); 
-            menustart = starttime(); 
-        }
+        if(on != menuon && (menuon = on)) { menupos = menuinfrontofplayer(); menustart = starttime(); }
     }
 
     void show()
     {   
         if(!menuon) return;
         filltexlist();
-        extern int usegui2d;
-        if(!editmode || ((!texgui2d || !usegui2d) && camera1->o.dist(menupos) > menuautoclose)) menuon = false;
+        if(!editmode || camera1->o.dist(menupos) > menuautoclose) menuon = false;
         else g3d_addgui(this, menupos, texgui2d ? GUI_2D : 0);
     }
 } gui;
@@ -1742,14 +1702,14 @@ void showtexgui(int *n)
 // 0/noargs = toggle, 1 = on, other = off - will autoclose if too far away or exit editmode
 COMMAND(showtexgui, "i");
 
-void rendertexturepanel(int w, int h)
+void render_texture_panel(int w, int h)
 {
     if((texpaneltimer -= curtime)>0 && editmode)
     {
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
-        glPushMatrix();
-        glScalef(h/1800.0f, h/1800.0f, 1);
+        glLoadIdentity();
+        int width = w*1800/h;
+        glOrtho(0, width, 1800, 0, -1, 1);
         int y = 50, gap = 10;
 
         static Shader *rgbonlyshader = NULL;
@@ -1763,18 +1723,13 @@ void rendertexturepanel(int w, int h)
             if(ti>=0 && ti<curtexnum)
             {
                 Slot &slot = lookuptexture(texmru[ti]);
-                Texture *tex = slot.sts[0].t, *glowtex = NULL, *layertex = NULL;
+                Texture *tex = slot.sts[0].t, *glowtex = NULL;
                 if(slot.texmask&(1<<TEX_GLOW))
                 {
                     loopvj(slot.sts) if(slot.sts[j].type==TEX_GLOW) { glowtex = slot.sts[j].t; break; }
                 }
-                if(slot.layer)
-                {
-                    Slot &layer = lookuptexture(slot.layer);
-                    layertex = layer.sts[0].t;
-                }
                 float sx = min(1.0f, tex->xs/(float)tex->ys), sy = min(1.0f, tex->ys/(float)tex->xs);
-                int x = w*1800/h-s-50, r = s;
+                int x = width-s-50, r = s;
                 float tc[4][2] = { { 0, 0 }, { 1, 0 }, { 1, 1 }, { 0, 1 } };
                 float xoff = slot.xoffset, yoff = slot.yoffset;
                 if(slot.rotation)
@@ -1801,17 +1756,6 @@ void rendertexturepanel(int w, int h)
                     glTexCoord2fv(tc[3]); glVertex2f(x,   y+r);
                     glEnd();
                     xtraverts += 4;
-                    if(j==1 && layertex)
-                    {
-                        glBindTexture(GL_TEXTURE_2D, layertex->id);
-                        glBegin(GL_QUADS);
-                        glTexCoord2fv(tc[0]); glVertex2f(x+r/2, y+r/2);
-                        glTexCoord2fv(tc[1]); glVertex2f(x+r,   y+r/2);
-                        glTexCoord2fv(tc[2]); glVertex2f(x+r,   y+r);
-                        glTexCoord2fv(tc[3]); glVertex2f(x+r/2, y+r);
-                        glEnd();
-                        xtraverts += 4;
-                    }
                     if(!j)
                     {
                         r -= 10;
@@ -1825,7 +1769,5 @@ void rendertexturepanel(int w, int h)
         }
 
         defaultshader->set();
-
-        glPopMatrix();
     }
 }
diff --git a/engine/octarender.cpp b/engine/octarender.cpp
index 1aea98f..7d127c0 100644
--- a/engine/octarender.cpp
+++ b/engine/octarender.cpp
@@ -1,5 +1,6 @@
 // octarender.cpp: fill vertex arrays with different cube surfaces.
 
+#include "pch.h"
 #include "engine.h"
 
 VARF(floatvtx, 0, 0, 1, allchanged());
@@ -232,14 +233,13 @@ struct verthash
 
 struct sortkey
 {
-     ushort tex, lmid, layer, envmap;
-
+     ushort tex, lmid, envmap;
      sortkey() {}
-     sortkey(ushort tex, uchar lmid, uchar layer = LAYER_TOP, ushort envmap = EMID_NONE)
-      : tex(tex), lmid(lmid), layer(layer), envmap(envmap)
+     sortkey(ushort tex, ushort lmid, ushort envmap = EMID_NONE)
+      : tex(tex), lmid(lmid), envmap(envmap)
      {}
 
-     bool operator==(const sortkey &o) const { return tex==o.tex && lmid==o.lmid && layer==o.layer && envmap==o.envmap; }
+     bool operator==(const sortkey &o) const { return tex==o.tex && lmid==o.lmid && envmap==o.envmap; }
 };
 
 struct sortval
@@ -262,23 +262,20 @@ static inline uint hthash(const sortkey &k)
 
 struct vacollect : verthash
 {
-    ivec origin;
-    int size;
     hashtable<sortkey, sortval> indices;
     vector<sortkey> texs;
     vector<grasstri> grasstris;
     vector<materialsurface> matsurfs;
     vector<octaentities *> mapmodels;
     usvector skyindices, explicitskyindices;
-    int worldtris, skytris, skyfaces, skyclip, skyarea;
+    int curtris, skyfaces, skyclip;
 
     void clear()
     {
         clearverts();
-        worldtris = skytris = 0;
+        curtris = 0;
         skyfaces = 0;
         skyclip = INT_MAX;
-        skyarea = 0;
         indices.clear();
         skyindices.setsizenodelete(0);
         explicitskyindices.setsizenodelete(0);
@@ -290,17 +287,15 @@ struct vacollect : verthash
 
     void remapunlit(vector<sortkey> &remap)
     {
-        uint lastlmid[4] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT }, 
-             firstlmid[4] = { LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT, LMID_AMBIENT };
-        int firstlit[4] = { -1, -1, -1, -1 };
+        uint lastlmid[2] = { LMID_AMBIENT, LMID_AMBIENT }, firstlmid[2] = { LMID_AMBIENT, LMID_AMBIENT };
+        int firstlit[2] = { -1, -1 };
         loopv(texs)
         {
             sortkey &k = texs[i];
             if(k.lmid>=LMID_RESERVED) 
             {
                 LightMapTexture &lmtex = lightmaptexs[k.lmid];
-                int type = lmtex.type&LM_TYPE;
-                if(k.layer&LAYER_BLEND) type += 2;
+                int type = lmtex.type;
                 lastlmid[type] = lmtex.unlitx>=0 ? k.lmid : LMID_AMBIENT;
                 if(firstlmid[type]==LMID_AMBIENT && lastlmid[type]!=LMID_AMBIENT)
                 {
@@ -311,8 +306,8 @@ struct vacollect : verthash
             else if(k.lmid==LMID_AMBIENT)
             {
                 Shader *s = lookuptexture(k.tex, false).shader;
+                if(!s) continue;
                 int type = s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE;
-                if(k.layer&LAYER_BLEND) type += 2;
                 if(lastlmid[type]!=LMID_AMBIENT)
                 {
                     sortval &t = indices[k];
@@ -320,21 +315,16 @@ struct vacollect : verthash
                 }
             }
         }
-        loopj(2)
+        if(firstlmid[0]!=LMID_AMBIENT || firstlmid[1]!=LMID_AMBIENT) loopi(max(firstlit[0], firstlit[1]))
         {
-            int offset = 2*j;
-            if(firstlmid[offset]==LMID_AMBIENT && firstlmid[offset+1]==LMID_AMBIENT) continue;
-            loopi(max(firstlit[offset], firstlit[offset+1]))
-            {
-                sortkey &k = texs[i];
-                if(j ? !(k.layer&LAYER_BLEND) : k.layer&LAYER_BLEND) continue;
-                if(k.lmid!=LMID_AMBIENT) continue;
-                Shader *s = lookuptexture(k.tex, false).shader;
-                int type = offset + (s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE);
-                if(firstlmid[type]==LMID_AMBIENT) continue;
-                indices[k].unlit = firstlmid[type];
-            }
-        }  
+            sortkey &k = texs[i];
+            if(k.lmid!=LMID_AMBIENT) continue;
+            Shader *s = lookuptexture(k.tex, false).shader;
+            if(!s) continue;
+            int type = s->type&SHADER_NORMALSLMS ? LM_BUMPMAP0 : LM_DIFFUSE;
+            if(firstlmid[type]==LMID_AMBIENT) continue;
+            indices[k].unlit = firstlmid[type];
+        } 
         loopv(remap)
         {
             sortkey &k = remap[i];
@@ -359,7 +349,7 @@ struct vacollect : verthash
                     t.dims[l][j] = addvert(vv, u, v, n);
                 }
             }
-            sortval *dst = indices.access(sortkey(k.tex, t.unlit, k.layer, k.envmap));
+            sortval *dst = indices.access(sortkey(k.tex, t.unlit, k.envmap));
             if(dst) loopl(6) loopvj(t.dims[l]) dst->dims[l].add(t.dims[l][j]);
         }
     }
@@ -372,7 +362,7 @@ struct vacollect : verthash
             {
                 if(k.lmid>=LMID_RESERVED && lightmaptexs[k.lmid].unlitx>=0)
                 {
-                    sortkey ukey(k.tex, LMID_AMBIENT, k.layer, k.envmap);
+                    sortkey ukey(k.tex, LMID_AMBIENT, k.envmap);
                     sortval *uval = indices.access(ukey);
                     if(uval && uval->unlit<=0)
                     {
@@ -399,8 +389,6 @@ struct vacollect : verthash
 
     static int texsort(const sortkey *x, const sortkey *y)
     {
-        if(x->layer < y->layer) return -1;
-        if(x->layer > y->layer) return 1;
         if(x->tex == y->tex) 
         {
             if(x->lmid < y->lmid) return -1;
@@ -424,7 +412,7 @@ struct vacollect : verthash
     void setupdata(vtxarray *va)
     {
         va->verts = verts.length();
-        va->tris = worldtris/3;
+        va->tris = curtris;
         va->vbuf = 0;
         va->vdata = 0;
         va->minvert = 0;
@@ -439,8 +427,8 @@ struct vacollect : verthash
                     flushvbo();
             }
             if(vbosize[VBO_VBUF] + verts.length() > maxvbosize || 
-               vbosize[VBO_EBUF] + worldtris > USHRT_MAX ||
-               vbosize[VBO_SKYBUF] + skytris > USHRT_MAX) 
+               vbosize[VBO_EBUF] + 3*curtris > USHRT_MAX ||
+               vbosize[VBO_SKYBUF] + skyindices.length() + explicitskyindices.length() > USHRT_MAX) 
                 flushvbo();
 
             va->voffset = vbosize[VBO_VBUF];
@@ -462,7 +450,7 @@ struct vacollect : verthash
         va->skydata = 0;
         va->sky = skyindices.length();
         va->explicitsky = explicitskyindices.length();
-        if(va->sky + va->explicitsky)
+        if(va->sky+va->explicitsky)
         {
             va->skydata += vbosize[VBO_SKYBUF];
             ushort *skydata = (ushort *)addvbo(va, VBO_SKYBUF, va->sky+va->explicitsky, sizeof(ushort));
@@ -473,15 +461,13 @@ struct vacollect : verthash
 
         va->eslist = NULL;
         va->texs = texs.length();
-        va->blends = 0;
         va->ebuf = 0;
         va->edata = 0;
         if(va->texs)
         {
             va->eslist = new elementset[va->texs];
             va->edata += vbosize[VBO_EBUF];
-            ushort *edata = (ushort *)addvbo(va, VBO_EBUF, worldtris, sizeof(ushort)), *curbuf = edata;
-            while(va->texs && texs[va->texs-1].layer&LAYER_BLEND) { va->texs--; va->blends++; }
+            ushort *edata = (ushort *)addvbo(va, VBO_EBUF, 3*curtris, sizeof(ushort)), *curbuf = edata;
             loopv(texs)
             {
                 const sortkey &k = texs[i];
@@ -489,7 +475,6 @@ struct vacollect : verthash
                 elementset &e = va->eslist[i];
                 e.texture = k.tex;
                 e.lmid = t.unlit>0 ? t.unlit : k.lmid;
-                e.layer = k.layer;
                 e.envmap = k.envmap;
                 ushort *startbuf = curbuf;
                 loopl(6) 
@@ -512,12 +497,11 @@ struct vacollect : verthash
                     }
                     e.length[l] = curbuf-startbuf;
                 }
-                if(k.layer&LAYER_BLEND) va->tris -= e.length[5]/3;
             }
         }
 
         va->texmask = 0;
-        loopi(va->texs+va->blends)
+        loopi(va->texs)
         {
             Slot &slot = lookuptexture(va->eslist[i].texture, false);
             loopvj(slot.sts) va->texmask |= 1<<slot.sts[j].type;
@@ -527,7 +511,6 @@ struct vacollect : verthash
         {
             va->grasstris = new vector<grasstri>;
             va->grasstris->move(grasstris);
-            useshaderbyname("grass");
         }
 
         if(mapmodels.length()) va->mapmodels = new vector<octaentities *>(mapmodels);
@@ -539,8 +522,11 @@ struct vacollect : verthash
     }            
 } vc;
 
+VARF(lodsize, 0, 32, 128, hdr.mapwlod = lodsize);
+VAR(loddistance, 0, 2000, 100000);
+
 int recalcprogress = 0;
-#define progress(s)     if((recalcprogress++&0xFFF)==0) renderprogress(recalcprogress/(float)allocnodes, s);
+#define progress(s)     if((recalcprogress++&0x7FF)==0) show_out_of_renderloop_progress(recalcprogress/(float)allocnodes, s);
 
 vector<tjoint> tjoints;
 
@@ -599,12 +585,53 @@ struct texcoords
     short u, v;
 };
 
-void addtris(const sortkey &key, int orient, vvec *vv, surfacenormals *normals, texcoords tc[4], int index[4], int shadowmask, int tj)
+void addcubeverts(int orient, int size, vvec *vv, ushort texture, surfaceinfo *surface, surfacenormals *normals, int tj = -1, ushort envmap = EMID_NONE)
 {
-    int dim = dimension(orient), &total = key.tex==DEFAULT_SKY ? vc.skytris : vc.worldtris;
+    int index[4];
+    int shadowmask = texture==DEFAULT_SKY || renderpath==R_FIXEDFUNCTION ? 0 : calcshadowmask(vv);
+    LightMap *lm = NULL;
+    LightMapTexture *lmtex = NULL;
+    if(!nolights && surface && lightmaps.inrange(surface->lmid-LMID_RESERVED))
+    {
+        lm = &lightmaps[surface->lmid-LMID_RESERVED];
+        if(lm->type==LM_DIFFUSE || 
+            (lm->type==LM_BUMPMAP0 && 
+                lightmaps.inrange(surface->lmid+1-LMID_RESERVED) && 
+                lightmaps[surface->lmid+1-LMID_RESERVED].type==LM_BUMPMAP1))
+            lmtex = &lightmaptexs[lm->tex];
+        else lm = NULL;
+    }
+    texcoords tc[4];
+    loopk(4)
+    {
+        if(lmtex)
+        {
+            tc[k].u = short(ceilf((lm->offsetx + surface->x + (surface->texcoords[k*2] / 255.0f) * (surface->w - 1) + 0.5f) * SHRT_MAX/lmtex->w));
+            tc[k].v = short(ceilf((lm->offsety + surface->y + (surface->texcoords[k*2 + 1] / 255.0f) * (surface->h - 1) + 0.5f) * SHRT_MAX/lmtex->h));
+        }
+        else tc[k].u = tc[k].v = 0;
+        index[k] = vc.addvert(vv[k], tc[k].u, tc[k].v, renderpath!=R_FIXEDFUNCTION && normals ? normals->normals[k] : bvec(128, 128, 128));
+        if(index[k] < 0) return;
+    }
+
+    if(texture == DEFAULT_SKY)
+    {
+        loopk(4) vc.skyclip = min(vc.skyclip, int(vv[k].z>>VVEC_FRAC));
+        vc.skyfaces |= 0x3F&~(1<<orient);
+    }
+
+    int lmid = LMID_AMBIENT;
+    if(surface)
+    {
+        if(surface->lmid < LMID_RESERVED) lmid = surface->lmid;
+        else if(lm) lmid = lm->tex;
+    }
+
+    int dim = dimension(orient);
+    sortkey key(texture, lmid, envmap);
     loopi(2) if(index[0]!=index[i+1] && index[i+1]!=index[i+2] && index[i+2]!=index[0])
     {
-        usvector &idxs = key.tex==DEFAULT_SKY ? vc.explicitskyindices : vc.indices[key].dims[2*dim + ((shadowmask>>i)&1)];
+        usvector &idxs = texture==DEFAULT_SKY ? vc.explicitskyindices : vc.indices[key].dims[2*dim + ((shadowmask>>i)&1)]; 
         int left = index[2*i], mid = index[2*i + 1], right = index[(2*i + 2)%4];
         loopj(2)
         {
@@ -650,142 +677,22 @@ void addtris(const sortkey &key, int orient, vvec *vv, surfacenormals *normals,
                 bvec nt;
                 loopk(3) nt[k] = uchar(n1[k] + (n2[k] - n1[k])*offset);
                 int nextindex = vc.addvert(vvt, ut, vt, nt);
-                if(nextindex < 0 || total + 3 > USHRT_MAX) return;
-                total += 3;
+                if(nextindex < 0) return;
+                if(idxs.length() + 3 > USHRT_MAX) return;
                 idxs.add(right);
                 idxs.add(left);
                 idxs.add(nextindex);
+                if(texture==DEFAULT_SKY) explicitsky++; else vc.curtris++;
                 tj = t.next;
                 left = nextindex;
             }
         }
 
-        if(total + 3 > USHRT_MAX) return;
-        total += 3;
+        if(idxs.length() + 3 > USHRT_MAX) return;
         idxs.add(right);
         idxs.add(left);
         idxs.add(mid);
-    }
-}
-
-void addgrasstri(int face, vvec *vv, int numv, ushort texture, ushort lmid, texcoords tc[4])
-{
-    grasstri &g = vc.grasstris.add();
-    int i1 = 2*face, i2 = i1+1, i3 = (i1+2)%4;
-    g.v[0] = vv[i1].tovec(vc.origin);
-    g.v[1] = vv[i2].tovec(vc.origin);
-    g.v[2] = vv[i3].tovec(vc.origin);
-    if(numv>3) g.v[3] = vv[3].tovec(vc.origin);
-    g.numv = numv;
-
-    g.surface.toplane(g.v[0], g.v[1], g.v[2]);
-    if(g.surface.z <= 0) { vc.grasstris.pop(); return; }
-
-    loopi(numv)
-    {
-        vec edir = g.v[(i+1)%numv];
-        edir.sub(g.v[i]);
-        g.e[i].cross(g.surface, edir).normalize();
-        g.e[i].offset = -g.e[i].dot(g.v[i]);
-    }
-
-    g.center = vec(0, 0, 0);
-    loopk(numv) g.center.add(g.v[k]);
-    g.center.div(numv);
-    g.radius = 0;
-    loopk(numv) g.radius = max(g.radius, g.v[k].dist(g.center));
-
-    vec area, bx, by;
-    area.cross(vec(g.v[1]).sub(g.v[0]), vec(g.v[2]).sub(g.v[0]));
-    float scale;
-    int px, py;
-
-    if(fabs(area.x) >= fabs(area.y) && fabs(area.x) >= fabs(area.z))
-        scale = 1/area.x, px = 1, py = 2;
-    else if(fabs(area.y) >= fabs(area.x) && fabs(area.y) >= fabs(area.z))
-        scale = -1/area.y, px = 0, py = 2;
-    else
-        scale = 1/area.z, px = 0, py = 1;
-
-    bx.x = (g.v[2][py] - g.v[0][py])*scale;
-    bx.y = (g.v[2][px] - g.v[0][px])*scale;
-    bx.z = bx.x*g.v[2][px] - bx.y*g.v[2][py];
-
-    by.x = (g.v[2][py] - g.v[1][py])*scale;
-    by.y = (g.v[2][px] - g.v[1][px])*scale;
-    by.z = by.x*g.v[1][px] - by.y*g.v[1][py] - 1;
-    by.sub(bx);
-
-    float tc1u = tc[i1].u/float(SHRT_MAX),
-          tc1v = tc[i1].v/float(SHRT_MAX),
-          tc2u = (tc[i2].u - tc[i1].u)/float(SHRT_MAX),
-          tc2v = (tc[i2].v - tc[i1].v)/float(SHRT_MAX),
-          tc3u = (tc[i3].u - tc[i1].u)/float(SHRT_MAX),
-          tc3v = (tc[i3].v - tc[i1].v)/float(SHRT_MAX);
-        
-    g.tcu = vec4(0, 0, 0, tc1u - (bx.z*tc2u + by.z*tc3u));
-    g.tcu[px] = bx.x*tc2u + by.x*tc3u;
-    g.tcu[py] = -(bx.y*tc2u + by.y*tc3u);
-
-    g.tcv = vec4(0, 0, 0, tc1v - (bx.z*tc2v + by.z*tc3v));
-    g.tcv[px] = bx.x*tc2v + by.x*tc3v;
-    g.tcv[py] = -(bx.y*tc2v + by.y*tc3v);
-
-    g.texture = texture;
-    g.lmid = lmid;
-}
- 
-void addcubeverts(int orient, int size, vvec *vv, ushort texture, surfaceinfo *surface, surfacenormals *normals, int tj = -1, ushort envmap = EMID_NONE, int grassy = 0)
-{
-    int index[4];
-    int shadowmask = texture==DEFAULT_SKY ? 0 : calcshadowmask(vv);
-    LightMap *lm = NULL;
-    LightMapTexture *lmtex = NULL;
-    if(!nolights && surface && lightmaps.inrange(surface->lmid-LMID_RESERVED))
-    {
-        lm = &lightmaps[surface->lmid-LMID_RESERVED];
-        if((lm->type&LM_TYPE)==LM_DIFFUSE ||
-            ((lm->type&LM_TYPE)==LM_BUMPMAP0 &&
-                lightmaps.inrange(surface->lmid+1-LMID_RESERVED) &&
-                (lightmaps[surface->lmid+1-LMID_RESERVED].type&LM_TYPE)==LM_BUMPMAP1))
-            lmtex = &lightmaptexs[lm->tex];
-        else lm = NULL;
-    }
-    texcoords tc[4];
-    loopk(4)
-    {
-        if(lmtex)
-        {
-            tc[k].u = short(ceilf((lm->offsetx + surface->x + (surface->texcoords[k*2] / 255.0f) * (surface->w - 1) + 0.5f) * SHRT_MAX/lmtex->w));
-            tc[k].v = short(ceilf((lm->offsety + surface->y + (surface->texcoords[k*2 + 1] / 255.0f) * (surface->h - 1) + 0.5f) * SHRT_MAX/lmtex->h));
-        }
-        else tc[k].u = tc[k].v = 0;
-        index[k] = vc.addvert(vv[k], tc[k].u, tc[k].v, renderpath!=R_FIXEDFUNCTION && normals ? normals->normals[k] : bvec(128, 128, 128));
-        if(index[k] < 0) return;
-    }
-
-    if(texture == DEFAULT_SKY)
-    {
-        loopk(4) vc.skyclip = min(vc.skyclip, int(vv[k].z>>VVEC_FRAC));
-        vc.skyfaces |= 0x3F&~(1<<orient);
-    }
-
-    int lmid = LMID_AMBIENT;
-    if(surface)
-    {
-        if(surface->lmid < LMID_RESERVED) lmid = surface->lmid;
-        else if(lm) lmid = lm->tex;
-    }
-
-    sortkey key(texture, lmid, surface ? surface->layer&LAYER_BLEND : LAYER_TOP, envmap);
-    addtris(key, orient, vv, normals, tc, index, shadowmask, tj);
-
-    if(grassy) 
-    {
-        int faces = 0;
-        loopi(2) if(index[0]!=index[i+1] && index[i+1]!=index[i+2] && index[i+2]!=index[0]) faces |= 1<<i;
-        if(grassy>1 && faces==3) addgrasstri(0, vv, 4, texture, lmid, tc);
-        else loopi(2) if(faces&(1<<i)) addgrasstri(i, vv, 3, texture, lmid, tc);
+        if(texture==DEFAULT_SKY) explicitsky++; else vc.curtris++;
     }
 }
 
@@ -827,8 +734,9 @@ hashtable<edgegroup, int> edgegroups(1<<13);
 void gencubeedges(cube &c, int x, int y, int z, int size)
 {
     ivec vv[4];
-    int mergeindex = 0, vis;
-    loopi(6) if((vis = visibletris(c, i, x, y, z, size)))
+    vvec vvcheck[4];
+    int mergeindex = 0;
+    loopi(6) if(visibleface(c, i, x, y, z, size)) 
     {
         if(c.ext && c.ext->merged&(1<<i))
         {
@@ -843,18 +751,14 @@ void gencubeedges(cube &c, int x, int y, int z, int size)
                 loopk(3) vv[j][k] = mv[j][k] + ((mo[k]&~VVEC_INT_MASK)<<VVEC_FRAC);
             }
         } 
-        else 
+        else loopj(4)
         {
-            int order = vis&4 || faceconvexity(c, i)<0 ? 1 : 0;
-            loopj(4)
-            {
-                int k = fv[i][(j+order)&3];
-                if(isentirelysolid(c)) vv[j] = cubecoords[k];
-                else genvectorvert(cubecoords[k], c, vv[j]);
-                vv[j].mul(size/(8>>VVEC_FRAC)).add(ivec(x, y, z).mul(1<<VVEC_FRAC));
-            }
-            if(!(vis&1)) vv[1] = vv[0];
-            if(!(vis&2)) vv[3] = vv[0];
+            int k = faceverts(c, i, j);
+            extern void genvectorvert(const ivec &p, cube &c, ivec &v);
+            if(isentirelysolid(c)) vv[j] = cubecoords[k];
+            else genvectorvert(cubecoords[k], c, vv[j]);
+            vv[j].mul(size/(8>>VVEC_FRAC)).add(ivec(x, y, z).mul(1<<VVEC_FRAC));
+            calcvert(c, x, y, z, size, vvcheck[j], k);
         }
         loopj(4)
         {
@@ -928,7 +832,7 @@ void gencubeedges(cube &c, int x, int y, int z, int size)
     }
 }
 
-void gencubeedges(cube *c = worldroot, int x = 0, int y = 0, int z = 0, int size = worldsize>>1)
+void gencubeedges(cube *c = worldroot, int x = 0, int y = 0, int z = 0, int size = hdr.worldsize>>1)
 {
     progress("fixing t-joints...");
     loopi(8)
@@ -943,10 +847,9 @@ void gencubeedges(cube *c = worldroot, int x = 0, int y = 0, int z = 0, int size
 void gencubeverts(cube &c, int x, int y, int z, int size, int csi, uchar &vismask, uchar &clipmask)
 {
     freeclipplanes(c);                          // physics planes based on rendering
-    if(c.ext) c.ext->visible = 0;
 
-    int tj = c.ext ? c.ext->tjoints : -1, numblends = 0, vis;
-    loopi(6) if((vis = visibletris(c, i, x, y, z, size)))
+    int tj = c.ext ? c.ext->tjoints : -1;
+    loopi(6) if(visibleface(c, i, x, y, z, size))
     {
         if(c.texture[i]!=DEFAULT_SKY) vismask |= 1<<i;
 
@@ -959,42 +862,25 @@ void gencubeverts(cube &c, int x, int y, int z, int size, int csi, uchar &vismas
             if(c.texture[i]!=DEFAULT_SKY && faceedges(c, i)==F_SOLID) clipmask |= 1<<i;
         }
 
-        if(e.surfaces && e.surfaces[i].layer&LAYER_BLEND) numblends++; 
-
         if(e.merged&(1<<i)) continue;
 
-        int order = vis&4 || faceconvexity(c, i)<0 ? 1 : 0;
         vvec vv[4];
-        loopk(4) calcvert(c, x, y, z, size, vv[k], fv[i][(k+order)&3]);
-        if(!(vis&1)) vv[1] = vv[0];
-        if(!(vis&2)) vv[3] = vv[0];
-
-        ushort envmap = EMID_NONE, envmap2 = EMID_NONE;
-        Slot &slot = lookuptexture(c.texture[i], false),
-             *layer = slot.layer ? &lookuptexture(slot.layer, false) : NULL;
-        if(slot.shader->type&SHADER_ENVMAP)
+        loopk(4) calcvert(c, x, y, z, size, vv[k], faceverts(c, i, k));
+        ushort envmap = EMID_NONE;
+        Slot &slot = lookuptexture(c.texture[i], false);
+        if(slot.shader && slot.shader->type&SHADER_ENVMAP)
         {
-            loopvj(slot.sts) if(slot.sts[j].type==TEX_ENVMAP) { envmap = EMID_CUSTOM; break; }
+            loopv(slot.sts) if(slot.sts[i].type==TEX_ENVMAP) { envmap = EMID_CUSTOM; break; }
             if(envmap==EMID_NONE) envmap = closestenvmap(i, x, y, z, size); 
         }
-        if(layer && layer->shader->type&SHADER_ENVMAP)
-        {
-            loopvj(layer->sts) if(layer->sts[j].type==TEX_ENVMAP) { envmap2 = EMID_CUSTOM; break; }
-            if(envmap2==EMID_NONE) envmap2 = closestenvmap(i, x, y, z, size); 
-        }
         while(tj >= 0 && tjoints[tj].edge < i*4) tj = tjoints[tj].next;
-        int hastj = tj >= 0 && tjoints[tj].edge/4 == i ? tj : -1;
-        int grassy = slot.autograss && i!=O_BOTTOM ? (vis!=3 || faceconvexity(c, i) ? 1 : 2) : 0;
-        if(e.surfaces && e.surfaces[i].layer&LAYER_BLEND)
-        {
-            addcubeverts(i, size, vv, c.texture[i], &e.surfaces[i], e.normals ? &e.normals[i] : NULL, hastj, envmap, grassy);
-            addcubeverts(i, size, vv, slot.layer, &e.surfaces[5+numblends], e.normals ? &e.normals[i] : NULL, hastj, envmap2);
-        }
-        else
+        addcubeverts(i, size, vv, c.texture[i], e.surfaces ? &e.surfaces[i] : NULL, e.normals ? &e.normals[i] : NULL, tj >= 0 && tjoints[tj].edge/4 == i ? tj : -1, envmap);
+        if(slot.autograss && i!=O_BOTTOM) 
         {
-            ushort tex = c.texture[i];
-            if(e.surfaces && e.surfaces[i].layer==LAYER_BOTTOM) { tex = slot.layer; envmap = envmap2; grassy = 0; }
-            addcubeverts(i, size, vv, tex, e.surfaces ? &e.surfaces[i] : NULL, e.normals ? &e.normals[i] : NULL, hastj, envmap, grassy);
+            grasstri &g = vc.grasstris.add();
+            memcpy(g.v, vv, sizeof(vv));
+            g.surface = e.surfaces ? &e.surfaces[i] : NULL;
+            g.texture = c.texture[i];
         }
     }
     else if(touchingface(c, i))
@@ -1016,11 +902,11 @@ int hasskyfaces(cube &c, int x, int y, int z, int size, int faces[6])
 {
     int numfaces = 0;
     if(x == 0 && !skyoccluded(c, O_LEFT)) faces[numfaces++] = O_LEFT;
-    if(x + size == worldsize && !skyoccluded(c, O_RIGHT)) faces[numfaces++] = O_RIGHT;
+    if(x + size == hdr.worldsize && !skyoccluded(c, O_RIGHT)) faces[numfaces++] = O_RIGHT;
     if(y == 0 && !skyoccluded(c, O_BACK)) faces[numfaces++] = O_BACK;
-    if(y + size == worldsize && !skyoccluded(c, O_FRONT)) faces[numfaces++] = O_FRONT;
+    if(y + size == hdr.worldsize && !skyoccluded(c, O_FRONT)) faces[numfaces++] = O_FRONT;
     if(z == 0 && !skyoccluded(c, O_BOTTOM)) faces[numfaces++] = O_BOTTOM;
-    if(z + size == worldsize && !skyoccluded(c, O_TOP)) faces[numfaces++] = O_TOP;
+    if(z + size == hdr.worldsize && !skyoccluded(c, O_TOP)) faces[numfaces++] = O_TOP;
     return numfaces;
 }
 
@@ -1059,7 +945,7 @@ void genskyfaces(cube &c, const ivec &o, int size)
         m.v2 = ((o[R[dim]]&VVEC_INT_MASK)+size)<<VVEC_FRAC;
         minskyface(c, orient, o, size, m);
         if(m.u1 >= m.u2 || m.v1 >= m.v2) continue;
-        vc.skyarea += (int(m.u2-m.u1)*int(m.v2-m.v1) + (1<<(2*VVEC_FRAC))-1)>>(2*VVEC_FRAC);
+        skyarea += (int(m.u2-m.u1)*int(m.v2-m.v1) + (1<<(2*VVEC_FRAC))-1)>>(2*VVEC_FRAC);
         skyfaces[orient].add(m);
     }
 }
@@ -1089,8 +975,7 @@ void addskyverts(const ivec &o, int size)
                 if(index[k] < 0) goto nextskyface;
                 vc.skyclip = min(vc.skyclip, int(vv.z>>VVEC_FRAC));
             }
-            if(vc.skytris + 6 > USHRT_MAX) break;
-            vc.skytris += 6;
+            if(vc.skyindices.length() + 6 > USHRT_MAX) break;
             vc.skyindices.add(index[0]);
             vc.skyindices.add(index[1]);
             vc.skyindices.add(index[2]);
@@ -1118,7 +1003,8 @@ vtxarray *newva(int x, int y, int z, int size)
     va->parent = NULL;
     va->o = ivec(x, y, z);
     va->size = size;
-    va->skyarea = vc.skyarea;
+    va->explicitsky = explicitsky;
+    va->skyarea = skyarea;
     va->skyfaces = vc.skyfaces;
     va->skyclip = vc.skyclip < INT_MAX ? vc.skyclip + (z&~VVEC_INT_MASK) : INT_MAX;
     va->curvfc = VFC_NOT_VISIBLE;
@@ -1128,6 +1014,7 @@ vtxarray *newva(int x, int y, int z, int size)
     va->bbmin = ivec(-1, -1, -1);
     va->bbmax = ivec(-1, -1, -1);
     va->grasstris = NULL;
+    va->grasssamples = NULL;
     va->hasmerges = 0;
 
     vc.setupdata(va);
@@ -1164,10 +1051,11 @@ void destroyva(vtxarray *va, bool reparent)
     if(va->matbuf) delete[] va->matbuf;
     if(va->mapmodels) delete va->mapmodels;
     if(va->grasstris) delete va->grasstris;
+    if(va->grasssamples) delete va->grasssamples;
     delete[] (uchar *)va;
 }
 
-void clearvas(cube *c)
+void vaclearc(cube *c)
 {
     loopi(8)
     {
@@ -1177,7 +1065,7 @@ void clearvas(cube *c)
             c[i].ext->va = NULL;
             c[i].ext->tjoints = -1;
         }
-        if(c[i].children) clearvas(c[i].children);
+        if(c[i].children) vaclearc(c[i].children);
     }
 }
 
@@ -1254,17 +1142,21 @@ static vector<mergedface> vamerges[VVEC_INT];
 void genmergedfaces(cube &c, const ivec &co, int size, int minlevel = -1)
 {
     if(!c.ext || !c.ext->merges || isempty(c)) return;
-    int index = 0, tj = c.ext->tjoints, numblends = 0;
-    loopi(6) 
+    int index = 0, tj = c.ext->tjoints;
+    loopi(6) if(c.ext->mergeorigin & (1<<i))
     {
-        if(c.ext->surfaces && c.ext->surfaces[i].layer&LAYER_BLEND) numblends++;
-        if(!(c.ext->mergeorigin & (1<<i))) continue;
         mergeinfo &m = c.ext->merges[index++];
         if(m.u1>=m.u2 || m.v1>=m.v2) continue;
         mergedface mf;
         mf.orient = i;
         mf.tex = c.texture[i];
         mf.envmap = EMID_NONE;
+        Slot &slot = lookuptexture(mf.tex, false);
+        if(slot.shader && slot.shader->type&SHADER_ENVMAP)
+        {
+            loopv(slot.sts) if(slot.sts[i].type==TEX_ENVMAP) { mf.envmap = EMID_CUSTOM; break; }
+            if(mf.envmap==EMID_NONE) mf.envmap = closestenvmap(i, co.x, co.y, co.z, size);
+        }
         mf.surface = c.ext->surfaces ? &c.ext->surfaces[i] : NULL;
         mf.normals = c.ext->normals ? &c.ext->normals[i] : NULL;
         mf.tjoints = -1;
@@ -1275,37 +1167,6 @@ void genmergedfaces(cube &c, const ivec &co, int size, int minlevel = -1)
             while(tj >= 0 && tjoints[tj].edge < i*4) tj = tjoints[tj].next;
             if(tj >= 0 && tjoints[tj].edge/4 == i) mf.tjoints = tj;
 
-            Slot &slot = lookuptexture(mf.tex, false),
-                 *layer = slot.layer ? &lookuptexture(slot.layer, false) : NULL;
-            ushort envmap2 = EMID_NONE;
-            if(slot.shader->type&SHADER_ENVMAP)
-            {
-                loopvj(slot.sts) if(slot.sts[j].type==TEX_ENVMAP) { mf.envmap = EMID_CUSTOM; break; }
-                if(mf.envmap==EMID_NONE) mf.envmap = closestenvmap(i, co.x, co.y, co.z, size);
-            }
-            if(layer && layer->shader->type&SHADER_ENVMAP)
-            {
-                loopvj(layer->sts) if(layer->sts[j].type==TEX_ENVMAP) { envmap2 = EMID_CUSTOM; break; }
-                if(envmap2==EMID_NONE) envmap2 = closestenvmap(i, co.x, co.y, co.z, size);
-            }
-
-            if(c.ext->surfaces)
-            {
-                if(c.ext->surfaces[i].layer&LAYER_BLEND)
-                {
-                    mergedface mf2 = mf;
-                    mf2.tex = slot.layer;
-                    mf2.envmap = envmap2;
-                    mf2.surface = &c.ext->surfaces[5+numblends];
-                    vamerges[level].add(mf2);
-                }
-                else if(c.ext->surfaces[i].layer==LAYER_BOTTOM)
-                {
-                    mf.tex = slot.layer;
-                    mf.envmap = envmap2;
-                }
-            } 
-
             vamerges[level].add(mf);
             vamergemax = max(vamergemax, level);
             vahasmerges |= MERGE_ORIGIN;
@@ -1334,9 +1195,15 @@ void addmergedverts(int level)
     loopv(mfl)
     {
         mergedface &mf = mfl[i];
+        addcubeverts(mf.orient, 1<<level, mf.v, mf.tex, mf.surface, mf.normals, mf.tjoints, mf.envmap);
         Slot &slot = lookuptexture(mf.tex, false);
-        int grassy = slot.autograss && mf.orient!=O_BOTTOM && (!mf.surface || mf.surface->layer!=LAYER_BOTTOM) ? 2 : 0;
-        addcubeverts(mf.orient, 1<<level, mf.v, mf.tex, mf.surface, mf.normals, mf.tjoints, mf.envmap, grassy);
+        if(slot.autograss && mf.orient!=O_BOTTOM)
+        {
+            grasstri &g = vc.grasstris.add();
+            memcpy(g.v, mf.v, sizeof(mf.v));
+            g.surface = mf.surface;
+            g.texture = mf.tex;
+        }
         vahasmerges |= MERGE_USE;
     }
     mfl.setsizenodelete(0);
@@ -1452,9 +1319,8 @@ void setva(cube &c, int cx, int cy, int cz, int size, int csi)
 {
     ASSERT(size <= VVEC_INT_MASK+1);
 
-    vc.origin = ivec(cx, cy, cz);
-    vc.size = size;
-
+    explicitsky = 0;
+    skyarea = 0;
     shadowmapmin = vvec(cx+size, cy+size, cz+size);
     shadowmapmax = vvec(cx, cy, cz);
 
@@ -1479,6 +1345,8 @@ void setva(cube &c, int cx, int cy, int cz, int size, int csi)
         va->hasmerges = vahasmerges;
     }
 
+    explicitsky = 0;
+    skyarea = 0;
     vc.clear();
 }
 
@@ -1508,7 +1376,7 @@ int updateva(cube *c, int cx, int cy, int cz, int size, int csi)
             if(c[i].children) count += updateva(c[i].children, o.x, o.y, o.z, size/2, csi-1);
             else if(!isempty(c[i]) || hasskyfaces(c[i], o.x, o.y, o.z, size, faces)) count++;
             int tcount = count + (csi < VVEC_INT ? vamerges[csi].length() : 0);
-            if(tcount > vacubemax || (tcount >= vacubemin && size >= vacubesize) || size == min(VVEC_INT_MASK+1, worldsize/2)) 
+            if(tcount > vacubemax || (tcount >= vacubemin && size >= vacubesize) || size == min(VVEC_INT_MASK+1, hdr.worldsize/2)) 
             {
                 setva(c[i], o.x, o.y, o.z, size, csi);
                 if(c[i].ext && c[i].ext->va)
@@ -1636,11 +1504,11 @@ void findtjoints(int cur, const edgegroup &g)
 void octarender()                               // creates va s for all leaf cubes that don't already have them
 {
     int csi = 0;
-    while(1<<csi < worldsize) csi++;
+    while(1<<csi < hdr.worldsize) csi++;
 
     recalcprogress = 0;
     varoot.setsizenodelete(0);
-    updateva(worldroot, 0, 0, 0, worldsize/2, csi-1);
+    updateva(worldroot, 0, 0, 0, hdr.worldsize/2, csi-1);
     flushvbo();
 
     loopi(8) buildclipmasks(worldroot[i]);
@@ -1653,31 +1521,15 @@ void octarender()                               // creates va s for all leaf cub
         explicitsky += va->explicitsky;
         skyarea += va->skyarea;
     }
-
-    extern vtxarray *visibleva;
-    visibleva = NULL;
 }
 
-void precachetextures()
-{
-    vector<int> texs;
-    loopv(valist)
-    {
-        vtxarray *va = valist[i];
-        loopj(va->texs) if(texs.find(va->eslist[j].texture) < 0) texs.add(va->eslist[j].texture);
-    }
-    loopv(texs)
-    {
-        loadprogress = float(i+1)/texs.length();
-        lookuptexture(texs[i]);
-    }
-    loadprogress = 0;
-}
+void precachetextures(vtxarray *va) { loopi(va->texs) lookuptexture(va->eslist[i].texture); }
+void precacheall() { loopv(valist) precachetextures(valist[i]); }
 
 void allchanged(bool load)
 {
-    renderprogress(0, "clearing vertex arrays...");
-    clearvas(worldroot);
+    show_out_of_renderloop_progress(0, "clearing VBOs...");
+    vaclearc(worldroot);
     resetqueries();
     if(load) initenvmaps();
     guessshadowdir();
@@ -1692,16 +1544,11 @@ void allchanged(bool load)
         edgegroups.clear();
     }
     octarender();
-    if(load) precachetextures();
+    if(load) precacheall();
     setupmaterials();
     invalidatepostfx();
     updatevabbs(true);
-    resetblobs();
-    if(load) 
-    {
-        seedparticles();
-        genenvmaps();
-    }
+    if(load) genenvmaps();
 }
 
 void recalc()
diff --git a/engine/pch.cpp b/engine/pch.cpp
deleted file mode 100644
index c9536a2..0000000
--- a/engine/pch.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "engine.h"
-
diff --git a/engine/physics.cpp b/engine/physics.cpp
index 725a9b4..6c32804 100644
--- a/engine/physics.cpp
+++ b/engine/physics.cpp
@@ -3,6 +3,7 @@
 // they "felt right", and have no basis in reality. Collision detection is simplistic but
 // very robust (uses discrete steps at fixed fps).
 
+#include "pch.h"
 #include "engine.h"
 
 const int MAXCLIPPLANES = 1024;
@@ -138,7 +139,7 @@ static float disttoent(octaentities *oc, octaentities *last, const vec &o, const
     int orient;
     float dist = 1e16f, f = 0.0f;
     if(oc == last || oc == NULL) return dist;
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
 
     #define entintersect(mask, type, func) {\
         if((mode&(mask))==(mask)) \
@@ -149,8 +150,7 @@ static float disttoent(octaentities *oc, octaentities *last, const vec &o, const
                     extentity &e = *ents[oc->type[i]]; \
                     if(!e.inoctanode || &e==t) continue; \
                     func; \
-                    if(f<dist && f>0) \
-                    { \
+                    if(f<dist && f>0) { \
                         hitentdist = dist = f; \
                         hitent = oc->type[i]; \
                         hitorient = orient; \
@@ -178,34 +178,12 @@ static float disttoent(octaentities *oc, octaentities *last, const vec &o, const
     return dist;
 }
 
-static float disttooutsideent(const vec &o, const vec &ray, float radius, int mode, extentity *t)
-{
-    vec eo, es;
-    int orient;
-    float dist = 1e16f, f = 0.0f;
-    const vector<extentity *> &ents = entities::getents();
-    loopv(outsideents)
-    {
-        extentity &e = *ents[outsideents[i]];
-        if(!e.inoctanode || &e == t) continue;
-        entselectionbox(e, eo, es);
-        if(!rayrectintersect(eo, es, o, ray, f, orient)) continue;
-        if(f<dist && f>0) 
-        {
-            hitentdist = dist = f;
-            hitent = outsideents[i];
-            hitorient = orient;
-        }
-    }
-    return dist;
-}
-         
 // optimized shadow version
 static float shadowent(octaentities *oc, octaentities *last, const vec &o, const vec &ray, float radius, int mode, extentity *t)
 {
     float dist = 1e16f, f = 0.0f;
     if(oc == last || oc == NULL) return dist;
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(oc->mapmodels) if(!last || last->mapmodels.find(oc->mapmodels[i])<0)
     {
         extentity &e = *ents[oc->mapmodels[i]];
@@ -233,13 +211,13 @@ static float shadowent(octaentities *oc, octaentities *last, const vec &o, const
         loopi(3) \
         { \
             float c = v[i]; \
-            if(c<0 || c>=worldsize) \
+            if(c<0 || c>=hdr.worldsize) \
             { \
-                float d = ((invray[i]>0?0:worldsize)-c)*invray[i]; \
+                float d = ((invray[i]>0?0:hdr.worldsize)-c)*invray[i]; \
                 if(d<0) return (radius>0?radius:-1); \
                 disttoworld = max(disttoworld, 0.1f + d); \
             } \
-            float e = ((invray[i]>0?worldsize:0)-c)*invray[i]; \
+            float e = ((invray[i]>0?hdr.worldsize:0)-c)*invray[i]; \
             exitworld = min(exitworld, e); \
         } \
         if(disttoworld > exitworld) return (radius>0?radius:-1); \
@@ -281,7 +259,7 @@ static float shadowent(octaentities *oc, octaentities *last, const vec &o, const
         y = int(v.y); \
         z = int(v.z); \
         uint diff = uint(lo.x^x)|uint(lo.y^y)|uint(lo.z^z); \
-        if(diff >= uint(worldsize)) exitworld; \
+        if(diff >= uint(hdr.worldsize)) exitworld; \
         diff >>= lshift; \
         if(!diff) exitworld; \
         do \
@@ -365,26 +343,21 @@ float shadowray(const vec &o, const vec &ray, float radius, int mode, extentity
     }
 }
 
-float rayent(const vec &o, const vec &ray, float radius, int mode, int size, int &orient, int &ent)
+float rayent(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size, int &orient, int &ent)
 {
     hitent = -1;
-    hitentdist = radius;
-    float dist = raycube(o, ray, radius, mode, size);
-    if((mode&RAY_ENTS) == RAY_ENTS)
-    {
-        float dent = disttooutsideent(o, ray, dist < 0 ? 1e16f : dist, mode, NULL);
-        if(dent < 1e15f && (dist < 0 || dent < dist)) dist = dent; 
-    }
+    float d = raycubepos(o, ray, hitpos, hitentdist = radius, mode, size);
     orient = hitorient;
-    ent = hitentdist == dist ? hitent : -1;
-    return dist;
+    ent = (hitentdist == d) ? hitent : -1;
+    return d;
 }
 
 float raycubepos(const vec &o, const vec &ray, vec &hitpos, float radius, int mode, int size)
 {
     hitpos = ray;
     float dist = raycube(o, ray, radius, mode, size);
-    hitpos.mul(dist).add(o); 
+    hitpos.mul(dist); 
+    hitpos.add(o);
     return dist; 
 }
 
@@ -420,85 +393,28 @@ const float FLOORZ = 0.867f;
 const float SLOPEZ = 0.5f;
 const float WALLZ = 0.2f;
 const float JUMPVEL = 125.0f;
-extern const float GRAVITY = 200.0f;
-
-bool ellipserectcollide(physent *d, const vec &dir, const vec &o, const vec &center, float yaw, float xr, float yr, float hi, float lo)
-{
-    float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye),
-          above = (d->o.z-d->eyeheight) - (o.z+center.z+hi);
-    if(below>=0 || above>=0) return true;
-
-    vec yo(d->o);
-    yo.sub(o);
-    yo.rotate_around_z(-yaw*RAD);
-    yo.sub(center);
- 
-    float dx = clamp(yo.x, -xr, xr) - yo.x, dy = clamp(yo.y, -yr, yr) - yo.y,
-          dist = sqrtf(dx*dx + dy*dy) - d->radius;
-    if(dist < 0)
-    {
-        int sx = yo.x <= -xr ? -1 : (yo.x >= xr ? 1 : 0),
-            sy = yo.y <= -yr ? -1 : (yo.y >= yr ? 1 : 0);
-        if(dist > (yo.z < 0 ? below : above) && (sx || sy))
-        {
-            vec ydir(dir);
-            ydir.rotate_around_z(-yaw*RAD);
-            if(sx*yo.x - xr > sy*yo.y - yr)
-            {
-                if(dir.iszero() || sx*ydir.x < -1e-6f)
-                {
-                    wall = vec(sx, 0, 0);
-                    wall.rotate_around_z(yaw*RAD);
-                    return false;
-                }
-            }
-            else if(dir.iszero() || sy*ydir.y < -1e-6f)
-            { 
-                wall = vec(0, sy, 0);
-                wall.rotate_around_z(yaw*RAD);
-                return false;
-            }
-        }
-        if(yo.z < 0)
-        {
-            if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
-            {
-                wall = vec(0, 0, -1);
-                return false;
-            }
-        }
-        else if(dir.iszero() || (dir.z < 0 && (d->type>=ENT_INANIMATE || above >= d->zmargin-(d->eyeheight+d->aboveeye)/3.0f)))
-        {
-            wall = vec(0, 0, 1);
-            return false;
-        }
-        inside = true;
-    }
-    return true;
-}
+const float GRAVITY = 200.0f;
+const float STEPSPEED = 1.0f;
 
-bool ellipsecollide(physent *d, const vec &dir, const vec &o, const vec &center, float yaw, float xr, float yr, float hi, float lo)
+bool ellipsecollide(physent *d, const vec &dir, const vec &o, float yaw, float xr, float yr,  float hi, float lo)
 {
-    float below = (o.z+center.z-lo) - (d->o.z+d->aboveeye),
-          above = (d->o.z-d->eyeheight) - (o.z+center.z+hi);
+    float below = (o.z-lo) - (d->o.z+d->aboveeye),
+          above = (d->o.z-d->eyeheight) - (o.z+hi);
     if(below>=0 || above>=0) return true;
-    vec yo(center);
-    yo.rotate_around_z(yaw*RAD);
-    yo.add(o);
-    float x = yo.x - d->o.x, y = yo.y - d->o.y;
+    float x = o.x - d->o.x, y = o.y - d->o.y;
     float angle = atan2f(y, x), dangle = angle-(d->yaw+90)*RAD, eangle = angle-(yaw+90)*RAD;
     float dx = d->xradius*cosf(dangle), dy = d->yradius*sinf(dangle);
     float ex = xr*cosf(eangle), ey = yr*sinf(eangle);
     float dist = sqrtf(x*x + y*y) - sqrtf(dx*dx + dy*dy) - sqrtf(ex*ex + ey*ey);
     if(dist < 0)
     {
-        if(dist > (d->o.z < yo.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0))
+        if(dist > (d->o.z < o.z ? below : above) && (dir.iszero() || x*dir.x + y*dir.y > 0))
         {
             wall = vec(-x, -y, 0);
             if(!wall.iszero()) wall.normalize();
             return false;
         }
-        if(d->o.z < yo.z)
+        if(d->o.z < o.z)
         {
             if(dir.iszero() || (dir.z > 0 && (d->type>=ENT_INANIMATE || below >= d->zmargin-(d->eyeheight+d->aboveeye)/4.0f)))
             {
@@ -518,6 +434,7 @@ bool ellipsecollide(physent *d, const vec &dir, const vec &o, const vec &center,
 
 bool rectcollide(physent *d, const vec &dir, const vec &o, float xr, float yr,  float hi, float lo, uchar visible = 0xFF, bool collideonly = true, float cutoff = 0)
 {
+    if(collideonly && !visible) return true;
     vec s(d->o);
     s.sub(o);
     float dxr = d->collidetype==COLLIDE_ELLIPSE ? d->radius : d->xradius, dyr = d->collidetype==COLLIDE_ELLIPSE ? d->radius : d->yradius;
@@ -574,11 +491,11 @@ const vector<physent *> &checkdynentcache(int x, int y)
     dec.y = y;
     dec.frame = dynentframe;
     dec.dynents.setsize(0);
-    int numdyns = game::numdynents(), dsize = 1<<dynentsize, dx = x<<dynentsize, dy = y<<dynentsize;
+    int numdyns = cl->numdynents(), dsize = 1<<dynentsize, dx = x<<dynentsize, dy = y<<dynentsize;
     loopi(numdyns)
     {
-        dynent *d = game::iterdynents(i);
-        if(d->state != CS_ALIVE ||
+        dynent *d = cl->iterdynents(i);
+        if(!d || d->state != CS_ALIVE ||
            d->o.x+d->radius <= dx || d->o.x-d->radius >= dx+dsize ||
            d->o.y+d->radius <= dy || d->o.y-d->radius >= dy+dsize)
             continue;
@@ -588,8 +505,8 @@ const vector<physent *> &checkdynentcache(int x, int y)
 }
 
 #define loopdynentcache(curx, cury, o, radius) \
-    for(int curx = max(int(o.x-radius), 0)>>dynentsize, endx = min(int(o.x+radius), worldsize-1)>>dynentsize; curx <= endx; curx++) \
-    for(int cury = max(int(o.y-radius), 0)>>dynentsize, endy = min(int(o.y+radius), worldsize-1)>>dynentsize; cury <= endy; cury++)
+    for(int curx = max(int(o.x-radius), 0)>>dynentsize, endx = min(int(o.x+radius), hdr.worldsize-1)>>dynentsize; curx <= endx; curx++) \
+    for(int cury = max(int(o.y-radius), 0)>>dynentsize, endy = min(int(o.y+radius), hdr.worldsize-1)>>dynentsize; cury <= endy; cury++)
 
 void updatedynentcache(physent *d)
 {
@@ -634,7 +551,7 @@ bool plcollide(physent *d, const vec &dir)    // collide with player or monster
                     return false;
                 }
             }
-            else if(!ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight))
+            else if(!ellipsecollide(d, dir, o->o, o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight))
             {
                 hitplayer = o;
                 if((d->type==ENT_AI || d->type==ENT_INANIMATE) && wall.z>0) d->onplayer = o;
@@ -677,7 +594,7 @@ void rotatebb(vec &center, vec &radius, int yaw)
 
 bool mmcollide(physent *d, const vec &dir, octaentities &oc)               // collide with a mapmodel
 {   
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(oc.mapmodels)
     {
         extentity &e = *ents[oc.mapmodels[i]];
@@ -686,20 +603,12 @@ bool mmcollide(physent *d, const vec &dir, octaentities &oc)               // co
         if(!m || !m->collide) continue;
         vec center, radius;
         m->collisionbox(0, center, radius);
-        if(d->collidetype==COLLIDE_ELLIPSE)
-        {
-            float yaw = 180 + float((e.attr1+7)-(e.attr1+7)%15);
-            if(m->ellipsecollide)
-            {
-                if(!ellipsecollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return false;
-            }
-            else if(!ellipserectcollide(d, dir, e.o, center, yaw, radius.x, radius.y, radius.z, radius.z)) return false;
-        } 
-        else
+        if(!m->ellipsecollide || d->collidetype!=COLLIDE_ELLIPSE)
         {
             rotatebb(center, radius, e.attr1);
             if(!rectcollide(d, dir, center.add(e.o), radius.x, radius.y, radius.z, radius.z)) return false;
         }
+        else if(!ellipsecollide(d, dir, center.add(e.o), float((e.attr1+7)-(e.attr1+7)%15), radius.x, radius.y, radius.z, radius.z)) return false;
     }
     return true;
 }
@@ -798,7 +707,7 @@ static inline bool octacollide(physent *d, const vec &dir, float cutoff, const i
             if(c[i].ext) switch(c[i].ext->material&MATF_CLIP)
             {
                 case MAT_NOCLIP: continue;
-                case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break;
+                case MAT_AICLIP: if(d->type==ENT_AI) solid = true; break;
                 case MAT_CLIP: if(isclipped(c[i].ext->material&MATF_VOLUME) || d->type<ENT_CAMERA) solid = true; break;
             }
             if(!solid && isempty(c[i])) continue;
@@ -812,8 +721,8 @@ static inline bool octacollide(physent *d, const vec &dir, float cutoff, const i
 {
     int diff = (bo.x^(bo.x+bs.x)) | (bo.y^(bo.y+bs.y)) | (bo.z^(bo.z+bs.z)),
         scale = worldscale-1;
-    if(diff&~((1<<scale)-1) || uint(bo.x|bo.y|bo.z|(bo.x+bs.x)|(bo.y+bs.y)|(bo.z+bs.z)) >= uint(worldsize))
-       return octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), worldsize>>1);
+    if(diff&~((1<<scale)-1) || uint(bo.x|bo.y|bo.z|(bo.x+bs.x)|(bo.y+bs.y)|(bo.z+bs.z)) >= uint(hdr.worldsize))
+       return octacollide(d, dir, cutoff, bo, bs, worldroot, ivec(0, 0, 0), hdr.worldsize>>1);
     cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)];
     if(c->ext && c->ext->ents && !mmcollide(d, dir, *c->ext->ents)) return false;
     scale--;
@@ -828,7 +737,7 @@ static inline bool octacollide(physent *d, const vec &dir, float cutoff, const i
     if(c->ext) switch(c->ext->material&MATF_CLIP)
     {
         case MAT_NOCLIP: return true;
-        case MAT_GAMECLIP: if(d->type==ENT_AI) solid = true; break;
+        case MAT_AICLIP: if(d->type==ENT_AI) solid = true; break;
         case MAT_CLIP: if(isclipped(c->ext->material&MATF_VOLUME) || d->type<ENT_CAMERA) solid = true; break;
     }
     if(!solid && isempty(*c)) return true;
@@ -845,7 +754,7 @@ bool collide(physent *d, const vec &dir, float cutoff, bool playercol)
     ivec bo(int(d->o.x-d->radius), int(d->o.y-d->radius), int(d->o.z-d->eyeheight)),
          bs(int(d->radius)*2, int(d->radius)*2, int(d->eyeheight+d->aboveeye));
     bs.add(2);  // guard space for rounding errors
-    if(!octacollide(d, dir, cutoff, bo, bs)) return false;//, worldroot, ivec(0, 0, 0), worldsize>>1)) return false; // collide with world
+    if(!octacollide(d, dir, cutoff, bo, bs)) return false;//, worldroot, ivec(0, 0, 0), hdr.worldsize>>1)) return false; // collide with world
     return !playercol || plcollide(d, dir);
 }
 
@@ -861,10 +770,10 @@ void recalcdir(physent *d, const vec &oldvel, vec &dir)
     }
 }
 
-void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor, bool slidecollide)
+void slideagainst(physent *d, vec &dir, const vec &obstacle, bool foundfloor)
 {
     vec wall(obstacle);
-    if(foundfloor ? wall.z > 0 : slidecollide)
+    if(foundfloor && wall.z)
     {
         wall.z = 0;
         if(!wall.iszero()) wall.normalize();
@@ -892,83 +801,32 @@ void switchfloor(physent *d, vec &dir, const vec &floor)
     recalcdir(d, oldvel, dir);
 }
 
-bool trystepup(physent *d, vec &dir, const vec &obstacle, float maxstep, const vec &floor)
+bool trystepup(physent *d, vec &dir, float maxstep)
 {
-    vec old(d->o), stairdir = (obstacle.z >= 0 && obstacle.z < SLOPEZ ? vec(-obstacle.x, -obstacle.y, 0) : vec(dir.x, dir.y, 0)).rescale(1);
-    bool cansmooth = true;
+    vec old(d->o);
     /* check if there is space atop the stair to move to */
     if(d->physstate != PHYS_STEP_UP)
     {
-        vec checkdir = stairdir;
-        checkdir.mul(0.1f);
-        checkdir.z += maxstep + 0.1f;
-        d->o.add(checkdir);
+        d->o.add(dir);
+        d->o.z += maxstep + 0.1f;
         if(!collide(d))
         {
             d->o = old;
-            if(collide(d, vec(0, 0, -1), SLOPEZ)) return false;
-            cansmooth = false;
-        }
-    }
-
-    if(cansmooth)
-    {
-        d->o = old;
-        vec checkdir = stairdir;
-        checkdir.z += 1;
-        checkdir.mul(maxstep);
-        d->o.add(checkdir);
-        if(!collide(d, checkdir))
-        {
-            if(collide(d, vec(0, 0, -1), SLOPEZ))
-            {
-                d->o = old;
-                return false;
-            }
-        }
-
-        /* try stepping up half as much as forward */
-        d->o = old;
-        vec smoothdir(dir.x, dir.y, 0);
-        float magxy = smoothdir.magnitude();
-        if(magxy > 1e-9f)
-        {
-            if(magxy > 2*dir.z) 
-            {
-                smoothdir.mul(1/magxy);
-                smoothdir.z = 0.5f;
-                smoothdir.mul(dir.magnitude()/smoothdir.magnitude());
-            }
-            else smoothdir.z = dir.z;
-            d->o.add(smoothdir);
-            d->o.z += maxstep + 0.1f;
-            if(collide(d, smoothdir))
-            {
-                d->o.z -= maxstep + 0.1f;
-                if(d->physstate == PHYS_FALL || d->floor != floor)
-                {
-                    d->timeinair = 0;
-                    d->floor = floor;
-                    switchfloor(d, dir, d->floor);
-                }
-                d->physstate = PHYS_STEP_UP;
-                return true;
-            }
+            return false;
         }
     }
-
     /* try stepping up */
-    d->o = old;     
-    d->o.z += dir.magnitude();
+    d->o = old;
+    d->o.z += dir.magnitude()*STEPSPEED;
     if(collide(d, vec(0, 0, 1)))
     {
-        if(d->physstate == PHYS_FALL || d->floor != floor)
+        if(d->physstate == PHYS_FALL)
         {
             d->timeinair = 0;
-            d->floor = floor;
+            d->floor = vec(0, 0, 1);
             switchfloor(d, dir, d->floor);
         }
-        if(cansmooth) d->physstate = PHYS_STEP_UP;
+        d->physstate = PHYS_STEP_UP;
         return true;
     }
     d->o = old;
@@ -1019,7 +877,7 @@ void falling(physent *d, vec &dir, const vec &floor)
     else d->physstate = PHYS_FALL;
 }
 
-void landing(physent *d, vec &dir, const vec &floor, bool collided)
+void landing(physent *d, vec &dir, const vec &floor)
 {
 #if 0
     if(d->physstate == PHYS_FALL)
@@ -1030,8 +888,8 @@ void landing(physent *d, vec &dir, const vec &floor, bool collided)
 #endif
     switchfloor(d, dir, floor);
     d->timeinair = 0;
-    if(d->physstate!=PHYS_STEP_UP || !collided)
-        d->physstate = floor.z >= FLOORZ ? PHYS_FLOOR : PHYS_SLOPE;
+    if(floor.z >= FLOORZ) d->physstate = PHYS_FLOOR;
+    else d->physstate = PHYS_SLOPE;
     d->floor = floor;
 }
 
@@ -1061,6 +919,7 @@ bool findfloor(physent *d, bool collided, const vec &obstacle, bool &slide, vec
     }
     else if(d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f)
     {
+        d->o.z -= d->radius;
         if(!collide(d, vec(d->floor).neg(), 0.95f) || !collide(d, vec(0, 0, -1)))
         {
             floor = wall;
@@ -1080,9 +939,9 @@ bool move(physent *d, vec &dir)
 {
     vec old(d->o);
 #if 0
-    if(d->physstate == PHYS_STEP_DOWN && dir.z <= 0.0f && game::allowmove(pl) && (d->move || d->strafe))
+    if(d->physstate == PHYS_STEP_DOWN && dir.z <= 0.0f && cl->allowmove(pl) && (d->move || d->strafe))
     {
-        float step = dir.magnitude();
+        float step = dir.magnitude()*STEPSPEED;
         if(trystepdown(d, dir, step, 0.75f, 0.25f)) return true;
         if(trystepdown(d, dir, step, 0.5f, 0.5f)) return true;
         if(trystepdown(d, dir, step, 0.25f, 0.75f)) return true;
@@ -1094,7 +953,7 @@ bool move(physent *d, vec &dir)
     bool collided = false, slidecollide = false;
     vec obstacle;
     d->o.add(dir);
-    if(!collide(d, dir) || ((d->type==ENT_AI || d->type==ENT_INANIMATE) && !collide(d, vec(0, 0, 0), 0, false)))
+    if(!collide(d, d->type!=ENT_CAMERA ? dir : vec(0, 0, 0)) || ((d->type==ENT_AI || d->type==ENT_INANIMATE) && !collide(d)))
     {
         obstacle = wall;
         /* check to see if there is an obstacle that would prevent this one from being used as a floor (or ceiling bump) */
@@ -1104,13 +963,16 @@ bool move(physent *d, vec &dir)
             obstacle = wall;
         }
         d->o = old;
-        d->o.z -= STAIRHEIGHT;
-        d->zmargin = -STAIRHEIGHT;
+        if(d->type == ENT_CAMERA) return false;
+        float stepdist = (d->physstate >= PHYS_SLOPE && d->floor.z < 1.0f ? d->radius+0.1f : STAIRHEIGHT);
+        d->o.z -= stepdist;
+        d->zmargin = -stepdist;
         if(d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR || (!collide(d, vec(0, 0, -1), SLOPEZ) && (d->physstate==PHYS_STEP_UP || wall.z>=FLOORZ)))
         {
             d->o = old;
             d->zmargin = 0;
-            if(trystepup(d, dir, obstacle, STAIRHEIGHT, d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor : vec(wall))) return true;
+            float floorz = (d->physstate == PHYS_SLOPE || d->physstate == PHYS_FLOOR ? d->floor.z : wall.z);
+            if(trystepup(d, dir, floorz < 1.0f ? d->radius+0.1f : STAIRHEIGHT)) return true;
         }
         else 
         {
@@ -1120,32 +982,25 @@ bool move(physent *d, vec &dir)
         /* can't step over the obstacle, so just slide against it */
         collided = true;
     }
-    else if(d->physstate == PHYS_STEP_UP) 
-    {
-        if(!collide(d, vec(0, 0, -1), SLOPEZ))
-        {
-            d->o = old;
-            if(trystepup(d, dir, vec(0, 0, 1), STAIRHEIGHT, vec(wall))) return true;
-            d->o.add(dir);
-        }
-    }
     vec floor(0, 0, 0);
     bool slide = collided,
          found = findfloor(d, collided, obstacle, slide, floor);
     if(slide || (!collided && floor.z > 0 && floor.z < WALLZ))
     {
-        slideagainst(d, dir, slide ? obstacle : floor, found, slidecollide);
+        slideagainst(d, dir, slide ? obstacle : floor, found || slidecollide);
         if(d->type == ENT_AI || d->type == ENT_INANIMATE) d->blocked = true;
     }
-    if(found) landing(d, dir, floor, collided);
+    if(found)
+    {
+        if(d->type == ENT_CAMERA) return false;
+        landing(d, dir, floor);
+    }
     else falling(d, dir, floor);
     return !collided;
 }
 
 bool bounce(physent *d, float secs, float elasticity, float waterfric)
 {
-    // make sure bouncers don't start inside geometry
-    if(d->physstate!=PHYS_BOUNCE && !collide(d, vec(0, 0, 0), 0, false)) return true;
     int mat = lookupmaterial(vec(d->o.x, d->o.y, d->o.z + (d->aboveeye - d->eyeheight)/2));
     bool water = isliquid(mat);
     if(water) 
@@ -1210,40 +1065,20 @@ void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space)
     if(mindist >= 0.0f && mindist < 1e15f) d->o.add(vec(dir).mul(mindist));
 }
 
-bool movecamera(physent *pl, const vec &dir, float dist, float stepdist)
-{
-    int steps = (int)ceil(dist/stepdist);
-    if(steps <= 0) return true;
-
-    vec d(dir);
-    d.mul(dist/steps);
-    loopi(steps)
-    {
-        vec oldpos(pl->o);
-        pl->o.add(d);
-        if(!collide(pl, vec(0, 0, 0), 0, false))
-        {
-            pl->o = oldpos;
-            return false;
-        }
-    }
-    return true;
-}
-
 bool droptofloor(vec &o, float radius, float height)
 {
     if(!insideworld(o)) return false;
     vec v(0.0001f, 0.0001f, -1);
     v.normalize();
-    if(raycube(o, v, worldsize) >= worldsize) return false;
+    if(raycube(o, v, hdr.worldsize) >= hdr.worldsize) return false;
     physent d;
     d.type = ENT_CAMERA;
     d.o = o;
     d.vel = vec(0, 0, -1);
-    d.radius = d.xradius = d.yradius = radius;
+    d.radius = radius;
     d.eyeheight = height;
     d.aboveeye = radius;
-    if(!movecamera(&d, d.vel, worldsize, 1))
+    loopi(hdr.worldsize) if(!move(&d, v))
     {
         o = d.o;
         return true;
@@ -1251,21 +1086,9 @@ bool droptofloor(vec &o, float radius, float height)
     return false;
 }
 
-float dropheight(entity &e)
-{
-    switch(e.type)
-    {
-        case ET_PARTICLES:
-        case ET_MAPMODEL: return 0.0f;
-        default:
-            if(e.type >= ET_GAMESPECIFIC) return entities::dropheight(e);
-            return 4.0f;
-    }
-}
-
 void dropenttofloor(entity *e)
 {
-    droptofloor(e->o, 1.0f, dropheight(*e));
+    droptofloor(e->o, 1.0f, et->dropheight(*e));
 }
 
 void phystest()
@@ -1308,30 +1131,29 @@ void vectoyawpitch(const vec &v, float &yaw, float &pitch)
 }
 
 VARP(maxroll, 0, 3, 20);
-FVAR(straferoll, 0, 0.033f, 90);
 VAR(floatspeed, 10, 100, 1000);
 
 void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curtime)
 {
     if(floating)
     {
-        if(pl->jumping)
+        if(pl->jumpnext)
         {
-            pl->jumping = false;
+            pl->jumpnext = false;
             pl->vel.z = JUMPVEL;
         }
     }
     else if(pl->physstate >= PHYS_SLOPE || water)
     {
-        if(water && !pl->inwater) pl->vel.div(8);
-        if(pl->jumping)
+        if(pl->type != ENT_CAMERA && water && !pl->inwater) pl->vel.div(8);
+        if(pl->jumpnext)
         {
-            pl->jumping = false;
+            pl->jumpnext = false;
 
             pl->vel.z = JUMPVEL; // physics impulse upwards
             if(water) { pl->vel.x /= 8.0f; pl->vel.y /= 8.0f; } // dampen velocity change even harder, gives correct water feel
 
-            game::physicstrigger(pl, local, 1, 0);
+            cl->physicstrigger(pl, local, 1, 0);
         }
     }
     if(!floating && pl->physstate == PHYS_FALL) pl->timeinair += curtime;
@@ -1343,7 +1165,7 @@ void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curt
         if(d->rotspeed && d->yaw!=d->targetyaw)
         {
             float oldyaw = d->yaw, diff = d->rotspeed*curtime/1000.0f, maxdiff = fabs(d->targetyaw-d->yaw);
-            if(diff >= maxdiff)
+            if(diff >= maxdiff) 
             {
                 d->yaw = d->targetyaw;
                 d->rotspeed = 0;
@@ -1360,17 +1182,18 @@ void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curt
         }
     }
 
-    if(m.iszero() && game::allowmove(pl) && (pl->move || pl->strafe))
+    if(m.iszero() && cl->allowmove(pl) && (pl->move || pl->strafe))
     {
         vecfromyawpitch(pl->yaw, floating || water || pl->type==ENT_CAMERA ? pl->pitch : 0, pl->move, pl->strafe, m);
 
-        if(!floating && pl->physstate >= PHYS_SLOPE)
+        if(!floating && pl->physstate >= PHYS_SLIDE)
         {
             /* move up or down slopes in air
              * but only move up slopes in water
              */
             float dz = -(m.x*pl->floor.x + m.y*pl->floor.y)/pl->floor.z;
-            m.z = water ? max(m.z, dz) : dz;
+            if(water) m.z = max(m.z, dz);
+            else if(pl->floor.z >= WALLZ) m.z = dz;
         }
 
         m.normalize();
@@ -1378,20 +1201,17 @@ void modifyvelocity(physent *pl, bool local, bool water, bool floating, int curt
 
     vec d(m);
     d.mul(pl->maxspeed);
-    if(pl->type==ENT_PLAYER)
+    if(floating) 
     {
-        if(floating) 
-        {
-            if(pl==player) d.mul(floatspeed/100.0f);
-        }
-        else if(!water && game::allowmove(pl)) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); // EXPERIMENTAL
-    }
-    float fric = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f);
-    pl->vel.lerp(d, pl->vel, pow(1 - 1/fric, curtime/20.0f));
-// old fps friction
-//    float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f);
-//    float fpsfric = min(curtime/(20.0f*friction), 1.0f);
-//    pl->vel.lerp(pl->vel, d, fpsfric);
+        if(pl==player) d.mul(floatspeed/100.0f);
+    }
+    else if(!water && cl->allowmove(pl)) d.mul((pl->move && !pl->strafe ? 1.3f : 1.0f) * (pl->physstate < PHYS_SLOPE ? 1.3f : 1.0f)); // EXPERIMENTAL
+    float friction = water && !floating ? 20.0f : (pl->physstate >= PHYS_SLOPE || floating ? 6.0f : 30.0f);
+    float fpsfric = friction/curtime*20.0f;
+
+    pl->vel.mul(fpsfric-1);
+    pl->vel.add(d);
+    pl->vel.div(fpsfric);
 }
 
 void modifygravity(physent *pl, bool water, int curtime)
@@ -1406,18 +1226,14 @@ void modifygravity(physent *pl, bool water, int curtime)
         g.normalize();
         g.mul(GRAVITY*secs);
     }
-    if(!water || !game::allowmove(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g);
+    if(!water || !cl->allowmove(pl) || (!pl->move && !pl->strafe)) pl->falling.add(g);
 
     if(water || pl->physstate >= PHYS_SLOPE)
     {
-        float fric = water ? 2.0f : 6.0f,
+        float friction = water ? 2.0f : 6.0f,
+              fpsfric = friction/curtime*20.0f,
               c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f);
-        pl->falling.mul(pow(1 - c/fric, curtime/20.0f));
-// old fps friction
-//        float friction = water ? 2.0f : 6.0f,
-//              fpsfric = friction/curtime*20.0f,
-//              c = water ? 1.0f : clamp((pl->floor.z - SLOPEZ)/(FLOORZ-SLOPEZ), 0.0f, 1.0f);
-//        pl->falling.mul(1 - c/fpsfric);
+        pl->falling.mul(1 - c/fpsfric);
     }
 }
 
@@ -1429,16 +1245,16 @@ bool moveplayer(physent *pl, int moveres, bool local, int curtime)
 {
     int material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (3*pl->aboveeye - pl->eyeheight)/4));
     bool water = isliquid(material&MATF_VOLUME);
-    bool floating = pl->type==ENT_PLAYER && (pl->state==CS_EDITING || pl->state==CS_SPECTATOR);
+    bool floating = pl->state==CS_EDITING || (pl->type!=ENT_CAMERA && pl->state==CS_SPECTATOR);
     float secs = curtime/1000.f;
 
     // apply gravity
-    if(!floating) modifygravity(pl, water, curtime);
+    if(!floating && pl->type!=ENT_CAMERA) modifygravity(pl, water, curtime);
     // apply any player generated changes in velocity
     modifyvelocity(pl, local, water, floating, curtime);
 
     vec d(pl->vel), oldpos(pl->o);
-    if(!floating && water) d.mul(0.5f);
+    if(!floating && pl->type!=ENT_CAMERA && water) d.mul(0.5f);
     d.add(pl->falling);
     d.mul(secs);
 
@@ -1463,14 +1279,14 @@ bool moveplayer(physent *pl, int moveres, bool local, int curtime)
         int collisions = 0;
 
         d.mul(f);
-        loopi(moveres) if(!move(pl, d) && ++collisions<5) i--; // discrete steps collision detection & sliding
+        loopi(moveres) if(!move(pl, d)) { if(pl->type==ENT_CAMERA) return false; if(++collisions<5) i--; } // discrete steps collision detection & sliding
         if(timeinair > 800 && !pl->timeinair && !water) // if we land after long time must have been a high jump, make thud sound
         {
-            game::physicstrigger(pl, local, -1, 0);
+            cl->physicstrigger(pl, local, -1, 0);
         }
     }
 
-    if(pl->state==CS_ALIVE) updatedynentcache(pl);
+    if(pl->type!=ENT_CAMERA && pl->state==CS_ALIVE) updatedynentcache(pl);
 
     if(!pl->timeinair && pl->physstate >= PHYS_FLOOR && pl->vel.squaredlen() < 1e-4f) pl->moving = false;
 
@@ -1485,23 +1301,26 @@ bool moveplayer(physent *pl, int moveres, bool local, int curtime)
     }
     else
     {
-        pl->roll -= pl->strafe*curtime*straferoll;
-        if(pl->roll > maxroll) pl->roll = maxroll;
-        else if(pl->roll < -maxroll) pl->roll = -maxroll;
+        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)
+    if(pl->type!=ENT_CAMERA)
     {
-        material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (pl->aboveeye - pl->eyeheight)/2));
-        water = isliquid(material&MATF_VOLUME);
-    }
-    if(!pl->inwater && water) game::physicstrigger(pl, local, 0, -1, material&MATF_VOLUME);
-    else if(pl->inwater && !water) game::physicstrigger(pl, local, 0, 1, pl->inwater);
-    pl->inwater = water ? material&MATF_VOLUME : MAT_AIR;
+        if(pl->inwater && !water)
+        {
+            material = lookupmaterial(vec(pl->o.x, pl->o.y, pl->o.z + (pl->aboveeye - pl->eyeheight)/2));
+            water = isliquid(material&MATF_VOLUME);
+        }
+        if(!pl->inwater && water) cl->physicstrigger(pl, local, 0, -1, material&MATF_VOLUME);
+        else if(pl->inwater && !water) cl->physicstrigger(pl, local, 0, 1, pl->inwater);
+        pl->inwater = water ? material&MATF_VOLUME : MAT_AIR;
 
-    if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) game::suicide(pl);
+        if(pl->state==CS_ALIVE && (pl->o.z < 0 || material&MAT_DEATH)) cl->suicide(pl);
+    }
 
     return true;
 }
@@ -1512,7 +1331,7 @@ int physsteps = 0, physframetime = PHYSFRAMETIME, lastphysframe = 0;
 
 void physicsframe()          // optimally schedule physics frames inside the graphics frames
 {
-    int diff = lastmillis - lastphysframe;
+    int diff = lastmillis + curtime - lastphysframe;
     if(diff <= 0) physsteps = 0;
     else
     {
@@ -1530,7 +1349,7 @@ void interppos(physent *pl)
 {
     pl->o = pl->newpos;
 
-    int diff = lastphysframe - lastmillis;
+    int diff = lastphysframe - (lastmillis + curtime); 
     if(diff <= 0 || !physinterp) return;
         
     vec deltapos(pl->deltapos);
@@ -1593,19 +1412,25 @@ void updatephysstate(physent *d)
     {
         case PHYS_SLOPE:
         case PHYS_FLOOR:
-            d->o.z -= 0.15f;
+            d->o.z -= 0.1f;
             if(!collide(d, vec(0, 0, -1), d->physstate == PHYS_SLOPE ? SLOPEZ : FLOORZ))
                 d->floor = wall;
+            else if(d->physstate == PHYS_SLOPE)
+            {
+                d->o.z -= d->radius;
+                if(!collide(d, vec(0, 0, -1), SLOPEZ))
+                    d->floor = wall;
+            }
             break;
 
         case PHYS_STEP_UP:
-            d->o.z -= STAIRHEIGHT+0.15f;
+            d->o.z -= STAIRHEIGHT+0.1f;
             if(!collide(d, vec(0, 0, -1), SLOPEZ))
                 d->floor = wall;
             break;
 
         case PHYS_SLIDE:
-            d->o.z -= 0.15f;
+            d->o.z -= d->radius+0.1f;
             if(!collide(d, vec(0, 0, -1)) && wall.z < SLOPEZ)
                 d->floor = wall;
             break;
@@ -1614,6 +1439,35 @@ void updatephysstate(physent *d)
     d->o = old;
 }
 
+bool intersect(physent *d, vec &from, vec &to)   // if lineseg hits entity bounding box
+{
+    vec v = to, w = d->o, *p;
+    v.sub(from);
+    w.sub(from);
+    float c1 = w.dot(v);
+
+    if(c1<=0) p = &from;
+    else
+    {
+        float c2 = v.dot(v);
+        if(c2<=c1) p = &to;
+        else
+        {
+            float f = c1/c2;
+            v.mul(f);
+            v.add(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;
+}
+
 const float PLATFORMMARGIN = 0.2f;
 const float PLATFORMBORDER = 10.0f;
 
@@ -1636,28 +1490,28 @@ bool platformcollide(physent *d, physent *o, const vec &dir, float margin = 0)
             o->eyeheight + margin))
             return true;
     }
-    else if(ellipsecollide(d, dir, o->o, vec(0, 0, 0), o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight + margin))
+    else if(ellipsecollide(d, dir, o->o, o->yaw, o->xradius, o->yradius, o->aboveeye, o->eyeheight + margin))
         return true;
     return false;
 }
 
 bool moveplatform(physent *p, const vec &dir)
-{
+{   
     if(!insideworld(p->newpos)) return false;
 
     vec oldpos(p->o);
     (p->o = p->newpos).add(dir);
     if(!collide(p, dir, 0, dir.z<=0))
     {
-        p->o = oldpos;
+        p->o = oldpos; 
         return false;
     }
-    p->o = oldpos;
+    p->o = oldpos; 
 
     static vector<physent *> candidates;
     candidates.setsizenodelete(0);
-    for(int x = int(max(p->o.x-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ex = int(min(p->o.x+p->radius+PLATFORMBORDER, worldsize-1.0f))>>dynentsize; x <= ex; x++)
-    for(int y = int(max(p->o.y-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ey = int(min(p->o.y+p->radius+PLATFORMBORDER, worldsize-1.0f))>>dynentsize; y <= ey; y++)
+    for(int x = int(max(p->o.x-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ex = int(min(p->o.x+p->radius+PLATFORMBORDER, hdr.worldsize-1.0f))>>dynentsize; x <= ex; x++)
+    for(int y = int(max(p->o.y-p->radius-PLATFORMBORDER, 0.0f))>>dynentsize, ey = int(min(p->o.y+p->radius+PLATFORMBORDER, hdr.worldsize-1.0f))>>dynentsize; y <= ey; y++)
     {
         const vector<physent *> &dynents = checkdynentcache(x, y);
         loopv(dynents)
@@ -1760,8 +1614,8 @@ dir(forward,  move,    1, k_up,    k_down);
 dir(left,     strafe,  1, k_left,  k_right);
 dir(right,    strafe, -1, k_right, k_left);
 
-ICOMMAND(jump,   "D", (int *down), { if(!*down || game::canjump()) player->jumping = *down!=0; });
-ICOMMAND(attack, "D", (int *down), { game::doattack(*down!=0); });
+ICOMMAND(jump,   "D", (int *down), { if(cl->canjump()) player->jumpnext = *down!=0; });
+ICOMMAND(attack, "D", (int *down), { cl->doattack(*down!=0); });
 
 bool entinmap(dynent *d, bool avoidplayers)        // brute force but effective way to find a free spawn spot in the map
 {
diff --git a/engine/pvs.cpp b/engine/pvs.cpp
index 1afbc6a..1ed80f6 100644
--- a/engine/pvs.cpp
+++ b/engine/pvs.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 #include "SDL_thread.h"
 
@@ -67,7 +68,7 @@ static bool mergepvsnodes(pvsnode &p, pvsnode *children)
     return true;
 }
 
-static void genpvsnodes(cube *c, int parent = 0, const ivec &co = ivec(0, 0, 0), int size = worldsize/2)
+static void genpvsnodes(cube *c, int parent = 0, const ivec &co = ivec(0, 0, 0), int size = hdr.worldsize/2)
 {
     int index = origpvsnodes.length();
     loopi(8)
@@ -328,7 +329,7 @@ struct pvsworker
     int hasvoxel(const ivec &p, int coord, int dir, int ocoord = 0, int odir = 0, int *omin = NULL)
     {
         uint diff = (origin.x^p.x)|(origin.y^p.y)|(origin.z^p.z);
-        if(diff >= uint(worldsize)) return 0;
+        if(diff >= uint(hdr.worldsize)) return 0;
         diff >>= curlevel;
         while(diff)
         {
@@ -395,7 +396,7 @@ struct pvsworker
         if(p.edges.x!=0xFF) p.flags |= PVS_HIDE_GEOM;
     }
 
-    void shaftcullpvs(shaft &s, pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = worldsize)
+    void shaftcullpvs(shaft &s, pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = hdr.worldsize)
     {
         if(p.flags&PVS_HIDE_BB) return;
         shaftbb bb(co, size);
@@ -421,7 +422,7 @@ struct pvsworker
 
     ringbuf<shaftbb, 32> prevblockers;
 
-    void cullpvs(pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = worldsize)
+    void cullpvs(pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = hdr.worldsize)
     {
         if(p.flags&(PVS_HIDE_BB | PVS_HIDE_GEOM) || genpvs_canceled) return;
         if(p.children && !(p.flags&PVS_HIDE_BB))
@@ -701,7 +702,7 @@ struct pvsworker
             bbsize[R[dim]] = m.rsize;
             bborigin[dim] -= 2;
             bbsize[dim] = 2;
-            if(!materialoccluded(pvsnodes[0], vec(0, 0, 0), worldsize/2, bborigin, bbsize)) return false;
+            if(!materialoccluded(pvsnodes[0], vec(0, 0, 0), hdr.worldsize/2, bborigin, bbsize)) return false;
         }
         return true;
     }
@@ -731,7 +732,7 @@ struct pvsworker
         waterbytes = 0;
         loopi(4) if(wateroccluded&(0xFF<<(i*8))) waterbytes = i+1;
 
-        compresspvs(pvsnodes[0], worldsize, pvsleafsize);
+        compresspvs(pvsnodes[0], hdr.worldsize, pvsleafsize);
         outbuf.setsizenodelete(0);
         serializepvs(pvsnodes[0]);
     }
@@ -819,11 +820,13 @@ static int totalviewcells = 0;
 
 static void show_genpvs_progress(int unique = pvs.length(), int processed = numviewcells)
 {
-    float bar1 = float(processed) / float(totalviewcells>0 ? totalviewcells : 1);
+    float bar1 = float(processed) / float(totalviewcells>0 ? totalviewcells : 1),
+          bar2 = float(unique) / float(totalviewcells>0 ? totalviewcells : 1);
 
-    defformatstring(text1)("%d%% - %d of %d view cells (%d unique)", int(bar1 * 100), processed, totalviewcells, unique);
+    s_sprintfd(text1)("%d%% - %d of %d view cells", int(bar1 * 100), processed, totalviewcells);
+    s_sprintfd(text2)("%d unique view cells", unique);
 
-    renderprogress(bar1, text1);
+    show_out_of_renderloop_progress(bar1, text1, bar2, text2);
 
     if(interceptkey(SDLK_ESCAPE)) genpvs_canceled = true;
     check_genpvs_progress = false;
@@ -926,7 +929,7 @@ static int curwaterpvs = 0, lockedwaterpvs = 0;
 static inline pvsdata *lookupviewcell(const vec &p)
 {
     uint x = uint(floor(p.x)), y = uint(floor(p.y)), z = uint(floor(p.z));
-    if(!viewcells || (x|y|z)>=uint(worldsize)) return NULL;
+    if(!viewcells || (x|y|z)>=uint(hdr.worldsize)) return NULL;
     viewcellnode *vc = viewcells;
     for(int scale = worldscale-1; scale>=0; scale--)
     {
@@ -1073,17 +1076,17 @@ COMMAND(testpvs, "i");
 
 void genpvs(int *viewcellsize)
 {
-    if(worldsize > 1<<15)
+    if(hdr.worldsize > 1<<15)
     {
         conoutf(CON_ERROR, "map is too large for PVS");
         return;
     }
 
-    renderbackground("generating PVS (esc to abort)");
+    computescreen("generating PVS (esc to abort)");
     genpvs_canceled = false;
     Uint32 start = SDL_GetTicks();
 
-    renderprogress(0, "finding view cells");
+    show_out_of_renderloop_progress(0, "finding view cells");
 
     clearpvs();
     calcpvsbounds();
@@ -1095,7 +1098,7 @@ void genpvs(int *viewcellsize)
     root.children = 0;
     genpvsnodes(worldroot);
 
-    totalviewcells = countviewcells(worldroot, ivec(0, 0, 0), worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
+    totalviewcells = countviewcells(worldroot, ivec(0, 0, 0), hdr.worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
     numviewcells = 0;
     genpvs_canceled = false;
     check_genpvs_progress = false;
@@ -1106,14 +1109,14 @@ void genpvs(int *viewcellsize)
         timer = SDL_AddTimer(500, genpvs_timer, NULL);
     }
     viewcells = new viewcellnode;
-    genviewcells(*viewcells, worldroot, ivec(0, 0, 0), worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
+    genviewcells(*viewcells, worldroot, ivec(0, 0, 0), hdr.worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
     if(pvsthreads<=1)
     {
         SDL_RemoveTimer(timer);
     }
     else
     {
-        renderprogress(0, "creating threads");
+        show_out_of_renderloop_progress(0, "creating threads");
         if(!pvsmutex) pvsmutex = SDL_CreateMutex();
         if(!viewcellmutex) viewcellmutex = SDL_CreateMutex();
         loopi(pvsthreads)
@@ -1217,63 +1220,91 @@ bool waterpvsoccluded(int height)
     return false;
 }
 
-void saveviewcells(stream *f, viewcellnode &p)
+void saveviewcells(gzFile f, viewcellnode &p)
 {
-    f->putchar(p.leafmask);
+    gzputc(f, p.leafmask);
     loopi(8)
     {
-        if(p.leafmask&(1<<i)) f->putlil<int>(p.children[i].pvs);
+        if(p.leafmask&(1<<i))
+        {
+            int pvsindex = p.children[i].pvs;
+            endianswap(&pvsindex, sizeof(int), 1);
+            gzwrite(f, &pvsindex, sizeof(int));
+        }
         else saveviewcells(f, *p.children[i].node);
     }
 }
 
-void savepvs(stream *f)
+void savepvs(gzFile f)
 {
     uint totallen = pvsbuf.length() | (numwaterplanes>0 ? 0x80000000U : 0);
-    f->putlil<uint>(totallen);
+    endianswap(&totallen, sizeof(uint), 1);
+    gzwrite(f, &totallen, sizeof(uint));
     if(numwaterplanes>0)
     {
-        f->putlil<uint>(numwaterplanes);
+        uint numwp = numwaterplanes;
+        endianswap(&numwp, sizeof(uint), 1);
+        gzwrite(f, &numwp, sizeof(uint));
         loopi(numwaterplanes)
         {
-            f->putlil<int>(waterplanes[i].height);
+            int height = waterplanes[i].height;
+            endianswap(&height, sizeof(int), 1);
+            gzwrite(f, &height, sizeof(int));
             if(waterplanes[i].height < 0) break;
         }
     }
-    loopv(pvs) f->putlil<ushort>(pvs[i].len);
-    f->write(pvsbuf.getbuf(), pvsbuf.length());
+    loopv(pvs)
+    {
+        ushort len = pvs[i].len;
+        endianswap(&len, sizeof(ushort), 1);
+        gzwrite(f, &len, sizeof(ushort));
+    }
+    gzwrite(f, pvsbuf.getbuf(), pvsbuf.length());
     saveviewcells(f, *viewcells);
 }
 
-viewcellnode *loadviewcells(stream *f)
+viewcellnode *loadviewcells(gzFile f)
 {
     viewcellnode *p = new viewcellnode;
-    p->leafmask = f->getchar();
+    p->leafmask = gzgetc(f);
     loopi(8)
     {
-        if(p->leafmask&(1<<i)) p->children[i].pvs = f->getlil<int>();
+        if(p->leafmask&(1<<i))
+        {
+            gzread(f, &p->children[i].pvs, sizeof(int));
+            endianswap(&p->children[i].pvs, sizeof(int), 1);
+        }
         else p->children[i].node = loadviewcells(f);
     }
     return p;
 }
 
-void loadpvs(stream *f, int numpvs)
+void loadpvs(gzFile f)
 {
-    uint totallen = f->getlil<uint>();
+    uint totallen = pvsbuf.length();
+    gzread(f, &totallen, sizeof(uint));
+    endianswap(&totallen, sizeof(uint), 1);
     if(totallen & 0x80000000U)
     {
         totallen &= ~0x80000000U;
-        numwaterplanes = f->getlil<uint>();
-        loopi(numwaterplanes) waterplanes[i].height = f->getlil<int>();
+        gzread(f, &numwaterplanes, sizeof(uint));
+        endianswap(&numwaterplanes, sizeof(uint), 1);
+        loopi(numwaterplanes)
+        {
+            gzread(f, &waterplanes[i].height, sizeof(int));
+            endianswap(&waterplanes[i].height, sizeof(int), 1);
+        }
     }
     int offset = 0;
-    loopi(numpvs)
+    loopi(hdr.numpvs)
     {
-        ushort len = f->getlil<ushort>();
+        ushort len;
+        gzread(f, &len, sizeof(ushort));
+        endianswap(&len, sizeof(ushort), 1);
         pvs.add(pvsdata(offset, len));
         offset += len;
     }
-    f->read(pvsbuf.reserve(totallen).buf, totallen);
+    gzread(f, pvsbuf.reserve(totallen).buf, totallen);
     pvsbuf.advance(totallen);
     viewcells = loadviewcells(f);
 }
diff --git a/engine/ragdoll.h b/engine/ragdoll.h
deleted file mode 100644
index 0e3ff72..0000000
--- a/engine/ragdoll.h
+++ /dev/null
@@ -1,399 +0,0 @@
-struct ragdollskel
-{
-    struct vert
-    {
-        vec pos;
-        float weight;
-    };
-
-    struct tri
-    {
-        int vert[3];
-    };
-
-    struct distlimit
-    {
-        int vert[2];
-        float mindist, maxdist;
-    }; 
-
-    struct rotlimit
-    {
-        int tri[2];
-        float maxangle;
-        matrix3x3 middle;
-    };
-
-    struct joint
-    {
-        int bone, tri, vert[3];
-        float weight;
-        matrix3x4 orient;
-    };
-
-    struct reljoint
-    {
-        int bone, parent;
-    };
-
-    bool loaded;
-    int eye;
-    vector<vert> verts;
-    vector<tri> tris;
-    vector<distlimit> distlimits;
-    vector<rotlimit> rotlimits;
-    vector<joint> joints;
-    vector<reljoint> reljoints;
-
-    ragdollskel() : loaded(false), eye(-1) {}
-
-    void setup()
-    {
-        loopv(verts) verts[i].weight = 0;
-        loopv(joints)
-        {
-            joint &j = joints[i];
-            j.weight = 0;
-            vec pos(0, 0, 0);
-            loopk(3) if(j.vert[k]>=0) 
-            {
-                pos.add(verts[j.vert[k]].pos);
-                j.weight++;
-                verts[j.vert[k]].weight++;
-            }
-            if(j.weight) j.weight = 1/j.weight;
-            pos.mul(j.weight);
-
-            tri &t = tris[j.tri];
-            matrix3x3 m;
-            const vec &v1 = verts[t.vert[0]].pos,
-                      &v2 = verts[t.vert[1]].pos,
-                      &v3 = verts[t.vert[2]].pos;
-            m.a = vec(v2).sub(v1).normalize();
-            m.c.cross(m.a, vec(v3).sub(v1)).normalize();
-            m.b.cross(m.c, m.a);
-
-            j.orient = matrix3x4(m, m.transform(pos).neg());        
-        }
-        loopv(verts) if(verts[i].weight) verts[i].weight = 1/verts[i].weight;
-        reljoints.setsize(0);
-
-        loaded = true;
-    } 
-
-    void addreljoint(int bone, int parent)
-    {
-        reljoint &r = reljoints.add();
-        r.bone = bone;
-        r.parent = parent;
-    }
-};
-
-struct ragdolldata
-{
-    struct vert
-    {
-        vec oldpos, pos, newpos;
-        float weight;
-        bool collided;
-
-        vert() : pos(0, 0, 0), newpos(0, 0, 0), weight(0), collided(false) {}
-    };
-
-    ragdollskel *skel;
-    int millis, collidemillis, collisions, floating, lastmove;
-    vec offset, center;
-    float radius, timestep, scale;
-    vert *verts;
-    matrix3x3 *tris;
-    matrix3x4 *reljoints;
-
-    ragdolldata(ragdollskel *skel, float scale = 1)
-        : skel(skel),
-          millis(lastmillis),
-          collidemillis(0),
-          collisions(0),
-          floating(0),
-          lastmove(lastmillis),
-          timestep(0),
-          scale(scale),
-          verts(new vert[skel->verts.length()]), 
-          tris(new matrix3x3[skel->tris.length()]),
-          reljoints(skel->reljoints.empty() ? NULL : new matrix3x4[skel->reljoints.length()])
-    {
-    }
-
-    ~ragdolldata()
-    {
-        delete[] verts;
-        delete[] tris;
-        if(reljoints) delete[] reljoints;
-    }
-
-    void calctris()
-    {
-        loopv(skel->tris)
-        {
-            ragdollskel::tri &t = skel->tris[i];
-            matrix3x3 &m = tris[i];
-            const vec &v1 = verts[t.vert[0]].pos,
-                      &v2 = verts[t.vert[1]].pos,
-                      &v3 = verts[t.vert[2]].pos;
-            m.a = vec(v2).sub(v1).normalize();
-            m.c.cross(m.a, vec(v3).sub(v1)).normalize();
-            m.b.cross(m.c, m.a);
-        }
-    }
-
-    void calcboundsphere()
-    {
-        center = vec(0, 0, 0);
-        loopv(skel->verts) center.add(verts[i].pos);
-        center.div(skel->verts.length());
-        radius = 0;
-        loopv(skel->verts) radius = max(radius, verts[i].pos.dist(center));
-    }
-
-    void init(dynent *d)
-    {
-        float ts = curtime/1000.0f;
-        loopv(skel->verts) (verts[i].oldpos = verts[i].pos).sub(vec(d->vel).add(d->falling).mul(ts));
-        timestep = ts;
-
-        calctris();
-        calcboundsphere();
-        offset = d->o;
-        offset.sub(skel->eye >= 0 ? verts[skel->eye].pos : center);
-        offset.z += (d->eyeheight + d->aboveeye)/2;
-    }
-
-    void move(dynent *pl, float ts);
-    void updatepos();
-    void constrain();
-    void constraindist();
-    void constrainrot();
-};
-
-/*
-    seed particle position = avg(modelview * base2anim * spherepos)  
-    mapped transform = invert(curtri) * origtrig 
-    parented transform = parent{invert(curtri) * origtrig} * (invert(parent{base2anim}) * base2anim)
-*/
-
-void ragdolldata::constraindist()
-{
-    loopv(skel->distlimits)
-    {
-        ragdollskel::distlimit &d = skel->distlimits[i];
-        vert &v1 = verts[d.vert[0]], &v2 = verts[d.vert[1]];
-        vec dir = vec(v2.pos).sub(v1.pos);
-        float dist = dir.magnitude(), cdist = dist;
-        if(dist < d.mindist) cdist = d.mindist;
-        else if(dist > d.maxdist) cdist = d.maxdist;
-        else continue;
-        if(dist < 1e-4f) dir = vec(0, 0, cdist*scale*0.5f);
-        else dir.mul(cdist*scale*0.5f/dist);
-        vec center = vec(v1.pos).add(v2.pos).mul(0.5f);
-        v1.newpos.add(vec(center).sub(dir));
-        v1.weight++;
-        v2.newpos.add(vec(center).add(dir));
-        v2.weight++;
-    }
-}
-        
-void ragdolldata::constrainrot()
-{
-    loopv(skel->rotlimits)
-    {
-        ragdollskel::rotlimit &r = skel->rotlimits[i];
-        matrix3x3 rot;
-        rot.transposemul(tris[r.tri[0]], r.middle);
-        rot.mul(tris[r.tri[1]]);
-
-        vec axis;
-        float angle;
-        rot.calcangleaxis(angle, axis);
-        angle = r.maxangle - fabs(angle);
-        if(angle >= 0) continue; 
-        angle += 1e-3f;
-
-        ragdollskel::tri &t1 = skel->tris[r.tri[0]], &t2 = skel->tris[r.tri[1]];
-        vec v1[3], v2[3], c1(0, 0, 0), c2(0, 0, 0);
-        loopk(3)
-        {
-            c1.add(v1[k] = verts[t1.vert[k]].pos);
-            c2.add(v2[k] = verts[t2.vert[k]].pos);
-        }
-        c1.div(3);
-        c2.div(3);
-        matrix3x3 wrot, crot1, crot2;
-        static const float wrotc = cosf(0.5f*RAD), wrots = sinf(0.5f*RAD);
-        wrot.rotate(wrotc, wrots, axis);
-        float w1 = 0, w2 = 0;
-        loopk(3) 
-        { 
-            v1[k].sub(c1);
-            v2[k].sub(c2);
-            w1 += wrot.transform(v1[k]).dist(v1[k]); 
-            w2 += wrot.transform(v2[k]).dist(v2[k]); 
-        }
-        crot1.rotate(angle*w2/(w1+w2), axis);
-        crot2.rotate(-angle*w1/(w1+w2), axis);
-        vec r1[3], r2[3], diff1(0, 0, 0), diff2(0, 0, 0);
-        loopk(3) 
-        { 
-            r1[k] = crot1.transform(v1[k]);
-            r2[k] = crot2.transform(v2[k]);
-            diff1.add(r1[k]).sub(v1[k]);
-            diff2.add(r2[k]).sub(v2[k]);
-        }
-        diff1.div(3).add(c1);
-        diff2.div(3).add(c2);
-        loopk(3)
-        {
-            verts[t1.vert[k]].newpos.add(r1[k]).add(diff1);
-            verts[t2.vert[k]].newpos.add(r2[k]).add(diff2);
-            verts[t1.vert[k]].weight++;
-            verts[t2.vert[k]].weight++;
-        }
-    }
-}
-
-extern vec wall;
-
-void ragdolldata::updatepos()
-{
-    static physent d;
-    d.type = ENT_BOUNCE;
-    d.radius = d.eyeheight = d.aboveeye = 1;
-    loopv(skel->verts)
-    {
-        vert &v = verts[i];
-        if(v.weight)
-        {
-            d.o = v.newpos.div(v.weight);
-            if(collide(&d, vec(v.newpos).sub(v.pos), 0, false)) v.pos = v.newpos;
-            else
-            {
-                vec dir = vec(v.newpos).sub(v.oldpos);
-                if(dir.dot(wall) < 0) v.oldpos = vec(v.pos).sub(dir.reflect(wall));
-                v.collided = true;
-            }
-        }
-        v.newpos = vec(0, 0, 0);
-        v.weight = 0;
-    }
-}
-
-VAR(ragdollconstrain, 1, 5, 100);
-
-void ragdolldata::constrain()
-{
-    loopi(ragdollconstrain)
-    {
-        constraindist();
-        updatepos();
-
-        calctris();
-        constrainrot();
-        updatepos();
-    }
-}
-
-FVAR(ragdollbodyfric, 0, 0.95f, 1);
-FVAR(ragdollbodyfricscale, 0, 2, 10);
-FVAR(ragdollwaterfric, 0, 0.85f, 1);
-FVAR(ragdollgroundfric, 0, 0.8f, 1);
-FVAR(ragdollairfric, 0, 0.996f, 1);
-VAR(ragdollexpireoffset, 0, 1000, 30000);
-VAR(ragdollwaterexpireoffset, 0, 3000, 30000);
-VAR(ragdollexpiremillis, 1, 1000, 30000);
-VAR(ragdolltimestepmin, 1, 5, 50);
-VAR(ragdolltimestepmax, 1, 10, 50);
-
-void ragdolldata::move(dynent *pl, float ts)
-{
-    extern const float GRAVITY;
-    float expirefric = collidemillis && lastmillis > collidemillis ? max(1 - float(lastmillis - collidemillis)/ragdollexpiremillis, 0.0f) : 1;
-    if(!expirefric) return;
-    if(timestep) expirefric *= ts/timestep;
-
-    int material = lookupmaterial(vec(center.x, center.y, center.z + radius/2));
-    bool water = isliquid(material&MATF_VOLUME);
-    if(!pl->inwater && water) game::physicstrigger(pl, true, 0, -1, material&MATF_VOLUME);
-    else if(pl->inwater && !water)
-    {
-        material = lookupmaterial(center);
-        water = isliquid(material&MATF_VOLUME);
-        if(!water) game::physicstrigger(pl, true, 0, 1, pl->inwater);
-    }
-    pl->inwater = water ? material&MATF_VOLUME : MAT_AIR;
-    
-    physent d;
-    d.type = ENT_BOUNCE;
-    d.radius = d.eyeheight = d.aboveeye = 1;
-    float airfric = ragdollairfric + min((ragdollbodyfricscale*collisions)/skel->verts.length(), 1.0f)*(ragdollbodyfric - ragdollairfric);
-    collisions = 0;
-    loopv(skel->verts)
-    {
-        vert &v = verts[i];
-        vec curpos = v.pos, dpos = vec(v.pos).sub(v.oldpos);
-        dpos.mul(pow((water ? ragdollwaterfric : 1.0f) * (v.collided ? ragdollgroundfric : airfric), ts*1000.0f/ragdolltimestepmin)*expirefric);
-        v.pos.z -= GRAVITY*ts*ts;
-        if(water) v.pos.z += 0.25f*sinf(detrnd(size_t(this)+i, 360)*RAD + lastmillis/10000.0f*M_PI)*ts;
-        v.pos.add(dpos);
-        if(v.pos.z < 0) { v.pos.z = 0; curpos = v.pos; collisions++; }
-        d.o = v.pos;
-        vec dir = vec(v.pos).sub(curpos);
-        v.collided = !collide(&d, dir, 0, false);
-        if(v.collided)
-        {
-            v.oldpos = vec(curpos).sub(dir.reflect(wall));
-            v.pos = curpos; 
-            collisions++;
-        }   
-        else v.oldpos = curpos;
-    }
-
-    timestep = ts;
-    if(collisions)
-    {
-        floating = 0;
-        if(!collidemillis) collidemillis = lastmillis + (water ? ragdollwaterexpireoffset : ragdollexpireoffset);
-    }
-    else if(++floating > 1 && lastmillis < collidemillis + ragdollexpiremillis) collidemillis = 0;
-
-    constrain();
-    calctris();
-    calcboundsphere();
-}    
-
-FVAR(ragdolleyesmooth, 0, 0.5f, 1);
-VAR(ragdolleyesmoothmillis, 1, 250, 10000);
-
-void moveragdoll(dynent *d)
-{
-    if(!curtime || !d->ragdoll) return;
-
-    if(!d->ragdoll->collidemillis || lastmillis < d->ragdoll->collidemillis + ragdollexpiremillis)
-    {
-        int lastmove = d->ragdoll->lastmove;
-        while(d->ragdoll->lastmove + (lastmove == d->ragdoll->lastmove ? ragdolltimestepmin : ragdolltimestepmax) <= lastmillis)
-        {
-            int timestep = min(ragdolltimestepmax, lastmillis - d->ragdoll->lastmove);
-            d->ragdoll->move(d, timestep/1000.0f);
-            d->ragdoll->lastmove += timestep;
-        }
-    }
-
-    vec eye = d->ragdoll->skel->eye >= 0 ? d->ragdoll->verts[d->ragdoll->skel->eye].pos : d->ragdoll->center;
-    eye.add(d->ragdoll->offset);
-    float k = pow(ragdolleyesmooth, float(curtime)/ragdolleyesmoothmillis);
-    d->o.mul(k).add(eye.mul(1-k));
-}
-
-void cleanragdoll(dynent *d)
-{
-    DELETEP(d->ragdoll);
-}
-
diff --git a/engine/rendergl.cpp b/engine/rendergl.cpp
index df4c0ff..2159188 100644
--- a/engine/rendergl.cpp
+++ b/engine/rendergl.cpp
@@ -1,13 +1,13 @@
 // rendergl.cpp: core opengl rendering stuff
 
+#include "pch.h"
 #include "engine.h"
 
-bool hasVBO = false, hasDRE = false, hasOQ = false, hasTR = false, hasFBO = false, hasDS = false, hasTF = false, hasBE = false, hasBC = false, hasCM = false, hasNP2 = false, hasTC = false, hasTE = false, hasMT = false, hasD3 = false, hasAF = false, hasVP2 = false, hasVP3 = false, hasPP = false, hasMDA = false, hasTE3 = false, hasTE4 = false, hasVP = false, hasFP = false, hasGLSL = false, hasGM = false, hasNVFB = false, hasSGIDT = false, hasSGISH = false, hasDT = false, hasSH = false, hasNVPCF = false, hasRN = false, hasPBO = false, hasFBB = false;
-int hasstencil = 0;
+bool hasVBO = false, hasDRE = false, hasOQ = false, hasTR = false, hasFBO = false, hasDS = false, hasTF = false, hasBE = false, hasCM = false, hasNP2 = false, hasTC = false, hasTE = false, hasMT = false, hasD3 = false, hasstencil = false, hasAF = false, hasVP2 = false, hasVP3 = false, hasPP = false, hasMDA = false, hasTE3 = false, hasTE4 = false, hasVP = false, hasFP = false, hasGLSL = false, hasGM = false, hasNVFB = false;
 
 VAR(renderpath, 1, 0, 0);
 
-// GL_ARB_vertex_buffer_object, GL_ARB_pixel_buffer_object
+// GL_ARB_vertex_buffer_object
 PFNGLGENBUFFERSARBPROC       glGenBuffers_       = NULL;
 PFNGLBINDBUFFERARBPROC       glBindBuffer_       = NULL;
 PFNGLMAPBUFFERARBPROC        glMapBuffer_        = NULL;
@@ -62,9 +62,6 @@ PFNGLFRAMEBUFFERTEXTURE2DEXTPROC    glFramebufferTexture2D_    = NULL;
 PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC glFramebufferRenderbuffer_ = NULL;
 PFNGLGENERATEMIPMAPEXTPROC          glGenerateMipmap_          = NULL;
 
-// GL_EXT_framebuffer_blit
-PFNGLBLITFRAMEBUFFEREXTPROC         glBlitFramebuffer_         = NULL;
-
 // GL_ARB_shading_language_100, GL_ARB_shader_objects, GL_ARB_fragment_shader, GL_ARB_vertex_shader
 PFNGLCREATEPROGRAMOBJECTARBPROC       glCreateProgramObject_      = NULL;
 PFNGLDELETEOBJECTARBPROC              glDeleteObject_             = NULL;
@@ -86,22 +83,10 @@ PFNGLDRAWRANGEELEMENTSEXTPROC glDrawRangeElements_ = NULL;
 // GL_EXT_blend_minmax
 PFNGLBLENDEQUATIONEXTPROC glBlendEquation_ = NULL;
 
-// GL_EXT_blend_color
-PFNGLBLENDCOLOREXTPROC glBlendColor_ = NULL;
-
 // GL_EXT_multi_draw_arrays
-PFNGLMULTIDRAWARRAYSEXTPROC   glMultiDrawArrays_   = NULL;
+PFNGLMULTIDRAWARRAYSEXTPROC   glMultiDrawArrays_ = NULL;
 PFNGLMULTIDRAWELEMENTSEXTPROC glMultiDrawElements_ = NULL;
 
-// GL_ARB_texture_compression
-PFNGLCOMPRESSEDTEXIMAGE3DARBPROC    glCompressedTexImage3D_    = NULL;
-PFNGLCOMPRESSEDTEXIMAGE2DARBPROC    glCompressedTexImage2D_    = NULL;
-PFNGLCOMPRESSEDTEXIMAGE1DARBPROC    glCompressedTexImage1D_    = NULL;
-PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC glCompressedTexSubImage3D_ = NULL;
-PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC glCompressedTexSubImage2D_ = NULL;
-PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC glCompressedTexSubImage1D_ = NULL;
-PFNGLGETCOMPRESSEDTEXIMAGEARBPROC   glGetCompressedTexImage_   = NULL;
-
 void *getprocaddress(const char *name)
 {
     return SDL_GL_GetProcAddress(name);
@@ -112,13 +97,11 @@ VAR(ati_texgen_bug, 0, 0, 1);
 VAR(ati_oq_bug, 0, 0, 1);
 VAR(ati_minmax_bug, 0, 0, 1);
 VAR(ati_dph_bug, 0, 0, 1);
-VAR(ati_teximage_bug, 0, 0, 1);
 VAR(nvidia_texgen_bug, 0, 0, 1);
 VAR(nvidia_scissor_bug, 0, 0, 1);
 VAR(apple_glsldepth_bug, 0, 0, 1);
 VAR(apple_ff_bug, 0, 0, 1);
 VAR(apple_vp_bug, 0, 0, 1);
-VAR(sdl_backingstore_bug, -1, 0, 1);
 VAR(intel_quadric_bug, 0, 0, 1);
 VAR(mesa_program_bug, 0, 0, 1);
 VAR(avoidshaders, 1, 0, 0);
@@ -140,8 +123,6 @@ static bool checkseries(const char *s, int low, int high)
     return n >= low && n < high;
 }
 
-VAR(dbgexts, 0, 0, 1);
-
 void gl_checkextensions()
 {
     const char *vendor = (const char *)glGetString(GL_VENDOR);
@@ -154,7 +135,6 @@ void gl_checkextensions()
 #ifdef __APPLE__
     extern int mac_osversion();
     int osversion = mac_osversion();  /* 0x1050 = 10.5 (Leopard) */
-    sdl_backingstore_bug = -1;
 #endif
 
     //extern int shaderprecision;
@@ -168,7 +148,6 @@ void gl_checkextensions()
         if(strstr(exts, "GL_ATI_texture_env_combine3")) hasTE3 = true;
         if(strstr(exts, "GL_NV_texture_env_combine4")) hasTE4 = true;
         if(strstr(exts, "GL_EXT_texture_env_dot3") || strstr(exts, "GL_ARB_texture_env_dot3")) hasD3 = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_env_combine extension.");
     }
     else conoutf(CON_WARN, "WARNING: No texture_env_combine extension! (your video card is WAY too old)");
 
@@ -180,25 +159,10 @@ void gl_checkextensions()
         glMultiTexCoord3f_     = (PFNGLMULTITEXCOORD3FARBPROC)    getprocaddress("glMultiTexCoord3fARB");
         glMultiTexCoord4f_     = (PFNGLMULTITEXCOORD4FARBPROC)    getprocaddress("glMultiTexCoord4fARB");
         hasMT = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_multitexture extension.");
     }
     else conoutf(CON_WARN, "WARNING: No multitexture extension!");
 
-
-    if(strstr(exts, "GL_ARB_vertex_buffer_object")) 
-    {
-        hasVBO = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_vertex_buffer_object extension.");
-    }
-    else conoutf(CON_WARN, "WARNING: No vertex_buffer_object extension! (geometry heavy maps will be SLOW)");
-
-    if(strstr(exts, "GL_ARB_pixel_buffer_object"))
-    {
-        hasPBO = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_pixel_buffer_object extension.");
-    }
-
-    if(hasVBO || hasPBO)
+    if(strstr(exts, "GL_ARB_vertex_buffer_object"))
     {
         glGenBuffers_       = (PFNGLGENBUFFERSARBPROC)      getprocaddress("glGenBuffersARB");
         glBindBuffer_       = (PFNGLBINDBUFFERARBPROC)      getprocaddress("glBindBufferARB");
@@ -208,13 +172,15 @@ void gl_checkextensions()
         glBufferSubData_    = (PFNGLBUFFERSUBDATAARBPROC)   getprocaddress("glBufferSubDataARB");
         glDeleteBuffers_    = (PFNGLDELETEBUFFERSARBPROC)   getprocaddress("glDeleteBuffersARB");
         glGetBufferSubData_ = (PFNGLGETBUFFERSUBDATAARBPROC)getprocaddress("glGetBufferSubDataARB");
+        hasVBO = true;
+        //conoutf(CON_INIT, "Using GL_ARB_vertex_buffer_object extension.");
     }
+    else conoutf(CON_WARN, "WARNING: No vertex_buffer_object extension! (geometry heavy maps will be SLOW)");
 
     if(strstr(exts, "GL_EXT_draw_range_elements"))
     {
         glDrawRangeElements_ = (PFNGLDRAWRANGEELEMENTSEXTPROC)getprocaddress("glDrawRangeElementsEXT");
         hasDRE = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_draw_range_elements extension.");
     }
 
     if(strstr(exts, "GL_EXT_multi_draw_arrays"))
@@ -222,7 +188,6 @@ void gl_checkextensions()
         glMultiDrawArrays_   = (PFNGLMULTIDRAWARRAYSEXTPROC)  getprocaddress("glMultiDrawArraysEXT");
         glMultiDrawElements_ = (PFNGLMULTIDRAWELEMENTSEXTPROC)getprocaddress("glMultiDrawElementsEXT");
         hasMDA = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_multi_draw_arrays extension.");
     }
 
 #ifdef __APPLE__
@@ -232,17 +197,13 @@ void gl_checkextensions()
     if(strstr(exts, "GL_ARB_texture_float") || strstr(exts, "GL_ATI_texture_float"))
     {
         hasTF = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_float extension.");
+        //conoutf("Using GL_ARB_texture_float extension");
         shadowmap = 1;
         extern int smoothshadowmappeel;
         smoothshadowmappeel = 1;
     }
 
-    if(strstr(exts, "GL_NV_float_buffer")) 
-    {
-        hasNVFB = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_NV_float_buffer extension.");
-    }
+    if(strstr(exts, "GL_NV_float_buffer")) hasNVFB = true;
 
     if(strstr(exts, "GL_EXT_framebuffer_object"))
     {
@@ -258,14 +219,7 @@ void gl_checkextensions()
         glFramebufferRenderbuffer_ = (PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC)getprocaddress("glFramebufferRenderbufferEXT");
         glGenerateMipmap_          = (PFNGLGENERATEMIPMAPEXTPROC)         getprocaddress("glGenerateMipmapEXT");
         hasFBO = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_framebuffer_object extension.");
-
-        if(strstr(exts, "GL_EXT_framebuffer_blit"))
-        {
-            glBlitFramebuffer_     = (PFNGLBLITFRAMEBUFFEREXTPROC)        getprocaddress("glBlitFramebufferEXT");
-            hasFBB = true;
-            if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_framebuffer_blit extension.");
-        }
+        //conoutf(CON_INIT, "Using GL_EXT_framebuffer_object extension.");
     }
     else conoutf(CON_WARN, "WARNING: No framebuffer object support. (reflective water may be slow)");
 
@@ -283,7 +237,7 @@ void gl_checkextensions()
             glGetQueryObjectiv_ =  (PFNGLGETQUERYOBJECTIVARBPROC) getprocaddress("glGetQueryObjectivARB");
             glGetQueryObjectuiv_ = (PFNGLGETQUERYOBJECTUIVARBPROC)getprocaddress("glGetQueryObjectuivARB");
             hasOQ = true;
-            if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_occlusion_query extension.");
+            //conoutf(CON_INIT, "Using GL_ARB_occlusion_query extension.");
 #if defined(__APPLE__) && SDL_BYTEORDER == SDL_BIG_ENDIAN
             if(strstr(vendor, "ATI") && (osversion<0x1050)) ati_oq_bug = 1;
 #endif
@@ -299,11 +253,11 @@ void gl_checkextensions()
         waterreflect = 0;
     }
 
-    extern int reservedynlighttc, reserveshadowmaptc, batchlightmaps, ffdynlights;
+    extern int reservedynlighttc, reserveshadowmaptc, maxtexsize, batchlightmaps;
     if(strstr(vendor, "ATI"))
     {
         floatvtx = 1;
-        //conoutf(CON_WARN, "WARNING: ATI cards may show garbage in skybox. (use \"/ati_skybox_bug 1\" to fix)");
+        //conoutf("WARNING: ATI cards may show garbage in skybox. (use \"/ati_skybox_bug 1\" to fix)");
 
         reservedynlighttc = 2;
         reserveshadowmaptc = 3;
@@ -312,13 +266,7 @@ void gl_checkextensions()
         extern int depthfxprecision;
         if(hasTF) depthfxprecision = 1;
 
-        //ati_texgen_bug = 1;
-#if !defined(WIN32) && !defined(__APPLE__)
-        // reported on ATI Radeon HD 4800, Gentoo Linux kernel 2.6.26, Catalyst 9.3, 4-29-09, driver overreads memory on mipmapped GL_RGB textures for base level once max level is specified 
-        // ... doesn't seem to affect Radeon X1300 on Catalyst 9.3, however
-        // TODO: verify if this is fixed in newer Catalyst releases
-        if(strstr(renderer, "Radeon HD")) ati_teximage_bug = 1;
-#endif
+        ati_texgen_bug = 1;
     }
     else if(strstr(vendor, "NVIDIA"))
     {
@@ -326,7 +274,7 @@ void gl_checkextensions()
         rtsharefb = 0; // work-around for strange driver stalls involving when using many FBOs
         extern int filltjoints;
         if(!strstr(exts, "GL_EXT_gpu_shader4")) filltjoints = 0; // DX9 or less NV cards seem to not cause many sparklies
-        
+
         nvidia_texgen_bug = 1;
         if(hasFBO && !hasTF) nvidia_scissor_bug = 1; // 5200 bug, clearing with scissor on an FBO messes up on reflections, may affect lesser cards too 
         extern int fpdepthfx;
@@ -340,7 +288,6 @@ void gl_checkextensions()
         maxtexsize = 256;
         reservevpparams = 20;
         batchlightmaps = 0;
-        ffdynlights = 0;
 
         if(!hasOQ) waterrefract = 0;
 
@@ -348,18 +295,17 @@ void gl_checkextensions()
         apple_vp_bug = 1;
 #endif
     }
-    else if(strstr(vendor, "Tungsten") || strstr(vendor, "Mesa") || strstr(vendor, "DRI") || strstr(vendor, "Microsoft") || strstr(vendor, "S3 Graphics"))
+    else if(strstr(vendor, "Tungsten") || strstr(vendor, "Mesa") || strstr(vendor, "Microsoft") || strstr(vendor, "S3 Graphics"))
     {
         avoidshaders = 1;
         floatvtx = 1;
         maxtexsize = 256;
         reservevpparams = 20;
         batchlightmaps = 0;
-        ffdynlights = 0;
 
         if(!hasOQ) waterrefract = 0;
     }
-    //if(floatvtx) conoutf(CON_WARN, "WARNING: Using floating point vertexes. (use \"/floatvtx 0\" to disable)");
+    //if(floatvtx) conoutf("WARNING: Using floating point vertexes. (use \"/floatvtx 0\" to disable)");
 
     if(strstr(exts, "GL_ARB_vertex_program") && strstr(exts, "GL_ARB_fragment_program"))
     {
@@ -407,15 +353,12 @@ void gl_checkextensions()
         else if(strstr(vendor, "Tungsten")) mesa_program_bug = 1;
 
 #ifdef __APPLE__
-        if(osversion>=0x1050) // fixed in 1055 for some hardware.. but not all..
+        if(osversion>=0x1050)
         {
             apple_ff_bug = 1;
             conoutf(CON_WARN, "WARNING: Using Leopard ARB_position_invariant bug workaround. (use \"/apple_ff_bug 0\" to disable if unnecessary)");
         }
 #endif
-
-        extern int matskel;
-        if(!avoidshaders) matskel = 0;
     }
 
     if(strstr(exts, "GL_NV_vertex_program2_option")) { usevp2 = 1; hasVP2 = true; }
@@ -426,21 +369,20 @@ void gl_checkextensions()
         glProgramEnvParameters4fv_   = (PFNGLPROGRAMENVPARAMETERS4FVEXTPROC)  getprocaddress("glProgramEnvParameters4fvEXT");
         glProgramLocalParameters4fv_ = (PFNGLPROGRAMLOCALPARAMETERS4FVEXTPROC)getprocaddress("glProgramLocalParameters4fvEXT");
         hasPP = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_gpu_program_parameters extension.");
     }
 
     if(strstr(exts, "GL_EXT_texture_rectangle") || strstr(exts, "GL_ARB_texture_rectangle"))
     {
         usetexrect = 1;
         hasTR = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_rectangle extension.");
+        //conoutf(CON_INIT, "Using GL_ARB_texture_rectangle extension.");
     }
     else if(hasMT && hasVP && hasFP) conoutf(CON_WARN, "WARNING: No texture rectangle support. (no full screen shaders)");
 
     if(strstr(exts, "GL_EXT_packed_depth_stencil") || strstr(exts, "GL_NV_packed_depth_stencil"))
     {
         hasDS = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_packed_depth_stencil extension.");
+        //conoutf(CON_INIT, "Using GL_EXT_packed_depth_stencil extension.");
     }
 
     if(strstr(exts, "GL_EXT_blend_minmax"))
@@ -448,14 +390,7 @@ void gl_checkextensions()
         glBlendEquation_ = (PFNGLBLENDEQUATIONEXTPROC) getprocaddress("glBlendEquationEXT");
         hasBE = true;
         if(strstr(vendor, "ATI")) ati_minmax_bug = 1;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_blend_minmax extension.");
-    }
-
-    if(strstr(exts, "GL_EXT_blend_color"))
-    {
-        glBlendColor_ = (PFNGLBLENDCOLOREXTPROC) getprocaddress("glBlendColorEXT");
-        hasBC = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_blend_color extension.");
+        //conoutf(CON_INIT, "Using GL_EXT_blend_minmax extension.");
     }
 
     if(strstr(exts, "GL_ARB_texture_cube_map"))
@@ -464,30 +399,21 @@ void gl_checkextensions()
         glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &val);
         hwcubetexsize = val;
         hasCM = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_cube_map extension.");
+        //conoutf(CON_INIT, "Using GL_ARB_texture_cube_map extension.");
     }
     else conoutf(CON_WARN, "WARNING: No cube map texture support. (no reflective glass)");
 
-    extern int usenp2;
     if(strstr(exts, "GL_ARB_texture_non_power_of_two"))
     {
         hasNP2 = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_texture_non_power_of_two extension.");
+        //conoutf(CON_INIT, "Using GL_ARB_texture_non_power_of_two extension.");
     }
-    else if(usenp2) conoutf(CON_WARN, "WARNING: Non-power-of-two textures not supported!");
+    else conoutf(CON_WARN, "WARNING: Non-power-of-two textures not supported!");
 
-    if(strstr(exts, "GL_ARB_texture_compression") && strstr(exts, "GL_EXT_texture_compression_s3tc"))
+    if(strstr(exts, "GL_EXT_texture_compression_s3tc"))
     {
-        glCompressedTexImage3D_ =    (PFNGLCOMPRESSEDTEXIMAGE3DARBPROC)   getprocaddress("glCompressedTexImage3DARB");
-        glCompressedTexImage2D_ =    (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)   getprocaddress("glCompressedTexImage2DARB");
-        glCompressedTexImage1D_ =    (PFNGLCOMPRESSEDTEXIMAGE1DARBPROC)   getprocaddress("glCompressedTexImage1DARB");
-        glCompressedTexSubImage3D_ = (PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC)getprocaddress("glCompressedTexSubImage3DARB");
-        glCompressedTexSubImage2D_ = (PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC)getprocaddress("glCompressedTexSubImage2DARB");
-        glCompressedTexSubImage1D_ = (PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC)getprocaddress("glCompressedTexSubImage1DARB");
-        glGetCompressedTexImage_ =   (PFNGLGETCOMPRESSEDTEXIMAGEARBPROC)  getprocaddress("glGetCompressedTexImageARB");
-
         hasTC = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_compression_s3tc extension.");
+        //conoutf(CON_INIT, "Using GL_EXT_texture_compression_s3tc extension.");
     }
 
     if(strstr(exts, "GL_EXT_texture_filter_anisotropic"))
@@ -496,64 +422,13 @@ void gl_checkextensions()
        glGetIntegerv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &val);
        hwmaxaniso = val;
        hasAF = true;
-       if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_texture_filter_anisotropic extension.");
+       //conoutf(CON_INIT, "Using GL_EXT_texture_filter_anisotropic extension.");
     }
 
     if(strstr(exts, "GL_SGIS_generate_mipmap"))
     {
         hasGM = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_SGIS_generate_mipmap extension.");
-    }
-
-    if(strstr(exts, "GL_ARB_depth_texture"))
-    {
-        hasSGIDT = hasDT = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_depth_texture extension.");
-    }
-    else if(strstr(exts, "GL_SGIX_depth_texture"))
-    {
-        hasSGIDT = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_SGIX_depth_texture extension.");
-    }
-
-    if(strstr(exts, "GL_ARB_shadow"))
-    {
-        hasSGISH = hasSH = true;
-        if(strstr(vendor, "NVIDIA")) hasNVPCF = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_ARB_shadow extension.");
-    }
-    else if(strstr(exts, "GL_SGIX_shadow"))
-    {
-        hasSGISH = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_SGIX_shadow extension.");
-    }
-
-    if(strstr(exts, "GL_EXT_rescale_normal"))
-    {
-        hasRN = true;
-        if(dbgexts) conoutf(CON_INIT, "Using GL_EXT_rescale_normal extension.");
-    }
-
-    if(!hasSGIDT && !hasSGISH) shadowmap = 0;
-
-    if(strstr(exts, "GL_EXT_gpu_shader4") && !avoidshaders)
-    {
-        // on DX10 or above class cards (i.e. GF8 or RadeonHD) enable expensive features
-        extern int grass, glare, maxdynlights, depthfxsize, depthfxrect, depthfxfilter, blurdepthfx;
-        grass = 1;
-        if(hasOQ)
-        {
-            waterfallrefract = 1;
-            glare = 1;
-            maxdynlights = MAXDYNLIGHTS;
-            if(hasTR)
-            {
-                depthfxsize = 10;
-                depthfxrect = 1;
-                depthfxfilter = 0;
-                blurdepthfx = 0;
-            }
-        }
+        //conoutf(CON_INIT, "Using GL_SGIS_generate_mipmap extension.");
     }
 
     GLint val;
@@ -570,18 +445,21 @@ COMMAND(glext, "s");
 
 void gl_init(int w, int h, int bpp, int depth, int fsaa)
 {
+    #define fogvalues 0.5f, 0.6f, 0.7f, 1.0f
+
     glViewport(0, 0, w, h);
-    glClearColor(0, 0, 0, 0);
+    glClearColor(fogvalues);
     glClearDepth(1);
     glDepthFunc(GL_LESS);
-    glDisable(GL_DEPTH_TEST);
+    glEnable(GL_DEPTH_TEST);
     glShadeModel(GL_SMOOTH);
     
     
-    glDisable(GL_FOG);
+    glEnable(GL_FOG);
     glFogi(GL_FOG_MODE, GL_LINEAR);
+    glFogf(GL_FOG_DENSITY, 0.25f);
     glHint(GL_FOG_HINT, GL_NICEST);
-    GLfloat fogcolor[4] = { 0, 0, 0, 0 };
+    GLfloat fogcolor[4] = { fogvalues };
     glFogfv(GL_FOG_COLOR, fogcolor);
     
 
@@ -589,20 +467,7 @@ void gl_init(int w, int h, int bpp, int depth, int fsaa)
     glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
 
     glCullFace(GL_FRONT);
-    glDisable(GL_CULL_FACE);
-
-#ifdef __APPLE__
-    if(sdl_backingstore_bug)
-    {
-        if(fsaa)
-        {
-            sdl_backingstore_bug = 1;
-            // since SDL doesn't add kCGLPFABackingStore to the pixelformat and so it isn't guaranteed to be preserved - only manifests when using fsaa?
-            //conoutf(CON_WARN, "WARNING: Using SDL backingstore workaround. (use \"/sdl_backingstore_bug 0\" to disable if unnecessary)");
-        }
-        else sdl_backingstore_bug = -1;
-    }
-#endif
+    glEnable(GL_CULL_FACE);
 
     extern int useshaders;
     if(!useshaders || (useshaders<0 && avoidshaders) || !hasMT || !hasVP || !hasFP)
@@ -610,21 +475,20 @@ void gl_init(int w, int h, int bpp, int depth, int fsaa)
         if(!hasMT || !hasVP || !hasFP) conoutf(CON_WARN, "WARNING: No shader support! Using fixed-function fallback. (no fancy visuals for you)");
         else if(useshaders<0 && !hasTF) conoutf(CON_WARN, "WARNING: Disabling shaders for extra performance. (use \"/shaders 1\" to enable shaders if desired)");
         renderpath = R_FIXEDFUNCTION;
-        conoutf(CON_INIT, "Rendering using the OpenGL fixed-function path.");
+        conoutf(CON_INIT, "Rendering using the OpenGL 1.5 fixed-function path.");
         if(ati_texgen_bug) conoutf(CON_WARN, "WARNING: Using ATI texgen bug workaround. (use \"/ati_texgen_bug 0\" to disable if unnecessary)");
         if(nvidia_texgen_bug) conoutf(CON_WARN, "WARNING: Using NVIDIA texgen bug workaround. (use \"/nvidia_texgen_bug 0\" to disable if unnecessary)");
     }
     else
     {
         renderpath = hasGLSL ? R_GLSLANG : R_ASMSHADER;
-        if(renderpath==R_GLSLANG) conoutf(CON_INIT, "Rendering using the OpenGL GLSL shader path.");
-        else conoutf(CON_INIT, "Rendering using the OpenGL assembly shader path.");
+        if(renderpath==R_GLSLANG) conoutf(CON_INIT, "Rendering using the OpenGL 1.5 GLSL shader path.");
+        else conoutf(CON_INIT, "Rendering using the OpenGL 1.5 assembly shader path.");
     }
 
     if(fsaa) glEnable(GL_MULTISAMPLE);
 
     inittmus();
-    setuptexcompress();
 }
 
 void cleanupgl()
@@ -646,7 +510,9 @@ void findorientation()
     vecfromyawpitch(camera1->yaw, camera1->pitch+90, 1, 0, camup);
 
     if(raycubepos(camera1->o, camdir, worldpos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1)
-        worldpos = vec(camdir).mul(2*worldsize).add(camera1->o); //otherwise 3dgui won't work when outside of map
+        worldpos = vec(camdir).mul(2*hdr.worldsize).add(camera1->o); //otherwise 3dgui won't work when outside of map
+
+    setviewcell(camera1->o);
 }
 
 void transplayer()
@@ -664,61 +530,54 @@ void transplayer()
     glTranslatef(-camera1->o.x, -camera1->o.y, -camera1->o.z);   
 }
 
-float curfov = 100, curavatarfov = 65, fovy, aspect;
+float curfov = 100, curhgfov = 65, fovy, aspect;
 int farplane;
 VARP(zoominvel, 0, 250, 5000);
 VARP(zoomoutvel, 0, 100, 5000);
 VARP(zoomfov, 10, 35, 60);
 VARFP(fov, 10, 100, 150, curfov = fov);
-VAR(avatarzoomfov, 10, 25, 60);
-VARF(avatarfov, 10, 65, 150, curavatarfov = 65);
-FVAR(avatardepth, 0, 0.5f, 1);
+VAR(hudgunzoomfov, 10, 25, 60);
+VARF(hudgunfov, 10, 65, 150, curhgfov = 65);
 
 static int zoommillis = 0;
 VARF(zoom, -1, 0, 1,
     if(zoom) zoommillis = totalmillis;
 );
 
-void disablezoom()
-{
-    zoom = 0;
-    zoommillis = totalmillis;
-}
-
 void computezoom()
 {
-    if(!zoom) { curfov = fov; curavatarfov = avatarfov; return; }
+    if(!zoom) { curfov = fov; curhgfov = hudgunfov; return; }
     if(zoom < 0 && curfov >= fov) { zoom = 0; return; } // don't zoom-out if not zoomed-in
     int zoomvel = zoom > 0 ? zoominvel : zoomoutvel,
         oldfov = zoom > 0 ? fov : zoomfov,
         newfov = zoom > 0 ? zoomfov : fov,
-        oldavatarfov = zoom > 0 ? avatarfov : avatarzoomfov,
-        newavatarfov = zoom > 0 ? avatarzoomfov : avatarfov;
+        oldhgfov = zoom > 0 ? hudgunfov : hudgunzoomfov,
+        newhgfov = zoom > 0 ? hudgunzoomfov : hudgunfov;
     float t = zoomvel ? float(zoomvel - (totalmillis - zoommillis)) / zoomvel : 0;
     if(t <= 0) 
     {
         if(!zoomvel && fabs(newfov - curfov) >= 1) 
         {
             curfov = newfov;
-            curavatarfov = newavatarfov;
+            curhgfov = newhgfov;
         }
         zoom = max(zoom, 0);
     }
     else 
     {
         curfov = oldfov*t + newfov*(1 - t);
-        curavatarfov = oldavatarfov*t + newavatarfov*(1 - t);
+        curhgfov = oldhgfov*t + newhgfov*(1 - t);
     }
 }
 
-FVARP(zoomsens, 1e-3f, 1, 100);
+VARP(zoomsens, 1, 1, 10000);
 VARP(zoomautosens, 0, 1, 1);
-FVARP(sensitivity, 1e-3f, 3, 1000);
-FVARP(sensitivityscale, 1e-3f, 1, 1000);
+VARP(sensitivity, 0, 3, 10000);
+VARP(sensitivityscale, 1, 1, 10000);
 VARP(invmouse, 0, 0, 1);
 
 VAR(thirdperson, 0, 0, 2);
-FVAR(thirdpersondistance, 0, 20, 1000);
+VAR(thirdpersondistance, 10, 50, 1000);
 physent *camera1 = NULL;
 bool detachedcamera = false;
 bool isthirdperson() { return player!=camera1 || detachedcamera || reflecting; }
@@ -753,10 +612,10 @@ void mousemove(int dx, int dy)
 
 void recomputecamera()
 {
-    game::setupcamera();
+    cl->setupcamera();
     computezoom();
 
-    bool shoulddetach = thirdperson > 1 || game::detachcamera();
+    bool shoulddetach = thirdperson > 1 || cl->detachcamera();
     if(!thirdperson && !shoulddetach)
     {
         camera1 = player;
@@ -775,77 +634,65 @@ void recomputecamera()
         camera1->reset();
         camera1->type = ENT_CAMERA;
         camera1->move = -1;
-        camera1->eyeheight = camera1->aboveeye = camera1->radius = camera1->xradius = camera1->yradius = 2;
+        camera1->eyeheight = 2;
         
-        vec dir;
-        vecfromyawpitch(camera1->yaw, camera1->pitch, -1, 0, dir);
-        if(game::collidecamera()) 
+        loopi(10)
         {
-            movecamera(camera1, dir, thirdpersondistance, 1);
-            movecamera(camera1, dir, clamp(thirdpersondistance - camera1->o.dist(player->o), 0.0f, 1.0f), 0.1f);
+            if(!moveplayer(camera1, 10, true, thirdpersondistance)) break;
         }
-        else camera1->o.add(vec(dir).mul(thirdpersondistance));
     }
-
-    setviewcell(camera1->o);
 }
 
-FVAR(nearplane, 1e-3f, 0.54f, 1e3f);
-
-void project(float fovy, float aspect, int farplane, bool flipx = false, bool flipy = false, bool swapxy = false, float zscale = 1)
+void project(float fovy, float aspect, int farplane, bool flipx = false, bool flipy = false, bool swapxy = false)
 {
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     if(swapxy) glRotatef(90, 0, 0, 1);
-    if(flipx || flipy!=swapxy || zscale!=1) glScalef(flipx ? -1 : 1, flipy!=swapxy ? -1 : 1, zscale);
-    GLdouble ydist = nearplane * tan(fovy/2*RAD), xdist = ydist * aspect;
-    glFrustum(-xdist, xdist, -ydist, ydist, nearplane, farplane);
+    if(flipx || flipy!=swapxy) glScalef(flipx ? -1 : 1, flipy!=swapxy ? -1 : 1, 1);
+    gluPerspective(fovy, aspect, 0.54f, farplane);
     glMatrixMode(GL_MODELVIEW);
 }
 
-vec calcavatarpos(const vec &pos, float dist)
-{
-    vec eyepos;
-    mvmatrix.transform(pos, eyepos);
-    GLdouble ydist = nearplane * tan(curavatarfov/2*RAD), xdist = ydist * aspect;
-    vec4 scrpos;
-    scrpos.x = eyepos.x*nearplane/xdist;
-    scrpos.y = eyepos.y*nearplane/ydist;
-    scrpos.z = (eyepos.z*(farplane + nearplane) - 2*nearplane*farplane) / (farplane - nearplane);
-    scrpos.w = -eyepos.z;
-
-    vec worldpos;
-    worldpos.x = invmvpmatrix.v[0]*scrpos.x + invmvpmatrix.v[4]*scrpos.y + invmvpmatrix.v[8]*scrpos.z + invmvpmatrix.v[12]*scrpos.w;
-    worldpos.y = invmvpmatrix.v[1]*scrpos.x + invmvpmatrix.v[5]*scrpos.y + invmvpmatrix.v[9]*scrpos.z + invmvpmatrix.v[13]*scrpos.w;
-    worldpos.z = invmvpmatrix.v[2]*scrpos.x + invmvpmatrix.v[6]*scrpos.y + invmvpmatrix.v[10]*scrpos.z + invmvpmatrix.v[14]*scrpos.w;
-    worldpos.div(invmvpmatrix.v[3]*scrpos.x + invmvpmatrix.v[7]*scrpos.y + invmvpmatrix.v[11]*scrpos.z + invmvpmatrix.v[15]*scrpos.w);
-    vec dir = vec(worldpos).sub(camera1->o).rescale(dist);
-    return dir.add(camera1->o);
-}
-
 VAR(reflectclip, 0, 6, 64);
-VAR(reflectclipavatar, -64, 0, 64);
 
-glmatrixf clipmatrix;
+GLfloat clipmatrix[16];
+
+void genclipmatrix(float a, float b, float c, float d, GLfloat matrix[16])
+{
+    // transform the clip plane into camera space
+    float clip[4];
+    loopi(4) clip[i] = a*invmvmatrix[i*4 + 0] + b*invmvmatrix[i*4 + 1] + c*invmvmatrix[i*4 + 2] + d*invmvmatrix[i*4 + 3];
+
+    memcpy(matrix, projmatrix, 16*sizeof(GLfloat));
+
+    float x = ((clip[0]<0 ? -1 : (clip[0]>0 ? 1 : 0)) + matrix[8]) / matrix[0],
+          y = ((clip[1]<0 ? -1 : (clip[1]>0 ? 1 : 0)) + matrix[9]) / matrix[5],
+          w = (1 + matrix[10]) / matrix[14], 
+          scale = 2 / (x*clip[0] + y*clip[1] - clip[2] + w*clip[3]);
+    matrix[2] = clip[0]*scale;
+    matrix[6] = clip[1]*scale; 
+    matrix[10] = clip[2]*scale + 1.0f;
+    matrix[14] = clip[3]*scale;
+}
 
-void pushprojection(const glmatrixf &m)
+void setclipmatrix(GLfloat matrix[16])
 {
     glMatrixMode(GL_PROJECTION);
     glPushMatrix();
-    glLoadMatrixf(m.v);
+    glLoadMatrixf(matrix);
     glMatrixMode(GL_MODELVIEW);
 }
 
-void popprojection()
+void undoclipmatrix()
 {
     glMatrixMode(GL_PROJECTION);
     glPopMatrix();
     glMatrixMode(GL_MODELVIEW);
 }
 
-FVAR(polygonoffsetfactor, -1e4f, -3.0f, 1e4f);
-FVAR(polygonoffsetunits, -1e4f, -3.0f, 1e4f);
-FVAR(depthoffset, -1e4f, 0.01f, 1e4f);
+FVAR(polygonoffsetfactor, -3.0f);
+FVAR(polygonoffsetunits, -3.0f);
+FVAR(depthoffset, 0.01f);
 
 void enablepolygonoffset(GLenum type)
 {
@@ -858,12 +705,13 @@ void enablepolygonoffset(GLenum type)
     
     bool clipped = reflectz < 1e15f && reflectclip;
 
-    glmatrixf offsetmatrix = clipped ? clipmatrix : projmatrix;
+    GLfloat offsetmatrix[16];
+    memcpy(offsetmatrix, clipped ? clipmatrix : projmatrix, 16*sizeof(GLfloat));
     offsetmatrix[14] += depthoffset * projmatrix[10];
 
     glMatrixMode(GL_PROJECTION);
     if(!clipped) glPushMatrix();
-    glLoadMatrixf(offsetmatrix.v);
+    glLoadMatrixf(offsetmatrix);
     glMatrixMode(GL_MODELVIEW);
 }
 
@@ -878,105 +726,13 @@ void disablepolygonoffset(GLenum type)
     bool clipped = reflectz < 1e15f && reflectclip;
 
     glMatrixMode(GL_PROJECTION);
-    if(clipped) glLoadMatrixf(clipmatrix.v);
+    if(clipped) glLoadMatrixf(clipmatrix);
     else glPopMatrix();
     glMatrixMode(GL_MODELVIEW);
 }
 
-void calcspherescissor(const vec &center, float size, float &sx1, float &sy1, float &sx2, float &sy2)
-{
-    vec worldpos(center);
-    if(reflecting) worldpos.z = 2*reflectz - worldpos.z; 
-    vec e(mvmatrix.transformx(worldpos),
-          mvmatrix.transformy(worldpos),
-          mvmatrix.transformz(worldpos));
-    float zz = e.z*e.z, xx = e.x*e.x, yy = e.y*e.y, rr = size*size,
-          dx = zz*(xx + zz) - rr*zz, dy = zz*(yy + zz) - rr*zz,
-          focaldist = 1.0f/tan(fovy*0.5f*RAD);
-    sx1 = sy1 = -1;
-    sx2 = sy2 = 1;
-    #define CHECKPLANE(c, dir, focaldist, low, high) \
-    do { \
-        float nc = (size*e.c dir drt)/(c##c + zz), \
-              nz = (size - nc*e.c)/e.z, \
-              pz = (c##c + zz - rr)/(e.z - nz/nc*e.c); \
-        if(pz < 0) \
-        { \
-            float c = nz*(focaldist)/nc, \
-                  pc = -pz*nz/nc; \
-            if(pc < e.c) low = c; \
-            else if(pc > e.c) high = c; \
-        } \
-    } while(0)
-    if(dx > 0)
-    {
-        float drt = sqrt(dx);
-        CHECKPLANE(x, -, focaldist/aspect, sx1, sx2);
-        CHECKPLANE(x, +, focaldist/aspect, sx1, sx2);
-    }
-    if(dy > 0)
-    {
-        float drt = sqrt(dy);
-        CHECKPLANE(y, -, focaldist, sy1, sy2);
-        CHECKPLANE(y, +, focaldist, sy1, sy2);
-    }
-}
-
-static int scissoring = 0;
-static GLint oldscissor[4];
-
-int pushscissor(float sx1, float sy1, float sx2, float sy2)
-{
-    scissoring = 0;
-
-    if(sx1 <= -1 && sy1 <= -1 && sx2 >= 1 && sy2 >= 1) return 0;
-
-    sx1 = max(sx1, -1.0f);
-    sy1 = max(sy1, -1.0f);
-    sx2 = min(sx2, 1.0f);
-    sy2 = min(sy2, 1.0f);
-
-    GLint viewport[4];
-    glGetIntegerv(GL_VIEWPORT, viewport);
-    int sx = viewport[0] + int(floor((sx1+1)*0.5f*viewport[2])),
-        sy = viewport[1] + int(floor((sy1+1)*0.5f*viewport[3])),
-        sw = viewport[0] + int(ceil((sx2+1)*0.5f*viewport[2])) - sx,
-        sh = viewport[1] + int(ceil((sy2+1)*0.5f*viewport[3])) - sy;
-    if(sw <= 0 || sh <= 0) return 0;
-
-    if(glIsEnabled(GL_SCISSOR_TEST))
-    {
-        glGetIntegerv(GL_SCISSOR_BOX, oldscissor);
-        sw += sx;
-        sh += sy;
-        sx = max(sx, int(oldscissor[0]));
-        sy = max(sy, int(oldscissor[1]));
-        sw = min(sw, int(oldscissor[0] + oldscissor[2])) - sx;
-        sh = min(sh, int(oldscissor[1] + oldscissor[3])) - sy;
-        if(sw <= 0 || sh <= 0) return 0;
-        scissoring = 2;
-    }
-    else scissoring = 1;
-
-    glScissor(sx, sy, sw, sh);
-    if(scissoring<=1) glEnable(GL_SCISSOR_TEST);
-    
-    return scissoring;
-}
-
-void popscissor()
-{
-    if(scissoring>1) glScissor(oldscissor[0], oldscissor[1], oldscissor[2], oldscissor[3]);
-    else if(scissoring) glDisable(GL_SCISSOR_TEST);
-    scissoring = 0;
-}
-
 VARR(fog, 16, 4000, 1000024);
-bvec fogcolor(0x80, 0x99, 0xB3);
-HVARFR(fogcolour, 0, 0x8099B3, 0xFFFFFF,
-{
-    fogcolor = bvec((fogcolour>>16)&0xFF, (fogcolour>>8)&0xFF, fogcolour&0xFF);
-});
+VARR(fogcolour, 0, 0x8099B3, 0xFFFFFF);
 
 void setfogplane(const plane &p, bool flush)
 {
@@ -1027,27 +783,32 @@ static float findsurface(int fogmat, const vec &v, int &abovemat)
         }
         o.z = lu.z + lusize;
     }
-    while(o.z < worldsize);
+    while(o.z < hdr.worldsize);
     abovemat = MAT_AIR;
-    return worldsize;
+    return hdr.worldsize;
 }
 
 static void blendfog(int fogmat, float blend, float logblend, float &start, float &end, float *fogc)
 {
+    uchar col[3];
     switch(fogmat)
     {
         case MAT_WATER:
-            loopk(3) fogc[k] += blend*watercolor[k]/255.0f;
+            getwatercolour(col);
+            loopk(3) fogc[k] += blend*col[k]/255.0f;
             end += logblend*min(fog, max(waterfog*4, 32));
             break;
 
         case MAT_LAVA:
-            loopk(3) fogc[k] += blend*lavacolor[k]/255.0f;
+            getlavacolour(col);
+            loopk(3) fogc[k] += blend*col[k]/255.0f;
             end += logblend*min(fog, max(lavafog*4, 32));
             break;
 
         default:
-            loopk(3) fogc[k] += blend*fogcolor[k]/255.0f;
+            fogc[0] += blend*(fogcolour>>16)/255.0f;
+            fogc[1] += blend*((fogcolour>>8)&255)/255.0f;
+            fogc[2] += blend*(fogcolour&255)/255.0f;
             start += logblend*(fog+64)/8;
             end += logblend*fog;
             break;
@@ -1073,17 +834,20 @@ static void setfog(int fogmat, float below = 1, int abovemat = MAT_AIR)
 
 static void blendfogoverlay(int fogmat, float blend, float *overlay)
 {
+    uchar col[3];
     float maxc;
     switch(fogmat)
     {
         case MAT_WATER:
-            maxc = max(watercolor[0], max(watercolor[1], watercolor[2]));
-            loopk(3) overlay[k] += blend*max(0.4f, watercolor[k]/min(32.0f + maxc*7.0f/8.0f, 255.0f));
+            getwatercolour(col);
+            maxc = max(col[0], max(col[1], col[2]));
+            loopk(3) overlay[k] += blend*max(0.4f, col[k]/min(32.0f + maxc*7.0f/8.0f, 255.0f));
             break;
 
         case MAT_LAVA:
-            maxc = max(lavacolor[0], max(lavacolor[1], lavacolor[2]));
-            loopk(3) overlay[k] += blend*max(0.4f, lavacolor[k]/min(32.0f + maxc*7.0f/8.0f, 255.0f));
+            getlavacolour(col);
+            maxc = max(col[0], max(col[1], col[2]));
+            loopk(3) overlay[k] += blend*max(0.4f, col[k]/min(32.0f + maxc*7.0f/8.0f, 255.0f));
             break;
 
         default:
@@ -1092,49 +856,11 @@ static void blendfogoverlay(int fogmat, float blend, float *overlay)
     }
 }
 
-void drawfogoverlay(int fogmat, float fogblend, int abovemat)
-{
-    notextureshader->set();
-    glDisable(GL_TEXTURE_2D);
-
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_ZERO, GL_SRC_COLOR);
-    float overlay[3] = { 0, 0, 0 };
-    blendfogoverlay(fogmat, fogblend, overlay);
-    blendfogoverlay(abovemat, 1-fogblend, overlay);
-
-    glMatrixMode(GL_PROJECTION);
-    glPushMatrix();
-    glLoadIdentity();
-
-    glMatrixMode(GL_MODELVIEW);
-    glPushMatrix();
-    glLoadIdentity();
-
-    glColor3fv(overlay);
-    glBegin(GL_QUADS);
-    glVertex2f(-1, -1);
-    glVertex2f(1, -1);
-    glVertex2f(1, 1);
-    glVertex2f(-1, 1);
-    glEnd();
-    glDisable(GL_BLEND);
-
-    glMatrixMode(GL_PROJECTION);
-    glPopMatrix();
-
-    glMatrixMode(GL_MODELVIEW);
-    glPopMatrix();
-
-    glEnable(GL_TEXTURE_2D);
-    defaultshader->set();
-}
-
 bool renderedgame = false;
 
-void rendergame(bool mainpass)
+void rendergame()
 {
-    game::rendergame(mainpass);
+    cl->rendergame();
     if(!shadowmapping) renderedgame = true;
 }
 
@@ -1163,17 +889,18 @@ void drawglare()
 
     renderreflectedmapmodels();
     rendergame();
+
+    renderwater();
+    rendermaterials();
+    render_particles(0);
+
     if(!isthirdperson())
     {
-        project(curavatarfov, aspect, farplane, false, false, false, avatardepth);
-        game::renderavatar();
+        project(curhgfov, aspect, farplane);
+        cl->drawhudgun();
         project(fovy, aspect, farplane);
     }
 
-    renderwater();
-    rendermaterials();
-    renderparticles();
-
     glFogf(GL_FOG_START, oldfogstart);
     glFogf(GL_FOG_END, oldfogend);
     glFogfv(GL_FOG_COLOR, oldfogcolor);
@@ -1186,7 +913,9 @@ VARP(reflectmms, 0, 1, 1);
 
 void drawreflection(float z, bool refract, bool clear)
 {
-    float fogc[4] = { watercolor[0]/256.0f, watercolor[1]/256.0f, watercolor[2]/256.0f, 1.0f };
+    uchar wcol[3];
+    getwatercolour(wcol);
+    float fogc[4] = { wcol[0]/256.0f, wcol[1]/256.0f, wcol[2]/256.0f, 1.0f };
 
     if(refract && !waterfog)
     {
@@ -1253,59 +982,37 @@ void drawreflection(float z, bool refract, bool clear)
             if(camera1->o.z>=zclip && camera1->o.z<=z+4.0f) zclip = z;
             if(reflecting) zclip = 2*z - zclip;
         }
-        plane clipplane;
-        invmvmatrix.transposetransform(plane(0, 0, refracting>0 ? 1 : -1, refracting>0 ? -zclip : zclip), clipplane);
-        clipmatrix.clip(clipplane, projmatrix);
-        pushprojection(clipmatrix);
+        genclipmatrix(0, 0, refracting>0 ? 1 : -1, refracting>0 ? -zclip : zclip, clipmatrix);
+        setclipmatrix(clipmatrix);
     }
 
+
     renderreflectedgeom(refracting<0 && z>=0 && caustics, fogging);
 
     if(reflecting || refracting>0 || z<0)
     {
         if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-        if(reflectclip && z>=0) popprojection();
+        if(reflectclip && z>=0) undoclipmatrix();
         drawskybox(farplane, false);
-        if(reflectclip && z>=0) pushprojection(clipmatrix);
+        if(reflectclip && z>=0) setclipmatrix(clipmatrix);
         if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
     }
     else if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
 
-    if(renderpath!=R_FIXEDFUNCTION && fogging) setfogplane(1, z);
-    renderdecals();
-
     if(reflectmms) renderreflectedmapmodels();
     rendergame();
 
-    if(refracting && z>=0 && !isthirdperson() && fabs(camera1->o.z-z) <= 0.5f*(player->eyeheight + player->aboveeye))
-    {   
-        glmatrixf avatarproj;
-        avatarproj.perspective(curavatarfov, aspect, nearplane, farplane);
-        if(reflectclip)
-        {
-            popprojection();
-            glmatrixf avatarclip;
-            plane clipplane;
-            invmvmatrix.transposetransform(plane(0, 0, refracting, reflectclipavatar/4.0f - refracting*z), clipplane);
-            avatarclip.clip(clipplane, avatarproj);
-            pushprojection(avatarclip);
-        }
-        else pushprojection(avatarproj);
-        game::renderavatar();
-        popprojection();
-        if(reflectclip) pushprojection(clipmatrix);
-    }
-
-    if(renderpath!=R_FIXEDFUNCTION && fogging) setfogplane(1, z);
+    if(fogging) setfogplane(1, z);
     if(refracting) rendergrass();
+    renderdecals(0);
     rendermaterials();
-    renderparticles();
+    render_particles(0);
 
     if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
 
-    if(renderpath!=R_FIXEDFUNCTION && fogging) setfogplane();
+    if(fogging) setfogplane();
 
-    if(reflectclip && z>=0) popprojection();
+    if(reflectclip && z>=0) undoclipmatrix();
 
     if(reflecting)
     {
@@ -1353,17 +1060,16 @@ void drawcubemap(int size, const vec &o, float yaw, float pitch, const cubemapsi
 
     glClear(GL_DEPTH_BUFFER_BIT);
 
-    int farplane = worldsize*2;
+    int farplane = hdr.worldsize*2;
 
     project(90.0f, 1.0f, farplane, !side.flipx, !side.flipy, side.swapxy);
 
     transplayer();
 
-    glEnable(GL_FOG);
-    glEnable(GL_CULL_FACE);
-    glEnable(GL_DEPTH_TEST);
     glEnable(GL_TEXTURE_2D);
 
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+
     xtravertsva = xtraverts = glde = gbatches = 0;
 
     visiblecubes(90, 90);
@@ -1384,9 +1090,6 @@ void drawcubemap(int size, const vec &o, float yaw, float pitch, const cubemapsi
 //    rendermaterials();
 
     glDisable(GL_TEXTURE_2D);
-    glDisable(GL_DEPTH_TEST);
-    glDisable(GL_CULL_FACE);
-    glDisable(GL_FOG);
 
     camera1 = oldcamera;
     envmapping = false;
@@ -1399,19 +1102,35 @@ void invalidatepostfx()
     dopostfx = false;
 }
 
-glmatrixf mvmatrix, projmatrix, mvpmatrix, invmvmatrix, invmvpmatrix;
+GLfloat mvmatrix[16], projmatrix[16], mvpmatrix[16], invmvmatrix[16];
 
 void readmatrices()
 {
-    glGetFloatv(GL_MODELVIEW_MATRIX, mvmatrix.v);
-    glGetFloatv(GL_PROJECTION_MATRIX, projmatrix.v);
-    
-    mvpmatrix.mul(projmatrix, mvmatrix);
-    invmvmatrix.invert(mvmatrix);
-    invmvpmatrix.invert(mvpmatrix);
+    glGetFloatv(GL_MODELVIEW_MATRIX, mvmatrix);
+    glGetFloatv(GL_PROJECTION_MATRIX, projmatrix);
+
+    loopi(4) loopj(4)
+    {
+        float c = 0;
+        loopk(4) c += projmatrix[k*4 + j] * mvmatrix[i*4 + k];
+        mvpmatrix[i*4 + j] = c;
+    }
+
+    loopi(3)
+    {
+        loopj(3) invmvmatrix[i*4 + j] = mvmatrix[i + j*4];
+        invmvmatrix[i*4 + 3] = 0;
+    }
+    loopi(3)
+    {
+        float c = 0;
+        loopj(3) c -= mvmatrix[i*4 + j] * mvmatrix[12 + j];
+        invmvmatrix[12 + i] = c;
+    }
+    invmvmatrix[15] = 1;
 }
 
-void gl_drawhud(int w, int h);
+void gl_drawhud(int w, int h, int fogmat, float fogblend, int abovemat);
 
 int xtraverts, xtravertsva;
 
@@ -1419,6 +1138,8 @@ void gl_drawframe(int w, int h)
 {
     defaultshader->set();
 
+    recomputecamera();
+   
     updatedynlights();
 
     aspect = w/float(h);
@@ -1443,18 +1164,16 @@ void gl_drawframe(int w, int h)
         aspect += blend*sinf(lastmillis/1000.0+PI)*0.1f;
     }
 
-    farplane = worldsize*2;
+    farplane = hdr.worldsize*2;
 
     project(fovy, aspect, farplane);
     transplayer();
     readmatrices();
-    findorientation();
 
-    glEnable(GL_FOG);
-    glEnable(GL_CULL_FACE);
-    glEnable(GL_DEPTH_TEST);
     glEnable(GL_TEXTURE_2D);
 
+    glPolygonMode(GL_FRONT_AND_BACK, wireframe && editmode ? GL_LINE : GL_FILL);
+    
     xtravertsva = xtraverts = glde = gbatches = 0;
 
     if(!hasFBO)
@@ -1474,8 +1193,6 @@ void gl_drawframe(int w, int h)
 
     glClear(GL_DEPTH_BUFFER_BIT|(wireframe && editmode ? GL_COLOR_BUFFER_BIT : 0)|(hasstencil ? GL_STENCIL_BUFFER_BIT : 0));
 
-    if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 
-
     if(limitsky()) drawskybox(farplane, true);
 
     rendergeom(causticspass);
@@ -1485,22 +1202,10 @@ void gl_drawframe(int w, int h)
 
     queryreflections();
 
-    generategrass();
-
     if(!limitsky()) drawskybox(farplane, false);
 
-    renderdecals(true);
-
     rendermapmodels();
-    rendergame(true);
-    if(!isthirdperson())
-    {
-        project(curavatarfov, aspect, farplane, false, false, false, avatardepth);
-        game::renderavatar();
-        project(fovy, aspect, farplane);
-    }
-
-    if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    rendergame();
 
     if(hasFBO) 
     {
@@ -1509,23 +1214,26 @@ void gl_drawframe(int w, int h)
         drawreflections();
     }
 
-    if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+    renderdecals(curtime);
 
     renderwater();
     rendergrass();
 
     rendermaterials();
-    renderparticles(true);
+    render_particles(curtime);
 
-    if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    if(!isthirdperson())
+    {
+        project(curhgfov, aspect, farplane);
+        cl->drawhudgun();
+        project(fovy, aspect, farplane);
+    }
 
     glDisable(GL_FOG);
     glDisable(GL_CULL_FACE);
-    glDisable(GL_DEPTH_TEST);
 
     addglare();
-    if(fogmat==MAT_WATER || fogmat==MAT_LAVA) drawfogoverlay(fogmat, fogblend, abovemat);
-    renderpostfx();
+    renderfullscreenshader(w, h);
 
     defaultshader->set();
     g3d_render();
@@ -1533,31 +1241,12 @@ void gl_drawframe(int w, int h)
     glDisable(GL_TEXTURE_2D);
     notextureshader->set();
 
-    gl_drawhud(w, h);
-
-    renderedgame = false;
-}
-
-void gl_drawmainmenu(int w, int h)
-{
-    xtravertsva = xtraverts = glde = gbatches = 0;
+    gl_drawhud(w, h, fogmat, fogblend, abovemat);
 
-    renderbackground(NULL, NULL, NULL, NULL, true, true);
-    renderpostfx();
-    
-    glMatrixMode(GL_PROJECTION);
-    glLoadIdentity();
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-
-    defaultshader->set();
-    glEnable(GL_TEXTURE_2D);
-    g3d_render();
-
-    notextureshader->set();
-    glDisable(GL_TEXTURE_2D);
+    glEnable(GL_CULL_FACE);
+    glEnable(GL_FOG);
 
-    gl_drawhud(w, h);
+    renderedgame = false;
 }
 
 VARNP(damagecompass, usedamagecompass, 0, 1, 1);
@@ -1567,7 +1256,7 @@ VARP(damagecompassalpha, 1, 25, 100);
 VARP(damagecompassmin, 1, 25, 1000);
 VARP(damagecompassmax, 1, 200, 1000);
 
-float dcompass[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+float dcompass[4] = { 0, 0, 0, 0 };
 void damagecompass(int n, const vec &loc)
 {
     if(!usedamagecompass) return;
@@ -1578,16 +1267,16 @@ void damagecompass(int n, const vec &loc)
     else vectoyawpitch(delta, yaw, pitch);
     yaw -= camera1->yaw;
     if(yaw<0) yaw += 360;
-    int dir = (int(yaw+22.5f)%360)/45;
+    int dir = ((int(yaw)+45)%360)/90;
     dcompass[dir] += max(n, damagecompassmin)/float(damagecompassmax);
     if(dcompass[dir]>1) dcompass[dir] = 1;
 
 }
-void drawdamagecompass(int w, int h)
+void drawdamagecompass()
 {
     int dirs = 0;
-    float size = damagecompasssize/100.0f*min(h, w)/2.0f;
-    loopi(8) if(dcompass[i]>0)
+    float size = damagecompasssize/100.0f*min(screen->h, screen->w)/2.0f;
+    loopi(4) if(dcompass[i]>0)
     {
         if(!dirs)
         {
@@ -1597,9 +1286,9 @@ void drawdamagecompass(int w, int h)
         dirs++;
 
         glPushMatrix();
-        glTranslatef(w/2, h/2, 0);
-        glRotatef(i*45, 0, 0, 1);
-        glTranslatef(0, -size/2.0f-min(h, w)/4.0f, 0);
+        glTranslatef(screen->w/2, screen->h/2, 0);
+        glRotatef(i*90, 0, 0, 1);
+        glTranslatef(0, -size/2.0f-min(screen->h, screen->w)/4.0f, 0);
         float logscale = 32,
               scale = log(1 + (logscale - 1)*dcompass[i]) / log(logscale);
         glScalef(size*scale, size*scale, 0);
@@ -1617,52 +1306,14 @@ void drawdamagecompass(int w, int h)
     }
 }
 
-int damageblendmillis = 0;
-
-VARFP(damagescreen, 0, 1, 1, { if(!damagescreen) damageblendmillis = 0; });
-VARP(damagescreenfactor, 1, 7, 100);
-VARP(damagescreenalpha, 1, 45, 100);
-VARP(damagescreenfade, 0, 125, 1000);
-VARP(damagescreenmin, 1, 10, 1000);
-VARP(damagescreenmax, 1, 100, 1000);
-
-void damageblend(int n)
-{
-    if(!damagescreen) return;
-    if(lastmillis > damageblendmillis) damageblendmillis = lastmillis;
-    damageblendmillis += clamp(n, damagescreenmin, damagescreenmax)*damagescreenfactor;
-}
-
-void drawdamagescreen(int w, int h)
-{
-    if(lastmillis >= damageblendmillis) return;
-
-    defaultshader->set();
-    glEnable(GL_TEXTURE_2D);
-
-    static Texture *damagetex = NULL;
-    if(!damagetex) damagetex = textureload("packages/hud/damage.png", 3);
+VARNP(damageblend, usedamageblend, 0, 1, 1);
+VARP(damageblendfactor, 1, 300, 1000);
 
-    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-    glBindTexture(GL_TEXTURE_2D, damagetex->id);
-    float fade = damagescreenalpha/100.0f;
-    if(damageblendmillis - lastmillis < damagescreenfade)
-        fade *= float(damageblendmillis - lastmillis)/damagescreenfade;
-    glColor4f(fade, fade, fade, fade);
+float dblend = 0;
+void damageblend(int n) { if(usedamageblend) dblend += n; }
 
-    glBegin(GL_QUADS);
-    glTexCoord2f(0, 0); glVertex2f(0, 0);
-    glTexCoord2f(1, 0); glVertex2f(w, 0);
-    glTexCoord2f(1, 1); glVertex2f(w, h);
-    glTexCoord2f(0, 1); glVertex2f(0, h);
-    glEnd();
-
-    glDisable(GL_TEXTURE_2D);
-    notextureshader->set();
-}
-
-VAR(hidestats, 0, 0, 1);
-VAR(hidehud, 0, 0, 1);
+VARP(hidestats, 0, 0, 1);
+VARP(hidehud, 0, 0, 1);
 
 VARP(crosshairsize, 0, 15, 50);
 VARP(cursorsize, 0, 30, 50);
@@ -1677,7 +1328,7 @@ void loadcrosshair(const char *name, int i)
 	crosshairs[i] = name ? textureload(name, 3, true) : notexture;
     if(crosshairs[i] == notexture) 
     {
-        name = game::defaultcrosshair(i);
+        name = cl->defaultcrosshair(i);
         if(!name) name = "data/crosshair.png";
         crosshairs[i] = textureload(name, 3, true);
     }
@@ -1690,17 +1341,17 @@ void loadcrosshair_(const char *name, int *i)
 
 COMMANDN(loadcrosshair, loadcrosshair_, "si");
 
-void writecrosshairs(stream *f)
+void writecrosshairs(FILE *f)
 {
     loopi(MAXCROSSHAIRS) if(crosshairs[i] && crosshairs[i]!=notexture)
-        f->printf("loadcrosshair \"%s\" %d\n", crosshairs[i]->name, i);
-    f->printf("\n");
+        fprintf(f, "loadcrosshair \"%s\" %d\n", crosshairs[i]->name, i);
+    fprintf(f, "\n");
 }
 
 void drawcrosshair(int w, int h)
 {
     bool windowhit = g3d_windowhit(true, false);
-    if(!windowhit && (hidehud || mainmenu)) return; //(hidehud || player->state==CS_SPECTATOR || player->state==CS_DEAD)) return;
+    if(!windowhit && hidehud) return; //(hidehud || player->state==CS_SPECTATOR || player->state==CS_DEAD)) return;
 
     float r = 1, g = 1, b = 1, cx = 0.5f, cy = 0.5f, chsize;
     Texture *crosshair;
@@ -1709,12 +1360,12 @@ void drawcrosshair(int w, int h)
         static Texture *cursor = NULL;
         if(!cursor) cursor = textureload("data/guicursor.png", 3, true);
         crosshair = cursor;
-        chsize = cursorsize*w/900.0f;
+        chsize = cursorsize*w/300.0f;
         g3d_cursorpos(cx, cy);
     }
     else
     { 
-        int index = game::selectcrosshair(r, g, b);
+        int index = cl->selectcrosshair(r, g, b);
         if(index < 0) return;
         if(!crosshairfx)
         {
@@ -1727,13 +1378,13 @@ void drawcrosshair(int w, int h)
             loadcrosshair(NULL, index);
             crosshair = crosshairs[index];
         }
-        chsize = crosshairsize*w/900.0f;
+        chsize = crosshairsize*w/300.0f;
     }
-    if(crosshair->bpp==4) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+    if(crosshair->bpp==32) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     else glBlendFunc(GL_ONE, GL_ONE);
     glColor3f(r, g, b);
-    float x = cx*w - (windowhit ? 0 : chsize/2.0f);
-    float y = cy*h - (windowhit ? 0 : chsize/2.0f);
+    float x = cx*w*3.0f - (windowhit ? 0 : chsize/2.0f);
+    float y = cy*h*3.0f - (windowhit ? 0 : chsize/2.0f);
     glBindTexture(GL_TEXTURE_2D, crosshair->id);
     glBegin(GL_QUADS);
     glTexCoord2f(0, 0); glVertex2f(x,          y);
@@ -1745,33 +1396,30 @@ void drawcrosshair(int w, int h)
 
 VARP(showfpsrange, 0, 0, 1);
 VAR(showeditstats, 0, 0, 1);
-VAR(statrate, 1, 200, 1000);
+VAR(statrate, 0, 200, 1000);
 
-void gl_drawhud(int w, int h)
+void gl_drawhud(int w, int h, int fogmat, float fogblend, int abovemat)
 {
-    if(editmode && !hidehud && !mainmenu)
+    if(editmode && !hidehud)
     {
-        glEnable(GL_DEPTH_TEST);
-        glDepthMask(GL_FALSE);
-
-        renderblendbrush();
-
         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-        rendereditcursor();
-        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-
+        glDepthMask(GL_FALSE);
+        cursorupdate();
         glDepthMask(GL_TRUE);
-        glDisable(GL_DEPTH_TEST);
+        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
     }
 
+    glDisable(GL_DEPTH_TEST);
+
+    glMatrixMode(GL_MODELVIEW);
+    glLoadIdentity();
+
     gettextres(w, h);
 
     glMatrixMode(GL_PROJECTION);
     glLoadIdentity();
     glOrtho(0, w, h, 0, -1, 1);
-    glMatrixMode(GL_MODELVIEW);
-    glLoadIdentity();
-    
+
     glColor3f(1, 1, 1);
 
     extern int debugsm;
@@ -1797,34 +1445,54 @@ void gl_drawhud(int w, int h)
 
     glEnable(GL_BLEND);
     
-    if(!mainmenu)
+    if(dblend || fogmat==MAT_WATER || fogmat==MAT_LAVA)
     {
-        drawdamagescreen(w, h);
-        drawdamagecompass(w, h);
+        glBlendFunc(GL_ZERO, GL_SRC_COLOR);
+        if(dblend) 
+        {
+            glColor3f(1.0f, 0.1f, 0.1f);
+            dblend -= curtime*(100.0f/damageblendfactor);
+            if(dblend<0) dblend = 0;
+        }
+        else
+        {
+            float overlay[3] = { 0, 0, 0 };
+            blendfogoverlay(fogmat, fogblend, overlay);
+            blendfogoverlay(abovemat, 1-fogblend, overlay); 
+            glColor3fv(overlay);
+        }
+        glBegin(GL_QUADS);
+        glVertex2f(0, 0);
+        glVertex2f(w, 0);
+        glVertex2f(w, h);
+        glVertex2f(0, h);
+        glEnd();
     }
 
+    drawdamagecompass();
+
     glEnable(GL_TEXTURE_2D);
     defaultshader->set();
 
-    int abovehud = h*3 - FONTH, limitgui = abovehud;
-    if(!hidehud && !mainmenu)
+    glLoadIdentity();
+    glOrtho(0, w*3, h*3, 0, -1, 1);
+
+    drawcrosshair(w, h);
+
+    int abovehud = h*3 - FONTH;
+
+    if(!hidehud)
     {
+        /*int coff = */ renderconsole(w, h);
+        // can render stuff below console output here        
+
         if(!hidestats)
         {
-            glPushMatrix();
-            glScalef(1/3.0f, 1/3.0f, 1);
-
-            static int lastfps = 0, prevfps[3] = { 0, 0, 0 }, curfps[3] = { 0, 0, 0 };
-            if(totalmillis - lastfps >= statrate)
-            {
-                memcpy(prevfps, curfps, sizeof(prevfps));
-                lastfps = totalmillis - (totalmillis%statrate);
-            }
-            int nextfps[3];
-            getfps(nextfps[0], nextfps[1], nextfps[2]);
-            loopi(3) if(prevfps[i]==curfps[i]) curfps[i] = nextfps[i];
-            if(showfpsrange) draw_textf("fps %d+%d-%d", w*3-7*FONTH, h*3-FONTH*3/2, curfps[0], curfps[1], curfps[2]);
-            else draw_textf("fps %d", w*3-5*FONTH, h*3-100, curfps[0]);
+            extern void getfps(int &fps, int &bestdiff, int &worstdiff);
+            int fps, bestdiff, worstdiff;
+            getfps(fps, bestdiff, worstdiff);
+            if(showfpsrange) draw_textf("fps %d+%d-%d", w*3-7*FONTH, h*3-100, fps, bestdiff, worstdiff);
+            else draw_textf("fps %d", w*3-5*FONTH, h*3-100, fps);
 
             if(editmode || showeditstats)
             {
@@ -1850,7 +1518,7 @@ void gl_drawhud(int w, int h)
                 abovehud -= 2*FONTH;
                 draw_textf("wtr:%dk(%d%%) wvt:%dk(%d%%) evt:%dk eva:%dk", FONTH/2, abovehud, wtris/1024, curstats[0], wverts/1024, curstats[1], curstats[2], curstats[3]);
                 draw_textf("ond:%d va:%d gl:%d(%d) oq:%d lm:%d rp:%d pvs:%d", FONTH/2, abovehud+FONTH, allocnodes*8, allocva, curstats[4], curstats[5], curstats[6], lightmaps.length(), curstats[7], getnumviewcells());
-                limitgui = abovehud;
+                g3d_limitscale((2*abovehud - h*3) / float(h*3));
             }
 
             if(editmode)
@@ -1866,32 +1534,26 @@ void gl_drawhud(int w, int h)
                     DELETEA(editinfo);
                 }
             }
-
-            glPopMatrix();
         }
 
-        if(hidestats || (!editmode && !showeditstats))
+        if(!editmode && !showeditstats) 
         {
-            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-            game::gameplayhud(w, h);
-            limitgui = abovehud = min(abovehud, int(h*3*game::abovegameplayhud()));
+            cl->gameplayhud(w, h);
+            abovehud = int(h*3*cl->abovegameplayhud());
+            g3d_limitscale((2*abovehud - h*3) / float(h*3));
         }
 
-        rendertexturepanel(w, h);
+        render_texture_panel(w, h);
     }
-    
-    g3d_limitscale((2*limitgui - h*3) / float(h*3));
+    else g3d_limitscale((2*abovehud - h*3) / float(h*3));
 
-    glPushMatrix();
-    glScalef(1/3.0f, 1/3.0f, 1);
-    abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, w*3-FONTH);
-    extern bool fullconsole;
-    if(!hidehud || fullconsole) renderconsole(w*3, h*3, abovehud - FONTH/2);
-    glPopMatrix();
+    glLoadIdentity();
+    glOrtho(0, w*3, h*3, 0, -1, 1);
 
-    drawcrosshair(w, h);
+    rendercommand(FONTH/2, abovehud - FONTH*3/2, w*3-FONTH);
 
     glDisable(GL_BLEND);
     glDisable(GL_TEXTURE_2D);
+    glEnable(GL_DEPTH_TEST);
 }
 
diff --git a/engine/rendermodel.cpp b/engine/rendermodel.cpp
index ff035d2..58afdb9 100644
--- a/engine/rendermodel.cpp
+++ b/engine/rendermodel.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 VARP(oqdynent, 0, 1, 1);
@@ -5,7 +6,6 @@ VARP(animationinterpolationtime, 0, 150, 1000);
 
 model *loadingmodel = NULL;
 
-#include "ragdoll.h"
 #include "animmodel.h"
 #include "vertmodel.h"
 #include "skelmodel.h"
@@ -19,7 +19,7 @@ model *loadingmodel = NULL;
 void mdlcullface(int *cullface)
 {
     checkmdl;
-    loadingmodel->setcullface(*cullface!=0);
+    loadingmodel->cullface = *cullface!=0;
 }
 
 COMMAND(mdlcullface, "i");
@@ -78,14 +78,6 @@ void mdlalphablend(int *blend)
 
 COMMAND(mdlalphablend, "i");
 
-void mdlalphadepth(int *depth)
-{
-    checkmdl;
-    loadingmodel->alphadepth = *depth!=0;
-}
-
-COMMAND(mdlalphadepth, "i");
-
 void mdlglow(int *percent)
 {
     checkmdl;
@@ -113,6 +105,14 @@ void mdlenvmap(float *envmapmax, float *envmapmin, char *envmap)
 
 COMMAND(mdlenvmap, "ffs");
 
+void mdltranslucent(float *translucency)
+{
+    checkmdl;
+    loadingmodel->settranslucency(*translucency);
+}
+
+COMMAND(mdltranslucent, "f");
+
 void mdlfullbright(float *fullbright)
 {
     checkmdl;
@@ -129,14 +129,13 @@ void mdlshader(char *shader)
 
 COMMAND(mdlshader, "s");
 
-void mdlspin(float *yaw, float *pitch)
+void mdlspin(float *rate)
 {
     checkmdl;
-    loadingmodel->spinyaw = *yaw;
-    loadingmodel->spinpitch = *pitch;
+    loadingmodel->spin = *rate;
 }
 
-COMMAND(mdlspin, "ff");
+COMMAND(mdlspin, "f");
 
 void mdlscale(int *percent)
 {
@@ -191,14 +190,6 @@ void mdlbb(float *rad, float *h, float *eyeheight)
 
 COMMAND(mdlbb, "fff");
 
-void mdlextendbb(float *x, float *y, float *z)
-{
-    checkmdl;
-    loadingmodel->bbextend = vec(*x, *y, *z);
-}
-
-COMMAND(mdlextendbb, "fff");
-
 void mdlname()
 {
     checkmdl;
@@ -207,76 +198,6 @@ void mdlname()
 
 COMMAND(mdlname, "");
 
-#define checkragdoll \
-    skelmodel *m = dynamic_cast<skelmodel *>(loadingmodel); \
-    if(!m) { conoutf(CON_ERROR, "not loading a skeletal model"); return; } \
-    skelmodel::skelmeshgroup *meshes = (skelmodel::skelmeshgroup *)m->parts.last()->meshes; \
-    if(!meshes) return; \
-    skelmodel::skeleton *skel = meshes->skel; \
-    if(!skel->ragdoll) skel->ragdoll = new ragdollskel; \
-    ragdollskel *ragdoll = skel->ragdoll; \
-    if(ragdoll->loaded) return;
-    
-
-void rdvert(float *x, float *y, float *z)
-{
-    checkragdoll;
-    ragdollskel::vert &v = ragdoll->verts.add();
-    v.pos = vec(*x, *y, *z);
-}
-COMMAND(rdvert, "fff");
-
-void rdeye(int *v)
-{
-    checkragdoll;
-    ragdoll->eye = *v;
-}
-COMMAND(rdeye, "i");
-
-void rdtri(int *v1, int *v2, int *v3)
-{
-    checkragdoll;
-    ragdollskel::tri &t = ragdoll->tris.add();
-    t.vert[0] = *v1;
-    t.vert[1] = *v2;
-    t.vert[2] = *v3;
-}
-COMMAND(rdtri, "iii");
-
-void rdjoint(int *n, int *t, char *v1, char *v2, char *v3)
-{
-    checkragdoll;
-    ragdollskel::joint &j = ragdoll->joints.add();
-    j.bone = *n;
-    j.tri = *t;
-    j.vert[0] = v1[0] ? atoi(v1) : -1;
-    j.vert[1] = v2[0] ? atoi(v2) : -1;
-    j.vert[2] = v3[0] ? atoi(v3) : -1;
-}
-COMMAND(rdjoint, "iisss");
-   
-void rdlimitdist(int *v1, int *v2, float *mindist, float *maxdist)
-{
-    checkragdoll;
-    ragdollskel::distlimit &d = ragdoll->distlimits.add();
-    d.vert[0] = *v1;
-    d.vert[1] = *v2;
-    d.mindist = *mindist;
-    d.maxdist = max(*maxdist, *mindist);
-}
-COMMAND(rdlimitdist, "iiff");
-
-void rdlimitrot(int *t1, int *t2, float *maxangle, float *qx, float *qy, float *qz, float *qw)
-{
-    checkragdoll;
-    ragdollskel::rotlimit &r = ragdoll->rotlimits.add();
-    r.tri[0] = *t1;
-    r.tri[1] = *t2;
-    r.maxangle = *maxangle * RAD;
-    r.middle = matrix3x3(quat(*qx, *qy, *qz, *qw));
-}
-COMMAND(rdlimitrot, "iifffff");
- 
 // mapmodels
 
 vector<mapmodelinfo> mapmodels;
@@ -284,7 +205,7 @@ vector<mapmodelinfo> mapmodels;
 void mmodel(char *name)
 {
     mapmodelinfo &mmi = mapmodels.add();
-    copystring(mmi.name, name);
+    s_strcpy(mmi.name, name);
     mmi.m = NULL;
 }
 
@@ -293,11 +214,7 @@ void mapmodelcompat(int *rad, int *h, int *tex, char *name, char *shadow)
     mmodel(name);
 }
 
-void mapmodelreset() 
-{ 
-    if(!overrideidents && !game::allowedittoggle()) return;
-    mapmodels.setsize(0); 
-}
+void mapmodelreset() { mapmodels.setsize(0); }
 
 mapmodelinfo &getmminfo(int i) { return mapmodels.inrange(i) ? mapmodels[i] : *(mapmodelinfo *)0; }
 const char *mapmodelname(int i) { return mapmodels.inrange(i) ? mapmodels[i].name : NULL; }
@@ -311,24 +228,6 @@ ICOMMAND(nummapmodels, "", (), { intret(mapmodels.length()); });
 // model registry
 
 hashtable<const char *, model *> mdllookup;
-vector<const char *> preloadmodels;
-
-void preloadmodel(const char *name)
-{
-    if(mdllookup.access(name)) return;
-    preloadmodels.add(newstring(name));
-}
-
-void flushpreloadedmodels()
-{
-    loopv(preloadmodels)
-    {
-        loadprogress = float(i+1)/preloadmodels.length();
-        loadmodel(preloadmodels[i], -1, true);
-    }
-    preloadmodels.deletecontentsa();
-    loadprogress = 0;
-}
 
 model *loadmodel(const char *name, int i, bool msg)
 {
@@ -346,8 +245,8 @@ model *loadmodel(const char *name, int i, bool msg)
     { 
         if(msg)
         {
-            defformatstring(filename)("packages/models/%s", name);
-            renderprogress(loadprogress, filename);
+            s_sprintfd(filename)("packages/models/%s", name);
+            show_out_of_renderloop_progress(0, filename);
         }
         m = new md2(name);
         loadingmodel = m;
@@ -376,18 +275,12 @@ model *loadmodel(const char *name, int i, bool msg)
             }
         }
         loadingmodel = NULL;
-        mdllookup.access(m->name(), m);
+        mdllookup.access(m->name(), &m);
     }
     if(mapmodels.inrange(i) && !mapmodels[i].m) mapmodels[i].m = m;
     return m;
 }
 
-void preloadmodelshaders()
-{
-    if(initing) return;
-    enumerate(mdllookup, model *, m, m->preloadshaders());
-}
-
 void clear_mdls()
 {
     enumerate(mdllookup, model *, m, delete m);
@@ -402,7 +295,6 @@ void clearmodel(char *name)
 {
     model **m = mdllookup.access(name);
     if(!m) { conoutf("model %s is not loaded", name); return; }
-    loopv(mapmodels) if(mapmodels[i].m==*m) mapmodels[i].m = NULL;
     mdllookup.remove(name);
     (*m)->cleanup();
     delete *m;
@@ -466,12 +358,116 @@ void renderellipse(vec &o, float xradius, float yradius, float yaw)
     glEnable(GL_TEXTURE_2D);
 }
 
+void setshadowmatrix(const plane &p, const vec &dir)
+{
+    float d = p.dot(dir);
+    GLfloat m[16] =
+    {
+        d-dir.x*p.x,       -dir.y*p.x,       -dir.z*p.x,      0, 
+         -dir.x*p.y,      d-dir.y*p.y,       -dir.z*p.y,      0,
+         -dir.x*p.z,       -dir.y*p.z,      d-dir.z*p.z,      0,
+         -dir.x*p.offset,  -dir.y*p.offset,  -dir.z*p.offset, d
+    };
+    glMultMatrixf(m);
+}
+
+VARP(bounddynshadows, 0, 1, 1);
+VARP(dynshadow, 0, 60, 100);
+
+void rendershadow(vec &dir, model *m, int anim, const vec &o, vec center, float radius, float yaw, float pitch, float speed, int basetime, dynent *d, int cull, modelattach *a)
+{
+    vec floor;
+    float dist = rayfloor(center, floor, 0, center.z);
+    if(dist<=0 || dist>=center.z) return;
+    center.z -= dist;
+    if((cull&MDL_CULL_VFC) && refracting<0 && center.z>=reflectz) return;
+    if(vec(center).sub(camera1->o).dot(floor)>0) return;
+
+    vec shaddir; 
+    if(cull&MDL_DYNSHADOW) 
+    {
+        extern vec shadowdir;
+        shaddir = shadowdir;
+        shaddir.normalize();
+    }
+    else
+    {
+        shaddir = dir;
+        shaddir.z = 0;
+        if(!shaddir.iszero()) shaddir.normalize();
+        shaddir.z = 1.5f*(dir.z*0.5f+1);
+        shaddir.normalize();
+    }
+
+    glDisable(GL_TEXTURE_2D);
+    glDepthMask(GL_FALSE);
+    
+    if(!hasFBO || !reflecting || !refracting || hasDS) 
+    {
+        glEnable(GL_STENCIL_TEST);
+
+        if(bounddynshadows)
+        {    
+            nocolorshader->set();
+            glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+            glStencilFunc(GL_ALWAYS, 1, 1);
+            glStencilOp(GL_KEEP, GL_REPLACE, GL_ZERO);
+
+            vec below(center);
+            below.z -= 1.0f;
+            glPushMatrix();
+            setshadowmatrix(plane(floor, -floor.dot(below)), shaddir);
+            glBegin(GL_QUADS);
+            loopi(6) if((shaddir[dimension(i)]>0)==dimcoord(i)) loopj(4)
+            {
+                const ivec &cc = cubecoords[fv[i][j]];
+                glVertex3f(center.x + (cc.x ? 1.5f : -1.5f)*radius,
+                           center.y + (cc.y ? 1.5f : -1.5f)*radius,
+                           cc.z ? center.z + dist + radius : below.z);
+                xtraverts += 4;
+            }
+            glEnd();
+            glPopMatrix();
+
+            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, fading ? GL_FALSE : GL_TRUE);
+        }
+    }
+
+    float intensity = dynshadow/100.0f;
+    if(fogging)
+    {
+        if(renderpath!=R_FIXEDFUNCTION) setfogplane(0, max(0.1f, reflectz-center.z));
+        else intensity *= 1.0f - max(0.0f, min(1.0f, (reflectz - center.z)/waterfog));
+    }
+    if((anim&ANIM_INDEX)==ANIM_DYING) intensity *= max(1.0f - (lastmillis - basetime)/1000.0f, 0.0f);
+    glColor4f(0, 0, 0, intensity);
+
+    if(!hasFBO || !reflecting || !refracting || hasDS)
+    {
+        glStencilFunc(GL_NOTEQUAL, bounddynshadows ? 0 : 1, 1);
+        glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+    }
+
+    vec above(center);
+    above.z += 0.25f;
+
+    glPushMatrix();
+    setshadowmatrix(plane(floor, -floor.dot(above)), shaddir);
+    m->render(anim|ANIM_NOSKIN|ANIM_SHADOW, speed, basetime, o, yaw, pitch, d, a);
+    glPopMatrix();
+
+    glEnable(GL_TEXTURE_2D);
+    glDepthMask(GL_TRUE);
+    
+    if(!hasFBO || !reflecting || !refracting || hasDS) glDisable(GL_STENCIL_TEST);
+}
+
 struct batchedmodel
 {
     vec pos, color, dir;
     int anim;
-    float yaw, pitch, transparent;
-    int basetime, basetime2, flags;
+    float yaw, pitch, speed;
+    int basetime, cull;
     dynent *d;
     int attached;
     occludequery *query;
@@ -479,7 +475,6 @@ struct batchedmodel
 struct modelbatch
 {
     model *m;
-    int flags;
     vector<batchedmodel> batched;
 };  
 static vector<modelbatch *> batches;
@@ -493,7 +488,7 @@ void startmodelbatches()
     modelattached.setsizenodelete(0);
 }
 
-modelbatch &addbatchedmodel(model *m)
+batchedmodel &addbatchedmodel(model *m)
 {
     modelbatch *b = NULL;
     if(m->batch>=0 && m->batch<numbatches && batches[m->batch]->m==m) b = batches[m->batch];
@@ -506,39 +501,50 @@ modelbatch &addbatchedmodel(model *m)
         }
         else b = batches.add(new modelbatch);
         b->m = m;
-        b->flags = 0;
         m->batch = numbatches++;
     }
-    return *b;
+    batchedmodel &bm = b->batched.add();
+    bm.query = modelquery;
+    return bm;
 }
 
 void renderbatchedmodel(model *m, batchedmodel &b)
 {
     modelattach *a = NULL;
     if(b.attached>=0) a = &modelattached[b.attached];
+    if((!shadowmap || renderpath==R_FIXEDFUNCTION) && (b.cull&(MDL_SHADOW|MDL_DYNSHADOW)) && dynshadow && hasstencil && !reflecting && refracting<=0)
+    {
+        vec center;
+        float radius = m->boundsphere(0/*frame*/, center, a); // FIXME
+        center.add(b.pos);
+        rendershadow(b.dir, m, b.anim, b.pos, center, radius, b.yaw, b.pitch, b.speed, b.basetime, b.d, b.cull, a);
+        if((b.cull&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) return;
+    }
 
     int anim = b.anim;
     if(shadowmapping) 
     {
         anim |= ANIM_NOSKIN; 
-        if(renderpath!=R_FIXEDFUNCTION) setenvparamf("shadowintensity", SHPARAM_VERTEX, 1, b.transparent);
+        setenvparamf("shadowintensity", SHPARAM_VERTEX, 1,
+            (anim&ANIM_INDEX)==ANIM_DYING ? max(1.0f - (lastmillis - b.basetime)/1000.0f, 0.0f) : 1.0f);
     }
     else 
     {
-        if(b.flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
+        if(b.cull&MDL_TRANSLUCENT) anim |= ANIM_TRANSLUCENT;
+        if(b.cull&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
     }
 
-    m->render(anim, b.basetime, b.basetime2, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir, b.transparent);
+    m->render(anim, b.speed, b.basetime, b.pos, b.yaw, b.pitch, b.d, a, b.color, b.dir);
 }
 
-struct transparentmodel
+struct translucentmodel
 {
     model *m;
     batchedmodel *batched;
     float dist;
 };
 
-static int sorttransparentmodels(const transparentmodel *x, const transparentmodel *y)
+static int sorttranslucentmodels(const translucentmodel *x, const translucentmodel *y)
 {
     if(x->dist > y->dist) return -1;
     if(x->dist < y->dist) return 1;
@@ -547,41 +553,28 @@ static int sorttransparentmodels(const transparentmodel *x, const transparentmod
 
 void endmodelbatches()
 {
-    vector<transparentmodel> transparent;
+    vector<translucentmodel> translucent;
     loopi(numbatches)
     {
         modelbatch &b = *batches[i];
         if(b.batched.empty()) continue;
-        if(b.flags&(MDL_SHADOW|MDL_DYNSHADOW))
-        {
-            vec center, bbradius;
-            b.m->boundbox(0/*frame*/, center, bbradius); // FIXME
-            loopvj(b.batched)
-            {
-                batchedmodel &bm = b.batched[j];
-                if(bm.flags&(MDL_SHADOW|MDL_DYNSHADOW))
-                    renderblob(bm.flags&MDL_DYNSHADOW ? BLOB_DYNAMIC : BLOB_STATIC, bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos, bm.d ? bm.d->radius : max(bbradius.x, bbradius.y), bm.transparent);
-            }
-            flushblobs();
-        }
         bool rendered = false;
         occludequery *query = NULL;
         loopvj(b.batched) 
         {
             batchedmodel &bm = b.batched[j];
-            if(bm.flags&MDL_CULL_VFC) continue;
             if(bm.query!=query)
             {
                 if(query) endquery(query);
                 query = bm.query;
                 if(query) startquery(query);
             }
-            if(bm.transparent < 1 && (!query || query->owner==bm.d) && !shadowmapping)
+            if(bm.cull&MDL_TRANSLUCENT && (!query || query->owner==bm.d))
             {
-                transparentmodel &tm = transparent.add();
+                translucentmodel &tm = translucent.add();
                 tm.m = b.m;
                 tm.batched = &bm;
-                tm.dist = camera1->o.dist(bm.d && bm.d->ragdoll ? bm.d->ragdoll->center : bm.pos);
+                tm.dist = camera1->o.dist(bm.pos);
                 continue;
             }
             if(!rendered) { b.m->startrender(); rendered = true; }
@@ -590,14 +583,14 @@ void endmodelbatches()
         if(query) endquery(query);
         if(rendered) b.m->endrender();
     }
-    if(transparent.length())
+    if(translucent.length())
     {
-        transparent.sort(sorttransparentmodels);
+        translucent.sort(sorttranslucentmodels);
         model *lastmodel = NULL;
         occludequery *query = NULL;
-        loopv(transparent)
+        loopv(translucent)
         {
-            transparentmodel &tm = transparent[i];
+            translucentmodel &tm = translucent[i];
             if(lastmodel!=tm.m)
             {
                 if(lastmodel) lastmodel->endrender();
@@ -684,31 +677,22 @@ void rendermodelquery(model *m, dynent *d, const vec &center, float radius)
 
 extern int oqfrags;
 
-void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw, float pitch, int flags, dynent *d, modelattach *a, int basetime, int basetime2, float trans)
+void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw, float pitch, int cull, dynent *d, modelattach *a, int basetime, float speed)
 {
-    if(shadowmapping && !(flags&(MDL_SHADOW|MDL_DYNSHADOW))) return;
+    if(shadowmapping && !(cull&(MDL_SHADOW|MDL_DYNSHADOW))) return;
     model *m = loadmodel(mdl); 
     if(!m) return;
-    vec center, bbradius;
+    vec center;
     float radius = 0;
-    bool shadow = !shadowmap && !glaring && (flags&(MDL_SHADOW|MDL_DYNSHADOW)) && showblobs,
-         doOQ = flags&MDL_CULL_QUERY && hasOQ && oqfrags && oqdynent;
-    if(flags&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW))
+    bool shadow = (!shadowmap || renderpath==R_FIXEDFUNCTION) && (cull&(MDL_SHADOW|MDL_DYNSHADOW)) && dynshadow && hasstencil,
+         doOQ = cull&MDL_CULL_QUERY && hasOQ && oqfrags && oqdynent;
+    if(cull&(MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED|MDL_CULL_QUERY|MDL_SHADOW|MDL_DYNSHADOW))
     {
-        m->boundbox(0/*frame*/, center, bbradius); // FIXME
-        radius = bbradius.magnitude();
-        if(d && d->ragdoll)
-        {
-            radius = max(radius, d->ragdoll->radius);
-            center = d->ragdoll->center;
-        }
-        else
-        {
-            center.rotate_around_z((-180-yaw)*RAD);
-            center.add(o);
-        }
-        if(flags&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return;
-        if(flags&MDL_CULL_VFC)
+        radius = m->boundsphere(0/*frame*/, center, a); // FIXME
+        center.rotate_around_z((-180-yaw)*RAD);
+        center.add(o);
+        if(cull&MDL_CULL_DIST && center.dist(camera1->o)/radius>maxmodelradiusdistance) return;
+        if(cull&MDL_CULL_VFC)
         {
             if(reflecting || refracting)
             {
@@ -730,12 +714,12 @@ void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, fl
         {
             if(d)
             {
-                if(flags&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return;
+                if(cull&MDL_CULL_OCCLUDED && d->occluded>=OCCLUDE_PARENT) return;
                 if(doOQ && d->occluded+1>=OCCLUDE_BB && d->query && d->query->owner==d && checkquery(d->query)) return;
             }
             if(!addshadowmapcaster(center, radius, radius)) return;
         }
-        else if(flags&MDL_CULL_OCCLUDED && modeloccluded(center, radius))
+        else if(cull&MDL_CULL_OCCLUDED && modeloccluded(center, radius))
         {
             if(!reflecting && !refracting && d)
             {
@@ -754,9 +738,7 @@ void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, fl
             return;
         }
     }
-
-    if(flags&MDL_NORENDER) anim |= ANIM_NORENDER;
-    else if(showboundingbox && !shadowmapping && !reflecting && !refracting)
+    if(showboundingbox && !shadowmapping && !reflecting && !refracting)
     {
         if(d && showboundingbox==1) 
         {
@@ -777,45 +759,39 @@ void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, fl
     vec lightcolor(1, 1, 1), lightdir(0, 0, 1);
     if(!shadowmapping)
     {
-        vec pos = o;
         if(d) 
         {
             if(!reflecting && !refracting) d->occluded = OCCLUDE_NOTHING;
             if(!light) light = &d->light;
-            if(flags&MDL_LIGHT && light->millis!=lastmillis)
+            if(cull&MDL_LIGHT && light->millis!=lastmillis)
             {
-                if(d->ragdoll)
-                {
-                    pos = d->ragdoll->center;
-                    pos.z += radius/2;
-                }
-                lightreaching(pos, light->color, light->dir);
-                dynlightreaching(pos, light->color, light->dir);
-                game::lighteffects(d, light->color, light->dir);
+                lightreaching(d->o, light->color, light->dir);
+                dynlightreaching(o, light->color, light->dir);
+                cl->lighteffects(d, light->color, light->dir);
                 light->millis = lastmillis;
             }
         }
-        else if(flags&MDL_LIGHT)
+        else if(cull&MDL_LIGHT)
         {
             if(!light) 
             {
-                lightreaching(pos, lightcolor, lightdir);
-                dynlightreaching(pos, lightcolor, lightdir);
+                lightreaching(o, lightcolor, lightdir);
+                dynlightreaching(o, lightcolor, lightdir);
             }
             else if(light->millis!=lastmillis)
             {
-                lightreaching(pos, light->color, light->dir);
-                dynlightreaching(pos, light->color, light->dir);
+                lightreaching(o, light->color, light->dir);
+                dynlightreaching(o, light->color, light->dir);
                 light->millis = lastmillis;
             }
         }
         if(light) { lightcolor = light->color; lightdir = light->dir; }
-        if(flags&MDL_DYNLIGHT) dynlightreaching(pos, lightcolor, lightdir);
+        if(cull&MDL_DYNLIGHT) dynlightreaching(o, lightcolor, lightdir);
     }
 
-    if(a) for(int i = 0; a[i].tag; i++)
+    if(a) for(int i = 0; a[i].name; i++)
     {
-        if(a[i].name) a[i].m = loadmodel(a[i].name);
+        a[i].m = loadmodel(a[i].name);
         //if(a[i].m && a[i].m->type()!=m->type()) a[i].m = NULL;
     }
 
@@ -823,49 +799,41 @@ void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, fl
   
     if(numbatches>=0)
     {
-        modelbatch &mb = addbatchedmodel(m);
-        batchedmodel &b = mb.batched.add();
-        b.query = modelquery;
+        batchedmodel &b = addbatchedmodel(m);
         b.pos = o;
         b.color = lightcolor;
         b.dir = lightdir;
         b.anim = anim;
         b.yaw = yaw;
         b.pitch = pitch;
+        b.speed = speed;
         b.basetime = basetime;
-        b.basetime2 = basetime2;
-        b.transparent = trans;
-        b.flags = flags & ~(MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED);
-        if(!shadow || reflecting || refracting>0) 
-        {
-            b.flags &= ~(MDL_SHADOW|MDL_DYNSHADOW);
-            if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) b.flags |= MDL_CULL_VFC;
-        }
-        mb.flags |= b.flags;
+        b.cull = cull;
         b.d = d;
         b.attached = a ? modelattached.length() : -1;
-        if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].tag) break; }
+        if(a) for(int i = 0;; i++) { modelattached.add(a[i]); if(!a[i].name) break; }
         if(doOQ) d->query = b.query = newquery(d);
         return;
     }
 
+    m->startrender();
+
     if(shadow && !reflecting && refracting<=0)
     {
-        renderblob(flags&MDL_DYNSHADOW ? BLOB_DYNAMIC : BLOB_STATIC, d && d->ragdoll ? center : o, d ? d->radius : max(bbradius.x, bbradius.y), trans);
-        flushblobs();
-        if((flags&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) return;
+        rendershadow(lightdir, m, anim, o, center, radius, yaw, pitch, speed, basetime, d, cull, a);
+        if((cull&MDL_CULL_VFC) && refracting<0 && center.z-radius>=reflectz) { m->endrender(); return; }
     }
 
-    m->startrender();
-
     if(shadowmapping)
     {
         anim |= ANIM_NOSKIN;
-        if(renderpath!=R_FIXEDFUNCTION) setenvparamf("shadowintensity", SHPARAM_VERTEX, 1, trans);
+        setenvparamf("shadowintensity", SHPARAM_VERTEX, 1,
+            (anim&ANIM_INDEX)==ANIM_DYING ? max(1.0f - (lastmillis - basetime)/1000.0f, 0.0f) : 1.0f);
     }
-    else 
+    else
     {
-        if(flags&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
+        if(cull&MDL_TRANSLUCENT) anim |= ANIM_TRANSLUCENT;
+        if(cull&MDL_FULLBRIGHT) anim |= ANIM_FULLBRIGHT;
     }
 
     if(doOQ)
@@ -874,7 +842,7 @@ void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, fl
         if(d->query) startquery(d->query);
     }
 
-    m->render(anim, basetime, basetime2, o, yaw, pitch, d, a, lightcolor, lightdir, trans);
+    m->render(anim, speed, basetime, o, yaw, pitch, d, a, lightcolor, lightdir);
 
     if(doOQ && d->query) endquery(d->query);
 
@@ -919,14 +887,12 @@ void findanims(const char *pattern, vector<int> &anims)
     { 
         "dead", "dying", "idle", 
         "forward", "backward", "left", "right", 
-        "hold 1", "hold 2", "hold 3", "hold 4", "hold 5", "hold 6", "hold 7",
-        "attack 1", "attack 2", "attack 3", "attack 4", "attack 5", "attack 6", "attack 7",
-        "pain", 
+        "punch", "shoot", "pain", 
         "jump", "sink", "swim", 
         "edit", "lag", "taunt", "win", "lose", 
-        "gun idle", "gun shoot",
-        "vwep idle", "vwep shoot", "shield", "powerup", 
-        "mapmodel", "trigger"
+        "gun shoot", "gun idle",
+        "vwep", "shield", "powerup", 
+        "mapmodel", "trigger" 
     };
     loopi(sizeof(names)/sizeof(names[0])) if(matchanim(names[i], pattern)) anims.add(i);
 }
@@ -934,20 +900,20 @@ void findanims(const char *pattern, vector<int> &anims)
 void loadskin(const char *dir, const char *altdir, Texture *&skin, Texture *&masks) // model skin sharing
 {
 #define ifnoload(tex, path) if((tex = textureload(path, 0, true, false))==notexture)
-#define tryload(tex, cmd, name) \
-    ifnoload(tex, makerelpath(mdir, name ".jpg", NULL, cmd)) \
+#define tryload(tex, prefix, name) \
+    ifnoload(tex, makerelpath(mdir, name ".jpg", prefix)) \
     { \
-        ifnoload(tex, makerelpath(mdir, name ".png", NULL, cmd)) \
+        ifnoload(tex, makerelpath(mdir, name ".png", prefix)) \
         { \
-            ifnoload(tex, makerelpath(maltdir, name ".jpg", NULL, cmd)) \
+            ifnoload(tex, makerelpath(maltdir, name ".jpg", prefix)) \
             { \
-                ifnoload(tex, makerelpath(maltdir, name ".png", NULL, cmd)) return; \
+                ifnoload(tex, makerelpath(maltdir, name ".png", prefix)) return; \
             } \
         } \
     }
    
-    defformatstring(mdir)("packages/models/%s", dir);
-    defformatstring(maltdir)("packages/models/%s", altdir);
+    s_sprintfd(mdir)("packages/models/%s", dir);
+    s_sprintfd(maltdir)("packages/models/%s", altdir);
     masks = notexture;
     tryload(skin, NULL, "skin");
     tryload(masks, "<ffmask:25>", "masks");
@@ -959,63 +925,58 @@ VAR(animoverride, -1, 0, NUMANIMS-1);
 VAR(testanims, 0, 0, 1);
 VAR(testpitch, -90, 0, 90);
 
-void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int hold, int attack, int attackdelay, int lastaction, int lastpain, float fade, bool ragdoll)
+void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int attack, int attackdelay, int lastaction, int lastpain, float sink)
 {
-    int anim = hold ? hold : ANIM_IDLE|ANIM_LOOP;
-    float yaw = testanims && d==player ? 0 : d->yaw+90,
-          pitch = testpitch && d==player ? testpitch : d->pitch;
-    vec o = d->feetpos();
+    int anim = ANIM_IDLE|ANIM_LOOP;
+    float yaw = d->yaw, pitch = d->pitch;
+    vec o(d->o);
+    o.z -= d->eyeheight + sink;
     int basetime = 0;
     if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP;
     else if(d->state==CS_DEAD)
     {
+        pitch = 0;
         anim = ANIM_DYING;
         basetime = lastpain;
-        if(ragdoll)
-        {
-            if(!d->ragdoll || d->ragdoll->millis < basetime) anim |= ANIM_RAGDOLL;
-        }
-        else 
-        {
-            pitch *= max(1.0f - (lastmillis-basetime)/500.0f, 0.0f);
-            if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP;
-        }
+        int t = lastmillis-lastpain;
+        if(t<0 || t>20000) return;
+        if(t>1000) { anim = ANIM_DEAD|ANIM_LOOP; if(t>1600) { t -= 1600; o.z -= t*t/10000000000.0f*t/16.0f; } }
+        if(o.z<-1000) return;
     }
     else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP;
     else if(d->state==CS_LAGGED)                            anim = ANIM_LAG|ANIM_LOOP;
     else
     {
-        if(lastmillis-lastpain < 300) 
+        if(lastmillis-lastpain<300) 
         { 
             anim = ANIM_PAIN;
             basetime = lastpain;
         }
-        else if(lastpain < lastaction && (attack < 0 || (d->type != ENT_AI && lastmillis-lastaction < attackdelay)))
+        else if(lastpain < lastaction && (attack<0 || (d->type!=ENT_AI && lastmillis-lastaction<attackdelay)))
         { 
-            anim = attack < 0 ? -attack : attack; 
+            anim = attack<0 ? -attack : attack; 
             basetime = lastaction; 
         }
 
-        if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<<ANIM_SECONDARY;
+        if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((cl->allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<<ANIM_SECONDARY;
         else if(d->timeinair>100) anim |= (ANIM_JUMP|ANIM_END)<<ANIM_SECONDARY;
-        else if(game::allowmove(d) && (d->move || d->strafe)) 
+        else if(cl->allowmove(d)) 
         {
             if(d->move>0) anim |= (ANIM_FORWARD|ANIM_LOOP)<<ANIM_SECONDARY;
             else if(d->strafe) anim |= ((d->strafe>0 ? ANIM_LEFT : ANIM_RIGHT)|ANIM_LOOP)<<ANIM_SECONDARY;
             else if(d->move<0) anim |= (ANIM_BACKWARD|ANIM_LOOP)<<ANIM_SECONDARY;
         }
-        
+
         if((anim&ANIM_INDEX)==ANIM_IDLE && (anim>>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY;
     }
-    if(d->ragdoll && (!ragdoll || anim!=ANIM_DYING)) DELETEP(d->ragdoll);
     if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<<ANIM_SECONDARY;
     int flags = MDL_LIGHT;
-    if(d!=player && !(anim&ANIM_RAGDOLL)) flags |= MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY;
+    if(d!=player) flags |= MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY;
     if(d->type==ENT_PLAYER) flags |= MDL_FULLBRIGHT;
     else flags |= MDL_CULL_DIST;
-    if(d->state==CS_LAGGED) fade = min(fade, 0.3f);
-    else flags |= MDL_DYNSHADOW;
-    rendermodel(NULL, mdlname, anim, o, yaw, pitch, flags, d, attachments, basetime, 0, fade);
+    if(d->state==CS_LAGGED) flags |= MDL_TRANSLUCENT;
+    else if((anim&ANIM_INDEX)!=ANIM_DEAD) flags |= MDL_DYNSHADOW;
+    rendermodel(NULL, mdlname, anim, o, testanims && d==player ? 0 : yaw+90, testpitch && d==player ? testpitch : pitch, flags, d, attachments, basetime);
 }
 
 void setbbfrommodel(dynent *d, const char *mdl)
diff --git a/engine/renderparticles.cpp b/engine/renderparticles.cpp
index 695643a..d321ad3 100644
--- a/engine/renderparticles.cpp
+++ b/engine/renderparticles.cpp
@@ -1,5 +1,6 @@
 // renderparticles.cpp
 
+#include "pch.h"
 #include "engine.h"
 #include "rendertarget.h"
 
@@ -10,76 +11,13 @@ VARP(particlesize, 20, 100, 500);
 // Check emit_particles() to limit the rate that paricles can be emitted for models/sparklies
 // Automatically stops particles being emitted when paused or in reflective drawing
 VARP(emitmillis, 1, 17, 1000);
-static int lastemitframe = 0, emitoffset = 0;
-static bool canemit = false, regenemitters = false, canstep = false;
+static int lastemitframe = 0;
+static bool emit = false;
 
 static bool emit_particles()
 {
     if(reflecting || refracting) return false;
-    return canemit || emitoffset;
-}
-
-VAR(dbgpseed, 0, 0, 1);
-
-struct particleemitter
-{
-    extentity *ent;
-    vec bbmin, bbmax;
-    vec center;
-    float radius;
-    ivec bborigin, bbsize;
-    int maxfade, lastemit, lastcull;
-
-    particleemitter(extentity *ent)
-        : ent(ent), bbmin(ent->o), bbmax(ent->o), maxfade(-1), lastemit(0), lastcull(0)
-    {}
-
-    void finalize()
-    {
-        center = vec(bbmin).add(bbmax).mul(0.5f);
-        radius = bbmin.dist(bbmax)/2;
-        bborigin = ivec(int(floor(bbmin.x)), int(floor(bbmin.y)), int(floor(bbmin.z)));
-        bbsize = ivec(int(ceil(bbmax.x)), int(ceil(bbmax.y)), int(ceil(bbmax.z))).sub(bborigin);
-        if(dbgpseed) conoutf(CON_DEBUG, "radius: %f, maxfade: %d", radius, maxfade);
-    }
-    
-    void extendbb(const vec &o, float size = 0)
-    {
-        bbmin.x = min(bbmin.x, o.x - size);
-        bbmin.y = min(bbmin.y, o.y - size);
-        bbmin.z = min(bbmin.z, o.z - size);
-        bbmax.x = max(bbmax.x, o.x + size);
-        bbmax.y = max(bbmax.y, o.y + size);
-        bbmax.z = max(bbmax.z, o.z + size);
-    }
-
-    void extendbb(float z, float size = 0)
-    {
-        bbmin.z = min(bbmin.z, z - size);
-        bbmax.z = max(bbmax.z, z + size);
-    }
-};
-
-static vector<particleemitter> emitters;
-static particleemitter *seedemitter = NULL;
-
-void clearparticleemitters()
-{
-    emitters.setsize(0);
-    regenemitters = true;
-}
-
-void addparticleemitters()
-{
-    emitters.setsize(0);
-    const vector<extentity *> &ents = entities::getents();
-    loopv(ents)
-    {
-        extentity &e = *ents[i];
-        if(e.type != ET_PARTICLES) continue;
-        emitters.add(particleemitter(&e));
-    }
-    regenemitters = false;
+    return emit;
 }
 
 enum
@@ -100,13 +38,6 @@ enum
     PT_LERP  = 1<<10, // use very sparingly - order of blending issues
     PT_TRACK = 1<<11,
     PT_GLARE = 1<<12,
-    PT_SOFT  = 1<<13,
-    PT_HFLIP = 1<<14,
-    PT_VFLIP = 1<<15,
-    PT_ROT   = 1<<16,
-    PT_CULL  = 1<<17,
-    PT_FEW   = 1<<18,
-    PT_FLIP  = PT_HFLIP | PT_VFLIP | PT_ROT
 };
 
 const char *partnames[] = { "part", "tape", "trail", "text", "textup", "meter", "metervs", "fireball", "lightning", "flare" };
@@ -114,7 +45,7 @@ const char *partnames[] = { "part", "tape", "trail", "text", "textup", "meter",
 struct particle
 {
     vec o, d;
-    int gravity, fade, millis;
+    int fade, millis;
     bvec color;
     uchar flags;
     float size;
@@ -123,11 +54,6 @@ struct particle
         const char *text;         // will call delete[] on this only if it starts with an @
         float val;
         physent *owner;
-        struct
-        {
-            uchar color2[3];
-            uchar progress;
-        };
     }; 
 };
 
@@ -147,10 +73,10 @@ struct partrenderer
     Texture *tex;
     const char *texname;
     uint type;
-    int collide;
+    int grav, collide;
     
-    partrenderer(const char *texname, int type, int collide = 0) 
-        : tex(NULL), texname(texname), type(type), collide(collide)
+    partrenderer(const char *texname, int type, int grav, int collide) 
+        : tex(NULL), texname(texname), type(type), grav(grav), collide(collide)
     {
     }
     virtual ~partrenderer()
@@ -160,8 +86,7 @@ struct partrenderer
     virtual void init(int n) { }
     virtual void reset() = NULL;
     virtual void resettracked(physent *owner) { }   
-    virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity = 0) = NULL;    
-    virtual int adddepthfx(vec &bbmin, vec &bbmax) { return 0; }
+    virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size) = NULL;    
     virtual void update() { }
     virtual void render() = NULL;
     virtual bool haswork() = NULL;
@@ -169,16 +94,12 @@ struct partrenderer
     virtual bool usesvertexarray() { return false; } 
     virtual void cleanup() {}
 
-    virtual void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity)
-    {
-    }
-
     //blend = 0 => remove it
-    void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool step = true)
+    void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool lastpass = true)
     {
         o = p->o;
         d = p->d;
-        if(type&PT_TRACK && p->owner) game::particletrack(p->owner, o, d);
+        if(type&PT_TRACK && p->owner) cl->particletrack(p->owner, o, d);
         if(p->fade <= 5) 
         {
             ts = 1;
@@ -188,14 +109,16 @@ struct partrenderer
         {
             ts = lastmillis-p->millis;
             blend = max(255 - (ts<<8)/p->fade, 0);
-            if(p->gravity)
+            if(grav)
             {
                 if(ts > p->fade) ts = p->fade;
-                float t = ts;
-                o.add(vec(d).mul(t/5000.0f));
-                o.z -= t*t/(2.0f * 5000.0f * p->gravity);
+                float t = (float)(ts);
+                vec v = d;
+                v.mul(t/5000.0f);
+                o.add(v);
+                o.z -= t*t/(2.0f * 5000.0f * grav);
             }
-            if(collide && o.z < p->val && step)
+            if(collide && o.z < p->val && lastpass)
             {
                 vec surface;
                 float floorz = rayfloor(vec(o.x, o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS);
@@ -204,7 +127,7 @@ struct partrenderer
                     p->val = collidez+COLLIDEERROR;
                 else 
                 {
-                    adddecal(collide, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, type&PT_RND4 ? (p->flags>>5)&3 : 0);
+                    adddecal(collide, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, type&PT_RND4 ? detrnd((size_t)p, 4) : 0);
                     blend = 0;
                 }
             }
@@ -217,15 +140,16 @@ struct listparticle : particle
     listparticle *next;
 };
 
+static listparticle *parempty = NULL;
+
 VARP(outlinemeters, 0, 0, 1);
 
 struct listrenderer : partrenderer
 {
-    static listparticle *parempty;
     listparticle *list;
 
-    listrenderer(const char *texname, int type, int collide = 0) 
-        : partrenderer(texname, type, collide), list(NULL)
+    listrenderer(const char *texname, int type, int grav, int collide) 
+        : partrenderer(texname, type, grav, collide), list(NULL)
     {
     }
 
@@ -267,7 +191,7 @@ struct listrenderer : partrenderer
         }
     }
     
-    particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) 
+    particle *addpart(const vec &o, const vec &d, int fade, int color, float size) 
     {
         if(!parempty)
         {
@@ -282,9 +206,8 @@ struct listrenderer : partrenderer
         list = p;
         p->o = o;
         p->d = d;
-        p->gravity = gravity;
         p->fade = fade;
-        p->millis = lastmillis + emitoffset;
+        p->millis = lastmillis;
         p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF);
         p->size = size;
         p->owner = NULL;
@@ -317,16 +240,18 @@ struct listrenderer : partrenderer
             glBindTexture(GL_TEXTURE_2D, tex->id);
         }
         
+        bool lastpass = !reflecting && !refracting;
+        
         for(listparticle **prev = &list, *p = list; p; p = *prev)
         {   
             vec o, d;
             int blend, ts;
-            calc(p, blend, ts, o, d, canstep);
+            calc(p, blend, ts, o, d, lastpass);
             if(blend > 0) 
             {
                 renderpart(p, o, d, blend, ts, p->color.v);
 
-                if(p->fade > 5 || !canstep) 
+                if(p->fade > 5 || !lastpass) 
                 {
                     prev = &p->next;
                     continue;
@@ -343,12 +268,10 @@ struct listrenderer : partrenderer
     }
 };
 
-listparticle *listrenderer::parempty = NULL;
-
 struct meterrenderer : listrenderer
 {
     meterrenderer(int type)
-        : listrenderer(NULL, type)
+        : listrenderer(NULL, type, 0, 0)
     {}
 
     void startrender()
@@ -379,23 +302,19 @@ struct meterrenderer : listrenderer
         float scale = p->size/80.0f;
         glScalef(-scale, scale, -scale);
 
-        float right = 8*FONTH, left = p->progress/100.0f*right;
+        float right = 8*FONTH, left = p->val*right;
         glTranslatef(-right/2.0f, 0, 0);
-
-        if(outlinemeters)
+        glColor3ubv(color);
+        glBegin(GL_TRIANGLE_STRIP);
+        loopk(10)
         {
-            glColor3f(0, 0.8f, 0);
-            glBegin(GL_TRIANGLE_STRIP);
-            loopk(10)
-            {
-                float c = (0.5f + 0.1f)*sinf(k/9.0f*M_PI), s = 0.5f - (0.5f + 0.1f)*cosf(k/9.0f*M_PI);
-                glVertex2f(-c*FONTH, s*FONTH);
-                glVertex2f(right + c*FONTH, s*FONTH);
-            }
-            glEnd();
+            float c = -0.5f*sinf(k/9.0f*M_PI), s = 0.5f + 0.5f*cosf(k/9.0f*M_PI);
+            glVertex2f(left - c*FONTH, s*FONTH);
+            glVertex2f(c*FONTH, s*FONTH);
         }
+        glEnd();
 
-        if(basetype==PT_METERVS) glColor3ubv(p->color2);
+        if(basetype==PT_METERVS) glColor3ub(color[2], color[1], color[0]); //swap r<->b                    
         else glColor3f(0, 0, 0);
         glBegin(GL_TRIANGLE_STRIP);
         loopk(10)
@@ -409,26 +328,28 @@ struct meterrenderer : listrenderer
         if(outlinemeters)
         {
             glColor3f(0, 0.8f, 0);
-            glBegin(GL_TRIANGLE_FAN);
+            glBegin(GL_LINE_LOOP);
+            loopk(10)
+            {
+                float c = -0.5f*sinf(k/9.0f*M_PI), s = 0.5f + 0.5f*cosf(k/9.0f*M_PI);
+                glVertex2f(c*FONTH, s*FONTH);
+            }
+            loopk(10)
+            {
+                float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
+                glVertex2f(right + c*FONTH, s*FONTH);
+            }
+            glEnd();
+           
+            glBegin(GL_LINE_STRIP);
             loopk(10)
             {
-                float c = (0.5f + 0.1f)*sinf(k/9.0f*M_PI), s = 0.5f - (0.5f + 0.1f)*cosf(k/9.0f*M_PI);
+                float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
                 glVertex2f(left + c*FONTH, s*FONTH);
             }
             glEnd();
         }
 
-        glColor3ubv(color);
-        glBegin(GL_TRIANGLE_STRIP);
-        loopk(10)
-        {
-            float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
-            glVertex2f(-c*FONTH, s*FONTH);
-            glVertex2f(left + c*FONTH, s*FONTH);
-        }
-        glEnd();
-
-
         glPopMatrix();
     }
 };
@@ -436,8 +357,8 @@ static meterrenderer meters(PT_METER|PT_LERP), metervs(PT_METERVS|PT_LERP);
 
 struct textrenderer : listrenderer
 {
-    textrenderer(int type)
-        : listrenderer(NULL, type)
+    textrenderer(int type, int grav = 0)
+        : listrenderer(NULL, type, grav, 0)
     {}
 
     void startrender()
@@ -481,7 +402,7 @@ struct textrenderer : listrenderer
         glPopMatrix();
     } 
 };
-static textrenderer texts(PT_TEXT|PT_LERP);
+static textrenderer texts(PT_TEXT|PT_LERP), textups(PT_TEXTUP|PT_LERP, -8);
 
 template<int T>
 static inline void modifyblend(const vec &o, int &blend)
@@ -524,86 +445,22 @@ inline void genpos<PT_TRAIL>(const vec &o, const vec &d, float size, int ts, int
 {
     vec e = d;
     if(grav) e.z -= float(ts)/grav;
-    e.div(-75.0f).add(o);
+    e.div(-75.0f);
+    e.add(o);
     genpos<PT_TAPE>(o, e, size, ts, grav, vs);
 }
 
 template<int T>
-static inline void genrotpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int rot)
-{
-    genpos<T>(o, d, size, grav, ts, vs);
-}
-
-#define ROTCOEFFS(n) { \
-    vec(-1,  1, 0).rotate_around_z(n*2*M_PI/32.0f), \
-    vec( 1,  1, 0).rotate_around_z(n*2*M_PI/32.0f), \
-    vec( 1, -1, 0).rotate_around_z(n*2*M_PI/32.0f), \
-    vec(-1, -1, 0).rotate_around_z(n*2*M_PI/32.0f) \
-}
-static const vec rotcoeffs[32][4] =
-{
-    ROTCOEFFS(0),  ROTCOEFFS(1),  ROTCOEFFS(2),  ROTCOEFFS(3),  ROTCOEFFS(4),  ROTCOEFFS(5),  ROTCOEFFS(6),  ROTCOEFFS(7),
-    ROTCOEFFS(8),  ROTCOEFFS(9),  ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15),
-    ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7),
-    ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31),
-};
-
-template<>
-inline void genrotpos<PT_PART>(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int rot)
-{
-    const vec *coeffs = rotcoeffs[rot];
-    (vs[0].pos = o).add(vec(camright).mul(coeffs[0].x*size)).add(vec(camup).mul(coeffs[0].y*size));
-    (vs[1].pos = o).add(vec(camright).mul(coeffs[1].x*size)).add(vec(camup).mul(coeffs[1].y*size));
-    (vs[2].pos = o).add(vec(camright).mul(coeffs[2].x*size)).add(vec(camup).mul(coeffs[2].y*size));
-    (vs[3].pos = o).add(vec(camright).mul(coeffs[3].x*size)).add(vec(camup).mul(coeffs[3].y*size));
-}
-
-template<int T>
-static inline void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
-{
-    if(grav)
-    {
-        vec end(o);
-        float t = fade;
-        end.add(vec(d).mul(t/5000.0f));
-        end.z -= t*t/(2.0f * 5000.0f * grav);
-        pe.extendbb(end, size);
-
-        float tpeak = d.z*grav;
-        if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
-    }
-}
-
-template<>
-inline void seedpos<PT_TAPE>(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
-{
-    pe.extendbb(d, size);
-}
-
-template<>
-inline void seedpos<PT_TRAIL>(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
-{
-    vec e = d;
-    if(grav) e.z -= float(fade)/grav;
-    e.div(-75.0f).add(o);
-    pe.extendbb(e, size); 
-}
-
-template<int T>
 struct varenderer : partrenderer
 {
     partvert *verts;
     particle *parts;
-    int maxparts, numparts, lastupdate, rndmask;
+    int maxparts, numparts, lastupdate;
 
-    varenderer(const char *texname, int type, int collide = 0) 
-        : partrenderer(texname, type, collide),
-          verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1), rndmask(0)
+    varenderer(const char *texname, int type, int grav, int collide) 
+        : partrenderer(texname, type, grav, collide),
+          verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1)
     {
-        if(type & PT_HFLIP) rndmask |= 0x01;
-        if(type & PT_VFLIP) rndmask |= 0x02;
-        if(type & PT_ROT) rndmask |= 0x1F<<2;
-        if(type & PT_RND4) rndmask |= 0x03<<5;
     }
     
     void init(int n)
@@ -646,41 +503,24 @@ struct varenderer : partrenderer
 
     bool usesvertexarray() { return true; }
 
-    particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) 
+    particle *addpart(const vec &o, const vec &d, int fade, int color, float size) 
     {
         particle *p = parts + (numparts < maxparts ? numparts++ : rnd(maxparts)); //next free slot, or kill a random kitten
         p->o = o;
         p->d = d;
-        p->gravity = gravity;
         p->fade = fade;
-        p->millis = lastmillis + emitoffset;
+        p->millis = lastmillis;
         p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF);
         p->size = size;
         p->owner = NULL;
-        p->flags = 0x80 | (rndmask ? rnd(0x80) & rndmask : 0);
+        p->flags = 0x80;
+        int offset = p-parts;
+        if(type&PT_RND4) p->flags |= detrnd(offset, 4)<<2;
+        if((type&0xFF)==PT_PART) p->flags |= detrnd(offset*offset+37, 4);
         lastupdate = -1;
         return p;
     }
- 
-    void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity)
-    {
-        pe.maxfade = max(pe.maxfade, fade);
-        size *= SQRT2;
-        pe.extendbb(o, size);
-
-        seedpos<T>(pe, o, d, fade, size, gravity);
-        if(!gravity) return;
-
-        vec end(o);
-        float t = fade;
-        end.add(vec(d).mul(t/5000.0f));
-        end.z -= t*t/(2.0f * 5000.0f * gravity);
-        pe.extendbb(end, size);
-
-        float tpeak = d.z*gravity;
-        if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
-    }
- 
+  
     void genverts(particle *p, partvert *vs, bool regen)
     {
         vec o, d;
@@ -695,23 +535,21 @@ struct varenderer : partrenderer
         {
             p->flags &= ~0x80;
 
-            #define SETTEXCOORDS(u1c, u2c, v1c, v2c) \
-            { \
-                float u1 = u1c, u2 = u2c, v1 = v1c, v2 = v2c; \
-                if(p->flags&0x01) swap(u1, u2); \
-                if(p->flags&0x02) swap(v1, v2); \
-                vs[0].u = u1; \
-                vs[0].v = v1; \
-                vs[1].u = u2; \
-                vs[1].v = v1; \
-                vs[2].u = u2; \
-                vs[2].v = v2; \
-                vs[3].u = u1; \
-                vs[3].v = v2; \
-            }
+            int orient = p->flags&3;
+            #define SETTEXCOORDS(u1, u2, v1, v2) \
+            do { \
+                vs[orient].u       = u1; \
+                vs[orient].v       = v2; \
+                vs[(orient+1)&3].u = u2; \
+                vs[(orient+1)&3].v = v2; \
+                vs[(orient+2)&3].u = u2; \
+                vs[(orient+2)&3].v = v1; \
+                vs[(orient+3)&3].u = u1; \
+                vs[(orient+3)&3].v = v1; \
+            } while(0)    
             if(type&PT_RND4)
             {
-                float tx = 0.5f*((p->flags>>5)&1), ty = 0.5f*((p->flags>>6)&1);
+                float tx = 0.5f*((p->flags>>2)&1), ty = 0.5f*((p->flags>>3)&1);
                 SETTEXCOORDS(tx, tx + 0.5f, ty, ty + 0.5f);
             } 
             else SETTEXCOORDS(0, 1, 0, 1);
@@ -728,8 +566,7 @@ struct varenderer : partrenderer
         else if(type&PT_MOD) SETMODCOLOR;
         else loopi(4) vs[i].alpha = blend;
 
-        if(type&PT_ROT) genrotpos<T>(o, d, p->size, ts, p->gravity, vs, (p->flags>>2)&0x1F);
-        else genpos<T>(o, d, p->size, ts, p->gravity, vs);
+        genpos<T>(o, d, p->size, ts, grav, vs); 
     }
 
     void update()
@@ -770,111 +607,46 @@ typedef varenderer<PT_PART> quadrenderer;
 typedef varenderer<PT_TAPE> taperenderer;
 typedef varenderer<PT_TRAIL> trailrenderer;
 
-#include "depthfx.h"
 #include "explosion.h"
 #include "lensflare.h"
 #include "lightning.h"
 
-struct softquadrenderer : quadrenderer
-{
-    softquadrenderer(const char *texname, int type, int collide = 0)
-        : quadrenderer(texname, type|PT_SOFT, collide)
-    {
-    }
-
-    int adddepthfx(vec &bbmin, vec &bbmax)
-    {
-        if(!depthfxtex.highprecision() && !depthfxtex.emulatehighprecision()) return 0;
-        int numsoft = 0;
-        loopi(numparts)
-        {
-            particle &p = parts[i];
-            float radius = p.size*SQRT2;
-            vec o, d;
-            int blend, ts;
-            calc(&p, blend, ts, o, d, false);
-            if(depthfxscissor==2 ? depthfxtex.addscissorbox(o, radius) : isvisiblesphere(radius, o) < VFC_FOGGED) 
-            {
-                numsoft++;
-                loopk(3)
-                {
-                    bbmin[k] = min(bbmin[k], o[k] - radius);
-                    bbmax[k] = max(bbmax[k], o[k] + radius);
-                }
-            }
-        }
-        return numsoft;
-    }
-};
-
 static partrenderer *parts[] = 
 {
-    new quadrenderer("packages/particles/blood.png", PT_PART|PT_FLIP|PT_MOD|PT_RND4, DECAL_BLOOD), // blood spats (note: rgb is inverted) 
-    new trailrenderer("packages/particles/base.png", PT_TRAIL|PT_LERP),                            // water, entity
-    new quadrenderer("packages/particles/smoke.png", PT_PART|PT_FLIP|PT_LERP),                     // smoke
-    new quadrenderer("packages/particles/steam.png", PT_PART|PT_FLIP),                             // steam
-    new quadrenderer("packages/particles/flames.png", PT_PART|PT_HFLIP|PT_RND4|PT_GLARE),          // flame on - no flipping please, they have orientation
-    new quadrenderer("packages/particles/ball1.png", PT_PART|PT_FEW|PT_GLARE),                     // fireball1
-    new quadrenderer("packages/particles/ball2.png", PT_PART|PT_FEW|PT_GLARE),                     // fireball2
-    new quadrenderer("packages/particles/ball3.png", PT_PART|PT_FEW|PT_GLARE),                     // fireball3
-    new taperenderer("packages/particles/flare.jpg", PT_TAPE|PT_GLARE),                            // streak
-    &lightnings,                                                                                   // lightning
-    &fireballs,                                                                                    // explosion fireball
-    &noglarefireballs,                                                                             // explosion fireball no glare
-    new quadrenderer("packages/particles/spark.png", PT_PART|PT_FLIP|PT_GLARE),                    // sparks
-    new quadrenderer("packages/particles/base.png",  PT_PART|PT_FLIP|PT_GLARE),                    // edit mode entities
-    new quadrenderer("packages/particles/muzzleflash1.jpg", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash
-    new quadrenderer("packages/particles/muzzleflash2.jpg", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash
-    new quadrenderer("packages/particles/muzzleflash3.jpg", PT_PART|PT_FEW|PT_FLIP|PT_GLARE|PT_TRACK), // muzzle flash
-    &texts,                                                                                        // text
-    &meters,                                                                                       // meter
-    &metervs,                                                                                      // meter vs.
-    &flares                                                                                        // lens flares - must be done last
+    new quadrenderer("packages/particles/blood.png", PT_PART|PT_MOD|PT_RND4,   2, 1), // 0 blood spats (note: rgb is inverted) 
+    new quadrenderer("packages/particles/spark.png", PT_PART|PT_GLARE,   2, 0), // 1 sparks
+    new quadrenderer("packages/particles/smoke.png", PT_PART,          -20, 0), // 2 small slowly rising smoke
+    new quadrenderer("packages/particles/base.png",  PT_PART|PT_GLARE,  20, 0), // 3 edit mode entities
+    new quadrenderer("packages/particles/ball1.png", PT_PART|PT_GLARE,  20, 0), // 4 fireball1
+    new quadrenderer("packages/particles/smoke.png", PT_PART,          -20, 0), // 5 big  slowly rising smoke   
+    new quadrenderer("packages/particles/ball2.png", PT_PART|PT_GLARE,  20, 0), // 6 fireball2
+    new quadrenderer("packages/particles/ball3.png", PT_PART|PT_GLARE,  20, 0), // 7 big fireball3
+    &textups,                                                            // 8 TEXT, floats up
+    new taperenderer("packages/particles/flare.jpg", PT_TAPE|PT_GLARE,  0, 0), // 9 streak
+    &texts,                                                              // 10 TEXT, SMALL, NON-MOVING
+    &meters,                                                             // 11 METER, SMALL, NON-MOVING
+    &metervs,                                                            // 12 METER vs., SMALL, NON-MOVING
+    new quadrenderer("packages/particles/smoke.png", PT_PART,           20, 0), // 13 small  slowly sinking smoke trail
+    &fireballs,                                                          // 14 explosion fireball
+    &lightnings,                                                         // 15 lightning
+    new quadrenderer("packages/particles/smoke.png", PT_PART,          -15, 0), // 16 big  fast rising smoke          
+    new trailrenderer("packages/particles/base.png", PT_TRAIL|PT_LERP,   2, 0), // 17 water, entity
+    &noglarefireballs,                                                   // 18 explosion fireball no glare
+    &flares // must be done last
 };
 
-void finddepthfxranges()
-{
-    depthfxmin = vec(1e16f, 1e16f, 1e16f);
-    depthfxmax = vec(0, 0, 0);
-    numdepthfxranges = fireballs.finddepthfxranges(depthfxowners, depthfxranges, MAXDFXRANGES, depthfxmin, depthfxmax);
-    loopk(3)
-    {
-        depthfxmin[k] -= depthfxmargin;
-        depthfxmax[k] += depthfxmargin;
-    }
-    if(depthfxparts)
-    {
-        loopi(sizeof(parts)/sizeof(parts[0]))
-        {
-            partrenderer *p = parts[i];
-            if(p->type&PT_SOFT && p->adddepthfx(depthfxmin, depthfxmax))
-            {
-                if(!numdepthfxranges)
-                {
-                    numdepthfxranges = 1;
-                    depthfxowners[0] = NULL;
-                    depthfxranges[0] = 0;
-                }
-            }
-        }
-    }              
-    if(depthfxscissor<2 && numdepthfxranges>0) depthfxtex.addscissorbox(depthfxmin, depthfxmax);
-}
- 
 VARFP(maxparticles, 10, 4000, 40000, particleinit());
-VARFP(fewparticles, 10, 100, 40000, particleinit());
 
 void particleinit() 
 {
     if(!particleshader) particleshader = lookupshaderbyname("particle");
     if(!particlenotextureshader) particlenotextureshader = lookupshaderbyname("particlenotexture");
-    loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(parts[i]->type&PT_FEW ? min(fewparticles, maxparticles) : maxparticles);
+    loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(maxparticles);
 }
 
 void clearparticles()
 {   
     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->reset();
-    clearparticleemitters();
 }   
 
 void cleanupparticles()
@@ -887,13 +659,12 @@ void removetrackedparticles(physent *owner)
     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->resettracked(owner);
 }
 
-VARP(particleglare, 0, 2, 100);
+VARP(particleglare, 0, 4, 100);
 
 VAR(debugparticles, 0, 0, 1);
 
-void renderparticles(bool mainpass)
+void render_particles(int time)
 {
-    canstep = mainpass;
     //want to debug BEFORE the lastpass render (that would delete particles)
     if(debugparticles && !glaring && !reflecting && !refracting) 
     {
@@ -901,7 +672,7 @@ void renderparticles(bool mainpass)
         glMatrixMode(GL_PROJECTION);
         glPushMatrix();
         glLoadIdentity();
-        glOrtho(0, FONTH*n*2*screen->w/float(screen->h), FONTH*n*2, 0, -1, 1); //squeeze into top-left corner        
+        glOrtho(0, FONTH*n*2, FONTH*n*2, 0, -1, 1); //squeeze into top-left corner        
         glMatrixMode(GL_MODELVIEW);
         glPushMatrix();
         glLoadIdentity();
@@ -913,15 +684,13 @@ void renderparticles(bool mainpass)
             int type = parts[i]->type;
             const char *title = parts[i]->texname ? strrchr(parts[i]->texname, '/')+1 : NULL;
             string info = "";
-            if(type&PT_GLARE) concatstring(info, "g,");
-            if(type&PT_LERP) concatstring(info, "l,");
-            if(type&PT_MOD) concatstring(info, "m,");
-            if(type&PT_RND4) concatstring(info, "r,");
-            if(type&PT_TRACK) concatstring(info, "t,");
-            if(type&PT_FLIP) concatstring(info, "f,");
-            if(parts[i]->collide) concatstring(info, "c,");
-            if(info[0]) info[strlen(info)-1] = '\0';
-            defformatstring(ds)("%d\t%s(%s) %s", parts[i]->count(), partnames[type&0xFF], info, title ? title : "");
+            if(type&PT_GLARE) s_strcat(info, "g,");
+            if(type&PT_LERP) s_strcat(info, "l,");
+            if(type&PT_MOD) s_strcat(info, "m,");
+            if(type&PT_RND4) s_strcat(info, "r,");
+            if(type&PT_TRACK) s_strcat(info, "t,");
+            if(parts[i]->collide) s_strcat(info, "c,");
+            s_sprintfd(ds)("%d\t%s(%s%d) %s", parts[i]->count(), partnames[type&0xFF], info, parts[i]->grav, (title?title:""));
             draw_text(ds, FONTH, (i+n/2)*FONTH);
         }
         glDisable(GL_BLEND);
@@ -943,10 +712,8 @@ void renderparticles(bool mainpass)
     static float zerofog[4] = { 0, 0, 0, 1 };
     float oldfogc[4];
     bool rendered = false;
-    uint lastflags = PT_LERP, flagmask = PT_LERP|PT_MOD;
-   
-    if(binddepthfxtex()) flagmask |= PT_SOFT;
-
+    uint lastflags = PT_LERP;
+    
     loopi(sizeof(parts)/sizeof(parts[0]))
     {
         partrenderer *p = parts[i];
@@ -967,7 +734,7 @@ void renderparticles(bool mainpass)
             glGetFloatv(GL_FOG_COLOR, oldfogc);
         }
         
-        uint flags = p->type & flagmask;
+        uint flags = p->type & (PT_LERP|PT_MOD);
         if(p->usesvertexarray()) flags |= 0x01; //0x01 = VA marker
         uint changedbits = (flags ^ lastflags);
         if(changedbits != 0x0000)
@@ -994,25 +761,6 @@ void renderparticles(bool mainpass)
                 else if(flags&PT_MOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
                 else glBlendFunc(GL_SRC_ALPHA, GL_ONE);
             }
-            if(changedbits&PT_SOFT)
-            {
-                if(flags&PT_SOFT)
-                {
-                    if(depthfxtex.target==GL_TEXTURE_RECTANGLE_ARB)
-                    {
-                        if(!depthfxtex.highprecision()) SETSHADER(particlesoft8rect);
-                        else SETSHADER(particlesoftrect);
-                    }
-                    else
-                    {
-                        if(!depthfxtex.highprecision()) SETSHADER(particlesoft8);
-                        else SETSHADER(particlesoft);
-                    }
-
-                    binddepthfxparams(depthfxpartblend);
-                }
-                else particleshader->set();
-            }
             lastflags = flags;        
         }
         p->render();
@@ -1033,26 +781,16 @@ void renderparticles(bool mainpass)
     }
 }
 
-static int addedparticles = 0;
-
-static inline particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size, int gravity = 0)
+static inline particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size)
 {
-    static particle dummy;
-    if(seedemitter) 
-    {
-        parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity);
-        return &dummy;
-    }
-    if(fade + emitoffset < 0) return &dummy;
-    addedparticles++;
-    return parts[type]->addpart(o, d, fade, color, size, gravity);
+    return parts[type]->addpart(o, d, fade, color, size);
 }
 
 VARP(maxparticledistance, 256, 1024, 4096);
 
-static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity)
+static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size)
 {
-    if(camera1->o.dist(p) > maxparticledistance && !seedemitter) return;
+    if(camera1->o.dist(p) > maxparticledistance) return;
     float collidez = parts[type]->collide ? p.z - raycube(p, vec(0, 0, -1), COLLIDERADIUS, RAY_CLIPMAT) + COLLIDEERROR : -1; 
     int fmin = 1;
     int fmax = fade*3;
@@ -1068,36 +806,81 @@ static void splash(int type, int color, int radius, int num, int fade, const vec
         while(x*x+y*y+z*z>radius*radius);
     	vec tmp = vec((float)x, (float)y, (float)z);
         int f = (num < 10) ? (fmin + rnd(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random
-        newparticle(p, tmp, f, type, color, size, gravity)->val = collidez;
+        newparticle(p, tmp, f, type, color, size)->val = collidez;
     }
 }
 
-static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity, int delay = 0) 
+static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int delay=0) 
 {
     if(!emit_particles() || (delay > 0 && rnd(delay) != 0)) return;
-    splash(type, color, radius, num, fade, p, size, gravity);
+    splash(type, color, radius, num, fade, p, size);
 }
 
-bool canaddparticles()
+//maps 'classic' particles types to newer types and colors
+// @NOTE potentially this and the following public funcs can be tidied up, but lets please defer that for a little bit...
+static struct partmap { int type; int color; float size; } partmaps[] = 
 {
-    return !shadowmapping && !renderedgame;
+    {  1, 0xB49B4B, 0.24f}, // 0 yellow: sparks 
+    {  2, 0x897661, 0.6f }, // 1 greyish-brown:   small slowly rising smoke
+    {  3, 0x3232FF, 0.32f}, // 2 blue:   edit mode entities
+    {  0, 0x60FFFF, 2.96f}, // 3 red:    blood spats (note: rgb is inverted)
+    {  4, 0xFFC8C8, 4.8f }, // 4 yellow: fireball1
+    {  5, 0x897661, 2.4f }, // 5 greyish-brown:   big  slowly rising smoke   
+    {  6, 0xFFFFFF, 4.8f }, // 6 blue:   fireball2
+    {  7, 0xFFFFFF, 4.8f }, // 7 green:  big fireball3
+    {  8, 0xFF4B19, 4.0f }, // 8 TEXT RED
+    {  8, 0x32FF64, 4.0f }, // 9 TEXT GREEN
+    {  9, 0xFFC864, 0.28f}, // 10 yellow flare
+    { 10, 0x1EC850, 2.0f }, // 11 TEXT DARKGREEN, SMALL, NON-MOVING
+    {  7, 0xFFFFFF, 2.0f }, // 12 green small fireball3
+    { 10, 0xFF4B19, 2.0f }, // 13 TEXT RED, SMALL, NON-MOVING
+    { 10, 0xB4B4B4, 2.0f }, // 14 TEXT GREY, SMALL, NON-MOVING
+    {  8, 0xFFC864, 4.0f }, // 15 TEXT YELLOW
+    { 10, 0x6496FF, 2.0f }, // 16 TEXT BLUE, SMALL, NON-MOVING
+    { 11, 0xFF1932, 2.0f }, // 17 METER RED, SMALL, NON-MOVING
+    { 11, 0x3219FF, 2.0f }, // 18 METER BLUE, SMALL, NON-MOVING
+    { 12, 0xFF1932, 2.0f }, // 19 METER RED vs. BLUE, SMALL, NON-MOVING (note swaps r<->b)
+    { 12, 0x3219FF, 2.0f }, // 20 METER BLUE vs. RED, SMALL, NON-MOVING (note swaps r<->b)
+    { 13, 0x897661, 0.6f }, // 21 greyish-brown:   small  slowly sinking smoke trail
+    { 14, 0xFF8080, 4.0f }, // 22 red explosion fireball
+    { 14, 0xA0C080, 4.0f }, // 23 orange explosion fireball
+    /* @UNUSED */ { -1, 0, 0.0f}, // 24
+    { 16, 0x897661, 2.4f }, // 25 greyish-brown:   big  fast rising smoke          
+    /* @UNUSED */ { -1, 0, 0.0f}, // 26  
+    /* @UNUSED */ { -1, 0, 0.0f}, // 27
+    { 15, 0xFFFFFF, 0.28f}, // 28 lightning
+    { 15, 0xFF2222, 0.28f}, // 29 lightning: red
+    { 15, 0x2222FF, 0.28f}, // 30 lightning: blue
+    { 18, 0x802020, 4.8f }, // 31 fireball: red, no glare
+    { 18, 0x2020FF, 4.8f }, // 32 fireball: blue, no glare
+    { 18, 0x208020, 4.8f }, // 33 fireball: green, no glare
+    {  8, 0x6496FF, 4.0f }, // 34 TEXT BLUE
+    { 14, 0x802020, 4.8f }, // 35 fireball: red
+    { 14, 0x2020FF, 4.8f }, // 36 fireball: blue
+    // fill the above @UNUSED slots first!
+};
+
+static inline float partsize(int type)
+{
+    return partmaps[type].size * particlesize/100.0f;
 }
 
-void regular_particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity, int delay) 
+void regular_particle_splash(int type, int num, int fade, const vec &p, int delay) 
 {
-    if(shadowmapping || renderedgame) return;
-    regularsplash(type, color, radius, num, fade, p, size, gravity, delay);
+    if(shadowmapping) return;
+    int radius = (type==5 || type == 25) ? 50 : 150;
+    regularsplash(partmaps[type].type, partmaps[type].color, radius, num, fade, p, partsize(type), delay);
 }
 
-void particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity) 
+void particle_splash(int type, int num, int fade, const vec &p) 
 {
     if(shadowmapping || renderedgame) return;
-    splash(type, color, radius, num, fade, p, size, gravity);
+    splash(partmaps[type].type, partmaps[type].color, 150, num, fade, p, partsize(type));
 }
 
 VARP(maxtrail, 1, 500, 10000);
 
-void particle_trail(int type, int fade, const vec &s, const vec &e, int color, float size, int gravity)
+void particle_trail(int type, int fade, const vec &s, const vec &e)
 {
     if(shadowmapping || renderedgame) return;
     vec v;
@@ -1105,47 +888,46 @@ void particle_trail(int type, int fade, const vec &s, const vec &e, int color, f
     int steps = clamp(int(d*2), 1, maxtrail);
     v.div(steps);
     vec p = s;
+    int ptype = partmaps[type].type;
+    int color = partmaps[type].color;
+    float size = partsize(type);
     loopi(steps)
     {
         p.add(v);
         vec tmp = vec(float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5));
-        newparticle(p, tmp, rnd(fade)+fade, type, color, size, gravity);
+        newparticle(p, tmp, rnd(fade)+fade, ptype, color, size);
     }
 }
 
 VARP(particletext, 0, 1, 1);
-VARP(maxparticletextdistance, 0, 128, 10000);
 
-void particle_text(const vec &s, const char *t, int type, int fade, int color, float size, int gravity)
+void particle_text(const vec &s, const char *t, int type, int fade)
 {
     if(shadowmapping || renderedgame) return;
-    if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return;
+    if(!particletext || camera1->o.dist(s) > 128) return;
     if(t[0]=='@') t = newstring(t);
-    newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity)->text = t;
+    newparticle(s, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, partmaps[type].size)->text = t;
 }
 
-void particle_meter(const vec &s, float val, int type, int fade, int color, int color2, float size)
+void particle_meter(const vec &s, float val, int type, int fade)
 {
     if(shadowmapping || renderedgame) return;
-    particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size);
-    p->color2[0] = color2>>16;
-    p->color2[1] = (color2>>8)&0xFF;
-    p->color2[2] = color2&0xFF;
-    p->progress = clamp(int(val*100), 0, 100);
+    newparticle(s, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, partmaps[type].size)->val = val;
 }
 
-void particle_flare(const vec &p, const vec &dest, int fade, int type, int color, float size, physent *owner)
+void particle_flare(const vec &p, const vec &dest, int fade, int type, physent *owner)
 {
     if(shadowmapping || renderedgame) return;
-    newparticle(p, dest, fade, type, color, size)->owner = owner;
+    newparticle(p, dest, fade, partmaps[type].type, partmaps[type].color, partsize(type))->owner = owner;
 }
 
-void particle_fireball(const vec &dest, float maxsize, int type, int fade, int color, float size)
+void particle_fireball(const vec &dest, float maxsize, int type, int fade)
 {
     if(shadowmapping || renderedgame) return;
+    float size = partsize(type);
     float growth = maxsize - size;
     if(fade < 0) fade = int(growth*25);
-    newparticle(dest, vec(0, 0, 1), fade, type, color, size)->val = growth;
+    newparticle(dest, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, size)->val = growth;
 }
 
 //dir = 0..6 where 0=up
@@ -1172,14 +954,15 @@ static inline int colorfromattr(int attr)
  * 21 sphere
  * +32 to inverse direction
  */
-void regularshape(int type, int radius, int color, int dir, int num, int fade, const vec &p, float size, int gravity)
+void regularshape(int type, int radius, int color, int dir, int num, int fade, const vec &p, float size)
 {
     if(!emit_particles()) return;
     
     int basetype = parts[type]->type&0xFF;
-    bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING),
-         inv = (dir&0x20)!=0, taper = (dir&0x40)!=0 && !seedemitter;
-    dir &= 0x1F;
+    bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING);
+    
+    bool inv = (dir >= 32);
+    dir = dir&0x1F;
     loopi(num)
     {
         vec to, from;
@@ -1236,105 +1019,58 @@ void regularshape(int type, int radius, int color, int dir, int num, int fade, c
             to.add(p);
             from = p;
         }
-       
-        if(taper)
-        {
-            vec o = inv ? to : from;
-            o.sub(camera1->o);
-            float dist = clamp(sqrtf(o.x*o.x + o.y*o.y)/maxparticledistance, 0.0f, 1.0f);
-            if(dist > 0.2f)
-            {
-                dist = 1 - (dist - 0.2f)/0.8f;
-                if(rnd(0x10000) > dist*dist*0xFFFF) continue;
-            }
-        }
- 
+        
         if(flare)
-            newparticle(inv?to:from, inv?from:to, rnd(fade*3)+1, type, color, size, gravity);
+            newparticle(inv?to:from, inv?from:to, rnd(fade*3)+1, type, color, size);
         else 
         {  
             vec d(to);
             d.sub(from);
             d.normalize().mul(inv ? -200.0f : 200.0f); //velocity
-            newparticle(inv?to:from, d, rnd(fade*3)+1, type, color, size, gravity);
+            newparticle(inv?to:from, d, rnd(fade*3)+1, type, color, size);
         }
     }
 }
 
-static void regularflame(int type, const vec &p, float radius, float height, int color, int density = 3, float scale = 2.0f, float speed = 200.0f, float fade = 600.0f, int gravity = -15) 
-{
-    if(!emit_particles()) return;
-    
-    float size = scale * min(radius, height);
-    vec v(0, 0, min(1.0f, height)*speed);
-    loopi(density)
-    {
-        vec s = p;        
-        s.x += rndscale(radius*2.0f)-radius;
-        s.y += rndscale(radius*2.0f)-radius;
-        newparticle(s, v, rnd(max(int(fade*height), 1))+1, type, color, size, gravity);
-    }
-}
-
-
 static void makeparticles(entity &e) 
 {
     switch(e.attr1)
     {
-        case 0: //fire and smoke -  <radius> <height> <rgb> - 0 values default to compat for old maps
-        {
-            //regularsplash(PART_FIREBALL1, 0xFFC8C8, 150, 1, 40, e.o, 4.8f);
-            //regularsplash(PART_SMOKE, 0x897661, 50, 1, 200,  vec(e.o.x, e.o.y, e.o.z+3.0f), 2.4f, -20, 3);
-            float radius = e.attr2 ? float(e.attr2)/100.0f : 1.5f,
-                  height = e.attr3 ? float(e.attr3)/100.0f : radius/3;
-            regularflame(PART_FLAME, e.o, radius, height, e.attr4 ? colorfromattr(e.attr4) : 0x903020, 3, 2.0f);
-            regularflame(PART_SMOKE, vec(e.o.x, e.o.y, e.o.z + 4.0f*min(radius, height)), radius, height, 0x303020, 1, 4.0f, 100.0f, 2000.0f, -20);
+        case 0: //fire
+            regularsplash(4, 0xFFC8C8, 150, 1, 40, e.o, 4.8);
+            regularsplash(5, 0x897661, 50, 1, 200,  vec(e.o.x, e.o.y, e.o.z+3.0), 2.4, 3);
             break;
-        }
-        case 1: //steam vent - <dir>
-            regularsplash(PART_STEAM, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, rnd(10)), 2.4f, -20);
+        case 1: //smoke vent - <dir>
+            regularsplash(5, 0x897661, 50, 1, 200,  offsetvec(e.o, e.attr2, rnd(10)), 2.4);
             break;
         case 2: //water fountain - <dir>
         {
-            int color = (int(waterfallcolor[0])<<16) | (int(waterfallcolor[1])<<8) | int(waterfallcolor[2]);
-            if(!color) color = (int(watercolor[0])<<16) | (int(watercolor[1])<<8) | int(watercolor[2]);
-            regularsplash(PART_WATER, color, 150, 4, 200, offsetvec(e.o, e.attr2, rnd(10)), 0.6f, 2);
+            uchar col[3];
+            getwatercolour(col);
+            int color = (col[0]<<16) | (col[1]<<8) | col[2];
+            regularsplash(17, color, 150, 4, 200, offsetvec(e.o, e.attr2, rnd(10)), 0.6);
             break;
         }
         case 3: //fire ball - <size> <rgb>
-            newparticle(e.o, vec(0, 0, 1), 1, PART_EXPLOSION, colorfromattr(e.attr3), 4.0f)->val = 1+e.attr2;
+            newparticle(e.o, vec(0, 0, 1), 1, 14, colorfromattr(e.attr3), 4.0)->val = 1+e.attr2;
             break;
         case 4:  //tape - <dir> <length> <rgb>
         case 7:  //lightning 
-        case 9:  //steam
+        case 8:  //fire
+        case 9:  //smoke
         case 10: //water
         {
-            static const int typemap[]   = { PART_STREAK, -1, -1, PART_LIGHTNING, -1, PART_STEAM, PART_WATER };
-            static const float sizemap[] = { 0.28f, 0.0f, 0.0f, 0.28f, 0.0f, 2.4f, 0.60f };
-            static const int gravmap[] = { 0, 0, 0, 0, 0, -20, 2 };
+            const int typemap[]   = {    9,  -1,  -1,   15,   4,   5,   17 };
+            const float sizemap[] = { 0.28, 0.0, 0.0, 0.28, 4.8, 2.4, 0.60 };
             int type = typemap[e.attr1-4];
             float size = sizemap[e.attr1-4];
-            int gravity = gravmap[e.attr1-4];
-            if(e.attr2 >= 256) regularshape(type, max(1+e.attr3, 1), colorfromattr(e.attr4), e.attr2-256, 5, 200, e.o, size, gravity);
-            else newparticle(e.o, offsetvec(e.o, e.attr2, max(1+e.attr3, 0)), 1, type, colorfromattr(e.attr4), size, gravity);
+            if(e.attr2 >= 256) regularshape(type, 1+e.attr3, colorfromattr(e.attr4), e.attr2-256, 5, 200, e.o, size);
+            else newparticle(e.o, offsetvec(e.o, e.attr2, 1+e.attr3), 1, type, colorfromattr(e.attr4), size);
             break;
         }
-        case 5: //meter, metervs - <percent> <rgb> <rgb2>
+        case 5: //meter, metervs - <percent> <rgb>
         case 6:
-        {
-            particle *p = newparticle(e.o, vec(0, 0, 1), 1, e.attr1==5 ? PART_METER : PART_METER_VS, colorfromattr(e.attr3), 2.0f);
-            int color2 = colorfromattr(e.attr4);
-            p->color2[0] = color2>>16;
-            p->color2[1] = (color2>>8)&0xFF;
-            p->color2[2] = color2&0xFF;
-            p->progress = clamp(int(e.attr2), 0, 100);
-            break;
-        }
-        case 11: // flame <radius> <height> <rgb> - radius=100, height=100 is the classic size
-            regularflame(PART_FLAME, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 3, 2.0f);
-            break;
-        case 12: // smoke plume <radius> <height> <rgb>
-            regularflame(PART_SMOKE, e.o, float(e.attr2)/100.0f, float(e.attr3)/100.0f, colorfromattr(e.attr4), 1, 4.0f, 100.0f, 2000.0f, -20);
+            newparticle(e.o, vec(0, 0, 1), 1, (e.attr1==5)?11:12, colorfromattr(e.attr3), 2.0)->val = min(1.0f, float(e.attr2)/100);
             break;
         case 32: //lens flares - plain/sparkle/sun/sparklesun <red> <green> <blue>
         case 33:
@@ -1343,113 +1079,46 @@ static void makeparticles(entity &e)
             flares.addflare(e.o, e.attr2, e.attr3, e.attr4, (e.attr1&0x02)!=0, (e.attr1&0x01)!=0);
             break;
         default:
-            if(!editmode)
-            {
-                defformatstring(ds)("@particles %d?", e.attr1);
-                particle_text(e.o, ds, PART_TEXT, 1, 0x6496FF, 2.0f);
-            }
-            break;
+            s_sprintfd(ds)("@particles %d?", e.attr1);
+            particle_text(e.o, ds, 16, 1);
     }
 }
 
-bool printparticles(extentity &e, char *buf)
+void entity_particles()
 {
-    switch(e.attr1)
-    {
-        case 0: case 4: case 7: case 8: case 9: case 10: case 11: case 12: 
-            formatstring(buf)("%s %d %d %d 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
-            return true;
-        case 3:
-            formatstring(buf)("%s %d %d 0x%.3hX %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
-            return true;
-        case 5: case 6:
-            formatstring(buf)("%s %d %d 0x%.3hX 0x%.3hX %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
-            return true; 
-    }
-    return false;
-}
-
-VARP(showparticles, 0, 1, 1);
-VAR(cullparticles, 0, 1, 1);
-VAR(replayparticles, 0, 1, 1);
-VARN(seedparticles, seedmillis, 0, 3000, 10000);
-VAR(dbgpcull, 0, 0, 1);
-
-void seedparticles()
-{
-    renderprogress(0, "seeding particles");
-    addparticleemitters();
-    canemit = true;
-    loopv(emitters)
-    {
-        particleemitter &pe = emitters[i];
-        extentity &e = *pe.ent;
-        seedemitter = &pe;
-        for(int millis = 0; millis < seedmillis; millis += min(emitmillis, seedmillis/10))
-            makeparticles(e);    
-        seedemitter = NULL;
-        pe.lastemit = -seedmillis;
-        pe.finalize();
-    }
-}
-
-void updateparticles()
-{
-    if(regenemitters) addparticleemitters();
-
     if(lastmillis - lastemitframe >= emitmillis)
     {
-        canemit = true;
+        emit = true;
         lastemitframe = lastmillis - (lastmillis%emitmillis);
     }
-    else canemit = false;
+    else emit = false;
    
     flares.makelightflares();
-
-    if(!editmode || showparticles) 
+ 
+    const vector<extentity *> &ents = et->getents();
+    if(!editmode) 
     {
-        int emitted = 0, replayed = 0;
-        addedparticles = 0;
-        loopv(emitters)
+        loopv(ents)
         {
-            particleemitter &pe = emitters[i];
-            extentity &e = *pe.ent;
-            if(e.o.dist(camera1->o) > maxparticledistance) { pe.lastemit = lastmillis; continue; } 
-            if(cullparticles && pe.maxfade >= 0)
-            {
-                if(isvisiblesphere(pe.radius, pe.center) >= VFC_FOGGED) { pe.lastcull = lastmillis; continue; }
-                if(pvsoccluded(pe.bborigin, pe.bbsize)) { pe.lastcull = lastmillis; continue; }
-            }
+            entity &e = *ents[i];
+            if(e.type != ET_PARTICLES || e.o.dist(camera1->o) > maxparticledistance) continue;
             makeparticles(e);
-            emitted++;
-            if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit)
-            {
-                for(emitoffset = max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis)
-                {
-                    makeparticles(e);
-                    replayed++;
-                }
-                emitoffset = 0;
-            } 
-            pe.lastemit = lastmillis;
         }
-        if(dbgpcull && (canemit || replayed) && addedparticles) conoutf(CON_DEBUG, "%d emitters, %d particles", emitted, addedparticles);
     }
-    if(editmode) // show sparkly thingies for map entities in edit mode
+    else // show sparkly thingies for map entities in edit mode
     {
-        const vector<extentity *> &ents = entities::getents();
         // note: order matters in this case as particles of the same type are drawn in the reverse order that they are added
         loopv(entgroup)
         {
             entity &e = *ents[entgroup[i]];
-            particle_text(e.o, entname(e), PART_TEXT, 1, 0xFF4B19, 2.0f);
+            particle_text(e.o, entname(e), 13, 1);
         }
         loopv(ents)
         {
             entity &e = *ents[i];
             if(e.type==ET_EMPTY) continue;
-            particle_text(e.o, entname(e), PART_TEXT, 1, 0x1EC850, 2.0f);
-            regular_particle_splash(PART_EDIT, 2, 40, e.o, 0x3232FF, 0.32f*particlesize/100.0f);
+            particle_text(e.o, entname(e), 11, 1);
+            regular_particle_splash(2, 2, 40, e.o);
         }
     }
 }
diff --git a/engine/rendersky.cpp b/engine/rendersky.cpp
index c4aa3c5..e6d0db8 100644
--- a/engine/rendersky.cpp
+++ b/engine/rendersky.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 Texture *sky[6] = { 0, 0, 0, 0, 0, 0 }, *clouds[6] = { 0, 0, 0, 0, 0, 0 };
@@ -7,7 +8,7 @@ void loadsky(const char *basename, Texture *texs[6])
     loopi(6)
     {
         const char *side = cubemapsides[i].name;
-        defformatstring(name)("%s_%s.jpg", makerelpath("packages", basename), side);
+        s_sprintfd(name)("packages/%s_%s.jpg", basename, side);
         if((texs[i] = textureload(name, 3, true, false))==notexture)
         {
             strcpy(name+strlen(name)-3, "png");
@@ -20,7 +21,7 @@ Texture *cloudoverlay = NULL;
 
 Texture *loadskyoverlay(const char *basename)
 {
-    defformatstring(name)("%s.jpg", makerelpath("packages", basename));
+    s_sprintfd(name)("packages/%s.jpg", basename);
     Texture *t = textureload(name, 0, true, false);
     if(t!=notexture) return t;
     strcpy(name+strlen(name)-3, "png");
@@ -30,23 +31,22 @@ Texture *loadskyoverlay(const char *basename)
 }
 
 SVARFR(skybox, "", { if(skybox[0]) loadsky(skybox, sky); }); 
-FVARR(spinsky, -720, 0, 720);
+FVARR(spinsky, 0);
 VARR(yawsky, 0, 0, 360);
 SVARFR(cloudbox, "", { if(cloudbox[0]) loadsky(cloudbox, clouds); });
-FVARR(spinclouds, -720, 0, 720);
+FVARR(spinclouds, 0);
 VARR(yawclouds, 0, 0, 360);
-FVARR(cloudclip, 0, 0.5f, 1);
+FVARR(cloudclip, 0.5f);
 SVARFR(cloudlayer, "", { if(cloudlayer[0]) cloudoverlay = loadskyoverlay(cloudlayer); });
-FVARR(cloudscrollx, -16, 0, 16);
-FVARR(cloudscrolly, -16, 0, 16);
-FVARR(cloudscale, 0, 1, 64);
-FVARR(spincloudlayer, -720, 0, 720);
-VARR(yawcloudlayer, 0, 0, 360);
-FVARR(cloudheight, -1, 0.2f, 1);
-FVARR(cloudfade, 0, 0.2f, 1);
-FVARR(cloudalpha, 0, 1, 1);
+FVARR(cloudscrollx, 0);
+FVARR(cloudscrolly, 0);
+FVARR(cloudscale, 1);
+FVARR(spincloudlayer, 0);
+FVARR(yawcloudlayer, 0);
+FVARR(cloudheight, 0.2f);
+FVARR(cloudfade, 0.2f);
 VARR(cloudsubdiv, 4, 16, 64);
-HVARR(cloudcolour, 0, 0xFFFFFF, 0xFFFFFF);
+VARR(cloudcolour, 0, 0xFFFFFF, 0xFFFFFF);
 
 void draw_envbox_face(float s0, float t0, int x0, int y0, int z0,
                       float s1, float t1, int x1, int y1, int z1,
@@ -111,7 +111,7 @@ void draw_env_overlay(int w, Texture *overlay = NULL, float tx = 0, float ty = 0
     float z = -w*cloudheight, tsz = 0.5f*(1-cloudfade)/cloudscale, psz = w*(1-cloudfade);
     glBindTexture(GL_TEXTURE_2D, overlay ? overlay->id : notexture->id);
     float r = (cloudcolour>>16)/255.0f, g = ((cloudcolour>>8)&255)/255.0f, b = (cloudcolour&255)/255.0f;
-    glColor4f(r, g, b, cloudalpha);
+    glColor3f(r, g, b);
     glBegin(GL_POLYGON);
     loopi(cloudsubdiv)
     {
@@ -126,7 +126,7 @@ void draw_env_overlay(int w, Texture *overlay = NULL, float tx = 0, float ty = 0
     {
         vec p(1, 1, 0);
         p.rotate_around_z((-2.0f*M_PI*i)/cloudsubdiv);
-        glColor4f(r, g, b, cloudalpha);
+        glColor4f(r, g, b, 1);
         glTexCoord2f(tx + p.x*tsz, ty + p.y*tsz); glVertex3f(p.x*psz, p.y*psz, z);
         glColor4f(r, g, b, 0);
         glTexCoord2f(tx + p.x*tsz2, ty + p.y*tsz2); glVertex3f(p.x*w, p.y*w, z);
@@ -178,21 +178,6 @@ void drawskyoutline()
 
 VAR(clampsky, 0, 1, 1);
 
-static int yawskyfaces(int faces, int yaw, float spin = 0)
-{
-    if(spin || yaw%90) return faces&0x0F ? faces | 0x0F : faces;
-    static const int faceidxs[3][4] =
-    {
-        { 3, 2, 0, 1 },
-        { 1, 0, 3, 2 },
-        { 2, 3, 1, 0 }
-    };
-    yaw /= 90;
-    if(yaw < 1 || yaw > 3) return faces;
-    const int *idxs = faceidxs[yaw - 1];
-    return (faces & ~0x0F) | (((faces>>idxs[0])&1)<<0) | (((faces>>idxs[1])&1)<<1) | (((faces>>idxs[2])&1)<<2) | (((faces>>idxs[3])&1)<<3);
-}
-
 void drawskybox(int farplane, bool limited)
 {
     extern int renderedskyfaces, renderedskyclip; // , renderedsky, renderedexplicitsky;
@@ -227,7 +212,7 @@ void drawskybox(int farplane, bool limited)
     }
 
     float skyclip = clipsky ? max(renderedskyclip-1, 0) : 0;
-    if(reflectz<worldsize && reflectz>skyclip) skyclip = reflectz;
+    if(reflectz<hdr.worldsize && reflectz>skyclip) skyclip = reflectz;
 
     if(glaring)
     {
@@ -237,7 +222,8 @@ void drawskybox(int farplane, bool limited)
     }
     else defaultshader->set();
 
-    if(renderpath!=R_FIXEDFUNCTION || !fogging) glDisable(GL_FOG);
+    bool fog = glIsEnabled(GL_FOG)==GL_TRUE;
+    if(fog) glDisable(GL_FOG);
 
     if(limited) 
     {
@@ -259,11 +245,13 @@ void drawskybox(int farplane, bool limited)
     glRotatef(camera1->yaw+spinsky*lastmillis/1000.0f+yawsky, 0, 1, 0);
     glRotatef(90, 1, 0, 0);
     if(reflecting) glScalef(1, 1, -1);
-    draw_envbox(farplane/2, skyclip ? 0.5f + 0.5f*(skyclip-camera1->o.z)/float(worldsize) : 0, yawskyfaces(renderedskyfaces, yawsky, spinsky), sky);
+    draw_envbox(farplane/2, skyclip ? 0.5f + 0.5f*(skyclip-camera1->o.z)/float(hdr.worldsize) : 0, renderedskyfaces | ((spinsky || yawsky) && renderedskyfaces&0x0F ? 0x0F : 0), sky);
     glPopMatrix();
 
     if(!glaring && cloudbox[0])
     {
+        if((spinclouds || yawclouds) && renderedskyfaces&0x0F) renderedskyfaces |= 0x0F;
+
         if(fading) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
 
         glEnable(GL_BLEND);
@@ -276,7 +264,7 @@ void drawskybox(int farplane, bool limited)
         glRotatef(camera1->yaw+spinclouds*lastmillis/1000.0f+yawclouds, 0, 1, 0);
         glRotatef(90, 1, 0, 0);
         if(reflecting) glScalef(1, 1, -1);
-        draw_envbox(farplane/2, skyclip ? 0.5f + 0.5f*(skyclip-camera1->o.z)/float(worldsize) : cloudclip, yawskyfaces(renderedskyfaces, yawclouds, spinclouds), clouds);
+        draw_envbox(farplane/2, skyclip ? 0.5f + 0.5f*(skyclip-camera1->o.z)/float(hdr.worldsize) : cloudclip, renderedskyfaces | ((spinclouds || yawclouds) && renderedskyfaces&0x0F ? 0x0F : 0), clouds);
         glPopMatrix();
 
         glDisable(GL_BLEND);
@@ -318,7 +306,7 @@ void drawskybox(int farplane, bool limited)
     }
     else glDepthFunc(GL_LESS);
 
-    if(renderpath!=R_FIXEDFUNCTION || !fogging) glEnable(GL_FOG);
+    if(fog) glEnable(GL_FOG);
 }
 
 VARNR(skytexture, useskytexture, 0, 1, 1);
@@ -328,6 +316,6 @@ double skyarea = 0;
 
 bool limitsky()
 {
-    return (explicitsky && (useskytexture || editmode)) || (sparklyfix && skyarea / (double(worldsize)*double(worldsize)*6) < 0.9);
+    return (explicitsky && (useskytexture || editmode)) || (sparklyfix && skyarea / (double(hdr.worldsize)*double(hdr.worldsize)*6) < 0.9);
 }
 
diff --git a/engine/rendertarget.h b/engine/rendertarget.h
index 9ad800c..5f2b684 100644
--- a/engine/rendertarget.h
+++ b/engine/rendertarget.h
@@ -14,19 +14,12 @@ struct rendertarget
 #define BLURTILEMASK (0xFFFFFFFFU>>(32-BLURTILES))
     uint blurtiles[BLURTILES+1];
 
-    bool initialized;
-
-    rendertarget() : texw(0), texh(0), vieww(0), viewh(0), colorfmt(GL_FALSE), depthfmt(GL_FALSE), target(GL_TEXTURE_2D), rendertex(0), renderfb(0), renderdb(0), blurtex(0), blurfb(0), blurdb(0), blursize(0), blursigma(0), initialized(false)
+    rendertarget() : texw(0), texh(0), vieww(0), viewh(0), colorfmt(GL_FALSE), depthfmt(GL_FALSE), target(GL_TEXTURE_2D), rendertex(0), renderfb(0), renderdb(0), blurtex(0), blurfb(0), blurdb(0), blursize(0), blursigma(0)
     {
     }
 
     virtual ~rendertarget() {}
 
-    virtual GLenum attachment() const
-    {
-        return GL_COLOR_ATTACHMENT0_EXT;
-    }
-
     virtual const GLenum *colorformats() const
     {
         static const GLenum colorfmts[] = { GL_RGB, GL_RGB8, GL_FALSE };
@@ -63,7 +56,7 @@ struct rendertarget
     {
         if(!hasFBO) return;
         if(!blurtex) glGenTextures(1, &blurtex);
-        createtexture(blurtex, texw, texh, NULL, 3, 1, colorfmt, target);
+        createtexture(blurtex, texw, texh, NULL, 3, false, colorfmt, target);
 
         if(!swaptexs() || rtsharefb) return;
         if(!blurfb) glGenFramebuffers_(1, &blurfb);
@@ -88,29 +81,22 @@ struct rendertarget
 
         target = texrect() ? GL_TEXTURE_RECTANGLE_ARB : GL_TEXTURE_2D;
 
-        GLenum attach = attachment();
-        if(hasFBO && attach == GL_DEPTH_ATTACHMENT_EXT)
-        {
-            glDrawBuffer(GL_NONE);
-            glReadBuffer(GL_NONE);
-        }
-
         const GLenum *colorfmts = colorformats();
         int find = 0;
         do
         {
-            createtexture(rendertex, w, h, NULL, 3, filter() ? 1 : 0, colorfmt ? colorfmt : colorfmts[find], target);
+            createtexture(rendertex, w, h, NULL, 3, false, colorfmt ? colorfmt : colorfmts[find], target, false, filter());
             if(!hasFBO) break;
             else
             {
-                glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, attach, target, rendertex, 0);
+                glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, rendertex, 0);
                 if(glCheckFramebufferStatus_(GL_FRAMEBUFFER_EXT)==GL_FRAMEBUFFER_COMPLETE_EXT) break;
             }
         }
         while(!colorfmt && colorfmts[++find]);
         if(!colorfmt) colorfmt = colorfmts[find];
 
-        if(hasFBO && attach != GL_DEPTH_ATTACHMENT_EXT)
+        if(hasFBO)
         {
             if(!renderdb) { glGenRenderbuffers_(1, &renderdb); depthfmt = GL_FALSE; }
             if(!depthfmt) glBindRenderbuffer_(GL_RENDERBUFFER_EXT, renderdb);
@@ -129,7 +115,6 @@ struct rendertarget
         }
         texw = w;
         texh = h;
-        initialized = false;
     }
 
     bool addblurtiles(float x1, float y1, float x2, float y2, float blurmargin = 0)
@@ -332,7 +317,7 @@ struct rendertarget
     virtual bool texrect() const { return false; }
     virtual bool filter() const { return true; }
 
-    void render(int w, int h, int blursize = 0, float blursigma = 0)
+    void render(int w, int h, int blursize, float blursigma)
     {
         w = min(w, hwtexsize);
         h = min(h, hwtexsize);
@@ -390,7 +375,7 @@ struct rendertarget
             }
             glBindFramebuffer_(GL_FRAMEBUFFER_EXT, renderfb);
             if(swaptexs() && blursize && rtsharefb)
-                glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, attachment(), target, rendertex, 0);
+                glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, target, rendertex, 0);
             glViewport(0, 0, vieww, viewh);
         }
         else glViewport(screen->w-vieww, screen->h-viewh, vieww, viewh);
@@ -426,16 +411,8 @@ struct rendertarget
             if(!hasFBO)
             {
                 glBindTexture(target, rendertex);
-                if(!initialized) 
-                {
-                    sx = screen->w-vieww;
-                    sy = screen->h-viewh;
-                    sw = vieww;
-                    sh = viewh;
-                }
                 glCopyTexSubImage2D(target, 0, sx-(screen->w-vieww), sy-(screen->h-viewh), sx, sy, sw, sh);
             }
-            initialized = true;
 
             if(blursize) doblur(blursize, blursigma);
         }
diff --git a/engine/rendertext.cpp b/engine/rendertext.cpp
index 9d179e7..85465c0 100644
--- a/engine/rendertext.cpp
+++ b/engine/rendertext.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 
 static hashtable<const char *, font> fonts;
@@ -74,25 +75,9 @@ int text_width(const char *str) { //@TODO deprecate in favour of text_bounds(..)
     return width;
 }
 
-void tabify(const char *str, int *numtabs)
-{
-    vector<char> tabbed;
-    tabbed.put(str, strlen(str));
-    int w = text_width(str), tw = max(*numtabs, 0)*PIXELTAB;
-    while(w < tw)
-    {
-        tabbed.add('\t');
-        w = ((w+PIXELTAB)/PIXELTAB)*PIXELTAB;
-    }
-    tabbed.add('\0');
-    result(tabbed.getbuf());
-}
-
-COMMAND(tabify, "si");
-    
 void draw_textf(const char *fstr, int left, int top, ...)
 {
-    defvformatstring(str, top, fstr);
+    s_sprintfdlv(str, top, fstr);
     draw_text(str, left, top);
 }
 
@@ -119,7 +104,7 @@ static void text_color(char c, char *stack, int size, int &sp, bvec color, int a
     if(c=='s') // save color
     {   
         c = stack[sp];
-        if(sp<size-1) stack[++sp] = c;
+        if(sp<size-1) stack[sp++] = c;
     }
     else
     {
@@ -134,8 +119,7 @@ static void text_color(char c, char *stack, int size, int &sp, bvec color, int a
             case '4': color = bvec(128, 128, 128); break;   // gray
             case '5': color = bvec(192,  64, 192); break;   // magenta
             case '6': color = bvec(255, 128,   0); break;   // orange
-            case '7': color = bvec(255, 255, 255); break;   // white
-            // provided color: everything else
+            // white (provided color): everything else
         }
         glColor4ub(color.x, color.y, color.z, a);
     } 
diff --git a/engine/renderva.cpp b/engine/renderva.cpp
index d053c4b..29dfaed 100644
--- a/engine/renderva.cpp
+++ b/engine/renderva.cpp
@@ -1,5 +1,6 @@
 // renderva.cpp: handles the occlusion and rendering of vertex arrays
 
+#include "pch.h"
 #include "engine.h"
 
 static inline void drawtris(GLsizei numindices, const GLvoid *indices, ushort minvert, ushort maxvert)
@@ -75,7 +76,7 @@ void addvisibleva(vtxarray *va)
     float dist = vadist(va, camera1->o);
     va->distance = int(dist); /*cv.dist(camera1->o) - va->size*SQRT3/2*/
 
-    int hash = min(int(dist*VASORTSIZE/worldsize), VASORTSIZE-1);
+    int hash = min(int(dist*VASORTSIZE/hdr.worldsize), VASORTSIZE-1);
     vtxarray **prev = &vasort[hash], *cur = vasort[hash];
 
     while(cur && va->distance >= cur->distance)
@@ -281,7 +282,6 @@ void clearqueries()
 
 VAR(oqfrags, 0, 8, 64);
 VAR(oqreflect, 0, 4, 64);
-VAR(oqwait, 0, 1, 1);
 
 bool checkquery(occludequery *query, bool nowait)
 {
@@ -289,7 +289,7 @@ bool checkquery(occludequery *query, bool nowait)
     if(query->fragments >= 0) fragments = query->fragments;
     else
     {
-        if(nowait || !oqwait)
+        if(nowait)
         {
             GLint avail;
             glGetQueryObjectiv_(query->id, GL_QUERY_RESULT_AVAILABLE, &avail);
@@ -358,7 +358,7 @@ void findvisiblemms(const vector<extentity *> &ents)
                 loopv(oe->mapmodels)
                 {
                     extentity &e = *ents[oe->mapmodels[i]];
-                    if(e.attr3 && e.triggerstate == TRIGGER_DISAPPEARED) continue;
+                    if(e.visible || (e.attr3 && e.triggerstate == TRIGGER_DISAPPEARED)) continue;
                     e.visible = true;
                     ++visible;
                 }
@@ -401,30 +401,27 @@ void rendermapmodel(extentity &e)
 
 extern int reflectdist;
 
+static vector<octaentities *> renderedmms;
+
 vtxarray *reflectedva;
 
 void renderreflectedmapmodels()
 {
-    const vector<extentity *> &ents = entities::getents();
+    vector<octaentities *> reflectedmms;
+    vector<octaentities *> &mms = reflecting ? reflectedmms : renderedmms;
+    const vector<extentity *> &ents = et->getents();
 
-    octaentities *mms = visiblemms;
     if(reflecting)
     {
-        octaentities **lastmms = &mms;
         for(vtxarray *va = reflectedva; va; va = va->rnext)
         {
             if(!va->mapmodels || va->distance > reflectdist) continue;
-            loopv(*va->mapmodels) 
-            {
-                octaentities *oe = (*va->mapmodels)[i];
-                *lastmms = oe;
-                lastmms = &oe->rnext;
-            }
+            loopv(*va->mapmodels) reflectedmms.add((*va->mapmodels)[i]);
         }
-        *lastmms = NULL;
     }
-    for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next)
+    loopv(mms)
     {
+        octaentities *oe = mms[i];
         if(reflecting || refracting>0 ? oe->bbmax.z <= reflectz : oe->bbmin.z >= reflectz) continue;
         if(isvisiblecube(oe->o, oe->size) >= VFC_FOGGED) continue;
         loopv(oe->mapmodels)
@@ -434,11 +431,12 @@ void renderreflectedmapmodels()
            e.visible = true;
         }
     }
-    if(mms)
+    if(mms.length())
     {
         startmodelbatches();
-        for(octaentities *oe = mms; oe; oe = reflecting ? oe->rnext : oe->next)
+        loopv(mms)
         {
+            octaentities *oe = mms[i];
             loopv(oe->mapmodels)
             {
                 extentity &e = *ents[oe->mapmodels[i]];
@@ -453,7 +451,7 @@ void renderreflectedmapmodels()
 
 void rendermapmodels()
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
 
     visiblemms = NULL;
     lastvisiblemms = &visiblemms;
@@ -462,24 +460,24 @@ void rendermapmodels()
     static int skipoq = 0;
     bool doquery = hasOQ && oqfrags && oqmm;
 
+    renderedmms.setsizenodelete(0);
     startmodelbatches();
     for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance>=0)
     {
-        bool rendered = false;
         loopv(oe->mapmodels)
         {
             extentity &e = *ents[oe->mapmodels[i]];
-            if(!e.visible) continue;
-            if(!rendered)
+            if(!e.visible || (e.attr3 && e.triggerstate == TRIGGER_DISAPPEARED)) continue;
+            if(renderedmms.empty() || renderedmms.last()!=oe)
             {
-                rendered = true;
+                renderedmms.add(oe);
                 oe->query = doquery && oe->distance>0 && !(++skipoq%oqmm) ? newquery(oe) : NULL;
                 if(oe->query) startmodelquery(oe->query);
             }        
             rendermapmodel(e);
             e.visible = false;
         }
-        if(rendered && oe->query) endmodelquery();
+        if(renderedmms.length() && renderedmms.last()==oe && oe->query) endmodelquery();
     }
     endmodelbatches();
 
@@ -538,7 +536,7 @@ bool bboccluded(const ivec &bo, const ivec &br)
     if(c->ext && c->ext->va)
     {
         vtxarray *va = c->ext->va;
-        if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true;
+        if(va->curvfc >= VFC_FOGGED || va->occluded >= OCCLUDE_BB) return true;
     }
     scale--;
     while(c->children && !(diff&(1<<scale)))
@@ -547,7 +545,7 @@ bool bboccluded(const ivec &bo, const ivec &br)
         if(c->ext && c->ext->va)
         {
             vtxarray *va = c->ext->va;
-            if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true;
+            if(va->curvfc >= VFC_FOGGED || va->occluded >= OCCLUDE_BB) return true;
         }
         scale--;
     }
@@ -555,31 +553,7 @@ bool bboccluded(const ivec &bo, const ivec &br)
     return false;
 }
 
-extern int ati_texgen_bug;
-
-static void setuptexgen(int dims = 2)
-{
-    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-    glEnable(GL_TEXTURE_GEN_S);
-    if(dims>=2)
-    {
-        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-        glEnable(GL_TEXTURE_GEN_T);
-        if(ati_texgen_bug) glEnable(GL_TEXTURE_GEN_R);     // should not be needed, but apparently makes some ATI drivers happy
-    }
-}
-
-static void disabletexgen(int dims = 2)
-{
-    glDisable(GL_TEXTURE_GEN_S);
-    if(dims>=2)
-    {
-        glDisable(GL_TEXTURE_GEN_T);
-        if(ati_texgen_bug) glDisable(GL_TEXTURE_GEN_R);
-    }
-}
-
-HVAR(outline, 0, 0, 0xFFFFFF);
+VAR(outline, 0, 0, 0xFFFFFF);
 VAR(dtoutline, 0, 1, 1);
 
 void renderoutline()
@@ -640,90 +614,6 @@ void renderoutline()
     defaultshader->set();
 }
 
-HVAR(blendbrushcolor, 0, 0x0000C0, 0xFFFFFF);
-
-void renderblendbrush(GLuint tex, float x, float y, float w, float h)
-{
-    static Shader *blendbrushshader = NULL;
-    if(!blendbrushshader) blendbrushshader = lookupshaderbyname("blendbrush");
-    blendbrushshader->set();
-
-    glEnableClientState(GL_VERTEX_ARRAY);
-
-    glPushMatrix();
-
-    glDepthFunc(GL_LEQUAL);
-
-    glEnable(GL_BLEND);
-    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
-
-    glEnable(GL_TEXTURE_2D); 
-    glBindTexture(GL_TEXTURE_2D, tex);
-    glColor4ub((blendbrushcolor>>16)&0xFF, (blendbrushcolor>>8)&0xFF, blendbrushcolor&0xFF, 0x40);
-
-    GLfloat s[4] = { 0, 0, 0, 0 }, t[4] = { 0, 0, 0, 0 };
-    if(renderpath==R_FIXEDFUNCTION) setuptexgen();
-
-    resetorigin();
-    vtxarray *prev = NULL;
-    for(vtxarray *va = visibleva; va; va = va->next)
-    {
-        if(!va->texs || va->occluded >= OCCLUDE_GEOM) continue;
-        if(va->o.x + va->size <= x || va->o.y + va->size <= y || va->o.x >= x + w || va->o.y >= y + h) continue;
-
-        if(!prev || va->vbuf != prev->vbuf)
-        {
-            if(setorigin(va))
-            {
-                s[0] = 1.0f / (w*(1<<VVEC_FRAC));
-                s[3] = (vaorigin.x - x) / w;
-                t[1] = 1.0f / (h*(1<<VVEC_FRAC));
-                t[3] = (vaorigin.y - y) / h;
-                if(renderpath==R_FIXEDFUNCTION)
-                {
-                    glTexGenfv(GL_S, GL_OBJECT_PLANE, s);
-                    glTexGenfv(GL_T, GL_OBJECT_PLANE, t);
-                }
-                else
-                {
-                    setlocalparamfv("texgenS", SHPARAM_VERTEX, 0, s);
-                    setlocalparamfv("texgenT", SHPARAM_VERTEX, 1, t);
-                }
-            }
-        
-            if(hasVBO)
-            {
-                glBindBuffer_(GL_ARRAY_BUFFER_ARB, va->vbuf);
-                glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER_ARB, va->ebuf);
-            }
-            glVertexPointer(3, floatvtx ? GL_FLOAT : GL_SHORT, VTXSIZE, &va->vdata[0].x);
-        }
-
-        drawvatris(va, 3*va->tris, va->edata);
-        xtravertsva += va->verts;
-
-        prev = va;
-    }
-
-    if(renderpath==R_FIXEDFUNCTION) disabletexgen();
-
-    glDisable(GL_TEXTURE_2D);
-    glDisable(GL_BLEND);
-
-    glDepthFunc(GL_LESS);
-
-    glPopMatrix();
-
-    if(hasVBO)
-    {
-        glBindBuffer_(GL_ARRAY_BUFFER_ARB, 0);
-        glBindBuffer_(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
-    }
-    glDisableClientState(GL_VERTEX_ARRAY);
-
-    notextureshader->set();
-}
- 
 void rendershadowmapreceivers()
 {
     if(!hasBE) return;
@@ -884,7 +774,7 @@ float orientation_binormal[6][3][4] =
 
 struct renderstate
 {
-    bool colormask, depthmask, blending, mtglow, skippedglow;
+    bool colormask, depthmask, mtglow, skippedglow;
     GLuint vbuf;
     float fogplane;
     int diffusetmu, lightmaptmu, glowtmu, fogtmu, causticstmu;
@@ -897,10 +787,8 @@ struct renderstate
     bool mttexgen;
     int visibledynlights;
     uint dynlightmask;
-    vec dynlightpos;
-    float dynlightradius;
 
-    renderstate() : colormask(true), depthmask(true), blending(false), mtglow(false), skippedglow(false), vbuf(0), fogplane(-1), diffusetmu(0), lightmaptmu(1), glowtmu(-1), fogtmu(-1), causticstmu(-1), glowcolor(1, 1, 1), slot(NULL), texgendim(-1), mttexgen(false), visibledynlights(0), dynlightmask(0)
+    renderstate() : colormask(true), depthmask(true), mtglow(false), skippedglow(false), vbuf(0), fogplane(-1), diffusetmu(0), lightmaptmu(1), glowtmu(-1), fogtmu(-1), causticstmu(-1), glowcolor(1, 1, 1), slot(NULL), texgendim(-1), mttexgen(false), visibledynlights(0), dynlightmask(0)
     {
         loopk(4) color[k] = 1;
         loopk(8) textures[k] = 0;
@@ -930,10 +818,7 @@ enum
     RENDERPASS_Z,
     RENDERPASS_GLOW,
     RENDERPASS_CAUSTICS,
-    RENDERPASS_FOG,
-    RENDERPASS_SHADOWMAP,
-    RENDERPASS_DYNLIGHT,
-    RENDERPASS_LIGHTMAP_BLEND
+    RENDERPASS_FOG
 };
 
 struct geombatch
@@ -953,10 +838,10 @@ struct geombatch
     {
         if(va->vbuf < b.va->vbuf) return -1;
         if(va->vbuf > b.va->vbuf) return 1;
+        if(va->dynlightmask < b.va->dynlightmask) return -1;
+        if(va->dynlightmask > b.va->dynlightmask) return 1;
         if(renderpath!=R_FIXEDFUNCTION)
         {
-            if(va->dynlightmask < b.va->dynlightmask) return -1;
-            if(va->dynlightmask > b.va->dynlightmask) return 1;
             if(slot.shader < b.slot.shader) return -1;
             if(slot.shader > b.slot.shader) return 1;
             if(slot.params.length() < b.slot.params.length()) return -1;
@@ -1071,7 +956,7 @@ static void changefogplane(renderstate &cur, int pass, vtxarray *va)
 {
     if(renderpath!=R_FIXEDFUNCTION)
     {
-        if((fading && !cur.blending) || fogging)
+        if(fading || fogging)
         {
             float fogplane = reflectz - vaorigin.z;
             if(cur.fogplane!=fogplane)
@@ -1082,33 +967,20 @@ static void changefogplane(renderstate &cur, int pass, vtxarray *va)
             }
         }
     }
-    else if(pass==RENDERPASS_FOG || (cur.fogtmu>=0 && (pass==RENDERPASS_LIGHTMAP || pass==RENDERPASS_GLOW || pass==RENDERPASS_SHADOWMAP)))
+    else if(pass==RENDERPASS_FOG || (cur.fogtmu>=0 && (pass==RENDERPASS_LIGHTMAP || pass==RENDERPASS_GLOW)))
     {
         if(pass==RENDERPASS_LIGHTMAP) glActiveTexture_(GL_TEXTURE0_ARB+cur.fogtmu);
-        else if(pass==RENDERPASS_GLOW || pass==RENDERPASS_SHADOWMAP) glActiveTexture_(GL_TEXTURE1_ARB);
+        else if(pass==RENDERPASS_GLOW) glActiveTexture_(GL_TEXTURE1_ARB);
         GLfloat s[4] = { 0, 0, -1.0f/(waterfog<<VVEC_FRAC), (reflectz - vaorigin.z)/waterfog };
         glTexGenfv(GL_S, GL_OBJECT_PLANE, s);
         if(pass==RENDERPASS_LIGHTMAP) glActiveTexture_(GL_TEXTURE0_ARB+cur.diffusetmu);
-        else if(pass==RENDERPASS_GLOW || pass==RENDERPASS_SHADOWMAP) glActiveTexture_(GL_TEXTURE0_ARB);
+        else if(pass==RENDERPASS_GLOW) glActiveTexture_(GL_TEXTURE0_ARB);
     }
 }
 
-static void changedynlightpos(renderstate &cur, vtxarray *va)
-{
-    GLfloat tx[4] = { 0.5f/(cur.dynlightradius * (1<<VVEC_FRAC)), 0, 0, 0.5f + 0.5f*(vaorigin.x - cur.dynlightpos.x)/cur.dynlightradius },
-            ty[4] = { 0, 0.5f/(cur.dynlightradius * (1<<VVEC_FRAC)), 0, 0.5f + 0.5f*(vaorigin.y - cur.dynlightpos.y)/cur.dynlightradius },
-            tz[4] = { 0, 0, 0.5f/(cur.dynlightradius * (1<<VVEC_FRAC)), 0.5f + 0.5f*(vaorigin.z - cur.dynlightpos.z)/cur.dynlightradius };
-    glActiveTexture_(GL_TEXTURE0_ARB);
-    glTexGenfv(GL_S, GL_OBJECT_PLANE, tx);
-    glTexGenfv(GL_T, GL_OBJECT_PLANE, ty);
-    glActiveTexture_(GL_TEXTURE1_ARB);
-    glTexGenfv(GL_S, GL_OBJECT_PLANE, tz);
-    glActiveTexture_(GL_TEXTURE2_ARB);
-}
-    
 static void changevbuf(renderstate &cur, int pass, vtxarray *va)
 {
-    if(setorigin(va, renderpath!=R_FIXEDFUNCTION ? pass==RENDERPASS_LIGHTMAP && !envmapping && !glaring : pass==RENDERPASS_SHADOWMAP))
+    if(setorigin(va, pass==RENDERPASS_LIGHTMAP && !envmapping))
     {
         cur.visibledynlights = 0;
         cur.dynlightmask = 0;
@@ -1144,8 +1016,7 @@ static void changebatchtmus(renderstate &cur, int pass, geombatch &b)
 {
     bool changed = false;
     extern bool brightengeom;
-    extern int fullbright;
-    int lmid = brightengeom && (b.es.lmid < LMID_RESERVED || (fullbright && editmode)) ? LMID_BRIGHT : b.es.lmid; 
+    int lmid = brightengeom ? LMID_BRIGHT : b.es.lmid; 
     if(cur.textures[cur.lightmaptmu]!=lightmaptexs[lmid].id)
     {
         glActiveTexture_(GL_TEXTURE0_ARB+cur.lightmaptmu);
@@ -1200,15 +1071,15 @@ static void changeglow(renderstate &cur, int pass, Slot &slot)
             if(color==vec(1, 1, 1)) 
             {
                 glActiveTexture_(GL_TEXTURE0_ARB+cur.glowtmu);
-                setuptmu(cur.glowtmu, "P + T", "= Pa");
+                setuptmu(cur.glowtmu, "P + T");
             }
             else if(hasTE3 || hasTE4)
             {
                 glActiveTexture_(GL_TEXTURE0_ARB+cur.glowtmu);
                 if(cur.glowcolor==vec(1, 1, 1))
                 {
-                    if(hasTE3) setuptmu(cur.glowtmu, "TPK3", "= Pa");
-                    else if(hasTE4) setuptmu(cur.glowtmu, "TKP14", "= Pa");
+                    if(hasTE3) setuptmu(cur.glowtmu, "TPK3");
+                    else if(hasTE4) setuptmu(cur.glowtmu, "TKP14");
                 }
                 colortmu(cur.glowtmu, color.x, color.y, color.z);
             }
@@ -1238,7 +1109,7 @@ static void changeglow(renderstate &cur, int pass, Slot &slot)
 
 static void changeslottmus(renderstate &cur, int pass, Slot &slot)
 {
-    if(pass==RENDERPASS_LIGHTMAP || pass==RENDERPASS_COLOR || pass==RENDERPASS_DYNLIGHT) 
+    if(pass==RENDERPASS_LIGHTMAP || pass==RENDERPASS_COLOR) 
     {
         GLuint diffusetex = slot.sts.empty() ? notexture->id : slot.sts[0].t->id;
         if(cur.textures[cur.diffusetmu]!=diffusetex)
@@ -1326,20 +1197,23 @@ static void changeshader(renderstate &cur, Shader *s, Slot &slot, bool shadowed)
 {
     if(glaring)
     {
-        static Shader *noglareshader = NULL, *noglareblendshader = NULL;
-        if(!noglareshader) noglareshader = lookupshaderbyname("noglareworld");
-        if(!noglareblendshader) noglareblendshader = lookupshaderbyname("noglareblendworld");
-        if(s->hasoption(4)) s->setvariant(cur.visibledynlights, 4, &slot, cur.blending ? noglareblendshader : noglareshader);
-        else s->setvariant(cur.blending ? 1 : 0, 4, &slot, cur.blending ? noglareblendshader : noglareshader);
+        Shader *g = s->hasvariant(min(s->variants[4].length()-1, cur.visibledynlights), 4);
+        if(g) g->set(&slot);
+        else
+        {
+            static Shader *noglareshader = NULL;
+            if(!noglareshader) noglareshader = lookupshaderbyname("noglareworld");
+            noglareshader->set(&slot);
+        }
     }
-    else if(fading && !cur.blending)
+    else if(fading)
     {
-        if(shadowed) s->setvariant(cur.visibledynlights, 3, &slot);
-        else s->setvariant(cur.visibledynlights, 2, &slot);
+        if(shadowed) s->variant(min(s->variants[3].length()-1, cur.visibledynlights), 3)->set(&slot);
+        else s->variant(min(s->variants[2].length()-1, cur.visibledynlights), 2)->set(&slot);
     }
-    else if(shadowed) s->setvariant(cur.visibledynlights, 1, &slot);
+    else if(shadowed) s->variant(min(s->variants[1].length()-1, cur.visibledynlights), 1)->set(&slot);
     else if(!cur.visibledynlights) s->set(&slot);
-    else s->setvariant(cur.visibledynlights-1, 0, &slot);
+    else s->variant(min(s->variants[0].length()-1, cur.visibledynlights-1))->set(&slot);
     if(s->type&SHADER_GLSLANG) cur.texgendim = -1;
 }
 
@@ -1390,8 +1264,8 @@ static void changetexgen(renderstate &cur, Slot &slot, int dim)
             glTexGenfv(GL_S, GL_OBJECT_PLANE, sgen);
             glTexGenfv(GL_T, GL_OBJECT_PLANE, tgen);
             glActiveTexture_(GL_TEXTURE0_ARB+cur.diffusetmu);
+            cur.mttexgen = cur.mtglow;
         }
-        cur.mttexgen = cur.mtglow;
     }
     else
     {
@@ -1493,8 +1367,7 @@ static void renderbatches(renderstate &cur, int pass)
         if(cur.vbuf != b.va->vbuf) 
         {
             changevbuf(cur, pass, b.va);
-            if(pass == RENDERPASS_DYNLIGHT) changedynlightpos(cur, b.va);
-            else changefogplane(cur, pass, b.va);
+            changefogplane(cur, pass, b.va);
         }
         if(pass == RENDERPASS_LIGHTMAP) 
         {
@@ -1597,7 +1470,9 @@ void renderfoggedvas(renderstate &cur, bool doquery = false)
 
     glDisable(GL_TEXTURE_2D);
         
-    glColor3ubv(watercolor.v);
+    uchar wcol[3];
+    getwatercolour(wcol);
+    glColor3ubv(wcol);
 
     loopv(foggedvas)
     {
@@ -1615,41 +1490,6 @@ void renderfoggedvas(renderstate &cur, bool doquery = false)
     foggedvas.setsizenodelete(0);
 }
 
-void rendershadowmappass(renderstate &cur, vtxarray *va)
-{
-    if(cur.vbuf!=va->vbuf) 
-    {
-        changevbuf(cur, RENDERPASS_SHADOWMAP, va);
-        changefogplane(cur, RENDERPASS_SHADOWMAP, va);
-    }
-
-    elementset *texs = va->eslist;
-    ushort *edata = va->edata;
-    loopi(va->texs)
-    {
-        elementset &es = texs[i];
-        int len = es.length[1] - es.length[0];
-        if(len > 0) 
-        {
-            drawtris(len, &edata[es.length[0]], es.minvert[1], es.maxvert[1]);
-            vtris += len/3;
-        }
-        len = es.length[3] - es.length[2];
-        if(len > 0) 
-        {
-            drawtris(len, &edata[es.length[2]], es.minvert[3], es.maxvert[3]);
-            vtris += len/3;
-        }
-        len = es.length[5] - es.length[4];
-        if(len > 0) 
-        {
-            drawtris(len, &edata[es.length[4]], es.minvert[5], es.maxvert[5]);
-            vtris += len/3;
-        }
-        edata += es.length[5];
-    }
-}
-
 VAR(batchgeom, 0, 1, 1);
 
 void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_LIGHTMAP, bool fogpass = false, bool doquery = false)
@@ -1672,7 +1512,7 @@ void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_LIGHTMAP, bo
                 foggedvas.add(va);
                 break;
             }
-            if(renderpath!=R_FIXEDFUNCTION && !envmapping && !glaring)
+            if(renderpath!=R_FIXEDFUNCTION && !envmapping)
             {
                 va->shadowed = isshadowmapreceiver(va);
                 calcdynlightmask(va);
@@ -1683,24 +1523,6 @@ void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_LIGHTMAP, bo
             else if(!batchgeom && geombatches.length()) renderbatches(cur, pass);
             break;
 
-        case RENDERPASS_LIGHTMAP_BLEND:
-        {
-            if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); });
-            ushort *edata = va->edata;
-            loopi(va->texs) edata += va->eslist[i].length[5];
-            mergetexs(va, &va->eslist[va->texs], va->blends, edata);
-            if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP); });
-            else if(!batchgeom && geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP);
-            break;
-        }
-
-        case RENDERPASS_DYNLIGHT:
-            if(cur.dynlightpos.dist_to_bb(va->geommin, va->geommax) >= cur.dynlightradius) break;
-            vverts += va->verts;
-            mergetexs(va);
-            if(!batchgeom && geombatches.length()) renderbatches(cur, pass);
-            break;
-
         case RENDERPASS_FOG:
             if(cur.vbuf!=va->vbuf)
             {
@@ -1710,11 +1532,7 @@ void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_LIGHTMAP, bo
             drawvatris(va, 3*va->tris, va->edata);
             xtravertsva += va->verts;
             break;
-
-        case RENDERPASS_SHADOWMAP:
-            if(isshadowmapreceiver(va)) rendershadowmappass(cur, va);
-            break;
-
+ 
         case RENDERPASS_CAUSTICS:
             if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va);
             drawvatris(va, 3*va->tris, va->edata);
@@ -1733,65 +1551,55 @@ VAR(oqdist, 0, 256, 1024);
 VAR(zpass, 0, 1, 1);
 VAR(glowpass, 0, 1, 1);
 
-GLuint fogtex = 0;
+extern int ati_texgen_bug;
 
-void createfogtex()
+static void setuptexgen(int dims = 2)
 {
-    extern int bilinear;
-    uchar buf[2*256] = { 255, 0, 255, 255 };
-    if(!bilinear) loopi(256) { buf[2*i] = 255; buf[2*i+1] = i; }
-    glGenTextures(1, &fogtex);
-    createtexture(fogtex, bilinear ? 2 : 256, 1, buf, 3, 1, GL_LUMINANCE_ALPHA, GL_TEXTURE_1D);
+    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+    glEnable(GL_TEXTURE_GEN_S);
+    if(dims>=2) 
+    {
+        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+        glEnable(GL_TEXTURE_GEN_T);
+        if(ati_texgen_bug) glEnable(GL_TEXTURE_GEN_R);     // should not be needed, but apparently makes some ATI drivers happy
+    }
 }
 
-GLuint attenxytex = 0, attenztex = 0;
-
-static GLuint createattenxytex(int size)
+static void disabletexgen(int dims = 2)
 {
-    uchar *data = new uchar[size*size], *dst = data;
-    loop(y, size) loop(x, size)
+    glDisable(GL_TEXTURE_GEN_S);
+    if(dims>=2)
     {
-        float dx = 2*float(x)/(size-1) - 1, dy = 2*float(y)/(size-1) - 1;
-        float atten = max(0.0f, 1.0f - dx*dx - dy*dy);
-        *dst++ = uchar(atten*255);
+        glDisable(GL_TEXTURE_GEN_T);
+        if(ati_texgen_bug) glDisable(GL_TEXTURE_GEN_R);
     }
-    GLuint tex = 0;
-    glGenTextures(1, &tex);
-    createtexture(tex, size, size, data, 3, 1, GL_ALPHA);
-    delete[] data;
-    return tex;
 }
 
-static GLuint createattenztex(int size)
+GLuint fogtex = 0;
+
+void createfogtex()
 {
-    uchar *data = new uchar[size], *dst = data;
-    loop(z, size) 
-    {
-        float dz = 2*float(z)/(size-1) - 1;
-        float atten = dz*dz;
-        *dst++ = uchar(atten*255);
-    }
-    GLuint tex = 0;
-    glGenTextures(1, &tex);
-    createtexture(tex, size, 1, data, 3, 1, GL_ALPHA, GL_TEXTURE_1D);
-    delete[] data;
-    return tex;
+    extern int bilinear;
+    uchar buf[2*256] = { 255, 0, 255, 255 };
+    if(!bilinear) loopi(256) { buf[2*i] = 255; buf[2*i+1] = i; }
+    glGenTextures(1, &fogtex);
+    createtexture(fogtex, bilinear ? 2 : 256, 1, buf, 3, false, GL_LUMINANCE_ALPHA, GL_TEXTURE_1D);
 }
 
 #define NUMCAUSTICS 32
 
+VARR(causticscale, 0, 100, 10000);
+VARR(causticmillis, 0, 75, 1000);
+VARP(caustics, 0, 1, 1);
+
 static Texture *caustictex[NUMCAUSTICS] = { NULL };
 
-void loadcaustics(bool force)
+void loadcaustics()
 {
-    static bool needcaustics = false;
-    if(force) needcaustics = true;
-    if(!caustics || !needcaustics) return;
-    useshaderbyname("caustic");
     if(caustictex[0]) return;
     loopi(NUMCAUSTICS)
     {
-        defformatstring(name)(
+        s_sprintfd(name)(
             renderpath==R_FIXEDFUNCTION ? 
                 "<mad:0.6,0.4>packages/caustics/caust%.2d.png" :
                 "<mad:-0.6,0.6>packages/caustics/caust%.2d.png",
@@ -1802,20 +1610,16 @@ void loadcaustics(bool force)
 
 void cleanupva()
 {
-    clearvas(worldroot);
+    vaclearc(worldroot);
     clearqueries();
     if(fogtex) { glDeleteTextures(1, &fogtex); fogtex = 0; }
-    if(attenxytex) { glDeleteTextures(1, &attenxytex); attenxytex = 0; }
-    if(attenztex) { glDeleteTextures(1, &attenztex); attenztex = 0; }
     loopi(NUMCAUSTICS) caustictex[i] = NULL;
 }
 
-VARR(causticscale, 0, 100, 10000);
-VARR(causticmillis, 0, 75, 1000);
-VARFP(caustics, 0, 1, 1, loadcaustics());
-
 void setupcaustics(int tmu, float blend, GLfloat *color = NULL)
 {
+    if(!caustictex[0]) loadcaustics();
+
     GLfloat s[4] = { 0.011f, 0, 0.0066f, 0 };
     GLfloat t[4] = { 0, 0.011f, 0.0066f, 0 };
     loopk(3)
@@ -1880,15 +1684,7 @@ void setupTMUs(renderstate &cur, float causticspass, bool fogpass)
                 cur.lightmaptmu = 3;
                 if(maxtmus>=5)
                 {
-                    if(fogpass) 
-                    {
-                        if(glowpass && maxtmus>=6) 
-                        { 
-                            cur.fogtmu = 5;
-                            cur.glowtmu = 4;
-                        }
-                        else cur.fogtmu = 4;
-                    } 
+                    if(fogpass) cur.fogtmu = 4;
                     else if(glowpass) cur.glowtmu = 4;
                 }
             }
@@ -1899,17 +1695,19 @@ void setupTMUs(renderstate &cur, float causticspass, bool fogpass)
         {
             glActiveTexture_(GL_TEXTURE0_ARB+cur.glowtmu);
             setuptexgen();
-            setuptmu(cur.glowtmu, "P + T", "= Pa");
+            setuptmu(cur.glowtmu, "P + T");
         }
         if(cur.fogtmu>=0)
         {
             glActiveTexture_(GL_TEXTURE0_ARB+cur.fogtmu);
             glEnable(GL_TEXTURE_1D);
             setuptexgen(1);
-            setuptmu(cur.fogtmu, "C , P @ Ta", "= Pa");
+            setuptmu(cur.fogtmu, "C , P @ Ta");
             if(!fogtex) createfogtex();
             glBindTexture(GL_TEXTURE_1D, fogtex);
-            loopk(3) cur.color[k] = watercolor[k]/255.0f;
+            uchar wcol[3];
+            getwatercolour(wcol);
+            loopk(3) cur.color[k] = wcol[k]/255.0f;
         }
         if(cur.causticstmu>=0) setupcaustics(cur.causticstmu, causticspass, cur.color);
     }
@@ -1920,7 +1718,7 @@ void setupTMUs(renderstate &cur, float causticspass, bool fogpass)
         glEnableClientState(GL_COLOR_ARRAY);
         loopi(8-2) { glActiveTexture_(GL_TEXTURE2_ARB+i); glEnable(GL_TEXTURE_2D); }
         glActiveTexture_(GL_TEXTURE0_ARB);
-        setenvparamf("ambient", SHPARAM_PIXEL, 5, ambientcolor[0]/255.0f, ambientcolor[1]/255.0f, ambientcolor[2]/255.0f);
+        setenvparamf("ambient", SHPARAM_PIXEL, 5, hdr.ambient/255.0f, hdr.ambient/255.0f, hdr.ambient/255.0f);
         setenvparamf("millis", SHPARAM_VERTEX, 6, lastmillis/1000.0f, lastmillis/1000.0f, lastmillis/1000.0f);
     }
  
@@ -1931,7 +1729,7 @@ void setupTMUs(renderstate &cur, float causticspass, bool fogpass)
         glActiveTexture_(GL_TEXTURE0_ARB+cur.lightmaptmu);
         glClientActiveTexture_(GL_TEXTURE0_ARB+cur.lightmaptmu);
 
-        setuptmu(cur.lightmaptmu, "P * T x 2", "= Ta");
+        setuptmu(cur.lightmaptmu, "P * T x 2");
         glEnable(GL_TEXTURE_2D);
         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
         glMatrixMode(GL_TEXTURE);
@@ -2009,7 +1807,7 @@ void cleanupTMUs(renderstate &cur)
 #define FIRSTVA (reflecting ? reflectedva : visibleva)
 #define NEXTVA (reflecting ? va->rnext : va->next)
 
-static void rendergeommultipass(renderstate &cur, int pass, bool fogpass)
+void rendergeommultipass(renderstate &cur, int pass, bool fogpass)
 {
     cur.vbuf = 0;
     for(vtxarray *va = FIRSTVA; va; va = NEXTVA)
@@ -2034,10 +1832,6 @@ static void rendergeommultipass(renderstate &cur, int pass, bool fogpass)
 VAR(oqgeom, 0, 1, 1);
 VAR(oqbatch, 0, 1, 1);
 
-VAR(dbgffsm, 0, 0, 1);
-VAR(dbgffdl, 0, 0, 1);
-VAR(ffdlscissor, 0, 1, 1);
-
 void rendergeom(float causticspass, bool fogpass)
 {
     renderstate cur;
@@ -2056,10 +1850,10 @@ void rendergeom(float causticspass, bool fogpass)
     if(!doOQ) 
     {
         setupTMUs(cur, causticspass, fogpass);
-        if(shadowmap && !envmapping && !glaring && renderpath!=R_FIXEDFUNCTION) pushshadowmap();
+        if(shadowmap) pushshadowmap();
     }
 
-    int hasdynlights = finddynlights();
+    finddynlights();
 
     glPushMatrix();
 
@@ -2067,7 +1861,6 @@ void rendergeom(float causticspass, bool fogpass)
 
     resetbatches();
 
-    int blends = 0;
     for(vtxarray *va = FIRSTVA; va; va = NEXTVA)
     {
         if(!va->texs) continue;
@@ -2148,7 +1941,6 @@ void rendergeom(float causticspass, bool fogpass)
             if(va->occluded >= OCCLUDE_GEOM) continue;
         }
 
-        if(!doOQ) blends += va->blends;
         renderva(cur, va, doOQ ? RENDERPASS_Z : (nolights ? RENDERPASS_COLOR : RENDERPASS_LIGHTMAP), fogpass, true);
     }
 
@@ -2160,7 +1952,7 @@ void rendergeom(float causticspass, bool fogpass)
     if(doOQ)
     {
         setupTMUs(cur, causticspass, fogpass);
-        if(shadowmap && !envmapping && !glaring && renderpath!=R_FIXEDFUNCTION) 
+        if(shadowmap) 
         {
             glPopMatrix();
             glPushMatrix();
@@ -2199,7 +1991,6 @@ void rendergeom(float causticspass, bool fogpass)
                 if(va->occluded >= OCCLUDE_GEOM) continue;
             }
             
-            blends += va->blends;
             renderva(cur, va, nolights ? RENDERPASS_COLOR : RENDERPASS_LIGHTMAP, fogpass);
         }
         if(geombatches.length()) renderbatches(cur, nolights ? RENDERPASS_COLOR : RENDERPASS_LIGHTMAP);
@@ -2218,8 +2009,6 @@ void rendergeom(float causticspass, bool fogpass)
                 va->occluded = pvsoccluded(va->geommin, va->geommax) ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
                 if(va->occluded >= OCCLUDE_GEOM) continue;
             }
-
-            blends += va->blends;
             renderva(cur, va, nolights ? RENDERPASS_COLOR : RENDERPASS_LIGHTMAP, fogpass);
         }
         if(geombatches.length()) renderbatches(cur, nolights ? RENDERPASS_COLOR : RENDERPASS_LIGHTMAP);
@@ -2227,48 +2016,7 @@ void rendergeom(float causticspass, bool fogpass)
         if(foggedvas.empty()) glDepthFunc(GL_LESS);
     }
 
-    if(blends && (renderpath!=R_FIXEDFUNCTION || !nolights))
-    {
-        if(shadowmap && !envmapping && !glaring && renderpath!=R_FIXEDFUNCTION)
-        {
-            glPopMatrix();
-            glPushMatrix();
-            resetorigin();
-        }
-        if(foggedvas.empty()) glDepthFunc(GL_LEQUAL);
-        glDepthMask(GL_FALSE);
-        glEnable(GL_BLEND);
-        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
-
-        cur.vbuf = 0;
-        cur.blending = true;
-        for(vtxarray **prevva = &FIRSTVA, *va = FIRSTVA; va; prevva = &NEXTVA, va = NEXTVA)
-        {
-            if(!va->blends || va->occluded >= OCCLUDE_GEOM) continue;
-            if(refracting)
-            {
-                if(refracting < 0 ? va->geommin.z > reflectz : va->geommax.z <= reflectz) continue;
-                if(isvisiblecube(va->o, va->size) >= VFC_NOT_VISIBLE) continue;
-                if((!hasOQ || !oqfrags) && va->distance > reflectdist) break;
-            }
-            else if(reflecting)
-            {
-                if(va->geommax.z <= reflectz || (va->rquery && checkquery(va->rquery))) continue;
-            }
-            if(fogpass ? va->geommax.z <= reflectz-waterfog : va->curvfc==VFC_FOGGED) continue;
-            renderva(cur, va, RENDERPASS_LIGHTMAP_BLEND, fogpass);
-        }
-        if(geombatches.length()) renderbatches(cur, RENDERPASS_LIGHTMAP);
-        cur.blending = false;
-
-        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-        glDisable(GL_BLEND);
-        glDepthMask(GL_TRUE);
-        if(foggedvas.empty()) glDepthFunc(GL_LESS);
-    }
-
-    if(shadowmap && !envmapping && !glaring && renderpath!=R_FIXEDFUNCTION) popshadowmap();
+    if(shadowmap) popshadowmap();
 
     cleanupTMUs(cur);
 
@@ -2278,7 +2026,7 @@ void rendergeom(float causticspass, bool fogpass)
         if(doOQ) glDepthFunc(GL_LESS);
     }
 
-    if(renderpath==R_FIXEDFUNCTION ? (glowpass && cur.skippedglow) || (causticspass>=1 && cur.causticstmu<0) || (fogpass && cur.fogtmu<0) || (shadowmap && shadowmapcasters) || hasdynlights : causticspass)
+    if(renderpath==R_FIXEDFUNCTION ? (glowpass && cur.skippedglow) || (causticspass>=1 && cur.causticstmu<0) || (fogpass && cur.fogtmu<0) : causticspass)
     {
         glDepthFunc(GL_LEQUAL);
         glDepthMask(GL_FALSE);
@@ -2312,7 +2060,7 @@ void rendergeom(float causticspass, bool fogpass)
                 resettmu(0);
                 glActiveTexture_(GL_TEXTURE1_ARB);
                 resettmu(1);
-                disabletexgen(1);
+                disabletexgen();
                 glDisable(GL_TEXTURE_1D);
                 glActiveTexture_(GL_TEXTURE0_ARB);
             } 
@@ -2339,101 +2087,7 @@ void rendergeom(float causticspass, bool fogpass)
             }
             glActiveTexture_(GL_TEXTURE0_ARB);
         }
-
-        if(renderpath==R_FIXEDFUNCTION && shadowmap && shadowmapcasters)
-        {
-            glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
-            glFogfv(GL_FOG_COLOR, zerofog);
-            glPopMatrix();
-            glPushMatrix();
-            pushshadowmap();
-            resetorigin();
-            if(cur.fogtmu>=0)
-            {
-                setuptmu(0, "C * T");
-                glActiveTexture_(GL_TEXTURE1_ARB);
-                glEnable(GL_TEXTURE_1D);
-                setuptexgen(1);
-                setuptmu(1, "P * T~a");
-                if(!fogtex) createfogtex();
-                glBindTexture(GL_TEXTURE_1D, fogtex);
-                glActiveTexture_(GL_TEXTURE0_ARB);
-            }
-            if(dbgffsm) { glDisable(GL_BLEND); glDisable(GL_TEXTURE_2D); glColor3f(1, 0, 1); }
-            rendergeommultipass(cur, RENDERPASS_SHADOWMAP, fogpass);
-            if(dbgffsm) { glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); }
-            popshadowmap();
-            if(cur.fogtmu>=0)
-            {
-                resettmu(0);
-                glActiveTexture_(GL_TEXTURE1_ARB);
-                resettmu(1);
-                disabletexgen();
-                glDisable(GL_TEXTURE_1D);
-                glActiveTexture_(GL_TEXTURE0_ARB);
-            }
-        }
-   
-        if(renderpath==R_FIXEDFUNCTION && hasdynlights)
-        {
-            glBlendFunc(GL_SRC_ALPHA, dbgffdl ? GL_ZERO : GL_ONE);
-            glFogfv(GL_FOG_COLOR, zerofog);
-
-            if(!attenxytex) attenxytex = createattenxytex(64);
-            glBindTexture(GL_TEXTURE_2D, attenxytex);
-
-            setuptmu(0, "= C", "= Ta");
-            setuptexgen();
-
-            glActiveTexture_(GL_TEXTURE1_ARB);
-            setuptmu(1, "= P", "Pa - Ta");
-            setuptexgen(1);
-            if(!attenztex) attenztex = createattenztex(64);
-            glBindTexture(GL_TEXTURE_1D, attenztex);
-            glEnable(GL_TEXTURE_1D);
- 
-            glActiveTexture_(GL_TEXTURE2_ARB);
-            cur.diffusetmu = 2;
-            setuptmu(2, "P * T x 4", "= Pa");
-            setuptexgen();
-            glEnable(GL_TEXTURE_2D);
-
-            vec lightcolor;
-            for(int n = 0; getdynlight(n, cur.dynlightpos, cur.dynlightradius, lightcolor); n++)
-            {
-                lightcolor.mul(0.5f);
-                if(fogpass && cur.fogtmu>=0)
-                {
-                    float fog = (reflectz - cur.dynlightpos.z)/waterfog;
-                    if(fog >= 1.0f) continue;
-                    lightcolor.mul(1.0f - max(fog, 0.0f));
-                }
-                glColor3f(lightcolor.x, lightcolor.y, lightcolor.z);
-                if(ffdlscissor)
-                {
-                    float sx1, sy1, sx2, sy2;
-                    calcspherescissor(cur.dynlightpos, cur.dynlightradius, sx1, sy1, sx2, sy2);
-                    pushscissor(sx1, sy1, sx2, sy2);
-                }
-                resetorigin();
-                rendergeommultipass(cur, RENDERPASS_DYNLIGHT, fogpass);
-                if(ffdlscissor) popscissor();
-            }
-
-            glDisable(GL_TEXTURE_2D);
-            disabletexgen();
-            resettmu(2);
-
-            glActiveTexture_(GL_TEXTURE1_ARB);
-            glDisable(GL_TEXTURE_1D);
-            resettmu(1);
-            disabletexgen(1);
-            
-            glActiveTexture_(GL_TEXTURE0_ARB);
-            resettmu(0);
-            disabletexgen();
-        }
-
+    
         if(renderpath==R_FIXEDFUNCTION && fogpass && cur.fogtmu<0)
         {
             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -2443,13 +2097,15 @@ void rendergeom(float causticspass, bool fogpass)
             if(!fogtex) createfogtex();
             glBindTexture(GL_TEXTURE_1D, fogtex);
             setuptexgen(1);
-            glColor3ubv(watercolor.v);
+            uchar wcol[3];
+            getwatercolour(wcol);
+            glColor3ubv(wcol);
             rendergeommultipass(cur, RENDERPASS_FOG, fogpass);
             disabletexgen(1);
             glDisable(GL_TEXTURE_1D);
             glEnable(GL_TEXTURE_2D);
         }
-
+ 
         glFogfv(GL_FOG_COLOR, oldfogc);
         glDisable(GL_BLEND);
         glDepthFunc(GL_LESS);
diff --git a/engine/scale.h b/engine/scale.h
deleted file mode 100644
index 7e87108..0000000
--- a/engine/scale.h
+++ /dev/null
@@ -1,122 +0,0 @@
-static void FUNCNAME(halvetexture)(uchar *src, uint sw, uint sh, uint stride, uchar *dst)
-{
-    for(uchar *yend = &src[sh*stride]; src < yend;)
-    {
-        for(uchar *xend = &src[stride]; src < xend; src += 2*BPP, dst += BPP)
-        {
-            #define OP(c, n) dst[n] = (uint(src[n]) + uint(src[n+BPP]) + uint(src[stride+n]) + uint(src[stride+n+BPP]))>>2
-            PIXELOP
-            #undef OP
-        }
-        src += stride;
-    }
-}
-
-static void FUNCNAME(shifttexture)(uchar *src, uint sw, uint sh, uint stride, uchar *dst, uint dw, uint dh)
-{
-    uint wfrac = sw/dw, hfrac = sh/dh, wshift = 0, hshift = 0;
-    while(dw<<wshift < sw) wshift++;
-    while(dh<<hshift < sh) hshift++;
-    uint tshift = wshift + hshift;
-    for(uchar *yend = &src[sh*stride]; src < yend;)
-    {
-        for(uchar *xend = &src[stride]; src < xend; src += wfrac*BPP, dst += BPP)
-        {        
-            #define OP(c, n) c##t = 0
-            DEFPIXEL
-            #undef OP
-            for(uchar *ycur = src, *xend = &ycur[wfrac*BPP], *yend = &src[hfrac*stride]; 
-                ycur < yend; 
-                ycur += stride, xend += stride)
-            {
-                for(uchar *xcur = ycur; xcur < xend; xcur += BPP)
-                {
-                    #define OP(c, n) c##t += xcur[n]    
-                    PIXELOP
-                    #undef OP
-                }
-            }
-            #define OP(c, n) dst[n] = (c##t)>>tshift
-            PIXELOP
-            #undef OP
-        }
-        src += (hfrac-1)*stride; 
-    }
-}
-
-static void FUNCNAME(scaletexture)(uchar *src, uint sw, uint sh, uint stride, uchar *dst, uint dw, uint dh)
-{
-    uint wfrac = (sw<<12)/dw, hfrac = (sh<<12)/dh, darea = dw*dh, sarea = sw*sh;
-    int over, under;
-    for(over = 0; (darea>>over) > sarea; over++);
-    for(under = 0; (darea<<under) < sarea; under++);
-    uint cscale = clamp(under, over - 12, 12),
-         ascale = clamp(12 + under - over, 0, 24),
-         dscale = ascale + 12 - cscale,
-         area = ((unsigned long long int)darea<<ascale)/sarea;
-    dw *= wfrac;
-    dh *= hfrac;
-    for(uint y = 0; y < dh; y += hfrac)
-    {
-        const uint yn = y + hfrac - 1, yi = y>>12, h = (yn>>12) - yi, ylow = ((yn|(-int(h)>>24))&0xFFFU) + 1 - (y&0xFFFU), yhigh = (yn&0xFFFU) + 1;
-        const uchar *ysrc = &src[yi*stride]; 
-        for(uint x = 0; x < dw; x += wfrac, dst += BPP)
-        {
-            const uint xn = x + wfrac - 1, xi = x>>12, w = (xn>>12) - xi, xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU), xhigh = (xn&0xFFFU) + 1;
-            const uchar *xsrc = &ysrc[xi*BPP], *xend = &xsrc[w*BPP];
-            #define OP(c, n) c##t = 0
-            DEFPIXEL
-            #undef OP
-            for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
-            {
-                #define OP(c, n) c##t += xcur[n]    
-                PIXELOP
-                #undef OP
-            }
-            #define OP(c, n) c##t = (ylow*(c##t + ((xsrc[n]*xlow + xend[n]*xhigh)>>12)))>>cscale
-            PIXELOP
-            #undef OP
-            if(h)
-            {
-                xsrc += stride;
-                xend += stride;
-                for(uint hcur = h; --hcur; xsrc += stride, xend += stride)
-                {
-                    #define OP(c, n) c = 0
-                    DEFPIXEL
-                    #undef OP
-                    for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
-                    {
-                        #define OP(c, n) c += xcur[n]    
-                        PIXELOP
-                        #undef OP
-                    }
-                    #define OP(c, n) c##t += ((c<<12) + xsrc[n]*xlow + xend[n]*xhigh)>>cscale;
-                    PIXELOP
-                    #undef OP
-                }
-                #define OP(c, n) c = 0
-                DEFPIXEL
-                #undef OP
-                for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
-                {
-                    #define OP(c, n) c += xcur[n]    
-                    PIXELOP
-                    #undef OP
-                }
-                #define OP(c, n) c##t += (yhigh*(c + ((xsrc[n]*xlow + xend[n]*xhigh)>>12)))>>cscale
-                PIXELOP
-                #undef OP
-            }
-            #define OP(c, n) dst[n] = (c##t * area)>>dscale
-            PIXELOP
-            #undef OP
-        }
-    }
-}
-
-#undef FUNCNAME
-#undef DEFPIXEL
-#undef PIXELOP
-#undef BPP
-
diff --git a/engine/server.cpp b/engine/server.cpp
index 79cb767..ec951f8 100644
--- a/engine/server.cpp
+++ b/engine/server.cpp
@@ -1,54 +1,74 @@
 // server.cpp: little more than enhanced multicaster
 // runs dedicated or as client coroutine
 
-#include "engine.h"
+#include "pch.h"
 
 #ifdef STANDALONE
+#include "cube.h"
+#include "iengine.h"
+#include "igame.h"
+void localservertoclient(int chan, uchar *buf, int len) {}
 void fatal(const char *s, ...) 
 { 
-    void cleanupserver();
+    void cleanupserver(); 
     cleanupserver(); 
-    defvformatstring(msg,s,s);
+    s_sprintfdlv(msg,s,s);
     printf("servererror: %s\n", msg); 
     exit(EXIT_FAILURE); 
 }
+#else
+#include "engine.h"
+#endif
 
-void conoutfv(int type, const char *fmt, va_list args)
-{
-    string sf, sp;
-    vformatstring(sf, fmt, args);
-    filtertext(sp, sf);
-    puts(sp);
-}
+igameclient     *cl = NULL;
+igameserver     *sv = NULL;
+iclientcom      *cc = NULL;
+icliententities *et = NULL;
 
-void conoutf(const char *fmt, ...)
+hashtable<const char *, igame *> *gamereg = NULL;
+
+vector<char *> gameargs;
+
+void registergame(const char *name, igame *ig)
 {
-    va_list args;
-    va_start(args, fmt);
-    conoutfv(CON_INFO, fmt, args);
-    va_end(args);
+    if(!gamereg) gamereg = new hashtable<const char *, igame *>;
+    (*gamereg)[name] = ig;
 }
 
-void conoutf(int type, const char *fmt, ...)
+void initgame(const char *game)
 {
-    va_list args;
-    va_start(args, fmt);
-    conoutfv(type, fmt, args);
-    va_end(args);
-}
+    igame **ig = gamereg->access(game);
+    if(!ig) fatal("cannot start game module: %s", game);
+    sv = (*ig)->newserver();
+    cl = (*ig)->newclient();
+    if(cl)
+    {
+        cc = cl->getcom();
+        et = cl->getents();
+        cl->initclient();
+    }
+    loopv(gameargs)
+    {
+        if(!cl || !cl->clientoption(gameargs[i]))
+        {
+            if(!sv->serveroption(gameargs[i])) 
+#ifdef STANDALONE
+                printf("unknown command-line option: %s\n", gameargs[i]);
+#else
+                conoutf(CON_ERROR, "unknown command-line option: %s", gameargs[i]);
 #endif
+        }
+    }
+}
 
 // all network traffic is in 32bit ints, which are then compressed using the following simple scheme (assumes that most values are small).
 
-template<class T>
-static inline void putint_(T &p, int n)
+void putint(ucharbuf &p, int n)
 {
     if(n<128 && n>-127) p.put(n);
     else if(n<0x8000 && n>=-0x8000) { p.put(0x80); p.put(n); p.put(n>>8); }
     else { p.put(0x81); p.put(n); p.put(n>>8); p.put(n>>16); p.put(n>>24); }
 }
-void putint(ucharbuf &p, int n) { putint_(p, n); }
-void putint(packetbuf &p, int n) { putint_(p, n); }
 
 int getint(ucharbuf &p)
 {
@@ -59,8 +79,7 @@ int getint(ucharbuf &p)
 }
 
 // much smaller encoding for unsigned integers up to 28 bits, but can handle signed
-template<class T>
-static inline void putuint_(T &p, int n)
+void putuint(ucharbuf &p, int n)
 {
     if(n < 0 || n >= (1<<21))
     {
@@ -82,8 +101,6 @@ static inline void putuint_(T &p, int n)
         p.put(n >> 14); 
     }
 }
-void putuint(ucharbuf &p, int n) { putuint_(p, n); }
-void putuint(packetbuf &p, int n) { putuint_(p, n); }
 
 int getuint(ucharbuf &p)
 {
@@ -98,30 +115,11 @@ int getuint(ucharbuf &p)
     return n;
 }
 
-template<class T>
-static inline void putfloat_(T &p, float f)
-{
-    lilswap(&f, 1);
-    p.put((uchar *)&f, sizeof(float));
-}
-void putfloat(ucharbuf &p, float f) { putfloat_(p, f); }
-void putfloat(packetbuf &p, float f) { putfloat_(p, f); }
-
-float getfloat(ucharbuf &p)
-{
-    float f;
-    p.get((uchar *)&f, sizeof(float));
-    return lilswap(f);
-}
-
-template<class T>
-static inline void sendstring_(const char *t, T &p)
+void sendstring(const char *t, ucharbuf &p)
 {
     while(*t) putint(p, *t++);
     putint(p, 0);
 }
-void sendstring(const char *t, ucharbuf &p) { sendstring_(t, p); }
-void sendstring(const char *t, packetbuf &p) { sendstring_(t, p); }
 
 void getstring(char *text, ucharbuf &p, int len)
 {
@@ -168,31 +166,75 @@ vector<client *> clients;
 ENetHost *serverhost = NULL;
 size_t bsend = 0, brec = 0;
 int laststatus = 0; 
-ENetSocket pongsock = ENET_SOCKET_NULL, lansock = ENET_SOCKET_NULL;
+ENetSocket pongsock = ENET_SOCKET_NULL;
 
 void cleanupserver()
 {
     if(serverhost) enet_host_destroy(serverhost);
-    serverhost = NULL;
+}
 
-    if(pongsock != ENET_SOCKET_NULL) enet_socket_destroy(pongsock);
-    if(lansock != ENET_SOCKET_NULL) enet_socket_destroy(lansock);
-    pongsock = lansock = ENET_SOCKET_NULL;
+void sendfile(int cn, int chan, FILE *file, const char *format, ...)
+{
+#ifndef STANDALONE
+    extern ENetHost *clienthost;
+#endif
+    if(cn < 0)
+    {
+#ifndef STANDALONE
+        if(!clienthost || clienthost->peers[0].state != ENET_PEER_STATE_CONNECTED) 
+#endif
+            return;
+    }
+    else if(cn >= clients.length() || clients[cn]->type != ST_TCPIP) return;
+
+    fseek(file, 0, SEEK_END);
+    int len = ftell(file);
+    bool reliable = false;
+    if(*format=='r') { reliable = true; ++format; }
+    ENetPacket *packet = enet_packet_create(NULL, MAXTRANS+len, ENET_PACKET_FLAG_RELIABLE);
+    rewind(file);
+
+    ucharbuf p(packet->data, packet->dataLength);
+    va_list args;
+    va_start(args, format);
+    while(*format) switch(*format++)
+    {
+        case 'i':
+        {
+            int n = isdigit(*format) ? *format++-'0' : 1;
+            loopi(n) putint(p, va_arg(args, int));
+            break;
+        }
+        case 's': sendstring(va_arg(args, const char *), p); break;
+        case 'l': putint(p, len); break;
+    }
+    va_end(args);
+    enet_packet_resize(packet, p.length()+len);
+
+    fread(&packet->data[p.length()], 1, len, file);
+    enet_packet_resize(packet, p.length()+len);
+
+    if(cn >= 0) enet_peer_send(clients[cn]->peer, chan, packet);
+#ifndef STANDALONE
+    else enet_peer_send(&clienthost->peers[0], chan, packet);
+#endif
+
+    if(!packet->referenceCount) enet_packet_destroy(packet);
 }
 
 void process(ENetPacket *packet, int sender, int chan);
 //void disconnect_client(int n, int reason);
 
-void *getclientinfo(int i) { return !clients.inrange(i) || clients[i]->type==ST_EMPTY ? NULL : clients[i]->info; }
-int getnumclients()        { return clients.length(); }
-uint getclientip(int n)    { return clients.inrange(n) && clients[n]->type==ST_TCPIP ? clients[n]->peer->address.host : 0; }
+void *getinfo(int i)    { return !clients.inrange(i) || clients[i]->type==ST_EMPTY ? NULL : clients[i]->info; }
+int getnumclients()     { return clients.length(); }
+uint getclientip(int n) { return clients.inrange(n) && clients[n]->type==ST_TCPIP ? clients[n]->peer->address.host : 0; }
 
 void sendpacket(int n, int chan, ENetPacket *packet, int exclude)
 {
     if(n<0)
     {
-        server::recordpacket(chan, packet->data, packet->dataLength);
-        loopv(clients) if(i!=exclude && server::allowbroadcast(i)) sendpacket(i, chan, packet);
+        sv->recordpacket(chan, packet->data, packet->dataLength);
+        loopv(clients) if(i!=exclude) sendpacket(i, chan, packet);
         return;
     }
     switch(clients[n]->type)
@@ -204,11 +246,9 @@ void sendpacket(int n, int chan, ENetPacket *packet, int exclude)
             break;
         }
 
-#ifndef STANDALONE
         case ST_LOCAL:
-            localservertoclient(chan, packet);
+            localservertoclient(chan, packet->data, (int)packet->dataLength);
             break;
-#endif
     }
 }
 
@@ -241,12 +281,6 @@ void sendf(int cn, int chan, const char *format, ...)
             loopi(n) putint(p, va_arg(args, int));
             break;
         }
-        case 'f':
-        {
-            int n = isdigit(*format) ? *format++-'0' : 1;
-            loopi(n) putfloat(p, (float)va_arg(args, double));
-            break;
-        }
         case 's': sendstring(va_arg(args, const char *), p); break;
         case 'm':
         {
@@ -264,96 +298,55 @@ void sendf(int cn, int chan, const char *format, ...)
     if(packet->referenceCount==0) enet_packet_destroy(packet);
 }
 
-void sendfile(int cn, int chan, stream *file, const char *format, ...)
-{
-    if(cn < 0)
-    {
-#ifdef STANDALONE
-        return;
-#endif
-    }
-    else if(!clients.inrange(cn)) return;
-
-    int len = file->size();
-    if(len <= 0) return;
-
-    bool reliable = false;
-    if(*format=='r') { reliable = true; ++format; }
-    ENetPacket *packet = enet_packet_create(NULL, MAXTRANS+len, ENET_PACKET_FLAG_RELIABLE);
-
-    ucharbuf p(packet->data, packet->dataLength);
-    va_list args;
-    va_start(args, format);
-    while(*format) switch(*format++)
-    {
-        case 'i':
-        {
-            int n = isdigit(*format) ? *format++-'0' : 1;
-            loopi(n) putint(p, va_arg(args, int));
-            break;
-        }
-        case 's': sendstring(va_arg(args, const char *), p); break;
-        case 'l': putint(p, len); break;
-    }
-    va_end(args);
-    enet_packet_resize(packet, p.length()+len);
-
-    file->seek(0, SEEK_SET);
-    file->read(&packet->data[p.length()], len);
-    enet_packet_resize(packet, p.length()+len);
-
-    if(cn >= 0) sendpacket(cn, chan, packet, -1);
-#ifndef STANDALONE
-    else sendclientpacket(packet, chan);
-#endif
-    if(!packet->referenceCount) enet_packet_destroy(packet);
-}
-
-const char *disc_reasons[] = { "normal", "end of packet", "client num", "kicked/banned", "tag type", "ip is banned", "server is in private mode", "server FULL (maxclients)", "connection timed out" };
+const char *disc_reasons[] = { "normal", "end of packet", "client num", "kicked/banned", "tag type", "ip is banned", "server is in private mode", "server FULL (maxclients)" };
 
 void disconnect_client(int n, int reason)
 {
     if(clients[n]->type!=ST_TCPIP) return;
+    s_sprintfd(s)("client (%s) disconnected because: %s\n", clients[n]->hostname, disc_reasons[reason]);
+    puts(s);
     enet_peer_disconnect(clients[n]->peer, reason);
-    server::clientdisconnect(n);
+    sv->clientdisconnect(n);
     clients[n]->type = ST_EMPTY;
     clients[n]->peer->data = NULL;
-    server::deleteclientinfo(clients[n]->info);
+    sv->deleteinfo(clients[n]->info);
     clients[n]->info = NULL;
-    defformatstring(s)("client (%s) disconnected because: %s", clients[n]->hostname, disc_reasons[reason]);
-    puts(s);
-    server::sendservmsg(s);
+    sv->sendservmsg(s);
 }
 
-void kicknonlocalclients(int reason)
+void process(ENetPacket *packet, int sender, int chan)   // sender may be -1
 {
-    loopv(clients) if(clients[i]->type==ST_TCPIP) disconnect_client(i, reason);
+    ucharbuf p(packet->data, (int)packet->dataLength);
+    sv->parsepacket(sender, chan, (packet->flags&ENET_PACKET_FLAG_RELIABLE)!=0, p);
+    if(p.overread()) { disconnect_client(sender, DISC_EOP); return; }
 }
 
-void process(ENetPacket *packet, int sender, int chan)   // sender may be -1
+void send_welcome(int n)
 {
-    packetbuf p(packet);
-    server::parsepacket(sender, chan, p);
-    if(p.overread()) { disconnect_client(sender, DISC_EOP); return; }
+    ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+    ucharbuf p(packet->data, packet->dataLength);
+    int chan = sv->welcomepacket(p, n, packet);
+    enet_packet_resize(packet, p.length());
+    sendpacket(n, chan, packet);
+    if(packet->referenceCount==0) enet_packet_destroy(packet);
 }
 
 void localclienttoserver(int chan, ENetPacket *packet)
 {
-    client *c = NULL;
-    loopv(clients) if(clients[i]->type==ST_LOCAL) { c = clients[i]; break; }
-    if(c) process(packet, c->num, chan);
+    process(packet, 0, chan);
+    if(packet->referenceCount==0) enet_packet_destroy(packet);
 }
 
 client &addclient()
 {
     loopv(clients) if(clients[i]->type==ST_EMPTY)
     {
-        clients[i]->info = server::newclientinfo();
+        clients[i]->info = sv->newinfo();
         return *clients[i];
     }
     client *c = new client;
     c->num = clients.length();
-    c->info = server::newclientinfo();
+    c->info = sv->newinfo();
     clients.add(c);
     return *c;
 }
@@ -363,13 +356,41 @@ int localclients = 0, nonlocalclients = 0;
 bool hasnonlocalclients() { return nonlocalclients!=0; }
 bool haslocalclients() { return localclients!=0; }
 
+static ENetAddress pongaddr;
+
+void sendserverinforeply(ucharbuf &p)
+{
+    ENetBuffer buf;
+    buf.data = p.buf;
+    buf.dataLength = p.length();
+    enet_socket_send(pongsock, &pongaddr, &buf, 1);
+}
+
+void sendpongs()        // reply all server info requests
+{
+    ENetBuffer buf;
+    uchar pong[MAXTRANS];
+    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, &pongaddr, &buf, 1);
+        if(len < 0) return;
+        ucharbuf req(pong, len), p(pong, sizeof(pong));
+        p.len += len;
+        sv->serverinforeply(req, p);
+    }
+}      
+
 #ifdef STANDALONE
 bool resolverwait(const char *name, ENetAddress *address)
 {
     return enet_address_set_host(address, name) >= 0;
 }
 
-int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress &remoteaddress)
+int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &remoteaddress)
 {
     int result = enet_socket_connect(sock, &remoteaddress);
     if(result<0) enet_socket_destroy(sock);
@@ -377,209 +398,139 @@ int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress
 }
 #endif
 
-ENetSocket mastersock = ENET_SOCKET_NULL;
-ENetAddress masteraddress = { ENET_HOST_ANY, ENET_PORT_ANY }, serveraddress = { ENET_HOST_ANY, ENET_PORT_ANY };
-int lastupdatemaster = 0;
-vector<char> masterout, masterin;
-int masteroutpos = 0, masterinpos = 0;
-VARN(updatemaster, allowupdatemaster, 0, 1, 1);
-SVAR(mastername, server::defaultmaster());
-
-void disconnectmaster()
+ENetSocket httpgetsend(ENetAddress &remoteaddress, const char *hostname, const char *req, const char *ref, const char *agent, ENetAddress *localaddress = NULL)
 {
-    if(mastersock == ENET_SOCKET_NULL) return;
-
-    enet_socket_destroy(mastersock);
-    mastersock = ENET_SOCKET_NULL;
-
-    masterout.setsizenodelete(0);
-    masterin.setsizenodelete(0);
-    masteroutpos = masterinpos = 0;
-}
-
-ENetSocket connectmaster()
-{
-    if(!mastername[0]) return ENET_SOCKET_NULL;
-
-    if(masteraddress.host == ENET_HOST_ANY)
+    if(remoteaddress.host==ENET_HOST_ANY)
     {
 #ifdef STANDALONE
-        printf("looking up %s...\n", mastername);
+        printf("looking up %s...\n", hostname);
 #endif
-        masteraddress.port = server::masterport();
-        if(!resolverwait(mastername, &masteraddress)) return ENET_SOCKET_NULL;
-    }
-    ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM);
-    if(sock != ENET_SOCKET_NULL && serveraddress.host != ENET_HOST_ANY && enet_socket_bind(sock, &serveraddress) < 0)
-    {
-        enet_socket_destroy(sock);
-        sock = ENET_SOCKET_NULL;
+        if(!resolverwait(hostname, &remoteaddress)) return ENET_SOCKET_NULL;
     }
-    if(sock == ENET_SOCKET_NULL || connectwithtimeout(sock, mastername, masteraddress) < 0) 
-    {
+    ENetSocket sock = enet_socket_create(ENET_SOCKET_TYPE_STREAM, localaddress);
+    if(sock==ENET_SOCKET_NULL || connectwithtimeout(sock, hostname, remoteaddress)<0) 
+    { 
 #ifdef STANDALONE
         printf(sock==ENET_SOCKET_NULL ? "could not open socket\n" : "could not connect\n"); 
 #endif
-        return ENET_SOCKET_NULL;
+        return ENET_SOCKET_NULL; 
     }
-    
-    enet_socket_set_option(sock, ENET_SOCKOPT_NONBLOCK, 1);
+    ENetBuffer buf;
+    s_sprintfd(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);
+#ifdef STANDALONE
+    printf("sending request to %s...\n", hostname);
+#endif
+    enet_socket_send(sock, NULL, &buf, 1);
     return sock;
-}
+}  
 
-bool requestmaster(const char *req)
+bool httpgetreceive(ENetSocket sock, ENetBuffer &buf, int timeout = 0)
 {
-    if(mastersock == ENET_SOCKET_NULL)
+    if(sock==ENET_SOCKET_NULL) return false;
+    enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
+    if(enet_socket_wait(sock, &events, timeout) >= 0 && events)
     {
-        mastersock = connectmaster();
-        if(mastersock == ENET_SOCKET_NULL) return false;
+        int len = enet_socket_receive(sock, NULL, &buf, 1);
+        if(len<=0)
+        {
+            enet_socket_destroy(sock);
+            return false;
+        }
+        buf.data = ((char *)buf.data)+len;
+        ((char*)buf.data)[0] = 0;
+        buf.dataLength -= len;
     }
-
-    masterout.put(req, strlen(req));
     return true;
-}
+}  
 
-bool requestmasterf(const char *fmt, ...)
+uchar *stripheader(uchar *b)
 {
-    defvformatstring(req, fmt, fmt);
-    return requestmaster(req);
+    char *s = strstr((char *)b, "\n\r\n");
+    if(!s) s = strstr((char *)b, "\n\n");
+    return s ? (uchar *)s : b;
 }
 
-void processmasterinput()
-{
-    if(masterinpos >= masterin.length()) return;
-
-    char *input = &masterin[masterinpos], *end = (char *)memchr(input, '\n', masterin.length() - masterinpos);
-    while(end)
-    {
-        *end++ = '\0';
-
-        const char *args = input;
-        while(args < end && !isspace(*args)) args++;
-        int cmdlen = args - input;
-        while(args < end && isspace(*args)) args++;
-
-        if(!strncmp(input, "failreg", cmdlen))
-            conoutf(CON_ERROR, "master server registration failed: %s", args);
-        else if(!strncmp(input, "succreg", cmdlen))
-            conoutf("master server registration succeeded");
-        else server::processmasterinput(input, cmdlen, args);
-
-        masterinpos = end - masterin.getbuf();
-        input = end;
-        end = (char *)memchr(input, '\n', masterin.length() - masterinpos);
-    } 
-
-    if(masterinpos >= masterin.length())
-    {
-        masterin.setsizenodelete(0);
-        masterinpos = 0;
-    }
-}
+ENetSocket mssock = ENET_SOCKET_NULL;
+ENetAddress msaddress = { ENET_HOST_ANY, ENET_PORT_ANY };
+ENetAddress masterserver = { ENET_HOST_ANY, 80 };
+int lastupdatemaster = 0;
+string masterbase;
+string masterpath;
+uchar masterrep[MAXTRANS];
+ENetBuffer masterb;
 
-void flushmasteroutput()
+void updatemasterserver()
 {
-    if(masterout.empty()) return;
-
-    ENetBuffer buf;
-    buf.data = &masterout[masteroutpos];
-    buf.dataLength = masterout.length() - masteroutpos;
-    int sent = enet_socket_send(mastersock, NULL, &buf, 1);
-    if(sent >= 0)
-    {
-        masteroutpos += sent;
-        if(masteroutpos >= masterout.length())
-        {
-            masterout.setsizenodelete(0);
-            masteroutpos = 0;
-        }
-    }
-    else disconnectmaster();
-}
+    s_sprintfd(path)("%sregister.do?action=add", masterpath);
+    if(mssock!=ENET_SOCKET_NULL) enet_socket_destroy(mssock);
+    mssock = httpgetsend(masterserver, masterbase, path, sv->servername(), sv->servername(), &msaddress);
+    masterrep[0] = 0;
+    masterb.data = masterrep;
+    masterb.dataLength = MAXTRANS-1;
+} 
 
-void flushmasterinput()
+void checkmasterreply()
 {
-    if(masterin.length() >= masterin.capacity())
-        masterin.reserve(4096);
-
-    ENetBuffer buf;
-    buf.data = &masterin[masterin.length()];
-    buf.dataLength = masterin.capacity() - masterin.length();
-    int recv = enet_socket_receive(mastersock, NULL, &buf, 1);
-    if(recv > 0)
+    if(mssock!=ENET_SOCKET_NULL && !httpgetreceive(mssock, masterb))
     {
-        masterin.advance(recv);
-        processmasterinput();
+        mssock = ENET_SOCKET_NULL;
+        printf("masterserver reply: %s\n", stripheader(masterrep));
     }
-    else disconnectmaster();
-}
+} 
 
-static ENetAddress pongaddr;
+#ifndef STANDALONE
 
-void sendserverinforeply(ucharbuf &p)
-{
-    ENetBuffer buf;
-    buf.data = p.buf;
-    buf.dataLength = p.length();
-    enet_socket_send(pongsock, &pongaddr, &buf, 1);
-}
+#define RETRIEVELIMIT 20000
 
-void checkserversockets()        // reply all server info requests
+uchar *retrieveservers(uchar *buf, int buflen)
 {
-    static ENetSocketSet sockset;
-    ENET_SOCKETSET_EMPTY(sockset);
-    ENetSocket maxsock = pongsock;
-    ENET_SOCKETSET_ADD(sockset, pongsock);
-    if(mastersock != ENET_SOCKET_NULL)
-    {
-        maxsock = max(maxsock, mastersock);
-        ENET_SOCKETSET_ADD(sockset, mastersock);
-    }
-    if(lansock != ENET_SOCKET_NULL)
-    {
-        maxsock = max(maxsock, lansock);
-        ENET_SOCKETSET_ADD(sockset, lansock);
-    }
-    if(enet_socketset_select(maxsock, &sockset, NULL, 0) <= 0) return;
+    buf[0] = '\0';
 
-    ENetBuffer buf;
-    uchar pong[MAXTRANS];
-    loopi(2)
-    {
-        ENetSocket sock = i ? lansock : pongsock;
-        if(sock == ENET_SOCKET_NULL || !ENET_SOCKETSET_CHECK(sockset, sock)) continue;
+    s_sprintfd(path)("%sretrieve.do?item=list", masterpath);
+    ENetAddress address = masterserver;
+    ENetSocket sock = httpgetsend(address, masterbase, path, sv->servername(), sv->servername());
+    if(sock==ENET_SOCKET_NULL) return buf;
+    /* only cache this if connection succeeds */
+    masterserver = address;
 
-        buf.data = pong;
-        buf.dataLength = sizeof(pong);
-        int len = enet_socket_receive(sock, &pongaddr, &buf, 1);
-        if(len < 0) return;
-        ucharbuf req(pong, len), p(pong, sizeof(pong));
-        p.len += len;
-        server::serverinforeply(req, p);
+    s_sprintfd(text)("retrieving servers from %s... (esc to abort)", masterbase);
+    show_out_of_renderloop_progress(0, text);
+
+    ENetBuffer eb;
+    eb.data = buf;
+    eb.dataLength = buflen-1;
+   
+    int starttime = SDL_GetTicks(), timeout = 0;
+    while(httpgetreceive(sock, eb, 250))
+    {
+        timeout = SDL_GetTicks() - starttime;
+        show_out_of_renderloop_progress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text);
+        if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1;
+        if(timeout > RETRIEVELIMIT)
+        {
+            buf[0] = '\0';
+            enet_socket_destroy(sock);
+            return buf;
+        }
     }
 
-    if(mastersock != ENET_SOCKET_NULL && ENET_SOCKETSET_CHECK(sockset, mastersock)) flushmasterinput();
+    return stripheader(buf);
 }
+#endif
 
-#define DEFAULTCLIENTS 8
+#define DEFAULTCLIENTS 6
 
-VARF(maxclients, 0, DEFAULTCLIENTS, MAXCLIENTS, { if(!maxclients) maxclients = DEFAULTCLIENTS; });
-VAR(serveruprate, 0, 0, INT_MAX);
-SVAR(serverip, "");
-VARF(serverport, 0, server::serverport(), 0xFFFF, { if(!serverport) serverport = server::serverport(); });
+int uprate = 0, maxclients = DEFAULTCLIENTS;
+const char *ip = "", *master = NULL;
+const char *game = "fps";
 
 #ifdef STANDALONE
-int curtime = 0, lastmillis = 0, totalmillis = 0;
+int lastmillis = 0, totalmillis = 0;
 #endif
 
-void updatemasterserver()
-{
-    if(!mastername[0] || !allowupdatemaster) return;
-
-    requestmasterf("regserv %d\n", serverport);
-}
-
-void serverslice(bool dedicated, uint timeout)   // main server update, called from main loop in sp, or from below in dedicated server
+void serverslice(uint timeout)   // main server update, called from main loop in sp, or from below in dedicated server
 {
     localclients = nonlocalclients = 0;
     loopv(clients) switch(clients[i]->type)
@@ -590,25 +541,20 @@ void serverslice(bool dedicated, uint timeout)   // main server update, called f
 
     if(!serverhost) 
     {
-        server::serverupdate();
-        server::sendpackets();
+        sv->serverupdate(lastmillis, totalmillis);
         return;
     }
        
     // below is network only
 
-    if(dedicated) 
-    {
-        int millis = (int)enet_time_get();
-        curtime = millis - totalmillis;
-        lastmillis = totalmillis = millis;
-    }
-    server::serverupdate();
+    lastmillis = totalmillis = (int)enet_time_get();
+    sv->serverupdate(lastmillis, totalmillis);
 
-    flushmasteroutput();
-    checkserversockets();
+    sendpongs();
+    
+    if(*masterpath) checkmasterreply();
 
-    if(totalmillis-lastupdatemaster>60*60*1000)       // send alive signal to masterserver every hour of uptime
+    if(totalmillis-lastupdatemaster>60*60*1000 && *masterpath)       // send alive signal to masterserver every hour of uptime
     {
         updatemasterserver();
         lastupdatemaster = totalmillis;
@@ -639,10 +585,14 @@ void serverslice(bool dedicated, uint timeout)   // main server update, called f
                 c.peer = event.peer;
                 c.peer->data = &c;
                 char hn[1024];
-                copystring(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown");
+                s_strcpy(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown");
                 printf("client connected (%s)\n", c.hostname);
-                int reason = server::clientconnect(c.num, c.peer->address.host);
-                if(!reason) nonlocalclients++;
+                int reason = DISC_MAXCLIENTS;
+                if(nonlocalclients<maxclients && !(reason = sv->clientconnect(c.num, c.peer->address.host))) 
+                {
+                    nonlocalclients++;
+                    send_welcome(c.num);
+                }
                 else disconnect_client(c.num, reason);
                 break;
             }
@@ -659,11 +609,11 @@ void serverslice(bool dedicated, uint timeout)   // main server update, called f
                 client *c = (client *)event.peer->data;
                 if(!c) break;
                 printf("disconnected client (%s)\n", c->hostname);
-                server::clientdisconnect(c->num);
+                sv->clientdisconnect(c->num);
                 nonlocalclients--;
                 c->type = ST_EMPTY;
                 event.peer->data = NULL;
-                server::deleteclientinfo(c->info);
+                sv->deleteinfo(c->info);
                 c->info = NULL;
                 break;
             }
@@ -671,167 +621,99 @@ void serverslice(bool dedicated, uint timeout)   // main server update, called f
                 break;
         }
     }
-    if(server::sendpackets()) enet_host_flush(serverhost);
+    if(sv->sendpackets()) enet_host_flush(serverhost);
 }
 
-#ifndef STANDALONE
-void localdisconnect(bool cleanup)
+void localdisconnect()
 {
-    bool disconnected = false;
     loopv(clients) if(clients[i]->type==ST_LOCAL) 
     {
-        server::localdisconnect(i);
+        sv->localdisconnect(i);
         localclients--;
         clients[i]->type = ST_EMPTY;
-        server::deleteclientinfo(clients[i]->info);
+        sv->deleteinfo(clients[i]->info);
         clients[i]->info = NULL;
-        disconnected = true;
     }
-    if(!disconnected) return;
-    game::gamedisconnect(cleanup);
-    mainmenu = 1;
 }
 
 void localconnect()
 {
     client &c = addclient();
     c.type = ST_LOCAL;
-    copystring(c.hostname, "local");
+    s_strcpy(c.hostname, "local");
     localclients++;
-    game::gameconnect(false);
-    server::localconnect(c.num);
+    sv->localconnect(c.num);
+    send_welcome(c.num); 
 }
-#endif
 
-void rundedicatedserver()
+void initserver(bool dedicated)
 {
-    #ifdef WIN32
-    SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
-    #endif
-    printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n");
-    for(;;) serverslice(true, 5);
-}
+    initgame(game);
 
-bool servererror(bool dedicated, const char *desc)
-{
-#ifndef STANDALONE
-    if(!dedicated)
-    {
-        conoutf(CON_ERROR, desc);
-        cleanupserver();
-    }
-    else
-#endif
-        fatal(desc);
-    return false;
-}
-  
-bool setuplistenserver(bool dedicated)
-{
-    ENetAddress address = { ENET_HOST_ANY, serverport <= 0 ? server::serverport() : serverport };
-    if(*serverip)
-    {
-        if(enet_address_set_host(&address, serverip)<0) conoutf(CON_WARN, "WARNING: server ip not resolved");
-        else serveraddress.host = address.host;
-    }
-    serverhost = enet_host_create(&address, min(maxclients + server::reserveclients(), MAXCLIENTS), 0, serveruprate);
-    if(!serverhost) return servererror(dedicated, "could not create server host");
-    loopi(maxclients) serverhost->peers[i].data = NULL;
-    address.port = server::serverinfoport(serverport > 0 ? serverport : -1);
-    pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
-    if(pongsock != ENET_SOCKET_NULL && enet_socket_bind(pongsock, &address) < 0)
-    {
-        enet_socket_destroy(pongsock);
-        pongsock = ENET_SOCKET_NULL;
-    }
-    if(pongsock == ENET_SOCKET_NULL) return servererror(dedicated, "could not create server info socket");
-    else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1);
-    address.port = server::laninfoport();
-    lansock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
-    if(lansock != ENET_SOCKET_NULL && (enet_socket_set_option(lansock, ENET_SOCKOPT_REUSEADDR, 1) < 0 || enet_socket_bind(lansock, &address) < 0))
+    if(!master) master = sv->getdefaultmaster();
+    const char *mid = strstr(master, "/");
+    if(!mid) mid = master;
+    s_strcpy(masterpath, mid);
+    s_strncpy(masterbase, master, mid-master+1);
+
+    if(dedicated)
     {
-        enet_socket_destroy(lansock);
-        lansock = ENET_SOCKET_NULL;
+        ENetAddress address = { ENET_HOST_ANY, sv->serverport() };
+        if(*ip)
+        {
+            if(enet_address_set_host(&address, ip)<0) printf("WARNING: server ip not resolved");
+            else msaddress.host = address.host;
+        }
+        serverhost = enet_host_create(&address, maxclients+1, 0, uprate);
+        if(!serverhost) fatal("could not create server host");
+        loopi(maxclients) serverhost->peers[i].data = NULL;
+        address.port = sv->serverinfoport();
+        pongsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, &address);
+        if(pongsock == ENET_SOCKET_NULL) fatal("could not create server info socket");
+        else enet_socket_set_option(pongsock, ENET_SOCKOPT_NONBLOCK, 1);
     }
-    if(lansock == ENET_SOCKET_NULL) conoutf(CON_WARN, "WARNING: could not create LAN server info socket");
-    else enet_socket_set_option(lansock, ENET_SOCKOPT_NONBLOCK, 1);
-    return true;
-}
-
-void initserver(bool listen, bool dedicated)
-{
-    if(dedicated) execfile("server-init.cfg", false);
 
-    if(listen) setuplistenserver(dedicated);
+    sv->serverinit();
 
-    server::serverinit();
-
-    if(listen)
+    if(dedicated)       // do not return, this becomes main loop
     {
-        updatemasterserver();
-        if(dedicated) rundedicatedserver(); // never returns
-#ifndef STANDALONE
-        else conoutf("listen server started");
-#endif
+        #ifdef WIN32
+        SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
+        #endif
+        printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n");
+        atexit(enet_deinitialize);
+        atexit(cleanupserver);
+        enet_time_set(0);
+        if(*masterpath) updatemasterserver();
+        for(;;) serverslice(5);
     }
 }
 
-#ifndef STANDALONE
-void startlistenserver(int *usemaster)
-{
-    if(serverhost) { conoutf(CON_ERROR, "listen server is already running"); return; }
-
-    allowupdatemaster = *usemaster>0 ? 1 : 0;
- 
-    if(!setuplistenserver(false)) return;
-    
-    updatemasterserver();
-   
-    conoutf("listen server started for %d clients%s", maxclients, allowupdatemaster ? " and listed with master server" : ""); 
-}
-COMMAND(startlistenserver, "i");
-
-void stoplistenserver()
-{
-    if(!serverhost) { conoutf(CON_ERROR, "listen server is not running"); return; }
-
-    kicknonlocalclients();
-    enet_host_flush(serverhost);
-    cleanupserver();
-
-    conoutf("listen server stopped");
-}
-COMMAND(stoplistenserver, "");
-#endif
-
 bool serveroption(char *opt)
 {
     switch(opt[1])
     {
-        case 'u': setvar("serveruprate", atoi(opt+2)); return true;
-        case 'c': setvar("maxclients", atoi(opt+2)); return true;
-        case 'i': setsvar("serverip", opt+2); return true;
-        case 'j': setvar("serverport", atoi(opt+2)); return true; 
-        case 'm': setsvar("mastername", opt+2); setvar("updatemaster", mastername[0] ? 1 : 0); return true;
-#ifdef STANDALONE
-        case 'q': printf("Using home directory: %s\n", opt+2); sethomedir(opt+2); return true;
-        case 'k': printf("Adding package directory: %s\n", opt+2); addpackagedir(opt+2); return true;
-#endif
+        case 'u': uprate = atoi(opt+2); return true;
+        case 'c': 
+        {
+            int clients = atoi(opt+2); 
+            if(clients > 0) maxclients = min(clients, MAXCLIENTS);
+            else maxclients = DEFAULTCLIENTS;
+            return true;
+        }
+        case 'i': ip = opt+2; return true;
+        case 'm': master = opt+2; return true;
+        case 'g': game = opt+2; return true;
         default: return false;
     }
 }
 
-vector<const char *> gameargs;
-
 #ifdef STANDALONE
 int main(int argc, char* argv[])
 {   
-    if(enet_initialize()<0) fatal("Unable to initialise network module");
-    atexit(enet_deinitialize);
-    enet_time_set(0);
     for(int i = 1; i<argc; i++) if(argv[i][0]!='-' || !serveroption(argv[i])) gameargs.add(argv[i]);
-    game::parseoptions(gameargs);
-    initserver(true, true);
+    if(enet_initialize()<0) fatal("Unable to initialise network module");
+    initserver(true);
     return 0;
 }
 #endif
diff --git a/engine/serverbrowser.cpp b/engine/serverbrowser.cpp
index c2f19c3..dcdca17 100644
--- a/engine/serverbrowser.cpp
+++ b/engine/serverbrowser.cpp
@@ -1,5 +1,6 @@
 // serverbrowser.cpp: eihrul's concurrent resolver, and server browser window management
 
+#include "pch.h"
 #include "engine.h"
 #include "SDL_thread.h"
 
@@ -41,7 +42,7 @@ int resolverloop(void * data)
         rt->starttime = totalmillis;
         SDL_UnlockMutex(resolvermutex);
 
-        ENetAddress address = { ENET_HOST_ANY, ENET_PORT_ANY };
+        ENetAddress address = { ENET_HOST_ANY, sv->serverinfoport() };
         enet_address_set_host(&address, rt->query);
 
         SDL_LockMutex(resolvermutex);
@@ -145,8 +146,8 @@ bool resolverwait(const char *name, ENetAddress *address)
 {
     if(resolverthreads.empty()) resolverinit();
 
-    defformatstring(text)("resolving %s... (esc to abort)", name);
-    renderprogress(0, text);
+    s_sprintfd(text)("resolving %s... (esc to abort)", name);
+    show_out_of_renderloop_progress(0, text);
 
     SDL_LockMutex(resolvermutex);
     resolverqueries.add(name);
@@ -166,7 +167,7 @@ bool resolverwait(const char *name, ENetAddress *address)
         if(resolved) break;
     
         timeout = SDL_GetTicks() - starttime;
-        renderprogress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text);
+        show_out_of_renderloop_progress(min(float(timeout)/RESOLVERLIMIT, 1.0f), text);
         if(interceptkey(SDLK_ESCAPE)) timeout = RESOLVERLIMIT + 1;
         if(timeout > RESOLVERLIMIT) break;    
     }
@@ -224,10 +225,10 @@ int connectthread(void *data)
 
 #define CONNLIMIT 20000
 
-int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress &address)
+int connectwithtimeout(ENetSocket sock, const char *hostname, ENetAddress &address)
 {
-    defformatstring(text)("connecting to %s... (esc to abort)", hostname);
-    renderprogress(0, text);
+    s_sprintfd(text)("connecting to %s... (esc to abort)", hostname);
+    show_out_of_renderloop_progress(0, text);
 
     if(!connmutex) connmutex = SDL_CreateMutex();
     if(!conncond) conncond = SDL_CreateCond();
@@ -244,7 +245,7 @@ int connectwithtimeout(ENetSocket sock, const char *hostname, const ENetAddress
             break;
         }      
         timeout = SDL_GetTicks() - starttime;
-        renderprogress(min(float(timeout)/CONNLIMIT, 1.0f), text);
+        show_out_of_renderloop_progress(min(float(timeout)/CONNLIMIT, 1.0f), text);
         if(interceptkey(SDLK_ESCAPE)) timeout = CONNLIMIT + 1;
         if(timeout > CONNLIMIT) break;
     }
@@ -262,78 +263,34 @@ enum { UNRESOLVED = 0, RESOLVING, RESOLVED };
 
 struct serverinfo
 {
-    string name, map, sdesc;
-    int port, numplayers, ping, resolved, lastping;
+    string name;
+    string map;
+    string sdesc;
+    int numplayers, ping, resolved;
     vector<int> attr;
     ENetAddress address;
-    bool keep;
-    const char *password;
 
     serverinfo()
-     : port(-1), numplayers(0), ping(INT_MAX), resolved(UNRESOLVED), lastping(-1), keep(false), password(NULL)
+     : numplayers(0), ping(999), resolved(UNRESOLVED)
     {
         name[0] = map[0] = sdesc[0] = '\0';
     }
-
-    ~serverinfo()
-    {
-        DELETEA(password);
-    }
-
-    void reset()
-    {
-        lastping = -1;
-    }
-
-    void checkdecay(int decay)
-    {
-        if(lastping >= 0 && totalmillis - lastping >= decay)
-        {
-            ping = INT_MAX;
-            lastping = -1;
-        }
-        if(lastping < 0) lastping = totalmillis;
-    }
-
-    void addping(int rtt, int millis)
-    {
-        if(millis >= lastping) lastping = -1;
-        if(ping == INT_MAX) ping = rtt;
-        else ping = (ping*4 + rtt)/5;
-    }
-
-    static int compare(serverinfo **ap, serverinfo **bp)
-    {
-        serverinfo *a = *ap, *b = *bp;
-        bool ac = server::servercompatible(a->name, a->sdesc, a->map, a->ping, a->attr, a->numplayers),
-             bc = server::servercompatible(b->name, b->sdesc, b->map, b->ping, b->attr, b->numplayers);
-        if(ac > bc) return -1;
-        if(bc > ac) return 1;
-        if(a->numplayers < b->numplayers) return 1;
-        if(a->numplayers > b->numplayers) return -1;
-        if(a->ping > b->ping) return 1;
-        if(a->ping < b->ping) return -1;
-        int cmp = strcmp(a->name, b->name);
-        if(cmp != 0) return cmp;
-        if(a->port < b->port) return -1;
-        if(a->port > b->port) return 1;
-        return 0;
-    }
 };
 
 vector<serverinfo *> servers;
 ENetSocket pingsock = ENET_SOCKET_NULL;
 int lastinfo = 0;
 
-static serverinfo *newserver(const char *name, int port, uint ip = ENET_HOST_ANY)
+char *getservername(int n) { return servers[n]->name; }
+
+static serverinfo *newserver(const char *name, uint ip = ENET_HOST_ANY, uint port = sv->serverinfoport())
 {
     serverinfo *si = new serverinfo;
     si->address.host = ip;
-    si->address.port = server::serverinfoport(port);
+    si->address.port = port;
     if(ip!=ENET_HOST_ANY) si->resolved = RESOLVED;
 
-    si->port = port;
-    if(name) copystring(si->name, name);
+    if(name) s_strcpy(si->name, name);
     else if(ip==ENET_HOST_ANY || enet_address_get_host_ip(&si->address, si->name, sizeof(si->name)) < 0)
     {
         delete si;
@@ -346,37 +303,19 @@ static serverinfo *newserver(const char *name, int port, uint ip = ENET_HOST_ANY
     return si;
 }
 
-void addserver(const char *name, int port, const char *password, bool keep)
+void addserver(char *servername)
 {
-    if(port <= 0) port = server::serverport();
-    loopv(servers)
-    {
-        serverinfo *s = servers[i];
-        if(strcmp(s->name, name) || s->port != port) continue;
-        if(password && (!s->password || strcmp(s->password, password)))
-        {
-            DELETEA(s->password);
-            s->password = newstring(password);
-        }
-        if(keep && !s->keep) s->keep = true;
-        return;
-    }
-    serverinfo *s = newserver(name, port);
-    if(!s) return;
-    if(password) s->password = newstring(password);
-    s->keep = keep;
+    loopv(servers) if(!strcmp(servers[i]->name, servername)) return;
+    newserver(servername);
 }
 
-VARP(searchlan, 0, 0, 1);
-VARP(servpingrate, 1000, 5000, 60000);
-VARP(servpingdecay, 1000, 15000, 60000);
-VARP(maxservpings, 0, 25, 1000);
+VAR(searchlan, 0, 0, 1);
 
 void pingservers()
 {
     if(pingsock == ENET_SOCKET_NULL) 
     {
-        pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM);
+        pingsock = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM, NULL);
         if(pingsock == ENET_SOCKET_NULL)
         {
             lastinfo = totalmillis;
@@ -389,25 +328,19 @@ void pingservers()
     uchar ping[MAXTRANS];
     ucharbuf p(ping, sizeof(ping));
     putint(p, totalmillis);
-
-    static int lastping = 0;
-    if(lastping >= servers.length()) lastping = 0;
-    loopi(maxservpings ? min(servers.length(), maxservpings) : servers.length())
+    loopv(servers)
     {
-        serverinfo &si = *servers[lastping];
-        if(++lastping >= servers.length()) lastping = 0;
+        serverinfo &si = *servers[i];
         if(si.address.host == ENET_HOST_ANY) continue;
         buf.data = ping;
         buf.dataLength = p.length();
         enet_socket_send(pingsock, &si.address, &buf, 1);
-        
-        si.checkdecay(servpingdecay);
     }
     if(searchlan)
     {
         ENetAddress address;
         address.host = ENET_HOST_BROADCAST;
-        address.port = server::laninfoport();
+        address.port = sv->serverinfoport();
         buf.data = ping;
         buf.dataLength = p.length();
         enet_socket_send(pingsock, &address, &buf, 1);
@@ -431,17 +364,17 @@ void checkresolver()
     if(!resolving) return;
 
     const char *name = NULL;
-    for(;;)
+    ENetAddress addr = { ENET_HOST_ANY, sv->serverinfoport() };
+    while(resolvercheck(&name, &addr))
     {
-        ENetAddress addr = { ENET_HOST_ANY, ENET_PORT_ANY };
-        if(!resolvercheck(&name, &addr)) break;
         loopv(servers)
         {
             serverinfo &si = *servers[i];
             if(name == si.name)
             {
                 si.resolved = RESOLVED; 
-                si.address.host = addr.host;
+                si.address = addr;
+                addr.host = ENET_HOST_ANY;
                 break;
             }
         }
@@ -463,12 +396,11 @@ void checkpings()
         int len = enet_socket_receive(pingsock, &addr, &buf, 1);
         if(len <= 0) return;  
         serverinfo *si = NULL;
-        loopv(servers) if(addr.host == servers[i]->address.host && addr.port == servers[i]->address.port) { si = servers[i]; break; }
-        if(!si && searchlan) si = newserver(NULL, server::serverport(addr.port), addr.host); 
+        loopv(servers) if(addr.host == servers[i]->address.host) { si = servers[i]; break; }
+        if(!si && searchlan) si = newserver(NULL, addr.host); 
         if(!si) continue;
         ucharbuf p(ping, len);
-        int millis = getint(p), rtt = clamp(totalmillis - millis, 0, min(servpingdecay, totalmillis));
-        if(rtt < servpingdecay) si->addping(rtt, millis);
+        si->ping = totalmillis - getint(p);
         si->numplayers = getint(p);
         int numattr = getint(p);
         si->attr.setsize(0);
@@ -480,23 +412,36 @@ void checkpings()
     }
 }
 
+int sicompare(serverinfo **ap, serverinfo **bp)
+{
+    serverinfo *a = *ap, *b = *bp;
+    bool ac = sv->servercompatible(a->name, a->sdesc, a->map, a->ping, a->attr, a->numplayers),
+         bc = sv->servercompatible(b->name, b->sdesc, b->map, b->ping, b->attr, b->numplayers);
+    if(ac>bc) return -1;
+    if(bc>ac) return 1;   
+    if(a->numplayers<b->numplayers) return 1;
+    if(a->numplayers>b->numplayers) return -1;
+    if(a->ping>b->ping) return 1;
+    if(a->ping<b->ping) return -1;
+    return strcmp(a->name, b->name);
+}
+
 void refreshservers()
 {
     static int lastrefresh = 0;
     if(lastrefresh==totalmillis) return;
-    if(totalmillis - lastrefresh > 1000) loopv(servers) servers[i]->reset();
     lastrefresh = totalmillis;
 
     checkresolver();
     checkpings();
-    if(totalmillis - lastinfo >= servpingrate/(maxservpings ? max(1, (servers.length() + maxservpings - 1) / maxservpings) : 1)) pingservers();
-    servers.sort(serverinfo::compare);
+    if(totalmillis - lastinfo >= 5000) pingservers();
+    servers.sort(sicompare);
 }
 
-char *showservers(g3d_gui *cgui)
+const char *showservers(g3d_gui *cgui)
 {
     refreshservers();
-    serverinfo *sc = NULL;
+    const char *name = NULL;
     for(int start = 0; start < servers.length();)
     {
         if(start > 0) cgui->tab();
@@ -504,138 +449,55 @@ char *showservers(g3d_gui *cgui)
         cgui->pushlist();
         loopi(10)
         {
-            if(!game::serverinfostartcolumn(cgui, i)) break;
+            if(!cl->serverinfostartcolumn(cgui, i)) break;
             for(int j = start; j < end; j++)
             {
                 if(!i && cgui->shouldtab()) { end = j; break; }
                 serverinfo &si = *servers[j];
                 const char *sdesc = si.sdesc;
                 if(si.address.host == ENET_HOST_ANY) sdesc = "[unknown host]";
-                else if(si.ping == INT_MAX) sdesc = "[waiting for response]";
-                if(game::serverinfoentry(cgui, i, si.name, si.port, sdesc, si.map, sdesc == si.sdesc ? si.ping : -1, si.attr, si.numplayers))
-                    sc = &si;
+                else if(si.ping == 999) sdesc = "[waiting for response]";
+                if(cl->serverinfoentry(cgui, i, si.name, sdesc, si.map, sdesc == si.sdesc ? si.ping : -1, si.attr, si.numplayers))
+                    name = si.name;
             }
-            game::serverinfoendcolumn(cgui, i);
+            cl->serverinfoendcolumn(cgui, i);
         }
         cgui->poplist();
         start = end;
     }
-    if(!sc) return NULL;
-    string command;
-    if(sc->password) formatstring(command)("connect %s %d \"%s\"", sc->name, sc->port, sc->password);
-    else formatstring(command)("connect %s %d", sc->name, sc->port);
-    return newstring(command);
+    return name;
 }
 
-void clearservers(bool full = false)
+void clearservers()
 {
     resolverclear();
-    if(full) servers.deletecontentsp();
-    else loopvrev(servers) if(!servers[i]->keep) delete servers.remove(i);
-}
-
-#define RETRIEVELIMIT 20000
-
-void retrieveservers(vector<char> &data)
-{
-    ENetSocket sock = connectmaster();
-    if(sock == ENET_SOCKET_NULL) return;
-
-    extern char *mastername;
-    defformatstring(text)("retrieving servers from %s... (esc to abort)", mastername);
-    renderprogress(0, text);
-
-    int starttime = SDL_GetTicks(), timeout = 0;
-    const char *req = "list\n";
-    int reqlen = strlen(req);
-    ENetBuffer buf;
-    while(reqlen > 0)
-    {
-        enet_uint32 events = ENET_SOCKET_WAIT_SEND;
-        if(enet_socket_wait(sock, &events, 250) >= 0 && events) 
-        {
-            buf.data = (void *)req;
-            buf.dataLength = reqlen;
-            int sent = enet_socket_send(sock, NULL, &buf, 1);
-            if(sent < 0) break;
-            req += sent;
-            reqlen -= sent;
-            if(reqlen <= 0) break;
-        }
-        timeout = SDL_GetTicks() - starttime;
-        renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text);
-        if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1;
-        if(timeout > RETRIEVELIMIT) break;
-    }
-
-    if(reqlen <= 0) for(;;)
-    {
-        enet_uint32 events = ENET_SOCKET_WAIT_RECEIVE;
-        if(enet_socket_wait(sock, &events, 250) >= 0 && events)
-        {
-            if(data.length() >= data.capacity()) data.reserve(4096);
-            buf.data = &data[data.length()];
-            buf.dataLength = data.capacity() - data.length();
-            int recv = enet_socket_receive(sock, NULL, &buf, 1);
-            if(recv <= 0) break;
-            data.advance(recv);
-        }
-        timeout = SDL_GetTicks() - starttime;
-        renderprogress(min(float(timeout)/RETRIEVELIMIT, 1.0f), text);
-        if(interceptkey(SDLK_ESCAPE)) timeout = RETRIEVELIMIT + 1;
-        if(timeout > RETRIEVELIMIT) break;
-    }
-
-    if(data.length()) data.add('\0');
-    enet_socket_destroy(sock);
+    servers.deletecontentsp();
 }
 
 void updatefrommaster()
 {
-    vector<char> data;
-    retrieveservers(data);
-    if(data.empty()) conoutf("master server not replying");
+    uchar buf[32000];
+    uchar *reply = retrieveservers(buf, sizeof(buf));
+    if(!*reply || strstr((char *)reply, "<html>") || strstr((char *)reply, "<HTML>")) conoutf("master server not replying");
     else
     {
         clearservers();
-        execute(data.getbuf());
+        execute((char *)reply);
     }
     refreshservers();
 }
 
-ICOMMAND(addserver, "sis", (const char *name, int *port, const char *password), addserver(name, *port, password[0] ? password : NULL));
-ICOMMAND(keepserver, "sis", (const char *name, int *port, const char *password), addserver(name, *port, password[0] ? password : NULL, true));
-ICOMMAND(clearservers, "i", (int *full), clearservers(*full!=0));
+COMMAND(addserver, "s");
+COMMAND(clearservers, "");
 COMMAND(updatefrommaster, "");
 
 void writeservercfg()
 {
-    if(!game::savedservers()) return;
-    stream *f = openfile(path(game::savedservers(), true), "w");
+    if(!cl->savedservers()) return;
+    FILE *f = openfile(path(cl->savedservers(), true), "w");
     if(!f) return;
-    int kept = 0;
-    loopv(servers)
-    {
-        serverinfo *s = servers[i];
-        if(s->keep)
-        {
-            if(!kept) f->printf("// servers that should never be cleared from the server list\n\n");
-            if(s->password) f->printf("keepserver %s %d \"%s\"\n", s->name, s->port, s->password);
-            else f->printf("keepserver %s %d\n", s->name, s->port);
-            kept++;
-        }
-    }
-    if(kept) f->printf("\n");
-    f->printf("// servers connected to are added here automatically\n\n");
-    loopv(servers) 
-    {
-        serverinfo *s = servers[i];
-        if(!s->keep) 
-        {
-            if(s->password) f->printf("addserver %s %d \"%s\"\n", s->name, s->port, s->password);
-            else f->printf("addserver %s %d\n", s->name, s->port);
-        }
-    }
-    delete f;
+    fprintf(f, "// servers connected to are added here automatically\n\n");
+    loopvrev(servers) fprintf(f, "addserver %s\n", servers[i]->name);
+    fclose(f);
 }
 
diff --git a/engine/shader.cpp b/engine/shader.cpp
index ab983e5..a697a29 100644
--- a/engine/shader.cpp
+++ b/engine/shader.cpp
@@ -1,5 +1,6 @@
 // shader.cpp: OpenGL assembly/GLSL shader management
 
+#include "pch.h"
 #include "engine.h"
 
 Shader *Shader::lastshader = NULL;
@@ -10,7 +11,7 @@ static hashtable<const char *, Shader> shaders;
 static Shader *curshader = NULL;
 static vector<ShaderParam> curparams;
 static ShaderParamState vertexparamstate[RESERVEDSHADERPARAMS + MAXSHADERPARAMS], pixelparamstate[RESERVEDSHADERPARAMS + MAXSHADERPARAMS];
-static bool dirtyenvparams = false, standardshader = false, initshaders = false, forceshaders = true;
+static bool dirtyenvparams = false, standardshader = false;
 
 VAR(reservevpparams, 1, 16, 0);
 VAR(maxvpenvparams, 1, 0, 0);
@@ -33,11 +34,9 @@ void loadshaders()
         maxfplocalparams = val;
     }
 
-    initshaders = true;
     standardshader = true;
-    execfile("data/stdshader.cfg");
+    exec("data/stdshader.cfg");
     standardshader = false;
-    initshaders = false;
     defaultshader = lookupshaderbyname("default");
     stdworldshader = lookupshaderbyname("stdworld");
     if(!defaultshader || !stdworldshader) fatal("cannot find shader definitions");
@@ -63,7 +62,8 @@ void loadshaders()
 Shader *lookupshaderbyname(const char *name) 
 { 
     Shader *s = shaders.access(name);
-    return s && s->detailshader ? s : NULL;
+    if(!s || s->type==SHADER_INVALID) return NULL;
+    return s->altshader ? s->altshader : s;
 }
 
 static bool compileasmshader(GLenum type, GLuint &idx, const char *def, const char *tname, const char *name, bool msg = true, bool nativeonly = false)
@@ -88,7 +88,6 @@ static bool compileasmshader(GLenum type, GLuint &idx, const char *def, const ch
         }
     }
     else if(msg && !native) conoutf(CON_ERROR, "%s:%s EXCEEDED NATIVE LIMITS", tname, name);
-    glBindProgram_(type, 0);
     if(err!=-1 || (!native && nativeonly))
     {
         glDeletePrograms_(1, &idx);
@@ -143,7 +142,7 @@ static void linkglslprogram(Shader &s, bool msg = true)
         glUseProgramObject_(s.program);
         loopi(8)
         {
-            defformatstring(arg)("tex%d", i);
+            s_sprintfd(arg)("tex%d", i);
             GLint loc = glGetUniformLocation_(s.program, arg);
             if(loc != -1) glUniform1i_(loc, i);
         }
@@ -151,8 +150,8 @@ static void linkglslprogram(Shader &s, bool msg = true)
         {
             ShaderParam &param = s.defaultparams[i];
             string pname;
-            if(param.type==SHPARAM_UNIFORM) copystring(pname, param.name);
-            else formatstring(pname)("%s%d", param.type==SHPARAM_VERTEX ? "v" : "p", param.index);
+            if(param.type==SHPARAM_UNIFORM) s_strcpy(pname, param.name);
+            else s_sprintf(pname)("%s%d", param.type==SHPARAM_VERTEX ? "v" : "p", param.index);
             param.loc = glGetUniformLocation_(s.program, pname);
         }
         glUseProgramObject_(0);
@@ -212,7 +211,7 @@ static void allocglsluniformparam(Shader &s, int type, int index, bool local = f
     int loc = val.name ? glGetUniformLocation_(s.program, val.name) : -1;
     if(loc == -1)
     {
-        defformatstring(altname)("%s%d", type==SHPARAM_VERTEX ? "v" : "p", index);
+        s_sprintfd(altname)("%s%d", type==SHPARAM_VERTEX ? "v" : "p", index);
         loc = glGetUniformLocation_(s.program, val.name);
     }
     else
@@ -287,7 +286,7 @@ void Shader::allocenvparams(Slot *slot)
                 case TEX_DEPTH: if(t.combined<0) UNIFORMTEX("depthmap", tmu++); break;
                 case TEX_UNKNOWN: 
                 {
-                    defformatstring(sname)("stex%d", stex++); 
+                    s_sprintfd(sname)("stex%d", stex++); 
                     UNIFORMTEX(sname, tmu++);
                     break;
                 }
@@ -449,23 +448,22 @@ void Shader::setslotparams(Slot &slot)
     loopv(slot.params)
     {
         ShaderParam &p = slot.params[i];
-        if(!defaultparams.inrange(p.loc)) continue;
-        LocalShaderParamState &l = defaultparams[p.loc];
         if(type & SHADER_GLSLANG)
         {
-            unimask |= p.loc;
+            LocalShaderParamState &l = defaultparams[p.index];
+            unimask |= p.index;
             if(!memcmp(l.curval, p.val, sizeof(l.curval))) continue;
             memcpy(l.curval, p.val, sizeof(l.curval));
             glUniform4fv_(l.loc, 1, l.curval); 
         }
         else if(p.type!=SHPARAM_UNIFORM)
         {
-            ShaderParamState &val = (l.type==SHPARAM_VERTEX ? vertexparamstate[RESERVEDSHADERPARAMS+l.index] : pixelparamstate[RESERVEDSHADERPARAMS+l.index]);
-            if(l.type==SHPARAM_VERTEX) vertmask |= 1<<l.index;
-            else pixmask |= 1<<l.index;
+            ShaderParamState &val = (p.type==SHPARAM_VERTEX ? vertexparamstate[RESERVEDSHADERPARAMS+p.index] : pixelparamstate[RESERVEDSHADERPARAMS+p.index]);
+            if(p.type==SHPARAM_VERTEX) vertmask |= 1<<p.index;
+            else pixmask |= 1<<p.index;
             if(memcmp(val.val, p.val, sizeof(val.val))) memcpy(val.val, p.val, sizeof(val.val));
             else if(val.dirty==ShaderParamState::CLEAN) continue;
-            glProgramEnvParameter4fv_(l.type==SHPARAM_VERTEX ? GL_VERTEX_PROGRAM_ARB : GL_FRAGMENT_PROGRAM_ARB, RESERVEDSHADERPARAMS+l.index, val.val);
+            glProgramEnvParameter4fv_(p.type==SHPARAM_VERTEX ? GL_VERTEX_PROGRAM_ARB : GL_FRAGMENT_PROGRAM_ARB, RESERVEDSHADERPARAMS+p.index, val.val);
             val.local = true;
             val.dirty = ShaderParamState::CLEAN;
         }
@@ -499,7 +497,7 @@ void Shader::setslotparams(Slot &slot)
 
 void Shader::bindprograms()
 {
-    if(this == lastshader || type&(SHADER_DEFERRED|SHADER_INVALID)) return;
+    if(this==lastshader) return;
     if(type & SHADER_GLSLANG)
     {
         glUseProgramObject_(program);
@@ -516,6 +514,7 @@ void Shader::bindprograms()
 
 VARFN(shaders, useshaders, -1, -1, 1, initwarning("shaders"));
 VARF(shaderprecision, 0, 0, 2, initwarning("shader quality"));
+VARP(shaderdetail, 0, MAXSHADERDETAIL, MAXSHADERDETAIL);
 
 VAR(dbgshader, 0, 0, 1);
 
@@ -523,19 +522,19 @@ bool Shader::compile()
 {
     if(type & SHADER_GLSLANG)
     {
-        if(!vsstr) vsobj = !reusevs || reusevs->type&SHADER_INVALID ? 0 : reusevs->vsobj;
+        if(!vsstr) vsobj = reusevs && reusevs->type != SHADER_INVALID ? reusevs->vsobj : 0;
         else compileglslshader(GL_VERTEX_SHADER_ARB,   vsobj, vsstr, "VS", name, dbgshader || !variantshader);
-        if(!psstr) psobj = !reuseps || reuseps->type&SHADER_INVALID ? 0 : reuseps->psobj;
+        if(!psstr) psobj = reuseps && reuseps->type != SHADER_INVALID ? reuseps->psobj : 0;
         else compileglslshader(GL_FRAGMENT_SHADER_ARB, psobj, psstr, "PS", name, dbgshader || !variantshader);
         linkglslprogram(*this, !variantshader);
         return program!=0;
     }
     else
     {
-        if(!vsstr) vs = !reusevs || reusevs->type&SHADER_INVALID ? 0 : reusevs->vs;
+        if(!vsstr) vs = reusevs && reusevs->type != SHADER_INVALID ? reusevs->vs : 0;
         else if(!compileasmshader(GL_VERTEX_PROGRAM_ARB, vs, vsstr, "VS", name, dbgshader || !variantshader, variantshader!=NULL))
             native = false;
-        if(!psstr) ps = !reuseps || reuseps->type&SHADER_INVALID ? 0 : reuseps->ps;
+        if(!psstr) ps = reuseps && reuseps->type != SHADER_INVALID ? reuseps->ps : 0;
         else if(!compileasmshader(GL_FRAGMENT_PROGRAM_ARB, ps, psstr, "PS", name, dbgshader || !variantshader, variantshader!=NULL))
             native = false;
         return vs && ps && (!variantshader || native);
@@ -544,7 +543,6 @@ bool Shader::compile()
 
 void Shader::cleanup(bool invalid)
 {
-    detailshader = NULL;
     used = false;
     native = true;
     if(vs) { if(reusevs) glDeletePrograms_(1, &vs); vs = 0; }
@@ -556,45 +554,30 @@ void Shader::cleanup(bool invalid)
     DELETEA(extparams);
     DELETEA(extvertparams);
     extpixparams = NULL;
-    loopv(defaultparams) memset(defaultparams[i].curval, -1, sizeof(defaultparams[i].curval));
+    loopv(defaultparams) memset(defaultparams[i].curval, 0, sizeof(defaultparams[i].curval));
     if(standard || invalid)
     {
         type = SHADER_INVALID;
         loopi(MAXVARIANTROWS) variants[i].setsizenodelete(0);
         DELETEA(vsstr);
         DELETEA(psstr);
-        DELETEA(defer);
         defaultparams.setsizenodelete(0);
         altshader = NULL;
-        loopi(MAXSHADERDETAIL) fastshader[i] = this;
         reusevs = reuseps = NULL;
     }
 }
 
 Shader *newshader(int type, const char *name, const char *vs, const char *ps, Shader *variant = NULL, int row = 0)
 {
-    if(Shader::lastshader)
-    {
-        if(renderpath!=R_FIXEDFUNCTION)
-        {
-            glBindProgram_(GL_VERTEX_PROGRAM_ARB, 0);
-            glBindProgram_(GL_FRAGMENT_PROGRAM_ARB, 0);
-            if(renderpath==R_GLSLANG) glUseProgramObject_(0);
-        }
-        Shader::lastshader = NULL;
-    }
-
     Shader *exists = shaders.access(name); 
     char *rname = exists ? exists->name : newstring(name);
     Shader &s = shaders[rname];
     s.name = rname;
     s.vsstr = newstring(vs);
     s.psstr = newstring(ps);
-    DELETEA(s.defer);
     s.type = type;
     s.variantshader = variant;
     s.standard = standardshader;
-    if(forceshaders) s.forced = true;
     s.reusevs = s.reuseps = NULL;
     if(variant)
     {
@@ -611,16 +594,16 @@ Shader *newshader(int type, const char *name, const char *vs, const char *ps, Sh
             s.reuseps = !ps[0] ? variant : (variant->variants[row].inrange(col) ? variant->variants[row][col] : NULL);
         }
     }
+    loopi(MAXSHADERDETAIL) s.fastshader[i] = &s;
     if(variant) loopv(variant->defaultparams) s.defaultparams.add(variant->defaultparams[i]);
     else loopv(curparams) s.defaultparams.add(curparams[i]);
     if(renderpath!=R_FIXEDFUNCTION && !s.compile())
     {
-        s.cleanup(true);
-        if(variant) shaders.remove(rname);
+        s.cleanup(!standardshader);        
+        if(standardshader) shaders.remove(rname);
         return NULL;
     }
     if(variant) variant->variants[row].add(&s);
-    s.fixdetailshader();
     return &s;
 }
 
@@ -680,7 +663,7 @@ static bool findunusedtexcoordcomponent(const char *str, int &texcoord, int &com
         vsbuf.put(start, fogcoord-start); \
         const char *afterfogcoord = fogcoord + strlen("result.fogcoord"); \
         if(*afterfogcoord=='.') afterfogcoord += 2; \
-        defformatstring(repfogcoord)("result.texcoord[%d].%c", fogtc, fogcomp==3 ? 'w' : 'x'+fogcomp); \
+        s_sprintfd(repfogcoord)("result.texcoord[%d].%c", fogtc, fogcomp==3 ? 'w' : 'x'+fogcomp); \
         vsbuf.put(repfogcoord, strlen(repfogcoord)); \
         vsbuf.put(afterfogcoord, end-afterfogcoord); \
     } \
@@ -703,7 +686,7 @@ static bool findunusedtexcoordcomponent(const char *str, int &texcoord, int &com
             if(str[12]!='.' || (str[13]!='a' && str[13]!='w')) memcpy(str, tmpuse, strlen(tmpuse)); \
             str += 12; \
         } \
-        defformatstring(fogtcstr)("fragment.texcoord[%d].%c", fogtc, fogcomp==3 ? 'w' : 'x'+fogcomp); \
+        s_sprintfd(fogtcstr)("fragment.texcoord[%d].%c", fogtc, fogcomp==3 ? 'w' : 'x'+fogcomp); \
         str = strstr(psbuf.getbuf(), "fragment.fogcoord.x"); \
         if(str) \
         { \
@@ -713,7 +696,7 @@ static bool findunusedtexcoordcomponent(const char *str, int &texcoord, int &com
         } \
         char *end = strstr(psbuf.getbuf(), "END"); \
         if(end) psbuf.setsizenodelete(end - psbuf.getbuf()); \
-        defformatstring(calcfog)( \
+        s_sprintfd(calcfog)( \
             "TEMP emufog;\n" \
             "SUB emufog, state.fog.params.z, %s;\n" \
             "MUL_SAT emufog, emufog, state.fog.params.w;\n" \
@@ -727,31 +710,6 @@ VAR(reserveshadowmaptc, 1, 0, 0);
 VAR(reservedynlighttc, 1, 0, 0);
 VAR(minimizedynlighttcusage, 1, 0, 0);
 
-static void gengenericvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row)
-{
-    bool vschanged = false, pschanged = false;
-    vector<char> vsv, psv;
-    vsv.put(vs, strlen(vs)+1);
-    psv.put(ps, strlen(ps)+1);
-
-    int len = strlen("#pragma CUBE2_variant");
-    for(char *vspragma = vsv.getbuf();; vschanged = true)
-    {
-        vspragma = strstr(vspragma, "#pragma CUBE2_variant");
-        if(!vspragma) break;
-        memset(vspragma, ' ', len);
-    }
-    for(char *pspragma = psv.getbuf();; pschanged = true)
-    {
-        pspragma = strstr(pspragma, "#pragma CUBE2_variant");
-        if(!pspragma) break;
-        memset(pspragma, ' ', len);
-    }
-    defformatstring(varname)("<variant:%d,%d>%s", s.variants[row].length(), row, sname);
-    defformatstring(reuse)("%d", row);
-    newshader(s.type, varname, vschanged ? vsv.getbuf() : reuse, pschanged ? psv.getbuf() : reuse, &s, row);
-}
-
 static bool genwatervariant(Shader &s, const char *sname, vector<char> &vs, vector<char> &ps, int row)
 {
     char *vspragma = strstr(vs.getbuf(), "#pragma CUBE2_water");
@@ -785,10 +743,10 @@ static bool genwatervariant(Shader &s, const char *sname, vector<char> &vs, vect
         }
         if(fadetc>=0)
         {
-            defformatstring(fadedef)("MAD result.texcoord[%d].%c, vertex.position.z, program.env[8].y, program.env[8].z;\n", 
+            s_sprintfd(fadedef)("MAD result.texcoord[%d].%c, vertex.position.z, program.env[8].y, program.env[8].z;\n", 
                                 fadetc, fadecomp==3 ? 'w' : 'x'+fadecomp);
             vs.insert(vspragma-vs.getbuf(), fadedef, strlen(fadedef));
-            defformatstring(fadeuse)("MOV result.color.a, fragment.texcoord[%d].%c;\n",
+            s_sprintfd(fadeuse)("MOV result.color.a, fragment.texcoord[%d].%c;\n",
                                 fadetc, fadecomp==3 ? 'w' : 'x'+fadecomp);
             ps.insert(pspragma-ps.getbuf(), fadeuse, strlen(fadeuse));
         }
@@ -798,7 +756,7 @@ static bool genwatervariant(Shader &s, const char *sname, vector<char> &vs, vect
             ps.insert(pspragma-ps.getbuf(), fogfade, strlen(fogfade));
         }
     }
-    defformatstring(name)("<water>%s", sname);
+    s_sprintfd(name)("<water>%s", sname);
     Shader *variant = newshader(s.type, name, vs.getbuf(), ps.getbuf(), &s, row);
     return variant!=NULL;
 }
@@ -867,7 +825,7 @@ static void gendynlightvariant(Shader &s, const char *sname, const char *vs, con
         {
             loopk(i+1)
             {
-                defformatstring(pos)("%sdynlight%dpos%s", 
+                s_sprintfd(pos)("%sdynlight%dpos%s", 
                     !k || k==numlights ? "uniform vec4 " : " ", 
                     k, 
                     k==i || k+1==numlights ? ";\n" : ",");
@@ -876,12 +834,12 @@ static void gendynlightvariant(Shader &s, const char *sname, const char *vs, con
             }
             loopk(i+1)
             {
-                defformatstring(color)("%sdynlight%dcolor%s", !k ? "uniform vec4 " : " ", k, k==i ? ";\n" : ",");
+                s_sprintfd(color)("%sdynlight%dcolor%s", !k ? "uniform vec4 " : " ", k, k==i ? ";\n" : ",");
                 psdl.put(color, strlen(color));
             }
             loopk(min(i+1, numlights))
             {
-                defformatstring(dir)("%sdynlight%ddir%s", !k ? "varying vec3 " : " ", k, k==i || k+1==numlights ? ";\n" : ",");
+                s_sprintfd(dir)("%sdynlight%ddir%s", !k ? "varying vec3 " : " ", k, k==i || k+1==numlights ? ";\n" : ",");
                 vsdl.put(dir, strlen(dir));
                 psdl.put(dir, strlen(dir));
             }
@@ -894,35 +852,35 @@ static void gendynlightvariant(Shader &s, const char *sname, const char *vs, con
         {
             extern int ati_dph_bug;
             string tc, dl;
-            if(s.type & SHADER_GLSLANG) formatstring(tc)(
+            if(s.type & SHADER_GLSLANG) s_sprintf(tc)(
                 k<numlights ? 
                     "dynlight%ddir = gl_Vertex.xyz*dynlight%dpos.w + dynlight%dpos.xyz;\n" :
                     "vec3 dynlight%ddir = dynlight0dir*dynlight%dpos.w + dynlight%dpos.xyz;\n",   
                 k, k, k);
-            else if(k>=numlights) formatstring(tc)(
+            else if(k>=numlights) s_sprintf(tc)(
                 "%s"
                 "MAD dynlightdir.xyz, fragment.texcoord[%d], program.env[%d].w, program.env[%d];\n",
                 k==numlights ? "TEMP dynlightdir;\n" : "",
                 lights[0], k-1, k-1);
-            else if(ati_dph_bug || lights[k]==emufogtc) formatstring(tc)(
+            else if(ati_dph_bug || lights[k]==emufogtc) s_sprintf(tc)(
                 "MAD result.texcoord[%d].xyz, vertex.position, program.env[%d].w, program.env[%d];\n",
                 lights[k], 10+k, 10+k);
-            else formatstring(tc)(
+            else s_sprintf(tc)(
                 "MAD result.texcoord[%d].xyz, vertex.position, program.env[%d].w, program.env[%d];\n" 
                 "MOV result.texcoord[%d].w, 1;\n",
                 lights[k], 10+k, 10+k, lights[k]);
             if(k < numlights) vsdl.put(tc, strlen(tc));
             else psdl.put(tc, strlen(tc));
 
-            if(s.type & SHADER_GLSLANG) formatstring(dl)(
+            if(s.type & SHADER_GLSLANG) s_sprintf(dl)(
                 "%s.rgb += dynlight%dcolor.rgb * (1.0 - clamp(dot(dynlight%ddir, dynlight%ddir), 0.0, 1.0));\n",
                 pslight, k, k, k);
-            else if(k>=numlights) formatstring(dl)(
+            else if(k>=numlights) s_sprintf(dl)(
                 "DP3_SAT dynlight.x, dynlightdir, dynlightdir;\n"
                 "SUB dynlight.x, 1, dynlight.x;\n"
                 "MAD %s.rgb, program.env[%d], dynlight.x, %s;\n",
                 pslight, 10+k, pslight);
-            else if(ati_dph_bug || lights[k]==emufogtc) formatstring(dl)(
+            else if(ati_dph_bug || lights[k]==emufogtc) s_sprintf(dl)(
                 "%s"
                 "DP3_SAT dynlight.x, fragment.texcoord[%d], fragment.texcoord[%d];\n"
                 "SUB dynlight.x, 1, dynlight.x;\n"
@@ -930,7 +888,7 @@ static void gendynlightvariant(Shader &s, const char *sname, const char *vs, con
                 !k ? "TEMP dynlight;\n" : "",
                 lights[k], lights[k],
                 pslight, 10+k, pslight);
-            else formatstring(dl)(
+            else s_sprintf(dl)(
                 "%s"
                 "DPH_SAT dynlight.x, -fragment.texcoord[%d], fragment.texcoord[%d];\n"
                 "MAD %s.rgb, program.env[%d], dynlight.x, %s;\n",
@@ -945,7 +903,7 @@ static void gendynlightvariant(Shader &s, const char *sname, const char *vs, con
        
         EMUFOGPS(emufogcoord && i+1==numlights, psdl, emufogtc, emufogcomp);
 
-        defformatstring(name)("<dynlight %d>%s", i+1, sname);
+        s_sprintfd(name)("<dynlight %d>%s", i+1, sname);
         Shader *variant = newshader(s.type, name, vsdl.getbuf(), psdl.getbuf(), &s, row); 
         if(!variant) return;
         if(row < 4) genwatervariant(s, name, vsdl, psdl, row+2);
@@ -1018,38 +976,38 @@ static void genshadowmapvariant(Shader &s, const char *sname, const char *vs, co
 
                 "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n"
                 "float smtest = shadowmaptc.z*smvals.y;\n"
-                "float shadowed = smtest < smvals.x && smtest > smvals.z ? smvals.w : 0.0;\n";
+                "float shadowed = smtest < smvals.x && smtest > smvals.z ? smvals.w : 0.0;\n"; 
         pssm.put(sm, strlen(sm));
-        defformatstring(smlight)(
+        s_sprintfd(smlight)(
             "%s.rgb -= shadowed*clamp(%s.rgb - shadowmapambient.rgb, 0.0, 1.0);\n",
             pslight, pslight, pslight);
         pssm.put(smlight, strlen(smlight));
     }
     else
     {
-        defformatstring(tc)(
+        s_sprintfd(tc)(
             "DP4 result.texcoord[%d].x, state.matrix.texture[2].row[0], vertex.position;\n"
             "DP4 result.texcoord[%d].y, state.matrix.texture[2].row[1], vertex.position;\n"
             "DP4 result.texcoord[%d].z, state.matrix.texture[2].row[2], vertex.position;\n",
             smtc, smtc, smtc);
         vssm.put(tc, strlen(tc));
 
-        defformatstring(sm)(
+        s_sprintfd(sm)(
             smoothshadowmappeel ? 
                 "TEMP smvals, smdiff, smambient;\n"
                 "TEX smvals, fragment.texcoord[%d], texture[7], 2D;\n"
                 "MAD_SAT smdiff.xz, -fragment.texcoord[%d].z, smvals.y, smvals;\n"
                 "CMP smvals.w, -smdiff.x, smvals.w, 0;\n"
                 "MAD_SAT smvals.w, -8, smdiff.z, smvals.w;\n" :
-
+            
                 "TEMP smvals, smtest, smambient;\n"
                 "TEX smvals, fragment.texcoord[%d], texture[7], 2D;\n"
                 "MUL smtest.z, fragment.texcoord[%d].z, smvals.y;\n"
-                "SLT smtest.xz, smtest.z, smvals;\n"
+                "SLT smtest.xz, smtest.z, smvals;\n" 
                 "MAD_SAT smvals.w, smvals.w, smtest.x, -smtest.z;\n",
             smtc, smtc);
         pssm.put(sm, strlen(sm));
-        formatstring(sm)(
+        s_sprintf(sm)(
             "SUB_SAT smambient.rgb, %s, program.env[7];\n"
             "MAD %s.rgb, smvals.w, -smambient, %s;\n",
             pslight, pslight, pslight);
@@ -1069,7 +1027,7 @@ static void genshadowmapvariant(Shader &s, const char *sname, const char *vs, co
 
     EMUFOGPS(emufogcoord, pssm, emufogtc, emufogcomp);
 
-    defformatstring(name)("<shadowmap>%s", sname);
+    s_sprintfd(name)("<shadowmap>%s", sname);
     Shader *variant = newshader(s.type, name, vssm.getbuf(), pssm.getbuf(), &s, row);
     if(!variant) return;
     genwatervariant(s, name, vssm.getbuf(), pssm.getbuf(), row+2);
@@ -1077,85 +1035,6 @@ static void genshadowmapvariant(Shader &s, const char *sname, const char *vs, co
     if(strstr(vs, "#pragma CUBE2_dynlight")) gendynlightvariant(s, name, vssm.getbuf(), pssm.getbuf(), row);
 }
 
-VAR(defershaders, 0, 1, 1);
-
-void defershader(int *type, const char *name, const char *contents)
-{
-    Shader *exists = shaders.access(name);
-    if(exists && !(exists->type&SHADER_INVALID)) return;
-    if(!defershaders) { execute(contents); return; }
-    char *rname = exists ? exists->name : newstring(name);
-    Shader &s = shaders[rname];
-    s.name = rname;
-    DELETEA(s.defer);
-    s.defer = newstring(contents);
-    s.type = SHADER_DEFERRED | *type;
-    s.standard = standardshader;
-}
-
-void useshader(Shader *s)
-{
-    if(!(s->type&SHADER_DEFERRED) || !s->defer) return;
-        
-    char *defer = s->defer;
-    s->defer = NULL;
-    bool wasstandard = standardshader, wasforcing = forceshaders, waspersisting = persistidents;
-    standardshader = s->standard;
-    forceshaders = false;
-    persistidents = false;
-    execute(defer);
-    persistidents = waspersisting;
-    forceshaders = wasforcing;
-    standardshader = wasstandard;
-    delete[] defer;
-
-    if(s->type&SHADER_DEFERRED)
-    {
-        DELETEA(s->defer);
-        s->type = SHADER_INVALID;
-    }
-}
-
-void fixshaderdetail()
-{
-    // must null out separately because fixdetailshader can recursively set it
-    enumerate(shaders, Shader, s, { if(!s.forced) s.detailshader = NULL; });
-    enumerate(shaders, Shader, s, { if(s.forced) s.fixdetailshader(); }); 
-    linkslotshaders();
-}
-
-VARF(nativeshaders, 0, 1, 1, fixshaderdetail());
-VARFP(shaderdetail, 0, MAXSHADERDETAIL, MAXSHADERDETAIL, fixshaderdetail());
-
-void Shader::fixdetailshader(bool force, bool recurse)
-{
-    Shader *alt = this;
-    detailshader = NULL;
-    do
-    {
-        Shader *cur = shaderdetail < MAXSHADERDETAIL ? alt->fastshader[shaderdetail] : alt;
-        if(cur->type&SHADER_DEFERRED && force) useshader(cur);
-        if(!(cur->type&SHADER_INVALID))
-        {
-            if(cur->type&SHADER_DEFERRED) break;
-            detailshader = cur;
-            if(cur->native || !nativeshaders) break;
-        }
-        alt = alt->altshader;
-    } while(alt && alt!=this);
-
-    if(recurse && detailshader) loopi(MAXVARIANTROWS) loopvj(detailshader->variants[i]) detailshader->variants[i][j]->fixdetailshader(force, false);
-}
-
-Shader *useshaderbyname(const char *name)
-{
-    Shader *s = shaders.access(name);
-    if(!s) return NULL;
-    if(!s->detailshader) s->fixdetailshader(); 
-    s->forced = true;
-    return s;
-}
-
 void shader(int *type, char *name, char *vs, char *ps)
 {
     if(lookupshaderbyname(name)) return;
@@ -1164,6 +1043,10 @@ void shader(int *type, char *name, char *vs, char *ps)
        (!hasCM && strstr(ps, *type & SHADER_GLSLANG ? "textureCube" : "CUBE;")) ||
        (!hasTR && strstr(ps, *type & SHADER_GLSLANG ? "texture2DRect" : "RECT;")))
     {
+        loopv(curparams)
+        {
+            if(curparams[i].name) delete[] curparams[i].name;
+        }
         curparams.setsize(0);
         return;
     }
@@ -1171,9 +1054,9 @@ void shader(int *type, char *name, char *vs, char *ps)
     extern int mesa_program_bug;
     if(renderpath!=R_FIXEDFUNCTION)
     {
-        defformatstring(info)("shader %s", name);
-        renderprogress(loadprogress, info);
-        if(mesa_program_bug && initshaders)
+        s_sprintfd(info)("shader %s", name);
+        show_out_of_renderloop_progress(0.0, info);
+        if(mesa_program_bug && standardshader)
         {
             glEnable(GL_VERTEX_PROGRAM_ARB);
             glEnable(GL_FRAGMENT_PROGRAM_ARB);
@@ -1187,7 +1070,7 @@ void shader(int *type, char *name, char *vs, char *ps)
         if(strstr(vs, "#pragma CUBE2_shadowmap")) genshadowmapvariant(*s, s->name, vs, ps);
         if(strstr(vs, "#pragma CUBE2_dynlight")) gendynlightvariant(*s, s->name, vs, ps);
     }
-    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && initshaders)
+    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && standardshader)
     {
         glDisable(GL_VERTEX_PROGRAM_ARB);
         glDisable(GL_FRAGMENT_PROGRAM_ARB);
@@ -1208,11 +1091,11 @@ void variantshader(int *type, char *name, int *row, char *vs, char *ps)
     Shader *s = lookupshaderbyname(name);
     if(!s) return;
 
-    defformatstring(varname)("<variant:%d,%d>%s", s->variants[*row].length(), *row, name);
-    //defformatstring(info)("shader %s", varname);
-    //renderprogress(loadprogress, info);
+    s_sprintfd(varname)("<variant:%d,%d>%s", s->variants[*row].length(), *row, name);
+    //s_sprintfd(info)("shader %s", varname);
+    //show_out_of_renderloop_progress(0.0, info);
     extern int mesa_program_bug;
-    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && initshaders)
+    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && standardshader)
     {
         glEnable(GL_VERTEX_PROGRAM_ARB);
         glEnable(GL_FRAGMENT_PROGRAM_ARB);
@@ -1222,9 +1105,8 @@ void variantshader(int *type, char *name, int *row, char *vs, char *ps)
     {
         // '#' is a comment in vertex/fragment programs, while '#pragma' allows an escape for GLSL, so can handle both at once
         if(strstr(vs, "#pragma CUBE2_dynlight")) gendynlightvariant(*s, varname, vs, ps, *row);
-        if(strstr(vs, "#pragma CUBE2_variant")) gengenericvariant(*s, varname, vs, ps, *row);
     }
-    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && initshaders)
+    if(renderpath!=R_FIXEDFUNCTION && mesa_program_bug && standardshader)
     {
         glDisable(GL_VERTEX_PROGRAM_ARB);
         glDisable(GL_FRAGMENT_PROGRAM_ARB);
@@ -1233,37 +1115,35 @@ void variantshader(int *type, char *name, int *row, char *vs, char *ps)
 
 void setshader(char *name)
 {
-    curparams.setsize(0);
-    Shader *s = shaders.access(name);
+    Shader *s = lookupshaderbyname(name);
     if(!s)
     {
         if(renderpath!=R_FIXEDFUNCTION) conoutf(CON_ERROR, "no such shader: %s", name);
     }
     else curshader = s;
+    loopv(curparams)
+    {
+        if(curparams[i].name) delete[] curparams[i].name;
+    }
+    curparams.setsize(0);
 }
 
 ShaderParam *findshaderparam(Slot &s, const char *name, int type, int index)
 {
+    if(!s.shader) return NULL;
     loopv(s.params)
     {
         ShaderParam &param = s.params[i];
         if((name && param.name && !strcmp(name, param.name)) || (param.type==type && param.index==index)) return &param;
     }
-    if(!s.shader->detailshader) return NULL;
-    loopv(s.shader->detailshader->defaultparams)
+    loopv(s.shader->defaultparams)
     {
-        ShaderParam &param = s.shader->detailshader->defaultparams[i];
+        ShaderParam &param = s.shader->defaultparams[i];
         if((name && param.name && !strcmp(name, param.name)) || (param.type==type && param.index==index)) return &param;
     }
     return NULL;
 }
 
-void resetslotshader()
-{
-    curshader = NULL;
-    curparams.setsize(0);
-}
-
 void setslotshader(Slot &s)
 {
     s.shader = curshader;
@@ -1272,92 +1152,74 @@ void setslotshader(Slot &s)
         s.shader = stdworldshader;
         return;
     }
-    loopv(curparams) s.params.add(curparams[i]);
-}
-
-void linkslotshader(Slot &s, bool load)
-{
-    if(!s.shader) return;
-
-    if(load && !s.shader->detailshader) s.shader->fixdetailshader();
-    
-    Shader *sh = s.shader->detailshader;
-    if(!sh)
+    loopv(curparams)
     {
-        if(load)
-        {
-            loopv(s.params) s.params[i].loc = -1;
-            return;
-        }
-        sh = s.shader;
+        ShaderParam &param = curparams[i], *defaultparam = findshaderparam(s, param.name, param.type, param.index);
+        if(!defaultparam || !memcmp(param.val, defaultparam->val, sizeof(param.val))) continue;
+        ShaderParam &override = s.params.add(param);
+        override.name = defaultparam->name;
+        if(s.shader->type&SHADER_GLSLANG) override.index = (LocalShaderParamState *)defaultparam - &s.shader->defaultparams[0];
     }
 
-    loopv(s.params)
+    if(strstr(s.shader->name, "glowworld"))
     {
-        int loc = -1;
-        ShaderParam &param = s.params[i];
-        loopv(sh->defaultparams)
-        {
-            ShaderParam &dparam = sh->defaultparams[i];
-            if(param.name ? dparam.name==param.name : dparam.type==param.type && dparam.index==param.index)
-            {
-                if(memcmp(param.val, dparam.val, sizeof(param.val))) loc = i;
-                break;
-            }
-        }
-        param.loc = loc;
-    }
-
-    if(strstr(sh->name, "glowworld"))
-    {
-        ShaderParam *cparam = findshaderparam(s, "glowcolor", SHPARAM_PIXEL, 0);
-        if(!cparam) cparam = findshaderparam(s, "glowcolor", SHPARAM_VERTEX, 0);
+        ShaderParam *cparam = findshaderparam(s, "glowscale", SHPARAM_PIXEL, 0);
+        if(!cparam) cparam = findshaderparam(s, "glowscale", SHPARAM_VERTEX, 0);
         if(cparam) loopk(3) s.glowcolor[k] = cparam->val[k];
-        else s.glowcolor = vec(1, 1, 1);
-        if(strstr(sh->name, "pulse"))
+        if(strstr(s.shader->name, "pulse"))
         {
             ShaderParam *pulseparam, *speedparam;
-            if(strstr(sh->name, "bump"))
+            if(strstr(s.shader->name, "bump"))
             {
-                pulseparam = findshaderparam(s, "pulseglowcolor", SHPARAM_PIXEL, 5);
+                pulseparam = findshaderparam(s, "pulseglowscale", SHPARAM_PIXEL, 5);
                 speedparam = findshaderparam(s, "pulseglowspeed", SHPARAM_VERTEX, 4);
             }
             else
             {
-                pulseparam = findshaderparam(s, "pulseglowcolor", SHPARAM_VERTEX, 2);
+                pulseparam = findshaderparam(s, "pulseglowscale", SHPARAM_VERTEX, 2);
                 speedparam = findshaderparam(s, "pulseglowspeed", SHPARAM_VERTEX, 1);
             }
             if(pulseparam) loopk(3) s.pulseglowcolor[k] = pulseparam->val[k];
-            else s.pulseglowcolor = vec(0, 0, 0);
             if(speedparam) s.pulseglowspeed = speedparam->val[0]/1000.0f;
-            else s.pulseglowspeed = 1;
         }
     }
-    else if(!strcmp(sh->name, "colorworld"))
+    else if(!strcmp(s.shader->name, "colorworld"))
     {
         ShaderParam *cparam = findshaderparam(s, "colorscale", SHPARAM_PIXEL, 0);
-        if(cparam && (cparam->val[0]!=1 || cparam->val[1]!=1 || cparam->val[2]!=1) && s.sts.length()>=1 && !strstr(s.sts[0].name, "<ffcolor:"))
+        if(cparam && (cparam->val[0]!=1 || cparam->val[1]!=1 || cparam->val[2]!=1) && s.sts.length()>=1)
         {
-            defformatstring(colorname)("<ffcolor:%f/%f/%f>%s", cparam->val[0], cparam->val[1], cparam->val[2], s.sts[0].name);
-            copystring(s.sts[0].name, colorname);
+            s_sprintfd(colorname)("<ffcolor:%f/%f/%f>%s", cparam->val[0], cparam->val[1], cparam->val[2], s.sts[0].name);
+            s_strcpy(s.sts[0].name, colorname);
         }
     }
 }
 
+VAR(nativeshaders, 0, 1, 1);
+
 void altshader(char *origname, char *altname)
 {
-    Shader *orig = shaders.access(origname), *alt = shaders.access(altname);
-    if(!orig || !alt) return;
-    orig->altshader = alt;
-    orig->fixdetailshader(false);
+    Shader *alt = lookupshaderbyname(altname);
+    if(!alt) return;
+    Shader *orig = lookupshaderbyname(origname);
+    if(orig)
+    {
+        if(nativeshaders && !orig->native) orig->altshader = alt;
+        return;
+    }
+    Shader *exists = shaders.access(origname);
+    char *rname = exists ? exists->name : newstring(origname);
+    Shader &s = shaders[rname];
+    s.name = rname;
+    s.altshader = alt;
 }
 
 void fastshader(char *nice, char *fast, int *detail)
 {
-    Shader *ns = shaders.access(nice), *fs = shaders.access(fast);
-    if(!ns || !fs) return;
+    Shader *ns = shaders.access(nice);
+    if(!ns || ns->type==SHADER_INVALID || ns->altshader) return;
+    Shader *fs = lookupshaderbyname(fast);
+    if(!fs) return;
     loopi(min(*detail+1, MAXSHADERDETAIL)) ns->fastshader[i] = fs;
-    ns->fixdetailshader(false);
 }
 
 COMMAND(shader, "isss");
@@ -1365,8 +1227,6 @@ COMMAND(variantshader, "isiss");
 COMMAND(setshader, "s");
 COMMAND(altshader, "ss");
 COMMAND(fastshader, "ssi");
-COMMAND(defershader, "iss");
-ICOMMAND(forceshader, "s", (const char *name), useshaderbyname(name));
 
 void isshaderdefined(char *name)
 {
@@ -1383,29 +1243,17 @@ void isshadernative(char *name)
 COMMAND(isshaderdefined, "s");
 COMMAND(isshadernative, "s");
 
-static hashtable<const char *, const char *> shaderparamnames(256);
-
-const char *getshaderparamname(const char *name)
+void setshaderparam(char *name, int type, int n, float x, float y, float z, float w)
 {
-    const char **exists = shaderparamnames.access(name);
-    if(exists) return *exists;
-    name = newstring(name);
-    shaderparamnames[name] = name;
-    return name;
-}
-
-void addshaderparam(const char *name, int type, int n, float x, float y, float z, float w)
-{
-    if((type==SHPARAM_VERTEX || type==SHPARAM_PIXEL) && (n<0 || n>=MAXSHADERPARAMS))
+    if(!name && (n<0 || n>=MAXSHADERPARAMS))
     {
         conoutf(CON_ERROR, "shader param index must be 0..%d\n", MAXSHADERPARAMS-1);
         return;
     }
-    if(name) name = getshaderparamname(name);
     loopv(curparams)
     {
         ShaderParam &param = curparams[i];
-        if(param.type == type && (name ? param.name==name : param.index == n))
+        if(param.type == type && (name ? !strstr(param.name, name) : param.index == n))
         {
             param.val[0] = x;
             param.val[1] = y;
@@ -1414,210 +1262,166 @@ void addshaderparam(const char *name, int type, int n, float x, float y, float z
             return;
         }
     }
-    ShaderParam param = {name, type, n, -1, {x, y, z, w}};
+    ShaderParam param = {name ? newstring(name) : NULL, type, n, -1, {x, y, z, w}};
     curparams.add(param);
 }
 
-ICOMMAND(setvertexparam, "iffff", (int *n, float *x, float *y, float *z, float *w), addshaderparam(NULL, SHPARAM_VERTEX, *n, *x, *y, *z, *w));
-ICOMMAND(setpixelparam, "iffff", (int *n, float *x, float *y, float *z, float *w), addshaderparam(NULL, SHPARAM_PIXEL, *n, *x, *y, *z, *w));
-ICOMMAND(setuniformparam, "sffff", (char *name, float *x, float *y, float *z, float *w), addshaderparam(name, SHPARAM_UNIFORM, -1, *x, *y, *z, *w));
-ICOMMAND(setshaderparam, "sffff", (char *name, float *x, float *y, float *z, float *w), addshaderparam(name, SHPARAM_LOOKUP, -1, *x, *y, *z, *w));
-ICOMMAND(defvertexparam, "siffff", (char *name, int *n, float *x, float *y, float *z, float *w), addshaderparam(name[0] ? name : NULL, SHPARAM_VERTEX, *n, *x, *y, *z, *w));
-ICOMMAND(defpixelparam, "siffff", (char *name, int *n, float *x, float *y, float *z, float *w), addshaderparam(name[0] ? name : NULL, SHPARAM_PIXEL, *n, *x, *y, *z, *w));
-ICOMMAND(defuniformparam, "sffff", (char *name, float *x, float *y, float *z, float *w), addshaderparam(name, SHPARAM_UNIFORM, -1, *x, *y, *z, *w));
-
-#define NUMPOSTFXBINDS 10
-
-struct postfxtex
+void setvertexparam(int *n, float *x, float *y, float *z, float *w)
 {
-    GLuint id;
-    int scale, used;
+    setshaderparam(NULL, SHPARAM_VERTEX, *n, *x, *y, *z, *w);
+}
 
-    postfxtex() : id(0), scale(0), used(-1) {}
-};
-vector<postfxtex> postfxtexs;
-int postfxbinds[NUMPOSTFXBINDS];
-GLuint postfxfb = 0;
-int postfxw = 0, postfxh = 0;
+void setpixelparam(int *n, float *x, float *y, float *z, float *w)
+{
+    setshaderparam(NULL, SHPARAM_PIXEL, *n, *x, *y, *z, *w);
+}
 
-struct postfxpass
+void setuniformparam(char *name, float *x, float *y, float *z, float *w)
 {
-    Shader *shader;
-    vec4 params;
-    uint inputs, freeinputs;
-    int outputbind, outputscale;
+    setshaderparam(name, SHPARAM_UNIFORM, -1, *x, *y, *z, *w);
+}
 
-    postfxpass() : shader(NULL), inputs(1), freeinputs(1), outputbind(0), outputscale(0) {}
-};
-vector<postfxpass> postfxpasses;
+COMMAND(setvertexparam, "iffff");
+COMMAND(setpixelparam, "iffff");
+COMMAND(setuniformparam, "sffff");
 
-static int allocatepostfxtex(int scale)
+const int NUMSCALE = 7;
+Shader *fsshader = NULL, *scaleshader = NULL, *initshader = NULL;
+GLuint rendertarget[NUMSCALE];
+GLuint fsfb[NUMSCALE-1];
+GLfloat fsparams[4];
+int fs_w = 0, fs_h = 0, fspasses = NUMSCALE, fsskip = 1; 
+   
+void setfullscreenshader(char *name, int *x, int *y, int *z, int *w)
 {
-    loopv(postfxtexs)
+    if(!hasTR || !*name)
+    {
+        fsshader = NULL;
+    }
+    else
     {
-        postfxtex &t = postfxtexs[i];
-        if(t.scale==scale && t.used < 0) return i; 
+        Shader *s = lookupshaderbyname(name);
+        if(!s) return conoutf(CON_ERROR, "no such fullscreen shader: %s", name);
+        fsshader = s;
+        s_sprintfd(ssname)("%s_scale", name);
+        s_sprintfd(isname)("%s_init", name);
+        scaleshader = lookupshaderbyname(ssname);
+        initshader = lookupshaderbyname(isname);
+        fspasses = NUMSCALE;
+        fsskip = 1;
+        if(scaleshader)
+        {
+            int len = strlen(name);
+            char c = name[--len];
+            if(isdigit(c)) 
+            {
+                if(len>0 && isdigit(name[--len])) 
+                { 
+                    fsskip = c-'0';
+                    fspasses = name[len]-'0';
+                }
+                else fspasses = c-'0';
+            }
+        }
+        conoutf("now rendering with: %s", name);
+        fsparams[0] = *x/255.0f;
+        fsparams[1] = *y/255.0f;
+        fsparams[2] = *z/255.0f;
+        fsparams[3] = *w/255.0f;
     }
-    postfxtex &t = postfxtexs.add();
-    t.scale = scale;
-    glGenTextures(1, &t.id);
-    createtexture(t.id, max(screen->w>>scale, 1), max(screen->h>>scale, 1), NULL, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE_ARB);
-    return postfxtexs.length()-1;
 }
 
-void cleanuppostfx(bool fullclean)
+COMMAND(setfullscreenshader, "siiii");
+
+void renderfsquad(int w, int h, Shader *s)
 {
-    if(fullclean && postfxfb)
+    s->set();
+    glViewport(0, 0, w, h);
+    if(s==scaleshader || s==initshader)
     {
-        glDeleteFramebuffers_(1, &postfxfb);
-        postfxfb = 0;
+        w <<= fsskip;
+        h <<= fsskip;
     }
-
-    loopv(postfxtexs) glDeleteTextures(1, &postfxtexs[i].id);
-    postfxtexs.setsize(0);
-
-    postfxw = 0;
-    postfxh = 0;
+    glBegin(GL_QUADS);
+    glTexCoord2f(0, 0); glVertex3f(-1, -1, 0);
+    glTexCoord2f(w, 0); glVertex3f( 1, -1, 0);
+    glTexCoord2f(w, h); glVertex3f( 1,  1, 0);
+    glTexCoord2f(0, h); glVertex3f(-1,  1, 0);
+    glEnd();
 }
 
-void renderpostfx()
+void renderfullscreenshader(int w, int h)
 {
-    if(postfxpasses.empty() || renderpath==R_FIXEDFUNCTION) return;
-
-    if(postfxw != screen->w || postfxh != screen->h) 
+    if(!fsshader || renderpath==R_FIXEDFUNCTION) return;
+    
+    glDisable(GL_DEPTH_TEST);
+    glDepthMask(GL_FALSE);
+    glEnable(GL_TEXTURE_RECTANGLE_ARB);
+    
+    if(fs_w != w || fs_h != h)
     {
-        cleanuppostfx(false);
-        postfxw = screen->w;
-        postfxh = screen->h;
+        if(!fs_w && !fs_h)
+        {
+            glGenTextures(NUMSCALE, rendertarget);
+            if(hasFBO) glGenFramebuffers_(NUMSCALE-1, fsfb);
+        }
+        loopi(NUMSCALE)
+            createtexture(rendertarget[i], w>>i, h>>i, NULL, 3, false, GL_RGB, GL_TEXTURE_RECTANGLE_ARB);
+        fs_w = w;
+        fs_h = h;
+        if(fsfb[0])
+        {
+            loopi(NUMSCALE-1)
+            {
+                glBindFramebuffer_(GL_FRAMEBUFFER_EXT, fsfb[i]);
+                glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, rendertarget[i+1], 0);
+            }
+            glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
+        }
     }
 
-    int binds[NUMPOSTFXBINDS];
-    loopi(NUMPOSTFXBINDS) binds[i] = -1;
-    loopv(postfxtexs) postfxtexs[i].used = -1;
+    setenvparamfv("fsparams", SHPARAM_PIXEL, 0, fsparams);
+    setenvparamf("millis", SHPARAM_VERTEX, 1, lastmillis/1000.0f, lastmillis/1000.0f, lastmillis/1000.0f);
 
-    binds[0] = allocatepostfxtex(0);
-    postfxtexs[binds[0]].used = 0;
-    glBindTexture(GL_TEXTURE_RECTANGLE_ARB, postfxtexs[binds[0]].id);
-    glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, 0, 0, screen->w, screen->h);
+    int nw = w, nh = h;
 
-    if(hasFBO && postfxpasses.length() > 1)
+    loopi(fspasses)
     {
-        if(!postfxfb) glGenFramebuffers_(1, &postfxfb);
-        glBindFramebuffer_(GL_FRAMEBUFFER_EXT, postfxfb);
+        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, rendertarget[i*fsskip]);
+        glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, 0, 0, nw, nh);
+        if(i>=fspasses-1 || !scaleshader || fsfb[0]) break;
+        renderfsquad(nw >>= fsskip, nh >>= fsskip, !i && initshader ? initshader : scaleshader);
     }
-
-    setenvparamf("millis", SHPARAM_VERTEX, 1, lastmillis/1000.0f, lastmillis/1000.0f, lastmillis/1000.0f);
-
-    loopv(postfxpasses)
+    if(scaleshader && fsfb[0])
     {
-        postfxpass &p = postfxpasses[i];
-
-        int tex = -1;
-        if(!postfxpasses.inrange(i+1))
+        loopi(fspasses-1)
         {
-            if(hasFBO && postfxpasses.length() > 1) glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
-        }
-        else
-        {
-            tex = allocatepostfxtex(p.outputscale);
-            if(hasFBO) glFramebufferTexture2D_(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB, postfxtexs[tex].id, 0);
+            if(i) glBindTexture(GL_TEXTURE_RECTANGLE_ARB, rendertarget[i*fsskip]);
+            glBindFramebuffer_(GL_FRAMEBUFFER_EXT, fsfb[(i+1)*fsskip-1]);
+            renderfsquad(nw >>= fsskip, nh >>= fsskip, !i && initshader ? initshader : scaleshader);
         }
+        glBindFramebuffer_(GL_FRAMEBUFFER_EXT, 0);
+    }
 
-        int w = tex >= 0 ? max(screen->w>>postfxtexs[tex].scale, 1) : screen->w, 
-            h = tex >= 0 ? max(screen->h>>postfxtexs[tex].scale, 1) : screen->h;
-        glViewport(0, 0, w, h);
-        p.shader->set();
-        setlocalparamfv("params", SHPARAM_VERTEX, 0, p.params.v);
-        setlocalparamfv("params", SHPARAM_PIXEL, 0, p.params.v);
-        int tw = w, th = h, tmu = 0;
-        loopj(NUMPOSTFXBINDS) if(p.inputs&(1<<j) && binds[j] >= 0)
-        {
-            if(!tmu)
-            {
-                tw = max(screen->w>>postfxtexs[binds[j]].scale, 1);
-                th = max(screen->h>>postfxtexs[binds[j]].scale, 1);
-            }
-            else glActiveTexture_(GL_TEXTURE0_ARB + tmu);
-            glBindTexture(GL_TEXTURE_RECTANGLE_ARB, postfxtexs[binds[j]].id);
-            ++tmu;
-        }
-        if(tmu) glActiveTexture_(GL_TEXTURE0_ARB);
-        glBegin(GL_QUADS);
-        glTexCoord2f(0,  0);  glVertex2f(-1, -1);
-        glTexCoord2f(tw, 0);  glVertex2f( 1, -1);
-        glTexCoord2f(tw, th); glVertex2f( 1,  1);
-        glTexCoord2f(0,  th); glVertex2f(-1,  1);
-        glEnd();
-
-        loopj(NUMPOSTFXBINDS) if(p.freeinputs&(1<<j) && binds[j] >= 0)
-        {
-            postfxtexs[binds[j]].used = -1;
-            binds[j] = -1;
-        }
-        if(tex >= 0)
-        {
-            if(binds[p.outputbind] >= 0) postfxtexs[binds[p.outputbind]].used = -1;
-            binds[p.outputbind] = tex;
-            postfxtexs[tex].used = p.outputbind;
-            if(!hasFBO)
-            {
-                glBindTexture(GL_TEXTURE_RECTANGLE_ARB, postfxtexs[tex].id);
-                glCopyTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, 0, 0, 0, 0, w, h);
-            }
-        }
+    if(scaleshader) loopi(fspasses)
+    {
+        glActiveTexture_(GL_TEXTURE0_ARB+i);
+        glEnable(GL_TEXTURE_RECTANGLE_ARB);
+        glBindTexture(GL_TEXTURE_RECTANGLE_ARB, rendertarget[i*fsskip]);
     }
-}
+    renderfsquad(w, h, fsshader);
 
-static bool addpostfx(const char *name, int outputbind, int outputscale, uint inputs, uint freeinputs, const vec4 &params)
-{
-    if(!hasTR || !*name) return false;
-    Shader *s = useshaderbyname(name);
-    if(!s)
+    if(scaleshader) loopi(fspasses)
     {
-        conoutf(CON_ERROR, "no such postfx shader: %s", name);
-        return false;
+        glActiveTexture_(GL_TEXTURE0_ARB+i);
+        glDisable(GL_TEXTURE_RECTANGLE_ARB);
     }
-    postfxpass &p = postfxpasses.add();
-    p.shader = s;
-    p.outputbind = outputbind;
-    p.outputscale = outputscale;
-    p.inputs = inputs;
-    p.freeinputs = freeinputs;
-    p.params = params;
-    return true;
-}
 
-void clearpostfx()
-{
-    postfxpasses.setsize(0);
-    cleanuppostfx(false);
+    glActiveTexture_(GL_TEXTURE0_ARB);
+    glDepthMask(GL_TRUE);
+    glEnable(GL_DEPTH_TEST);
 }
 
-COMMAND(clearpostfx, "");
-
-ICOMMAND(addpostfx, "siisffff", (char *name, int *bind, int *scale, char *inputs, float *x, float *y, float *z, float *w),
-{
-    int inputmask = inputs[0] ? 0 : 1;
-    int freemask = inputs[0] ? 0 : 1;
-    bool freeinputs = true;
-    for(; *inputs; inputs++) if(isdigit(*inputs)) 
-    {
-        inputmask |= 1<<(*inputs-'0');
-        if(freeinputs) freemask |= 1<<(*inputs-'0');
-    }
-    else if(*inputs=='+') freeinputs = false;
-    else if(*inputs=='-') freeinputs = true;
-    inputmask &= (1<<NUMPOSTFXBINDS)-1;
-    freemask &= (1<<NUMPOSTFXBINDS)-1;
-    addpostfx(name, clamp(*bind, 0, NUMPOSTFXBINDS-1), max(*scale, 0), inputmask, freemask, vec4(*x, *y, *z, *w));
-});
-
-ICOMMAND(setpostfx, "sffff", (char *name, float *x, float *y, float *z, float *w),
-{
-    clearpostfx();
-    addpostfx(name, 0, 0, 1, 1, vec4(*x, *y, *z, *w));
-});
-
 struct tmufunc
 {
     GLenum combine, sources[4], ops[4];
@@ -1635,8 +1439,8 @@ struct tmu
 { \
     0, \
     { -1, -1, -1, -1 }, \
-    { 0, { 0, 0, 0, ~0 }, { 0, 0, 0, 0 }, 0 }, \
-    { 0, { 0, 0, 0, ~0 }, { 0, 0, 0, 0 }, 0 } \
+    { 0, { 0, 0, 0, -1 }, { 0, 0, 0, 0 }, 0 }, \
+    { 0, { 0, 0, 0, -1 }, { 0, 0, 0, 0 }, 0 } \
 }
 
 #define INITTMU \
@@ -1799,7 +1603,13 @@ void inittmus()
 
 void cleanupshaders()
 {
-    cleanuppostfx(true);
+    if(fs_w || fs_h)
+    {
+        glDeleteTextures(NUMSCALE, rendertarget);
+        if(hasFBO) glDeleteFramebuffers_(NUMSCALE-1, fsfb);
+        fs_w = fs_h = 0;
+    }
+    fsshader = NULL;
 
     defaultshader = notextureshader = nocolorshader = foggedshader = foggednotextureshader = NULL;
     enumerate(shaders, Shader, s, s.cleanup());
@@ -1827,35 +1637,30 @@ void reloadshaders()
     persistidents = false;
     loadshaders();
     persistidents = true;
-    if(renderpath==R_FIXEDFUNCTION) return;
-    linkslotshaders();
-    enumerate(shaders, Shader, s, 
-    {
-        if(!s.standard && !(s.type&(SHADER_DEFERRED|SHADER_INVALID)) && !s.variantshader) 
+    if(renderpath!=R_FIXEDFUNCTION) enumerate(shaders, Shader, s, 
+        if(!s.standard && s.type!=SHADER_INVALID && !s.variantshader) 
         {
-            defformatstring(info)("shader %s", s.name);
-            renderprogress(0.0, info);
+            s_sprintfd(info)("shader %s", s.name);
+            show_out_of_renderloop_progress(0.0, info);
             if(!s.compile()) s.cleanup(true);
             loopi(MAXVARIANTROWS) loopvj(s.variants[i])
             {
                 Shader *v = s.variants[i][j];
-                if((v->reusevs && v->reusevs->type&SHADER_INVALID) || 
-                   (v->reuseps && v->reuseps->type&SHADER_INVALID) ||
+                if((v->reusevs && v->reusevs->type==SHADER_INVALID) || 
+                   (v->reuseps && v->reuseps->type==SHADER_INVALID) ||
                    !v->compile())
                     v->cleanup(true);
             }
         }
-        if(s.forced && !s.detailshader) s.fixdetailshader();
-    });
+    );
 }
 
 void setupblurkernel(int radius, float sigma, float *weights, float *offsets)
 {
     if(radius<1 || radius>MAXBLURRADIUS) return;
     sigma *= 2*radius;
-    float total = 1.0f/sigma;
-    weights[0] = total;
-    offsets[0] = 0;
+    float total = 1.0f/sigma, lastoffset = 0;
+    weights[0] = 1.0f/sigma;
     // rely on bilinear filtering to sample 2 pixels at once
     // transforms a*X + b*Y into (u+v)*[X*u/(u+v) + Y*(1 - u/(u+v))]
     loopi(radius)
@@ -1865,7 +1670,8 @@ void setupblurkernel(int radius, float sigma, float *weights, float *offsets)
               scale = weight1 + weight2,
               offset = 2*i+1 + weight2 / scale;
         weights[i+1] = scale;
-        offsets[i+1] = offset;
+        offsets[i+1] = offset - lastoffset;
+        lastoffset = offset;
         total += 2*scale;
     }
     loopi(radius+1) weights[i] /= total;
@@ -1880,24 +1686,21 @@ void setblurshader(int pass, int size, int radius, float *weights, float *offset
     Shader *&s = (target == GL_TEXTURE_RECTANGLE_ARB ? blurrectshader : blurshader)[radius-1][pass];
     if(!s)
     {
-        defformatstring(name)("blur%c%d%s", 'x'+pass, radius, target == GL_TEXTURE_RECTANGLE_ARB ? "rect" : "");
+        s_sprintfd(name)("blur%c%d%s", 'x'+pass, radius, target == GL_TEXTURE_RECTANGLE_ARB ? "rect" : "");
         s = lookupshaderbyname(name);
     }
     s->set();
     setlocalparamfv("weights", SHPARAM_PIXEL, 0, weights);
     setlocalparamfv("weights2", SHPARAM_PIXEL, 2, &weights[4]);
-    setlocalparamf("offsets", SHPARAM_VERTEX, 1,
+    setlocalparamf("offsets", SHPARAM_PIXEL, 1,
         pass==0 ? offsets[1]/size : offsets[0]/size,
         pass==1 ? offsets[1]/size : offsets[0]/size,
-        (offsets[2] - offsets[1])/size,
-        (offsets[3] - offsets[2])/size);
-    loopk(4)
-    {
-        static const char *names[4] = { "offset4", "offset5", "offset6", "offset7" };
-        setlocalparamf(names[k], SHPARAM_PIXEL, 3+k,
-            pass==0 ? offsets[4+k]/size : offsets[0]/size,
-            pass==1 ? offsets[4+k]/size : offsets[0]/size,
-            0, 0);
-    }
+        offsets[2]/size,
+        offsets[3]/size);
+    setlocalparamf("offsets2", SHPARAM_PIXEL, 3,
+        offsets[4]/size,
+        offsets[5]/size,
+        offsets[6]/size,
+        offsets[7]/size);
 }
 
diff --git a/engine/shadowmap.cpp b/engine/shadowmap.cpp
index 296bf79..0b3b7c2 100644
--- a/engine/shadowmap.cpp
+++ b/engine/shadowmap.cpp
@@ -1,3 +1,4 @@
+#include "pch.h"
 #include "engine.h"
 #include "rendertarget.h"
 
@@ -7,17 +8,10 @@ extern void cleanshadowmap();
 VARFP(shadowmapsize, 7, 9, 11, cleanshadowmap());
 VARP(shadowmapradius, 64, 96, 256);
 VAR(shadowmapheight, 0, 32, 128);
-VARP(ffshadowmapdist, 128, 1024, 4096);
 VARP(shadowmapdist, 128, 256, 512);
 VARFP(fpshadowmap, 0, 0, 1, cleanshadowmap());
 VARFP(shadowmapprecision, 0, 0, 1, cleanshadowmap());
-bvec shadowmapambientcolor(0, 0, 0);
-HVARFR(shadowmapambient, 0, 0, 0xFFFFFF,
-{
-    if(shadowmapambient <= 255) shadowmapambient |= (shadowmapambient<<8) | (shadowmapambient<<16);
-    shadowmapambientcolor = bvec((shadowmapambient>>16)&0xFF, (shadowmapambient>>8)&0xFF, shadowmapambient&0xFF);
-});
-VARP(shadowmapintensity, 0, 40, 100);
+VARR(shadowmapambient, 0, 0, 0xFFFFFF);
 
 VARP(blurshadowmap, 0, 1, 3);
 VARP(blursmsigma, 1, 100, 200);
@@ -26,7 +20,6 @@ VARP(blursmsigma, 1, 100, 200);
 
 vec shadowoffset(0, 0, 0), shadowfocus(0, 0, 0), shadowdir(0, SHADOWSKEW, 1);
 VAR(shadowmapcasters, 1, 0, 0);
-float shadowmapmaxz = 0;
 
 void setshadowdir(int angle)
 {
@@ -41,7 +34,7 @@ void guessshadowdir()
     if(shadowmapangle) return;
     vec lightpos(0, 0, 0), casterpos(0, 0, 0);
     int numlights = 0, numcasters = 0;
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
@@ -78,7 +71,7 @@ void guessshadowdir()
 
 bool shadowmapping = false;
 
-static glmatrixf shadowmapmatrix;
+static GLdouble shadowmapprojection[16], shadowmapmodelview[16];
 
 VARP(shadowmapbias, 0, 5, 1024);
 VARP(shadowmappeelbias, 0, 20, 1024);
@@ -87,25 +80,14 @@ VAR(smoothshadowmappeel, 1, 0, 0);
 
 static struct shadowmaptexture : rendertarget
 {
-    GLenum attachment() const
-    {
-        return renderpath==R_FIXEDFUNCTION ? GL_DEPTH_ATTACHMENT_EXT : GL_COLOR_ATTACHMENT0_EXT;
-    }
-
     const GLenum *colorformats() const
     {
-        if(renderpath==R_FIXEDFUNCTION) 
-        {
-            static const GLenum depthtexfmts[] = { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT32, GL_FALSE };
-            return depthtexfmts;
-        }
-       
-        static const GLenum rgbfmts[] = { GL_RGB, GL_RGB8, GL_FALSE }, rgbafmts[] = { GL_RGBA16F_ARB, GL_RGBA16, GL_RGBA, GL_RGBA8, GL_FALSE };
-        return hasFBO ? &rgbafmts[fpshadowmap && hasTF ? 0 : (shadowmapprecision ? 1 : 2)] : rgbfmts;
+        static const GLenum colorfmts[] = { GL_RGBA16F_ARB, GL_RGBA16, GL_RGBA, GL_RGBA8, GL_FALSE };
+        int offset = fpshadowmap && hasTF && hasFBO ? 0 : (shadowmapprecision && hasFBO ? 1 : 2);
+        return &colorfmts[offset];
     }
 
-    bool filter() const { return renderpath!=R_FIXEDFUNCTION || hasNVPCF; }
-    bool swaptexs() const { return renderpath!=R_FIXEDFUNCTION; }
+    bool swaptexs() const { return true; }
 
     bool scissorblur(int &x, int &y, int &w, int &h)
     {
@@ -126,14 +108,8 @@ static struct shadowmaptexture : rendertarget
 
     void doclear()
     {
-        if(!hasFBO && rtscissor)
-        {
-            glEnable(GL_SCISSOR_TEST);
-            glScissor(screen->w-vieww, screen->h-viewh, vieww, viewh);
-        }
         glClearColor(0, 0, 0, 0);
-        glClear(GL_DEPTH_BUFFER_BIT | (renderpath!=R_FIXEDFUNCTION ? GL_COLOR_BUFFER_BIT : 0));
-        if(!hasFBO && rtscissor) glDisable(GL_SCISSOR_TEST);
+        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
     }
 
     bool dorender()
@@ -144,9 +120,7 @@ static struct shadowmaptexture : rendertarget
         glMatrixMode(GL_PROJECTION);
         glPushMatrix();
         glLoadIdentity();
-        glOrtho(-shadowmapradius, shadowmapradius, -shadowmapradius, shadowmapradius, 
-            renderpath==R_FIXEDFUNCTION ? 0 : -shadowmapdist, 
-            renderpath==R_FIXEDFUNCTION ? ffshadowmapdist : shadowmapdist);
+        glOrtho(-shadowmapradius, shadowmapradius, -shadowmapradius, shadowmapradius, -shadowmapdist, shadowmapdist);
 
         glMatrixMode(GL_MODELVIEW);
 
@@ -178,34 +152,21 @@ static struct shadowmaptexture : rendertarget
         glTranslatef(-camera1->o.x, -camera1->o.y, -camera1->o.z);
         shadowfocus = camera1->o;
         shadowfocus.add(dir);
-        shadowfocus.add(vec(shadowdir).mul(shadowmapheight));
+        shadowfocus.add(vec(-shadowdir.x, -shadowdir.y, 1).mul(shadowmapheight));
         shadowfocus.add(dirx.mul(shadowoffset.x));
         shadowfocus.add(diry.mul(shadowoffset.y));
 
-        glmatrixf proj, mv;
-        glGetFloatv(GL_PROJECTION_MATRIX, proj.v);
-        glGetFloatv(GL_MODELVIEW_MATRIX, mv.v);
-        shadowmapmatrix.mul(proj, mv);
-        if(renderpath==R_FIXEDFUNCTION) shadowmapmatrix.projective();
-        else shadowmapmatrix.projective(-1, 1-shadowmapbias/float(shadowmapdist));
-
-        glColor3f(0, 0, 0);
-        glDisable(GL_TEXTURE_2D);
+        glGetDoublev(GL_PROJECTION_MATRIX, shadowmapprojection);
+        glGetDoublev(GL_MODELVIEW_MATRIX, shadowmapmodelview);
 
-        if(renderpath!=R_FIXEDFUNCTION) setenvparamf("shadowmapbias", SHPARAM_VERTEX, 0, -shadowmapbias/float(shadowmapdist), 1 - (shadowmapbias + (smoothshadowmappeel ? 0 : shadowmappeelbias))/float(shadowmapdist));
-        else glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+        setenvparamf("shadowmapbias", SHPARAM_VERTEX, 0, -shadowmapbias/float(shadowmapdist), 1 - (shadowmapbias + (smoothshadowmappeel ? 0 : shadowmappeelbias))/float(shadowmapdist));
 
         shadowmapcasters = 0;
-        shadowmapmaxz = shadowfocus.z - shadowmapdist;
         shadowmapping = true;
         rendergame();
         shadowmapping = false;
-        shadowmapmaxz = min(shadowmapmaxz, shadowfocus.z);
 
-        glEnable(GL_TEXTURE_2D);
-
-        if(renderpath==R_FIXEDFUNCTION) glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
-        else if(shadowmapcasters && smdepthpeel) 
+        if(shadowmapcasters && smdepthpeel) 
         {
             int sx, sy, sw, sh;
             bool scissoring = rtscissor && scissorblur(sx, sy, sw, sh) && sw > 0 && sh > 0;
@@ -250,66 +211,6 @@ void cleanshadowmap()
     shadowmaptex.cleanup(true);
 }
 
-VAR(ffsmscissor, 0, 1, 1);
-
-static void calcscissorbox()
-{
-    int smx, smy, smw, smh;
-    shadowmaptex.scissorblur(smx, smy, smw, smh);
-
-    vec forward, right;
-    vecfromyawpitch(camera1->yaw, 0, -1, 0, forward);
-    vecfromyawpitch(camera1->yaw, 0, 0, -1, right);
-    forward.mul(shadowmapradius*2.0f/shadowmaptex.viewh);
-    right.mul(shadowmapradius*2.0f/shadowmaptex.vieww);
-
-    vec bottom(shadowfocus);
-    bottom.sub(vec(shadowdir).mul(shadowmapdist));
-    bottom.add(vec(forward).mul(smy - shadowmaptex.viewh/2)).add(vec(right).mul(smx - shadowmaptex.vieww/2));
-    vec top(bottom);
-    top.add(vec(shadowdir).mul(shadowmapmaxz - (shadowfocus.z - shadowmapdist)));
-   
-    vec4 v[8];
-    float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
-    loopi(8)
-    {
-        vec c = i&4 ? top : bottom;
-        if(i&1) c.add(vec(right).mul(smw));
-        if(i&2) c.add(vec(forward).mul(smh));
-        if(reflecting) c.z = 2*reflectz - c.z;
-        vec4 &p = v[i];
-        mvpmatrix.transform(c, p);
-        if(p.z >= 0)
-        {
-            float x = p.x / p.w, y = p.y / p.w;
-            sx1 = min(sx1, x);
-            sy1 = min(sy1, y);
-            sx2 = max(sx2, x);
-            sy2 = max(sy2, y);
-        }
-    }
-    if(sx1 >= sx2 || sy1 >= sy2) return;
-    loopi(8)
-    {
-        const vec4 &p = v[i];
-        if(p.z >= 0) continue;
-        loopj(3)
-        {
-            const vec4 &o = v[i^(1<<j)];
-            if(o.z <= 0) continue;
-            float t = p.z/(p.z - o.z),
-                  w = p.w + t*(o.w - p.w),
-                  x = (p.x + t*(o.x - p.x))/w,
-                  y = (p.y + t*(o.y - p.y))/w;
-            sx1 = min(sx1, x);
-            sy1 = min(sy1, y);
-            sx2 = max(sx2, x);
-            sy2 = max(sy2, y);
-        }
-    }
-    pushscissor(sx1, sy1, sx2, sy2);
-}
-
 void calcshadowmapbb(const vec &o, float xyrad, float zrad, float &x1, float &y1, float &x2, float &y2)
 {
     vec skewdir(shadowdir);
@@ -338,8 +239,6 @@ bool addshadowmapcaster(const vec &o, float xyrad, float zrad)
 {
     if(o.z + zrad <= shadowfocus.z - shadowmapdist || o.z - zrad >= shadowfocus.z) return false;
 
-    shadowmapmaxz = max(shadowmapmaxz, o.z + zrad);
-
     float x1, y1, x2, y2;
     calcshadowmapbb(o, xyrad, zrad, x1, y1, x2, y2);
 
@@ -351,9 +250,9 @@ bool addshadowmapcaster(const vec &o, float xyrad, float zrad)
 
 bool isshadowmapreceiver(vtxarray *va)
 {
-    if(!shadowmap || !shadowmapcasters) return false;
+    if(!shadowmap || renderpath==R_FIXEDFUNCTION || !shadowmapcasters) return false;
 
-    if(va->shadowmapmax.z <= shadowfocus.z - shadowmapdist || va->shadowmapmin.z >= shadowmapmaxz) return false;
+    if(va->shadowmapmax.z <= shadowfocus.z - shadowmapdist || va->shadowmapmin.z >= shadowfocus.z) return false;
 
     float xyrad = SQRT2*0.5f*max(va->shadowmapmax.x-va->shadowmapmin.x, va->shadowmapmax.y-va->shadowmapmin.y),
           zrad = 0.5f*(va->shadowmapmax.z-va->shadowmapmin.z),
@@ -369,10 +268,10 @@ bool isshadowmapreceiver(vtxarray *va)
 #if 0
     // cheaper inexact test
     float dz = va->o.z + va->size/2 - shadowfocus.z;
-    float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y;
+    float cx = shadowfocus.x - dz*shadowdir.x, cy = shadowfocus.y - dz*shadowdir.y;
     float skew = va->size/2*SHADOWSKEW;
     if(!shadowmap || !shadowmaptex ||
-       va->o.z + va->size <= shadowfocus.z - shadowmapdist || va->o.z >= shadowmapmaxz ||
+       va->o.z + va->size <= shadowfocus.z - shadowmapdist || va->o.z >= shadowfocus.z ||
        va->o.x + va->size <= cx - shadowmapradius-skew || va->o.x >= cx + shadowmapradius+skew || 
        va->o.y + va->size <= cy - shadowmapradius-skew || va->o.y >= cy + shadowmapradius+skew) 
         return false;
@@ -384,7 +283,7 @@ bool isshadowmapcaster(const vec &o, float rad)
 {
     // cheaper inexact test
     float dz = o.z - shadowfocus.z;
-    float cx = shadowfocus.x + dz*shadowdir.x, cy = shadowfocus.y + dz*shadowdir.y;
+    float cx = shadowfocus.x - dz*shadowdir.x, cy = shadowfocus.y - dz*shadowdir.y;
     float skew = rad*SHADOWSKEW;
     if(!shadowmapping ||
        o.z + rad <= shadowfocus.z - shadowmapdist || o.z - rad >= shadowfocus.z ||
@@ -398,77 +297,36 @@ void pushshadowmap()
 {
     if(!shadowmap || !shadowmaptex.rendertex) return;
 
-    if(renderpath==R_FIXEDFUNCTION)
-    {
-        static GLfloat texgenS[4] = { 1, 0, 0, 0 },
-                       texgenT[4] = { 0, 1, 0, 0 },
-                       texgenR[4] = { 0, 0, 1, 0 };
-
-        glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex);
-
-        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-        glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS);
-        glEnable(GL_TEXTURE_GEN_S);
-
-        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-        glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT);
-        glEnable(GL_TEXTURE_GEN_T);
-
-        glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-        glTexGenfv(GL_R, GL_OBJECT_PLANE, texgenR);
-        glEnable(GL_TEXTURE_GEN_R);
-
-        // intel driver bug workaround: when R texgen is enabled, it uses the value of Q, even if not enabled!
-        // MUST set Q with glTexCoord4f, glTexCoord3f does not work
-        glTexCoord4f(0, 0, 0, 1);
-
-        if(hasDT && hasSH)
-        {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_GEQUAL);
-            glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);
-        }
-        else
-        {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_SGIX, GL_TRUE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_OPERATOR_SGIX, GL_TEXTURE_GEQUAL_R_SGIX);
-        }
-
-        glColor3f(shadowmapintensity/100.0f, shadowmapintensity/100.0f, shadowmapintensity/100.0f);
-
-        if(ffsmscissor) calcscissorbox();
-        return;
-    }
-
     glActiveTexture_(GL_TEXTURE7_ARB);
+    glEnable(GL_TEXTURE_2D);
     glBindTexture(GL_TEXTURE_2D, shadowmaptex.rendertex);
 
     glActiveTexture_(GL_TEXTURE2_ARB);
+    glEnable(GL_TEXTURE_2D);
     glMatrixMode(GL_TEXTURE);
-    glLoadMatrixf(shadowmapmatrix.v);
+    glLoadIdentity();
+    glTranslatef(0.5f, 0.5f, 1-shadowmapbias/float(shadowmapdist));
+    glScalef(0.5f, 0.5f, -1);
+    glMultMatrixd(shadowmapprojection);
+    glMultMatrixd(shadowmapmodelview);
     glPushMatrix();
     glMatrixMode(GL_MODELVIEW);
-
     glActiveTexture_(GL_TEXTURE0_ARB);
     glClientActiveTexture_(GL_TEXTURE0_ARB);
 
     float r, g, b;
 	if(!shadowmapambient)
 	{
-		if(skylightcolor[0] || skylightcolor[1] || skylightcolor[2])
+		if(hdr.skylight[0] || hdr.skylight[1] || hdr.skylight[2])
 		{
-			r = max(25.0f, 0.4f*ambientcolor[0] + 0.6f*max(ambientcolor[0], skylightcolor[0]));
-			g = max(25.0f, 0.4f*ambientcolor[1] + 0.6f*max(ambientcolor[1], skylightcolor[1]));
-			b = max(25.0f, 0.4f*ambientcolor[2] + 0.6f*max(ambientcolor[2], skylightcolor[2]));
+			r = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[0]));
+			g = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[1]));
+			b = max(25.0f, 0.4f*hdr.ambient + 0.6f*max(hdr.ambient, hdr.skylight[2]));
 		}
-		else 
-        {
-            r = max(25.0f, 2.0f*ambientcolor[0]);
-            g = max(25.0f, 2.0f*ambientcolor[1]);
-            b = max(25.0f, 2.0f*ambientcolor[2]);
-        }
+		else r = g = b = max(25.0f, 2.0f*hdr.ambient);
 	}
-    else { r = shadowmapambientcolor[0]; g = shadowmapambientcolor[1]; b = shadowmapambientcolor[2]; }
+    else if(shadowmapambient<=255) r = g = b = shadowmapambient;
+    else { r = (shadowmapambient>>16)&0xFF; g = (shadowmapambient>>8)&0xFF; b = shadowmapambient&0xFF; }
     setenvparamf("shadowmapambient", SHPARAM_PIXEL, 7, r/255.0f, g/255.0f, b/255.0f);
 }
 
@@ -476,63 +334,39 @@ void adjustshadowmatrix(const ivec &o, float scale)
 {
     if(!shadowmap || !shadowmaptex.rendertex) return;
 
-    if(renderpath==R_FIXEDFUNCTION)
-    {
-        const GLfloat *v = shadowmapmatrix.v;
-        GLfloat texgenS[4] = { v[0]*scale, v[4]*scale, v[8]*scale, v[0]*o.x + v[4]*o.y + v[8]*o.z + v[12] },
-                texgenT[4] = { v[1]*scale, v[5]*scale, v[9]*scale, v[1]*o.x + v[5]*o.y + v[9]*o.z + v[13] },
-                texgenR[4] = { v[2]*scale, v[6]*scale, v[10]*scale, v[2]*o.x + v[6]*o.y + v[10]*o.z + v[14] };
-        glTexGenfv(GL_S, GL_OBJECT_PLANE, texgenS);
-        glTexGenfv(GL_T, GL_OBJECT_PLANE, texgenT);
-        glTexGenfv(GL_R, GL_OBJECT_PLANE, texgenR);
-    }
-    else
-    {
-        glActiveTexture_(GL_TEXTURE2_ARB);
-        glMatrixMode(GL_TEXTURE);
-        glPopMatrix();
-        glPushMatrix();
-        glTranslatef(o.x, o.y, o.z);
-        glScalef(scale, scale, scale);
-        glMatrixMode(GL_MODELVIEW);
-        glActiveTexture_(GL_TEXTURE0_ARB);
-    }
+    glActiveTexture_(GL_TEXTURE2_ARB);
+    glMatrixMode(GL_TEXTURE);
+    glPopMatrix();
+    glPushMatrix();
+    glTranslatef(o.x, o.y, o.z);
+    glScalef(scale, scale, scale);
+    glMatrixMode(GL_MODELVIEW);
+    glActiveTexture_(GL_TEXTURE0_ARB);
 }
 
 void popshadowmap()
 {
     if(!shadowmap || !shadowmaptex.rendertex) return;
 
-    if(renderpath!=R_FIXEDFUNCTION) 
-    {
-        glActiveTexture_(GL_TEXTURE2_ARB);
-        glMatrixMode(GL_TEXTURE);
-        glPopMatrix();
-        glMatrixMode(GL_MODELVIEW);
-
-        glActiveTexture_(GL_TEXTURE0_ARB);
-    }
-    else
-    {
-        popscissor();
+    glActiveTexture_(GL_TEXTURE7_ARB);
+    glDisable(GL_TEXTURE_2D);
 
-        glDisable(GL_TEXTURE_GEN_S);
-        glDisable(GL_TEXTURE_GEN_T);
-        glDisable(GL_TEXTURE_GEN_R);
+    glActiveTexture_(GL_TEXTURE2_ARB);
+    glMatrixMode(GL_TEXTURE);
+    glPopMatrix();
+    glMatrixMode(GL_MODELVIEW);
 
-        if(hasDT && hasSH) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE);
-        else glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_SGIX, GL_FALSE);
-    }
+    glActiveTexture_(GL_TEXTURE0_ARB);
 }
 
 void rendershadowmap()
 {
-    if(!shadowmap || (renderpath==R_FIXEDFUNCTION && (!hasSGIDT || !hasSGISH))) return;
+    if(!shadowmap || renderpath==R_FIXEDFUNCTION) return;
 
     // Apple/ATI bug - fixed-function fog state can force software fallback even when fragment program is enabled
-    if(renderpath!=R_FIXEDFUNCTION || !fogging) glDisable(GL_FOG); 
-    shadowmaptex.render(1<<shadowmapsize, 1<<shadowmapsize, renderpath!=R_FIXEDFUNCTION ? blurshadowmap : 0, blursmsigma/100.0f);
-    if(renderpath!=R_FIXEDFUNCTION || !fogging) glEnable(GL_FOG);
+    glDisable(GL_FOG); 
+    shadowmaptex.render(1<<shadowmapsize, 1<<shadowmapsize, blurshadowmap, blursmsigma/100.0f);
+    glEnable(GL_FOG);
 }
 
 VAR(debugsm, 0, 0, 1);
diff --git a/engine/skelmodel.h b/engine/skelmodel.h
index ab54005..d778d81 100644
--- a/engine/skelmodel.h
+++ b/engine/skelmodel.h
@@ -42,7 +42,7 @@ struct skelmodel : animmodel
 
         int size() const
         {
-            int i = 1;
+            int i = 0;
             while(i < 4 && weights[i]) i++;
             return i;
         }
@@ -63,7 +63,6 @@ struct skelmodel : animmodel
 
         int addweight(int sorted, float weight, int bone)
         {
-            if(weight <= 1e-3f) return sorted;
             loopk(sorted) if(weight > weights[k])
             {
                 for(int l = min(sorted-1, 2); l >= k; l--)
@@ -84,7 +83,7 @@ struct skelmodel : animmodel
         void finalize(int sorted)
         {
             loopj(4-sorted) { weights[sorted+j] = 0; bones[sorted+j] = 0; }
-            if(sorted <= 0) return;
+
             float total = 0;
             loopj(sorted) total += weights[j];
             total = 1.0f/total;
@@ -98,20 +97,11 @@ struct skelmodel : animmodel
                 v.weights[0] = 255;
                 loopk(3) v.weights[k+1] = 0;
                 v.bones[0] = (matskel ? 3 : 2)*interpindex;
-                loopk(3) v.bones[k+1] = v.bones[0];
+                loopk(3) v.bones[k+1] = 0;
             }
             else
             {
-                int total = 0;
-                loopk(4) total += (v.weights[k] = uchar(weights[k]*255));
-                while(total > 255)
-                {
-                    loopk(4) if(v.weights[k] > 0 && total > 255) { v.weights[k]--; total--; } 
-                }
-                while(total < 255)
-                {
-                    loopk(4) if(v.weights[k] < 255 && total < 255) { v.weights[k]++; total++; }
-                }
+                loopk(4) v.weights[k] = uchar(weights[k]*255);
                 loopk(4) v.bones[k] = (matskel ? 3 : 2)*interpbones[k];
             }
         }
@@ -124,9 +114,8 @@ struct skelmodel : animmodel
         float pitch;
         int millis;
         uchar *partmask;
-        ragdolldata *ragdoll;
 
-        animcacheentry() : ragdoll(NULL)
+        animcacheentry()
         {
             loopk(MAXANIMPARTS) as[k].cur.fr1 = as[k].prev.fr1 = -1;
         }
@@ -134,7 +123,7 @@ struct skelmodel : animmodel
         bool operator==(const animcacheentry &c) const
         {
             loopi(MAXANIMPARTS) if(as[i]!=c.as[i]) return false;
-            return pitch==c.pitch && partmask==c.partmask && ragdoll==c.ragdoll && (!ragdoll || min(millis, c.millis) >= ragdoll->lastmove);
+            return pitch==c.pitch && partmask==c.partmask;
         }
     };
 
@@ -174,7 +163,7 @@ struct skelmodel : animmodel
         int voffset, eoffset, elen;
         ushort minvert, maxvert;
 
-        skelmesh() : verts(NULL), bumpverts(NULL), tris(NULL), numverts(0), numtris(0), maxweights(0)
+        skelmesh() : verts(NULL), bumpverts(NULL), tris(0), numverts(0), numtris(0), maxweights(0)
         {
         }
 
@@ -185,75 +174,37 @@ struct skelmodel : animmodel
             DELETEA(tris);
         }
 
-        int addblendcombo(const blendcombo &c)
-        {
-            maxweights = max(maxweights, c.size());
-            return ((skelmeshgroup *)group)->addblendcombo(c);
-        }
+        virtual mesh *allocate() { return new skelmesh; }
 
-        void smoothnorms(float limit = 0, bool areaweight = true)
+        mesh *copy()
         {
-            hashtable<vec, int> share;
-            int *next = new int[numverts];
-            memset(next, -1, numverts*sizeof(int));
-            loopi(numverts) 
+            skelmesh &m = *(skelmesh *)mesh::copy();
+            m.numverts = numverts;
+            m.verts = new vert[numverts];
+            memcpy(m.verts, verts, numverts*sizeof(vert));
+            m.numtris = numtris;
+            m.tris = new tri[numtris];
+            memcpy(m.tris, tris, numtris*sizeof(tri));
+            m.maxweights = maxweights;
+            if(bumpverts)
             {
-                vert &v = verts[i];
-                v.norm = vec(0, 0, 0);
-                int idx = share.access(v.pos, i);
-                if(idx != i) { next[i] = next[idx]; next[idx] = i; }
+                m.bumpverts = new bumpvert[numverts];
+                memcpy(m.bumpverts, bumpverts, numverts*sizeof(bumpvert));
             }
-            loopi(numtris)
-            {
-                tri &t = tris[i];
-                vert &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]];
-                vec norm;
-                norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos));
-                if(!areaweight) norm.normalize();
-                v1.norm.add(norm);
-                v2.norm.add(norm);
-                v3.norm.add(norm);
-            }
-            vec *norms = new vec[numverts];
-            memset(norms, 0, numverts*sizeof(vec));
-            loopi(numverts)
-            {
-                vert &v = verts[i];
-                norms[i].add(v.norm);
-                if(next[i] >= 0)
-                {
-                    float vlimit = limit*v.norm.magnitude();
-                    for(int j = next[i]; j >= 0; j = next[j])
-                    {
-                        vert &o = verts[j];
-                        if(v.norm.dot(o.norm) >= vlimit*o.norm.magnitude()) 
-                        {
-                            norms[i].add(o.norm);
-                            norms[j].add(v.norm);
-                        }
-                    }
-                }
-            } 
-            loopi(numverts) verts[i].norm = norms[i].normalize();
-            delete[] next;
-            delete[] norms;
+            else m.bumpverts = NULL;
+            return &m;
         }
 
-        void buildnorms(bool areaweight = true)
+        int addblendcombo(const blendcombo &c)
         {
-            loopi(numverts) verts[i].norm = vec(0, 0, 0);
-            loopi(numtris)
-            {
-                tri &t = tris[i];
-                vert &v1 = verts[t.vert[0]], &v2 = verts[t.vert[1]], &v3 = verts[t.vert[2]];
-                vec norm;
-                norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos));
-                if(!areaweight) norm.normalize();
-                v1.norm.add(norm);
-                v2.norm.add(norm);
-                v3.norm.add(norm);
-            }
-            loopi(numverts) verts[i].norm.normalize();
+            maxweights = max(maxweights, c.size());
+            return ((skelmeshgroup *)group)->addblendcombo(c);
+        }
+
+        void scaleverts(const vec &transdiff, float scalediff)
+        {
+            if(((skelmeshgroup *)group)->skel->numframes) loopi(numverts) verts[i].pos.mul(scalediff);
+            else loopi(numverts) verts[i].pos.add(transdiff).mul(scalediff);
         }
 
         void calctangents(bool areaweight = true)
@@ -296,7 +247,7 @@ struct skelmodel : animmodel
             loopi(numverts)
             {
                 const vec &n = verts[i].norm,
-                          &t = tangent[i],
+                          &t = tangent[i], 
                           &bt = bitangent[i];
                 bumpvert &bv = bumpverts[i];
                 (bv.tangent = t).sub(vec(n).mul(n.dot(t))).normalize();
@@ -323,7 +274,7 @@ struct skelmodel : animmodel
             loopj(numtris)
             {
                 BIH::tri &t = out[noclip ? 1 : 0].add();
-                t.tex = tex->bpp==4 ? tex : NULL;
+                t.tex = tex->bpp==32 ? tex : NULL;
                 vert &av = verts[tris[j].vert[0]],
                      &bv = verts[tris[j].vert[1]],
                      &cv = verts[tris[j].vert[2]];
@@ -410,20 +361,7 @@ struct skelmodel : animmodel
         {
             voffset = offset;
             eoffset = idxs.length();
-            if(((skelmeshgroup *)group)->skel->numframes)
-            {
-                loopi(numverts)
-                {
-                    vert &v = verts[i];
-                    assignvert(vverts.add(), i, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
-                }
-                loopi(numtris) loopj(3) idxs.add(voffset + tris[i].vert[j]);
-                elen = idxs.length()-eoffset;
-                minvert = voffset;
-                maxvert = voffset + numverts-1;
-                return numverts;
-            }
-            minvert = 0xFFFF;
+            if(!((skelmeshgroup *)group)->skel->numframes) minvert = 0xFFFF;
             loopi(numtris)
             {
                 tri &t = tris[i];
@@ -431,9 +369,12 @@ struct skelmodel : animmodel
                 {
                     int index = t.vert[j];
                     vert &v = verts[index];
-                    loopvk(vverts)
+                    if(!((skelmeshgroup *)group)->skel->numframes) 
                     {
-                        if(comparevert(vverts[k], index, v)) { minvert = min(minvert, (ushort)k); idxs.add((ushort)k); goto found; }
+                        loopvk(vverts)
+                        {
+                            if(comparevert(vverts[k], index, v)) { minvert = min(minvert, (ushort)k); idxs.add((ushort)k); goto found; }
+                        }
                     }
                     idxs.add(vverts.length());
                     assignvert(vverts.add(), index, v, ((skelmeshgroup *)group)->blendcombos[v.blend]);
@@ -441,9 +382,18 @@ struct skelmodel : animmodel
                 }
             }
             elen = idxs.length()-eoffset;
-            minvert = min(minvert, ushort(voffset));
-            maxvert = max(minvert, ushort(vverts.length()-1));
-            return vverts.length()-voffset;
+            if(((skelmeshgroup *)group)->skel->numframes)
+            {
+                minvert = voffset;
+                maxvert = voffset + numverts-1;
+                return numverts;
+            }
+            else
+            {
+                minvert = min(minvert, ushort(voffset));
+                maxvert = max(minvert, ushort(vverts.length()-1));
+                return vverts.length()-voffset;
+            }
         }
 
         int genvbo(vector<ushort> &idxs, int offset)
@@ -550,13 +500,13 @@ struct skelmodel : animmodel
             skelmeshgroup *g = (skelmeshgroup *)group;
             if(glaring)
             {
-                if(!g->skel->usegpuskel) s->setvariant(0, 2);
-                else if(g->skel->usematskel) s->setvariant(min(maxweights, g->vweights), 2);
-                else s->setvariant(min(maxweights, g->vweights)-1, 3);
+                if(!g->skel->usegpuskel) s->variant(0, 2)->set();
+                else if(g->skel->usematskel) s->variant(min(maxweights, g->vweights), 2)->set();
+                else s->variant(maxweights-1, 3)->set();
             }
             else if(!g->skel->usegpuskel) s->set();
-            else if(g->skel->usematskel) s->setvariant(min(maxweights, g->vweights)-1, 0);
-            else s->setvariant(min(maxweights, g->vweights)-1, 1);
+            else if(g->skel->usematskel) s->variant(min(maxweights, g->vweights)-1, 0)->set();
+            else s->variant(maxweights-1, 1)->set();
         }
 
         void render(const animstate *as, skin &s, vbocacheentry &vc)
@@ -585,10 +535,10 @@ struct skelmodel : animmodel
 
                 if(s.tangents())
                 {
-                    if(!enabletangents || lastxbuf!=lastvbuf)
+                    if(!enabletangents || lastnbuf!=lastvbuf)
                     {
                         if(!enabletangents) glEnableVertexAttribArray_(1);
-                        if(lastxbuf!=lastvbuf)
+                        if(lastnbuf!=lastvbuf)
                         {
                             if(((skelmeshgroup *)group)->vertsize==sizeof(vvertbumpw))
                             {
@@ -601,7 +551,7 @@ struct skelmodel : animmodel
                                 glVertexAttribPointer_(1, 4, GL_FLOAT, GL_FALSE, ((skelmeshgroup *)group)->vertsize, &vverts->tangent.x);
                             }
                         }
-                        lastxbuf = lastvbuf;
+                        lastnbuf = lastvbuf;
                         enabletangents = true;
                     }
                 }
@@ -668,42 +618,33 @@ struct skelmodel : animmodel
     struct boneinfo
     {
         const char *name;
-        int parent, children, next, group, scheduled, interpindex, interpparent, ragdollindex;
+        int parent, children, next, interpindex, interpparent, interpgroup;
         float pitchscale, pitchoffset, pitchmin, pitchmax;
         dualquat base;
 
-        boneinfo() : name(NULL), parent(-1), children(-1), next(-1), group(INT_MAX), scheduled(-1), interpindex(-1), interpparent(-1), ragdollindex(-1), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {}
+        boneinfo() : name(NULL), parent(-1), children(-1), next(-1), interpindex(-1), interpparent(-1), interpgroup(0), pitchscale(0), pitchoffset(0), pitchmin(0), pitchmax(0) {}
         ~boneinfo()
         {
             DELETEA(name);
         }
     };
 
-    struct antipode
-    {
-        int parent, child;
-
-        antipode(int parent, int child) : parent(parent), child(child) {}
-    };
-
     struct skeleton
     {
         char *name;
         int shared;
         vector<skelmeshgroup *> users;
         boneinfo *bones;
-        int numbones, numinterpbones, numgpubones, numframes;
+        int numbones, numinterpbones, numgpubones, numframes, optimizedframes;
         dualquat *invbones, *framebones;
         matrix3x4 *matinvbones, *matframebones;
         vector<skelanimspec> skelanims;
         vector<tag> tags;
-        vector<antipode> antipodes;
-        ragdollskel *ragdoll;
 
         bool usegpuskel, usematskel;
         vector<skelcacheentry> skelcache;
 
-        skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), invbones(NULL), framebones(NULL), matinvbones(NULL), matframebones(NULL), ragdoll(NULL), usegpuskel(false), usematskel(false)
+        skeleton() : name(NULL), shared(0), bones(NULL), numbones(0), numinterpbones(0), numgpubones(0), numframes(0), optimizedframes(0), invbones(NULL), framebones(NULL), matinvbones(NULL), matframebones(NULL), usegpuskel(false), usematskel(false)
         {
         }
 
@@ -715,7 +656,6 @@ struct skelmodel : animmodel
             DELETEA(framebones);
             DELETEA(matinvbones);
             DELETEA(matframebones);
-            DELETEP(ragdoll);
             loopv(skelcache)
             {
                 DELETEA(skelcache[i].bdata);
@@ -762,40 +702,35 @@ struct skelmodel : animmodel
             return true;
         }
 
-        void calcantipodes()
+        skeleton *copy()
         {
-            antipodes.setsize(0);
-            vector<int> schedule;
-            loopi(numbones) 
+            skeleton &s = *new skeleton;
+            s.numbones = numbones;
+            s.numinterpbones = numinterpbones;
+            s.numgpubones = numgpubones;
+            s.numframes = numframes;
+            s.optimizedframes = optimizedframes;
+            s.bones = new boneinfo[numbones];
+            memcpy(s.bones, bones, numbones*sizeof(boneinfo));
+            loopi(numbones) if(bones[i].name) s.bones[i].name = newstring(bones[i].name);
+            if(numframes)
             {
-                if(bones[i].group >= numbones) 
-                {
-                    bones[i].scheduled = schedule.length();
-                    schedule.add(i);
-                }
-                else bones[i].scheduled = -1;
+                s.framebones = new dualquat[numframes*numbones];
+                memcpy(s.framebones, framebones, numframes*numbones*sizeof(dualquat));
             }
-            loopv(schedule)
+            loopv(skelanims)
             {
-                int bone = schedule[i];
-                const boneinfo &info = bones[bone];
-                loopj(numbones) if(abs(bones[j].group) == bone && bones[j].scheduled < 0)
-                {
-                    antipodes.add(antipode(info.interpindex, bones[j].interpindex));
-                    bones[j].scheduled = schedule.length();
-                    schedule.add(j);
-                }
-                if(i + 1 == schedule.length())
-                {
-                    int conflict = INT_MAX;
-                    loopj(numbones) if(bones[j].group < numbones && bones[j].scheduled < 0) conflict = min(conflict, abs(bones[j].group));
-                    if(conflict < numbones)
-                    {
-                        bones[conflict].scheduled = schedule.length();
-                        schedule.add(conflict);
-                    }
-                }
+                skelanimspec &sa = s.addskelanim(skelanims[i].name);
+                sa.frame = skelanims[i].frame;
+                sa.range = skelanims[i].range;
+            }
+            loopv(tags)
+            {
+                tag &t = s.tags.add();
+                t.name = newstring(tags[i].name);
+                t.bone = tags[i].bone;
             }
+            return &s;
         }
 
         void remapbones()
@@ -804,7 +739,7 @@ struct skelmodel : animmodel
             {
                 boneinfo &info = bones[i];
                 info.interpindex = -1;
-                info.ragdollindex = -1;
+                info.interpgroup = i;
             }
             numgpubones = 0;
             loopv(users)
@@ -813,78 +748,83 @@ struct skelmodel : animmodel
                 loopvj(group->blendcombos)
                 {
                     blendcombo &c = group->blendcombos[j];
-                    loopk(4) 
+                    int group = c.bones[0];
+                    for(int k = 1; k < 4; k++) if(c.weights[k]) group = min(group, int(c.bones[k]));
+                    loopk(4) if(c.weights[k])
                     {
-                        if(!c.weights[k]) { c.interpbones[k] = k > 0 ? c.interpbones[k-1] : 0; continue; } 
                         boneinfo &info = bones[c.bones[k]];
-                        if(info.interpindex < 0) info.interpindex = numgpubones++;
+                        if(info.interpindex<0) info.interpindex = numgpubones++;
                         c.interpbones[k] = info.interpindex;
-                        if(info.group < 0) continue;
-                        loopl(4)
-                        {
-                            if(!c.weights[l]) break;
-                            if(l == k) continue;
-                            int parent = c.bones[l];
-                            if(info.parent == parent || (info.parent >= 0 && info.parent == bones[parent].parent)) { info.group = -info.parent; break; }
-                            if(info.group <= parent) continue;
-                            int child = c.bones[k];
-                            while(parent > child) parent = bones[parent].parent;
-                            if(parent != child) info.group = c.bones[l];
-                        }
+                        info.interpgroup = min(info.interpgroup, group);
                     }
                 }
             }
-            numinterpbones = numgpubones;
-            calcantipodes();
-            loopv(tags)
-            {
-                boneinfo &info = bones[tags[i].bone];
-                if(info.interpindex < 0) info.interpindex = numinterpbones++;
-            }
-            if(ragdoll)
+            loopi(numbones) 
             {
-                loopv(ragdoll->joints) 
-                {
-                    boneinfo &info = bones[ragdoll->joints[i].bone];
-                    if(info.interpindex < 0) info.interpindex = numinterpbones++;
-                    info.ragdollindex = i;
-                }
+                int group = bones[i].interpgroup;
+                bones[i].interpgroup = group < i ? bones[group].interpindex : -1;
             }
+            numinterpbones = numgpubones;
             loopi(numbones)
             {
                 boneinfo &info = bones[i];
-                if(info.interpindex < 0) continue;
-                for(int parent = info.parent; parent >= 0 && bones[parent].interpindex < 0; parent = bones[parent].parent)
-                    bones[parent].interpindex = numinterpbones++;
+                if(!info.pitchscale) continue;
+                if(info.interpindex < 0) info.interpindex = numinterpbones++;
+                if(info.parent >= 0 && bones[info.parent].interpindex < 0) bones[info.parent].interpindex = numinterpbones++;
+            }
+            loopv(tags)
+            {
+                boneinfo &info = bones[tags[i].bone];
+                if(info.interpindex < 0) info.interpindex = numinterpbones++;
             }
+        }
+
+        void compactbones()
+        {
             loopi(numbones)
             {
                 boneinfo &info = bones[i];
                 if(info.interpindex < 0) continue;
-                info.interpparent = info.parent >= 0 ? bones[info.parent].interpindex : -1;
+                int parent = info.parent;
+                while(parent >= 0 && bones[parent].interpindex < 0) parent = bones[parent].parent;
+                info.interpparent = parent >= 0 ? bones[parent].interpindex : -1;
             }
-            if(ragdoll)
+        }
+
+        void optimizeframes()
+        {
+            while(optimizedframes < numframes)
             {
+                dualquat *frame = &framebones[optimizedframes*numbones];
                 loopi(numbones)
                 {
                     boneinfo &info = bones[i];
-                    if(info.interpindex < 0 || info.ragdollindex >= 0) continue;
-                    for(int parent = info.parent; parent >= 0; parent = bones[parent].parent)
+                    if(info.interpindex < 0 || info.parent < 0 || bones[info.parent].interpindex >= 0) 
+                    {
+                        frame[i].fixantipodal(framebones[i]);
+                        continue;
+                    }
+                    dualquat d = frame[i];
+                    int parent = info.parent;
+                    while(parent >= 0 && bones[parent].interpindex < 0)
                     {
-                        if(bones[parent].ragdollindex >= 0) { ragdoll->addreljoint(i, bones[parent].ragdollindex); break; }
+                        d.mul(frame[parent], dualquat(d));
+                        parent = bones[parent].parent;
                     }
+                    d.normalize();
+                    d.fixantipodal(framebones[i]);
+                    frame[i] = d;
                 }
+                optimizedframes++;
             }
         }
 
         void optimize()
         {
-            DELETEA(invbones);
-            DELETEA(matinvbones);
-            DELETEA(matframebones);
             cleanup();
-            if(ragdoll) ragdoll->setup();
             remapbones();
+            compactbones();
+            optimizeframes();
         }
 
         void expandbonemask(uchar *expansion, int bone, int val)
@@ -926,6 +866,28 @@ struct skelmodel : animmodel
         int availgpubones() const { return (min(maxvpenvparams - reservevpparams, 256) - 10) / (matskel ? 3 : 2); }
         bool gpuaccelerate() const { return renderpath!=R_FIXEDFUNCTION && numframes && gpuskel && numgpubones<=availgpubones(); }
 
+        void scaletags(const vec &transdiff, float scalediff)
+        {
+            DELETEA(invbones);
+            DELETEA(matinvbones);
+            DELETEA(matframebones);
+            if(shared > 1) return;
+            loopi(numbones)
+            {
+                if(bones[i].parent < 0) bones[i].base.translate(transdiff);
+                bones[i].base.scale(scalediff);
+            }
+            loopi(numframes)
+            {
+                dualquat *frame = &framebones[i*numbones];
+                loopj(numbones) if(bones[j].interpindex >= 0)
+                {
+                    if(bones[j].interpparent < 0) frame[j].translate(transdiff);
+                    frame[j].scale(scalediff);
+                }
+            }
+        }
+
         void geninvbones()
         {
             if(invbones) return;
@@ -952,7 +914,6 @@ struct skelmodel : animmodel
                 loopi(numinterpbones) matinvbones[i] = invbones[i];
             }
             if(!sc.mdata) sc.mdata = new matrix3x4[numinterpbones];
-            if(lastsdata == sc.mdata) lastsdata = NULL;
             struct framedata
             {
                 matrix3x4 *fr1, *fr2, *pfr1, *pfr2;
@@ -980,16 +941,16 @@ struct skelmodel : animmodel
                     m.accumulate(f.pfr2[i], s.prev.t*(1-s.interp));
                 }
                 const boneinfo &b = bones[i];
-                if(b.interpparent<0) sc.mdata[b.interpindex] = m;
-                else sc.mdata[b.interpindex].mul(sc.mdata[b.interpparent], m);
                 if(b.pitchscale)
                 {
                     float angle = b.pitchscale*pitch + b.pitchoffset;
                     if(b.pitchmin || b.pitchmax) angle = max(b.pitchmin, min(b.pitchmax, angle));
-                    matrix3x3 rmat;
-                    rmat.rotate(angle*RAD, axis);
-                    sc.mdata[b.interpindex].mulorient(rmat);
+                    matrix3x4 rmat;
+                    rmat.rotate(angle*RAD, b.interpparent>=0 ? sc.mdata[b.interpparent].transposedtransformnormal(axis) : axis);
+                    m.mul(rmat, matrix3x4(m));
                 }
+                if(b.interpparent<0) sc.mdata[b.interpindex] = m;
+                else sc.mdata[b.interpindex].mul(sc.mdata[b.interpparent], m);
             }
             loopi(numinterpbones)
             {
@@ -1002,7 +963,6 @@ struct skelmodel : animmodel
         {
             if(!invbones) geninvbones();
             if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
-            if(lastsdata == sc.bdata) lastsdata = NULL;
             struct framedata
             {
                 dualquat *fr1, *fr2, *pfr1, *pfr2;
@@ -1030,14 +990,15 @@ struct skelmodel : animmodel
                     d.accumulate(f.pfr2[i], s.prev.t*(1-s.interp));
                 }
                 const boneinfo &b = bones[i];
-                if(b.interpparent<0) sc.bdata[b.interpindex] = d;
-                else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
                 if(b.pitchscale)
                 {
                     float angle = b.pitchscale*pitch + b.pitchoffset;
                     if(b.pitchmin || b.pitchmax) angle = max(b.pitchmin, min(b.pitchmax, angle));
-                    sc.bdata[b.interpindex].mulorient(quat(axis, angle*RAD));
+                    vec raxis = b.interpparent>=0 ? quat(sc.bdata[b.interpparent].real).invert().rotate(axis) : axis;
+                    d.mul(dualquat(quat(raxis, angle*RAD)), dualquat(d));
                 }
+                if(b.interpparent<0) sc.bdata[b.interpindex] = d;
+                else sc.bdata[b.interpindex].mul(sc.bdata[b.interpparent], d);
             }
             loopi(numinterpbones)
             {
@@ -1045,129 +1006,32 @@ struct skelmodel : animmodel
                 d.normalize();
                 d.mul(invbones[i]);
             }
-            loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
-        }
-
-        void initmatragdoll(ragdolldata &d, skelcacheentry &sc, part *p)
-        {
-            const matrix3x4 *mdata = sc.mdata;
-            loopv(ragdoll->joints)
-            {
-                const ragdollskel::joint &j = ragdoll->joints[i];
-                const boneinfo &b = bones[j.bone];
-                const matrix3x4 &m = mdata[b.interpindex];
-                loopk(3) if(j.vert[k] >= 0)
-                {
-                    int vert = j.vert[k];
-                    vec pos;
-                    matrixstack[matrixpos].transform(m.transform(ragdoll->verts[vert].pos).add(p->translate).mul(p->model->scale), pos);
-                    d.verts[vert].pos.add(pos);
-                }
-            }
-            loopv(ragdoll->reljoints)
-            {
-                const ragdollskel::reljoint &r = ragdoll->reljoints[i];
-                const ragdollskel::joint &j = ragdoll->joints[r.parent];
-                const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
-                d.reljoints[i].transposemul(mdata[bj.interpindex], mdata[br.interpindex]);
-            }
-            loopv(ragdoll->verts) d.verts[i].pos.mul(ragdoll->verts[i].weight);
-        }
-
-        void initragdoll(ragdolldata &d, skelcacheentry &sc, part *p)
-        {
-            const dualquat *bdata = sc.bdata;
-            loopv(ragdoll->joints)
-            {
-                const ragdollskel::joint &j = ragdoll->joints[i];
-                const boneinfo &b = bones[j.bone];
-                const dualquat &q = bdata[b.interpindex];
-                loopk(3) if(j.vert[k] >= 0)
-                {
-                    int vert = j.vert[k];
-                    vec pos;
-                    matrixstack[matrixpos].transform(q.transform(ragdoll->verts[vert].pos).add(p->translate).mul(p->model->scale), pos);
-                    d.verts[vert].pos.add(pos);
-                }
-            }
-            loopv(ragdoll->reljoints)
-            {
-                const ragdollskel::reljoint &r = ragdoll->reljoints[i];
-                const ragdollskel::joint &j = ragdoll->joints[r.parent];
-                const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
-                dualquat q = bdata[bj.interpindex];
-                q.invert().mul(bdata[br.interpindex]);
-                d.reljoints[i] = matrix3x4(q);
-            }
-            loopv(ragdoll->verts) d.verts[i].pos.mul(ragdoll->verts[i].weight);
-        }
-
-        void genmatragdollbones(ragdolldata &d, skelcacheentry &sc, part *p)
-        {
-            if(!sc.mdata) sc.mdata = new matrix3x4[numinterpbones];
-            if(lastsdata == sc.mdata) lastsdata = NULL;
-            loopv(ragdoll->joints)
-            {
-                const ragdollskel::joint &j = ragdoll->joints[i];
-                const boneinfo &b = bones[j.bone];
-                vec pos(0, 0, 0);
-                loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos);
-                pos.mul(j.weight/p->model->scale).sub(p->translate);
-                sc.mdata[b.interpindex].transposemul(d.tris[j.tri], pos, j.orient);
-            }
-            loopv(ragdoll->reljoints)
-            {
-                const ragdollskel::reljoint &r = ragdoll->reljoints[i];
-                const ragdollskel::joint &j = ragdoll->joints[r.parent];
-                const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
-                sc.mdata[br.interpindex].mul(sc.mdata[bj.interpindex], d.reljoints[i]);
-            }
-        }
-
-        void genragdollbones(ragdolldata &d, skelcacheentry &sc, part *p)
-        {
-            if(!sc.bdata) sc.bdata = new dualquat[numinterpbones];
-            if(lastsdata == sc.bdata) lastsdata = NULL;
-            loopv(ragdoll->joints)
-            {
-                const ragdollskel::joint &j = ragdoll->joints[i];
-                const boneinfo &b = bones[j.bone];
-                vec pos(0, 0, 0);
-                loopk(3) if(j.vert[k]>=0) pos.add(d.verts[j.vert[k]].pos);
-                pos.mul(j.weight/p->model->scale).sub(p->translate);
-                matrix3x4 m;
-                m.transposemul(d.tris[j.tri], pos, j.orient);
-                sc.bdata[b.interpindex] = dualquat(m);
-            }
-            loopv(ragdoll->reljoints)
+            loopi(numbones) 
             {
-                const ragdollskel::reljoint &r = ragdoll->reljoints[i];
-                const ragdollskel::joint &j = ragdoll->joints[r.parent];
-                const boneinfo &br = bones[r.bone], &bj = bones[j.bone];
-                sc.bdata[br.interpindex].mul(sc.bdata[bj.interpindex], dualquat(d.reljoints[i]));
+                const boneinfo &b = bones[i];
+                if(b.interpgroup>=0) sc.bdata[b.interpindex].fixantipodal(sc.bdata[b.interpgroup]);
             }
-            loopv(antipodes) sc.bdata[antipodes[i].child].fixantipodal(sc.bdata[antipodes[i].parent]);
         }
 
-        void concattagtransform(part *p, int frame, int i, const matrix3x4 &m, matrix3x4 &n)
+        void concattagtransform(int frame, int i, const matrix3x4 &m, matrix3x4 &n)
         {
             matrix3x4 t = bones[tags[i].bone].base;
-            t.translate(vec(p->translate).mul(p->model->scale));
             n.mul(m, t);
         }
 
-        void calctagmatrix(part *p, int bone, const matrix3x4 &m, linkedpart &l)
+        void calctagmatrix(int bone, const matrix3x4 &m, linkedpart &l)
         {
-            if(numframes) 
+            matrix3x4 t;
+            if(numframes) t.mul(m, bones[bone].base);
+            else t = m;
+            loopk(4)
             {
-                matrix3x4 t;
-                t.mul(m, bones[bone].base); 
-                l.matrix = t;
+                l.matrix[4*k] = t.X[k];
+                l.matrix[4*k+1] = t.Y[k];
+                l.matrix[4*k+2] = t.Z[k];
             }
-            else l.matrix = m;
-            l.matrix[12] = (l.matrix[12] + p->translate.x) * p->model->scale;
-            l.matrix[13] = (l.matrix[13] + p->translate.y) * p->model->scale;
-            l.matrix[14] = (l.matrix[14] + p->translate.z) * p->model->scale;
+            l.matrix[3] = l.matrix[7] = l.matrix[11] = 0.0f;
+            l.matrix[15] = 1.0f;
         }
 
         void calctags(skelcacheentry &sc, part *p)
@@ -1175,7 +1039,7 @@ struct skelmodel : animmodel
             loopv(p->links)
             {
                 int tagbone = tags[p->links[i].tag].bone, interpindex = bones[tagbone].interpindex;
-                calctagmatrix(p, tagbone, usematskel ? sc.mdata[interpindex] : sc.bdata[interpindex], p->links[i]);
+                calctagmatrix(tagbone, usematskel ? sc.mdata[interpindex] : sc.bdata[interpindex], p->links[i]);
             }
         }
 
@@ -1184,7 +1048,7 @@ struct skelmodel : animmodel
             loopv(p->links)
             {
                int tagbone = tags[p->links[i].tag].bone;
-               calctagmatrix(p, tagbone, bones[tagbone].base, p->links[i]);
+               calctagmatrix(tagbone, bones[tagbone].base, p->links[i]);
             }
         }
 
@@ -1202,7 +1066,7 @@ struct skelmodel : animmodel
             loopv(users) users[i]->cleanup();
         }
 
-        skelcacheentry &checkskelcache(part *p, const animstate *as, float pitch, const vec &axis, ragdolldata *rdata)
+        skelcacheentry &checkskelcache(const animstate *as, float pitch, const vec &axis)
         {
             if(skelcache.empty()) 
             {
@@ -1218,7 +1082,7 @@ struct skelmodel : animmodel
             {
                 skelcacheentry &c = skelcache[i];
                 loopj(numanimparts) if(c.as[j]!=as[j]) goto mismatch;
-                if(c.pitch != pitch || c.partmask != partmask || c.ragdoll != rdata || (rdata && c.millis < rdata->lastmove)) goto mismatch;
+                if(c.pitch != pitch || c.partmask != partmask) goto mismatch;
                 match = true;
                 sc = &c;
                 break;
@@ -1231,13 +1095,7 @@ struct skelmodel : animmodel
                 loopi(numanimparts) sc->as[i] = as[i];
                 sc->pitch = pitch;
                 sc->partmask = partmask;
-                sc->ragdoll = rdata;
-                if(rdata)
-                {
-                    if(matskel) genmatragdollbones(*rdata, *sc, p);
-                    else genragdollbones(*rdata, *sc, p);
-                }
-                else if(matskel) interpmatbones(as, pitch, axis, numanimparts, partmask, *sc);
+                if(matskel) interpmatbones(as, pitch, axis, numanimparts, partmask, *sc);
                 else interpbones(as, pitch, axis, numanimparts, partmask, *sc);
             }
             sc->millis = lastmillis;
@@ -1251,14 +1109,14 @@ struct skelmodel : animmodel
             if(!offset) count = numgpubones;
             if(hasPP)
             {
-                if(usematskel) glProgramEnvParameters4fv_(GL_VERTEX_PROGRAM_ARB, 10 + 3*offset, 3*count, sc.mdata[0].a.v);
+                if(usematskel) glProgramEnvParameters4fv_(GL_VERTEX_PROGRAM_ARB, 10 + 3*offset, 3*count, sc.mdata[0].X.v);
                 else glProgramEnvParameters4fv_(GL_VERTEX_PROGRAM_ARB, 10 + 2*offset, 2*count, sc.bdata[0].real.v);
             }
             else if(usematskel) loopi(count)
             {
-                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 10 + 3*(offset+i), sc.mdata[i].a.v);
-                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 11 + 3*(offset+i), sc.mdata[i].b.v);
-                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 12 + 3*(offset+i), sc.mdata[i].c.v);
+                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 10 + 3*(offset+i), sc.mdata[i].X.v);
+                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 11 + 3*(offset+i), sc.mdata[i].Y.v);
+                glProgramEnvParameter4fv_(GL_VERTEX_PROGRAM_ARB, 12 + 3*(offset+i), sc.mdata[i].Z.v);
             }
             else loopi(count)
             {
@@ -1346,8 +1204,26 @@ struct skelmodel : animmodel
             return skel->findtag(name);
         }
 
+        virtual meshgroup *allocate() { return new skelmeshgroup; }
+
+        meshgroup *copy()
+        {
+            skelmeshgroup &group = *(skelmeshgroup *)meshgroup::copy();
+            group.skel = skel->shared ? skel : skel->copy();
+            group.skel->users.add(&group);
+            if(skel->shared) skel->shared++;
+            loopv(blendcombos) group.blendcombos.add(blendcombos[i]);
+            memcpy(group.numblends, numblends, sizeof(numblends));
+            return &group;
+        }
+
         int totalframes() const { return max(skel->numframes, 1); }
 
+        void scaletags(const vec &transdiff, float scalediff)
+        {
+            skel->scaletags(transdiff, scalediff);
+        }
+ 
         void genvbo(bool norms, bool tangents, vbocacheentry &vc)
         {
             if(hasVBO)
@@ -1471,41 +1347,20 @@ struct skelmodel : animmodel
             if(as->anim&ANIM_NOSKIN)
             {
                 if(enabletc) disabletc();
-                if(enablenormals) disablenormals();
             }
-            else
+            else if(!enabletc || lasttcbuf!=lastvbuf)
             {
                 if(vnorms || vtangents)
                 {
-                    if(!enablenormals)
-                    {
-                        glEnableClientState(GL_NORMAL_ARRAY);
-                        enablenormals = true;
-                    }
-                    if(lastnbuf!=lastvbuf)
-                    {
-                        glNormalPointer(GL_FLOAT, vertsize, &vverts->norm);
-                        lastnbuf = lastvbuf;
-                    }
-                }
-                else if(enablenormals) disablenormals();
-
-                if(!enabletc)
-                {
-                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-                    enabletc = true;
+                    if(!enabletc) glEnableClientState(GL_NORMAL_ARRAY);
+                    if(lasttcbuf!=lastvbuf) glNormalPointer(GL_FLOAT, vertsize, &vverts->norm);
                 }
-                if(lasttcbuf!=lastvbuf)
-                {
-                    glTexCoordPointer(2, GL_FLOAT, vertsize, &vverts->u);
-                    lasttcbuf = lastnbuf;
-                }
-            }
-            if(!sc || !skel->usegpuskel)
-            {
-                if(enablebones) disablebones();
-                return;
+                if(!enabletc) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+                if(lasttcbuf!=lastvbuf) glTexCoordPointer(2, GL_FLOAT, vertsize, &vverts->u);
+                lasttcbuf = lastvbuf;
+                enabletc = true;
             }
+            if(!sc || !skel->usegpuskel) return;
             if(!enablebones)
             {
                 glEnableVertexAttribArray_(6);
@@ -1522,9 +1377,9 @@ struct skelmodel : animmodel
             if(bc && vblends) skel->setgpubones(*bc, vblends);
         }
 
-        void concattagtransform(part *p, int frame, int i, const matrix3x4 &m, matrix3x4 &n)
+        void concattagtransform(int frame, int i, const matrix3x4 &m, matrix3x4 &n)
         {
-            skel->concattagtransform(p, frame, i, m, n);
+            skel->concattagtransform(frame, i, m, n);
         }
 
         int addblendcombo(const blendcombo &c)
@@ -1565,7 +1420,6 @@ struct skelmodel : animmodel
         void blendmatbones(const skelcacheentry &sc, blendcacheentry &bc)
         {
             if(!bc.mdata) bc.mdata = new matrix3x4[vblends];
-            if(lastbdata == bc.mdata) lastbdata = NULL;
             matrix3x4 *dst = bc.mdata - (skel->usegpuskel ? skel->numgpubones : skel->numinterpbones);
             loopv(blendcombos)
             {
@@ -1586,7 +1440,6 @@ struct skelmodel : animmodel
         void blendbones(const skelcacheentry &sc, blendcacheentry &bc)
         {
             if(!bc.bdata) bc.bdata = new dualquat[vblends];
-            if(lastbdata == bc.bdata) lastbdata = NULL;
             dualquat *dst = bc.bdata - (skel->usegpuskel ? skel->numgpubones : skel->numinterpbones);
             bool normalize = !skel->usegpuskel || vweights<=1;
             loopv(blendcombos)
@@ -1624,6 +1477,8 @@ struct skelmodel : animmodel
             }
             if(hasVBO) { if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } }
             else DELETEA(vdata);
+            lastvbuf = lasttcbuf = lastmtcbuf = lastnbuf = lastbbuf = lastbdata = NULL;
+            lastebuf = 0;
         }
 
         #define SEARCHCACHE(cachesize, cacheentry, cache, reusecheck) \
@@ -1655,7 +1510,7 @@ struct skelmodel : animmodel
             SEARCHCACHE(MAXBLENDCACHE, blendcacheentry, blendcache, )
         }
 
-        void render(const animstate *as, float pitch, const vec &axis, dynent *d, part *p)
+        void render(const animstate *as, float pitch, const vec &axis, part *p)
         {
             bool norms = false, tangents = false;
             loopv(p->skins)
@@ -1663,71 +1518,57 @@ struct skelmodel : animmodel
                 if(p->skins[i].normals()) norms = true;
                 if(p->skins[i].tangents()) tangents = true;
             }
-            if(skel->shouldcleanup()) { skel->cleanup(); disablevbo(); }
-            else if(norms!=vnorms || tangents!=vtangents) { cleanup(); disablevbo(); }
+            if(skel->shouldcleanup()) skel->cleanup();
+            else if(norms!=vnorms || tangents!=vtangents) cleanup();
 
             if(!skel->numframes)
             {
-                if(!(as->anim&ANIM_NORENDER))
-                {
-                    if(hasVBO ? !vbocache->vbuf : !vbocache->vdata) genvbo(norms, tangents, *vbocache);
-                    bindvbo(as, *vbocache);
-                    loopv(meshes) ((skelmesh *)meshes[i])->render(as, p->skins[i], *vbocache);
-                }
+                if(hasVBO ? !vbocache->vbuf : !vbocache->vdata) genvbo(norms, tangents, *vbocache);
+                bindvbo(as, *vbocache);
+                loopv(meshes) ((skelmesh *)meshes[i])->render(as, p->skins[i], *vbocache);
                 skel->calctags(p);
                 return;
             }
 
-            skelcacheentry &sc = skel->checkskelcache(p, as, pitch, axis, as->anim&ANIM_RAGDOLL || !d || !d->ragdoll || d->ragdoll->skel != skel->ragdoll ? NULL : d->ragdoll);
-            if(!(as->anim&ANIM_NORENDER))
+            skelcacheentry &sc = skel->checkskelcache(as, pitch, axis);
+            int owner = &sc-&skel->skelcache[0];
+            vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner);
+            vc.millis = lastmillis;
+            if(hasVBO ? !vc.vbuf : !vc.vdata) genvbo(norms, tangents, vc);
+            blendcacheentry *bc = NULL;
+            if(vblends)
             {
-                int owner = &sc-&skel->skelcache[0];
-                vbocacheentry &vc = skel->usegpuskel ? *vbocache : checkvbocache(sc, owner);
-                vc.millis = lastmillis;
-                if(hasVBO ? !vc.vbuf : !vc.vdata) genvbo(norms, tangents, vc);
-                blendcacheentry *bc = NULL;
-                if(vblends)
+                bc = &checkblendcache(sc, owner);
+                bc->millis = lastmillis;
+                if(bc->owner!=owner)
                 {
-                    bc = &checkblendcache(sc, owner);
-                    bc->millis = lastmillis;
-                    if(bc->owner!=owner)
-                    {
-                        bc->owner = owner;
-                        *(animcacheentry *)bc = sc;
-                        if(skel->usematskel) blendmatbones(sc, *bc);
-                        else blendbones(sc, *bc);
-                    }
+                    bc->owner = owner;
+                    *(animcacheentry *)bc = sc;
+                    if(skel->usematskel) blendmatbones(sc, *bc);
+                    else blendbones(sc, *bc);
                 }
-                if(!skel->usegpuskel && vc.owner!=owner)
-                { 
-                    vc.owner = owner;
-                    (animcacheentry &)vc = sc;
-                    loopv(meshes)
-                    {
-                        skelmesh &m = *(skelmesh *)meshes[i];
-                        if(skel->usematskel) m.interpmatverts(sc, bc, norms, tangents, (hasVBO ? vdata : vc.vdata) + m.voffset*vertsize, p->skins[i]);
-                        else m.interpverts(sc, bc, norms, tangents, (hasVBO ? vdata : vc.vdata) + m.voffset*vertsize, p->skins[i]);
-                    }
-                    if(hasVBO)
-                    {
-                        glBindBuffer_(GL_ARRAY_BUFFER_ARB, vc.vbuf);
-                        glBufferData_(GL_ARRAY_BUFFER_ARB, vlen*vertsize, vdata, GL_STREAM_DRAW_ARB);
-                    }
+            }
+            if(!skel->usegpuskel && vc.owner!=owner)
+            { 
+                vc.owner = owner;
+                (animcacheentry &)vc = sc;
+                loopv(meshes)
+                {
+                    skelmesh &m = *(skelmesh *)meshes[i];
+                    if(skel->usematskel) m.interpmatverts(sc, bc, norms, tangents, (hasVBO ? vdata : vc.vdata) + m.voffset*vertsize, p->skins[i]);
+                    else m.interpverts(sc, bc, norms, tangents, (hasVBO ? vdata : vc.vdata) + m.voffset*vertsize, p->skins[i]);
+                }
+                if(hasVBO)
+                {
+                    glBindBuffer_(GL_ARRAY_BUFFER_ARB, vc.vbuf);
+                    glBufferData_(GL_ARRAY_BUFFER_ARB, vlen*vertsize, vdata, GL_STREAM_DRAW_ARB);
                 }
-
-                bindvbo(as, vc, &sc, bc);
-                loopv(meshes) ((skelmesh *)meshes[i])->render(as, p->skins[i], vc);
             }
 
-            skel->calctags(sc, p);
+            bindvbo(as, vc, &sc, bc);
+            loopv(meshes) ((skelmesh *)meshes[i])->render(as, p->skins[i], vc);
 
-            if(as->anim&ANIM_RAGDOLL && skel->ragdoll && !d->ragdoll)
-            {
-                d->ragdoll = new ragdolldata(skel->ragdoll, p->model->scale);
-                if(matskel) skel->initmatragdoll(*d->ragdoll, sc, p);
-                else skel->initragdoll(*d->ragdoll, sc, p);
-                d->ragdoll->init(d);
-            }
+            skel->calctags(sc, p);
         }
     };
 
@@ -1740,11 +1581,11 @@ struct skelmodel : animmodel
 
     struct skelpart : part
     {
-        animpartmask *buildingpartmask;
+        static animpartmask *buildingpartmask;
 
         uchar *partmask;
         
-        skelpart() : buildingpartmask(NULL), partmask(NULL)
+        skelpart() : partmask(NULL)
         {
         }
 
@@ -1815,3 +1656,5 @@ struct skelmodel : animmodel
     }
 };
 
+skelmodel::animpartmask *skelmodel::skelpart::buildingpartmask = NULL;
+
diff --git a/engine/sound.cpp b/engine/sound.cpp
index 036f228..a7a3a62 100644
--- a/engine/sound.cpp
+++ b/engine/sound.cpp
@@ -1,137 +1,58 @@
 // sound.cpp: basic positional sound using sdl_mixer
 
+#include "pch.h"
 #include "engine.h"
 
 #include "SDL_mixer.h"
 #define MAXVOL MIX_MAX_VOLUME
+Mix_Music *mod = NULL;
 
 bool nosound = true;
 
-struct soundsample
+struct sample
 {
     char *name;
-    Mix_Chunk *chunk;
+    Mix_Chunk *sound;
 
-    soundsample() : name(NULL) {}
-    ~soundsample() { DELETEA(name); }
+    sample() : name(NULL) {}
+    ~sample() { DELETEA(name); }
 };
 
 struct soundslot
 {
-    soundsample *sample;
-    int volume, maxuses;
+    sample *s;
+    int vol;
+    int uses, maxuses;
 };
 
-struct soundchannel
-{ 
-    int id;
-    bool inuse;
-    vec loc; 
-    soundslot *slot; 
-    extentity *ent; 
-    int radius, volume, pan;
-    bool dirty;
-
-    soundchannel(int id) : id(id) { reset(); }
-
-    bool hasloc() const { return loc.x >= -1e15f; }
-    void clearloc() { loc = vec(-1e16f, -1e16f, -1e16f); }
-
-    void reset()
-    {
-        inuse = false;
-        clearloc();
-        slot = NULL;
-        ent = NULL;
-        radius = 0;
-        volume = -1;
-        pan = -1;
-        dirty = false;
-    }
-};
-vector<soundchannel> channels;
-int maxchannels = 0;
-
-soundchannel &newchannel(int n, soundslot *slot, const vec *loc = NULL, extentity *ent = NULL, int radius = 0)
-{
-    if(ent)
-    {
-        loc = &ent->o;
-        ent->visible = true;
-    }
-    while(!channels.inrange(n)) channels.add(channels.length());
-    soundchannel &chan = channels[n];
-    chan.reset();
-    chan.inuse = true;
-    if(loc) chan.loc = *loc;
-    chan.slot = slot;
-    chan.ent = ent;
-    chan.radius = radius;
-    return chan;
-}
+struct soundloc { vec loc; bool inuse; soundslot *slot; extentity *ent; };
+vector<soundloc> soundlocs;
 
-void freechannel(int n)
-{
-    // Note that this can potentially be called from the SDL_mixer audio thread.
-    // Be careful of race conditions when checking chan.inuse without locking audio.
-    // Can't use Mix_Playing() checks due to bug with looping sounds in SDL_mixer.
-    if(!channels.inrange(n) || !channels[n].inuse) return;
-    soundchannel &chan = channels[n];
-    chan.inuse = false;
-    if(chan.ent) chan.ent->visible = false;
-}
-
-void syncchannel(soundchannel &chan)
-{
-    if(!chan.dirty) return;
-    if(!Mix_FadingChannel(chan.id)) Mix_Volume(chan.id, chan.volume);
-    Mix_SetPanning(chan.id, 255-chan.pan, chan.pan);
-    chan.dirty = false;
-}
-
-void stopchannels()
+void setmusicvol(int musicvol)
 {
-    loopv(channels)
-    {
-        soundchannel &chan = channels[i];
-        if(!chan.inuse) continue;
-        Mix_HaltChannel(i);
-        freechannel(i);
-    }
+    if(nosound) return;
+    if(mod) Mix_VolumeMusic((musicvol*MAXVOL)/255);
 }
 
-void setmusicvol(int musicvol);
-VARFP(soundvol, 0, 255, 255, if(!soundvol) { stopchannels(); setmusicvol(0); });
-VARFP(musicvol, 0, 128, 255, setmusicvol(soundvol ? musicvol : 0));
+VARP(soundvol, 0, 255, 255);
+VARFP(musicvol, 0, 128, 255, setmusicvol(musicvol));
 
 char *musicfile = NULL, *musicdonecmd = NULL;
 
-Mix_Music *music = NULL;
-SDL_RWops *musicrw = NULL;
-stream *musicstream = NULL;
-
-void setmusicvol(int musicvol)
-{
-    if(nosound) return;
-    if(music) Mix_VolumeMusic((musicvol*MAXVOL)/255);
-}
-
-void stopmusic()
+void stopsound()
 {
     if(nosound) return;
     DELETEA(musicfile);
     DELETEA(musicdonecmd);
-    if(music)
+    if(mod)
     {
         Mix_HaltMusic();
-        Mix_FreeMusic(music);
-        music = NULL;
+        Mix_FreeMusic(mod);
+        mod = NULL;
     }
-    if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; }
-    DELETEP(musicstream);
 }
 
-VARF(soundchans, 1, 32, 128, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
+VARF(soundchans, 0, 32, 128, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
 VARF(soundfreq, 0, MIX_DEFAULT_FREQUENCY, 44100, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
 VARF(soundbufferlen, 128, 1024, 4096, initwarning("sound configuration", INIT_RESET, CHANGE_SOUND));
 
@@ -139,111 +60,77 @@ void initsound()
 {
     if(Mix_OpenAudio(soundfreq, MIX_DEFAULT_FORMAT, 2, soundbufferlen)<0)
     {
-        nosound = true;
         conoutf(CON_ERROR, "sound init failed (SDL_mixer): %s", (size_t)Mix_GetError());
         return;
     }
 	Mix_AllocateChannels(soundchans);	
-    Mix_ChannelFinished(freechannel);
-    maxchannels = soundchans;
     nosound = false;
 }
 
 void musicdone()
 {
-    if(music) { Mix_HaltMusic(); Mix_FreeMusic(music); music = NULL; }
-    if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; }
-    DELETEP(musicstream);
-    DELETEA(musicfile);
     if(!musicdonecmd) return;
+    if(mod) Mix_FreeMusic(mod);
+    mod = NULL;
+    DELETEA(musicfile);
     char *cmd = musicdonecmd;
     musicdonecmd = NULL;
     execute(cmd);
     delete[] cmd;
 }
 
-Mix_Music *loadmusic(const char *name)
-{
-    if(!musicstream) musicstream = openzipfile(name, "rb");
-    if(musicstream)
-    {
-        if(!musicrw) musicrw = musicstream->rwops();
-        if(!musicrw) DELETEP(musicstream);
-    }
-    if(musicrw) music = Mix_LoadMUS_RW(musicrw);
-    else music = Mix_LoadMUS(findfile(name, "rb")); 
-    if(!music)
-    {
-        if(musicrw) { SDL_FreeRW(musicrw); musicrw = NULL; }
-        DELETEP(musicstream);
-    }
-    return music;
-}
- 
-void startmusic(char *name, char *cmd)
+void music(char *name, char *cmd)
 {
     if(nosound) return;
-    stopmusic();
+    stopsound();
     if(soundvol && musicvol && *name)
     {
-        defformatstring(file)("packages/%s", name);
-        path(file);
-        if(loadmusic(file))
+        if(cmd[0]) musicdonecmd = newstring(cmd);
+        s_sprintfd(sn)("packages/%s", name);
+        const char *file = findfile(path(sn), "rb");
+        if((mod = Mix_LoadMUS(file)))
         {
-            DELETEA(musicfile);
-            DELETEA(musicdonecmd);
             musicfile = newstring(file);
-            if(cmd[0]) musicdonecmd = newstring(cmd);
-            Mix_PlayMusic(music, cmd[0] ? 0 : -1);
+            Mix_PlayMusic(mod, cmd[0] ? 0 : -1);
             Mix_VolumeMusic((musicvol*MAXVOL)/255);
-            intret(1);
         }
         else
         {
-            conoutf(CON_ERROR, "could not play music: %s", file);
-            intret(0); 
+            conoutf(CON_ERROR, "could not play music: %s", sn);
         }
     }
 }
 
-COMMANDN(music, startmusic, "ss");
+COMMAND(music, "ss");
 
-hashtable<const char *, soundsample> samples;
+hashtable<const char *, sample> samples;
 vector<soundslot> gamesounds, mapsounds;
 
 int findsound(const char *name, int vol, vector<soundslot> &sounds)
 {
     loopv(sounds)
     {
-        if(!strcmp(sounds[i].sample->name, name) && (!vol || sounds[i].volume==vol)) return i;
+        if(!strcmp(sounds[i].s->name, name) && (!vol || sounds[i].vol==vol)) return i;
     }
     return -1;
 }
 
 int addsound(const char *name, int vol, int maxuses, vector<soundslot> &sounds)
 {
-    soundsample *s = samples.access(name);
+    sample *s = samples.access(name);
     if(!s)
     {
         char *n = newstring(name);
         s = &samples[n];
         s->name = n;
-        s->chunk = NULL;
+        s->sound = NULL;
     }
-    soundslot *oldsounds = sounds.getbuf();
-    int oldlen = sounds.length();
     soundslot &slot = sounds.add();
-    // sounds.add() may relocate slot pointers
-    if(sounds.getbuf() != oldsounds) loopv(channels)
-    {
-        soundchannel &chan = channels[i];
-        if(chan.inuse && chan.slot >= oldsounds && chan.slot < &oldsounds[oldlen])
-            chan.slot = &sounds[chan.slot - oldsounds];
-    }
-    slot.sample = s;
-    slot.volume = vol ? vol : 100;
+    slot.s = s;
+    slot.vol = vol ? vol : 100;
+    slot.uses = 0;
     slot.maxuses = maxuses;
-    return oldlen;
+    return sounds.length()-1;
 }
 
 void registersound(char *name, int *vol) { intret(addsound(name, *vol, 0, gamesounds)); }
@@ -252,274 +139,166 @@ COMMAND(registersound, "si");
 void mapsound(char *name, int *vol, int *maxuses) { intret(addsound(name, *vol, *maxuses < 0 ? 0 : max(1, *maxuses), mapsounds)); }
 COMMAND(mapsound, "sii");
 
-void resetchannels()
-{
-    loopv(channels) if(channels[i].inuse) freechannel(i);
-    channels.setsize(0);
-}
-
 void clear_sound()
 {
     closemumble();
     if(nosound) return;
-    stopmusic();
-    Mix_CloseAudio();
-    resetchannels();
+    stopsound();
     gamesounds.setsizenodelete(0);
     mapsounds.setsizenodelete(0);
     samples.clear();
+    Mix_CloseAudio();
 }
 
-void clearmapsounds()
+void clearsoundlocs()
 {
-    loopv(channels) if(channels[i].inuse && channels[i].ent)
+    loopv(soundlocs) if(soundlocs[i].inuse && soundlocs[i].ent)
     {
-        Mix_HaltChannel(i);
-        freechannel(i);
+        if(Mix_Playing(i)) Mix_HaltChannel(i);
+        soundlocs[i].inuse = false;
+        soundlocs[i].ent->visible = false;
+        soundlocs[i].slot->uses--;
     }
-    mapsounds.setsizenodelete(0);
+    soundlocs.setsize(0);
 }
 
-void stopmapsound(extentity *e)
+void clearmapsounds()
 {
-    loopv(channels)
-    {
-        soundchannel &chan = channels[i];
-        if(chan.inuse && chan.ent == e)
-        {
-            Mix_HaltChannel(i);
-            freechannel(i);
-        }
-    }
+    clearsoundlocs();
+    mapsounds.setsizenodelete(0);
 }
-        
+
 void checkmapsounds()
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
-        if(e.type!=ET_SOUND) continue;
-        if(camera1->o.dist(e.o) < e.attr2)
-        {
-            if(!e.visible) playsound(e.attr1, NULL, &e, -1);
-        }
-        else if(e.visible) stopmapsound(&e);
+        if(e.type!=ET_SOUND || e.visible || camera1->o.dist(e.o)>=e.attr2) continue;
+        playsound(e.attr1, NULL, &e);
     }
 }
 
 VAR(stereo, 0, 1, 1);
 
-VARP(maxsoundradius, 0, 340, 10000);
-
-bool updatechannel(soundchannel &chan)
+void updatechanvol(int chan, int svol, const vec *loc = NULL, extentity *ent = NULL)
 {
-    if(!chan.slot) return false;
     int vol = soundvol, pan = 255/2;
-    if(chan.hasloc())
+    if(loc)
     {
         vec v;
-        float dist = camera1->o.dist(chan.loc, v);
-        int rad = maxsoundradius;
-        if(chan.ent)
+        float dist = camera1->o.dist(*loc, v);
+        if(ent)
         {
-            rad = chan.ent->attr2;
-            if(chan.ent->attr3)
+            int rad = ent->attr2;
+            if(ent->attr3)
             {
-                rad -= chan.ent->attr3;
-                dist -= chan.ent->attr3;
+                rad -= ent->attr3;
+                dist -= ent->attr3;
             }
+            vol -= (int)(min(max(dist/rad, 0.0f), 1.0f)*soundvol);
+        }
+        else
+        {
+            vol -= (int)(dist*3/4*soundvol/255); // simple mono distance attenuation
+            if(vol<0) vol = 0;
         }
-        else if(chan.radius > 0) rad = maxsoundradius ? min(maxsoundradius, chan.radius) : chan.radius;
-        if(rad > 0) vol -= int(clamp(dist/rad, 0.0f, 1.0f)*soundvol); // simple mono distance attenuation
         if(stereo && (v.x != 0 || v.y != 0) && dist>0)
         {
             float yaw = -atan2f(v.x, v.y) - camera1->yaw*RAD; // relative angle of sound along X-Y axis
             pan = int(255.9f*(0.5f*sinf(yaw)+0.5f)); // range is from 0 (left) to 255 (right)
         }
     }
-    vol = (vol*MAXVOL*chan.slot->volume)/255/255;
+    vol = (vol*MAXVOL*svol)/255/255;
     vol = min(vol, MAXVOL);
-    if(vol == chan.volume && pan == chan.pan) return false;
-    chan.volume = vol;
-    chan.pan = pan;
-    chan.dirty = true;
-    return true;
+    Mix_Volume(chan, vol);
+    Mix_SetPanning(chan, 255-pan, pan);
 }  
 
-void updatesounds()
+void newsoundloc(int chan, const vec *loc, soundslot *slot, extentity *ent = NULL)
+{
+    while(chan >= soundlocs.length()) soundlocs.add().inuse = false;
+    soundlocs[chan].loc = *loc;
+    soundlocs[chan].inuse = true;
+    soundlocs[chan].slot = slot;
+    soundlocs[chan].ent = ent;
+}
+
+void updatevol()
 {
     updatemumble();
     if(nosound) return;
-    checkmapsounds();
-    int dirty = 0;
-    loopv(channels)
-    {
-        soundchannel &chan = channels[i];
-        if(chan.inuse && chan.hasloc() && updatechannel(chan)) dirty++;
-    }
-    if(dirty)
+    loopv(soundlocs) if(soundlocs[i].inuse)
     {
-        SDL_LockAudio(); // workaround for race conditions inside Mix_SetPanning
-        loopv(channels) 
+        if(Mix_Playing(i))
+            updatechanvol(i, soundlocs[i].slot->vol, &soundlocs[i].loc, soundlocs[i].ent);
+        else 
         {
-            soundchannel &chan = channels[i];
-            if(chan.inuse && chan.dirty) syncchannel(chan);
+            soundlocs[i].inuse = false;
+            if(soundlocs[i].ent) 
+            {
+                soundlocs[i].ent->visible = false;
+                soundlocs[i].slot->uses--;
+            }
         }
-        SDL_UnlockAudio();
-    }
-    if(music)
-    {
-        if(!Mix_PlayingMusic()) musicdone();
-        else if(Mix_PausedMusic()) Mix_ResumeMusic();
     }
+    if(mod && !Mix_PlayingMusic()) musicdone();
 }
 
 VARP(maxsoundsatonce, 0, 5, 100);
 
-VAR(dbgsound, 0, 0, 1);
-
-static Mix_Chunk *loadwav(const char *name)
-{
-    Mix_Chunk *c = NULL;
-    stream *z = openzipfile(name, "rb");
-    if(z)
-    {
-        SDL_RWops *rw = z->rwops();
-        if(rw) 
-        {
-            c = Mix_LoadWAV_RW(rw, 0);
-            SDL_FreeRW(rw);
-        }
-        delete z;
-    }
-    if(!c) c = Mix_LoadWAV(findfile(name, "rb"));
-    return c;
-}
-
-int playsound(int n, const vec *loc, extentity *ent, int loops, int fade, int chanid, int radius)
+void playsound(int n, const vec *loc, extentity *ent)
 {
-    if(nosound || !soundvol) return -1;
-
-    vector<soundslot> &sounds = ent ? mapsounds : gamesounds;
-    if(!sounds.inrange(n)) { conoutf(CON_WARN, "unregistered sound: %d", n); return -1; }
-    soundslot &slot = sounds[n];
-
-    if(loc && (maxsoundradius || radius > 0))
-    {
-        // cull sounds that are unlikely to be heard
-        int rad = radius > 0 ? (maxsoundradius ? min(maxsoundradius, radius) : radius) : maxsoundradius;
-        if(camera1->o.dist(*loc) > 1.5f*rad)
-        {
-            if(channels.inrange(chanid) && channels[chanid].inuse && channels[chanid].slot == &slot)
-            {
-                Mix_HaltChannel(chanid);
-                freechannel(chanid);
-            }
-            return -1;    
-        }
-    }
+    if(nosound) return;
+    if(!soundvol) return;
 
-    if(chanid < 0)
+    if(!ent)
     {
-        if(slot.maxuses)
-        {
-            int uses = 0;
-            loopv(channels) if(channels[i].inuse && channels[i].slot == &slot && ++uses >= slot.maxuses) return -1;
-        }
-
-        // avoid bursts of sounds with heavy packetloss and in sp
         static int soundsatonce = 0, lastsoundmillis = 0;
-        if(totalmillis == lastsoundmillis) soundsatonce++; else soundsatonce = 1;
+        if(totalmillis==lastsoundmillis) soundsatonce++; else soundsatonce = 1;
         lastsoundmillis = totalmillis;
-        if(maxsoundsatonce && soundsatonce > maxsoundsatonce) return -1;
+        if(maxsoundsatonce && soundsatonce>maxsoundsatonce) return;  // avoid bursts of sounds with heavy packetloss and in sp
     }
 
-    if(!slot.sample->chunk)
-    {
-        if(!slot.sample->name[0]) return -1;
+    vector<soundslot> &sounds = ent ? mapsounds : gamesounds;
+    if(!sounds.inrange(n)) { conoutf(CON_WARN, "unregistered sound: %d", n); return; }
+    soundslot &slot = sounds[n];
+    if(ent && slot.maxuses && slot.uses>=slot.maxuses) return;
 
+    if(!slot.s->sound)
+    {
         const char *exts[] = { "", ".wav", ".ogg" };
         string buf;
         loopi(sizeof(exts)/sizeof(exts[0]))
         {
-            formatstring(buf)("packages/sounds/%s%s", slot.sample->name, exts[i]);
-            path(buf);
-            slot.sample->chunk = loadwav(buf);
-            if(slot.sample->chunk) break;
+            s_sprintf(buf)("packages/sounds/%s%s", slot.s->name, exts[i]);
+            const char *file = findfile(path(buf), "rb");
+            slot.s->sound = Mix_LoadWAV(file);
+            if(slot.s->sound) break;
         }
 
-        if(!slot.sample->chunk) { conoutf(CON_ERROR, "failed to load sample: %s", buf); return -1; }
+        if(!slot.s->sound) { conoutf(CON_ERROR, "failed to load sample: %s", buf); return; }
     }
 
-    if(channels.inrange(chanid))
-    {
-        soundchannel &chan = channels[chanid];
-        if(chan.inuse && chan.slot == &slot) 
-        {
-            if(loc) chan.loc = *loc;
-            else if(chan.hasloc()) chan.clearloc();
-            return chanid;
-        }
-    }
-    if(fade < 0) return -1;
-           
-    if(dbgsound) conoutf("sound: %s", slot.sample->name);
- 
-    chanid = -1;
-    loopv(channels) if(!channels[i].inuse) { chanid = i; break; }
-    if(chanid < 0 && channels.length() < maxchannels) chanid = channels.length();
-    if(chanid < 0) loopv(channels) if(!channels[i].volume) { chanid = i; break; }
-    if(chanid < 0) return -1;
-
-    SDL_LockAudio(); // must lock here to prevent freechannel/Mix_SetPanning race conditions
-    if(channels.inrange(chanid) && channels[chanid].inuse)
-    {
-        Mix_HaltChannel(chanid);
-        freechannel(chanid);
-    }
-    soundchannel &chan = newchannel(chanid, &slot, loc, ent, radius);
-    updatechannel(chan);
-    int playing = -1;
-    if(fade) 
-    {
-        Mix_Volume(chanid, chan.volume);
-        playing = Mix_FadeInChannel(chanid, slot.sample->chunk, loops, fade);
-    }
-    else playing = Mix_PlayChannel(chanid, slot.sample->chunk, loops);
-    if(playing >= 0) syncchannel(chan); 
-    else freechannel(chanid);
-    SDL_UnlockAudio();
-    return playing;
-}
+    int chan = Mix_PlayChannel(-1, slot.s->sound, 0);
+    if(chan<0) return;
 
-void stopsounds()
-{
-    loopv(channels) if(channels[i].inuse)
-    {
-        Mix_HaltChannel(i);
-        freechannel(i);
-    }
-}
-
-bool stopsound(int n, int chanid, int fade)
-{
-    if(!channels.inrange(chanid) || !channels[chanid].inuse || !gamesounds.inrange(n) || channels[chanid].slot != &gamesounds[n]) return false;
-    if(dbgsound) conoutf("stopsound: %s", channels[chanid].slot->sample->name);
-    if(!fade || !Mix_FadeOutChannel(chanid, fade))
+    if(ent)
     {
-        Mix_HaltChannel(chanid);
-        freechannel(chanid);
+        loc = &ent->o;
+        ent->visible = true;
+        slot.uses++;
     }
-    return true;
+    if(loc) newsoundloc(chan, loc, &slot, ent);
+    updatechanvol(chan, slot.vol, loc, ent);
 }
 
-int playsoundname(const char *s, const vec *loc, int vol, int loops, int fade, int chanid, int radius) 
+void playsoundname(const char *s, const vec *loc, int vol) 
 { 
     if(!vol) vol = 100;
     int id = findsound(s, vol, gamesounds);
     if(id < 0) id = addsound(s, vol, 0, gamesounds);
-    return playsound(id, loc, NULL, loops, fade, chanid, radius);
+    playsound(id, loc);
 }
 
 void sound(int *n) { playsound(*n); }
@@ -536,37 +315,31 @@ void resetsound()
     clearchanges(CHANGE_SOUND);
     if(!nosound) 
     {
-        enumerate(samples, soundsample, s, { Mix_FreeChunk(s.chunk); s.chunk = NULL; });
-        if(music)
+        clearsoundlocs();
+        enumerate(samples, sample, s, { Mix_FreeChunk(s.sound); s.sound = NULL; });
+        if(mod)
         {
             Mix_HaltMusic();
-            Mix_FreeMusic(music);
+            Mix_FreeMusic(mod);
         }
-        if(musicstream) musicstream->seek(0, SEEK_SET);
         Mix_CloseAudio();
     }
     initsound();
-    resetchannels();
     if(nosound)
     {
         DELETEA(musicfile);
         DELETEA(musicdonecmd);
-        music = NULL;
+        mod = NULL;
         gamesounds.setsizenodelete(0);
         mapsounds.setsizenodelete(0);
         samples.clear();
         return;
     }
-    if(music && loadmusic(musicfile))
+    if(mod && (mod = Mix_LoadMUS(musicfile)))
     {
-        Mix_PlayMusic(music, musicdonecmd ? 0 : -1);
+        Mix_PlayMusic(mod, musicdonecmd[0] ? 0 : -1);
         Mix_VolumeMusic((musicvol*MAXVOL)/255);
     }
-    else
-    {
-        DELETEA(musicfile);
-        DELETEA(musicdonecmd);
-    }
 }
 
 COMMAND(resetsound, "");
@@ -628,7 +401,7 @@ void initmumble()
             if(mumbleinfo) wcsncpy(mumbleinfo->name, L"Sauerbraten", 256);
         }
     #elif defined(_POSIX_SHARED_MEMORY_OBJECTS)
-        defformatstring(shmname)("/MumbleLink.%d", getuid());
+        s_sprintfd(shmname)("/MumbleLink.%d", getuid());
         mumblelink = shm_open(shmname, O_RDWR, 0);
         if(mumblelink >= 0)
         {
diff --git a/engine/textedit.h b/engine/textedit.h
index 7c28fc8..5ebd3f2 100644
--- a/engine/textedit.h
+++ b/engine/textedit.h
@@ -12,7 +12,7 @@ struct editline
         set(init);
     }
 
-    bool empty() { return len <= 0; }
+    bool empty() { return len <= 1; }
 
     void clear()
     {
@@ -71,11 +71,11 @@ struct editline
         len += slen;
     }
 
-    bool read(stream *f, int chop = -1)
+    bool read(FILE *f, int chop = -1)
     {
         if(chop < 0) chop = INT_MAX; else chop++;
         set("");
-        while(len + 1 < chop && f->getline(&text[len], min(maxlen, chop) - len))
+        while(len + 1 < chop && fgets(&text[len], min(maxlen, chop) - len, f))
         {
             len += strlen(&text[len]);
             if(len > 0 && text[len-1] == '\n')
@@ -88,7 +88,7 @@ struct editline
         if(len + 1 >= chop)
         {
             char buf[CHUNKSIZE];
-            while(f->getline(buf, sizeof(buf)))
+            while(fgets(buf, sizeof(buf), f))
             {
                 int blen = strlen(buf);
                 if(blen > 0 && buf[blen-1] == '\n') return true;
@@ -190,12 +190,12 @@ struct editor
     {
         if(!filename) return;
         clear(NULL);
-        stream *file = openfile(filename, "r");
+        FILE *file = openfile(filename, "r");
         if(file) 
         {
             while(lines.add().read(file, maxx) && (maxy < 0 || lines.length() <= maxy));
             lines.pop().clear();
-            delete file;
+            fclose(file);
         }
         if(lines.empty()) lines.add().set("");
     }
@@ -203,10 +203,10 @@ struct editor
     void save()
     {
         if(!filename) return;
-        stream *file = openfile(filename, "w");
+        FILE *file = openfile(filename, "w");
         if(!file) return;
-        loopv(lines) file->putline(lines[i].text);
-        delete file;
+        loopv(lines) fprintf(file, "%s\n", lines[i].text);
+        fclose(file);
     }
    
     void mark(bool enable) 
@@ -332,11 +332,7 @@ struct editor
     void del() // removes the current selection (if any)
     {
         int sx, sy, ex, ey;
-        if(!region(sx, sy, ex, ey)) 
-        { 
-            mark(false); 
-            return; 
-        }
+        if(!region(sx, sy, ex, ey)) return;
         if(sy == ey) 
         {
             if(sx == 0 || ex == lines[ey].len) removelines(sy, 1);
@@ -378,11 +374,6 @@ struct editor
         }
     }
 
-    void insert(const char *s)
-    {
-        while(*s) insert(*s++);
-    }
-
     void insertallfrom(editor *b) 
     {   
         if(b==this) return;
@@ -496,9 +487,6 @@ struct editor
                 }
                 break;
             }
-            case SDLK_LSHIFT:
-            case SDLK_RSHIFT:
-                break;
             case SDLK_RETURN:    
                 cooked = '\n';
                 // fall through
@@ -639,7 +627,7 @@ struct editor
 // a 'stack' where the last is the current focused editor
 static vector <editor*> editors;
 
-static editor *currentfocus() { return editors.length() ? editors.last() : NULL; }
+static editor *currentfocus() { return (editors.length() > 0)?editors.last():NULL; }
 
 static void readyeditors() 
 {
@@ -680,8 +668,8 @@ ICOMMAND(textlist, "", (), // @DEBUG return list of all the editors
     string s = "";
     loopv(editors)
     {   
-        if(i > 0) concatstring(s, ", ");
-        concatstring(s, editors[i]->name);
+        if(i > 0) s_strcat(s, ", ");
+        s_strcat(s, editors[i]->name);
     }
     result(s);
 );
@@ -700,7 +688,7 @@ TEXTCOMMAND(textmode, "i", (int *m), // (1= keep while focused, 2= keep while us
     if(*m) top->mode = *m;
     else
     {
-        defformatstring(s)("%d", top->mode);
+        s_sprintfd(s)("%d", top->mode);
         result(s);
     } 
 );
diff --git a/engine/texture.cpp b/engine/texture.cpp
index 51c06a2..de76dde 100644
--- a/engine/texture.cpp
+++ b/engine/texture.cpp
@@ -1,75 +1,18 @@
 // texture.cpp: texture slot management
 
+#include "pch.h"
 #include "engine.h"
 
-#define FUNCNAME(name) name##1
-#define DEFPIXEL uint OP(r, 0);
-#define PIXELOP OP(r, 0);
-#define BPP 1
-#include "scale.h"
-
-#define FUNCNAME(name) name##2
-#define DEFPIXEL uint OP(r, 0), OP(g, 1);
-#define PIXELOP OP(r, 0); OP(g, 1);
-#define BPP 2
-#include "scale.h"
-
-#define FUNCNAME(name) name##3
-#define DEFPIXEL uint OP(r, 0), OP(g, 1), OP(b, 2);
-#define PIXELOP OP(r, 0); OP(g, 1); OP(b, 2);
-#define BPP 3
-#include "scale.h"
-
-#define FUNCNAME(name) name##4
-#define DEFPIXEL uint OP(r, 0), OP(g, 1), OP(b, 2), OP(a, 3);
-#define PIXELOP OP(r, 0); OP(g, 1); OP(b, 2); OP(a, 3);
-#define BPP 4
-#include "scale.h"
-
-static void scaletexture(uchar *src, uint sw, uint sh, uint bpp, uint pitch, uchar *dst, uint dw, uint dh)
-{
-    if(sw == dw*2 && sh == dh*2)
-    {
-        switch(bpp)
-        {
-            case 1: return halvetexture1(src, sw, sh, pitch, dst);
-            case 2: return halvetexture2(src, sw, sh, pitch, dst);
-            case 3: return halvetexture3(src, sw, sh, pitch, dst);
-            case 4: return halvetexture4(src, sw, sh, pitch, dst);
-        }
-    }
-    else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1))
-    {
-        switch(bpp)
-        {
-            case 1: return scaletexture1(src, sw, sh, pitch, dst, dw, dh);
-            case 2: return scaletexture2(src, sw, sh, pitch, dst, dw, dh);
-            case 3: return scaletexture3(src, sw, sh, pitch, dst, dw, dh);
-            case 4: return scaletexture4(src, sw, sh, pitch, dst, dw, dh);
-        }
-    }
-    else
-    {
-        switch(bpp)
-        {
-            case 1: return shifttexture1(src, sw, sh, pitch, dst, dw, dh);
-            case 2: return shifttexture2(src, sw, sh, pitch, dst, dw, dh);
-            case 3: return shifttexture3(src, sw, sh, pitch, dst, dw, dh);
-            case 4: return shifttexture4(src, sw, sh, pitch, dst, dw, dh);
-        }
-    }
-}
-
-static inline void reorienttexture(uchar *src, int sw, int sh, int bpp, int stride, uchar *dst, bool flipx, bool flipy, bool swapxy, bool normals = false)
+static inline void reorienttexture(uchar *src, int sw, int sh, int bpp, uchar *dst, bool flipx, bool flipy, bool swapxy, bool normals = false)
 {
     int stridex = bpp, stridey = bpp;
     if(swapxy) stridex *= sh; else stridey *= sw;
     if(flipx) { dst += (sw-1)*stridex; stridex = -stridex; }
     if(flipy) { dst += (sh-1)*stridey; stridey = -stridey; }
-    uchar *srcrow = src;
     loopi(sh)
     {
-        for(uchar *curdst = dst, *src = srcrow, *end = &srcrow[sw*bpp]; src < end;)
+        uchar *curdst = dst;
+        loopj(sw)
         {
             loopk(bpp) curdst[k] = *src++;
             if(normals)
@@ -80,189 +23,147 @@ static inline void reorienttexture(uchar *src, int sw, int sh, int bpp, int stri
             }
             curdst += stridex;
         }
-        srcrow += stride;
         dst += stridey;
     }
 }
 
-#define writetex(t, body) \
-    { \
-        uchar *dstrow = t.data; \
-        loop(y, t.h) \
-        { \
-            for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp]; dst < end; dst += t.bpp) \
-            { \
-                body; \
-            } \
-            dstrow += t.pitch; \
-        } \
-    }
-
-#define readwritetex(t, s, body) \
-    { \
-        uchar *dstrow = t.data, *srcrow = s.data; \
-        loop(y, t.h) \
-        { \
-            for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.w*s.bpp]; src < end; dst += t.bpp, src += s.bpp) \
-            { \
-                body; \
-            } \
-            dstrow += t.pitch; \
-            srcrow += s.pitch; \
-        } \
-    }
-
-#define read2writetex(t, s1, src1, s2, src2, body) \
-    { \
-        uchar *dstrow = t.data, *src1row = s1.data, *src2row = s2.data; \
-        loop(y, t.h) \
-        { \
-            for(uchar *dst = dstrow, *end = &dstrow[t.w*t.bpp], *src1 = src1row, *src2 = src2row; dst < end; dst += t.bpp, src1 += s1.bpp, src2 += s2.bpp) \
-            { \
-                body; \
-            } \
-            dstrow += t.pitch; \
-            src1row += s1.pitch; \
-            src2row += s2.pitch; \
-        } \
-    }
-
-void texreorient(ImageData &s, bool flipx, bool flipy, bool swapxy, int type = TEX_DIFFUSE)
+SDL_Surface *texreorient(SDL_Surface *s, bool flipx, bool flipy, bool swapxy, int type = TEX_DIFFUSE)
 {
-    ImageData d(swapxy ? s.h : s.w, swapxy ? s.w : s.h, s.bpp);
-    reorienttexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, flipx, flipy, swapxy, type==TEX_NORMAL);
-    s.replace(d);
+    SDL_Surface *d = SDL_CreateRGBSurface(SDL_SWSURFACE, swapxy ? s->h : s->w, swapxy ? s->w : s->h, s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, s->format->Bmask, s->format->Amask);
+    if(!d) fatal("create surface");
+    reorienttexture((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel, (uchar *)d->pixels, flipx, flipy, swapxy, type==TEX_NORMAL);
+    SDL_FreeSurface(s);
+    return d;
 }
 
-void texrotate(ImageData &s, int numrots, int type = TEX_DIFFUSE)
+SDL_Surface *texrotate(SDL_Surface *s, int numrots, int type = TEX_DIFFUSE)
 {
     // 1..3 rotate through 90..270 degrees, 4 flips X, 5 flips Y 
-    if(numrots>=1 && numrots<=5) 
-        texreorient(s, 
-            numrots>=2 && numrots<=4, // flip X on 180/270 degrees
-            numrots<=2 || numrots==5, // flip Y on 90/180 degrees
-            (numrots&5)==1,           // swap X/Y on 90/270 degrees
-            type);
+    if(numrots<1 || numrots>5) return s; 
+    return texreorient(s, 
+        numrots>=2 && numrots<=4, // flip X on 180/270 degrees
+        numrots<=2 || numrots==5, // flip Y on 90/180 degrees
+        (numrots&5)==1,           // swap X/Y on 90/270 degrees
+        type);
 }
 
-void texoffset(ImageData &s, int xoffset, int yoffset)
+SDL_Surface *texoffset(SDL_Surface *s, int xoffset, int yoffset)
 {
     xoffset = max(xoffset, 0);
-    xoffset %= s.w;
+    xoffset %= s->w;
     yoffset = max(yoffset, 0);
-    yoffset %= s.h;
-    if(!xoffset && !yoffset) return;
-    ImageData d(s.w, s.h, s.bpp);
-    uchar *src = s.data;
-    loop(y, s.h)
+    yoffset %= s->h;
+    if(!xoffset && !yoffset) return s;
+    SDL_Surface *d = SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, s->format->Bmask, s->format->Amask);
+    if(!d) fatal("create surface");
+    int depth = s->format->BytesPerPixel;
+    uchar *src = (uchar *)s->pixels;
+    loop(y, s->h)
     {
-        uchar *dst = (uchar *)d.data+((y+yoffset)%d.h)*d.pitch;
-        memcpy(dst+xoffset*s.bpp, src, (s.w-xoffset)*s.bpp);
-        memcpy(dst, src+(s.w-xoffset)*s.bpp, xoffset*s.bpp);
-        src += s.pitch;
+        uchar *dst = (uchar *)d->pixels+((y+yoffset)%d->h)*d->pitch;
+        memcpy(dst+xoffset*depth, src, (s->w-xoffset)*depth);
+        memcpy(dst, src+(s->w-xoffset)*depth, xoffset*depth);
+        src += s->pitch;
     }
-    s.replace(d);
+    SDL_FreeSurface(s);
+    return d;
 }
 
-void texmad(ImageData &s, const vec &mul, const vec &add)
+void texmad(SDL_Surface *s, const vec &mul, const vec &add)
 {
-    int maxk = min(int(s.bpp), 3);
-    writetex(s,
-        loopk(maxk) dst[k] = uchar(clamp(dst[k]*mul[k] + 255*add[k], 0.0f, 255.0f));
-    );
+    int maxk = min(int(s->format->BytesPerPixel), 3);
+    uchar *src = (uchar *)s->pixels;
+    loopi(s->h*s->w) 
+    {
+        loopk(maxk)
+        {
+            float val = src[k]*mul[k] + 255*add[k];
+            src[k] = uchar(min(max(val, 0.0f), 255.0f));
+        }
+        src += s->format->BytesPerPixel;
+    }
 }
 
-void texffmask(ImageData &s, int minval)
-{
-    if(renderpath!=R_FIXEDFUNCTION) return;
-    if(nomasks || s.bpp<3) { s.cleanup(); return; }
-    bool glow = false, envmap = true;
-    writetex(s,
-        if(dst[1]>minval) glow = true;
-        if(dst[2]>minval) { glow = envmap = true; goto needmask; }
-    );
-    if(!glow && !envmap) { s.cleanup(); return; }
-needmask:
-    ImageData m(s.w, s.h, envmap ? 2 : 1);
-    readwritetex(m, s,
-        dst[0] = src[1];
-        if(envmap) dst[1] = src[2];
-    );
-    s.replace(m);
-}
+static SDL_Surface stubsurface;
 
-void texdup(ImageData &s, int srcchan, int dstchan)
+SDL_Surface *texffmask(SDL_Surface *s, int minval)
 {
-    if(srcchan==dstchan || max(srcchan, dstchan) >= s.bpp) return;
-    writetex(s, dst[dstchan] = dst[srcchan]);
+    if(renderpath!=R_FIXEDFUNCTION) return s;
+    if(nomasks || s->format->BytesPerPixel<3) { SDL_FreeSurface(s); return &stubsurface; }
+    bool glow = false, envmap = true;
+    uchar *src = (uchar *)s->pixels;
+    loopi(s->h*s->w)
+    {
+        if(src[1]>minval) glow = true;
+        if(src[2]>minval) { glow = envmap = true; break; }
+        src += s->format->BytesPerPixel;
+    }
+    if(!glow && !envmap) { SDL_FreeSurface(s); return &stubsurface; }
+    SDL_Surface *m = SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, envmap ? 16 : 8, 0, 0, 0, 0);
+    if(!m) fatal("create surface");
+    uchar *dst = (uchar *)m->pixels;
+    src = (uchar *)s->pixels;
+    loopi(s->h*s->w)
+    {
+        *dst++ = src[1];
+        if(envmap) *dst++ = src[2];
+        src += s->format->BytesPerPixel;
+    }
+    SDL_FreeSurface(s);
+    return m;
 }
 
-void texdecal(ImageData &s)
+void texdup(SDL_Surface *s, int srcchan, int dstchan)
 {
-    if(renderpath!=R_FIXEDFUNCTION || hasTE) return;
-    ImageData m(s.w, s.w, 2);
-    readwritetex(m, s,
-        dst[0] = src[0];
-        dst[1] = 255 - src[0];
-    );
-    s.replace(m);
+    if(srcchan==dstchan || max(srcchan, dstchan) >= s->format->BytesPerPixel) return;
+    uchar *src = (uchar *)s->pixels;
+    loopi(s->h*s->w)
+    {
+        src[dstchan] = src[srcchan];
+        src += s->format->BytesPerPixel;
+    }
 }
 
-void texmix(ImageData &s, int c1, int c2, int c3, int c4)
+SDL_Surface *texdecal(SDL_Surface *s)
 {
-    int numchans = c1 < 0 ? 0 : (c2 < 0 ? 1 : (c3 < 0 ? 2 : (c4 < 0 ? 3 : 4)));
-    if(numchans <= 0) return;
-    ImageData d(s.w, s.h, numchans);
-    readwritetex(d, s,
-        switch(numchans)
-        {
-            case 4: dst[3] = src[c4];
-            case 3: dst[2] = src[c3];
-            case 2: dst[1] = src[c2];
-            case 1: dst[0] = src[c1];
-        }
-    );
-    s.replace(d);
+    if(renderpath!=R_FIXEDFUNCTION || hasTE) return s;
+    SDL_Surface *m = SDL_CreateRGBSurface(SDL_SWSURFACE, s->w, s->h, 16, 0, 0, 0, 0);
+    if(!m) fatal("create surface");
+    uchar *dst = (uchar *)m->pixels, *src = (uchar *)s->pixels;
+    loopi(s->h*s->w)
+    {
+        *dst++ = *src;
+        *dst++ = 255 - *src;
+        src += s->format->BytesPerPixel;
+    }
+    SDL_FreeSurface(s);
+    return m;
 }
 
 VAR(hwtexsize, 1, 0, 0);
 VAR(hwcubetexsize, 1, 0, 0);
 VAR(hwmaxaniso, 1, 0, 0);
 VARFP(maxtexsize, 0, 0, 1<<12, initwarning("texture quality", INIT_LOAD));
-VARFP(reducefilter, 0, 1, 1, initwarning("texture quality", INIT_LOAD));
 VARFP(texreduce, 0, 0, 12, initwarning("texture quality", INIT_LOAD));
 VARFP(texcompress, 0, 1<<10, 1<<12, initwarning("texture quality", INIT_LOAD));
-VARFP(texcompressquality, -1, -1, 1, setuptexcompress());
 VARFP(trilinear, 0, 1, 1, initwarning("texture filtering", INIT_LOAD));
 VARFP(bilinear, 0, 1, 1, initwarning("texture filtering", INIT_LOAD));
 VARFP(aniso, 0, 0, 16, initwarning("texture filtering", INIT_LOAD));
 
-void setuptexcompress()
-{
-    if(!hasTC) return;
-
-    GLenum hint = GL_DONT_CARE;
-    switch(texcompressquality)
-    {
-        case 1: hint = GL_NICEST; break;
-        case 0: hint = GL_FASTEST; break;
-    }
-    glHint(GL_TEXTURE_COMPRESSION_HINT_ARB, hint);
-}
-
-GLenum compressedformat(GLenum format, int w, int h, int force = 0)
+GLenum compressedformat(GLenum format, int w, int h, bool force = false)
 {
+#ifdef __APPLE__
+#undef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
+#undef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT GL_COMPRESSED_RGB_ARB
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT GL_COMPRESSED_RGBA_ARB
+#endif
     if(hasTC && texcompress && (force || max(w, h) >= texcompress)) switch(format)
     {
         case GL_RGB5:
         case GL_RGB8:
-#ifdef __APPLE__
-        case GL_RGB: return GL_COMPRESSED_RGB_ARB;
-        case GL_RGBA: return GL_COMPRESSED_RGBA_ARB;
-#else
         case GL_RGB: return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
         case GL_RGBA: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
-#endif
     }
     return format;
 }
@@ -281,119 +182,10 @@ int formatsize(GLenum format)
 }
 
 VARFP(hwmipmap, 0, 0, 1, initwarning("texture filtering", INIT_LOAD));
-VARFP(usenp2, 0, 0, 1, initwarning("texture quality", INIT_LOAD));
-
-void resizetexture(int w, int h, bool mipmap, bool canreduce, GLenum target, int compress, int &tw, int &th)
-{
-    int hwlimit = target==GL_TEXTURE_CUBE_MAP_ARB ? hwcubetexsize : hwtexsize,
-        sizelimit = mipmap && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit;
-    if(compress && !hasTC)
-    {
-        w = max(w/compress, 1);
-        h = max(h/compress, 1);
-    }
-    if(canreduce && texreduce)
-    {
-        w = max(w>>texreduce, 1);
-        h = max(h>>texreduce, 1);
-    }
-    w = min(w, sizelimit);
-    h = min(h, sizelimit);
-    if((!hasNP2 || !usenp2) && target!=GL_TEXTURE_RECTANGLE_ARB && (w&(w-1) || h&(h-1)))
-    {
-        tw = th = 1;
-        while(tw < w) tw *= 2;
-        while(th < h) th *= 2;
-        if(w < tw - tw/4) tw /= 2;
-        if(h < th - th/4) th /= 2;
-    }
-    else
-    {
-        tw = w;
-        th = h;
-    }
-}
 
-void uploadtexture(GLenum target, GLenum internal, int tw, int th, GLenum format, GLenum type, void *pixels, int pw, int ph, int pitch, bool mipmap)
-{
-    int bpp = formatsize(format), row = 0, rowalign = 0;
-    if(!pitch) pitch = pw*bpp; 
-    uchar *buf = NULL;
-    if(pw!=tw || ph!=th) 
-    {
-        buf = new uchar[tw*th*bpp];
-        scaletexture((uchar *)pixels, pw, ph, bpp, pitch, buf, tw, th);
-    }
-    else if(tw*bpp != pitch)
-    {
-        row = pitch/bpp;
-        rowalign = texalign(pixels, pitch, 1);
-        while(rowalign > 0 && ((row*bpp + rowalign - 1)/rowalign)*rowalign != pitch) rowalign >>= 1;
-        if(!rowalign)
-        {
-            row = 0;
-            buf = new uchar[tw*th*bpp];
-            loopi(th) memcpy(&buf[i*tw*bpp], &((uchar *)pixels)[i*pitch], tw*bpp);
-        }
-    }
-    for(int level = 0, align = 0;; level++)
-    {
-        uchar *src = buf ? buf : (uchar *)pixels;
-        if(buf) pitch = tw*bpp;
-        int srcalign = row > 0 ? rowalign : texalign(src, pitch, 1);
-        if(align != srcalign) glPixelStorei(GL_UNPACK_ALIGNMENT, align = srcalign);
-        if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row);
-        extern int ati_teximage_bug;
-        if(ati_teximage_bug && (internal==GL_RGB || internal==GL_RGB8) && mipmap && src && !level)
-        {
-            if(target==GL_TEXTURE_1D) 
-            {
-                glTexImage1D(target, level, internal, tw, 0, format, type, NULL);
-                glTexSubImage1D(target, level, 0, tw, format, type, src);
-            }
-            else 
-            {
-                glTexImage2D(target, level, internal, tw, th, 0, format, type, NULL);
-                glTexSubImage2D(target, level, 0, 0, tw, th, format, type, src);
-            }
-        }
-        else if(target==GL_TEXTURE_1D) glTexImage1D(target, level, internal, tw, 0, format, type, src);
-        else glTexImage2D(target, level, internal, tw, th, 0, format, type, src);
-        if(row > 0) glPixelStorei(GL_UNPACK_ROW_LENGTH, row = 0);
-        if(!mipmap || (hasGM && hwmipmap) || max(tw, th) <= 1) break;
-        int srcw = tw, srch = th;
-        if(tw > 1) tw /= 2;
-        if(th > 1) th /= 2;
-        if(!buf) buf = new uchar[tw*th*bpp];
-        scaletexture(src, srcw, srch, bpp, pitch, buf, tw, th);
-    }
-    if(buf) delete[] buf;
-}
-
-void uploadcompressedtexture(GLenum target, GLenum format, int w, int h, uchar *data, int align, int blocksize, int levels, bool mipmap)
-{
-    int hwlimit = target==GL_TEXTURE_CUBE_MAP_ARB ? hwcubetexsize : hwtexsize,
-        sizelimit = levels > 1 && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit;
-    int level = 0;
-    loopi(levels)
-    {
-        int size = ((w + align-1)/align) * ((h + align-1)/align) * blocksize;
-        if(w <= sizelimit && h <= sizelimit)
-        {
-            if(target==GL_TEXTURE_1D) glCompressedTexImage1D_(target, level, format, w, 0, size, data);
-            else glCompressedTexImage2D_(target, level, format, w, h, 0, size, data);
-            level++;
-            if(!mipmap) break;
-        }
-        if(max(w, h) <= 1) break;
-        if(w > 1) w /= 2;
-        if(h > 1) h /= 2;
-        data += size;
-    }
-}
-
-GLenum textarget(GLenum subtarget)
+void createtexture(int tnum, int w, int h, void *pixels, int clamp, bool mipit, GLenum component, GLenum subtarget, bool compress, bool filter)
 {
+    GLenum target = subtarget;
     switch(subtarget)
     {
         case GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB:
@@ -402,48 +194,27 @@ GLenum textarget(GLenum subtarget)
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB:
         case GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB:
         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB:
-            return GL_TEXTURE_CUBE_MAP_ARB;
+            target = GL_TEXTURE_CUBE_MAP_ARB;
             break;
     }
-    return subtarget;
-}
-
-GLenum uncompressedformat(GLenum format)
-{
-    switch(format)
-    {
-        case GL_COMPRESSED_RGB_ARB:
-        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
-            return GL_RGB;
-        case GL_COMPRESSED_RGBA_ARB:
-        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
-        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
-        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
-            return GL_RGBA;
-    }
-    return GL_FALSE;
-}
-    
-void setuptexparameters(int tnum, void *pixels, int clamp, int filter, GLenum format, GLenum target)
-{
-    glBindTexture(target, tnum);
-    glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
-    if(target!=GL_TEXTURE_1D) glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
-    if(target==GL_TEXTURE_2D && hasAF && min(aniso, hwmaxaniso) > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(aniso, hwmaxaniso));
-    glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST);
-    glTexParameteri(target, GL_TEXTURE_MIN_FILTER,
-        filter > 1 ?
-            (trilinear ?
-                (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) :
-                (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) :
-            (filter && bilinear ? GL_LINEAR : GL_NEAREST));
-    if(hasGM && filter > 1 && pixels && hwmipmap && !uncompressedformat(format))
-        glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
-}
-
-void createtexture(int tnum, int w, int h, void *pixels, int clamp, int filter, GLenum component, GLenum subtarget, int pw, int ph, int pitch, bool resize)
-{
-    GLenum target = textarget(subtarget), format = component, type = GL_UNSIGNED_BYTE;
+    if(tnum)
+    {
+        glBindTexture(target, tnum);
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+        glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
+        if(target!=GL_TEXTURE_1D) glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : GL_REPEAT);
+        if(target==GL_TEXTURE_2D && hasAF && min(aniso, hwmaxaniso) > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, min(aniso, hwmaxaniso));
+        glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST);
+        glTexParameteri(target, GL_TEXTURE_MIN_FILTER, 
+            mipit ?
+                (trilinear ? 
+                    (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) : 
+                    (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) :
+                (filter && bilinear ? GL_LINEAR : GL_NEAREST));
+        if(hasGM && mipit && pixels)
+            glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, hwmipmap ? GL_TRUE : GL_FALSE);
+    }
+    GLenum format = component, type = GL_UNSIGNED_BYTE;
     switch(component)
     {
         case GL_FLOAT_RG16_NV:
@@ -460,47 +231,52 @@ void createtexture(int tnum, int w, int h, void *pixels, int clamp, int filter,
             type = GL_FLOAT;
             break;
 
-        case GL_DEPTH_COMPONENT16:
-        case GL_DEPTH_COMPONENT24:
-        case GL_DEPTH_COMPONENT32:
-            format = GL_DEPTH_COMPONENT;
+        case GL_DEPTH_COMPONENT:
+            type = GL_FLOAT;
             break;
 
         case GL_RGB5:
         case GL_RGB8:
         case GL_RGB16:
-        case GL_COMPRESSED_RGB_ARB:
-        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
             format = GL_RGB;
             break;
 
         case GL_RGBA8:
         case GL_RGBA16:
-        case GL_COMPRESSED_RGBA_ARB:
-        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
-        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
-        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
             format = GL_RGBA;
             break;
     }
-    if(tnum) setuptexparameters(tnum, pixels, clamp, filter, format, target);
-    if(!pw) pw = w;
-    if(!ph) ph = h;
-    int tw = w, th = h;
-    bool mipmap = filter > 1 && pixels;
-    if(resize && pixels) 
+    uchar *scaled = NULL;
+    int hwlimit = target==GL_TEXTURE_CUBE_MAP_ARB ? hwcubetexsize : hwtexsize,
+        sizelimit = mipit && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit;
+    if(pixels && max(w, h) > sizelimit && (!mipit || sizelimit < hwlimit))
+    {
+        int oldw = w, oldh = h;
+        while(w > sizelimit || h > sizelimit) { w /= 2; h /= 2; }
+        scaled = new uchar[w*h*formatsize(format)];
+        gluScaleImage(format, oldw, oldh, type, pixels, w, h, type, scaled);
+        pixels = scaled;
+    }
+    if(mipit && pixels)
     {
-        resizetexture(w, h, mipmap, false, target, 0, tw, th);
-        if(mipmap) component = compressedformat(component, tw, th);
+        GLenum compressed = compressedformat(component, w, h, compress);
+        if(target==GL_TEXTURE_1D)
+        {
+            if(hasGM && hwmipmap) glTexImage1D(subtarget, 0, compressed, w, 0, format, type, pixels);
+            else if(gluBuild1DMipmaps(subtarget, compressed, w, format, type, pixels))
+            {
+                if(compressed==component || gluBuild1DMipmaps(subtarget, component, w, format, type, pixels)) conoutf(CON_ERROR, "could not build mipmaps");
+            }
+        }
+        else if(hasGM && hwmipmap) glTexImage2D(subtarget, 0, compressed, w, h, 0, format, type, pixels);
+        else if(gluBuild2DMipmaps(subtarget, compressed, w, h, format, type, pixels))
+        {
+            if(compressed==component || gluBuild2DMipmaps(subtarget, component, w, h, format, type, pixels)) conoutf(CON_ERROR, "could not build mipmaps");
+        }
     }
-    uploadtexture(subtarget, component, tw, th, format, type, pixels, pw, ph, pitch, mipmap); 
-}
-
-void createcompressedtexture(int tnum, int w, int h, uchar *data, int align, int blocksize, int levels, int clamp, int filter, GLenum format, GLenum subtarget)
-{
-    GLenum target = textarget(subtarget);
-    if(tnum) setuptexparameters(tnum, data, clamp, filter, format, target);
-    uploadcompressedtexture(target, format, w, h, data, align, blocksize, levels, filter > 1); 
+    else if(target==GL_TEXTURE_1D) glTexImage1D(subtarget, 0, component, w, 0, format, type, pixels);
+    else glTexImage2D(subtarget, 0, component, w, h, 0, format, type, pixels);
+    if(scaled) delete[] scaled;
 }
 
 hashtable<char *, Texture> textures;
@@ -511,24 +287,38 @@ static GLenum texformat(int bpp)
 {
     switch(bpp)
     {
-        case 1: return GL_LUMINANCE;
-        case 2: return GL_LUMINANCE_ALPHA;
-        case 3: return GL_RGB;
-        case 4: return GL_RGBA;
+        case 8: return GL_LUMINANCE;
+        case 16: return GL_LUMINANCE_ALPHA;
+        case 24: return GL_RGB;
+        case 32: return GL_RGBA;
         default: return 0;
     }
 }
 
-int texalign(void *data, int w, int bpp)
+static void resizetexture(int &w, int &h, bool mipit = true, GLenum format = GL_RGB, GLenum target = GL_TEXTURE_2D)
 {
-    size_t address = size_t(data) | (w*bpp);
-    if(address&1) return 1;
-    if(address&2) return 2;
-    if(address&4) return 4;
-    return 8;
+    if(mipit) return;
+    int hwlimit = target==GL_TEXTURE_CUBE_MAP_ARB ? hwcubetexsize : hwtexsize,
+        sizelimit = mipit && maxtexsize ? min(maxtexsize, hwlimit) : hwlimit;
+    int w2 = w, h2 = h;
+    if(!hasNP2 && (w&(w-1) || h&(h-1)))
+    {
+        w2 = h2 = 1;
+        while(w2 < w) w2 *= 2;
+        while(h2 < h) h2 *= 2;
+        if(w2 > sizelimit || (w - w2)/2 < (w2 - w)/2) w2 /= 2;
+        if(h2 > sizelimit || (h - h2)/2 < (h2 - h)/2) h2 /= 2;
+    }
+    while(w2 > sizelimit || h2 > sizelimit)
+    {
+        w2 /= 2;
+        h2 /= 2;
+    }
+    w = w2;
+    h = h2;
 }
-    
-static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clamp = 0, bool mipit = true, bool canreduce = false, bool transient = false, int compress = 0)
+
+static Texture *newtexture(Texture *t, const char *rname, SDL_Surface *s, int clamp = 0, bool mipit = true, bool canreduce = false, bool transient = false, bool compress = false)
 {
     if(!t)
     {
@@ -539,251 +329,66 @@ static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clam
 
     t->clamp = clamp;
     t->mipmap = mipit;
-    t->type = Texture::IMAGE;
-    if(transient) t->type |= Texture::TRANSIENT;
-    if(!s.data)
+    t->type = s==&stubsurface ? Texture::STUB : (transient ? Texture::TRANSIENT : Texture::IMAGE);
+    if(t->type==Texture::STUB)
     {
-        t->type |= Texture::STUB;
         t->w = t->h = t->xs = t->ys = t->bpp = 0;
         return t;
     }
-    t->bpp = s.compressed ? formatsize(uncompressedformat(s.compressed)) : s.bpp;
-    t->w = t->xs = s.w;
-    t->h = t->ys = s.h;
+    t->bpp = s->format->BitsPerPixel;
+    t->w = t->xs = s->w;
+    t->h = t->ys = s->h;
 
-    int filter = !canreduce || reducefilter ? (mipit ? 2 : 1) : 0;
     glGenTextures(1, &t->id);
-    if(s.compressed)
+    if(canreduce) loopi(texreduce)
     {
-        uchar *data = s.data;
-        int levels = s.levels, level = 0;
-        if(canreduce && texreduce) loopi(min(texreduce, s.levels-1))
-        {
-            data += s.calclevelsize(level++);
-            levels--;
-            if(t->w > 1) t->w /= 2;
-            if(t->h > 1) t->h /= 2;
-        } 
-        int sizelimit = mipit && maxtexsize ? min(maxtexsize, hwtexsize) : hwtexsize;
-        while(t->w > sizelimit || t->h > sizelimit)
-        {
-            data += s.calclevelsize(level++);
-            levels--;
-            if(t->w > 1) t->w /= 2;
-            if(t->h > 1) t->h /= 2;
-        }
-        createcompressedtexture(t->id, t->w, t->h, data, s.align, s.bpp, levels, clamp, filter, s.compressed, GL_TEXTURE_2D);
+        if(t->w > 1) t->w /= 2;
+        if(t->h > 1) t->h /= 2;
     }
-    else
+    GLenum format = texformat(t->bpp);
+    resizetexture(t->w, t->h, mipit, format);
+    uchar *pixels = (uchar *)s->pixels;
+    if(t->w != t->xs || t->h != t->ys)
     {
-        resizetexture(t->w, t->h, mipit, canreduce, GL_TEXTURE_2D, compress, t->w, t->h);
-        GLenum format = compressedformat(texformat(t->bpp), t->w, t->h, compress);
-        createtexture(t->id, t->w, t->h, s.data, clamp, filter, format, GL_TEXTURE_2D, t->xs, t->ys, s.pitch, false);
+        if(t->w*t->h > t->xs*t->ys) pixels = new uchar[formatsize(format)*t->w*t->h];
+        gluScaleImage(format, t->xs, t->ys, GL_UNSIGNED_BYTE, s->pixels, t->w, t->h, GL_UNSIGNED_BYTE, pixels);
     }
+    createtexture(t->id, t->w, t->h, pixels, clamp, mipit, format, GL_TEXTURE_2D, compress);
+    if(pixels!=s->pixels) delete[] pixels;
+    SDL_FreeSurface(s);
     return t;
 }
 
 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
-#define RGBAMASKS 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff
-#define RGBMASKS  0xff0000, 0x00ff00, 0x0000ff, 0
+#define RMASK 0xff000000
+#define GMASK 0x00ff0000
+#define BMASK 0x0000ff00
+#define AMASK 0x000000ff
 #else
-#define RGBAMASKS 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000
-#define RGBMASKS  0x0000ff, 0x00ff00, 0xff0000, 0
+#define RMASK 0x000000ff
+#define GMASK 0x0000ff00
+#define BMASK 0x00ff0000
+#define AMASK 0xff000000
 #endif
 
-SDL_Surface *wrapsurface(void *data, int width, int height, int bpp)
-{
-    switch(bpp)
-    {
-        case 3: return SDL_CreateRGBSurfaceFrom(data, width, height, 8*bpp, bpp*width, RGBMASKS);
-        case 4: return SDL_CreateRGBSurfaceFrom(data, width, height, 8*bpp, bpp*width, RGBAMASKS);
-    }
-    return NULL;
-}
-
-SDL_Surface *creatergbsurface(SDL_Surface *os)
+SDL_Surface *creatergbasurface(SDL_Surface *os)
 {
-    SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 24, RGBMASKS);
-    if(ns) SDL_BlitSurface(os, NULL, ns, NULL);
+    SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, RMASK, GMASK, BMASK, AMASK);
+    if(!ns) fatal("creatergbsurface");
+    SDL_BlitSurface(os, NULL, ns, NULL);
     SDL_FreeSurface(os);
     return ns;
 }
 
-SDL_Surface *creatergbasurface(SDL_Surface *os)
+SDL_Surface *scalesurface(SDL_Surface *os, int w, int h)
 {
-    SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, RGBAMASKS);
-    if(ns) 
-    {
-        SDL_SetAlpha(os, 0, 0);
-        SDL_BlitSurface(os, NULL, ns, NULL);
-    }
+    SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, os->format->BitsPerPixel, os->format->Rmask, os->format->Gmask, os->format->Bmask, os->format->Amask);
+    if(!ns) fatal("scalesurface");
+    gluScaleImage(texformat(os->format->BitsPerPixel), os->w, os->h, GL_UNSIGNED_BYTE, os->pixels, w, h, GL_UNSIGNED_BYTE, ns->pixels);
     SDL_FreeSurface(os);
     return ns;
 }
 
-bool checkgrayscale(SDL_Surface *s)
-{
-    // gray scale images have 256 levels, no colorkey, and the palette is a ramp
-    if(s->format->palette)
-    {
-        if(s->format->palette->ncolors != 256 || s->format->colorkey) return false;
-        const SDL_Color *colors = s->format->palette->colors;
-        loopi(256) if(colors[i].r != i || colors[i].g != i || colors[i].b != i) return false;
-    }
-    return true;
-}
-
-SDL_Surface *fixsurfaceformat(SDL_Surface *s)
-{
-    if(!s) return NULL;
-    if(!s->pixels || min(s->w, s->h) <= 0 || s->format->BytesPerPixel <= 0)
-    { 
-        SDL_FreeSurface(s); 
-        return NULL; 
-    }
-    static const uint rgbmasks[] = { RGBMASKS }, rgbamasks[] = { RGBAMASKS };
-    switch(s->format->BytesPerPixel)
-    {
-        case 1:
-            if(!checkgrayscale(s)) return s->format->colorkey ? creatergbasurface(s) : creatergbsurface(s);
-            break;
-        case 3:
-            if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2]) 
-                return creatergbsurface(s);
-            break;
-        case 4:
-            if(s->format->Rmask != rgbamasks[0] || s->format->Gmask != rgbamasks[1] || s->format->Bmask != rgbamasks[2] || s->format->Amask != rgbamasks[3])
-                return creatergbasurface(s);
-            break;
-    }
-    return s;
-}
-
-void texflip(ImageData &s)
-{
-    ImageData d(s.w, s.h, s.bpp);
-    uchar *dst = d.data, *src = &s.data[s.pitch*s.h];
-    loopi(s.h)
-    {
-        src -= s.pitch;
-        memcpy(dst, src, s.bpp*s.w);
-        dst += d.pitch;
-    }
-    s.replace(d);
-}
-
-void texnormal(ImageData &s, int emphasis)    
-{
-    ImageData d(s.w, s.h, 3);
-    uchar *src = s.data, *dst = d.data;
-    loop(y, s.h) loop(x, s.w)
-    {
-        vec normal(0.0f, 0.0f, 255.0f/emphasis);
-        normal.x += src[y*s.pitch + ((x+s.w-1)%s.w)*s.bpp];
-        normal.x -= src[y*s.pitch + ((x+1)%s.w)*s.bpp];
-        normal.y += src[((y+s.h-1)%s.h)*s.pitch + x*s.bpp];
-        normal.y -= src[((y+1)%s.h)*s.pitch + x*s.bpp];
-        normal.normalize();
-        *dst++ = uchar(127.5f + normal.x*127.5f);
-        *dst++ = uchar(127.5f + normal.y*127.5f);
-        *dst++ = uchar(127.5f + normal.z*127.5f);
-    }
-    s.replace(d);
-}
-
-void scaleimage(ImageData &s, int w, int h)
-{
-    ImageData d(w, h, s.bpp);
-    scaletexture(s.data, s.w, s.h, s.bpp, s.pitch, d.data, w, h);
-    s.replace(d);
-}
-
-#define readwritergbtex(t, s, body) \
-    { \
-        if(t.bpp >= 3) { readwritetex(t, s, body); } \
-        else \
-        { \
-            ImageData rgb(t.w, t.h, 3); \
-            read2writetex(rgb, t, orig, s, src, \
-            { \
-                switch(t.bpp) \
-                { \
-                    case 1: dst[0] = orig[0]; dst[1] = orig[0]; dst[2] = orig[0]; break; \
-                    case 2: dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[1]; break; \
-                } \
-                body; \
-            }); \
-            t.replace(rgb); \
-        } \
-    }
-
-void forcergbimage(ImageData &s)
-{
-    if(s.bpp >= 3) return;
-    ImageData d(s.w, s.h, 3);
-    readwritetex(d, s,
-        switch(s.bpp)
-        {
-            case 1: dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; break;
-            case 2: dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[1]; break;
-        }
-    );
-    s.replace(d);
-}
-
-#define readwritergbatex(t, s, body) \
-    { \
-        if(t.bpp >= 4) { readwritetex(t, s, body); } \
-        else \
-        { \
-            ImageData rgba(t.w, t.h, 4); \
-            read2writetex(rgba, t, orig, s, src, \
-            { \
-                switch(t.bpp) \
-                { \
-                    case 1: dst[0] = orig[0]; dst[1] = orig[0]; dst[2] = orig[0]; break; \
-                    case 2: dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[1]; break; \
-                    case 3: dst[0] = orig[0]; dst[1] = orig[1]; dst[2] = orig[2]; break; \
-                } \
-                body; \
-            }); \
-            t.replace(rgba); \
-        } \
-    }
-
-void forcergbaimage(ImageData &s)
-{
-    if(s.bpp >= 4) return;
-    ImageData d(s.w, s.h, 4);
-    readwritetex(d, s,
-        switch(s.bpp)
-        {
-            case 1: dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; break;
-            case 2: dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[1]; break;
-            case 3: dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; break;
-        }
-    );
-    s.replace(d);
-}
-
-SDL_Surface *loadsurface(const char *name)
-{
-    SDL_Surface *s = NULL;
-    stream *z = openzipfile(name, "rb");
-    if(z)
-    {
-        SDL_RWops *rw = z->rwops();
-        if(rw) 
-        {
-            s = IMG_Load_RW(rw, 0);
-            SDL_FreeRW(rw);
-        }
-        delete z;
-    }
-    if(!s) s = IMG_Load(findfile(name, "rb"));
-    return fixsurfaceformat(s);
-}
-   
 static vec parsevec(const char *arg)
 {
     vec v(0, 0, 0);
@@ -797,26 +402,24 @@ static vec parsevec(const char *arg)
     return v;
 }
 
-VAR(usedds, 0, 1, 1);
-
-static bool texturedata(ImageData &d, const char *tname, Slot::Tex *tex = NULL, bool msg = true, int *compress = NULL)
+static SDL_Surface *texturedata(const char *tname, Slot::Tex *tex = NULL, bool msg = true, bool *compress = NULL)
 {
     const char *cmds = NULL, *file = tname;
 
     if(!tname)
     {
-        if(!tex) return false;
+        if(!tex) return NULL;
         if(tex->name[0]=='<') 
         {
             cmds = tex->name;
             file = strrchr(tex->name, '>');
-            if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture packages/%s", tex->name); return false; }
+            if(!file) { if(msg) conoutf(CON_ERROR, "could not load texture packages/%s", tex->name); return NULL; }
             file++;
         }
         else file = tex->name;
         
         static string pname;
-        formatstring(pname)("packages/%s", file);
+        s_sprintf(pname)("packages/%s", file);
         file = path(pname);
     }
     else if(tname[0]=='<') 
@@ -827,126 +430,96 @@ static bool texturedata(ImageData &d, const char *tname, Slot::Tex *tex = NULL,
         file++;
     }
 
-    bool raw = !usedds || !compress, dds = false;
-    for(const char *pcmds = cmds; pcmds;)
+    if(cmds)
     {
-        #define PARSETEXCOMMANDS(cmds) \
-            const char *cmd = NULL, *end = NULL, *arg[4] = { NULL, NULL, NULL, NULL }; \
-            cmd = &cmds[1]; \
-            end = strchr(cmd, '>'); \
-            if(!end) break; \
-            cmds = strchr(cmd, '<'); \
-            size_t len = strcspn(cmd, ":,><"); \
-            loopi(4) \
-            { \
-                arg[i] = strchr(i ? arg[i-1] : cmd, i ? ',' : ':'); \
-                if(!arg[i] || arg[i] >= end) arg[i] = ""; \
-                else arg[i]++; \
-            }
-        PARSETEXCOMMANDS(pcmds);
-        if(!strncmp(cmd, "noff", len))
-        {
-            if(renderpath==R_FIXEDFUNCTION) return true;
-        }
-        else if(!strncmp(cmd, "ffcolor", len) || !strncmp(cmd, "ffmask", len))
-        {
-            if(renderpath==R_FIXEDFUNCTION) raw = true;
-        }
-        else if(!strncmp(cmd, "decal", len))
-        {
-            if(renderpath==R_FIXEDFUNCTION && !hasTE) raw = true;
-        }
-        else if(!strncmp(cmd, "dds", len)) dds = true;
+        if(renderpath==R_FIXEDFUNCTION && !strncmp(cmds, "<noff>", 6)) return &stubsurface;
     }
+    
+    if(msg) show_out_of_renderloop_progress(0, file);
 
-    if(msg) renderprogress(loadprogress, file);
-
-    int flen = strlen(file);
-    if(flen >= 4 && (!strcasecmp(file + flen - 4, ".dds") || dds))
-    {
-        string dfile;
-        copystring(dfile, file);
-        memcpy(dfile + flen - 4, ".dds", 4);
-        if(!raw && hasTC && loaddds(dfile, d)) return true;
-        if(!dds) { if(msg) conoutf(CON_ERROR, "could not load texture %s", dfile); return false; }
-    }
-        
-    SDL_Surface *s = loadsurface(file);
-    if(!s) { if(msg) conoutf(CON_ERROR, "could not load texture %s", file); return false; }
+    SDL_Surface *s = IMG_Load(findfile(file, "rb"));
+    if(!s) { if(msg) conoutf(CON_ERROR, "could not load texture %s", file); return NULL; }
     int bpp = s->format->BitsPerPixel;
-    if(bpp%8 || !texformat(bpp/8)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture must be 8, 16, 24, or 32 bpp: %s", file); return false; }
-    if(max(s->w, s->h) > (1<<12)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture size exceeded %dx%d pixels: %s", 1<<12, 1<<12, file); return false; }
-    d.wrap(s);
+    if(!texformat(bpp)) { SDL_FreeSurface(s); conoutf(CON_ERROR, "texture must be 8, 16, 24, or 32 bpp: %s", file); return NULL; }
 
     while(cmds)
     {
-        PARSETEXCOMMANDS(cmds);
-        if(!strncmp(cmd, "mad", len)) texmad(d, parsevec(arg[0]), parsevec(arg[1])); 
-        else if(!strncmp(cmd, "ffcolor", len))
+        const char *cmd = NULL, *end = NULL, *arg[3] = { NULL, NULL, NULL };
+        cmd = &cmds[1];
+        end = strchr(cmd, '>');
+        if(!end) break;
+        cmds = strchr(cmd, '<');
+        size_t len = strcspn(cmd, ":,><");
+        loopi(3)
         {
-            if(renderpath==R_FIXEDFUNCTION) texmad(d, parsevec(arg[0]), parsevec(arg[1]));
+            arg[i] = strchr(i ? arg[i-1] : cmd, i ? ',' : ':');
+            if(!arg[i] || arg[i] >= end) arg[i] = "";
+            else arg[i]++;
         }
-        else if(!strncmp(cmd, "ffmask", len)) 
+        if(!strncmp(cmd, "mad", len)) texmad(s, parsevec(arg[0]), parsevec(arg[1])); 
+        else if(!strncmp(cmd, "ffcolor", len))
         {
-            texffmask(d, atoi(arg[0]));
-            if(!d.data) return true;
+            if(renderpath==R_FIXEDFUNCTION) texmad(s, parsevec(arg[0]), parsevec(arg[1]));
         }
-        else if(!strncmp(cmd, "normal", len)) 
+        else if(!strncmp(cmd, "ffmask", len)) 
         {
-            int emphasis = atoi(arg[0]);
-            texnormal(d, emphasis > 0 ? emphasis : 3);
+            s = texffmask(s, atoi(arg[0]));
+            if(s == &stubsurface) return s;
         }
-        else if(!strncmp(cmd, "dup", len)) texdup(d, atoi(arg[0]), atoi(arg[1]));
-        else if(!strncmp(cmd, "decal", len)) texdecal(d);
-        else if(!strncmp(cmd, "offset", len)) texoffset(d, atoi(arg[0]), atoi(arg[1]));
-        else if(!strncmp(cmd, "rotate", len)) texrotate(d, atoi(arg[0]), tex ? tex->type : 0);
-        else if(!strncmp(cmd, "reorient", len)) texreorient(d, atoi(arg[0])>0, atoi(arg[1])>0, atoi(arg[2])>0, tex ? tex->type : TEX_DIFFUSE);
-        else if(!strncmp(cmd, "mix", len)) texmix(d, *arg[0] ? atoi(arg[0]) : -1, *arg[1] ? atoi(arg[1]) : -1, *arg[2] ? atoi(arg[2]) : -1, *arg[3] ? atoi(arg[3]) : -1);
-        else if(!strncmp(cmd, "compress", len) || !strncmp(cmd, "dds", len)) 
+        else if(!strncmp(cmd, "dup", len)) texdup(s, atoi(arg[0]), atoi(arg[1]));
+        else if(!strncmp(cmd, "decal", len)) s = texdecal(s);
+        else if(!strncmp(cmd, "offset", len)) s = texoffset(s, atoi(arg[0]), atoi(arg[1]));
+        else if(!strncmp(cmd, "rotate", len)) s = texrotate(s, atoi(arg[0]), tex ? tex->type : 0);
+        else if(!strncmp(cmd, "reorient", len)) s = texreorient(s, atoi(arg[0])>0, atoi(arg[1])>0, atoi(arg[2])>0, tex ? tex->type : TEX_DIFFUSE);
+        else if(!strncmp(cmd, "compress", len)) 
         { 
-            int scale = atoi(arg[0]);
-            if(scale <= 0) scale = 2;
-            if(compress) *compress = scale;
+            if(compress) *compress = true; 
+            if(!hasTC)
+            {
+                int scale = atoi(arg[0]);
+                if(scale > 1) s = scalesurface(s, s->w/scale, s->h/scale);
+            }
         }
     }
 
-    return true;
+    return s;
 }
 
 void loadalphamask(Texture *t)
 {
-    if(t->alphamask || t->bpp!=4) return;
-    ImageData s;
-    if(!texturedata(s, t->name, NULL, false) || !s.data || s.bpp!=4 || s.compressed) return;
-    t->alphamask = new uchar[s.h * ((s.w+7)/8)];
-    uchar *srcrow = s.data, *dst = t->alphamask-1;
-    loop(y, s.h)
+    if(t->alphamask || t->bpp!=32) return;
+    SDL_Surface *s = texturedata(t->name, NULL, false);
+    if(!s || !s->format->Amask) { if(s) SDL_FreeSurface(s); return; }
+    uint alpha = s->format->Amask;
+    t->alphamask = new uchar[s->h * ((s->w+7)/8)];
+    uchar *srcrow = (uchar *)s->pixels, *dst = t->alphamask-1;
+    loop(y, s->h)
     {
-        uchar *src = srcrow+s.bpp-1;
-        loop(x, s.w)
+        uint *src = (uint *)srcrow;
+        loop(x, s->w)
         {
             int offset = x%8;
             if(!offset) *++dst = 0;
-            if(*src) *dst |= 1<<offset;
-            src += s.bpp;
+            if(*src & alpha) *dst |= 1<<offset;
+            src++;
         }
-        srcrow += s.pitch;
+        srcrow += s->pitch;
     }
+    SDL_FreeSurface(s);
 }
 
 Texture *textureload(const char *name, int clamp, bool mipit, bool msg)
 {
     string tname;
-    copystring(tname, name);
+    s_strcpy(tname, name);
     Texture *t = textures.access(path(tname));
     if(t) return t;
-    int compress = 0;
-    ImageData s;
-    if(texturedata(s, tname, NULL, msg, &compress)) return newtexture(NULL, tname, s, clamp, mipit, false, false, compress);
-    return notexture;
+    bool compress = false;
+    SDL_Surface *s = texturedata(tname, NULL, msg, &compress); 
+    return s ? newtexture(NULL, tname, s, clamp, mipit, false, false, compress) : notexture;
 }
 
-void settexture(const char *name, int clamp)
+void settexture(const char *name, bool clamp)
 {
     glBindTexture(GL_TEXTURE_2D, textureload(name, clamp, true, false)->id);
 }
@@ -954,22 +527,18 @@ void settexture(const char *name, int clamp)
 vector<Slot> slots;
 Slot materialslots[MATF_VOLUME+1];
 
-VAR(curtexnum, 1, 0, 0);
-int curmatslot = -1;
+int curtexnum = 0, curmatslot = -1;
 
-void texturereset(int *n)
+void texturereset()
 {
-    if(!overrideidents && !game::allowedittoggle()) return;
-    resetslotshader();
-    curtexnum = clamp(*n, 0, curtexnum);
-    slots.setsize(curtexnum);
+    curtexnum = 0;
+    slots.setsize(0);
 }
 
-COMMAND(texturereset, "i");
+COMMAND(texturereset, "");
 
 void materialreset()
 {
-    if(!overrideidents && !game::allowedittoggle()) return;
     loopi(MATF_VOLUME+1) materialslots[i].reset();
 }
 
@@ -997,7 +566,7 @@ void texture(char *type, char *name, int *rot, int *xoffset, int *yoffset, float
         if(matslot>=0) curmatslot = matslot;
         else { curmatslot = -1; curtexnum++; }
     }
-    else if(curmatslot>=0) matslot = curmatslot;
+    else if(curmatslot>=0) matslot=curmatslot;
     else if(!curtexnum) return;
     Slot &s = matslot>=0 ? materialslots[matslot] : (tnum!=TEX_DIFFUSE ? slots.last() : slots.add());
     s.loaded = false;
@@ -1007,7 +576,7 @@ void texture(char *type, char *name, int *rot, int *xoffset, int *yoffset, float
     st.type = tnum;
     st.combined = -1;
     st.t = NULL;
-    copystring(st.name, name);
+    s_strcpy(st.name, name);
     path(st.name);
     if(tnum==TEX_DIFFUSE)
     {
@@ -1025,7 +594,8 @@ void autograss(char *name)
 {
     Slot &s = slots.last();
     DELETEA(s.autograss);
-    s.autograss = name[0] ? newstring(makerelpath("packages", name)) : NULL;
+    s_sprintfd(pname)("packages/%s", name);
+    s.autograss = newstring(name[0] ? pname : "packages/textures/grass.png");
 }
 COMMAND(autograss, "s");
 
@@ -1063,75 +633,61 @@ void texscale(float *scale)
 }
 COMMAND(texscale, "f");
 
-void texlayer(int *layer, char *name, int *mode, float *scale)
-{
-    if(slots.empty()) return;
-    Slot &s = slots.last();
-    s.layer = *layer < 0 ? max(slots.length()-1+*layer, 0) : *layer;
-    s.layermaskname = name[0] ? newstring(path(makerelpath("packages", name))) : NULL; 
-    s.layermaskmode = *mode;
-    s.layermaskscale = *scale <= 0 ? 1 : *scale;
-}
-COMMAND(texlayer, "isif");
-
 static int findtextype(Slot &s, int type, int last = -1)
 {
     for(int i = last+1; i<s.sts.length(); i++) if((type&(1<<s.sts[i].type)) && s.sts[i].combined<0) return i;
     return -1;
 }
 
-static void addbump(ImageData &c, ImageData &n)
+#define writetex(t, body) \
+    { \
+        uchar *dst = (uchar *)t->pixels; \
+        loop(y, t->h) loop(x, t->w) \
+        { \
+            body; \
+            dst += t->format->BytesPerPixel; \
+        } \
+    }
+
+#define sourcetex(s) uchar *src = &((uchar *)s->pixels)[s->format->BytesPerPixel*(y*s->w + x)];
+
+static void addbump(SDL_Surface *c, SDL_Surface *n)
 {
-    if(n.bpp < 3) return;
-    readwritergbtex(c, n,
+    writetex(c,
+        sourcetex(n);
         loopk(3) dst[k] = int(dst[k])*(int(src[2])*2-255)/255;
     );
 }
 
-static void addglow(ImageData &c, ImageData &g, const vec &glowcolor)
+static void addglow(SDL_Surface *c, SDL_Surface *g, const vec &glowcolor)
 {
-    if(g.bpp < 3)
-    {
-        readwritergbtex(c, g,
-            loopk(3) dst[k] = clamp(int(dst[k]) + int(src[0]*glowcolor[k]), 0, 255);
-        );
-    }
-    else
-    {
-        readwritergbtex(c, g,
-            loopk(3) dst[k] = clamp(int(dst[k]) + int(src[k]*glowcolor[k]), 0, 255);
-        );
-    }
+    writetex(c,
+        sourcetex(g);
+        loopk(3) dst[k] = clamp(int(dst[k]) + int(src[k]*glowcolor[k]), 0, 255);
+    );
 }
 
-static void blenddecal(ImageData &c, ImageData &d)
+static void blenddecal(SDL_Surface *c, SDL_Surface *d)
 {
-    if(d.bpp < 4) return;
-    readwritergbtex(c, d,
+    writetex(c,
+        sourcetex(d);
         uchar a = src[3];
         loopk(3) dst[k] = (int(src[k])*int(a) + int(dst[k])*int(255-a))/255;
     );
 }
 
-static void mergespec(ImageData &c, ImageData &s)
+static void mergespec(SDL_Surface *c, SDL_Surface *s)
 {
-    if(s.bpp < 3)
-    {
-        readwritergbatex(c, s,
-            dst[3] = src[0];
-        );
-    }
-    else
-    {
-        readwritergbatex(c, s,
-            dst[3] = (int(src[0]) + int(src[1]) + int(src[2]))/3;
-        );
-    }
+    writetex(c,
+        sourcetex(s);
+        dst[3] = (int(src[0]) + int(src[1]) + int(src[2]))/3;
+    );
 }
 
-static void mergedepth(ImageData &c, ImageData &z)
+static void mergedepth(SDL_Surface *c, SDL_Surface *z)
 {
-    readwritergbatex(c, z,
+    writetex(c,
+        sourcetex(z);
         dst[3] = src[0];
     );
 }
@@ -1140,7 +696,7 @@ static void addname(vector<char> &key, Slot &slot, Slot::Tex &t, bool combined =
 {
     if(combined) key.add('&');
     if(prefix) { while(*prefix) key.add(*prefix++); }
-    defformatstring(tname)("packages/%s", t.name);
+    s_sprintfd(tname)("packages/%s", t.name);
     for(const char *s = path(tname); *s; key.add(*s++));
 }
 
@@ -1175,44 +731,46 @@ static void texcombine(Slot &s, int index, Slot::Tex &t, bool forceload = false)
     key.add('\0');
     t.t = textures.access(key.getbuf());
     if(t.t) return;
-    int compress = 0;
-    ImageData ts;
-    if(!texturedata(ts, NULL, &t, true, &compress)) { t.t = notexture; return; }
+    bool compress = false;
+    SDL_Surface *ts = texturedata(NULL, &t, true, &compress);
+    if(!ts) { t.t = notexture; return; }
     switch(t.type)
     {
         case TEX_DIFFUSE:
             if(renderpath==R_FIXEDFUNCTION)
             {
-                if(!ts.compressed) loopv(s.sts)
+                loopv(s.sts)
                 {
                     Slot::Tex &b = s.sts[i];
                     if(b.combined!=index) continue;
-                    ImageData bs;
-                    if(!texturedata(bs, NULL, &b)) continue;
-                    if(bs.w!=ts.w || bs.h!=ts.h) scaleimage(bs, ts.w, ts.h);
+                    SDL_Surface *bs = texturedata(NULL, &b);
+                    if(!bs) continue;
+                    if(bs->w!=ts->w || bs->h!=ts->h) bs = scalesurface(bs, ts->w, ts->h);
                     switch(b.type)
                     {
-                        case TEX_DECAL: blenddecal(ts, bs); break;
+                        case TEX_DECAL: if(bs->format->BitsPerPixel==32) blenddecal(ts, bs); break;
                         case TEX_NORMAL: addbump(ts, bs); break;
                     }
+                    SDL_FreeSurface(bs);
                 }
                 break;
             } // fall through to shader case
 
         case TEX_NORMAL:
-            if(!ts.compressed) loopv(s.sts)
+            loopv(s.sts)
             {
                 Slot::Tex &a = s.sts[i];
                 if(a.combined!=index) continue;
-                ImageData as;
-                if(!texturedata(as, NULL, &a)) continue;
-                //if(ts.bpp!=4) forcergbaimage(ts);
-                if(as.w!=ts.w || as.h!=ts.h) scaleimage(as, ts.w, ts.h);
+                SDL_Surface *as = texturedata(NULL, &a);
+                if(!as) break;
+                if(ts->format->BitsPerPixel!=32) ts = creatergbasurface(ts);
+                if(as->w!=ts->w || as->h!=ts->h) as = scalesurface(as, ts->w, ts->h);
                 switch(a.type)
                 {
                     case TEX_SPEC: mergespec(ts, as); break;
                     case TEX_DEPTH: mergedepth(ts, as); break;
                 }
+                SDL_FreeSurface(as);
                 break; // only one combination
             }
             break;
@@ -1222,9 +780,10 @@ static void texcombine(Slot &s, int index, Slot::Tex &t, bool forceload = false)
 
 Slot dummyslot;
 
-static Slot &loadslot(Slot &s, bool forceload)
+Slot &lookuptexture(int slot, bool load)
 {
-    linkslotshader(s);
+    Slot &s = slot<0 && slot>=-MATF_VOLUME ? materialslots[-slot] : (slots.inrange(slot) ? slots[slot] : (slots.empty() ? dummyslot : slots[0]));
+    if(s.loaded || !load) return s;
     loopv(s.sts)
     {
         Slot::Tex &t = s.sts[i];
@@ -1232,11 +791,11 @@ static Slot &loadslot(Slot &s, bool forceload)
         switch(t.type)
         {
             case TEX_ENVMAP:
-                if(hasCM && (renderpath!=R_FIXEDFUNCTION || forceload)) t.t = cubemapload(t.name);
+                if(hasCM && (renderpath!=R_FIXEDFUNCTION || (slot<0 && slot>=-MATF_VOLUME))) t.t = cubemapload(t.name);
                 break;
 
             default:
-                texcombine(s, i, t, forceload);
+                texcombine(s, i, t, slot<0 && slot>=-MATF_VOLUME);
                 break;
         }
     }
@@ -1244,28 +803,11 @@ static Slot &loadslot(Slot &s, bool forceload)
     return s;
 }
 
-Slot &lookupmaterialslot(int slot, bool load)
-{
-    Slot &s = materialslots[slot];
-    return s.loaded || !load ? s : loadslot(s, true);
-}
-
-Slot &lookuptexture(int slot, bool load)
-{
-    Slot &s = slots.inrange(slot) ? slots[slot] : (slots.empty() ? dummyslot : slots[0]);
-    return s.loaded || !load ? s : loadslot(s, false);
-}
-
-void linkslotshaders()
-{
-    loopv(slots) if(slots[i].loaded) linkslotshader(slots[i]);
-    loopi(MATF_VOLUME+1) if(materialslots[i].loaded) linkslotshader(materialslots[i]);
-}
+Shader *lookupshader(int slot) { return slot<0 && slot>=-MATF_VOLUME ? materialslots[-slot].shader : (slots.inrange(slot) ? slots[slot].shader : defaultshader); }
 
 Texture *loadthumbnail(Slot &slot)
 {
     if(slot.thumbnail) return slot.thumbnail;
-    linkslotshader(slot, false);
     vector<char> name;
     addname(name, slot, slot.sts[0], false, "<thumbnail>");
     int glow = -1;
@@ -1274,68 +816,36 @@ Texture *loadthumbnail(Slot &slot)
         loopvj(slot.sts) if(slot.sts[j].type==TEX_GLOW) { glow = j; break; } 
         if(glow >= 0) 
         {
-            defformatstring(prefix)("<mad:%.2f/%.2f/%.2f>", slot.glowcolor.x, slot.glowcolor.y, slot.glowcolor.z); 
+            s_sprintfd(prefix)("<mad:%.2f/%.2f/%.2f>", slot.glowcolor.x, slot.glowcolor.y, slot.glowcolor.z); 
             addname(name, slot, slot.sts[glow], true, prefix);
         }
     }
-    Slot *layer = slot.layer ? &lookuptexture(slot.layer, false) : NULL;
-    if(layer) addname(name, *layer, layer->sts[0], true, "<layer>");
     name.add('\0');
     Texture *t = textures.access(path(name.getbuf()));
     if(t) slot.thumbnail = t;
     else
     {
-        ImageData s, g, l;
-        texturedata(s, NULL, &slot.sts[0], false);
-        if(glow >= 0) texturedata(g, NULL, &slot.sts[glow], false);
-        if(layer) texturedata(l, NULL, &layer->sts[0], false);
-        if(!s.data) slot.thumbnail = notexture;
+        SDL_Surface *s = texturedata(NULL, &slot.sts[0], false), *g = glow >= 0 ? texturedata(NULL, &slot.sts[glow], false) : NULL;
+        if(!s) slot.thumbnail = notexture;
         else
         {
-            int xs = s.w, ys = s.h;
-            if(s.w > 64 || s.h > 64) scaleimage(s, min(s.w, 64), min(s.h, 64));
-            if(g.data)
+            int xs = s->w, ys = s->h;
+            if(s->w > 64 || s->h > 64) s = scalesurface(s, min(s->w, 64), min(s->h, 64));
+            if(g)
             {
-                if(g.w != s.w || g.h != s.h) scaleimage(g, s.w, s.h);
+                if(g->w != s->w || g->h != s->h) g = scalesurface(g, s->w, s->h);
                 addglow(s, g, slot.glowcolor);
             }
-            if(l.data)
-            {
-                if(l.w != s.w/2 || l.h != s.h/2) scaleimage(l, s.w/2, s.h/2);
-                forcergbimage(s);
-                forcergbimage(l); 
-                uchar *dstrow = &s.data[s.pitch*l.h + s.bpp*l.w], *srcrow = l.data;
-                loop(y, l.h) 
-                {
-                    for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[l.w*l.bpp]; src < end; dst += s.bpp, src += l.bpp)
-                        loopk(3) dst[k] = src[k]; 
-                    dstrow += s.pitch;
-                    srcrow += l.pitch;
-                }
-            }
             t = newtexture(NULL, name.getbuf(), s, 0, false, false, true);
             t->xs = xs;
             t->ys = ys;
             slot.thumbnail = t;
         }
+        if(g) SDL_FreeSurface(g);
     }
     return t;
 }
 
-void loadlayermasks()
-{
-    loopv(slots)
-    {
-        Slot &slot = slots[i];
-        if(slot.loaded && slot.layermaskname && !slot.layermask) 
-        {
-            slot.layermask = new ImageData;
-            texturedata(*slot.layermask, slot.layermaskname);
-            if(!slot.layermask->data) DELETEP(slot.layermask);
-        }
-    }
-}
-
 // environment mapped reflections
 
 cubemapside cubemapsides[6] =
@@ -1348,52 +858,82 @@ cubemapside cubemapsides[6] =
     { GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, "up", false, false, true  },
 };
 
-VARFP(envmapsize, 4, 7, 10, setupmaterials());
+GLuint cubemapfromsky(int size)
+{
+    extern Texture *sky[6];
+    if(!sky[0]) return 0;
+    
+    int tsize = 0, cmw, cmh;
+    GLint tw[6], th[6]; 
+    loopi(6)
+    {
+        glBindTexture(GL_TEXTURE_2D, sky[i]->id);
+        glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw[i]);
+        glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th[i]);
+        tsize = max(tsize, (int)max(tw[i], th[i]));
+    }
+    cmw = cmh = min(tsize, size);
+    resizetexture(cmw, cmh, true, GL_RGB5, GL_TEXTURE_CUBE_MAP_ARB);
+    
+    GLuint tex;
+    glGenTextures(1, &tex);
+    int bufsize = 3*max(cmw, tsize)*max(cmh, tsize);
+    uchar *pixels = new uchar[2*bufsize],
+          *rpixels = &pixels[bufsize];
+    loopi(6)
+    {
+        glBindTexture(GL_TEXTURE_2D, sky[i]->id);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
+        if(tw[i]!=cmw || th[i]!=cmh) gluScaleImage(GL_RGB, tw[i], th[i], GL_UNSIGNED_BYTE, pixels, cmw, cmh, GL_UNSIGNED_BYTE, pixels);
+        cubemapside &side = cubemapsides[i];
+        reorienttexture(pixels, cmw, cmh, 3, rpixels, side.flipx, side.flipy, side.swapxy); 
+        createtexture(!i ? tex : 0, cmw, cmh, rpixels, 3, true, GL_RGB5, side.target);
+    }
+    delete[] pixels;
+    return tex;
+}   
 
-Texture *cubemaploadwildcard(Texture *t, const char *name, bool mipit, bool msg, bool transient = false)
+Texture *cubemaploadwildcard(Texture *t, const char *name, bool mipit, bool msg)
 {
     if(!hasCM) return NULL;
     string tname;
-    if(!name) copystring(tname, t->name);
+    if(!name) s_strcpy(tname, t->name);
     else
     {
-        copystring(tname, name);
+        s_strcpy(tname, name);
         t = textures.access(path(tname));
-        if(t) 
-        {
-            if(!transient && t->type&Texture::TRANSIENT) t->type &= ~Texture::TRANSIENT;
-            return t;
-        }
+        if(t) return t;
     }
     char *wildcard = strchr(tname, '*');
-    ImageData surface[6];
+    SDL_Surface *surface[6];
     string sname;
-    if(!wildcard) copystring(sname, tname);
-    GLenum format = GL_FALSE;
-    int tsize = 0, compress = 0;
+    if(!wildcard) s_strcpy(sname, tname);
+    GLenum format = 0;
+    int tsize = 0;
+    bool compress = false;
     loopi(6)
     {
         if(wildcard)
         {
-            copystring(sname, tname, wildcard-tname+1);
-            concatstring(sname, cubemapsides[i].name);
-            concatstring(sname, wildcard+1);
+            s_strncpy(sname, tname, wildcard-tname+1);
+            s_strcat(sname, cubemapsides[i].name);
+            s_strcat(sname, wildcard+1);
         }
-        ImageData &s = surface[i];
-        texturedata(s, sname, NULL, msg, &compress);
-        if(!s.data) return NULL;
-        if(s.w != s.h)
+        surface[i] = texturedata(sname, NULL, msg, &compress);
+        if(!surface[i])
         {
-            if(msg) conoutf(CON_ERROR, "cubemap texture %s does not have square size", sname);
+            loopj(i) SDL_FreeSurface(surface[j]);
             return NULL;
         }
-        if(!format) format = s.compressed ? s.compressed : texformat(s.bpp);
-        else if((s.compressed ? s.compressed : texformat(s.bpp))!=format || (s.compressed && (s.w!=surface[0].w || s.h!=surface[0].h || s.levels!=surface[0].levels)))
+        if(!format) format = texformat(surface[i]->format->BitsPerPixel);
+        else if(texformat(surface[i]->format->BitsPerPixel)!=format)
         {
-            if(msg) conoutf(CON_ERROR, "cubemap texture %s doesn't match other sides' format", sname);
+            if(surface[i] && msg) conoutf(CON_ERROR, "cubemap texture %s doesn't match other sides' format", sname);
+            loopj(i) SDL_FreeSurface(surface[j]);
             return NULL;
         }
-        tsize = max(tsize, max(s.w, s.h));
+        tsize = max(tsize, max(surface[i]->w, surface[i]->h));
     }
     if(name)
     {
@@ -1401,68 +941,54 @@ Texture *cubemaploadwildcard(Texture *t, const char *name, bool mipit, bool msg,
         t = &textures[key];
         t->name = key;
     }
-    t->bpp = surface[0].compressed ? formatsize(uncompressedformat(format)) : surface[0].bpp;
+    t->bpp = surface[0]->format->BitsPerPixel;
     t->mipmap = mipit;
     t->clamp = 3;
-    t->type = Texture::CUBEMAP | (transient ? Texture::TRANSIENT : 0);
-    t->xs = t->ys = tsize;
-    t->w = t->h = min(1<<envmapsize, tsize);
-    resizetexture(t->w, t->h, mipit, false, GL_TEXTURE_CUBE_MAP_ARB, compress, t->w, t->h);
-    format = compressedformat(format, t->w, t->h, compress);
-    switch(format)
-    {
-        case GL_RGB: format = GL_RGB5; break;
-    }
+    t->type = Texture::CUBEMAP;
+    t->w = t->xs = tsize;
+    t->h = t->ys = tsize;
+    resizetexture(t->w, t->h, mipit, format, GL_TEXTURE_CUBE_MAP_ARB);
     glGenTextures(1, &t->id);
-    int sizelimit = mipit && maxtexsize ? min(maxtexsize, hwcubetexsize) : hwcubetexsize;
+    uchar *pixels = NULL;
     loopi(6)
     {
-        ImageData &s = surface[i];
         cubemapside &side = cubemapsides[i];
-        if(surface[i].compressed)
-        {
-            int w = s.w, h = s.h, levels = s.levels, level = 0;
-            uchar *data = s.data;
-            while(levels > 1 && (w > sizelimit || h > sizelimit))
-            {
-                data += s.calclevelsize(level++);
-                levels--;
-                if(w > 1) w /= 2;
-                if(h > 1) h /= 2;
-            }
-            createcompressedtexture(!i ? t->id : 0, w, h, data, s.align, s.bpp, levels, 3, mipit ? 2 : 1, s.compressed, side.target);
-        }
-        else
+        SDL_Surface *s = texreorient(surface[i], side.flipx, side.flipy, side.swapxy);
+        if(s->w != t->w || s->h != t->h)
         {
-            texreorient(s, side.flipx, side.flipy, side.swapxy);
-            createtexture(!i ? t->id : 0, t->w, t->h, s.data, 3, mipit ? 2 : 1, format, side.target, s.w, s.h, s.pitch, false);
+            if(!pixels) pixels = new uchar[formatsize(format)*t->w*t->h];
+            gluScaleImage(format, s->w, s->h, GL_UNSIGNED_BYTE, s->pixels, t->w, t->h, GL_UNSIGNED_BYTE, pixels);
         }
+        createtexture(!i ? t->id : 0, t->w, t->h, s->w != t->w || s->h != t->h ? pixels : s->pixels, 3, mipit, format, side.target, compress);
+        SDL_FreeSurface(s);
     }
+    if(pixels) delete[] pixels;
     return t;
 }
 
-Texture *cubemapload(const char *name, bool mipit, bool msg, bool transient)
+Texture *cubemapload(const char *name, bool mipit, bool msg)
 {
     if(!hasCM) return NULL;
     string pname;
-    copystring(pname, makerelpath("packages", name));
+    s_strcpy(pname, makerelpath("packages", name));
     path(pname);
     Texture *t = NULL;
     if(!strchr(pname, '*'))
     {
-        defformatstring(jpgname)("%s_*.jpg", pname);
-        t = cubemaploadwildcard(NULL, jpgname, mipit, false, transient);
+        s_sprintfd(jpgname)("%s_*.jpg", pname);
+        t = cubemaploadwildcard(NULL, jpgname, mipit, false);
         if(!t)
         {
-            defformatstring(pngname)("%s_*.png", pname);
-            t = cubemaploadwildcard(NULL, pngname, mipit, false, transient);
+            s_sprintfd(pngname)("%s_*.png", pname);
+            t = cubemaploadwildcard(NULL, pngname, mipit, false);
             if(!t && msg) conoutf(CON_ERROR, "could not load envmap %s", name);
         }
     }
-    else t = cubemaploadwildcard(NULL, pname, mipit, msg, transient);
+    else t = cubemaploadwildcard(NULL, pname, mipit, msg);
     return t;
 }
 
+VARFP(envmapsize, 4, 7, 9, setupmaterials());
 VAR(envmapradius, 0, 128, 10000);
 
 struct envmap
@@ -1473,14 +999,14 @@ struct envmap
 };  
 
 static vector<envmap> envmaps;
-static Texture *skyenvmap = NULL;
+static GLuint skyenvmap = 0;
 
 void clearenvmaps()
 {
     if(skyenvmap)
     {
-        if(skyenvmap->type&Texture::TRANSIENT) cleanuptexture(skyenvmap);
-        skyenvmap = NULL;
+        glDeleteTextures(1, &skyenvmap);
+        skyenvmap = 0;
     }
     loopv(envmaps) glDeleteTextures(1, &envmaps[i].tex);
     envmaps.setsize(0);
@@ -1498,9 +1024,9 @@ GLuint genenvmap(const vec &o, int envmapsize)
     GLuint tex;
     glGenTextures(1, &tex);
     glViewport(0, 0, rendersize, rendersize);
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
     float yaw = 0, pitch = 0;
     uchar *pixels = new uchar[3*rendersize*rendersize];
-    glPixelStorei(GL_PACK_ALIGNMENT, texalign(pixels, rendersize, 3));
     loopi(6)
     {
         const cubemapside &side = cubemapsides[i];
@@ -1522,7 +1048,8 @@ GLuint genenvmap(const vec &o, int envmapsize)
         glFrontFace((side.flipx==side.flipy)!=side.swapxy ? GL_CCW : GL_CW);
         drawcubemap(rendersize, o, yaw, pitch, side);
         glReadPixels(0, 0, rendersize, rendersize, GL_RGB, GL_UNSIGNED_BYTE, pixels);
-        createtexture(tex, texsize, texsize, pixels, 3, 2, GL_RGB5, side.target, rendersize, rendersize);
+        if(texsize<rendersize) gluScaleImage(GL_RGB, rendersize, rendersize, GL_UNSIGNED_BYTE, pixels, texsize, texsize, GL_UNSIGNED_BYTE, pixels);
+        createtexture(tex, texsize, texsize, pixels, 3, true, GL_RGB5, side.target);
     }
     glFrontFace(GL_CCW);
     delete[] pixels;
@@ -1535,9 +1062,8 @@ void initenvmaps()
 {
     if(!hasCM) return;
     clearenvmaps();
-    extern char *skybox;
-    skyenvmap = skybox[0] ? cubemapload(skybox, true, false, true) : NULL;
-    const vector<extentity *> &ents = entities::getents();
+    skyenvmap = cubemapfromsky(1<<envmapsize);
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         const extentity &ent = *ents[i];
@@ -1553,19 +1079,11 @@ void initenvmaps()
 void genenvmaps()
 {
     if(envmaps.empty()) return;
-    renderprogress(0, "generating environment maps...");
-    int lastprogress = SDL_GetTicks();
+    show_out_of_renderloop_progress(0, "generating environment maps...");
     loopv(envmaps)
     {
         envmap &em = envmaps[i];
         em.tex = genenvmap(em.o, em.size ? em.size : envmapsize);
-        if(renderedframe) continue;
-        int millis = SDL_GetTicks();
-        if(millis - lastprogress >= 250)
-        {
-            renderprogress(float(i+1)/envmaps.length(), "generating environment maps...", 0, true);
-            lastprogress = millis;
-        }
     }
 }
 
@@ -1599,50 +1117,113 @@ ushort closestenvmap(int orient, int x, int y, int z, int size)
 GLuint lookupenvmap(Slot &slot)
 {
     loopv(slot.sts) if(slot.sts[i].type==TEX_ENVMAP && slot.sts[i].t) return slot.sts[i].t->id;
-    return skyenvmap ? skyenvmap->id : 0;
+    return skyenvmap;
 }
 
 GLuint lookupenvmap(ushort emid)
 {
-    if(emid==EMID_SKY || emid==EMID_CUSTOM) return skyenvmap ? skyenvmap->id : 0;
+    if(emid==EMID_SKY || emid==EMID_CUSTOM) return skyenvmap;
     if(emid==EMID_NONE || !envmaps.inrange(emid-EMID_RESERVED)) return 0;
     GLuint tex = envmaps[emid-EMID_RESERVED].tex;
-    return tex ? tex : (skyenvmap ? skyenvmap->id : 0);
+    return tex ? tex : skyenvmap;
 }
 
-void cleanuptexture(Texture *t)
+void writetgaheader(FILE *f, SDL_Surface *s, int bits)
 {
-    DELETEA(t->alphamask);
-    if(t->id) { glDeleteTextures(1, &t->id); t->id = 0; }
-    if(t->type&Texture::TRANSIENT) textures.remove(t->name); 
+    fwrite("\0\0\x02\0\0\0\0\0\0\0\0\0", 1, 12, f);
+    ushort dim[] = { s->w, s->h };
+    endianswap(dim, sizeof(ushort), 2);
+    fwrite(dim, sizeof(short), 2, f);
+    fputc(bits, f);
+    fputc(0, f);
+}
+
+void flipnormalmapy(char *destfile, char *normalfile)           // RGB (jpg/png) -> BGR (tga)
+{
+    SDL_Surface *ns = IMG_Load(findfile(path(normalfile), "rb"));
+    if(!ns) return;
+    FILE *f = openfile(path(destfile), "wb");
+    if(f)
+    {
+        writetgaheader(f, ns, 24);
+        for(int y = ns->h-1; y>=0; y--) loop(x, ns->w)
+        {
+            uchar *nd = (uchar *)ns->pixels+(x+y*ns->w)*3;
+            fputc(nd[2], f);
+            fputc(255-nd[1], f);
+            fputc(nd[0], f);
+        }
+        fclose(f);
+    }
+    if(ns) SDL_FreeSurface(ns);
+}
+
+void mergenormalmaps(char *heightfile, char *normalfile)    // BGR (tga) -> BGR (tga) (SDL loads TGA as BGR!)
+{
+    SDL_Surface *hs = IMG_Load(findfile(path(heightfile), "rb"));
+    SDL_Surface *ns = IMG_Load(findfile(path(normalfile), "rb"));
+    if(hs && ns)
+    {
+        uchar def_n[] = { 255, 128, 128 };
+        FILE *f = openfile(normalfile, "wb");
+        if(f)
+        {
+            writetgaheader(f, ns, 24); 
+            for(int y = ns->h-1; y>=0; y--) loop(x, ns->w)
+            {
+                int off = (x+y*ns->w)*3;
+                uchar *hd = hs ? (uchar *)hs->pixels+off : def_n;
+                uchar *nd = ns ? (uchar *)ns->pixels+off : def_n;
+                #define S(x) x/255.0f*2-1 
+                vec n(S(nd[0]), S(nd[1]), S(nd[2]));
+                vec h(S(hd[0]), S(hd[1]), S(hd[2]));
+                n.mul(2).add(h).normalize().add(1).div(2).mul(255);
+                uchar o[3] = { (uchar)n.x, (uchar)n.y, (uchar)n.z };
+                fwrite(o, 3, 1, f);
+                #undef S
+            }
+            fclose(f);
+        }
+    }
+    if(hs) SDL_FreeSurface(hs);
+    if(ns) SDL_FreeSurface(ns);
 }
 
+COMMAND(flipnormalmapy, "ss");
+COMMAND(mergenormalmaps, "sss");
+
 void cleanuptextures()
 {
     clearenvmaps();
     loopv(slots) slots[i].cleanup();
     loopi(MATF_VOLUME+1) materialslots[i].cleanup();
-    enumerate(textures, Texture, tex, cleanuptexture(&tex));
+    vector<Texture *> transient;
+    enumerate(textures, Texture, tex,
+        DELETEA(tex.alphamask);
+        if(tex.id) { glDeleteTextures(1, &tex.id); tex.id = 0; }
+        if(tex.type==Texture::TRANSIENT) transient.add(&tex);
+    );
+    loopv(transient) textures.remove(transient[i]->name);
 }
 
 bool reloadtexture(const char *name)
 {
     Texture *t = textures.access(path(name, true));
     if(t) return reloadtexture(*t);
-    return true;
+    return false;
 }
 
 bool reloadtexture(Texture &tex)
 {
     if(tex.id) return true;
-    switch(tex.type&Texture::TYPE)
+    switch(tex.type)
     {
         case Texture::STUB:
         case Texture::IMAGE:
         {
-            int compress = 0;
-            ImageData s;
-            if(!texturedata(s, tex.name, NULL, true, &compress) || !newtexture(&tex, NULL, s, tex.clamp, tex.mipmap, false, false, compress)) return false;
+            bool compress = false;
+            SDL_Surface *s = texturedata(tex.name, NULL, true, &compress);
+            if(!s || !newtexture(&tex, NULL, s, tex.clamp, tex.mipmap, false, false, compress)) return false;
             break;
         }
 
@@ -1656,8 +1237,8 @@ bool reloadtexture(Texture &tex)
 void reloadtex(char *name)
 {
     Texture *t = textures.access(path(name, true));
-    if(!t) { conoutf(CON_ERROR, "texture %s is not loaded", name); return; }
-    if(t->type&Texture::TRANSIENT) { conoutf(CON_ERROR, "can't reload transient texture %s", name); return; }
+    if(!t) { conoutf("texture %s is not loaded", name); return; }
+    if(t->type==Texture::TRANSIENT) { conoutf("can't reload transient texture %s", name); return; }
     DELETEA(t->alphamask);
     Texture oldtex = *t;
     t->id = 0;
@@ -1665,7 +1246,7 @@ void reloadtex(char *name)
     {
         if(t->id) glDeleteTextures(1, &t->id);
         *t = oldtex;
-        conoutf(CON_ERROR, "failed to reload texture %s", name);
+        conoutf("failed to reload texture %s", name);
     }
 }
 
@@ -1673,514 +1254,6 @@ COMMAND(reloadtex, "s");
 
 void reloadtextures()
 {
-    int reloaded = 0;
-    enumerate(textures, Texture, tex, 
-    {
-        loadprogress = float(++reloaded)/textures.numelems;
-        reloadtexture(tex);
-    });
-    loadprogress = 0;
-}
-
-enum
-{
-    DDSD_CAPS                  = 0x00000001, 
-    DDSD_HEIGHT                = 0x00000002,
-    DDSD_WIDTH                 = 0x00000004, 
-    DDSD_PITCH                 = 0x00000008, 
-    DDSD_PIXELFORMAT           = 0x00001000, 
-    DDSD_MIPMAPCOUNT           = 0x00020000, 
-    DDSD_LINEARSIZE            = 0x00080000, 
-    DDSD_BACKBUFFERCOUNT       = 0x00800000, 
-    DDPF_ALPHAPIXELS           = 0x00000001, 
-    DDPF_FOURCC                = 0x00000004, 
-    DDPF_INDEXED               = 0x00000020, 
-    DDPF_ALPHA                 = 0x00000002,
-    DDPF_RGB                   = 0x00000040, 
-    DDPF_COMPRESSED            = 0x00000080,
-    DDPF_LUMINANCE             = 0x00020000,
-    DDSCAPS_COMPLEX            = 0x00000008, 
-    DDSCAPS_TEXTURE            = 0x00001000, 
-    DDSCAPS_MIPMAP             = 0x00400000, 
-    DDSCAPS2_CUBEMAP           = 0x00000200, 
-    DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400, 
-    DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800, 
-    DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000, 
-    DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000, 
-    DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000, 
-    DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000, 
-    DDSCAPS2_VOLUME            = 0x00200000,
-    FOURCC_DXT1                = 0x31545844,
-    FOURCC_DXT3                = 0x33545844,
-    FOURCC_DXT5                = 0x35545844
-
-};
-
-struct DDCOLORKEY { uint dwColorSpaceLowValue, dwColorSpaceHighValue; };
-struct DDPIXELFORMAT
-{
-    uint dwSize, dwFlags, dwFourCC;
-    union { uint dwRGBBitCount, dwYUVBitCount, dwZBufferBitDepth, dwAlphaBitDepth, dwLuminanceBitCount, dwBumpBitCount, dwPrivateFormatBitCount; };
-    union { uint dwRBitMask, dwYBitMask, dwStencilBitDepth, dwLuminanceBitMask, dwBumpDuBitMask, dwOperations; };
-    union { uint dwGBitMask, dwUBitMask, dwZBitMask, dwBumpDvBitMask; struct { ushort wFlipMSTypes, wBltMSTypes; } MultiSampleCaps; };
-    union { uint dwBBitMask, dwVBitMask, dwStencilBitMask, dwBumpLuminanceBitMask; };
-    union { uint dwRGBAlphaBitMask, dwYUVAlphaBitMask, dwLuminanceAlphaBitMask, dwRGBZBitMask, dwYUVZBitMask; };
-
-};
-struct DDSCAPS2 { uint dwCaps, dwCaps2, dwCaps3, dwCaps4; };
-struct DDSURFACEDESC2
-{
-    uint dwSize, dwFlags, dwHeight, dwWidth; 
-    union { int lPitch; uint dwLinearSize; };
-    uint dwBackBufferCount; 
-    union { uint dwMipMapCount, dwRefreshRate, dwSrcVBHandle; };
-    uint dwAlphaBitDepth, dwReserved, lpSurface; 
-    union { DDCOLORKEY ddckCKDestOverlay; uint dwEmptyFaceColor; };
-    DDCOLORKEY ddckCKDestBlt, ddckCKSrcOverlay, ddckCKSrcBlt;     
-    union { DDPIXELFORMAT ddpfPixelFormat; uint dwFVF; };
-    DDSCAPS2 ddsCaps;  
-    uint dwTextureStage;   
-};
-
-VAR(dbgdds, 0, 0, 1);
-
-bool loaddds(const char *filename, ImageData &image)
-{
-    stream *f = openfile(filename, "rb");
-    if(!f) return false;
-    GLenum format = GL_FALSE;
-    uchar magic[4];
-    if(f->read(magic, 4) != 4 || memcmp(magic, "DDS ", 4)) { delete f; return false; }
-    DDSURFACEDESC2 d;
-    if(f->read(&d, sizeof(d)) != sizeof(d)) { delete f; return false; }
-    lilswap((uint *)&d, sizeof(d)/sizeof(uint));
-    if(d.dwSize != sizeof(DDSURFACEDESC2) || d.ddpfPixelFormat.dwSize != sizeof(DDPIXELFORMAT)) { delete f; return false; }
-    if(d.ddpfPixelFormat.dwFlags & DDPF_FOURCC)
-    {
-        switch(d.ddpfPixelFormat.dwFourCC)
-        {
-            case FOURCC_DXT1: format = d.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS ? GL_COMPRESSED_RGBA_S3TC_DXT1_EXT : GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break;
-            case FOURCC_DXT3: format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break;
-            case FOURCC_DXT5: format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break;
-        }        
-    }
-    if(!format) { delete f; return false; }
-    if(dbgdds) conoutf(CON_DEBUG, "%s: format 0x%X, %d x %d, %d mipmaps", filename, format, d.dwWidth, d.dwHeight, d.dwMipMapCount);
-    int bpp = 0;
-    switch(format)
-    {
-        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: 
-        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: bpp = 8; break;
-        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: 
-        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: bpp = 16; break;
-    }
-    image.setdata(NULL, d.dwWidth, d.dwHeight, bpp, d.dwMipMapCount, 4, format); 
-    int size = image.calcsize();
-    if(f->read(image.data, size) != size) { delete f; image.cleanup(); return false; }
-    delete f;
-    return true;
-}
-
-void gendds(char *infile, char *outfile)
-{
-    if(!hasTC) { conoutf(CON_ERROR, "OpenGL driver does not support texture compression"); return; }
-
-    glHint(GL_TEXTURE_COMPRESSION_HINT_ARB, GL_NICEST);
-
-    defformatstring(cfile)("<compress>%s", infile);
-    extern void reloadtex(char *name);
-    Texture *t = textures.access(path(cfile));
-    if(t) reloadtex(cfile);
-    t = textureload(cfile);
-    if(t==notexture) { conoutf(CON_ERROR, "failed loading %s", infile); return; }
-
-    glBindTexture(GL_TEXTURE_2D, t->id);
-    GLint compressed = 0, format = 0, width = 0, height = 0; 
-    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed);
-    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);
-    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
-    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
-
-    if(!compressed) { conoutf(CON_ERROR, "failed compressing %s", infile); return; }
-    int fourcc = 0;
-    switch(format)
-    {
-        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: fourcc = FOURCC_DXT1; conoutf("compressed as DXT1"); break;
-        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: fourcc = FOURCC_DXT1; conoutf("compressed as DXT1a"); break;
-        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: fourcc = FOURCC_DXT3; conoutf("compressed as DXT3"); break;
-        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: fourcc = FOURCC_DXT5; conoutf("compressed as DXT5"); break;
-        default:
-            conoutf(CON_ERROR, "failed compressing %s: unknown format: 0x%X", infile, format); break;
-            return;
-    }
-
-    if(!outfile[0])
-    {
-        static string buf;
-        copystring(buf, infile);
-        int len = strlen(buf);
-        if(len > 4 && buf[len-4]=='.') memcpy(&buf[len-4], ".dds", 4);
-        else concatstring(buf, ".dds");
-        outfile = buf;
-    }
-    
-    stream *f = openfile(path(outfile, true), "wb");
-    if(!f) { conoutf(CON_ERROR, "failed writing to %s", outfile); return; } 
-
-    int csize = 0;
-    for(int lw = width, lh = height, level = 0;;)
-    {
-        GLint size = 0;
-        glGetTexLevelParameteriv(GL_TEXTURE_2D, level++, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &size);
-        csize += size;
-        if(max(lw, lh) <= 1) break;
-        if(lw > 1) lw /= 2;
-        if(lh > 1) lh /= 2;
-    }
-
-    DDSURFACEDESC2 d;
-    memset(&d, 0, sizeof(d));
-    d.dwSize = sizeof(DDSURFACEDESC2);
-    d.dwWidth = width;
-    d.dwHeight = height;
-    d.dwLinearSize = csize;
-    d.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_LINEARSIZE | DDSD_MIPMAPCOUNT;
-    d.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_COMPLEX | DDSCAPS_MIPMAP;
-    d.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
-    d.ddpfPixelFormat.dwFlags = DDPF_FOURCC | (format!=GL_COMPRESSED_RGB_S3TC_DXT1_EXT ? DDPF_ALPHAPIXELS : 0);
-    d.ddpfPixelFormat.dwFourCC = fourcc;
-   
-    uchar *data = new uchar[csize], *dst = data;
-    for(int lw = width, lh = height;;)
-    {
-        GLint size;
-        glGetTexLevelParameteriv(GL_TEXTURE_2D, d.dwMipMapCount, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB, &size);
-        glGetCompressedTexImage_(GL_TEXTURE_2D, d.dwMipMapCount++, dst);
-        dst += size;
-        if(max(lw, lh) <= 1) break;
-        if(lw > 1) lw /= 2;
-        if(lh > 1) lh /= 2;
-    }
-
-    lilswap((uint *)&d, sizeof(d)/sizeof(uint));
-
-    f->write("DDS ", 4);
-    f->write(&d, sizeof(d));
-    f->write(data, csize);
-    delete f;
-    
-    delete[] data;
-
-    conoutf("wrote DDS file %s", outfile);
-
-    setuptexcompress();
-}
-COMMAND(gendds, "ss");
-
-void writepngchunk(stream *f, const char *type, uchar *data = NULL, uint len = 0)
-{
-    f->putbig<uint>(len);
-    f->write(type, 4);
-    f->write(data, len);
-
-    uint crc = crc32(0, Z_NULL, 0);
-    crc = crc32(crc, (const Bytef *)type, 4);
-    if(data) crc = crc32(crc, data, len);
-    f->putbig<uint>(crc);
-}
-
-VARP(compresspng, 0, 9, 9);
-
-void savepng(const char *filename, ImageData &image, bool flip)
-{
-    uchar ctype = 0;
-    switch(image.bpp)
-    {
-        case 1: ctype = 0; break;
-        case 2: ctype = 4; break;
-        case 3: ctype = 2; break;
-        case 4: ctype = 6; break;
-        default: conoutf(CON_ERROR, "failed saving png to %s", filename); return;
-    }
-    stream *f = openfile(filename, "wb");
-    if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; }
-
-    uchar signature[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
-    f->write(signature, sizeof(signature));
-
-    uchar ihdr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 8, ctype, 0, 0, 0 };
-    *(uint *)ihdr = bigswap<uint>(image.w);
-    *(uint *)(ihdr + 4) = bigswap<uint>(image.h);
-    writepngchunk(f, "IHDR", ihdr, sizeof(ihdr));
-
-    int idat = f->tell();
-    uint len = 0;
-    f->write("\0\0\0\0IDAT", 8);
-    uint crc = crc32(0, Z_NULL, 0);
-    crc = crc32(crc, (const Bytef *)"IDAT", 4);
-
-    z_stream z;
-    z.zalloc = NULL;
-    z.zfree = NULL;
-    z.opaque = NULL;
-
-    if(deflateInit(&z, compresspng) != Z_OK)
-        goto error;
-
-    uchar buf[1<<12];
-    z.next_out = (Bytef *)buf;
-    z.avail_out = sizeof(buf);
-
-    loopi(image.h)
-    {
-        uchar filter = 0;
-        loopj(2)
-        {
-            z.next_in = j ? (Bytef *)image.data + (flip ? image.h-i-1 : i)*image.pitch : (Bytef *)&filter;
-            z.avail_in = j ? image.w*image.bpp : 1;
-            while(z.avail_in > 0)
-            {
-                if(deflate(&z, Z_NO_FLUSH) != Z_OK) goto cleanuperror;
-                #define FLUSHZ do { \
-                    int flush = sizeof(buf) - z.avail_out; \
-                    crc = crc32(crc, buf, flush); \
-                    len += flush; \
-                    f->write(buf, flush); \
-                    z.next_out = (Bytef *)buf; \
-                    z.avail_out = sizeof(buf); \
-                } while(0)
-                FLUSHZ;
-            }
-        }
-    }
-
-    for(;;)
-    {
-        int err = deflate(&z, Z_FINISH);
-        if(err != Z_OK && err != Z_STREAM_END) goto cleanuperror;
-        FLUSHZ;
-        if(err == Z_STREAM_END) break;
-    }
-
-    deflateEnd(&z);
-
-    f->seek(idat, SEEK_SET);
-    f->putbig<uint>(len);
-    f->seek(0, SEEK_END);
-    f->putbig<uint>(crc);
-
-    writepngchunk(f, "IEND");
-
-    delete f;
-    return;
-
-cleanuperror:
-    deflateEnd(&z);
-
-error:
-    delete f;
-
-    conoutf(CON_ERROR, "failed saving png to %s", filename);
-}
-
-struct tgaheader
-{
-    uchar  identsize;
-    uchar  cmaptype;
-    uchar  imagetype;
-    uchar  cmaporigin[2];
-    uchar  cmapsize[2];
-    uchar  cmapentrysize;
-    uchar  xorigin[2];
-    uchar  yorigin[2];
-    uchar  width[2];
-    uchar  height[2];
-    uchar  pixelsize;
-    uchar  descbyte;
-};
-
-VARP(compresstga, 0, 1, 1);
-
-void savetga(const char *filename, ImageData &image, bool flip)
-{
-    switch(image.bpp)
-    {
-        case 3: case 4: break;
-        default: conoutf(CON_ERROR, "failed saving tga to %s", filename); return;
-    }
-
-    stream *f = openfile(filename, "wb");
-    if(!f) { conoutf(CON_ERROR, "could not write to %s", filename); return; }
-
-    tgaheader hdr;
-    memset(&hdr, 0, sizeof(hdr));
-    hdr.pixelsize = image.bpp*8;
-    hdr.width[0] = image.w&0xFF;
-    hdr.width[1] = (image.w>>8)&0xFF;
-    hdr.height[0] = image.h&0xFF;
-    hdr.height[1] = (image.h>>8)&0xFF;
-    hdr.imagetype = compresstga ? 10 : 2;
-    f->write(&hdr, sizeof(hdr));
-
-    uchar buf[128*4];
-    loopi(image.h)
-    {
-        uchar *src = image.data + (flip ? i : image.h - i - 1)*image.pitch;
-        for(int remaining = image.w; remaining > 0;)
-        {
-            int raw = 1;
-            if(compresstga)
-            {
-                int run = 1;
-                for(uchar *scan = src; run < min(remaining, 128); run++)
-                {
-                    scan += image.bpp;
-                    if(src[0]!=scan[0] || src[1]!=scan[1] || src[2]!=scan[2] || (image.bpp==4 && src[3]!=scan[3])) break;
-                }
-                if(run > 1)
-                {
-                    f->putchar(0x80 | (run-1));
-                    f->putchar(src[2]); f->putchar(src[1]); f->putchar(src[0]);
-                    if(image.bpp==4) f->putchar(src[3]);
-                    src += run*image.bpp;
-                    remaining -= run;
-                    if(remaining <= 0) break;
-                }
-                for(uchar *scan = src; raw < min(remaining, 128); raw++)
-                {
-                    scan += image.bpp;
-                    if(src[0]==scan[0] && src[1]==scan[1] && src[2]==scan[2] && (image.bpp!=4 || src[3]==scan[3])) break;
-                }
-                f->putchar(raw - 1);
-            }
-            else raw = min(remaining, 128);
-            uchar *dst = buf;
-            loopj(raw)
-            {
-                dst[0] = src[2];
-                dst[1] = src[1];
-                dst[2] = src[0];
-                if(image.bpp==4) dst[3] = src[3];
-                dst += image.bpp;
-                src += image.bpp;
-            }
-            f->write(buf, raw*image.bpp);
-            remaining -= raw;
-        }
-    }
-
-    delete f;
-}
-
-enum
-{
-    IMG_BMP = 0,
-    IMG_TGA = 1,
-    IMG_PNG = 2,
-    NUMIMG
-};
- 
-VARP(screenshotformat, 0, IMG_PNG, NUMIMG);
-
-const char *imageexts[NUMIMG] = { ".bmp", ".tga", ".png" };
-
-int guessimageformat(const char *filename, int format = IMG_BMP)
-{
-    int len = strlen(filename);
-    loopi(NUMIMG)
-    {
-        int extlen = strlen(imageexts[i]);
-        if(len >= extlen && !strcasecmp(&filename[len-extlen], imageexts[i])) return i;
-    }
-    return format;
-}
-
-void saveimage(const char *filename, int format, ImageData &image, bool flip = false)
-{
-    switch(format)
-    {
-        case IMG_PNG: savepng(filename, image, flip); break;
-        case IMG_TGA: savetga(filename, image, flip); break;
-        default:
-        {
-            ImageData flipped(image.w, image.h, image.bpp, image.data);
-            if(flip) texflip(flipped);
-            SDL_Surface *s = wrapsurface(flipped.data, flipped.w, flipped.h, flipped.bpp);
-            if(s) 
-            {
-                SDL_SaveBMP(s, findfile(filename, "wb"));
-                SDL_FreeSurface(s);
-            }
-            break;
-        }
-    }
+    enumerate(textures, Texture, tex, reloadtexture(tex)); 
 }
 
-bool loadimage(const char *filename, ImageData &image)
-{
-    SDL_Surface *s = loadsurface(path(filename, true));
-    if(!s) return false;
-    image.wrap(s);
-    return true;
-}
-
-void screenshot(char *filename)
-{
-    static string buf;
-    int format = -1;
-    if(filename[0])
-    {
-        path(filename);
-        format = guessimageformat(filename, -1);
-    }
-    else
-    {
-        formatstring(buf)("screenshot_%d", totalmillis);
-        filename = buf;
-    }
-    if(format < 0)
-    {
-        format = screenshotformat;
-        if(filename != buf)
-        {
-            copystring(buf, filename);
-            filename = buf;
-        }
-        concatstring(buf, imageexts[format]);         
-    }
-
-    ImageData image(screen->w, screen->h, 3);
-    glPixelStorei(GL_PACK_ALIGNMENT, texalign(image.data, screen->w, 3));
-    glReadPixels(0, 0, screen->w, screen->h, GL_RGB, GL_UNSIGNED_BYTE, image.data);
-    saveimage(filename, format, image, true);
-}
-
-COMMAND(screenshot, "s");
-
-void flipnormalmapy(char *destfile, char *normalfile) // jpg/png /tga-> tga
-{
-    ImageData ns;
-    if(!loadimage(normalfile, ns)) return;
-    ImageData d(ns.w, ns.h, 3);
-    readwritetex(d, ns,
-        dst[0] = src[0];
-        dst[1] = 255 - src[1];
-        dst[2] = src[2];
-    );
-    saveimage(destfile, guessimageformat(destfile, IMG_TGA), d);
-}
-
-void mergenormalmaps(char *heightfile, char *normalfile) // jpg/png/tga + tga -> tga
-{
-    ImageData hs, ns;
-    if(!loadimage(heightfile, hs) || !loadimage(normalfile, ns) || hs.w != ns.w || hs.h != ns.h) return;
-    ImageData d(ns.w, ns.h, 3);
-    read2writetex(d, hs, srch, ns, srcn,
-        *(bvec *)dst = bvec(((bvec *)srcn)->tovec().mul(2).add(((bvec *)srch)->tovec()).normalize());
-    );
-    saveimage(normalfile, guessimageformat(normalfile, IMG_TGA), d);
-}
-
-COMMAND(flipnormalmapy, "ss");
-COMMAND(mergenormalmaps, "sss");
-
diff --git a/engine/texture.h b/engine/texture.h
index dc59d4e..9a9d961 100644
--- a/engine/texture.h
+++ b/engine/texture.h
@@ -39,7 +39,7 @@ extern int renderpath;
 
 enum { R_FIXEDFUNCTION = 0, R_ASMSHADER, R_GLSLANG };
 
-enum { SHPARAM_LOOKUP = 0, SHPARAM_VERTEX, SHPARAM_PIXEL, SHPARAM_UNIFORM };
+enum { SHPARAM_VERTEX = 0, SHPARAM_PIXEL, SHPARAM_UNIFORM };
 
 #define RESERVEDSHADERPARAMS 16
 #define MAXSHADERPARAMS 8
@@ -57,11 +57,11 @@ struct LocalShaderParamState : ShaderParam
 
     LocalShaderParamState() 
     { 
-        memset(curval, -1, sizeof(curval)); 
+        memset(curval, 0, sizeof(curval)); 
     }
     LocalShaderParamState(const ShaderParam &p) : ShaderParam(p)
     {
-        memset(curval, -1, sizeof(curval));
+        memset(curval, 0, sizeof(curval));
     }
 };
 
@@ -82,19 +82,18 @@ struct ShaderParamState
     ShaderParamState()
         : name(NULL), local(false), dirty(INVALID)
     {
-        memset(val, -1, sizeof(val));
+        memset(val, 0, sizeof(val));
     }
 };
 
 enum 
 { 
+    SHADER_INVALID    = -1,
+
     SHADER_DEFAULT    = 0, 
     SHADER_NORMALSLMS = 1<<0, 
     SHADER_ENVMAP     = 1<<1,
-    SHADER_GLSLANG    = 1<<2,
-    SHADER_OPTION     = 1<<3,
-    SHADER_INVALID    = 1<<4,
-    SHADER_DEFERRED   = 1<<5
+    SHADER_GLSLANG    = 1<<2
 };
 
 #define MAXSHADERDETAIL 3
@@ -108,69 +107,60 @@ struct Shader
 {
     static Shader *lastshader;
 
-    char *name, *vsstr, *psstr, *defer;
+    char *name, *vsstr, *psstr;
     int type;
     GLuint vs, ps;
     GLhandleARB program, vsobj, psobj;
     vector<LocalShaderParamState> defaultparams;
-    Shader *detailshader, *variantshader, *altshader, *fastshader[MAXSHADERDETAIL];
+    Shader *variantshader, *altshader, *fastshader[MAXSHADERDETAIL];
     vector<Shader *> variants[MAXVARIANTROWS];
-    bool standard, forced, used, native;
+    bool standard, used, native;
     Shader *reusevs, *reuseps;
     int numextparams;
     LocalShaderParamState *extparams;
     uchar *extvertparams, *extpixparams;
 
 
-    Shader() : name(NULL), vsstr(NULL), psstr(NULL), defer(NULL), type(SHADER_DEFAULT), vs(0), ps(0), program(0), vsobj(0), psobj(0), detailshader(NULL), variantshader(NULL), altshader(NULL), standard(false), forced(false), used(false), native(true), reusevs(NULL), reuseps(NULL), numextparams(0), extparams(NULL), extvertparams(NULL), extpixparams(NULL)
-    {
-        loopi(MAXSHADERDETAIL) fastshader[i] = this;
-    }
+    Shader() : name(NULL), vsstr(NULL), psstr(NULL), type(SHADER_DEFAULT), vs(0), ps(0), program(0), vsobj(0), psobj(0), variantshader(NULL), altshader(NULL), standard(false), used(false), native(true), reusevs(NULL), reuseps(NULL), numextparams(0), extparams(NULL), extvertparams(NULL), extpixparams(NULL)
+    {}
 
     ~Shader()
     {
         DELETEA(name);
         DELETEA(vsstr);
         DELETEA(psstr);
-        DELETEA(defer);
         DELETEA(extparams);
         DELETEA(extvertparams);
         extpixparams = NULL;
     }
 
-    void fixdetailshader(bool force = true, bool recurse = true);
     void allocenvparams(Slot *slot = NULL);
     void flushenvparams(Slot *slot = NULL);
     void setslotparams(Slot &slot);
     void bindprograms();
 
-    bool hasoption(int row)
+    Shader *hasvariant(int col, int row = 0)
     {
-        if(!detailshader || detailshader->variants[row].empty()) return false;
-        return (detailshader->variants[row][0]->type&SHADER_OPTION)!=0;
+        if(!this || renderpath==R_FIXEDFUNCTION) return NULL;
+        Shader *s = shaderdetail < MAXSHADERDETAIL ? fastshader[shaderdetail] : this;
+        return row>=0 && row<MAXVARIANTROWS && s->variants[row].inrange(col) ? s->variants[row][col] : NULL;
     }
 
-    void setvariant(int col, int row, Slot *slot, Shader *fallbackshader)
+    Shader *variant(int col, int row = 0)
     {
-        if(!this || !detailshader || renderpath==R_FIXEDFUNCTION) return;
-        int len = detailshader->variants[row].length();
-        if(col >= len) col = len-1;
-        Shader *s = fallbackshader;
-        while(col >= 0) if(!(detailshader->variants[row][col]->type&SHADER_INVALID)) { s = detailshader->variants[row][col]; break; }
-        if(lastshader!=s) s->bindprograms();
-        lastshader->flushenvparams(slot);
-        if(slot) lastshader->setslotparams(*slot);
-    }
-
-    void setvariant(int col, int row = 0, Slot *slot = NULL)
-    {
-        setvariant(col, row, slot, detailshader);
+        if(!this || renderpath==R_FIXEDFUNCTION) return this;
+        Shader *s = shaderdetail < MAXSHADERDETAIL ? fastshader[shaderdetail] : this;
+        return row>=0 && row<MAXVARIANTROWS && s->variants[row].inrange(col) ? s->variants[row][col] : s;
     }
 
     void set(Slot *slot = NULL)
     {
-        if(!this || !detailshader || renderpath==R_FIXEDFUNCTION) return;
-        if(lastshader!=detailshader) detailshader->bindprograms();
+        if(!this || renderpath==R_FIXEDFUNCTION) return;
+        if(this!=lastshader)
+        {
+            if(shaderdetail < MAXSHADERDETAIL) fastshader[shaderdetail]->bindprograms();
+            else bindprograms();
+        }
         lastshader->flushenvparams(slot);
         if(slot) lastshader->setslotparams(*slot);
     }
@@ -186,95 +176,6 @@ struct Shader
         name##shader->set(); \
     } while(0)
 
-struct ImageData
-{
-    int w, h, bpp, levels, align, pitch;
-    GLenum compressed;
-    uchar *data;
-    void *owner;
-    void (*freefunc)(void *);
-
-    ImageData()
-        : data(NULL), owner(NULL), freefunc(NULL)
-    {}
-
-    
-    ImageData(int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE) 
-    { 
-        setdata(NULL, nw, nh, nbpp, nlevels, nalign, ncompressed); 
-    }
-
-    ImageData(int nw, int nh, int nbpp, uchar *data)
-        : owner(NULL), freefunc(NULL)
-    { 
-        setdata(data, nw, nh, nbpp); 
-    }
-
-    ImageData(SDL_Surface *s) { wrap(s); }
-    ~ImageData() { cleanup(); }
-
-    void setdata(uchar *ndata, int nw, int nh, int nbpp, int nlevels = 1, int nalign = 0, GLenum ncompressed = GL_FALSE)
-    {
-        w = nw;
-        h = nh;
-        bpp = nbpp;
-        levels = nlevels;
-        align = nalign;
-        pitch = align ? 0 : w*bpp;
-        compressed = ncompressed;
-        data = ndata ? ndata : new uchar[calcsize()];
-        if(!ndata) { owner = this; freefunc = NULL; }
-    }
-  
-    int calclevelsize(int level) const { return ((max(w>>level, 1)+align-1)/align)*((max(h>>level, 1)+align-1)/align)*bpp; }
- 
-    int calcsize() const
-    {
-        if(!align) return w*h*bpp;
-        int lw = w, lh = h,
-            size = 0;
-        loopi(levels)
-        {
-            if(lw<=0) lw = 1;
-            if(lh<=0) lh = 1;
-            size += ((lw+align-1)/align)*((lh+align-1)/align)*bpp;
-            if(lw*lh==1) break;
-            lw >>= 1;
-            lh >>= 1;
-        }
-        return size;
-    }
-
-    void disown()
-    {
-        data = NULL;
-        owner = NULL;
-        freefunc = NULL;
-    }
-
-    void cleanup()
-    {
-        if(owner==this) delete[] data;
-        else if(freefunc) (*freefunc)(owner);
-        disown();
-    }
-
-    void replace(ImageData &d)
-    {
-        cleanup();
-        *this = d;
-        d.disown();
-    }
-
-    void wrap(SDL_Surface *s)
-    {
-        setdata((uchar *)s->pixels, s->w, s->h, s->format->BytesPerPixel);
-        pitch = s->pitch;
-        owner = s;
-        freefunc = (void (*)(void *))SDL_FreeSurface;
-    }
-};
-
 // management of texture slots
 // each texture slot can have multiple texture frames, of which currently only the first is used
 // additional frames can be used for various shaders
@@ -283,13 +184,10 @@ struct Texture
 {
     enum
     {
-        IMAGE     = 0,
-        CUBEMAP   = 1,
-        TYPE      = 0xFF,
-        
-        STUB      = 1<<8,
-        TRANSIENT = 1<<9,
-        FLAGS     = 0xF0
+        STUB,
+        TRANSIENT,
+        IMAGE,
+        CUBEMAP
     };
 
     char *name;
@@ -329,19 +227,14 @@ struct Slot
     float scale;
     int rotation, xoffset, yoffset;
     float scrollS, scrollT;
-    int layer;
     vec glowcolor, pulseglowcolor;
     float pulseglowspeed;
     bool mtglowed, loaded;
     uint texmask;
     char *autograss;
     Texture *grasstex, *thumbnail;
-    char *layermaskname;
-    int layermaskmode;
-    float layermaskscale;
-    ImageData *layermask;
 
-    Slot() : autograss(NULL), layermaskname(NULL), layermask(NULL) { reset(); }
+    Slot() : autograss(NULL) { reset(); }
     
     void reset()
     {
@@ -351,7 +244,6 @@ struct Slot
         scale = 1;
         rotation = xoffset = yoffset = 0;
         scrollS = scrollT = 0;
-        layer = 0;
         glowcolor = vec(1, 1, 1);
         pulseglowcolor = vec(0, 0, 0);
         pulseglowspeed = 0;
@@ -360,10 +252,6 @@ struct Slot
         DELETEA(autograss);
         grasstex = NULL;
         thumbnail = NULL;
-        DELETEA(layermaskname);
-        layermaskmode = 0;
-        layermaskscale = 1;
-        if(layermask) DELETEP(layermask);
     }
 
     void cleanup()
@@ -393,12 +281,8 @@ extern Shader *defaultshader, *rectshader, *notextureshader, *nocolorshader, *fo
 extern int reservevpparams, maxvpenvparams, maxvplocalparams, maxfpenvparams, maxfplocalparams;
 
 extern Shader *lookupshaderbyname(const char *name);
-extern Shader *useshaderbyname(const char *name);
 extern Texture *loadthumbnail(Slot &slot);
-extern void resetslotshader();
 extern void setslotshader(Slot &s);
-extern void linkslotshader(Slot &s, bool load = true);
-extern void linkslotshaders();
 extern void setenvparamf(const char *name, int type, int index, float x = 0, float y = 0, float z = 0, float w = 0);
 extern void setenvparamfv(const char *name, int type, int index, const float *v);
 extern void flushenvparamf(const char *name, int type, int index, float x = 0, float y = 0, float z = 0, float w = 0);
@@ -425,8 +309,3 @@ extern void setuptmu(int n, const char *rgbfunc = NULL, const char *alphafunc =
 extern void setupblurkernel(int radius, float sigma, float *weights, float *offsets);
 extern void setblurshader(int pass, int size, int radius, float *weights, float *offsets, GLenum target = GL_TEXTURE_2D);
 
-extern void savepng(const char *filename, ImageData &image, bool flip = false);
-extern void savetga(const char *filename, ImageData &image, bool flip = false);
-extern bool loaddds(const char *filename, ImageData &image);
-extern bool loadimage(const char *filename, ImageData &image);
-
diff --git a/engine/vertmodel.h b/engine/vertmodel.h
index fe1bae5..43ceb9e 100644
--- a/engine/vertmodel.h
+++ b/engine/vertmodel.h
@@ -41,25 +41,31 @@ struct vertmodel : animmodel
             DELETEA(tris);
         }
 
-        void buildnorms(bool areaweight = true)
+        virtual mesh *allocate() { return new vertmesh; }
+
+        mesh *copy()
         {
-            loopk(((vertmeshgroup *)group)->numframes)
+            vertmesh &m = *(vertmesh *)mesh::copy();
+            m.numverts = numverts;
+            m.verts = new vert[numverts*((vertmeshgroup *)group)->numframes];
+            memcpy(m.verts, verts, numverts*((vertmeshgroup *)group)->numframes*sizeof(vert));
+            m.tcverts = new tcvert[numverts];
+            memcpy(m.tcverts, tcverts, numverts*sizeof(tcvert));
+            m.numtris = numtris;
+            m.tris = new tri[numtris];
+            memcpy(m.tris, tris, numtris*sizeof(tri));
+            if(bumpverts)
             {
-                vert *fverts = &verts[k*numverts];
-                loopi(numverts) fverts[i].norm = vec(0, 0, 0);
-                loopi(numtris)
-                {
-                    tri &t = tris[i];
-                    vert &v1 = fverts[t.vert[0]], &v2 = fverts[t.vert[1]], &v3 = fverts[t.vert[2]];
-                    vec norm;
-                    norm.cross(vec(v2.pos).sub(v1.pos), vec(v3.pos).sub(v1.pos));
-                    if(!areaweight) norm.normalize();
-                    v1.norm.add(norm);
-                    v2.norm.add(norm);
-                    v3.norm.add(norm);
-                }
-                loopi(numverts) fverts[i].norm.normalize();
+                m.bumpverts = new bumpvert[numverts];
+                memcpy(m.bumpverts, bumpverts, numverts*sizeof(bumpvert));
             }
+            else m.bumpverts = NULL;
+            return &m;
+        }
+
+        void scaleverts(const vec &transdiff, float scalediff)
+        {
+            loopi(((vertmeshgroup *)group)->numframes*numverts) verts[i].pos.add(transdiff).mul(scalediff);
         }
 
         void calctangents()
@@ -132,7 +138,7 @@ struct vertmodel : animmodel
             loopj(numtris)
             {
                 BIH::tri &t = out[noclip ? 1 : 0].add();
-                t.tex = tex->bpp==4 ? tex : NULL;
+                t.tex = tex->bpp==32 ? tex : NULL;
                 t.a = m.transform(fverts[tris[j].vert[0]].pos);
                 t.b = m.transform(fverts[tris[j].vert[1]].pos);
                 t.c = m.transform(fverts[tris[j].vert[2]].pos);
@@ -322,15 +328,15 @@ struct vertmodel : animmodel
 
                 if(s.tangents())
                 {
-                    if(!enabletangents || lastxbuf!=lastvbuf)
+                    if(!enabletangents || lastnbuf!=lastvbuf)
                     {
                         if(!enabletangents) glEnableVertexAttribArray_(1);
-                        if(lastxbuf!=lastvbuf)
+                        if(lastnbuf!=lastvbuf)
                         {
                             vvertbump *vverts = hasVBO ? 0 : (vvertbump *)vc.vdata;
                             glVertexAttribPointer_(1, 4, GL_FLOAT, GL_FALSE, ((vertmeshgroup *)group)->vertsize, &vverts->tangent.x);
                         }
-                        lastxbuf = lastvbuf;
+                        lastnbuf = lastvbuf;
                         enabletangents = true;
                     }
                 }
@@ -418,15 +424,38 @@ struct vertmodel : animmodel
             return -1;
         }
 
+        virtual meshgroup *allocate() { return new vertmeshgroup; }
+
+        meshgroup *copy()
+        {
+            vertmeshgroup &group = *(vertmeshgroup *)meshgroup::copy();
+            group.numframes = numframes;
+            group.numtags = numtags;
+            group.tags = new tag[numframes*numtags];
+            memcpy(group.tags, tags, numframes*numtags*sizeof(tag));
+            loopi(numframes*numtags) if(group.tags[i].name) group.tags[i].name = newstring(group.tags[i].name);
+            return &group;
+        }
+        
         int totalframes() const { return numframes; }
 
-        void concattagtransform(part *p, int frame, int i, const matrix3x4 &m, matrix3x4 &n)
+        void scaletags(const vec &transdiff, float scalediff)
+        {
+            loopi(numframes*numtags) 
+            {
+                matrix3x4 &m = tags[i].transform;
+                m.X.w = (m.X.w+transdiff.x)*scalediff;
+                m.Y.w = (m.Y.w+transdiff.y)*scalediff;
+                m.Z.w = (m.Z.w+transdiff.z)*scalediff;
+            }
+        }
+
+        void concattagtransform(int frame, int i, const matrix3x4 &m, matrix3x4 &n)
         {
             n.mul(m, tags[frame*numtags + i].transform);
-            n.translate(m.transformnormal(p->translate).mul(p->model->scale));
         }
 
-        void calctagmatrix(part *p, int i, const animstate &as, glmatrixf &matrix)
+        void calctagmatrix(int i, const animstate &as, GLfloat *matrix)
         {
             const matrix3x4 &tag1 = tags[as.cur.fr1*numtags + i].transform, 
                             &tag2 = tags[as.cur.fr2*numtags + i].transform;
@@ -438,22 +467,19 @@ struct vertmodel : animmodel
                                 &tag2p = tags[as.prev.fr2*numtags + i].transform;
                 loopj(4)
                 {
-                    matrix[4*j+0] = ip_ai_tag(a[j]);
-                    matrix[4*j+1] = ip_ai_tag(b[j]);
-                    matrix[4*j+2] = ip_ai_tag(c[j]);
+                    matrix[4*j+0] = ip_ai_tag(X[j]);
+                    matrix[4*j+1] = ip_ai_tag(Y[j]);
+                    matrix[4*j+2] = ip_ai_tag(Z[j]);
                 }
             }
             else loopj(4)
             {
-                matrix[4*j+0] = ip(tag1.a[j], tag2.a[j], as.cur.t);
-                matrix[4*j+1] = ip(tag1.b[j], tag2.b[j], as.cur.t);
-                matrix[4*j+2] = ip(tag1.c[j], tag2.c[j], as.cur.t);
+                matrix[4*j+0] = ip(tag1.X[j], tag2.X[j], as.cur.t);
+                matrix[4*j+1] = ip(tag1.Y[j], tag2.Y[j], as.cur.t);
+                matrix[4*j+2] = ip(tag1.Z[j], tag2.Z[j], as.cur.t);
             } 
             #undef ip_ai_tag
             #undef ip 
-            matrix[12] = (matrix[12] + p->translate.x) * p->model->scale;
-            matrix[13] = (matrix[13] + p->translate.y) * p->model->scale;
-            matrix[14] = (matrix[14] + p->translate.z) * p->model->scale;
             matrix[3] = matrix[7] = matrix[11] = 0.0f;
             matrix[15] = 1.0f;
         }
@@ -545,37 +571,19 @@ struct vertmodel : animmodel
             if(as->anim&ANIM_NOSKIN)
             {
                 if(enabletc) disabletc();
-                if(enablenormals) disablenormals();
             }
-            else
+            else if(!enabletc || lasttcbuf!=lastvbuf)
             {
                 if(vnorms || vtangents)
                 {
-                    if(!enablenormals)
-                    {
-                        glEnableClientState(GL_NORMAL_ARRAY);
-                        enablenormals = true;
-                    }
-                    if(lastnbuf!=lastvbuf)
-                    {
-                        glNormalPointer(GL_FLOAT, vertsize, &vverts->norm);
-                        lastnbuf = lastvbuf;
-                    }
-                }
-                else if(enablenormals) disablenormals();
-
-                if(!enabletc)
-                {
-                    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-                    enabletc = true;
-                }
-                if(lasttcbuf!=lastvbuf)
-                {
-                    glTexCoordPointer(2, GL_FLOAT, vertsize, &vverts->u);
-                    lasttcbuf = lastnbuf;
+                    if(!enabletc) glEnableClientState(GL_NORMAL_ARRAY);
+                    if(lasttcbuf!=lastvbuf) glNormalPointer(GL_FLOAT, vertsize, &vverts->norm);
                 }
+                if(!enabletc) glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+                if(lasttcbuf!=lastvbuf) glTexCoordPointer(2, GL_FLOAT, vertsize, &vverts->u);
+                lasttcbuf = lastvbuf;
+                enabletc = true;
             }
-            if(enablebones) disablebones();
         }
 
         void cleanup()
@@ -589,23 +597,19 @@ struct vertmodel : animmodel
             }
             if(hasVBO) { if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; } }
             else DELETEA(vdata);
+            lastvbuf = lasttcbuf = lastmtcbuf = lastnbuf = NULL;
+            lastebuf = 0;
         }
 
-        void render(const animstate *as, float pitch, const vec &axis, dynent *d, part *p)
+        void render(const animstate *as, float pitch, const vec &axis, part *p)
         {
-            if(as->anim&ANIM_NORENDER)
-            {
-                loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix);
-                return;
-            }
-
             bool norms = false, tangents = false;
             loopv(p->skins) 
             {
                 if(p->skins[i].normals()) norms = true;
                 if(p->skins[i].tangents()) tangents = true;
             }
-            if(norms!=vnorms || tangents!=vtangents) { cleanup(); disablevbo(); }
+            if(norms!=vnorms || tangents!=vtangents) cleanup();
             vbocacheentry *vc = NULL;
             if(numframes<=1) vc = vbocache;
             else
@@ -642,7 +646,7 @@ struct vertmodel : animmodel
             bindvbo(as, *vc);
             loopv(meshes) ((vertmesh *)meshes[i])->render(as, p->skins[i], *vc);
             
-            loopv(p->links) calctagmatrix(p, p->links[i].tag, *as, p->links[i].matrix);
+            loopv(p->links) calctagmatrix(p->links[i].tag, *as, p->links[i].matrix);
         }
     };
 
diff --git a/engine/water.cpp b/engine/water.cpp
index 27ed244..42e89dc 100644
--- a/engine/water.cpp
+++ b/engine/water.cpp
@@ -1,9 +1,10 @@
+#include "pch.h"
 #include "engine.h"
 
-VARFP(waterreflect, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
-VARFP(waterrefract, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
-VARFP(waterenvmap, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
-VARFP(waterfallrefract, 0, 0, 1, { cleanreflections(); preloadwatershaders(); });
+VARFP(waterreflect, 0, 1, 1, cleanreflections());
+VARFP(waterrefract, 0, 1, 1, cleanreflections());
+VARFP(waterenvmap, 0, 1, 1, cleanreflections());
+VARFP(waterfallrefract, 0, 0, 1, cleanreflections());
 VARP(refractfog, 0, 1, 1);
 
 /* vertex water */
@@ -266,7 +267,7 @@ struct Reflection
 {
     GLuint tex, refracttex;
     int height, depth, lastupdate, lastused;
-    glmatrixf projmat;
+    GLfloat tm[16];
     occludequery *query;
     vector<materialsurface *> matsurfs;
 
@@ -276,31 +277,66 @@ struct Reflection
 Reflection *findreflection(int height);
 
 VARP(reflectdist, 0, 2000, 10000);
+VARR(waterfog, 0, 150, 10000);
 
-bvec watercolor(0x14, 0x46, 0x50), waterfallcolor(0, 0, 0);
-HVARFR(watercolour, 0, 0x144650, 0xFFFFFF,
+void getwatercolour(uchar *wcol)
 {
-    if(!watercolour) watercolour = 0x144650;
-    watercolor = bvec((watercolour>>16)&0xFF, (watercolour>>8)&0xFF, watercolour&0xFF);
-});
-VARR(waterfog, 0, 150, 10000);
-HVARFR(waterfallcolour, 0, 0, 0xFFFFFF,
+    static const uchar defaultwcol[3] = { 20, 70, 80};
+    if(hdr.watercolour[0] || hdr.watercolour[1] || hdr.watercolour[2]) memcpy(wcol, hdr.watercolour, 3);
+    else memcpy(wcol, defaultwcol, 3);
+}
+
+void watercolour(int *r, int *g, int *b)
+{
+    hdr.watercolour[0] = *r;
+    hdr.watercolour[1] = *g;
+    hdr.watercolour[2] = *b;
+}
+
+COMMAND(watercolour, "iii");
+
+void getwaterfallcolour(uchar *fcol)
 {
-    waterfallcolor = bvec((waterfallcolour>>16)&0xFF, (waterfallcolour>>8)&0xFF, waterfallcolour&0xFF);
-});
-bvec lavacolor(0xFF, 0x40, 0x00);
-HVARFR(lavacolour, 0, 0xFF4000, 0xFFFFFF,
+    if(hdr.waterfallcolour[0] || hdr.waterfallcolour[1] || hdr.waterfallcolour[2]) memcpy(fcol, hdr.waterfallcolour, 3);
+    else getwatercolour(fcol);
+}
+
+void waterfallcolour(int *r, int *g, int *b)
 {
-    if(!lavacolour) lavacolour = 0xFF4000;
-    lavacolor = bvec((lavacolour>>16)&0xFF, (lavacolour>>8)&0xFF, lavacolour&0xFF);
-});
+    hdr.waterfallcolour[0] = *r;
+    hdr.waterfallcolour[1] = *g;
+    hdr.waterfallcolour[2] = *b;
+}
+
+COMMAND(waterfallcolour, "iii");
+
 VARR(lavafog, 0, 50, 10000);
 
+void getlavacolour(uchar *lcol)
+{
+    static const uchar defaultlcol[3] = { 255, 64, 0 };
+    if(hdr.lavacolour[0] || hdr.lavacolour[1] || hdr.lavacolour[2]) memcpy(lcol, hdr.lavacolour, 3);
+    else memcpy(lcol, defaultlcol, 3);
+}
+
+void lavacolour(int *r, int *g, int *b)
+{
+    hdr.lavacolour[0] = *r;
+    hdr.lavacolour[1] = *g;
+    hdr.lavacolour[2] = *b;
+}
+
+COMMAND(lavacolour, "iii");
+
 void setprojtexmatrix(Reflection &ref, bool init = true)
 {
-    if(init && ref.lastupdate==totalmillis) (ref.projmat = mvpmatrix).projective();
+    if(init && ref.lastupdate==totalmillis)
+    {
+        memcpy(ref.tm, mvpmatrix, 16*sizeof(GLfloat));
+        loopi(2) loopj(4) ref.tm[i + j*4] = 0.5f*(ref.tm[i + j*4] + ref.tm[3 + j*4]);
+    }
     
-    glLoadMatrixf(ref.projmat.v);
+    glLoadMatrixf(ref.tm);
 }
 
 void setuprefractTMUs()
@@ -311,7 +347,7 @@ void setuprefractTMUs()
     { 
         glActiveTexture_(GL_TEXTURE1_ARB);
         glEnable(waterreflect ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP_ARB);
-        if(!waterreflect) glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, lookupenvmap(lookupmaterialslot(MAT_WATER)));
+        if(!waterreflect) glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, lookupenvmap(lookuptexture(-MAT_WATER)));
  
         setuptmu(1, "P , T @ C~a");
 
@@ -331,7 +367,7 @@ void setupreflectTMUs()
     {
         glDisable(GL_TEXTURE_2D);
         glEnable(GL_TEXTURE_CUBE_MAP_ARB);
-        glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, lookupenvmap(lookupmaterialslot(MAT_WATER)));
+        glBindTexture(GL_TEXTURE_CUBE_MAP_ARB, lookupenvmap(lookuptexture(-MAT_WATER)));
     }
 }
 
@@ -390,7 +426,9 @@ void renderwaterff()
 
     float offset = -WATER_OFFSET;
 
-    loopi(3) wcol[i] = watercolor[i]/255.0f;
+    uchar wcolub[3];
+    getwatercolour(wcolub);
+    loopi(3) wcol[i] = wcolub[i]/255.0f;
 
     bool wasbelow = false;
     loopi(MAXREFLECTIONS)
@@ -490,26 +528,7 @@ void renderwaterff()
     glEnable(GL_CULL_FACE);
 }
 
-VARFP(waterfade, 0, 1, 1, { cleanreflections(); preloadwatershaders(); });
-
-void preloadwatershaders(bool force)
-{
-    static bool needwater = false;
-    if(force) needwater = true;
-    if(!needwater) return;
-
-    useshaderbyname("waterglare");
-
-    if(waterenvmap && !waterreflect && hasCM)
-        useshaderbyname(waterrefract ? (waterfade && hasFBO ? "waterenvfade" : "waterenvrefract") : "waterenv");
-    else useshaderbyname(waterrefract ? (waterfade && hasFBO ? "waterfade" : "waterrefract") : (waterreflect ? "waterreflect" : "water"));
-
-    useshaderbyname(waterrefract ? (waterfade && hasFBO ? "underwaterfade" : "underwaterrefract") : "underwater");
-
-    extern int waterfallenv;
-    if(waterfallenv && hasCM) useshaderbyname("waterfallenv");
-    if(waterfallrefract) useshaderbyname(waterfallenv && hasCM ? "waterfallenvrefract" : "waterfallrefract");
-}
+VARFP(waterfade, 0, 1, 1, cleanreflections());
 
 void renderwater()
 {
@@ -520,9 +539,11 @@ void renderwater()
 
     glDisable(GL_CULL_FACE);
 
-    glColor3ubv(watercolor.v);
+    uchar wcol[3];
+    getwatercolour(wcol);
+    glColor3ubv(wcol);
 
-    Slot &s = lookupmaterialslot(MAT_WATER);
+    Slot &s = lookuptexture(-MAT_WATER);
 
     glActiveTexture_(GL_TEXTURE1_ARB);
     glEnable(GL_TEXTURE_2D);
@@ -601,7 +622,7 @@ void renderwater()
         if(waterreflect || waterrefract) glMatrixMode(GL_TEXTURE);
     }
 
-    vec ambient(max(skylightcolor[0], ambientcolor[0]), max(skylightcolor[1], ambientcolor[1]), max(skylightcolor[2], ambientcolor[2]));
+    vec ambient(max(hdr.skylight[0], hdr.ambient), max(hdr.skylight[1], hdr.ambient), max(hdr.skylight[2], hdr.ambient));
     float offset = -WATER_OFFSET;
     loopi(MAXREFLECTIONS)
     {
@@ -649,8 +670,8 @@ void renderwater()
             if(light!=lastlight)
             {
                 if(begin) { glEnd(); begin = false; }
-                const vec &lightpos = light ? light->o : vec(worldsize/2, worldsize/2, worldsize);
-                float lightrad = light && light->attr1 ? light->attr1 : worldsize*8.0f;
+                const vec &lightpos = light ? light->o : vec(hdr.worldsize/2, hdr.worldsize/2, hdr.worldsize);
+                float lightrad = light && light->attr1 ? light->attr1 : hdr.worldsize*8.0f;
                 const vec &lightcol = (light ? vec(light->attr2, light->attr3, light->attr4) : vec(ambient)).div(255.0f).mul(waterspec/100.0f);
                 setlocalparamf("lightpos", SHPARAM_VERTEX, 2, lightpos.x, lightpos.y, lightpos.z);
                 setlocalparamf("lightcolor", SHPARAM_PIXEL, 3, lightcol.x, lightcol.y, lightcol.z);
@@ -792,7 +813,7 @@ void genwatertex(GLuint &tex, GLuint &fb, GLuint &db, bool refract = false)
     GLenum &colorfmt = refract ? refractfmt : reflectfmt;
     if(colorfmt && (!hasFBO || (fb && db)))
     {
-        createtexture(tex, size, size, buf, 3, 1, colorfmt);
+        createtexture(tex, size, size, buf, 3, false, colorfmt);
         delete[] buf;
         return;
     }
@@ -806,7 +827,7 @@ void genwatertex(GLuint &tex, GLuint &fb, GLuint &db, bool refract = false)
     int find = needsalpha ? 0 : 2;
     do
     {
-        createtexture(tex, size, size, buf, 3, 1, colorfmt ? colorfmt : colorfmts[find]);
+        createtexture(tex, size, size, buf, 3, false, colorfmt ? colorfmt : colorfmts[find]);
         if(!hasFBO) break;
         else
         {
@@ -937,7 +958,6 @@ void queryreflections()
     static int lastsize = 0;
     int size = 1<<reflectsize;
     if(!hasFBO) while(size>screen->w || size>screen->h) size /= 2;
-    while(size>hwtexsize) size /= 2;
     if(size!=lastsize) { if(lastsize) cleanreflections(); lastsize = size; }
 
     bool shouldrefract = waterfallrefract && renderpath!=R_FIXEDFUNCTION;
@@ -1074,11 +1094,16 @@ static bool calcscissorbox(Reflection &ref, int size, float &minyaw, float &maxy
     float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1;
     loopi(8)
     {
-        vec4 &p = v[i];
-        mvpmatrix.transform(vec(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, (i&4 ? bbmax.z + WATER_AMPLITUDE : bbmin.z - WATER_AMPLITUDE) - WATER_OFFSET), p);
-        if(p.z >= 0)
+        ivec p(i&1 ? bbmax.x : bbmin.x, i&2 ? bbmax.y : bbmin.y, i&4 ? bbmax.z : bbmin.z);
+        float w = p.x*mvpmatrix[3] + p.y*mvpmatrix[7] + p.z*mvpmatrix[11] + mvpmatrix[15],
+              x = (p.x*mvpmatrix[0] + p.y*mvpmatrix[4] + p.z*mvpmatrix[8] + mvpmatrix[12]),
+              y = (p.x*mvpmatrix[1] + p.y*mvpmatrix[5] + p.z*mvpmatrix[9] + mvpmatrix[13]),
+              z = (p.x*mvpmatrix[2] + p.y*mvpmatrix[6] + p.z*mvpmatrix[10] + mvpmatrix[14]);
+        v[i] = vec4(x, y, z, w);
+        if(z >= 0)
         {
-            float x = p.x / p.w, y = p.y / p.w;
+            x /= w;
+            y /= w;
             sx1 = min(sx1, x);
             sy1 = min(sy1, y);
             sx2 = max(sx2, x);
@@ -1133,7 +1158,6 @@ void drawreflections()
     float offset = -WATER_OFFSET;
     int size = 1<<reflectsize;
     if(!hasFBO) while(size>screen->w || size>screen->h) size /= 2;
-    while(size>hwtexsize) size /= 2;
 
     if(waterreflect || waterrefract) loopi(MAXREFLECTIONS)
     {
diff --git a/engine/world.cpp b/engine/world.cpp
index 792dfc0..2607c65 100644
--- a/engine/world.cpp
+++ b/engine/world.cpp
@@ -1,11 +1,10 @@
 // world.cpp: core map management stuff
 
+#include "pch.h"
 #include "engine.h"
 
-VARR(mapversion, 1, MAPVERSION, 0);
-VARNR(mapscale, worldscale, 1, 0, 0);
-VARNR(mapsize, worldsize, 1, 0, 0);
-SVARR(maptitle, "Untitled Map by Unknown");
+header hdr;
+int worldscale;
 
 VAR(octaentsize, 0, 128, 1024);
 VAR(entselradius, 0, 2, 10);
@@ -62,10 +61,10 @@ void modifyoctaentity(int flags, int id, cube *c, const ivec &cor, int size, con
         {
             if(!c[i].ext || !c[i].ext->ents) ext(c[i]).ents = new octaentities(o, size);
             octaentities &oe = *c[i].ext->ents;
-            switch(entities::getents()[id]->type)
+            switch(et->getents()[id]->type)
             {
                 case ET_MAPMODEL:
-                    if(loadmodel(NULL, entities::getents()[id]->attr2))
+                    if(loadmodel(NULL, et->getents()[id]->attr2))
                     {
                         if(va)
                         {
@@ -94,10 +93,10 @@ void modifyoctaentity(int flags, int id, cube *c, const ivec &cor, int size, con
         else if(c[i].ext && c[i].ext->ents)
         {
             octaentities &oe = *c[i].ext->ents;
-            switch(entities::getents()[id]->type)
+            switch(et->getents()[id]->type)
             {
                 case ET_MAPMODEL:
-                    if(loadmodel(NULL, entities::getents()[id]->attr2))
+                    if(loadmodel(NULL, et->getents()[id]->attr2))
                     {
                         oe.mapmodels.removeobj(id);
                         if(va)
@@ -113,7 +112,7 @@ void modifyoctaentity(int flags, int id, cube *c, const ivec &cor, int size, con
                         oe.bbmin.add(oe.size);
                         loopvj(oe.mapmodels)
                         {
-                            extentity &e = *entities::getents()[oe.mapmodels[j]];
+                            extentity &e = *et->getents()[oe.mapmodels[j]];
                             ivec eo, er;
                             if(getentboundingbox(e, eo, er)) loopk(3)
                             {
@@ -148,38 +147,23 @@ void modifyoctaentity(int flags, int id, cube *c, const ivec &cor, int size, con
     }
 }
 
-vector<int> outsideents;
-
-static bool modifyoctaent(int flags, int id)
+static void modifyoctaent(int flags, int id)
 {
-    vector<extentity *> &ents = entities::getents();
-    if(!ents.inrange(id)) return false;
+    vector<extentity *> &ents = et->getents();
+    if(!ents.inrange(id)) return;
     ivec o, r;
     extentity &e = *ents[id];
-    if((e.inoctanode!=0)==flags || !getentboundingbox(e, o, r)) return false;
+    if((e.inoctanode!=0)==flags || !getentboundingbox(e, o, r)) return;
+
+    int leafsize = octaentsize, limit = max(r.x, max(r.y, r.z));
+    while(leafsize < limit) leafsize *= 2;
+    int diff = ~(leafsize-1) & ((o.x^(o.x+r.x))|(o.y^(o.y+r.y))|(o.z^(o.z+r.z)));
+    if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2;
 
-    if(!insideworld(e.o)) 
-    {
-        int idx = outsideents.find(id);
-        if(flags&MODOE_ADD)
-        {
-            if(idx < 0) outsideents.add(id);
-        }
-        else if(idx >= 0) outsideents.removeunordered(idx);
-    }
-    else
-    {
-        int leafsize = octaentsize, limit = max(r.x, max(r.y, r.z));
-        while(leafsize < limit) leafsize *= 2;
-        int diff = ~(leafsize-1) & ((o.x^(o.x+r.x))|(o.y^(o.y+r.y))|(o.z^(o.z+r.z)));
-        if(diff && (limit > octaentsize/2 || diff < leafsize*2)) leafsize *= 2;
-        modifyoctaentity(flags, id, worldroot, ivec(0, 0, 0), worldsize>>1, o, r, leafsize);
-    }
     e.inoctanode = flags&MODOE_ADD ? 1 : 0;
+    modifyoctaentity(flags, id, worldroot, ivec(0, 0, 0), hdr.worldsize>>1, o, r, leafsize);
     if(e.type == ET_LIGHT) clearlightcache(id);
-    else if(e.type == ET_PARTICLES) clearparticleemitters();
     else if(flags&MODOE_ADD) lightent(e);
-    return true;
 }
 
 static inline void addentity(int id)    { modifyoctaent(MODOE_ADD|MODOE_UPDATEBB, id); }
@@ -188,7 +172,7 @@ static inline void removeentity(int id) { modifyoctaent(MODOE_UPDATEBB, id); }
 void freeoctaentities(cube &c)
 {
     if(!c.ext) return;
-    if(entities::getents().length())
+    if(et->getents().length())
     {
         while(c.ext->ents && !c.ext->ents->mapmodels.empty()) removeentity(c.ext->ents->mapmodels.pop());
         while(c.ext->ents && !c.ext->ents->other.empty())     removeentity(c.ext->ents->other.pop());
@@ -202,68 +186,19 @@ void freeoctaentities(cube &c)
 
 void entitiesinoctanodes()
 {
-    const vector<extentity *> &ents = entities::getents();
-    loopv(ents) modifyoctaent(MODOE_ADD, i);
-}
-
-static inline void findents(octaentities &oe, int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found)
-{
-    vector<extentity *> &ents = entities::getents();
-    loopv(oe.other)
-    {
-        int id = oe.other[i];
-        extentity &e = *ents[id];
-        if(e.type >= low && e.type <= high && (e.spawned || notspawned) && vec(e.o).mul(radius).squaredlen() <= 1) found.add(id);
-    }
-}
-
-static inline void findents(cube *c, const ivec &o, int size, const ivec &bo, const ivec &br, int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found)
-{
-    loopoctabox(o, size, bo, br)
-    {
-        if(c[i].ext && c[i].ext->ents) findents(*c[i].ext->ents, low, high, notspawned, pos, radius, found);
-        if(c[i].children && size > octaentsize) 
-        {
-            ivec co(i, o.x, o.y, o.z, size);
-            findents(c[i].children, co, size>>1, bo, br, low, high, notspawned, pos, radius, found);
-        }
-    }
-}
-
-void findents(int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found)
-{
-    vec invradius(1/radius.x, 1/radius.y, 1/radius.z);
-    ivec bo = vec(pos).sub(radius).sub(1),
-         br = vec(radius).add(1).mul(2);
-    int diff = (bo.x^(bo.x+br.x)) | (bo.y^(bo.y+br.y)) | (bo.z^(bo.z+br.z)) | octaentsize,
-        scale = worldscale-1;
-    if(diff&~((1<<scale)-1) || uint(bo.x|bo.y|bo.z|(bo.x+br.x)|(bo.y+br.y)|(bo.z+br.z)) >= uint(worldsize))
-    {
-        findents(worldroot, ivec(0, 0, 0), 1<<scale, bo, br, low, high, notspawned, pos, invradius, found);
-        return;
-    }
-    cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)];
-    if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found);
-    scale--;
-    while(c->children && !(diff&(1<<scale)))
-    {
-        c = &c->children[octastep(bo.x, bo.y, bo.z, scale)];
-        if(c->ext && c->ext->ents) findents(*c->ext->ents, low, high, notspawned, pos, invradius, found);
-        scale--;
-    }
-    if(c->children && 1<<scale >= octaentsize) findents(c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale, bo, br, low, high, notspawned, pos, invradius, found);
+    loopv(et->getents()) modifyoctaent(MODOE_ADD, i);
 }
 
 char *entname(entity &e)
 {
     static string fullentname;
-    copystring(fullentname, "@");
-    concatstring(fullentname, entities::entname(e.type));
-    const char *einfo = entities::entnameinfo(e);
+    s_strcpy(fullentname, "@");
+    s_strcat(fullentname, et->entname(e.type));
+    const char *einfo = et->entnameinfo(e);
     if(*einfo)
     {
-        concatstring(fullentname, ": ");
-        concatstring(fullentname, einfo);
+        s_strcat(fullentname, ": ");
+        s_strcat(fullentname, einfo);
     }
     return fullentname;
 }
@@ -320,7 +255,7 @@ undoblock *newundoent()
     loopv(entgroup)
     {
         e->i = entgroup[i];
-        e->e = *entities::getents()[entgroup[i]];
+        e->e = *et->getents()[entgroup[i]];
         e++;
     }
     return u;
@@ -352,13 +287,13 @@ void attachentity(extentity &e)
             break;
 
         default:
-            if(e.type<ET_GAMESPECIFIC || !entities::mayattach(e)) return;
+            if(e.type<ET_GAMESPECIFIC || !et->mayattach(e)) return;
             break;
     }
 
     detachentity(e);
 
-    vector<extentity *> &ents = entities::getents();
+    vector<extentity *> &ents = et->getents();
     int closest = -1;
     float closedist = 1e10f;
     loopv(ents)
@@ -372,7 +307,7 @@ void attachentity(extentity &e)
                 break;
 
             default:
-                if(e.type<ET_GAMESPECIFIC || !entities::attachent(e, *a)) continue;
+                if(e.type<ET_GAMESPECIFIC || !et->attachent(e, *a)) continue;
                 break;
         }
         float dist = e.o.dist(a->o);
@@ -389,7 +324,7 @@ void attachentity(extentity &e)
 
 void attachentities()
 {
-    vector<extentity *> &ents = entities::getents();
+    vector<extentity *> &ents = et->getents();
     loopv(ents) attachentity(*ents[i]);
 }
 
@@ -397,7 +332,7 @@ void attachentities()
 // e         entity, currently edited ent
 // n         int,    index to currently edited ent
 #define addimplicit(f)  { if(entgroup.empty() && enthover>=0) { entadd(enthover); undonext = (enthover != oldhover); f; entgroup.drop(); } else f; }
-#define entfocus(i, f)  { int n = efocus = (i); if(n>=0) { extentity &e = *entities::getents()[n]; f; } }
+#define entfocus(i, f)  { int n = efocus = (i); if(n>=0) { extentity &e = *et->getents()[n]; f; } }
 #define entedit(i, f) \
 { \
     entfocus(i, \
@@ -406,23 +341,15 @@ void attachentities()
     f; \
     if(oldtype!=e.type) detachentity(e); \
     if(e.type!=ET_EMPTY) { addentity(n); if(oldtype!=e.type) attachentity(e); } \
-    entities::editent(n)); \
+    et->editent(n)); \
 }
-#define addgroup(exp)   { loopv(entities::getents()) entfocus(i, if(exp) entadd(n)); }
+#define addgroup(exp)   { loopv(et->getents()) entfocus(i, if(exp) entadd(n)); }
 #define setgroup(exp)   { entcancel(); addgroup(exp); }
 #define groupeditloop(f){ entlooplevel++; int _ = efocus; loopv(entgroup) entedit(entgroup[i], f); efocus = _; entlooplevel--; }
 #define groupeditpure(f){ if(entlooplevel>0) { entedit(efocus, f); } else groupeditloop(f); }
 #define groupeditundo(f){ makeundoent(); groupeditpure(f); }
 #define groupedit(f)    { addimplicit(groupeditundo(f)); }
 
-vec getselpos()
-{
-    vector<extentity *> &ents = entities::getents();
-    if(entgroup.length() && ents.inrange(entgroup[0])) return ents[entgroup[0]]->o;
-    if(ents.inrange(enthover)) return ents[enthover]->o;
-    return sel.o.tovec();
-}
-
 undoblock *copyundoents(undoblock *u)
 {
     entcancel();
@@ -468,26 +395,14 @@ void entrotate(int *cw)
 void entselectionbox(const entity &e, vec &eo, vec &es) 
 {
     model *m = NULL;
-    const char *mname = entities::entmodel(e);
-    if(mname && (m = loadmodel(mname)))
-    {   
-        m->collisionbox(0, eo, es);
-        if(es.x > es.y) es.y = es.x; else es.x = es.y; // square
-        es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box
-        eo.x += e.o.x;
-        eo.y += e.o.y;
-        eo.z = e.o.z - entselradius + es.z;
-    } 
-    else if(e.type == ET_MAPMODEL && (m = loadmodel(NULL, e.attr2)))
+    if(e.type == ET_MAPMODEL && (m = loadmodel(NULL, e.attr2)))
     {
         m->collisionbox(0, eo, es);
         rotatebb(eo, es, e.attr1);
-#if 0
         if(m->collide)
             eo.z -= player->aboveeye; // wacky but true. see physics collide                    
         else
             es.div(2);  // cause the usual bb is too big...
-#endif
         eo.add(e.o);
     }   
     else
@@ -539,141 +454,148 @@ void entdrag(const vec &ray)
 
 VAR(showentradius, 0, 1, 1);
 
-void renderentring(const extentity &e, float radius, int axis)
-{
-    if(radius <= 0) return;
-    glBegin(GL_LINE_LOOP);
-    loopi(16)
-    {
-        vec p(e.o);
-        p[axis>=2 ? 1 : 0] += radius*cosf(2*M_PI*i/16.0f);
-        p[axis>=1 ? 2 : 1] += radius*sinf(2*M_PI*i/16.0f);
-        glVertex3fv(p.v);
-    }
-    glEnd();
-}
-
-void renderentsphere(const extentity &e, float radius)
-{
-    if(radius <= 0) return;
-    loopk(3) renderentring(e, radius, k);
-}
-
-void renderentattachment(const extentity &e)
-{
-    if(!e.attached) return;
-    glBegin(GL_LINES);
-    glVertex3fv(e.o.v);
-    glVertex3fv(e.attached->o.v);
-    glEnd();
-}
-
-void renderentarrow(const extentity &e, const vec &dir, float radius)
-{
-    if(radius <= 0) return;
-    float arrowsize = min(radius/8, 0.5f);
-    vec target = vec(dir).mul(radius).add(e.o), arrowbase = vec(dir).mul(radius - arrowsize).add(e.o), spoke;
-    spoke.orthogonal(dir);
-    spoke.normalize();
-    spoke.mul(arrowsize);
-    glBegin(GL_LINES);
-    glVertex3fv(e.o.v);
-    glVertex3fv(target.v);
-    glEnd();
-    glBegin(GL_TRIANGLE_FAN);
-    glVertex3fv(target.v);
-    loopi(5)
-    {
-        vec p(spoke);
-        p.rotate(2*M_PI*i/4.0f, dir);
-        p.add(arrowbase);
-        glVertex3fv(p.v);
-    }
-    glEnd();
-}
-
-void renderentcone(const extentity &e, const vec &dir, float radius, float angle)
-{
-    if(radius <= 0) return;
-    vec spot = vec(dir).mul(radius*cosf(angle*RAD)).add(e.o), spoke;
-    spoke.orthogonal(dir);
-    spoke.normalize();
-    spoke.mul(radius*sinf(angle*RAD));
-    glBegin(GL_LINES);
-    loopi(8)
-    {
-        vec p(spoke);
-        p.rotate(2*M_PI*i/8.0f, dir);
-        p.add(spot);
-        glVertex3fv(e.o.v);
-        glVertex3fv(p.v);
-    }
-    glEnd();
-    glBegin(GL_LINE_LOOP);
-    loopi(8)
-    {
-        vec p(spoke);
-        p.rotate(2*M_PI*i/8.0f, dir);
-        p.add(spot);
-        glVertex3fv(p.v);
-    }
-    glEnd();
-}
-
-void renderentradius(extentity &e, bool color)
+void renderentradius(extentity &e)
 {
+    if(!showentradius) return;
+    float radius = 0.0f, angle = 0.0f, ring = 0.0f;
+    vec dir(0, 0, 0);
+    float color[3] = {0, 1, 1};
     switch(e.type)
     {
         case ET_LIGHT:
-            if(color) glColor3f(e.attr2/255.0f, e.attr3/255.0f, e.attr4/255.0f);
-            renderentsphere(e, e.attr1);
+            radius = e.attr1;
+            color[0] = e.attr2/255.0f;
+            color[1] = e.attr3/255.0f;
+            color[2] = e.attr4/255.0f;
             break;
 
         case ET_SPOTLIGHT:
             if(e.attached)
             {
-                if(color) glColor3f(0, 1, 1);
-                float radius = e.attached->attr1;
+                radius = e.attached->attr1;
                 if(!radius) radius = 2*e.o.dist(e.attached->o);
-                vec dir = vec(e.o).sub(e.attached->o).normalize();
-                float angle = max(1, min(90, int(e.attr1)));
-                renderentattachment(e);
-                renderentcone(*e.attached, dir, radius, angle); 
+                dir = vec(e.o).sub(e.attached->o).normalize();
+                angle = max(1, min(90, int(e.attr1)));
             }
             break;
 
         case ET_SOUND:
-            if(color) glColor3f(0, 1, 1);
-            renderentsphere(e, e.attr2);
+            radius = e.attr2;
             break;
 
         case ET_ENVMAP:
         {
             extern int envmapradius;
-            if(color) glColor3f(0, 1, 1);
-            renderentsphere(e, e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius);
+            radius = e.attr1 ? max(0, min(10000, int(e.attr1))) : envmapradius;
             break;
         }
 
         case ET_MAPMODEL:
         case ET_PLAYERSTART:
-        {
-            if(color) glColor3f(0, 1, 1);
-            vec dir;
+            radius = 4;
+            if(e.type==ET_MAPMODEL && e.attr3) ring = checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12;
             vecfromyawpitch(e.attr1, 0, 1, 0, dir);
-            if(e.type==ET_MAPMODEL && e.attr3) renderentring(e, checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12);
-            renderentarrow(e, dir, 4);
             break;
-        }
 
         default:
-            if(e.type>=ET_GAMESPECIFIC) 
+            if(e.type>=ET_GAMESPECIFIC) et->entradius(e, radius, angle, dir);
+            break;
+    }
+    if(radius<=0) return;
+    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+    loopj(2)
+    {
+        if(!j)
+        {
+            glDepthFunc(GL_GREATER);
+            glColor3f(0.25f, 0.25f, 0.25f);
+        }
+        else 
+        {
+            glDepthFunc(GL_LESS);
+            glColor3fv(color);
+        }
+        if(e.attached)
+        {
+            glBegin(GL_LINES);
+            glVertex3fv(e.o.v);
+            glVertex3fv(e.attached->o.v);
+            glEnd();
+        }
+        if(ring)
+        {
+            glBegin(GL_LINE_LOOP);
+            loopi(16)
             {
-                if(color) glColor3f(0, 1, 1);
-                entities::entradius(e, color);
+                vec p(e.o);
+                p.x += ring*cosf(2*M_PI*i/16.0f);
+                p.y += ring*sinf(2*M_PI*i/16.0f);
+                glVertex3fv(p.v);
             }
-            break;
+            glEnd();
+        }
+        if(dir.iszero()) loopk(3)
+        {
+            glBegin(GL_LINE_LOOP);
+            loopi(16)
+            {
+                vec p(e.o);
+                p[k>=2 ? 1 : 0] += radius*cosf(2*M_PI*i/16.0f);
+                p[k>=1 ? 2 : 1] += radius*sinf(2*M_PI*i/16.0f);
+                glVertex3fv(p.v);
+            }
+            glEnd();
+        }
+        else if(!angle)
+        {
+            float arrowsize = min(radius/8, 0.5f);
+            vec target(vec(dir).mul(radius).add(e.o)), arrowbase(vec(dir).mul(radius - arrowsize).add(e.o)), spoke;
+            spoke.orthogonal(dir);
+            spoke.normalize();
+            spoke.mul(arrowsize);
+            glBegin(GL_LINES);
+            glVertex3fv(e.o.v);
+            glVertex3fv(target.v);
+            glEnd();
+            glBegin(GL_TRIANGLE_FAN);
+            glVertex3fv(target.v);
+            loopi(5)
+            {
+                vec p(spoke);
+                p.rotate(2*M_PI*i/4.0f, dir);
+                p.add(arrowbase);
+                glVertex3fv(p.v);
+            }
+            glEnd();
+        }
+        else
+        {
+            vec spot(vec(dir).mul(radius*cosf(angle*RAD)).add(e.attached->o)), spoke;
+            spoke.orthogonal(dir);
+            spoke.normalize();
+            spoke.mul(radius*sinf(angle*RAD));
+            glBegin(GL_LINES);
+            loopi(8)
+            {
+                vec p(spoke);
+                p.rotate(2*M_PI*i/8.0f, dir);
+                p.add(spot);
+                glVertex3fv(e.attached->o.v);
+                glVertex3fv(p.v);
+            }
+            glEnd();
+            glBegin(GL_LINE_LOOP);
+            loopi(8)
+            {
+                vec p(spoke);
+                p.rotate(2*M_PI*i/8.0f, dir);
+                p.add(spot);
+                glVertex3fv(p.v);
+            }
+            glEnd();
+        }
     }
+    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
 }
 
 void renderentselection(const vec &o, const vec &ray, bool entmoving)
@@ -695,9 +617,9 @@ void renderentselection(const vec &o, const vec &ray, bool entmoving)
         {
             vec a, b;
             glColor3ub(20, 20, 20);
-            (a = eo).x = eo.x - fmod(eo.x, worldsize); (b = es).x = a.x + worldsize; boxs3D(a, b, 1);  
-            (a = eo).y = eo.y - fmod(eo.y, worldsize); (b = es).y = a.x + worldsize; boxs3D(a, b, 1);  
-            (a = eo).z = eo.z - fmod(eo.z, worldsize); (b = es).z = a.x + worldsize; boxs3D(a, b, 1);
+            (a=eo).x=0; (b=es).x=hdr.worldsize; boxs3D(a, b, 1);  
+            (a=eo).y=0; (b=es).y=hdr.worldsize; boxs3D(a, b, 1);  
+            (a=eo).z=0; (b=es).z=hdr.worldsize; boxs3D(a, b, 1);
         }
         glColor3ub(150,0,0);
         glLineWidth(5);
@@ -705,18 +627,8 @@ void renderentselection(const vec &o, const vec &ray, bool entmoving)
         glLineWidth(1);
     }
 
-    if(showentradius && (entgroup.length() || enthover >= 0))
-    {
-        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
-        glDepthFunc(GL_GREATER);
-        glColor3f(0.25f, 0.25f, 0.25f);
-        loopv(entgroup) entfocus(entgroup[i], renderentradius(e, false));
-        if(enthover>=0) entfocus(enthover, renderentradius(e, false));
-        glDepthFunc(GL_LESS);
-        loopv(entgroup) entfocus(entgroup[i], renderentradius(e, true));
-        if(enthover>=0) entfocus(enthover, renderentradius(e, true));
-        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-    }
+    loopv(entgroup) entfocus(entgroup[i], renderentradius(e));
+    if(enthover>=0) entfocus(enthover, renderentradius(e));
 }
 
 bool enttoggle(int id)
@@ -801,7 +713,7 @@ void delent()
 
 int findtype(char *what)
 {
-    for(int i = 0; *entities::entname(i); i++) if(strcmp(what, entities::entname(i))==0) return i;
+    for(int i = 0; *et->entname(i); i++) if(strcmp(what, et->entname(i))==0) return i;
     conoutf(CON_ERROR, "unknown entity type \"%s\"", what);
     return ET_EMPTY;
 }
@@ -869,15 +781,15 @@ void attachent()
 
 COMMAND(attachent, "");
 
-extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4, int v5)
+extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3, int v4)
 {
-    extentity &e = *entities::newentity();
+    extentity &e = *et->newentity();
     e.o = o;
     e.attr1 = v1;
     e.attr2 = v2;
     e.attr3 = v3;
     e.attr4 = v4;
-    e.attr5 = v5;
+    e.attr5 = 0;
     e.type = type;
     e.reserved = 0;
     e.spawned = false;
@@ -890,37 +802,35 @@ extentity *newentity(bool local, const vec &o, int type, int v1, int v2, int v3,
         {
                 case ET_MAPMODEL:
                 case ET_PLAYERSTART:
-                    e.attr5 = e.attr4;
                     e.attr4 = e.attr3;
                     e.attr3 = e.attr2;
                     e.attr2 = e.attr1;
                     e.attr1 = (int)camera1->yaw;
                     break;
         }
-        entities::fixentity(e);
+        et->fixentity(e);
     }
     return &e;
 }
 
-void newentity(int type, int a1, int a2, int a3, int a4, int a5)
+void newentity(int type, int a1, int a2, int a3, int a4)
 {
-    if(entities::getents().length() >= MAXENTS) { conoutf("too many entities"); return; }
-    extentity *t = newentity(true, player->o, type, a1, a2, a3, a4, a5);
+    extentity *t = newentity(true, player->o, type, a1, a2, a3, a4);
     dropentity(*t);
-    entities::getents().add(t);
-    int i = entities::getents().length()-1;
+    et->getents().add(t);
+    int i = et->getents().length()-1;
     t->type = ET_EMPTY;
     enttoggle(i);
     makeundoent();
     entedit(i, e.type = type);
 }
 
-void newent(char *what, int *a1, int *a2, int *a3, int *a4, int *a5)
+void newent(char *what, int *a1, int *a2, int *a3, int *a4)
 {
     if(noentedit()) return;
     int type = findtype(what);
     if(type != ET_EMPTY)
-        newentity(type, *a1, *a2, *a3, *a4, *a5);
+        newentity(type, *a1, *a2, *a3, *a4);
 }
 
 int entcopygrid;
@@ -940,28 +850,28 @@ void entpaste()
     if(noentedit()) return;
     if(entcopybuf.length()==0) return;
     entcancel();
-    int last = entities::getents().length()-1;
+    int last = et->getents().length()-1;
     float m = float(sel.grid)/float(entcopygrid);
     loopv(entcopybuf)
     {
         entity &c = entcopybuf[i];
         vec o(c.o);
         o.mul(m).add(sel.o.v);
-        extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4, c.attr5);
-        entities::getents().add(e);
+        extentity *e = newentity(true, o, ET_EMPTY, c.attr1, c.attr2, c.attr3, c.attr4);
+        et->getents().add(e);
         entadd(++last);
     }
     int j = 0;
     groupeditundo(e.type = entcopybuf[j++].type;);
 }
 
-COMMAND(newent, "siiiii");
+COMMAND(newent, "siiii");
 COMMAND(delent, "");
 COMMAND(dropent, "");
 COMMAND(entcopy, "");
 COMMAND(entpaste, "");
 
-void entset(char *what, int *a1, int *a2, int *a3, int *a4, int *a5)
+void entset(char *what, int *a1, int *a2, int *a3, int *a4)
 {
     if(noentedit()) return;
     int type = findtype(what);
@@ -969,57 +879,19 @@ void entset(char *what, int *a1, int *a2, int *a3, int *a4, int *a5)
               e.attr1=*a1;
               e.attr2=*a2;
               e.attr3=*a3;
-              e.attr4=*a4;
-              e.attr5=*a5);
+              e.attr4=*a4;);
 }
 
-void printent(extentity &e, char *buf)
-{
-    switch(e.type)
-    {
-        case ET_PARTICLES:
-            if(printparticles(e, buf)) return; 
-            break;
- 
-        default:
-            if(e.type >= ET_GAMESPECIFIC && entities::printent(e, buf)) return;
-            break;
-    }
-    formatstring(buf)("%s %d %d %d %d %d", entities::entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
-}
-
-void nearestent()
-{
-    if(noentedit()) return;
-    int closest = -1;
-    float closedist = 1e16f;
-    vector<extentity *> &ents = entities::getents();
-    loopv(ents)
-    {
-        extentity &e = *ents[i];
-        if(e.type == ET_EMPTY) continue;
-        float dist = e.o.dist(player->o);
-        if(dist < closedist)
-        {
-            closest = i;
-            closedist = dist;
-        }
-    }
-    if(closest >= 0) entadd(closest);
-}    
-            
 ICOMMAND(enthavesel,"",  (), addimplicit(intret(entgroup.length())));
 ICOMMAND(entselect, "s", (char *body), if(!noentedit()) addgroup(e.type != ET_EMPTY && entgroup.find(n)<0 && execute(body)>0));
 ICOMMAND(entloop,   "s", (char *body), if(!noentedit()) addimplicit(groupeditloop(((void)e, execute(body)))));
 ICOMMAND(insel,     "",  (), entfocus(efocus, intret(pointinsel(sel, e.o))));
-ICOMMAND(entget,    "",  (), entfocus(efocus, string s; printent(e, s); result(s)));
-ICOMMAND(entindex,  "",  (), intret(efocus));
-COMMAND(entset, "siiiii");
-COMMAND(nearestent, "");
+ICOMMAND(entget,    "",  (), entfocus(efocus, s_sprintfd(s)("%s %d %d %d %d", et->entname(e.type), e.attr1, e.attr2, e.attr3, e.attr4);  result(s)));
+COMMAND(entset, "siiii");
 
 int findentity(int type, int index, int attr1, int attr2)
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     for(int i = index; i<ents.length(); i++) 
     {
         extentity &e = *ents[i];
@@ -1035,14 +907,14 @@ int findentity(int type, int index, int attr1, int attr2)
     return -1;
 }
 
-int spawncycle = -1;
+int spawncycle = -1, fixspawn = 4;
 
 void findplayerspawn(dynent *d, int forceent, int tag)   // place at random spawn. also used by monsters!
 {
     int pick = forceent;
     if(pick<0)
     {
-        int r = rnd(10)+1;
+        int r = fixspawn-->0 ? 7 : rnd(10)+1;
         loopi(r) spawncycle = findentity(ET_PLAYERSTART, spawncycle+1, -1, tag);
         pick = spawncycle;
     }
@@ -1052,14 +924,14 @@ void findplayerspawn(dynent *d, int forceent, int tag)   // place at random spaw
         d->roll = 0;
         for(int attempt = pick;;)
         {
-            d->o = entities::getents()[attempt]->o;
-            d->yaw = entities::getents()[attempt]->attr1;
+            d->o = et->getents()[attempt]->o;
+            d->yaw = et->getents()[attempt]->attr1;
             if(entinmap(d, true)) break;
             attempt = findentity(ET_PLAYERSTART, attempt+1, -1, tag);
             if(attempt<0 || attempt==pick)
             {
-                d->o = entities::getents()[attempt]->o;
-                d->yaw = entities::getents()[attempt]->attr1;
+                d->o = et->getents()[attempt]->o;
+                d->yaw = et->getents()[attempt]->attr1;
                 entinmap(d);
                 break;
             }    
@@ -1067,8 +939,7 @@ void findplayerspawn(dynent *d, int forceent, int tag)   // place at random spaw
     }
     else
     {
-        d->o.x = d->o.y = d->o.z = 0.5f*worldsize;
-        d->o.z += 1;
+        d->o.x = d->o.y = d->o.z = 0.5f*getworldsize();
         entinmap(d);
     }
 }
@@ -1088,7 +959,6 @@ void resetmap()
     clearoverrides();
     clearmapsounds();
     cleanreflections();
-    resetblendmap();
     resetlightmaps();
     clearpvs();
     clearparticles();
@@ -1097,19 +967,18 @@ void resetmap()
     cancelsel();
     pruneundos();
 
-    setvar("gamespeed", 100, false);
-    setvar("paused", 0, false);
+    setvar("gamespeed", 100);
+    setvar("paused", 0);
 
-    entities::clearents();
-    outsideents.setsizenodelete(0);
+    et->getents().deletecontentsp();
 }
 
 void startmap(const char *name)
 {
-    game::startmap(name);
+    cl->startmap(name);
 }
 
-bool emptymap(int scale, bool force, const char *mname, bool usecfg)    // main empty world creation routine
+bool emptymap(int scale, bool force, const char *mname)    // main empty world creation routine
 {
     if(!force && !editmode) 
     {
@@ -1119,29 +988,37 @@ bool emptymap(int scale, bool force, const char *mname, bool usecfg)    // main
 
     resetmap();
 
-    setvar("mapscale", scale<10 ? 10 : (scale>16 ? 16 : scale), true, false);
-    setvar("mapsize", 1<<worldscale, true, false);
+    strncpy(hdr.head, "OCTA", 4);
+    hdr.version = MAPVERSION;
+    hdr.headersize = sizeof(header);
+    worldscale = scale<10 ? 10 : (scale>20 ? 20 : scale);
+    hdr.worldsize = 1<<worldscale;
     
+    s_strncpy(hdr.maptitle, "Untitled Map by Unknown", 128);
+    memset(hdr.watercolour, 0, sizeof(hdr.watercolour));
+    hdr.maple = 8;
+    hdr.mapprec = 32;
+    hdr.mapllod = 0;
+    hdr.numpvs = 0;
+    hdr.lightmaps = 0;
+    memset(hdr.skylight, 0, sizeof(hdr.skylight));
+    memset(hdr.reserved, 0, sizeof(hdr.reserved));
     texmru.setsize(0);
     freeocta(worldroot);
     worldroot = newcubes(F_EMPTY);
     loopi(4) solidfaces(worldroot[i]);
 
-    if(worldsize > VVEC_INT_MASK+1) splitocta(worldroot, worldsize>>1);
-
-    clearmainmenu();
-
-    if(usecfg)
-    {
-        overrideidents = true;
-        execfile("data/default_map_settings.cfg", false);
-        overrideidents = false;
-    }
+    if(hdr.worldsize > VVEC_INT_MASK+1) splitocta(worldroot, hdr.worldsize>>1);
 
     clearlights();
     allchanged();
 
+    overrideidents = true;
+    execfile("data/default_map_settings.cfg");
+    overrideidents = false;
+
     startmap(mname);
+    player->o.z += player->eyeheight+1;
 
     return true;
 }
@@ -1153,65 +1030,69 @@ bool enlargemap(bool force)
         conoutf(CON_ERROR, "mapenlarge only allowed in edit mode");
         return false;
     }
-    if(worldsize >= 1<<16) return false;
-
-    while(outsideents.length()) removeentity(outsideents.pop());
+    if(hdr.worldsize >= 1<<20) return false;
 
     worldscale++;
-    worldsize *= 2;
+    hdr.worldsize *= 2;
     cube *c = newcubes(F_EMPTY);
     c[0].children = worldroot;
     loopi(3) solidfaces(c[i+1]);
     worldroot = c;
 
-    if(worldsize > VVEC_INT_MASK+1) splitocta(worldroot, worldsize>>1);
-
-    enlargeblendmap();
+    if(hdr.worldsize > VVEC_INT_MASK+1) splitocta(worldroot, hdr.worldsize>>1);
 
     allchanged();
 
     return true;
 }
 
-void newmap(int *i) { bool force = !isconnected() && !haslocalclients(); if(force) game::forceedit(""); if(emptymap(*i, force, NULL)) game::newmap(max(*i, 0)); }
-void mapenlarge() { if(enlargemap(false)) game::newmap(-1); }
+void newmap(int *i) { if(emptymap(*i, false)) cl->newmap(max(*i, 0)); }
+void mapenlarge() { if(enlargemap(false)) cl->newmap(-1); }
 COMMAND(newmap, "i");
 COMMAND(mapenlarge, "");
 
 void mapname()
 {
-    result(game::getclientmap());
+    result(cl->getclientmap());
 }
 
 COMMAND(mapname, "");
 
-void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, int attr5, bool local)
+void mapsize()
+{
+    int size = 0;
+    while(1<<size < hdr.worldsize) size++;
+    intret(size);
+}
+
+COMMAND(mapsize, "");
+
+void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, bool local)
 {
-    if(entities::getents().length()<=i)
+    if(et->getents().length()<=i)
     {
-        if(i >= MAXENTS) return;
-        while(entities::getents().length()<i) entities::getents().add(entities::newentity())->type = ET_EMPTY;
-        extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4, attr5);
-        entities::getents().add(e);
+        while(et->getents().length()<i) et->getents().add(et->newentity())->type = ET_EMPTY;
+        extentity *e = newentity(local, o, type, attr1, attr2, attr3, attr4);
+        et->getents().add(e);
         addentity(i);
         attachentity(*e);
     }
     else
     {
-        extentity &e = *entities::getents()[i];
+        extentity &e = *et->getents()[i];
         removeentity(i);
         int oldtype = e.type;
         if(oldtype!=type) detachentity(e);
         e.type = type;
         e.o = o;
-        e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4; e.attr5 = attr5;
+        e.attr1 = attr1; e.attr2 = attr2; e.attr3 = attr3; e.attr4 = attr4;
         addentity(i);
         if(oldtype!=type) attachentity(e);
     }
 }
 
-int getworldsize() { return worldsize; }
-int getmapversion() { return mapversion; }
+int getworldsize() { return hdr.worldsize; }
+int getmapversion() { return hdr.version; }
 
 int triggertypes[NUMTRIGGERTYPES] =
 {
@@ -1235,7 +1116,7 @@ int triggertypes[NUMTRIGGERTYPES] =
 
 void resettriggers()
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
@@ -1247,7 +1128,7 @@ void resettriggers()
 
 void unlocktriggers(int tag, int oldstate = TRIGGER_RESET, int newstate = TRIGGERING)
 {
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
@@ -1257,7 +1138,7 @@ void unlocktriggers(int tag, int oldstate = TRIGGER_RESET, int newstate = TRIGGE
             if(newstate == TRIGGER_RESETTING && checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) continue;
             e.triggerstate = newstate;
             e.lasttrigger = lastmillis;
-            if(checktriggertype(e.attr3, TRIG_RUMBLE)) entities::rumble(e);
+            if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
         }
     }
 }
@@ -1274,7 +1155,7 @@ VAR(triggerstate, -1, 0, 1);
 
 void doleveltrigger(int trigger, int state)
 {
-    defformatstring(aliasname)("level_trigger_%d", trigger);
+    s_sprintfd(aliasname)("level_trigger_%d", trigger);
     if(identexists(aliasname))
     {
         triggerstate = state;
@@ -1285,8 +1166,9 @@ void doleveltrigger(int trigger, int state)
 void checktriggers()
 {
     if(player->state != CS_ALIVE) return;
-    vec o = player->feetpos();
-    const vector<extentity *> &ents = entities::getents();
+    vec o(player->o);
+    o.z -= player->eyeheight;
+    const vector<extentity *> &ents = et->getents();
     loopv(ents)
     {
         extentity &e = *ents[i];
@@ -1324,8 +1206,8 @@ void checktriggers()
                 }
                 e.triggerstate = TRIGGERING;
                 e.lasttrigger = lastmillis;
-                if(checktriggertype(e.attr3, TRIG_RUMBLE)) entities::rumble(e);
-                entities::trigger(e);
+                if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
+                et->trigger(e);
                 if(e.attr4) doleveltrigger(e.attr4, 1);
                 break;
             case TRIGGERED:
@@ -1346,8 +1228,8 @@ void checktriggers()
                 if(checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) break;
                 e.triggerstate = TRIGGER_RESETTING;
                 e.lasttrigger = lastmillis;
-                if(checktriggertype(e.attr3, TRIG_RUMBLE)) entities::rumble(e);
-                entities::trigger(e);
+                if(checktriggertype(e.attr3, TRIG_RUMBLE)) et->rumble(e);
+                et->trigger(e);
                 if(e.attr4) doleveltrigger(e.attr4, 0);
                 break;
         }
diff --git a/engine/world.h b/engine/world.h
index 12ffb77..19e3fdf 100644
--- a/engine/world.h
+++ b/engine/world.h
@@ -8,36 +8,23 @@ enum                            // hardcoded texture numbers
     DEFAULT_CEIL
 };
 
-#define MAPVERSION 29           // bump if map format changes, see worldio.cpp
+#define MAPVERSION 27           // bump if map format changes, see worldio.cpp
 
-struct octaheader
+struct header                   // map file format header
 {
-    char magic[4];              // "OCTA"
+    char head[4];               // "OCTA"
     int version;                // any >8bit quantity is little endian
     int headersize;             // sizeof(header)
     int worldsize;
     int numents;
     int numpvs;
     int lightmaps;
-    int blendmap;
-    int numvars;
-};
-    
-struct compatheader             // map file format header
-{
-    char magic[4];              // "OCTA"
-    int version;                // any >8bit quantity is little endian
-    int headersize;             // sizeof(header)
-    int worldsize;
-    int numents;
-    int numpvs;
-    int lightmaps;
-    int lightprecision, lighterror, lightlod;
+    int mapprec, maple, mapllod;
     uchar ambient;
     uchar watercolour[3];
-    uchar blendmap;
+    uchar mapwlod;
     uchar lerpangle, lerpsubdiv, lerpsubdivsize;
-    uchar bumperror;
+    uchar mapbe;
     uchar skylight[3];
     uchar lavacolour[3];
     uchar waterfallcolour[3];
diff --git a/engine/worldio.cpp b/engine/worldio.cpp
index 17e70bc..73452b6 100644
--- a/engine/worldio.cpp
+++ b/engine/worldio.cpp
@@ -1,16 +1,17 @@
 // worldio.cpp: loading & saving of maps and savegames
 
+#include "pch.h"
 #include "engine.h"
 
 void backup(char *name, char *backupname)
 {
     string backupfile;
-    copystring(backupfile, findfile(backupname, "wb"));
+    s_strcpy(backupfile, findfile(backupname, "wb"));
     remove(backupfile);
     rename(findfile(name, "wb"), backupfile);
 }
 
-string ogzname, bakname, cfgname, picname;
+string ogzname, bakname, pcfname, mcfname, picname;
 
 VARP(savebak, 0, 2, 2);
 
@@ -20,83 +21,97 @@ void cutogz(char *s)
     if(ogzp) *ogzp = '\0';
 }
 
-void getmapfilenames(const char *fname, const char *cname, char *pakname, char *mapname, char *cfgname)
+void getnames(const char *fname, const char *cname, char *pakname, char *mapname, char *cfgname)
 {
     if(!cname) cname = fname;
     string name;
-    copystring(name, cname, 100);
+    s_strncpy(name, cname, 100);
     cutogz(name);
     char *slash = strpbrk(name, "/\\");
     if(slash)
     {
-        copystring(pakname, name, slash-name+1);
-        copystring(cfgname, slash+1);
+        s_strncpy(pakname, name, slash-name+1);
+        s_strcpy(cfgname, slash+1);
     }
     else
     {
-        copystring(pakname, "base");
-        copystring(cfgname, name);
+        s_strcpy(pakname, "base");
+        s_strcpy(cfgname, name);
     }
-    if(strpbrk(fname, "/\\")) copystring(mapname, fname);
-    else formatstring(mapname)("base/%s", fname);
+    if(strpbrk(fname, "/\\")) s_strcpy(mapname, fname);
+    else s_sprintf(mapname)("base/%s", fname);
     cutogz(mapname);
 }
 
-void setmapfilenames(const char *fname, const char *cname = 0)
+void setnames(const char *fname, const char *cname = 0)
 {
-    string pakname, mapname, mcfgname;
-    getmapfilenames(fname, cname, pakname, mapname, mcfgname);
+    string pakname, mapname, cfgname;
+    getnames(fname, cname, pakname, mapname, cfgname);
 
-    formatstring(ogzname)("packages/%s.ogz", mapname);
-    if(savebak==1) formatstring(bakname)("packages/%s.BAK", mapname);
-    else formatstring(bakname)("packages/%s_%d.BAK", mapname, totalmillis);
-    formatstring(cfgname)("packages/%s/%s.cfg", pakname, mcfgname);
-    formatstring(picname)("packages/%s.jpg", mapname);
+    s_sprintf(ogzname)("packages/%s.ogz", mapname);
+    if(savebak==1) s_sprintf(bakname)("packages/%s.BAK", mapname);
+    else s_sprintf(bakname)("packages/%s_%d.BAK", mapname, lastmillis);
+    s_sprintf(pcfname)("packages/%s/package.cfg", pakname);
+    s_sprintf(mcfname)("packages/%s/%s.cfg", pakname, cfgname);
+    s_sprintf(picname)("packages/%s.jpg", mapname);
 
     path(ogzname);
     path(bakname);
-    path(cfgname);
     path(picname);
 }
 
 void mapcfgname()
 {
-    const char *mname = game::getclientmap();
+    const char *mname = cl->getclientmap();
     if(!*mname) mname = "untitled";
 
-    string pakname, mapname, mcfgname;
-    getmapfilenames(mname, NULL, pakname, mapname, mcfgname);
-    defformatstring(cfgname)("packages/%s/%s.cfg", pakname, mcfgname);
-    path(cfgname);
-    result(cfgname);
+    string pakname, mapname, cfgname;
+    getnames(mname, NULL, pakname, mapname, cfgname);
+    s_sprintfd(mcfname)("packages/%s/%s.cfg", pakname, cfgname);
+    path(mcfname);
+    result(mcfname);
 }
 
 COMMAND(mapcfgname, "");
 
+ushort readushort(gzFile f)
+{
+    ushort t;
+    gzread(f, &t, sizeof(ushort));
+    endianswap(&t, sizeof(ushort), 1);
+    return t;
+}
+
+void writeushort(gzFile f, ushort u)
+{
+    endianswap(&u, sizeof(ushort), 1);
+    gzwrite(f, &u, sizeof(ushort));
+}
+
 enum { OCTSAV_CHILDREN = 0, OCTSAV_EMPTY, OCTSAV_SOLID, OCTSAV_NORMAL, OCTSAV_LODCUBE };
 
-void savec(cube *c, stream *f, bool nolms)
+void savec(cube *c, gzFile f, bool nolms)
 {
     loopi(8)
     {
         if(c[i].children && (!c[i].ext || !c[i].ext->surfaces))
         {
-            f->putchar(OCTSAV_CHILDREN);
+            gzputc(f, OCTSAV_CHILDREN);
             savec(c[i].children, f, nolms);
         }
         else
         {
             int oflags = 0;
             if(c[i].ext && c[i].ext->merged) oflags |= 0x80;
-            if(c[i].children) f->putchar(oflags | OCTSAV_LODCUBE);
-            else if(isempty(c[i])) f->putchar(oflags | OCTSAV_EMPTY);
-            else if(isentirelysolid(c[i])) f->putchar(oflags | OCTSAV_SOLID);
+            if(c[i].children) gzputc(f, oflags | OCTSAV_LODCUBE);
+            else if(isempty(c[i])) gzputc(f, oflags | OCTSAV_EMPTY);
+            else if(isentirelysolid(c[i])) gzputc(f, oflags | OCTSAV_SOLID);
             else
             {
-                f->putchar(oflags | OCTSAV_NORMAL);
-                f->write(c[i].edges, 12);
+                gzputc(f, oflags | OCTSAV_NORMAL);
+                gzwrite(f, c[i].edges, 12);
             }
-            loopj(6) f->putlil<ushort>(c[i].texture[j]);
+            loopj(6) writeushort(f, c[i].texture[j]);
             uchar mask = 0;
             if(c[i].ext)
             {
@@ -110,51 +125,42 @@ void savec(cube *c, stream *f, bool nolms)
             // save surface info for lighting
             if(!c[i].ext || !c[i].ext->surfaces || nolms)
             {
-                f->putchar(mask);
+                gzputc(f, mask);
                 if(c[i].ext)
                 {
-                    if(c[i].ext->material != MAT_AIR) f->putchar(c[i].ext->material);
+                    if(c[i].ext->material != MAT_AIR) gzputc(f, c[i].ext->material);
                     if(c[i].ext->normals && !nolms) loopj(6) if(mask & (1 << j))
                     {
-                        loopk(sizeof(surfaceinfo)) f->putchar(0);
-                        f->write(&c[i].ext->normals[j], sizeof(surfacenormals));
+                        loopk(sizeof(surfaceinfo)) gzputc(f, 0);
+                        gzwrite(f, &c[i].ext->normals[j], sizeof(surfacenormals));
                     } 
                 }
             }
             else
             {
-                int numsurfs = 6;
-                loopj(6) 
-                {
-                    surfaceinfo &surface = c[i].ext->surfaces[j];
-                    if(surface.lmid >= LMID_RESERVED || surface.layer!=LAYER_TOP) 
-                    {
-                        mask |= 1 << j;
-                        if(surface.layer&LAYER_BLEND) numsurfs++;
-                    }
-                }
-                f->putchar(mask);
-                if(c[i].ext->material != MAT_AIR) f->putchar(c[i].ext->material);
-                loopj(numsurfs) if(j >= 6 || mask & (1 << j))
+                loopj(6) if(c[i].ext->surfaces[j].lmid >= LMID_RESERVED) mask |= 1 << j;
+                gzputc(f, mask);
+                if(c[i].ext->material != MAT_AIR) gzputc(f, c[i].ext->material);
+                loopj(6) if(mask & (1 << j))
                 {
                     surfaceinfo tmp = c[i].ext->surfaces[j];
-                    lilswap(&tmp.x, 2);
-                    f->write(&tmp, sizeof(surfaceinfo));
-                    if(j < 6 && c[i].ext->normals) f->write(&c[i].ext->normals[j], sizeof(surfacenormals));
+                    endianswap(&tmp.x, sizeof(ushort), 3);
+                    gzwrite(f, &tmp, sizeof(surfaceinfo));
+                    if(c[i].ext->normals) gzwrite(f, &c[i].ext->normals[j], sizeof(surfacenormals));
                 }
             }
             if(c[i].ext && c[i].ext->merged)
             {
-                f->putchar(c[i].ext->merged | (c[i].ext->mergeorigin ? 0x80 : 0));
+                gzputc(f, c[i].ext->merged | (c[i].ext->mergeorigin ? 0x80 : 0));
                 if(c[i].ext->mergeorigin)
                 {
-                    f->putchar(c[i].ext->mergeorigin);
+                    gzputc(f, c[i].ext->mergeorigin);
                     int index = 0;
                     loopj(6) if(c[i].ext->mergeorigin&(1<<j))
                     {
                         mergeinfo tmp = c[i].ext->merges[index++];
-                        lilswap(&tmp.u1, 4);
-                        f->write(&tmp, sizeof(mergeinfo));
+                        endianswap(&tmp, sizeof(ushort), 4);
+                        gzwrite(f, &tmp, sizeof(mergeinfo));
                     }
                 }
             }
@@ -163,12 +169,12 @@ void savec(cube *c, stream *f, bool nolms)
     }
 }
 
-cube *loadchildren(stream *f);
+cube *loadchildren(gzFile f);
 
-void loadc(stream *f, cube &c)
+void loadc(gzFile f, cube &c)
 {
     bool haschildren = false;
-    int octsav = f->getchar();
+    int octsav = gzgetc(f);
     switch(octsav&0x7)
     {
         case OCTSAV_CHILDREN:
@@ -178,22 +184,22 @@ void loadc(stream *f, cube &c)
         case OCTSAV_LODCUBE: haschildren = true;    break;
         case OCTSAV_EMPTY:  emptyfaces(c);          break;
         case OCTSAV_SOLID:  solidfaces(c);          break;
-        case OCTSAV_NORMAL: f->read(c.edges, 12); break;
+        case OCTSAV_NORMAL: gzread(f, c.edges, 12); break;
 
         default:
             fatal("garbage in map");
     }
-    loopi(6) c.texture[i] = mapversion<14 ? f->getchar() : f->getlil<ushort>();
-    if(mapversion < 7) f->seek(3, SEEK_CUR);
+    loopi(6) c.texture[i] = hdr.version<14 ? gzgetc(f) : readushort(f);
+    if(hdr.version < 7) loopi(3) gzgetc(f); //gzread(f, c.colour, 3);
     else
     {
-        uchar mask = f->getchar();
+        uchar mask = gzgetc(f);
         if(mask & 0x80) 
         {
-            int mat = f->getchar();
-            if(mapversion < 27)
+            int mat = gzgetc(f);
+            if(hdr.version < 27)
             {
-                static uchar matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_GAMECLIP, MAT_DEATH };
+                static uchar matconv[] = { MAT_AIR, MAT_WATER, MAT_CLIP, MAT_GLASS|MAT_CLIP, MAT_NOCLIP, MAT_LAVA|MAT_DEATH, MAT_AICLIP, MAT_DEATH };
                 mat = size_t(mat) < sizeof(matconv)/sizeof(matconv[0]) ? matconv[mat] : MAT_AIR;
             }
             ext(c).material = mat;
@@ -201,49 +207,45 @@ void loadc(stream *f, cube &c)
         if(mask & 0x3F)
         {
             uchar lit = 0, bright = 0;
-            static surfaceinfo surfaces[12];
-            memset(surfaces, 0, 6*sizeof(surfaceinfo));
+            newsurfaces(c);
             if(mask & 0x40) newnormals(c);
-            int numsurfs = 6;
-            loopi(numsurfs)
+            loopi(6)
             {
-                if(i >= 6 || mask & (1 << i))
+                if(mask & (1 << i))
                 {
-                    f->read(&surfaces[i], sizeof(surfaceinfo));
-                    lilswap(&surfaces[i].x, 2);
-                    if(mapversion < 10) ++surfaces[i].lmid;
-                    if(mapversion < 18)
-                    {
-                        if(surfaces[i].lmid >= LMID_AMBIENT1) ++surfaces[i].lmid;
-                        if(surfaces[i].lmid >= LMID_BRIGHT1) ++surfaces[i].lmid;
-                    }
-                    if(mapversion < 19)
+                    gzread(f, &c.ext->surfaces[i], sizeof(surfaceinfo));
+                    endianswap(&c.ext->surfaces[i].x, sizeof(ushort), 3);
+                    if(hdr.version < 10) ++c.ext->surfaces[i].lmid;
+                    if(hdr.version < 18)
                     {
-                        if(surfaces[i].lmid >= LMID_DARK) surfaces[i].lmid += 2;
+                        if(c.ext->surfaces[i].lmid >= LMID_AMBIENT1) ++c.ext->surfaces[i].lmid;
+                        if(c.ext->surfaces[i].lmid >= LMID_BRIGHT1) ++c.ext->surfaces[i].lmid;
                     }
-                    if(i < 6)
+                    if(hdr.version < 19)
                     {
-                        if(mask & 0x40) f->read(&c.ext->normals[i], sizeof(surfacenormals));
-                        if(surfaces[i].layer != LAYER_TOP) lit |= 1 << i;
-                        else if(surfaces[i].lmid == LMID_BRIGHT) bright |= 1 << i;
-                        else if(surfaces[i].lmid != LMID_AMBIENT) lit |= 1 << i;
-                        if(surfaces[i].layer&LAYER_BLEND) numsurfs++;
+                        if(c.ext->surfaces[i].lmid >= LMID_DARK) c.ext->surfaces[i].lmid += 2;
                     }
+                    if(mask & 0x40) gzread(f, &c.ext->normals[i], sizeof(surfacenormals));
                 }
-                else surfaces[i].lmid = LMID_AMBIENT;
+                else c.ext->surfaces[i].lmid = LMID_AMBIENT;
+                if(c.ext->surfaces[i].lmid == LMID_BRIGHT) bright |= 1 << i;
+                else if(c.ext->surfaces[i].lmid != LMID_AMBIENT) lit |= 1 << i;
+            }
+            if(!lit) 
+            {
+                freesurfaces(c);
+                if(bright) brightencube(c);
             }
-            if(lit) newsurfaces(c, surfaces, numsurfs);
-            else if(bright) brightencube(c);
         }
-        if(mapversion >= 20)
+        if(hdr.version >= 20)
         {
             if(octsav&0x80)
             {
-                int merged = f->getchar();
+                int merged = gzgetc(f);
                 ext(c).merged = merged&0x3F;
                 if(merged&0x80)
                 {
-                    c.ext->mergeorigin = f->getchar();
+                    c.ext->mergeorigin = gzgetc(f);
                     int nummerges = 0;
                     loopi(6) if(c.ext->mergeorigin&(1<<i)) nummerges++;
                     if(nummerges)
@@ -252,9 +254,9 @@ void loadc(stream *f, cube &c)
                         loopi(nummerges)
                         {
                             mergeinfo *m = &c.ext->merges[i];
-                            f->read(m, sizeof(mergeinfo));
-                            lilswap(&m->u1, 4);
-                            if(mapversion <= 25)
+                            gzread(f, m, sizeof(mergeinfo));
+                            endianswap(m, sizeof(ushort), 4);
+                            if(hdr.version <= 25)
                             {
                                 int uorigin = m->u1 & 0xE000, vorigin = m->v1 & 0xE000;
                                 m->u1 = (m->u1 - uorigin) << 2;
@@ -271,7 +273,7 @@ void loadc(stream *f, cube &c)
     c.children = (haschildren ? loadchildren(f) : NULL);
 }
 
-cube *loadchildren(stream *f)
+cube *loadchildren(gzFile f)
 {
     cube *c = newcubes();
     loopi(8) loadc(f, c[i]);
@@ -279,83 +281,45 @@ cube *loadchildren(stream *f)
     return c;
 }
 
-VAR(dbgvars, 0, 0, 1);
-
 bool save_world(const char *mname, bool nolms)
 {
-    if(!*mname) mname = game::getclientmap();
-    setmapfilenames(*mname ? mname : "untitled");
+    if(!*mname) mname = cl->getclientmap();
+    setnames(*mname ? mname : "untitled");
     if(savebak) backup(ogzname, bakname);
-    stream *f = opengzfile(ogzname, "wb");
+    gzFile f = opengzfile(ogzname, "wb9");
     if(!f) { conoutf(CON_WARN, "could not write map to %s", ogzname); return false; }
-    octaheader hdr;
-    memcpy(hdr.magic, "OCTA", 4);
     hdr.version = MAPVERSION;
-    hdr.headersize = sizeof(hdr);
-    hdr.worldsize = worldsize;
     hdr.numents = 0;
-    const vector<extentity *> &ents = entities::getents();
+    const vector<extentity *> &ents = et->getents();
     loopv(ents) if(ents[i]->type!=ET_EMPTY || nolms) hdr.numents++;
     hdr.numpvs = nolms ? 0 : getnumviewcells();
     hdr.lightmaps = nolms ? 0 : lightmaps.length();
-    hdr.blendmap = shouldsaveblendmap();
-    hdr.numvars = 0;
-    enumerate(*idents, ident, id, 
-    {
-        if((id.type == ID_VAR || id.type == ID_FVAR || id.type == ID_SVAR) && id.flags&IDF_OVERRIDE && !(id.flags&IDF_READONLY) && id.override!=NO_OVERRIDE) hdr.numvars++;
-    });
-    lilswap(&hdr.version, 8);
-    f->write(&hdr, sizeof(hdr));
-   
-    enumerate(*idents, ident, id, 
-    {
-        if((id.type!=ID_VAR && id.type!=ID_FVAR && id.type!=ID_SVAR) || !(id.flags&IDF_OVERRIDE) || id.flags&IDF_READONLY || id.override==NO_OVERRIDE) continue;
-        f->putchar(id.type);
-        f->putlil<ushort>(strlen(id.name));
-        f->write(id.name, strlen(id.name));
-        switch(id.type)
-        {
-            case ID_VAR:
-                if(dbgvars) conoutf(CON_DEBUG, "wrote var %s: %d", id.name, *id.storage.i);
-                f->putlil<int>(*id.storage.i);
-                break;
-
-            case ID_FVAR:
-                if(dbgvars) conoutf(CON_DEBUG, "wrote fvar %s: %f", id.name, *id.storage.f);
-                f->putlil<float>(*id.storage.f);
-                break;
-
-            case ID_SVAR:
-                if(dbgvars) conoutf(CON_DEBUG, "wrote svar %s: %s", id.name, *id.storage.s);
-                f->putlil<ushort>(strlen(*id.storage.s));
-                f->write(*id.storage.s, strlen(*id.storage.s));
-                break;
-        }
-    });
-
-    if(dbgvars) conoutf(CON_DEBUG, "wrote %d vars", hdr.numvars);
-
-    f->putchar((int)strlen(game::gameident()));
-    f->write(game::gameident(), (int)strlen(game::gameident())+1);
-    f->putlil<ushort>(entities::extraentinfosize());
+    header tmp = hdr;
+    endianswap(&tmp.version, sizeof(int), 9);
+    gzwrite(f, &tmp, sizeof(header));
+    
+    gzputc(f, (int)strlen(cl->gameident()));
+    gzwrite(f, cl->gameident(), (int)strlen(cl->gameident())+1);
+    writeushort(f, et->extraentinfosize());
     vector<char> extras;
-    game::writegamedata(extras);
-    f->putlil<ushort>(extras.length());
-    f->write(extras.getbuf(), extras.length());
+    cl->writegamedata(extras);
+    writeushort(f, extras.length());
+    gzwrite(f, extras.getbuf(), extras.length());
+    
     
-    f->putlil<ushort>(texmru.length());
-    loopv(texmru) f->putlil<ushort>(texmru[i]);
-    char *ebuf = new char[entities::extraentinfosize()];
+    writeushort(f, texmru.length());
+    loopv(texmru) writeushort(f, texmru[i]);
+    char *ebuf = new char[et->extraentinfosize()];
     loopv(ents)
     {
         if(ents[i]->type!=ET_EMPTY || nolms)
         {
             entity tmp = *ents[i];
-            lilswap(&tmp.o.x, 3);
-            lilswap(&tmp.attr1, 5);
-            f->write(&tmp, sizeof(entity));
-            entities::writeent(*ents[i], ebuf);
-            if(entities::extraentinfosize()) f->write(ebuf, entities::extraentinfosize());
+            endianswap(&tmp.o, sizeof(int), 3);
+            endianswap(&tmp.attr1, sizeof(short), 5);
+            gzwrite(f, &tmp, sizeof(entity));
+            et->writeent(*ents[i], ebuf);
+            if(et->extraentinfosize()) gzwrite(f, ebuf, et->extraentinfosize());
         }
     }
     delete[] ebuf;
@@ -366,27 +330,22 @@ bool save_world(const char *mname, bool nolms)
         loopv(lightmaps)
         {
             LightMap &lm = lightmaps[i];
-            f->putchar(lm.type | (lm.unlitx>=0 ? 0x80 : 0));
+            gzputc(f, lm.type | (lm.unlitx>=0 ? 0x80 : 0));
             if(lm.unlitx>=0)
             {
-                f->putlil<ushort>(ushort(lm.unlitx));
-                f->putlil<ushort>(ushort(lm.unlity));
+                writeushort(f, ushort(lm.unlitx));
+                writeushort(f, ushort(lm.unlity));
             }
-            f->write(lm.data, lm.bpp*LM_PACKW*LM_PACKH);
+            gzwrite(f, lm.data, sizeof(lm.data));
         }
         if(getnumviewcells()>0) savepvs(f);
     }
-    if(shouldsaveblendmap()) saveblendmap(f);
 
-    delete f;
+    gzclose(f);
     conoutf("wrote map file %s", ogzname);
     return true;
 }
 
-static uint mapcrc = 0;
-
-uint getmapcrc() { return mapcrc; }
-
 static void swapXZ(cube *c)
 {	
 	loopi(8) 
@@ -416,162 +375,100 @@ static void fixoversizedcubes(cube *c, int size)
 bool load_world(const char *mname, const char *cname)        // still supports all map formats that have existed since the earliest cube betas!
 {
     int loadingstart = SDL_GetTicks();
-    setmapfilenames(mname, cname);
-    stream *f = opengzfile(ogzname, "rb");
+    setnames(mname, cname);
+    gzFile f = opengzfile(ogzname, "rb9");
     if(!f) { conoutf(CON_ERROR, "could not read map %s", ogzname); return false; }
-    octaheader hdr;
-    if(f->read(&hdr, 7*sizeof(int))!=7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
-    lilswap(&hdr.version, 6);
-    if(strncmp(hdr.magic, "OCTA", 4)!=0 || hdr.worldsize <= 0|| hdr.numents < 0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
-    if(hdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of Cube 2: Sauerbraten", ogzname); delete f; return false; }
-    compatheader chdr;
-    if(hdr.version <= 28)
-    {
-        if(f->read(&chdr.lightprecision, sizeof(chdr) - 7*sizeof(int)) != sizeof(chdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
-    }
-    else if(f->read(&hdr.blendmap, sizeof(hdr) - 7*sizeof(int)) != sizeof(hdr) - 7*sizeof(int)) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); delete f; return false; }
-
+    header newhdr;
+    gzread(f, &newhdr, sizeof(header));
+    endianswap(&newhdr.version, sizeof(int), 9);
+    if(strncmp(newhdr.head, "OCTA", 4)!=0) { conoutf(CON_ERROR, "map %s has malformatted header", ogzname); gzclose(f); return false; }
+    if(newhdr.version>MAPVERSION) { conoutf(CON_ERROR, "map %s requires a newer version of cube 2", ogzname); gzclose(f); return false; }
+    hdr = newhdr;
     resetmap();
-
-    Texture *mapshot = textureload(picname, 3, true, false);
-    renderbackground("loading...", mapshot, mname, game::getmapinfo());
-
-    setvar("mapversion", hdr.version, true, false);
-
-    if(hdr.version <= 28)
+    Texture *mapshot = textureload(picname, 0, true, false);
+    computescreen("loading...", mapshot!=notexture ? mapshot : NULL, mname);
+    if(hdr.version<=20) conoutf(CON_WARN, "loading older / less efficient map format, may benefit from \"calclight 2\", then \"savecurrentmap\"");
+    if(!hdr.ambient) hdr.ambient = 25;
+    if(!hdr.lerpsubdivsize)
     {
-        lilswap(&chdr.lightprecision, 3);
-        if(hdr.version<=20) conoutf(CON_WARN, "loading older / less efficient map format, may benefit from \"calclight 2\", then \"savecurrentmap\"");
-        if(chdr.lightprecision) setvar("lightprecision", chdr.lightprecision);
-        if(chdr.lighterror) setvar("lighterror", chdr.lighterror);
-        if(chdr.bumperror) setvar("bumperror", chdr.bumperror);
-        setvar("lightlod", chdr.lightlod);
-        if(chdr.ambient) setvar("ambient", chdr.ambient);
-        setvar("skylight", (int(chdr.skylight[0])<<16) | (int(chdr.skylight[1])<<8) | int(chdr.skylight[2]));
-        setvar("watercolour", (int(chdr.watercolour[0])<<16) | (int(chdr.watercolour[1])<<8) | int(chdr.watercolour[2]), true);
-        setvar("waterfallcolour", (int(chdr.waterfallcolour[0])<<16) | (int(chdr.waterfallcolour[1])<<8) | int(chdr.waterfallcolour[2]));
-        setvar("lavacolour", (int(chdr.lavacolour[0])<<16) | (int(chdr.lavacolour[1])<<8) | int(chdr.lavacolour[2]));
-        setvar("fullbright", 0, true);
-        if(chdr.lerpsubdivsize || chdr.lerpangle) setvar("lerpangle", chdr.lerpangle);
-        if(chdr.lerpsubdivsize)
-        {
-            setvar("lerpsubdiv", chdr.lerpsubdiv);
-            setvar("lerpsubdivsize", chdr.lerpsubdivsize);
-        }
-        setsvar("maptitle", chdr.maptitle);
-        hdr.blendmap = chdr.blendmap;
-        hdr.numvars = 0; 
+        if(!hdr.lerpangle) hdr.lerpangle = 44;
+        hdr.lerpsubdiv = 2;
+        hdr.lerpsubdivsize = 4;
     }
-    else lilswap(&hdr.blendmap, 2);
- 
-    loopi(hdr.numvars)
-    {
-        int type = f->getchar(), ilen = f->getlil<ushort>();
-        string name;
-        f->read(name, min(ilen, MAXSTRLEN-1));
-        name[min(ilen, MAXSTRLEN-1)] = '\0';
-        if(ilen >= MAXSTRLEN) f->seek(ilen - (MAXSTRLEN-1), SEEK_CUR);
-        ident *id = getident(name);
-        bool exists = id && id->type == type;
-        switch(type)
-        {
-            case ID_VAR:
-            {
-                int val = f->getlil<int>();
-                if(exists && id->minval <= id->maxval) setvar(name, val);
-                if(dbgvars) conoutf(CON_DEBUG, "read var %s: %d", name, val);
-                break;
-            }
- 
-            case ID_FVAR:
-            {
-                float val = f->getlil<float>();
-                if(exists && id->minvalf <= id->maxvalf) setfvar(name, val);
-                if(dbgvars) conoutf(CON_DEBUG, "read fvar %s: %f", name, val);
-                break;
-            }
+    setvar("lightprecision", hdr.mapprec ? hdr.mapprec : 32);
+    setvar("lighterror", hdr.maple ? hdr.maple : 8);
+    setvar("bumperror", hdr.mapbe ? hdr.mapbe : 3);
+    setvar("lightlod", hdr.mapllod);
+    setvar("lodsize", hdr.mapwlod);
+    setvar("ambient", hdr.ambient);
+    setvar("fullbright", 0);
+    setvar("lerpangle", hdr.lerpangle);
+    setvar("lerpsubdiv", hdr.lerpsubdiv);
+    setvar("lerpsubdivsize", hdr.lerpsubdivsize);
     
-            case ID_SVAR:
-            {
-                int slen = f->getlil<ushort>();
-                string val;
-                f->read(val, min(slen, MAXSTRLEN-1));
-                val[min(slen, MAXSTRLEN-1)] = '\0';
-                if(slen >= MAXSTRLEN) f->seek(slen - (MAXSTRLEN-1), SEEK_CUR);
-                if(exists) setsvar(name, val);
-                if(dbgvars) conoutf(CON_DEBUG, "read svar %s: %s", name, val);
-                break;
-            }
-        }
-    }
-    if(dbgvars) conoutf(CON_DEBUG, "read %d vars", hdr.numvars);
-
     string gametype;
-    copystring(gametype, "fps");
+    s_strcpy(gametype, "fps");
     bool samegame = true;
     int eif = 0;
     if(hdr.version>=16)
     {
-        int len = f->getchar();
-        f->read(gametype, len+1);
+        int len = gzgetc(f);
+        gzread(f, gametype, len+1);
     }
-    if(strcmp(gametype, game::gameident())!=0)
+    if(strcmp(gametype, cl->gameident())!=0)
     {
         samegame = false;
         conoutf(CON_WARN, "WARNING: loading map from %s game, ignoring entities except for lights/mapmodels)", gametype);
     }
     if(hdr.version>=16)
     {
-        eif = f->getlil<ushort>();
-        int extrasize = f->getlil<ushort>();
+        eif = readushort(f);
+        int extrasize = readushort(f);
         vector<char> extras;
-        loopj(extrasize) extras.add(f->getchar());
-        if(samegame) game::readgamedata(extras);
+        loopj(extrasize) extras.add(gzgetc(f));
+        if(samegame) cl->readgamedata(extras);
     }
     
-    renderprogress(0, "clearing world...");
+    show_out_of_renderloop_progress(0, "clearing world...");
 
     texmru.setsize(0);
     if(hdr.version<14)
     {
         uchar oldtl[256];
-        f->read(oldtl, sizeof(oldtl));
+        gzread(f, oldtl, sizeof(oldtl));
         loopi(256) texmru.add(oldtl[i]);
     }
     else
     {
-        ushort nummru = f->getlil<ushort>();
-        loopi(nummru) texmru.add(f->getlil<ushort>());
+        ushort nummru = readushort(f);
+        loopi(nummru) texmru.add(readushort(f));
     }
 
     freeocta(worldroot);
     worldroot = NULL;
 
-    setvar("mapsize", hdr.worldsize, true, false);
-    int worldscale = 0;
-    while(1<<worldscale < hdr.worldsize) worldscale++;
-    setvar("mapscale", worldscale, true, false);
-
-    renderprogress(0, "loading entities...");
+    show_out_of_renderloop_progress(0, "loading entities...");
 
-    vector<extentity *> &ents = entities::getents();
-    int einfosize = entities::extraentinfosize();
-    char *ebuf = einfosize > 0 ? new char[einfosize] : NULL;
-    loopi(min(hdr.numents, MAXENTS))
+    vector<extentity *> &ents = et->getents();
+    char *ebuf = new char[et->extraentinfosize()];
+    loopi(hdr.numents)
     {
-        extentity &e = *entities::newentity();
+        extentity &e = *et->newentity();
         ents.add(&e);
-        f->read(&e, sizeof(entity));
-        lilswap(&e.o.x, 3);
-        lilswap(&e.attr1, 5);
+        gzread(f, &e, sizeof(entity));
+        endianswap(&e.o, sizeof(int), 3);
+        endianswap(&e.attr1, sizeof(short), 5);
         e.spawned = false;
         e.inoctanode = false;
         if(samegame)
         {
-            if(einfosize > 0) f->read(ebuf, einfosize);
-            entities::readent(e, ebuf); 
+            if(et->extraentinfosize()) gzread(f, ebuf, et->extraentinfosize());
+            et->readent(e, ebuf); 
+        }
+        else
+        {
+            loopj(eif) gzgetc(f);
         }
-        else f->seek(eif, SEEK_CUR);
         if(hdr.version <= 14 && e.type >= ET_MAPMODEL && e.type <= 16)
         {
             if(e.type == 16) e.type = ET_MAPMODEL;
@@ -585,7 +482,7 @@ bool load_world(const char *mname, const char *cname)        // still supports a
         {
             if(e.type>=ET_GAMESPECIFIC || hdr.version<=14)
             {
-                entities::deleteentity(ents.pop());
+                ents.pop();
                 continue;
             }
         }
@@ -593,7 +490,7 @@ bool load_world(const char *mname, const char *cname)        // still supports a
         {
             if(e.type != ET_LIGHT && e.type != ET_SPOTLIGHT)
             {
-                conoutf(CON_WARN, "warning: ent outside of world: enttype[%s] index %d (%f, %f, %f)", entities::entname(e.type), i, e.o.x, e.o.y, e.o.z);
+                conoutf(CON_WARN, "warning: ent outside of world: enttype[%s] index %d (%f, %f, %f)", et->entname(e.type), i, e.o.x, e.o.y, e.o.z);
             }
         }
         if(hdr.version <= 14 && e.type == ET_MAPMODEL)
@@ -603,15 +500,9 @@ bool load_world(const char *mname, const char *cname)        // still supports a
             e.attr3 = e.attr4 = 0;
         }
     }
-    if(ebuf) delete[] ebuf;
-
-    if(hdr.numents > MAXENTS) 
-    {
-        conoutf(CON_WARN, "warning: map has %d entities", hdr.numents);
-        f->seek((hdr.numents-MAXENTS)*(sizeof(entity) + einfosize), SEEK_CUR);
-    }
+    delete[] ebuf;
 
-    renderprogress(0, "loading octree...");
+    show_out_of_renderloop_progress(0, "loading octree...");
     worldroot = loadchildren(f);
 
 	if(hdr.version <= 11)
@@ -623,85 +514,70 @@ bool load_world(const char *mname, const char *cname)        // still supports a
     if(hdr.version <= 25 && hdr.worldsize > VVEC_INT_MASK+1)
         fixoversizedcubes(worldroot, hdr.worldsize>>1);
 
-    renderprogress(0, "validating...");
+    show_out_of_renderloop_progress(0, "validating...");
     validatec(worldroot, hdr.worldsize>>1);
 
+    worldscale = 0;
+    while(1<<worldscale < hdr.worldsize) worldscale++;
+
     if(hdr.version >= 7) loopi(hdr.lightmaps)
     {
-        renderprogress(i/(float)hdr.lightmaps, "loading lightmaps...");
+        show_out_of_renderloop_progress(i/(float)hdr.lightmaps, "loading lightmaps...");
         LightMap &lm = lightmaps.add();
         if(hdr.version >= 17)
         {
-            int type = f->getchar();
-            lm.type = type&0x7F;
+            int type = gzgetc(f);
+            lm.type = type&0xF;
             if(hdr.version >= 20 && type&0x80)
             {
-                lm.unlitx = f->getlil<ushort>();
-                lm.unlity = f->getlil<ushort>();
+                lm.unlitx = readushort(f);
+                lm.unlity = readushort(f);
             }
         }
-        if(lm.type&LM_ALPHA && (lm.type&LM_TYPE)!=LM_BUMPMAP1) lm.bpp = 4;
-        lm.data = new uchar[lm.bpp*LM_PACKW*LM_PACKH];
-        f->read(lm.data, lm.bpp * LM_PACKW * LM_PACKH);
+        gzread(f, lm.data, 3 * LM_PACKW * LM_PACKH);
         lm.finalize();
     }
 
-    if(hdr.version >= 25 && hdr.numpvs > 0) loadpvs(f, hdr.numpvs);
-    if(hdr.version >= 28 && hdr.blendmap) loadblendmap(f, hdr.blendmap);
+    if(hdr.version >= 25 && hdr.numpvs > 0) loadpvs(f);
 
-    mapcrc = f->getcrc();
-    delete f;
+    gzclose(f);
 
     conoutf("read map %s (%.1f seconds)", ogzname, (SDL_GetTicks()-loadingstart)/1000.0f);
-
-    clearmainmenu();
+    if(hdr.maptitle[0]) conoutf(CON_ECHO, "%s", hdr.maptitle);
 
     overrideidents = true;
-    execfile("data/default_map_settings.cfg", false);
-    execfile(cfgname, false);
+    execfile("data/default_map_settings.cfg");
+    execfile(pcfname);
+    execfile(mcfname);
     overrideidents = false;
    
     extern void fixlightmapnormals();
     if(hdr.version <= 25) fixlightmapnormals();
 
-    vector<int> mapmodels;
     loopv(ents)
     {
         extentity &e = *ents[i];
         if(e.type==ET_MAPMODEL && e.attr2 >= 0)
         {
-            if(mapmodels.find(e.attr2) < 0) mapmodels.add(e.attr2);
+            mapmodelinfo &mmi = getmminfo(e.attr2);
+            if(!&mmi) conoutf(CON_WARN, "could not find map model: %d", e.attr2);
+            else if(!loadmodel(NULL, e.attr2, true)) conoutf(CON_WARN, "could not load model: %s", mmi.name);
         }
     }
 
-    loopv(mapmodels)
-    {
-        loadprogress = float(i+1)/mapmodels.length();
-        int mmindex = mapmodels[i];
-        mapmodelinfo &mmi = getmminfo(mmindex);
-        if(!&mmi) conoutf(CON_WARN, "could not find map model: %d", mmindex);
-        else if(!loadmodel(NULL, mmindex, true)) conoutf(CON_WARN, "could not load model: %s", mmi.name);
-    }
-    loadprogress = 0;
-
-    game::preload();
-    flushpreloadedmodels();
+    cl->preload();
 
-    entitiesinoctanodes();
-    attachentities();
     initlights();
     allchanged(true);
 
-    renderbackground("loading...", mapshot, mname, game::getmapinfo());
-
-    if(maptitle[0]) conoutf(CON_ECHO, "%s", maptitle);
+    computescreen("loading...", mapshot!=notexture ? mapshot : NULL, mname);
+    attachentities();
 
     startmap(cname ? cname : mname);
-    
     return true;
 }
 
-void savecurrentmap() { save_world(game::getclientmap()); }
+void savecurrentmap() { save_world(cl->getclientmap()); }
 void savemap(char *mname) { save_world(mname); }
 
 COMMAND(savemap, "s");
@@ -709,10 +585,10 @@ COMMAND(savecurrentmap, "");
 
 void writeobj(char *name)
 {
-    defformatstring(fname)("%s.obj", name);
-    stream *f = openfile(path(fname), "w"); 
+    s_sprintfd(fname)("%s.obj", name);
+    FILE *f = openfile(path(fname), "w"); 
     if(!f) return;
-    f->printf("# obj file of Cube 2: Sauerbraten level\n");
+    fprintf(f, "# obj file of sauerbraten level\n");
     extern vector<vtxarray *> valist;
     loopv(valist)
     {
@@ -727,23 +603,23 @@ void writeobj(char *name)
             vec v;
             if(floatvtx) (v = *(vec *)vert).div(1<<VVEC_FRAC); 
             else v = ((vvec *)vert)->tovec(va.o).add(0x8000>>VVEC_FRAC);
-            if(v.x != floor(v.x)) f->printf("v %.3f ", v.x); else f->printf("v %d ", int(v.x));
-            if(v.y != floor(v.y)) f->printf("%.3f ", v.y); else f->printf("%d ", int(v.y));
-            if(v.z != floor(v.z)) f->printf("%.3f\n", v.z); else f->printf("%d\n", int(v.z));
+            if(v.x != floor(v.x)) fprintf(f, "v %.3f ", v.x); else fprintf(f, "v %d ", int(v.x));
+            if(v.y != floor(v.y)) fprintf(f, "%.3f ", v.y); else fprintf(f, "%d ", int(v.y));
+            if(v.z != floor(v.z)) fprintf(f, "%.3f\n", v.z); else fprintf(f, "%d\n", int(v.z));
             vert += vtxsize;
         }
         ushort *tri = edata;
         loopi(va.tris)
         {
-            f->printf("f");
-            for(int k = 0; k<3; k++) f->printf(" %d", tri[k]-va.verts-va.voffset);
+            fprintf(f, "f");
+            for(int k = 0; k<3; k++) fprintf(f, " %d", tri[k]-va.verts-va.voffset);
             tri += 3;
-            f->printf("\n");
+            fprintf(f, "\n");
         }
         delete[] edata;
         delete[] vdata;
     }
-    delete f;
+    fclose(f);
 }  
     
 COMMAND(writeobj, "s"); 
diff --git a/fpsgame/ai.cpp b/fpsgame/ai.cpp
deleted file mode 100644
index dc1967c..0000000
--- a/fpsgame/ai.cpp
+++ /dev/null
@@ -1,1203 +0,0 @@
-#include "game.h"
-
-namespace ai
-{
-    using namespace game;
-
-    avoidset obstacles;
-    int updatemillis = 0, forcegun = -1;
-    vec aitarget(0, 0, 0);
-
-    VAR(aidebug, 0, 0, 6);
-    VAR(aiforcegun, -1, -1, NUMGUNS-1);
-
-    ICOMMAND(addbot, "s", (char *s), addmsg(SV_ADDBOT, "ri", *s ? clamp(atoi(s), 1, 101) : -1));
-    ICOMMAND(delbot, "", (), addmsg(SV_DELBOT, "r"));
-    ICOMMAND(botlimit, "i", (int *n), addmsg(SV_BOTLIMIT, "ri", *n));
-    ICOMMAND(botbalance, "i", (int *n), addmsg(SV_BOTBALANCE, "ri", *n));
-
-    float viewdist(int x)
-    {
-        int fog = getvar("fog");
-        return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), float(fog)) : float(fog);
-    }
-
-    float viewfieldx(int x)
-    {
-        return x <= 100 ? clamp((VIEWMIN+(VIEWMAX-VIEWMIN))/100.f*float(x), float(VIEWMIN), float(VIEWMAX)) : float(VIEWMAX);
-    }
-
-    float viewfieldy(int x)
-    {
-        return viewfieldx(x)*3.f/4.f;
-    }
-
-    bool canmove(fpsent *d)
-    {
-        return d->state != CS_DEAD && !intermission;
-    }
-
-    bool targetable(fpsent *d, fpsent *e, bool anyone)
-    {
-        if(d == e || !canmove(d)) return false;
-        aistate &b = d->ai->getstate();
-        if(b.type != AI_S_WAIT)
-            return e->state == CS_ALIVE && (!anyone || !isteam(d->team, e->team));
-        return false;
-    }
-
-    bool getsight(vec &o, float yaw, float pitch, vec &q, vec &v, float mdist, float fovx, float fovy)
-    {
-        float dist = o.dist(q);
-
-        if(dist <= mdist)
-        {
-            float x = fabs((asin((q.z-o.z)/dist)/RAD)-pitch);
-            float y = fabs((-(float)atan2(q.x-o.x, q.y-o.y)/PI*180+180)-yaw);
-            if(x <= fovx && y <= fovy) return raycubelos(o, q, v);
-        }
-        return false;
-    }
-
-    bool cansee(fpsent *d, vec &x, vec &y, vec &targ)
-    {
-        aistate &b = d->ai->getstate();
-        if(canmove(d) && b.type != AI_S_WAIT)
-            return getsight(x, d->yaw, d->pitch, y, targ, d->ai->views[2], d->ai->views[0], d->ai->views[1]);
-        return false;
-    }
-
-    vec getaimpos(fpsent *d, fpsent *e)
-    {
-        vec o = e->o;
-        if(d->skill <= 100)
-        {
-			float optimal = 1.f, scale = 1.f/float(d->skill);
-			if(d->gunselect == GUN_RL) optimal = (e->aboveeye*0.2f)-(0.8f*d->eyeheight);
-			else if(d->gunselect != GUN_GL) optimal = (e->aboveeye-e->eyeheight)*0.5f;
-			o.z += rnd(d->skill) ? optimal*scale : -optimal*scale;
-        }
-        return o;
-    }
-
-    void create(fpsent *d)
-    {
-        if(!d->ai) d->ai = new aiinfo;
-        if(d->ai)
-        {
-            d->ai->views[0] = viewfieldx(d->skill);
-            d->ai->views[1] = viewfieldy(d->skill);
-            d->ai->views[2] = viewdist(d->skill);
-        }
-    }
-
-    void destroy(fpsent *d)
-    {
-        if(d->ai) DELETEP(d->ai);
-    }
-
-    void init(fpsent *d, int at, int ocn, int sk, int bn, int pm, const char *name, const char *team)
-    {
-        loadwaypoints();
-
-        fpsent *o = newclient(ocn);
-
-        d->aitype = at;
-
-        bool resetthisguy = false;
-        if(!d->name[0])
-        {
-            if(aidebug) conoutf("%s assigned to %s at skill %d", colorname(d, name), o ? colorname(o) : "?", sk);
-            else conoutf("connected: %s", colorname(d, name));
-            resetthisguy = true;
-        }
-        else
-        {
-            if(d->ownernum != ocn)
-            {
-                if(aidebug) conoutf("%s reassigned to %s", colorname(d, name), o ? colorname(o) : "?");
-                resetthisguy = true;
-            }
-            if(d->skill != sk && aidebug) conoutf("%s changed skill to %d", colorname(d, name), sk);
-        }
-
-        copystring(d->name, name, MAXNAMELEN+1);
-        copystring(d->team, team, MAXTEAMLEN+1);
-        d->ownernum = ocn;
-        d->skill = sk;
-        d->playermodel = chooserandomplayermodel(pm);
-
-        if(resetthisguy) removeweapons(d);
-        if(player1->clientnum == d->ownernum) create(d);
-        else if(d->ai) destroy(d);
-    }
-
-    void update()
-    {
-        bool updating = lastmillis-updatemillis > 100; // fixed rate logic at 10fps
-        if(updating)
-        {
-        	avoid();
-        	forcegun = multiplayer(false) ? -1 : aiforcegun;
-        }
-		loopv(players) if(players[i]->ai)
-		{
-			if(!intermission) think(players[i], updating);
-			else players[i]->stopmoving();
-		}
-		if(updating) updatemillis = lastmillis;
-    }
-
-    bool checkothers(vector<int> &targets, fpsent *d, int state, int targtype, int target, bool teams)
-    { // checks the states of other ai for a match
-        targets.setsizenodelete(0);
-        loopv(players)
-        {
-            fpsent *e = players[i];
-            if(e == d || !e->ai || e->state != CS_ALIVE) continue;
-            if(targets.find(e->clientnum) >= 0) continue;
-            if(teams && d && !isteam(d->team, e->team)) continue;
-            aistate &b = e->ai->getstate();
-            if(state >= 0 && b.type != state) continue;
-            if(target >= 0 && b.target != target) continue;
-            if(targtype >=0 && b.targtype != targtype) continue;
-            targets.add(e->clientnum);
-        }
-        return !targets.empty();
-    }
-
-    bool makeroute(fpsent *d, aistate &b, int node, bool changed, bool check)
-    {
-        int n = node;
-        if((n == d->lastnode || d->ai->hasprevnode(n)) && waypoints.inrange(d->lastnode))
-        {
-            waypoint &w = waypoints[d->lastnode];
-            static vector<int> noderemap; noderemap.setsizenodelete(0);
-            if(w.links[0]) loopi(MAXWAYPOINTLINKS)
-            {
-                int link = w.links[i];
-                if(!link) break;
-                if(link != d->lastnode && !d->ai->hasprevnode(link))
-                    noderemap.add(link);
-            }
-            if(!noderemap.empty()) n = noderemap[rnd(noderemap.length())];
-        }
-        if(n != d->lastnode)
-        {
-            if(changed && !d->ai->route.empty() && d->ai->route[0] == n) return true;
-            if(route(d, d->lastnode, n, d->ai->route, obstacles, check))
-            {
-                b.override = false;
-                return true;
-            }
-        }
-        d->ai->route.setsize(0);
-		if(check) return makeroute(d, b, n, true, false);
-        return false;
-    }
-
-    bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed, bool check)
-    {
-        int node = closestwaypoint(pos, NEARDIST, true);
-        return makeroute(d, b, node, changed, check);
-    }
-
-    bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard, float wander)
-    {
-        static vector<int> candidates;
-        candidates.setsizenodelete(0);
-        findwaypointswithin(pos, guard, wander, candidates);
-
-        while(!candidates.empty())
-        {
-            int w = rnd(candidates.length()), n = candidates.removeunordered(w);
-            if(n != d->lastnode && !d->ai->hasprevnode(n) && !obstacles.find(n, d) && makeroute(d, b, n)) return true;
-        }
-        return false;
-    }
-
-    bool randomnode(fpsent *d, aistate &b, float guard, float wander)
-    {
-        return randomnode(d, b, d->feetpos(), guard, wander);
-    }
-
-    bool badhealth(fpsent *d)
-    {
-        if(d->skill <= 100) return d->health <= (111-d->skill)/4;
-        return false;
-    }
-
-    bool enemy(fpsent *d, aistate &b, const vec &pos, float guard = NEARDIST, bool pursue = false)
-    {
-        if(canmove(d))
-        {
-            fpsent *t = NULL;
-            vec dp = d->headpos(), tp(0, 0, 0);
-            bool insight = false, tooclose = false;
-            float mindist = guard*guard;
-            loopv(players)
-            {
-                fpsent *e = players[i];
-                if(e == d || !targetable(d, e, true)) continue;
-                vec ep = getaimpos(d, e);
-                bool close = ep.squaredist(pos) < mindist;
-                if(!t || ep.squaredist(dp) < tp.squaredist(dp) || close)
-                {
-                    bool see = cansee(d, dp, ep);
-                    if(!insight || see || close)
-                    {
-                        t = e; tp = ep;
-                        if(!insight && see) insight = see;
-                        if(!tooclose && close) tooclose = close;
-                    }
-                }
-            }
-            if(t && violence(d, b, t, pursue && (tooclose || insight))) return insight || tooclose;
-        }
-        return false;
-    }
-
-    void noenemy(fpsent *d)
-    {
-        d->ai->enemy = -1;
-        d->ai->enemymillis = 0;
-    }
-
-    bool patrol(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk, bool retry)
-    {
-        vec feet = d->feetpos();
-        if(walk == 2 || b.override || (walk && feet.squaredist(pos) <= guard*guard) || !makeroute(d, b, pos))
-        { // run away and back to keep ourselves busy
-            if(!b.override && randomnode(d, b, pos, guard, wander))
-            {
-                b.override = true;
-                return true;
-            }
-            else if(!d->ai->route.empty()) return true;
-            else if(!retry)
-            {
-                b.override = false;
-                return patrol(d, b, pos, guard, wander, walk, true);
-            }
-            b.override = false;
-            return false;
-        }
-        b.override = false;
-        return true;
-    }
-
-    bool defend(fpsent *d, aistate &b, const vec &pos, float guard, float wander, int walk)
-    {
-		bool hasenemy = enemy(d, b, pos, wander, false);
-		if(!walk && d->feetpos().squaredist(pos) <= guard*guard)
-		{
-			b.idle = hasenemy ? 2 : 1;
-			return true;
-		}
-        return patrol(d, b, pos, guard, wander, walk);
-    }
-
-    bool violence(fpsent *d, aistate &b, fpsent *e, bool pursue)
-    {
-        if(targetable(d, e, true))
-        {
-            if(pursue || d->gunselect == GUN_FIST || (b.type == AI_S_INTEREST && b.targtype == AI_T_NODE))
-                d->ai->addstate(AI_S_PURSUE, AI_T_PLAYER, e->clientnum);
-            if(d->ai->enemy != e->clientnum) d->ai->enemymillis = lastmillis;
-            d->ai->enemy = e->clientnum;
-            d->ai->enemyseen = lastmillis;
-            vec dp = d->headpos(), ep = getaimpos(d, e);
-            if(!cansee(d, dp, ep)) d->ai->enemyseen -= ((111-d->skill)*10)+10; // so we don't "quick"
-            return true;
-        }
-        return false;
-    }
-
-    bool target(fpsent *d, aistate &b, bool pursue = false, bool force = false)
-    {
-        fpsent *t = NULL;
-        vec dp = d->headpos(), tp(0, 0, 0);
-        loopv(players)
-        {
-            fpsent *e = players[i];
-            if(e == d || !targetable(d, e, true)) continue;
-            vec ep = getaimpos(d, e);
-            if((!t || ep.squaredist(dp) < tp.squaredist(dp)) && (force || cansee(d, dp, ep)))
-            {
-                t = e;
-                tp = ep;
-            }
-        }
-        if(t) return violence(d, b, t, pursue);
-        return false;
-    }
-
-    int isgoodammo(int gun) { return gun >= GUN_SG && gun <= GUN_GL; }
-
-    bool hasgoodammo(fpsent *d)
-    {
-        static const int goodguns[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE };
-        loopi(sizeof(goodguns)/sizeof(goodguns[0])) if(d->hasammo(goodguns[0])) return true;
-        if(d->ammo[GUN_GL] > 5) return true;
-        return false;
-    }
-
-    void assist(fpsent *d, aistate &b, vector<interest> &interests, bool all = false, bool force = false)
-    {
-        loopv(players)
-        {
-            fpsent *e = players[i];
-            if(e == d || (!all && e->aitype != AI_NONE) || !isteam(d->team, e->team)) continue;
-            interest &n = interests.add();
-            n.state = AI_S_DEFEND;
-            n.node = e->lastnode;
-            n.target = e->clientnum;
-            n.targtype = AI_T_PLAYER;
-            n.score = e->o.squaredist(d->o)/(force || hasgoodammo(d) ? 10.f : 1.f);
-        }
-    }
-
-    static void tryitem(fpsent *d, extentity &e, int id, aistate &b, vector<interest> &interests, bool force = false)
-    {
-        float score = 1.0f;
-        if(force) score = 100.0f;
-        else switch(e.type)
-        {
-            case I_HEALTH:
-                if(d->health < min(d->skill, 75)) score = 100.0f;
-                break;
-            case I_QUAD: score = 70.0f; break;
-            case I_BOOST: score = 50.0f; break;
-            case I_GREENARMOUR: case I_YELLOWARMOUR:
-            {
-                int atype = A_GREEN + e.type - I_GREENARMOUR;
-                if(atype > d->armourtype) score = atype == A_YELLOW ? 50.0f : 25.0f;
-                else if(d->armour < 50) score = 20.0f;
-                break;
-            }
-            default:
-                if(e.type >= I_SHELLS && e.type <= I_CARTRIDGES)
-                {
-                    int gun = e.type - I_SHELLS + GUN_SG;
-                    // go get a weapon upgrade
-                    if(gun == d->ai->weappref) score = 100.0f;
-                    else if(isgoodammo(gun)) score = hasgoodammo(d) ? 15.0f : 75.0f;
-                }
-                break;
-        }
-        interest &n = interests.add();
-        n.state = AI_S_INTEREST;
-        n.node = closestwaypoint(e.o, NEARDIST, true);
-        n.target = id;
-        n.targtype = AI_T_ENTITY;
-        n.score = d->feetpos().squaredist(e.o)/score;
-    }
-
-    void items(fpsent *d, aistate &b, vector<interest> &interests, bool force = false)
-    {
-        loopv(entities::ents)
-        {
-            extentity &e = *(extentity *)entities::ents[i];
-            if(!e.spawned || !d->canpickup(e.type)) continue;
-            tryitem(d, e, i, b, interests, force);
-        }
-    }
-
-    static vector<int> targets;
-
-    bool find(fpsent *d, aistate &b, bool override = false)
-    {
-        static vector<interest> interests;
-        interests.setsizenodelete(0);
-        if(!m_noitems)
-        {
-            if((!m_noammo && !hasgoodammo(d)) || d->health < min(d->skill - 15, 75))
-                items(d, b, interests);
-            else
-            {
-                static vector<int> nearby;
-                nearby.setsizenodelete(0);
-                findents(I_SHELLS, I_QUAD, false, d->feetpos(), vec(32, 32, 24), nearby);
-                loopv(nearby)
-                {
-                    int id = nearby[i];
-                    extentity &e = *(extentity *)entities::ents[id];
-                    if(d->canpickup(e.type)) tryitem(d, e, id, b, interests);
-                }
-            }
-        }
-        if(cmode) cmode->aifind(d, b, interests);
-        if(m_teammode) assist(d, b, interests);
-        while(!interests.empty())
-        {
-            int q = interests.length()-1;
-            loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i;
-            interest n = interests.removeunordered(q);
-            bool proceed = true;
-            switch(n.state)
-            {
-                case AI_S_DEFEND: // don't get into herds
-                    proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true);
-                    break;
-                default: break;
-            }
-            if(proceed && makeroute(d, b, n.node, false))
-            {
-                d->ai->setstate(n.state, n.targtype, n.target, override);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void damaged(fpsent *d, fpsent *e)
-    {
-        if(d->ai && canmove(d) && targetable(d, e, true)) // see if this ai is interested in a grudge
-        {
-            aistate &b = d->ai->getstate();
-            if(violence(d, b, e, false)) return;
-        }
-        if(checkothers(targets, d, AI_S_DEFEND, AI_T_PLAYER, d->clientnum, true))
-        {
-            loopv(targets)
-            {
-                fpsent *t = getclient(targets[i]);
-                if(!t->ai || !canmove(t) || !targetable(t, e, true)) continue;
-                aistate &c = t->ai->getstate();
-                if(violence(t, c, e, false)) return;
-            }
-        }
-    }
-
-    void setup(fpsent *d, bool tryreset = false)
-    {
-        d->ai->reset(tryreset);
-        aistate &b = d->ai->getstate();
-        b.next = lastmillis+((111-d->skill)*10)+rnd((111-d->skill)*10);
-        if(m_insta) d->ai->weappref = GUN_RIFLE;
-        else
-        {
-        	if(forcegun >= 0 && forcegun < NUMGUNS) d->ai->weappref = forcegun;
-        	else if(m_noammo) d->ai->weappref = -1;
-			else d->ai->weappref = rnd(GUN_GL-GUN_SG+1)+GUN_SG;
-        }
-    }
-
-    void spawned(fpsent *d)
-    {
-        if(d->ai) setup(d, false);
-    }
-
-    void killed(fpsent *d, fpsent *e)
-    {
-        if(d->ai) d->ai->reset();
-    }
-
-    bool check(fpsent *d, aistate &b)
-    {
-        if(cmode && cmode->aicheck(d, b)) return true;
-        return false;
-    }
-
-    int dowait(fpsent *d, aistate &b)
-    {
-        if(check(d, b)) return 1;
-        if(find(d, b)) return 1;
-        if(target(d, b, true, true)) return 1;
-        if(randomnode(d, b, NEARDIST, 1e16f))
-        {
-            d->ai->addstate(AI_S_INTEREST, AI_T_NODE, d->ai->route[0]);
-            return 1;
-        }
-        return 0; // but don't pop the state
-    }
-
-    int dodefend(fpsent *d, aistate &b)
-    {
-        if(d->state == CS_ALIVE)
-        {
-            switch(b.targtype)
-            {
-                case AI_T_NODE:
-                    if(check(d, b)) return 1;
-                    if(waypoints.inrange(b.target)) return defend(d, b, waypoints[b.target].o) ? 1 : 0;
-                    break;
-                case AI_T_ENTITY:
-                    if(check(d, b)) return 1;
-                    if(entities::ents.inrange(b.target)) return defend(d, b, entities::ents[b.target]->o) ? 1 : 0;
-                    break;
-                case AI_T_AFFINITY:
-                    if(cmode) return cmode->aidefend(d, b) ? 1 : 0;
-                    break;
-                case AI_T_PLAYER:
-                {
-                    fpsent *e = getclient(b.target);
-                    if(e && e->state == CS_ALIVE) return defend(d, b, e->feetpos()) ? 1 : 0;
-                    break;
-                }
-                default: break;
-            }
-        }
-        return 0;
-    }
-
-    int dointerest(fpsent *d, aistate &b)
-    {
-        if(d->state != CS_ALIVE) return 0;
-        switch(b.targtype)
-        {
-            case AI_T_NODE:
-                return !d->ai->route.empty() ? 1 : 0;
-            case AI_T_ENTITY:
-                if(d->hasammo(d->ai->weappref)) return 0;
-                if(entities::ents.inrange(b.target))
-                {
-                    extentity &e = *(extentity *)entities::ents[b.target];
-                    if(e.type < I_SHELLS || e.type > I_CARTRIDGES) return 0;
-                    if(!e.spawned || d->hasmaxammo(e.type)) return 0;
-                    return makeroute(d, b, e.o) ? 1 : 0;
-                }
-                break;
-        }
-        return 0;
-    }
-
-    int dopursue(fpsent *d, aistate &b)
-    {
-        if(d->state == CS_ALIVE)
-        {
-            switch(b.targtype)
-            {
-                case AI_T_AFFINITY:
-                {
-                    if(cmode) return cmode->aipursue(d, b) ? 1 : 0;
-                    break;
-                }
-
-                case AI_T_PLAYER:
-                {
-                    fpsent *e = getclient(b.target);
-                    if(e && e->state == CS_ALIVE)
-                    {
-                    	float guard = NEARDIST, wander = guns[d->gunselect].range;
-                    	if(d->gunselect == GUN_FIST) guard = 0.f;
-                    	return patrol(d, b, e->feetpos(), guard, wander) ? 1 : 0;
-                    }
-                    break;
-                }
-                default: break;
-            }
-        }
-        return 0;
-    }
-
-    int closenode(fpsent *d, bool force = false)
-    {
-        vec pos = d->feetpos();
-        int node = -1;
-        float mindist = NEARDISTSQ;
-        loopv(d->ai->route) if(waypoints.inrange(d->ai->route[i]) && (force || (d->ai->route[i] != d->lastnode && !d->ai->hasprevnode(d->ai->route[i]))))
-        {
-            waypoint &w = waypoints[d->ai->route[i]];
-            vec wpos = w.o;
-            int id = obstacles.remap(d, d->ai->route[i], wpos);
-            if(waypoints.inrange(id) && (force || id == d->ai->route[i] || !d->ai->hasprevnode(id)))
-            {
-                float dist = wpos.squaredist(pos);
-                if(dist < mindist)
-                {
-                    node = id;
-                    mindist = dist;
-                }
-            }
-        }
-        return node;
-    }
-
-    bool wpspot(fpsent *d, int n, bool force = false)
-    {
-        if(waypoints.inrange(n))
-        {
-            waypoint &w = waypoints[n];
-            vec wpos = w.o;
-            int id = obstacles.remap(d, n, wpos);
-            if(waypoints.inrange(id) && (force || id == n || !d->ai->hasprevnode(id)))
-            {
-                d->ai->spot = wpos;
-                return true;
-            }
-        }
-        return false;
-    }
-
-    bool anynode(fpsent *d, aistate &b, bool retry = false)
-    {
-        if(!waypoints.inrange(d->lastnode)) return false;
-        waypoint &w = waypoints[d->lastnode];
-        vec dir = vec(w.o).sub(d->feetpos());
-        if(d->timeinair || dir.magnitude() <= CLOSEDIST || retry)
-        {
-            static vector<int> anyremap; anyremap.setsizenodelete(0);
-            waypoint &w = waypoints[d->lastnode];
-            if(w.links[0])
-            {
-                loopi(MAXWAYPOINTLINKS)
-                {
-                    int link = w.links[i];
-                    if(!link) break;
-                    if(waypoints.inrange(link) && link != d->lastnode && (retry || !d->ai->hasprevnode(link)))
-                        anyremap.add(link);
-                }
-            }
-            while(!anyremap.empty())
-            {
-                int r = rnd(anyremap.length()), t = anyremap[r];
-                if(wpspot(d, t, retry)) return true;
-                anyremap.remove(r);
-            }
-        }
-        if(!retry)
-        {
-            if(wpspot(d, d->lastnode, true)) return true;
-            return anynode(d, b, true);
-        }
-        return false;
-    }
-
-    bool hunt(fpsent *d, aistate &b, int retries = 0)
-    {
-        if(!d->ai->route.empty())
-        {
-            bool alternate = (retries%2)!=0;
-            int n = alternate ? closenode(d, retries == 3) : d->ai->route.find(d->lastnode);
-            if(!alternate && d->ai->route.inrange(n))
-            {
-                while(d->ai->route.length() > n+1) d->ai->route.pop(); // waka-waka-waka-waka
-                if(!n)
-                {
-                    if(wpspot(d, d->ai->route[n], retries > 1))
-                    {
-                        if(vec(d->ai->spot).sub(d->feetpos()).magnitude() <= CLOSEDIST/2.f)
-                        {
-                            d->ai->dontmove = true;
-                            d->ai->route.setsize(0);
-                        }
-                        return true; // this is our goal?
-                    }
-                    else return anynode(d, b);
-                }
-                else n--; // otherwise, we want the next in line
-            }
-            if(d->ai->route.inrange(n) && wpspot(d, d->ai->route[n], retries > 1)) return true;
-            if(retries < 3) return hunt(d, b, retries+1); // try again
-            d->ai->route.setsize(0);
-        }
-        b.override = false;
-        return anynode(d, b);
-    }
-
-    bool hastarget(fpsent *d, aistate &b, fpsent *e)
-    { // add margins of error
-        if(d->skill <= 100 && !rnd(d->skill*10)) return true; // random margin of error
-        vec dp = d->headpos(), ep = getaimpos(d, e);
-        fpsent *h = (fpsent *)intersectclosest(dp, d->ai->target, d);
-        if(h && !targetable(d, h, true)) return false;
-        float targyaw, targpitch, mindist = d->radius*d->radius, dist = dp.squaredist(ep), range = guns[d->gunselect].range + d->radius;
-        if(guns[d->gunselect].projspeed && (b.type != AI_S_DEFEND || b.targtype != AI_T_AFFINITY)) mindist = RL_DAMRAD*RL_DAMRAD; // do if we're stuck guarding
-        if(d->skill <= 100) mindist -= mindist*(1.f/float(d->skill));
-        if((d->gunselect == GUN_FIST || mindist <= dist) && dist <= range*range)
-        {
-            if(d->skill > 100 && h) return true;
-            vec dir = vec(dp).sub(ep).normalize();
-            vectoyawpitch(dir, targyaw, targpitch);
-            float rtime = (d->skill*guns[d->gunselect].attackdelay/200.f),
-                    skew = clamp(float(lastmillis-d->ai->enemymillis)/float(rtime), 0.f, guns[d->gunselect].projspeed ? 1.f : 1e16f),
-                        cyaw = fabs(targyaw-d->yaw), cpitch = fabs(targpitch-d->pitch);
-            if(cyaw <= d->ai->views[0]*skew && cpitch <= d->ai->views[1]*skew) return true;
-        }
-        return false;
-    }
-
-    void jumpto(fpsent *d, aistate &b, const vec &pos)
-    {
-        vec off = vec(pos).sub(d->feetpos()), dir(off.x, off.y, 0);
-        bool offground = d->timeinair && !d->inwater, jumper = off.z >= JUMPMIN,
-            jump = jumper || lastmillis >= d->ai->jumprand;
-        if(jump)
-        {
-            if(offground || lastmillis < d->ai->jumpseed) jump = false;
-            else
-            {
-                vec old = d->o;
-                d->o = vec(pos).add(vec(0, 0, d->eyeheight));
-                if(!collide(d, vec(0, 0, 1))) jump = false;
-                d->o = old;
-            }
-        }
-        if(jump)
-        {
-            d->jumping = true;
-            //d->ai->dontmove = true; // going up
-            int seed = (111-d->skill)*10;
-            d->ai->jumpseed = lastmillis+seed+rnd(seed);
-            seed *= b.idle ? 25 : 50;
-            d->ai->jumprand = lastmillis+seed+rnd(seed);
-        }
-    }
-
-    void fixfullrange(float &yaw, float &pitch, float &roll, bool full)
-    {
-        if(full)
-        {
-            while(pitch < -180.0f) pitch += 360.0f;
-            while(pitch >= 180.0f) pitch -= 360.0f;
-            while(roll < -180.0f) roll += 360.0f;
-            while(roll >= 180.0f) roll -= 360.0f;
-        }
-        else
-        {
-            if(pitch > 89.9f) pitch = 89.9f;
-            if(pitch < -89.9f) pitch = -89.9f;
-            if(roll > 89.9f) roll = 89.9f;
-            if(roll < -89.9f) roll = -89.9f;
-        }
-        while(yaw < 0.0f) yaw += 360.0f;
-        while(yaw >= 360.0f) yaw -= 360.0f;
-    }
-
-    void fixrange(float &yaw, float &pitch)
-    {
-        float r = 0.f;
-        fixfullrange(yaw, pitch, r, false);
-    }
-
-    void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch)
-    {
-        float dist = from.dist(pos);
-        yaw = -(float)atan2(pos.x-from.x, pos.y-from.y)/PI*180+180;
-        pitch = asin((pos.z-from.z)/dist)/RAD;
-    }
-
-    void scaleyawpitch(float &yaw, float &pitch, float targyaw, float targpitch, float frame, float scale)
-    {
-        if(yaw < targyaw-180.0f) yaw += 360.0f;
-        if(yaw > targyaw+180.0f) yaw -= 360.0f;
-        float offyaw = fabs(targyaw-yaw)*frame, offpitch = fabs(targpitch-pitch)*frame*scale;
-        if(targyaw > yaw)
-        {
-            yaw += offyaw;
-            if(targyaw < yaw) yaw = targyaw;
-        }
-        else if(targyaw < yaw)
-        {
-            yaw -= offyaw;
-            if(targyaw > yaw) yaw = targyaw;
-        }
-        if(targpitch > pitch)
-        {
-            pitch += offpitch;
-            if(targpitch < pitch) pitch = targpitch;
-        }
-        else if(targpitch < pitch)
-        {
-            pitch -= offpitch;
-            if(targpitch > pitch) pitch = targpitch;
-        }
-        fixrange(yaw, pitch);
-    }
-
-    bool canshoot(fpsent *d)
-    {
-        return d->ammo[d->gunselect] > 0 && lastmillis - d->lastaction >= d->gunwait;
-    }
-
-    int process(fpsent *d, aistate &b)
-    {
-        int result = 0, stupify = d->skill <= 30+rnd(20) ? rnd(d->skill*1111) : 0, skmod = (111-d->skill)*10;
-        float frame = float(lastmillis-d->ai->lastrun)/float(skmod/2);
-        vec dp = d->headpos();
-        if(b.idle == 1 || (stupify && stupify <= skmod))
-        {
-            d->ai->lastaction = d->ai->lasthunt = lastmillis;
-            d->ai->dontmove = true;
-        }
-        else if(hunt(d, b))
-        {
-            getyawpitch(dp, vec(d->ai->spot).add(vec(0, 0, d->eyeheight)), d->ai->targyaw, d->ai->targpitch);
-            d->ai->lasthunt = lastmillis;
-        }
-        else d->ai->dontmove = true;
-		if(!d->ai->dontmove) jumpto(d, b, d->ai->spot);
-
-        fpsent *e = getclient(d->ai->enemy);
-        if(d->skill > 90 && (!e || !targetable(d, e, true))) e = (fpsent *)intersectclosest(dp, d->ai->target, d);
-        if(e && targetable(d, e, true))
-        {
-            vec ep = getaimpos(d, e);
-            bool insight = cansee(d, dp, ep), hasseen = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*50)+1000,
-                quick = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= skmod, targeted = hastarget(d, b, e), idle = b.idle == 1;
-			if(d->gunselect == GUN_FIST && targeted)
-			{
-				d->ai->spot = e->feetpos();
-				getyawpitch(dp, vec(d->ai->spot).add(vec(0, 0, d->eyeheight)), d->ai->targyaw, d->ai->targpitch);
-				idle = d->ai->dontmove = false;
-				insight = true;
-			}
-            if(insight) d->ai->enemyseen = lastmillis;
-            if(idle || insight || hasseen)
-            {
-                float yaw, pitch;
-                getyawpitch(dp, ep, yaw, pitch);
-                fixrange(yaw, pitch);
-                float sskew = (insight ? 2.f : (hasseen ? 1.f : 0.5f))*((insight || hasseen) && (d->jumping || d->timeinair) ? 1.5f : 1.f);
-                if(idle)
-                {
-                    d->ai->targyaw = yaw;
-                    d->ai->targpitch = pitch;
-                    if(!insight) frame /= 3.f;
-                }
-                else if(!insight) frame /= 2.f;
-                scaleyawpitch(d->yaw, d->pitch, yaw, pitch, frame, sskew);
-                if(insight || quick)
-                {
-                    if(targeted && canshoot(d))
-                    {
-                        d->attacking = true;
-                        d->ai->lastaction = lastmillis;
-                        result = 4;
-                    }
-                    else result = insight ? 3 : 2;
-                }
-                else
-                {
-                    if(!b.idle && !hasseen) noenemy(d);
-                    result = hasseen ? 2 : 1;
-                }
-            }
-            else
-            {
-                if(!b.idle) noenemy(d);
-                result = 0;
-                frame /= 2.f;
-            }
-        }
-        else
-        {
-            if(!b.idle) noenemy(d);
-            result = 0;
-            frame /= 2.f;
-        }
-
-        fixrange(d->ai->targyaw, d->ai->targpitch);
-        float aimyaw = d->ai->targyaw; //float aimpitch = d->ai->targpitch;
-        if(!result) scaleyawpitch(d->yaw, d->pitch, d->ai->targyaw, d->ai->targpitch, frame, 1.f);
-
-        if(!d->ai->dontmove)
-        { // our guys move one way.. but turn another?! :)
-            const struct aimdir { int move, strafe, offset; } aimdirs[8] =
-            {
-                {  1,  0,   0 },
-                {  1,  -1,  45 },
-                {  0,  -1,  90 },
-                { -1,  -1, 135 },
-                { -1,  0, 180 },
-                { -1, 1, 225 },
-                {  0, 1, 270 },
-                {  1, 1, 315 }
-            };
-            float yaw = aimyaw-d->yaw;
-            while(yaw < 0.0f) yaw += 360.0f;
-            while(yaw >= 360.0f) yaw -= 360.0f;
-            int r = clamp(((int)floor((yaw+22.5f)/45.0f))&7, 0, 7);
-            const aimdir &ad = aimdirs[r];
-            d->move = ad.move;
-            d->strafe = ad.strafe;
-            //aimyaw -= ad.offset;
-            //fixrange(d->aimyaw, d->aimpitch);
-        }
-        else d->move = d->strafe = 0;
-        d->ai->dontmove = false;
-        d->ai->lastrun = lastmillis;
-        return result;
-    }
-
-    void chooseweapon(fpsent *d)
-    {
-        static const int gunprefs[] = { GUN_CG, GUN_RL, GUN_SG, GUN_RIFLE, GUN_GL, GUN_PISTOL, GUN_FIST };
-        int gun = -1;
-        if(d->hasammo(d->ai->weappref)) gun = d->ai->weappref;
-        else
-        {
-        	loopi(sizeof(gunprefs)/sizeof(gunprefs[0])) if(d->hasammo(gunprefs[i]))
-        	{
-        		gun = gunprefs[i];
-        		break;
-			}
-        }
-        if(gun >= 0 && gun != d->gunselect) gunselect(gun, d);
-    }
-
-    void pickup(fpsent *d, extentity &e)
-    {
-        if(!d->hasammo(d->gunselect) || (d->gunselect != d->ai->weappref && (!isgoodammo(d->gunselect) || d->hasammo(d->ai->weappref))))
-            chooseweapon(d);
-    }
-
-    bool request(fpsent *d, aistate &b)
-    {
-        if(!d->hasammo(d->gunselect) || (d->gunselect != d->ai->weappref && (!isgoodammo(d->gunselect) || d->hasammo(d->ai->weappref))))
-            chooseweapon(d);
-        return process(d, b) >= 2;
-    }
-
-    void findorientation(vec &o, float yaw, float pitch, vec &pos)
-    {
-        vec dir;
-        vecfromyawpitch(yaw, pitch, 1, 0, dir);
-        if(raycubepos(o, dir, pos, 0, RAY_CLIPMAT|RAY_SKIPFIRST) == -1)
-            pos = dir.mul(2*getworldsize()).add(o); //otherwise 3dgui won't work when outside of map
-    }
-
-    void logic(fpsent *d, aistate &b, bool run)
-    {
-        vec dp = d->headpos();
-        findorientation(dp, d->yaw, d->pitch, d->ai->target);
-        bool allowmove = canmove(d) && b.type != AI_S_WAIT;
-        if(d->state != CS_ALIVE || !allowmove) d->stopmoving();
-        if(d->state == CS_ALIVE)
-        {
-            if(allowmove)
-            {
-                if(!request(d, b)) target(d, b, false, b.idle ? true : false);
-                shoot(d, d->ai->target);
-                if(d->ai->lasthunt)
-                {
-                    int millis = lastmillis-d->ai->lasthunt;
-                    if(millis < 5000) d->ai->tryreset = false;
-                    else if(millis < 10000)
-                    {
-                        if(!d->ai->tryreset) setup(d, true);
-                    }
-                    else
-                    {
-                        if(d->ai->tryreset)
-                        {
-                            suicide(d); // better off doing something than nothing
-                            d->ai->reset(false);
-                        }
-                    }
-                }
-
-                d->ai->addprevnode(d->lastnode);
-            }
-            if(!intermission)
-            {
-                if(d->ragdoll) cleanragdoll(d);
-                moveplayer(d, 10, true);
-                entities::checkitems(d);
-                if(cmode) cmode->checkitems(d);
-            }
-        }
-        else if(d->state == CS_DEAD)
-        {
-            if(d->ragdoll) moveragdoll(d);
-            else if(lastmillis-d->lastpain<2000)
-            {
-                d->move = d->strafe = 0;
-                moveplayer(d, 10, false);
-            }
-        }
-        d->attacking = d->jumping = false;
-    }
-
-    void avoid()
-    {
-        // guess as to the radius of ai and other critters relying on the avoid set for now
-        float guessradius = player1->radius;
-        obstacles.clear();
-        loopv(players)
-        {
-            dynent *d = players[i];
-            if(d->state != CS_ALIVE) continue;
-            obstacles.avoidnear(d, d->o.z + d->aboveeye + 1, d->feetpos(), guessradius + d->radius);
-        }
-        avoidweapons(obstacles, guessradius);
-    }
-
-    void think(fpsent *d, bool run)
-    {
-        // the state stack works like a chain of commands, certain commands simply replace each other
-        // others spawn new commands to the stack the ai reads the top command from the stack and executes
-        // it or pops the stack and goes back along the history until it finds a suitable command to execute
-        if(d->ai->state.empty()) setup(d);
-        bool cleannext = false;
-        loopvrev(d->ai->state)
-        {
-            aistate &c = d->ai->state[i];
-            if(cleannext)
-            {
-                c.millis = lastmillis;
-                c.override = false;
-                cleannext = false;
-            }
-            if(d->state == CS_DEAD && d->respawned!=d->lifesequence && (!cmode || cmode->respawnwait(d) <= 0) && lastmillis - d->lastpain >= 500)
-            {
-                addmsg(SV_TRYSPAWN, "rc", d);
-                d->respawned = d->lifesequence;
-            }
-            if(d->state == CS_ALIVE && run && lastmillis >= c.next)
-            {
-                int result = 0;
-                c.idle = c.type == AI_S_WAIT ? 1 : 0;
-                switch(c.type)
-                {
-                    case AI_S_WAIT: result = dowait(d, c); break;
-                    case AI_S_DEFEND: result = dodefend(d, c); break;
-                    case AI_S_PURSUE: result = dopursue(d, c); break;
-                    case AI_S_INTEREST: result = dointerest(d, c); break;
-                    default: result = 0; break;
-                }
-                if(result <= 0)
-                {
-                    d->ai->route.setsize(0);
-                    if(c.type != AI_S_WAIT)
-                    {
-                        d->ai->removestate(i);
-                        switch(result)
-                        {
-                            case 0: default: cleannext = true; break;
-                            case -1: i = d->ai->state.length()-1; break;
-                        }
-                    }
-                    else
-                    {
-                        c.next = lastmillis+1000;
-                        d->ai->dontmove = true;
-                    }
-                    continue; // shouldn't interfere
-                }
-            }
-            logic(d, c, run);
-            break;
-        }
-        if(d->ai->clear) d->ai->wipe();
-    }
-
-    void drawstate(fpsent *d, aistate &b, bool top, int above)
-    {
-        const char *bnames[AI_S_MAX] = {
-            "wait", "defend", "pursue", "interest"
-        }, *btypes[AI_T_MAX+1] = {
-            "none", "node", "player", "affinity", "entity"
-        };
-        string s;
-        if(top)
-        {
-            formatstring(s)("@\f0%s (%d[%d]) %s:%d (%d[%d])",
-                bnames[b.type],
-                lastmillis-b.millis, b.next-lastmillis,
-                btypes[clamp(b.targtype+1, 0, AI_T_MAX+1)], b.target,
-                !d->ai->route.empty() ? d->ai->route[0] : -1,
-                d->ai->route.length()
-            );
-        }
-        else
-        {
-            formatstring(s)("@\f2%s (%d[%d]) %s:%d",
-                bnames[b.type],
-                lastmillis-b.millis, b.next-lastmillis,
-                btypes[clamp(b.targtype+1, 0, AI_T_MAX+1)], b.target
-            );
-        }
-        particle_text(vec(d->abovehead()).add(vec(0, 0, above)), s, PART_TEXT, 1);
-        if(b.targtype == AI_T_ENTITY && entities::ents.inrange(b.target))
-        {
-            formatstring(s)("@GOAL: %s", colorname(d));
-            particle_text(entities::ents[b.target]->o, s, PART_TEXT, 1);
-        }
-    }
-
-    void drawroute(fpsent *d, aistate &b, float amt = 1.f)
-    {
-        int colour = 0xFFFFFF, last = -1;
-
-        loopvrev(d->ai->route)
-        {
-            if(d->ai->route.inrange(last))
-            {
-                int index = d->ai->route[i], prev = d->ai->route[last];
-                if(waypoints.inrange(index) && waypoints.inrange(prev))
-                {
-                    waypoint &e = waypoints[index],
-                        &f = waypoints[prev];
-                    vec fr(vec(f.o).add(vec(0, 0, 4.f*amt))),
-                        dr(vec(e.o).add(vec(0, 0, 4.f*amt)));
-                    particle_flare(fr, dr, 1, PART_STREAK, colour);
-                }
-            }
-            last = i;
-        }
-        if(aidebug > 4)
-        {
-            vec pos = d->feetpos();
-            if(d->ai->spot != vec(0, 0, 0)) particle_flare(pos, d->ai->spot, 1, PART_LIGHTNING, 0xFFFFFF);
-            if(waypoints.inrange(d->lastnode))
-                particle_flare(pos, waypoints[d->lastnode].o, 1, PART_LIGHTNING, 0x00FFFF);
-            if(waypoints.inrange(d->ai->prevnodes[1]))
-                particle_flare(pos, waypoints[d->ai->prevnodes[1]].o, 1, PART_LIGHTNING, 0xFF00FF);
-        }
-    }
-
-    VAR(showwaypoints, 0, 0, 1);
-    VAR(showwaypointsradius, 0, 200, 10000);
-
-    void render()
-    {
-        if(aidebug > 1)
-        {
-            int total = 0, alive = 0;
-            loopv(players) if(players[i]->ai) total++;
-            loopv(players) if(players[i]->state == CS_ALIVE && players[i]->ai)
-            {
-                fpsent *d = players[i];
-                bool top = true;
-                int above = 0;
-                alive++;
-                loopvrev(d->ai->state)
-                {
-                    aistate &b = d->ai->state[i];
-                    drawstate(d, b, top, above += 2);
-                    if(aidebug > 3 && top && b.type != AI_S_WAIT)
-                        drawroute(d, b, float(alive)/float(total));
-                    if(top)
-                    {
-                        if(aidebug > 2) top = false;
-                        else break;
-                    }
-                }
-            }
-        }
-        if(showwaypoints || aidebug > 5)
-        {
-            vector<int> close;
-            int len = waypoints.length();
-            if(showwaypointsradius)
-            {
-                findwaypointswithin(camera1->o, 0, showwaypointsradius, close);
-                len = close.length();
-            }
-            loopi(len)
-            {
-                waypoint &w = waypoints[showwaypointsradius ? close[i] : i];
-                loopj(MAXWAYPOINTLINKS)
-                {
-                     int link = w.links[j];
-                     if(!link) break;
-                     particle_flare(w.o, waypoints[link].o, 1, PART_STREAK, 0x0000FF);
-                }
-            }
-
-        }
-    }
-}
-
diff --git a/fpsgame/ai.h b/fpsgame/ai.h
deleted file mode 100644
index 9fde59f..0000000
--- a/fpsgame/ai.h
+++ /dev/null
@@ -1,282 +0,0 @@
-struct fpsent;
-
-#define MAXBOTS 32
-
-enum { AI_NONE = 0, AI_BOT, AI_MAX };
-#define isaitype(a) (a >= 0 && a <= AI_MAX-1)
-
-namespace ai
-{
-    const int MAXWAYPOINTS = USHRT_MAX - 2;
-    const int MAXWAYPOINTLINKS = 6;
-    const int WAYPOINTRADIUS = 16;
-
-    const float CLOSEDIST       = WAYPOINTRADIUS;                   // is close
-    const float NEARDIST        = CLOSEDIST*4.f;                    // is near
-    const float NEARDISTSQ      = NEARDIST*NEARDIST;                // .. squared (constant for speed)
-    const float FARDIST         = CLOSEDIST*16.f;                   // too far
-    const float JUMPMIN         = CLOSEDIST*0.25f;                  // decides to jump
-    const float JUMPMAX         = CLOSEDIST*1.5f;                   // max jump
-    const float SIGHTMIN        = CLOSEDIST*2.f;                    // minimum line of sight
-    const float SIGHTMAX        = CLOSEDIST*256.f;                  // maximum line of sight
-    const float VIEWMIN         = 70.f;                             // minimum field of view
-    const float VIEWMAX         = 150.f;                            // maximum field of view
-
-    struct waypoint
-    {
-        vec o;
-        short curscore, estscore;
-        ushort route, prev;
-        ushort links[MAXWAYPOINTLINKS];
-
-        waypoint() {}
-        waypoint(const vec &o) : o(o), route(0) { memset(links, 0, sizeof(links)); }
-
-        int score() const { return int(curscore) + int(estscore); }
-
-        int find(int wp)
-		{
-			loopi(MAXWAYPOINTLINKS) if(links[i] == wp) return i;
-			return -1;
-		}
-    };
-    extern vector<waypoint> waypoints;
-
-    extern int closestwaypoint(const vec &pos, float mindist, bool links);
-    extern void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector<int> &results);
-	extern void inferwaypoints(fpsent *d, const vec &o, const vec &v, float mindist = ai::CLOSEDIST);
-
-    struct avoidset
-    {
-        struct obstacle
-        {
-            void *owner;
-            int numwaypoints;
-            float above;
-
-            obstacle(void *owner, float above = -1) : owner(owner), numwaypoints(0), above(above) {}
-        };
-
-        vector<obstacle> obstacles;
-        vector<int> waypoints;
-
-        void clear()
-        {
-            obstacles.setsizenodelete(0);
-            waypoints.setsizenodelete(0);
-        }
-
-        void add(void *owner, float above)
-        {
-            obstacles.add(obstacle(owner, above));
-        }
-
-        void add(void *owner, float above, int wp)
-        {
-            if(obstacles.empty() || owner != &obstacles.last().owner) add(owner, above);
-            obstacles.last().numwaypoints++;
-            waypoints.add(wp);
-        }
-
-        void avoidnear(void *owner, float above, const vec &pos, float limit);
-
-        #define loopavoid(v, d, body) \
-            if(!(v).obstacles.empty()) \
-            { \
-                int cur = 0; \
-                loopv((v).obstacles) \
-                { \
-                    const ai::avoidset::obstacle &ob = (v).obstacles[i]; \
-                    int next = cur + ob.numwaypoints; \
-                    if(ob.owner != d) \
-                    { \
-                        for(; cur < next; cur++) \
-                        { \
-                            int wp = (v).waypoints[cur]; \
-                            body; \
-                        } \
-                    } \
-                    cur = next; \
-                } \
-            }
-
-        bool find(int n, fpsent *d) const
-        {
-            loopavoid(*this, d, { if(wp == n) return true; });
-            return false;
-        }
-
-        int remap(fpsent *d, int n, vec &pos);
-    };
-
-    extern bool route(fpsent *d, int node, int goal, vector<int> &route, const avoidset &obstacles, bool check = true);
-    extern void trydropwaypoint(fpsent *d);
-    extern void trydropwaypoints();
-    extern void clearwaypoints(bool full = false);
-    extern void seedwaypoints();
-    extern void loadwaypoints(bool force = false, const char *mname = NULL);
-    extern void savewaypoints(bool force = false, const char *mname = NULL);
-
-    // ai state information for the owner client
-    enum
-    {
-        AI_S_WAIT = 0,      // waiting for next command
-        AI_S_DEFEND,        // defend goal target
-        AI_S_PURSUE,        // pursue goal target
-        AI_S_INTEREST,      // interest in goal entity
-        AI_S_MAX
-    };
-
-    enum
-    {
-        AI_T_NODE,
-        AI_T_PLAYER,
-        AI_T_AFFINITY,
-        AI_T_ENTITY,
-        AI_T_MAX
-    };
-
-    struct interest
-    {
-        int state, node, target, targtype;
-        float score;
-        interest() : state(-1), node(-1), target(-1), targtype(-1), score(0.f) {}
-        ~interest() {}
-    };
-
-    struct aistate
-    {
-        int type, millis, next, targtype, target, idle;
-        bool override;
-
-        aistate(int m, int t, int r = -1, int v = -1) : type(t), millis(m), targtype(r), target(v)
-        {
-            reset();
-        }
-        ~aistate() {}
-
-        void reset()
-        {
-            next = millis;
-            idle = 0;
-            override = false;
-        }
-    };
-
-    const int NUMPREVNODES = 3;
-
-    struct aiinfo
-    {
-        vector<aistate> state;
-        vector<int> route;
-        vec target, spot;
-        int enemy, enemyseen, enemymillis, weappref, prevnodes[NUMPREVNODES],
-            lastrun, lasthunt, lastaction, jumpseed, jumprand;
-        float targyaw, targpitch, views[3];
-        bool dontmove, tryreset, clear;
-
-        aiinfo()
-        {
-            reset();
-            loopk(3) views[k] = 0.f;
-        }
-        ~aiinfo() {}
-
-        void wipe()
-        {
-            state.setsize(0);
-            route.setsize(0);
-            addstate(AI_S_WAIT);
-            clear = false;
-        }
-
-        void reset(bool tryit = false)
-        {
-            wipe();
-            if(!tryit)
-            {
-            	weappref = GUN_PISTOL;
-                spot = target = vec(0, 0, 0);
-                enemy = -1;
-                lastaction = lasthunt = enemyseen = enemymillis = 0;
-                lastrun = jumpseed = lastmillis;
-                jumprand = lastmillis+5000;
-                dontmove = false;
-            }
-            memset(prevnodes, -1, sizeof(prevnodes));
-            targyaw = rnd(360);
-            targpitch = 0.f;
-            tryreset = tryit;
-        }
-
-        bool hasprevnode(int n) const
-        {
-            loopi(NUMPREVNODES) if(prevnodes[i] == n) return true;
-            return false;
-        }
-        void addprevnode(int n)
-        {
-            if(prevnodes[0] != n)
-            {
-                memmove(&prevnodes[1], prevnodes, sizeof(prevnodes) - sizeof(prevnodes[0]));
-                prevnodes[0] = n;
-            }
-        }
-
-        aistate &addstate(int t, int r = -1, int v = -1)
-        {
-            return state.add(aistate(lastmillis, t, r, v));
-        }
-
-        void removestate(int index = -1)
-        {
-            if(index < 0) state.pop();
-            else if(state.inrange(index)) state.remove(index);
-            if(!state.length()) addstate(AI_S_WAIT);
-        }
-
-        aistate &setstate(int t, int r = 1, int v = -1, bool pop = true)
-        {
-            if(pop) removestate();
-            return addstate(t, r, v);
-        }
-
-        aistate &getstate(int idx = -1)
-        {
-            if(state.inrange(idx)) return state[idx];
-            return state.last();
-        }
-    };
-
-    extern vec aitarget;
-
-    extern float viewdist(int x = 101);
-    extern float viewfieldx(int x = 101);
-    extern float viewfieldy(int x = 101);
-    extern bool targetable(fpsent *d, fpsent *e, bool anyone = true);
-    extern bool cansee(fpsent *d, vec &x, vec &y, vec &targ = aitarget);
-
-    extern void init(fpsent *d, int at, int on, int sk, int bn, int pm, const char *name, const char *team);
-    extern void update();
-    extern void avoid();
-    extern void think(fpsent *d, bool run);
-
-    extern bool badhealth(fpsent *d);
-    extern bool checkothers(vector<int> &targets, fpsent *d = NULL, int state = -1, int targtype = -1, int target = -1, bool teams = false);
-    extern bool makeroute(fpsent *d, aistate &b, int node, bool changed = true, bool check = true);
-    extern bool makeroute(fpsent *d, aistate &b, const vec &pos, bool changed = true, bool check = true);
-    extern bool randomnode(fpsent *d, aistate &b, const vec &pos, float guard = NEARDIST, float wander = FARDIST);
-    extern bool randomnode(fpsent *d, aistate &b, float guard = NEARDIST, float wander = FARDIST);
-    extern bool violence(fpsent *d, aistate &b, fpsent *e, bool pursue = false);
-    extern bool patrol(fpsent *d, aistate &b, const vec &pos, float guard = NEARDIST, float wander = FARDIST, int walk = 1, bool retry = false);
-    extern bool defend(fpsent *d, aistate &b, const vec &pos, float guard = NEARDIST, float wander = FARDIST, int walk = 1);
-
-	extern void spawned(fpsent *d);
-	extern void damaged(fpsent *d, fpsent *e);
-	extern void killed(fpsent *d, fpsent *e);
-    extern void pickup(fpsent *d, extentity &e);
-
-    extern void render();
-}
-
-
diff --git a/fpsgame/aiman.h b/fpsgame/aiman.h
deleted file mode 100644
index a54fe9c..0000000
--- a/fpsgame/aiman.h
+++ /dev/null
@@ -1,279 +0,0 @@
-// server-side ai manager
-namespace aiman
-{
-    bool dorefresh = false;
-    VARN(serverbotlimit, botlimit, 0, 8, MAXBOTS);
-    VARN(serverbotbalance, botbalance, 0, 1, 1);
-
-    void calcteams(vector<teamscore> &teams)
-    {
-        const char *defaults[2] = { "good", "evil" };
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
-            teamscore *t = NULL;
-            loopvj(teams) if(!strcmp(teams[j].team, ci->team)) { t = &teams[j]; break; }
-            if(t) t->score++;
-            else teams.add(teamscore(ci->team, 1));
-        }
-        teams.sort(teamscore::compare);
-        if(teams.length() < int(sizeof(defaults)/sizeof(defaults[0])))
-        {
-            loopi(sizeof(defaults)/sizeof(defaults[0]))
-            {
-                loopvj(teams) if(!strcmp(teams[j].team, defaults[i])) goto nextteam;
-                teams.add(teamscore(defaults[i], 0));
-            nextteam:;
-            }
-        }
-    }
-
-    void balanceteams()
-    {
-        vector<teamscore> teams;
-        calcteams(teams);
-        vector<clientinfo *> reassign;
-        loopv(bots) if(bots[i]) reassign.add(bots[i]);
-        while(reassign.length() && teams.length() && teams[0].score > teams.last().score + 1)
-        {
-            teamscore &t = teams.last();
-            clientinfo *bot = NULL;
-            loopv(reassign) if(reassign[i] && !strcmp(reassign[i]->team, teams[0].team))
-            {
-                bot = reassign.removeunordered(i);
-                teams[0].score--;
-                t.score++;
-                for(int j = teams.length() - 2; j >= 0; j--)
-                {
-                    if(teams[j].score >= teams[j+1].score) break;
-                    swap(teams[j], teams[j+1]);
-                }
-                break;
-            }
-            if(bot)
-            {
-                if(smode && bot->state.state==CS_ALIVE) smode->changeteam(bot, bot->team, t.team);
-                copystring(bot->team, t.team, MAXTEAMLEN+1);
-                sendf(-1, 1, "riis", SV_SETTEAM, bot->clientnum, bot->team);
-            }
-            else teams.remove(0, 1);
-        }
-    }
-
-    const char *chooseteam()
-    {
-        vector<teamscore> teams;
-        calcteams(teams);
-        return teams.length() ? teams.last().team : "";
-    }
-
-    static inline bool validaiclient(clientinfo *ci)
-    {
-        return ci->clientnum >= 0 && ci->state.aitype == AI_NONE && (ci->state.state!=CS_SPECTATOR || ci->local || ci->privilege);
-    }
-
-	clientinfo *findaiclient(clientinfo *exclude = NULL)
-	{
-        clientinfo *least = NULL;
-		loopv(clients)
-		{
-			clientinfo *ci = clients[i];
-			if(!validaiclient(ci) || ci==exclude) continue;
-            if(!least || ci->bots.length() < least->bots.length()) least = ci;
-		}
-        return least;
-	}
-
-	bool addai(int skill, int limit)
-	{
-		int numai = 0, cn = -1, maxai = limit >= 0 ? min(limit, MAXBOTS) : MAXBOTS;
-		loopv(bots)
-        {
-            clientinfo *ci = bots[i];
-            if(!ci || ci->ownernum < 0) { if(cn < 0) cn = i; continue; }
-			numai++;
-		}
-		if(numai >= maxai) return false;
-        if(bots.inrange(cn))
-        {
-            clientinfo *ci = bots[cn];
-            if(ci)
-            { // reuse a slot that was going to removed
-
-                clientinfo *owner = findaiclient();
-                ci->ownernum = owner ? owner->clientnum : -1;
-                ci->aireinit = 2;
-                dorefresh = true;
-                return true;
-            }
-        }
-        else { cn = bots.length(); bots.add(NULL); }
-        const char *team = m_teammode ? chooseteam() : "";
-        if(!bots[cn]) bots[cn] = new clientinfo;
-        clientinfo *ci = bots[cn];
-		ci->clientnum = MAXCLIENTS + cn;
-		ci->state.aitype = AI_BOT;
-        clientinfo *owner = findaiclient();
-		ci->ownernum = owner ? owner->clientnum : -1;
-        if(owner) owner->bots.add(ci);
-        ci->state.skill = skill <= 0 ? rnd(50) + 51 : clamp(skill, 1, 101);
-	    clients.add(ci);
-		ci->state.lasttimeplayed = lastmillis;
-		copystring(ci->name, "bot", MAXNAMELEN+1);
-		ci->state.state = CS_DEAD;
-        copystring(ci->team, team, MAXTEAMLEN+1);
-        ci->playermodel = rnd(128);
-		ci->aireinit = 2;
-		ci->connected = true;
-        dorefresh = true;
-		return true;
-	}
-
-	void deleteai(clientinfo *ci)
-	{
-        int cn = ci->clientnum - MAXCLIENTS;
-        if(!bots.inrange(cn)) return;
-        if(smode) smode->leavegame(ci, true);
-        sendf(-1, 1, "ri2", SV_CDIS, ci->clientnum);
-        clientinfo *owner = (clientinfo *)getclientinfo(ci->ownernum);
-        if(owner) owner->bots.removeobj(ci);
-        clients.removeobj(ci);
-        DELETEP(bots[cn]);
-		dorefresh = true;
-	}
-
-	bool deleteai()
-	{
-        loopvrev(bots) if(bots[i] && bots[i]->ownernum >= 0)
-        {
-			deleteai(bots[i]);
-			return true;
-		}
-		return false;
-	}
-
-	void reinitai(clientinfo *ci)
-	{
-		if(ci->ownernum < 0) deleteai(ci);
-		else if(ci->aireinit >= 1)
-		{
-			sendf(-1, 1, "ri6ss", SV_INITAI, ci->clientnum, ci->ownernum, ci->state.aitype, ci->state.skill, ci->playermodel, ci->name, ci->team);
-			if(ci->aireinit == 2)
-            {
-                ci->reassign();
-                if(ci->state.state==CS_ALIVE) sendspawn(ci);
-                else sendresume(ci);
-            }
-			ci->aireinit = 0;
-		}
-	}
-
-	void shiftai(clientinfo *ci, clientinfo *owner)
-	{
-        clientinfo *prevowner = (clientinfo *)getclientinfo(ci->ownernum);
-        if(prevowner) prevowner->bots.removeobj(ci);
-		if(!owner) { ci->aireinit = 0; ci->ownernum = -1; }
-		else { ci->aireinit = 2; ci->ownernum = owner->clientnum; owner->bots.add(ci); }
-        dorefresh = true;
-	}
-
-	void removeai(clientinfo *ci)
-	{ // either schedules a removal, or someone else to assign to
-
-		loopvrev(ci->bots) shiftai(ci->bots[i], findaiclient(ci));
-	}
-
-	bool reassignai()
-	{
-        clientinfo *hi = NULL, *lo = NULL;
-		loopv(clients)
-		{
-			clientinfo *ci = clients[i];
-			if(!validaiclient(ci)) continue;
-            if(!lo || ci->bots.length() < lo->bots.length()) lo = ci;
-            if(!hi || ci->bots.length() > hi->bots.length()) hi = ci;
-		}
-		if(hi && lo && hi->bots.length() - lo->bots.length() > 1)
-		{
-			loopvrev(hi->bots)
-			{
-				shiftai(hi->bots[i], lo);
-				return true;
-			}
-		}
-		return false;
-	}
-
-
-	void checksetup()
-	{
-	    if(m_teammode && botbalance) balanceteams();
-		loopvrev(bots) if(bots[i]) reinitai(bots[i]);
-	}
-
-	void clearai()
-	{ // clear and remove all ai immediately
-        loopvrev(bots) if(bots[i]) deleteai(bots[i]);
-	}
-
-	void checkai()
-	{
-        if(!dorefresh) return;
-        dorefresh = false;
-        if(m_botmode && numclients(-1, false, true))
-		{
-			checksetup();
-			while(reassignai());
-		}
-		else clearai();
-	}
-
-	void reqadd(clientinfo *ci, int skill)
-	{
-        if(!ci->local && !ci->privilege) return;
-        if(!addai(skill, !ci->local && ci->privilege < PRIV_ADMIN ? botlimit : -1)) sendf(ci->clientnum, 1, "ris", SV_SERVMSG, "failed to create or assign bot");
-	}
-
-	void reqdel(clientinfo *ci)
-	{
-        if(!ci->local && !ci->privilege) return;
-        if(!deleteai()) sendf(ci->clientnum, 1, "ris", SV_SERVMSG, "failed to remove any bots");
-	}
-
-    void setbotlimit(clientinfo *ci, int limit)
-    {
-        if(ci && !ci->local && ci->privilege < PRIV_ADMIN) return;
-        botlimit = clamp(limit, 0, MAXBOTS);
-        dorefresh = true;
-        defformatstring(msg)("bot limit is now %d", botlimit);
-        sendservmsg(msg);
-    }
-
-    void setbotbalance(clientinfo *ci, bool balance)
-    {
-        if(ci && !ci->local && !ci->privilege) return;
-        botbalance = balance ? 1 : 0;
-        dorefresh = true;
-        defformatstring(msg)("bot team balancing is now %s", botbalance ? "enabled" : "disabled");
-        sendservmsg(msg);
-    }
-
-
-    void changemap()
-    {
-        dorefresh = true;
-        loopv(clients) if(clients[i]->local || clients[i]->privilege) return;
-        if(!botbalance) setbotbalance(NULL, true);
-    }
-
-    void addclient(clientinfo *ci)
-    {
-        if(ci->state.aitype == AI_NONE) dorefresh = true;
-    }
-
-    void changeteam(clientinfo *ci)
-    {
-        if(ci->state.aitype == AI_NONE) dorefresh = true;
-    }
-}
diff --git a/fpsgame/assassin.h b/fpsgame/assassin.h
new file mode 100644
index 0000000..36415cf
--- /dev/null
+++ b/fpsgame/assassin.h
@@ -0,0 +1,218 @@
+#ifdef ASSASSINSERV
+struct assassinservmode : servmode
+{
+    vector<clientinfo *> targets;
+
+    assassinservmode(fpsserver &sv) : servmode(sv) {}
+
+    void findvalidtargets()
+    {
+        targets.setsizenodelete(0);
+        loopv(sv.clients)
+        {
+            clientinfo *ci = sv.clients[i];
+            if(ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD) continue;
+            targets.add(ci);
+        }
+    }
+
+    clientinfo *choosetarget(clientinfo *exclude1, clientinfo *exclude2 = NULL)
+    {
+        if(targets.length() <= (exclude1 ? 1 : 0)) return NULL;
+        if(exclude2 && targets.length() < 3) exclude2 = NULL; 
+        clientinfo *target = NULL;
+        do target = targets[rnd(targets.length())];
+        while(target==exclude1 || target==exclude2);
+        return target;
+    }
+
+    void sendnewtarget(clientinfo *hunter, clientinfo *exclude = NULL)
+    {
+        clientinfo *target = choosetarget(hunter, exclude);
+        if(!target) return;
+        hunter->targets.add(target);
+        sendf(hunter->clientnum, 1, "ri2", SV_ADDTARGET, target->clientnum);
+        sendf(target->clientnum, 1, "ri2", SV_ADDHUNTER, hunter->clientnum);
+    }
+
+    void leavegame(clientinfo *ci, bool disconnecting = false)
+    {
+        loopv(ci->targets)
+        {
+            clientinfo *target = ci->targets[i];
+            sendf(target->clientnum, 1, "ri2", SV_REMOVEHUNTER, ci->clientnum);
+        }
+        ci->targets.setsizenodelete(0);
+        findvalidtargets();
+        targets.removeobj(ci);
+        loopv(sv.clients)
+        {
+            clientinfo *hunter = sv.clients[i];
+            if(hunter->state.state!=CS_ALIVE && hunter->state.state!=CS_DEAD) continue;
+            if(hunter->targets.find(ci)<0) continue;
+            hunter->targets.removeobj(ci);
+            sendf(hunter->clientnum, 1, "ri2", SV_REMOVETARGET, ci->clientnum);
+            if(hunter->targets.empty()) sendnewtarget(hunter);
+        }
+        if(!disconnecting) sendf(ci->clientnum, 1, "ri2", SV_CLEARTARGETS, SV_CLEARHUNTERS);
+        
+    }
+
+    void checkneedstarget(clientinfo *ci, clientinfo *exclude = NULL)
+    {
+        if(ci->targets.empty())
+        {
+            findvalidtargets();
+            sendnewtarget(ci, exclude);
+        }
+    }
+
+    void sendnewtargets()
+    {
+        findvalidtargets();
+        loopv(targets)
+        {
+            clientinfo *hunter = targets[i];
+            if(hunter->targets.empty()) sendnewtarget(hunter);
+        }
+    }
+
+    void entergame(clientinfo *ci)
+    {
+        sendnewtargets();
+    }
+
+    void spawned(clientinfo *ci)
+    {
+        sendnewtargets();
+    }
+
+    int fragvalue(clientinfo *victim, clientinfo *actor)
+    {
+        if(victim==actor) return -1;
+        if(actor->targets.find(victim)>=0) return 1;
+        if(victim->targets.find(actor)>=0) return 0;
+        return -1;
+    }
+
+    void died(clientinfo *victim, clientinfo *actor)
+    {
+        if(!actor || actor==victim) return;
+        if(actor->targets.find(victim)>=0)
+        {
+            actor->targets.removeobj(victim);
+            checkneedstarget(actor, victim);
+        }
+        else if(victim->targets.find(actor)<0)
+        {
+            victim->targets.add(actor);
+            sendf(victim->clientnum, 1, "ri2", SV_ADDTARGET, actor->clientnum);
+            sendf(actor->clientnum, 1, "ri2", SV_ADDHUNTER, victim->clientnum);
+        } 
+    }
+};
+
+#else
+struct assassinclient
+{
+    static const int RESPAWNSECS = 10;
+
+    fpsclient &cl;
+    vector<fpsent *> targets, hunters;
+    float radarscale;
+
+    assassinclient(fpsclient &cl) : cl(cl), radarscale(0) {}
+
+    void removeplayer(fpsent *d)
+    {
+        targets.removeobj(d);
+        hunters.removeobj(d);
+    }
+
+    void reset()
+    {
+        targets.setsize(0);
+        hunters.setsize(0);
+
+        vec center(0, 0, 0);
+        int numents = 0;
+        loopv(cl.et.ents)
+        {
+            extentity *e = cl.et.ents[i];
+            if(e->type<ET_GAMESPECIFIC && e->type!=ET_PLAYERSTART) continue;
+            center.add(e->o);
+            numents++;
+        }
+        if(numents) center.div(numents);
+        radarscale = 0;
+        loopv(cl.et.ents)
+        {
+            extentity *e = cl.et.ents[i];
+            if(e->type<ET_GAMESPECIFIC && e->type!=ET_PLAYERSTART) continue;
+            radarscale = max(radarscale, 2*center.dist(e->o));
+        }
+    }
+
+    void drawradar(float x, float y, float s)
+    {
+        glTexCoord2f(0.0f, 0.0f); glVertex2f(x,   y);
+        glTexCoord2f(1.0f, 0.0f); glVertex2f(x+s, y);
+        glTexCoord2f(1.0f, 1.0f); glVertex2f(x+s, y+s);
+        glTexCoord2f(0.0f, 1.0f); glVertex2f(x,   y+s);
+    }
+
+    void drawblips(vector<fpsent *> &blips, float x, float y, float s, float scale)
+    {
+        glBegin(GL_QUADS);
+        loopv(blips)
+        {
+            fpsent *d = blips[i];
+            vec dir(d->o);
+            dir.sub(cl.player1->o);
+            dir.z = 0.0f;
+            float dist = dir.magnitude();
+            if(dist >= scale) dir.mul(scale/dist);
+            dir.rotate_around_z(-cl.player1->yaw*RAD);
+            drawradar(x + s*0.5f*0.95f*(1.0f+dir.x/scale), y + s*0.5f*0.95f*(1.0f+dir.y/scale), 0.05f*s);
+        }
+        glEnd();
+    }
+
+    int respawnwait()
+    {
+        return max(0, RESPAWNSECS - (cl.lastmillis - cl.player1->lastpain)/1000);
+    }
+
+    void drawhud(int w, int h)
+    {
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        float x = 1800*w/h*34/40, y = 1800*1/40, s = 1800*w/h*5/40;
+        glColor3f(1, 1, 1);
+        settexture("packages/hud/radar.png");
+        glBegin(GL_QUADS);
+        drawradar(x, y, s);
+        glEnd();
+        float scale = radarscale<=0 || radarscale>cl.maxradarscale() ? cl.maxradarscale() : radarscale;
+        if(hunters.length())
+        {
+            settexture("packages/hud/blip_grey.png");
+            drawblips(hunters, x, y, s, scale);
+        }
+        if(targets.length())
+        {
+            settexture("packages/hud/blip_red.png");
+            drawblips(targets, x, y, s, scale);
+        }
+        if(cl.player1->state == CS_DEAD)
+        {
+            glPushMatrix();
+            glLoadIdentity();
+            glOrtho(0, w*900/h, 900, 0, -1, 1);
+            int wait = respawnwait();
+            draw_textf("%d", int((x+s/2)/2-(wait>=10 ? 28 : 16)), int((y+s/2)/2-32), wait);
+            glPopMatrix();
+        }
+    }
+};
+#endif
+
diff --git a/fpsgame/capture.h b/fpsgame/capture.h
index 9cfa49f..6477d82 100644
--- a/fpsgame/capture.h
+++ b/fpsgame/capture.h
@@ -1,18 +1,12 @@
 // capture.h: client and server state for capture gamemode
-#ifndef PARSEMESSAGES
 
-#ifdef SERVMODE
-struct captureservmode : servmode
-#else
-struct captureclientmode : clientmode
-#endif
+struct capturestate
 {
     static const int CAPTURERADIUS = 64;
     static const int CAPTUREHEIGHT = 24;
-    static const int OCCUPYBONUS = 1;
-    static const int OCCUPYPOINTS = 1;
-    static const int OCCUPYENEMYLIMIT = 28;
-    static const int OCCUPYNEUTRALLIMIT = 14;
+    static const int OCCUPYPOINTS = 15;
+    static const int OCCUPYLIMIT = 100;
+    static const int CAPTURESCORE = 1;
     static const int SCORESECS = 10;
     static const int AMMOSECS = 15;
     static const int REGENSECS = 1;
@@ -21,16 +15,16 @@ struct captureclientmode : clientmode
     static const int REGENAMMO = 20;
     static const int MAXAMMO = 5;
     static const int REPAMMODIST = 32;
-    static const int RESPAWNSECS = 5;
+    static const int RESPAWNSECS = 10;        
 
     struct baseinfo
     {
         vec o;
         string owner, enemy;
-#ifndef SERVMODE
+#ifndef CAPTURESERV
         vec ammopos;
         string name, info;
-        entitylight light;
+        extentity *ent;
 #endif
         int ammogroup, ammotype, ammo, owners, enemies, converted, capturetime;
 
@@ -66,7 +60,7 @@ struct captureclientmode : clientmode
                 if(strcmp(enemy, team))
                 {
                     converted = 0;
-                    copystring(enemy, team);
+                    s_strcpy(enemy, team);
                 }
                 enemies++;
                 return true;
@@ -80,7 +74,7 @@ struct captureclientmode : clientmode
         {
             return !enemies && strcmp(owner, team);
         }
-
+            
         bool leave(const char *team)
         {
             if(!strcmp(owner, team))
@@ -102,9 +96,9 @@ struct captureclientmode : clientmode
                 if(converted<=0) noenemy();
                 return -1;
             }
-            else if(converted<(owner[0] ? int(OCCUPYENEMYLIMIT) : int(OCCUPYNEUTRALLIMIT))) return -1;
-            if(owner[0]) { owner[0] = '\0'; converted = 0; copystring(enemy, team); return 0; }
-            else { copystring(owner, team); ammo = 0; capturetime = 0; owners = enemies; noenemy(); return 1; }
+            else if(converted<(owner[0] ? 2 : 1)*OCCUPYLIMIT) return -1;
+            if(owner[0]) { owner[0] = '\0'; converted = 0; s_strcpy(enemy, team); return 0; }
+            else { s_strcpy(owner, team); ammo = 0; capturetime = 0; owners = enemies; noenemy(); return 1; }
         }
 
         bool addammo(int i)
@@ -129,35 +123,20 @@ struct captureclientmode : clientmode
         string team;
         int total;
     };
-
+    
     vector<score> scores;
 
     int captures;
 
-    void resetbases()
+    capturestate() : captures(0) {}
+
+    void reset()
     {
         bases.setsize(0);
         scores.setsize(0);
         captures = 0;
     }
 
-    bool hidefrags() { return true; }
-
-    int getteamscore(const char *team)
-    {
-        loopv(scores)
-        {
-            score &cs = scores[i];
-            if(!strcmp(cs.team, team)) return cs.total;
-        }
-        return 0;
-    }
-
-    void getteamscores(vector<teamscore> &teamscores)
-    {
-        loopv(scores) teamscores.add(teamscore(scores[i].team, scores[i].total));
-    }
-
     score &findscore(const char *team)
     {
         loopv(scores)
@@ -166,7 +145,7 @@ struct captureclientmode : clientmode
             if(!strcmp(cs.team, team)) return cs;
         }
         score &cs = scores.add();
-        copystring(cs.team, team);
+        s_strcpy(cs.team, team);
         cs.total = 0;
         return cs;
     }
@@ -178,25 +157,9 @@ struct captureclientmode : clientmode
         b.ammotype = ammotype > 0 ? ammotype : rnd(5)+1;
         b.o = o;
 
-        if(b.ammogroup)
+        if(b.ammogroup) 
         {
-            loopi(bases.length()-1) if(b.ammogroup == bases[i].ammogroup)
-            {
-                b.ammotype = bases[i].ammotype;
-                return;
-            }
-            int uses[5] = { 0, 0, 0, 0, 0 };
-            loopi(bases.length()-1) if(bases[i].ammogroup)
-            {
-                loopj(i) if(bases[j].ammogroup == bases[i].ammogroup) goto nextbase;
-                uses[bases[i].ammotype-1]++;
-                nextbase:;
-            }
-            int mintype = 0;
-            loopi(5) if(uses[i] < uses[mintype]) mintype = i;
-            int numavail = 0, avail[5];
-            loopi(5) if(uses[i] == uses[mintype]) avail[numavail++] = i+1;
-            b.ammotype = avail[rnd(numavail)];
+            loopv(bases) if(b.ammogroup==bases[i].ammogroup) b.ammotype = bases[i].ammotype;
         }
     }
 
@@ -205,8 +168,8 @@ struct captureclientmode : clientmode
         if(!bases.inrange(i)) return;
         baseinfo &b = bases[i];
         b.ammotype = ammotype;
-        copystring(b.owner, owner);
-        copystring(b.enemy, enemy);
+        s_strcpy(b.owner, owner);
+        s_strcpy(b.enemy, enemy);
         b.converted = converted;
         b.ammo = ammo;
     }
@@ -215,7 +178,7 @@ struct captureclientmode : clientmode
     {
         loopv(bases)
         {
-            baseinfo &b = bases[i];
+            baseinfo &b = bases[i]; 
             if(b.owner[0] && !strcmp(b.owner, team)) return true;
         }
         return false;
@@ -235,160 +198,176 @@ struct captureclientmode : clientmode
 
     bool insidebase(const baseinfo &b, const vec &o)
     {
-        float dx = (b.o.x-o.x), dy = (b.o.y-o.y), dz = (b.o.z-o.z);
-        return dx*dx + dy*dy <= CAPTURERADIUS*CAPTURERADIUS && fabs(dz) <= CAPTUREHEIGHT;
+        float dx = (b.o.x-o.x), dy = (b.o.y-o.y), dz = (b.o.z-o.z+14);
+        return dx*dx + dy*dy <= CAPTURERADIUS*CAPTURERADIUS && fabs(dz) <= CAPTUREHEIGHT; 
     }
+};
 
-#ifndef SERVMODE
+#ifndef CAPTURESERV
+
+struct captureclient : capturestate
+{
     static const int FIREBALLRADIUS = 5;
 
+    fpsclient &cl;
     float radarscale;
+    int lastrepammo;
 
     IVARP(capturetether, 0, 1, 1);
     IVARP(autorepammo, 0, 1, 1);
 
-    captureclientmode() : captures(0), radarscale(0)
-    {
-        CCOMMAND(repammo, "", (captureclientmode *self), self->replenishammo());
-    }
-
-    void respawned(fpsent *d)
+    captureclient(fpsclient &cl) : cl(cl), radarscale(0), lastrepammo(-1)
     {
+        CCOMMAND(repammo, "", (captureclient *self), self->replenishammo());
     }
-
+    
     void replenishammo()
     {
-        if(!m_capture || m_regencapture) return;
+        int gamemode = cl.gamemode;
+        if(m_noitems) return;
         loopv(bases)
         {
             baseinfo &b = bases[i];
-            if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1 && insidebase(b, player1->feetpos()) && player1->hasmaxammo(b.ammotype-1+I_SHELLS)) return;
+            if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1 && insidebase(b, cl.player1->o) && cl.player1->hasmaxammo(b.ammotype-1+I_SHELLS)) return;
         }
-        addmsg(SV_REPAMMO, "rc", player1);
+        cl.cc.addmsg(SV_REPAMMO, "r");
     }
 
-    void receiveammo(fpsent *d, int type)
+    void receiveammo(int type)
     {
         type += I_SHELLS-1;
         if(type<I_SHELLS || type>I_CARTRIDGES) return;
-        entities::repammo(d, type, d==player1);
+        cl.et.repammo(cl.player1, type);
     }
 
-    void checkitems(fpsent *d)
+    void checkbaseammo(fpsent *d)
     {
-        if(m_regencapture || !autorepammo() || d!=player1 || d->state!=CS_ALIVE) return;
-        vec o = d->feetpos();
+        int gamemode = cl.gamemode;
+        if(m_noitems || !autorepammo() || d!=cl.player1 || d->state!=CS_ALIVE) return;
+        vec o = d->o;
+        o.z -= d->eyeheight;
         loopv(bases)
         {
             baseinfo &b = bases[i];
-            if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1 && insidebase(b, d->feetpos()) && !strcmp(b.owner, d->team) && b.o.dist(o) < 12)
+            if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1 && insidebase(b, d->o) && !strcmp(b.owner, d->team) && b.o.dist(o) < 12)
             {
-                if(d->lastrepammo!=i)
+                if(lastrepammo!=i)
                 {
-                    if(b.ammo > 0 && !player1->hasmaxammo(b.ammotype-1+I_SHELLS)) addmsg(SV_REPAMMO, "rc", d);
-                    d->lastrepammo = i;
+                    if(b.ammo > 0 && !cl.player1->hasmaxammo(b.ammotype-1+I_SHELLS)) cl.cc.addmsg(SV_REPAMMO, "r");
+                    lastrepammo = i;
                 }
                 return;
             }
         }
-        d->lastrepammo = -1;
-    }
-
+        lastrepammo = -1;
+    }    
+        
     void rendertether(fpsent *d)
     {
         int oldbase = d->lastbase;
-        d->lastbase = -1;
+        d->lastbase = -1;  
         vec pos(d->o.x, d->o.y, d->o.z + (d->aboveeye - d->eyeheight)/2);
-        if(d->state==CS_ALIVE)
+        if(d->state==CS_ALIVE) 
         {
             loopv(bases)
             {
                 baseinfo &b = bases[i];
-                if(!insidebase(b, d->feetpos()) || (strcmp(b.owner, d->team) && strcmp(b.enemy, d->team))) continue;
-                particle_flare(b.ammopos, pos, 0, PART_LIGHTNING, strcmp(d->team, player1->team) ? 0xFF2222 : 0x2222FF, 0.28f);
-                if(oldbase < 0)
+                if(!insidebase(b, d->o) || (strcmp(b.owner, d->team) && strcmp(b.enemy, d->team))) continue;
+                particle_flare(b.ammopos, pos, 0, strcmp(d->team, cl.player1->team) ? 29 : 30);
+                if(oldbase < 0) 
                 {
-                    particle_fireball(pos, 4.8f, PART_EXPLOSION_NO_GLARE, 250, strcmp(d->team, player1->team) ? 0x802020 : 0x2020FF, 4.8f);
-                    particle_splash(PART_SPARK, 50, 250, pos, 0xB49B4B, 0.24f);
+                    particle_fireball(pos, 4, strcmp(d->team, cl.player1->team) ? 31 : 32, 250);
+                    particle_splash(0, 50, 250, pos);
                 }
                 d->lastbase = i;
             }
         }
         if(d->lastbase < 0 && oldbase >= 0)
         {
-            particle_fireball(pos, 4.8f, PART_EXPLOSION_NO_GLARE, 250, strcmp(d->team, player1->team) ? 0x802020 : 0x2020FF, 4.8f);
-            particle_splash(PART_SPARK, 50, 250, pos, 0xB49B4B, 0.24f);
+            particle_fireball(pos, 4, strcmp(d->team, cl.player1->team) ? 31 : 32, 250);
+            particle_splash(0, 50, 250, pos);
         }
     }
 
-    void preload()
+    void preloadbases()
     {
         static const char *basemodels[3] = { "base/neutral", "base/red", "base/blue" };
-        loopi(3) preloadmodel(basemodels[i]);
+        loopi(3) loadmodel(basemodels[i], -1, true);
     }
 
-    void rendergame()
+    void renderbases()
     {
-        if(capturetether() && canaddparticles())
+        int gamemode = cl.gamemode;
+        extern bool shadowmapping;
+        if(capturetether() && !shadowmapping) 
         {
-            loopv(players)
+            loopv(cl.players)
             {
-                fpsent *d = players[i];
+                fpsent *d = cl.players[i];
                 if(d) rendertether(d);
             }
-            rendertether(player1);
+            rendertether(cl.player1);
         }
         loopv(bases)
         {
             baseinfo &b = bases[i];
-            const char *flagname = b.owner[0] ? (strcmp(b.owner, player1->team) ? "base/red" : "base/blue") : "base/neutral";
-            rendermodel(&b.light, flagname, ANIM_MAPMODEL|ANIM_LOOP, b.o, 0, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
-            particle_fireball(b.ammopos, 4.8f, PART_EXPLOSION_NO_GLARE, 0, b.owner[0] ? (strcmp(b.owner, player1->team) ? 0x802020 : 0x2020FF) : 0x208020, 4.8f);
+            const char *flagname = b.owner[0] ? (strcmp(b.owner, cl.player1->team) ? "base/red" : "base/blue") : "base/neutral";
+            rendermodel(&b.ent->light, flagname, ANIM_MAPMODEL|ANIM_LOOP, b.o, 0, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
+            particle_fireball(b.ammopos, 5, b.owner[0] ? (strcmp(b.owner, cl.player1->team) ? 31 : 32) : 33, 0);
 
-            if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1)
+            if(!m_noitemsrail && b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1) 
             {
-                const char *ammoname = entities::entmdlname(I_SHELLS+b.ammotype-1);
-                if(m_regencapture)
-                {
+                const char *ammoname = cl.et.entmdlname(I_SHELLS+b.ammotype-1);
+                if(m_noitems)
+                { 
                     vec height(0, 0, 0);
                     abovemodel(height, ammoname);
                     vec ammopos(b.ammopos);
-                    ammopos.z -= height.z/2 + sinf(lastmillis/100.0f)/20;
-                    rendermodel(&b.light, ammoname, ANIM_MAPMODEL|ANIM_LOOP, ammopos, lastmillis/10.0f, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
+                    ammopos.z -= height.z/2 + sinf(cl.lastmillis/100.0f)/20;
+                    rendermodel(&b.ent->light, ammoname, ANIM_MAPMODEL|ANIM_LOOP, ammopos, cl.lastmillis/10.0f, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
                 }
                 else loopj(b.ammo)
                 {
-                    float angle = 2*M_PI*(lastmillis/4000.0f + j/float(MAXAMMO));
+                    float angle = 2*M_PI*(cl.lastmillis/4000.0f + j/float(MAXAMMO));
                     vec ammopos(b.o);
                     ammopos.x += 10*cosf(angle);
                     ammopos.y += 10*sinf(angle);
                     ammopos.z += 4;
-                    rendermodel(&b.light, entities::entmdlname(I_SHELLS+b.ammotype-1), ANIM_MAPMODEL|ANIM_LOOP, ammopos, 0, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
+                    rendermodel(&b.ent->light, cl.et.entmdlname(I_SHELLS+b.ammotype-1), ANIM_MAPMODEL|ANIM_LOOP, ammopos, 0, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED);
                 }
             }
 
-            int tcolor = 0x1EC850, mtype = -1, mcolor = 0xFFFFFF, mcolor2 = 0;
+            int ttype = 11, mtype = -1;
             if(b.owner[0])
             {
-                bool isowner = !strcmp(b.owner, player1->team);
-                if(b.enemy[0]) { mtype = PART_METER_VS; mcolor = 0xFF1932; mcolor2 = 0x3219FF; if(!isowner) swap(mcolor, mcolor2); }
-                formatstring(b.info)("%s", b.owner); tcolor = isowner ? 0x6496FF : 0xFF4B19;
+                bool isowner = !strcmp(b.owner, cl.player1->team);
+#if 0
+                if(b.enemy[0])
+                {
+                    s_sprintf(b.info)("\f%d%s \f0vs. \f%d%s", isowner ? 3 : 1, b.enemy, isowner ? 1 : 3, b.owner);
+                    mtype = isowner ? 19 : 20; 
+                }
+                else { s_sprintf(b.info)("%s", b.owner); ttype = isowner ? 16 : 13; }
+#else
+                if(b.enemy[0]) mtype = isowner ? 19 : 20;
+                s_sprintf(b.info)("%s", b.owner); ttype = isowner ? 16 : 13;
+#endif
             }
             else if(b.enemy[0])
             {
-                formatstring(b.info)("%s", b.enemy);
-                if(strcmp(b.enemy, player1->team)) { tcolor = 0xFF4B19; mtype = PART_METER; mcolor = 0xFF1932; }
-                else { tcolor = 0x6496FF; mtype = PART_METER; mcolor = 0x3219FF; }
+                s_sprintf(b.info)("%s", b.enemy);
+                if(strcmp(b.enemy, cl.player1->team)) { ttype = 13; mtype = 17; }
+                else { ttype = 16; mtype = 18; }
             }
             else b.info[0] = '\0';
 
             vec above(b.ammopos);
             above.z += FIREBALLRADIUS+1.0f;
-            particle_text(above, b.info, PART_TEXT, 1, tcolor, 2.0f);
+            particle_text(above, b.info, ttype, 1);
             if(mtype>=0)
             {
                 above.z += 3.0f;
-                particle_meter(above, b.converted/float((b.owner[0] ? int(OCCUPYENEMYLIMIT) : int(OCCUPYNEUTRALLIMIT))), mtype, 1, mcolor, mcolor2, 2.0f);
+                particle_meter(above, b.converted/float((b.owner[0] ? 2 : 1) * OCCUPYLIMIT), mtype, 1);
             }
         }
     }
@@ -400,24 +379,24 @@ struct captureclientmode : clientmode
         glTexCoord2f(1.0f, 1.0f); glVertex2f(x+s, y+s);
         glTexCoord2f(0.0f, 1.0f); glVertex2f(x,   y+s);
     }
-
+   
     void drawblips(fpsent *d, int x, int y, int s, int type, bool skipenemy = false)
     {
         const char *textures[3] = {"packages/hud/blip_red.png", "packages/hud/blip_grey.png", "packages/hud/blip_blue.png"};
         settexture(textures[max(type+1, 0)]);
         glBegin(GL_QUADS);
-        float scale = radarscale<=0 || radarscale>maxradarscale ? maxradarscale : radarscale;
+        float scale = radarscale<=0 || radarscale>cl.maxradarscale() ? cl.maxradarscale() : radarscale;
         loopv(bases)
         {
             baseinfo &b = bases[i];
             if(skipenemy && b.enemy[0]) continue;
             switch(type)
             {
-                case 1: if(!b.owner[0] || strcmp(b.owner, player1->team)) continue; break;
+                case 1: if(!b.owner[0] || strcmp(b.owner, cl.player1->team)) continue; break;
                 case 0: if(b.owner[0]) continue; break;
-                case -1: if(!b.owner[0] || !strcmp(b.owner, player1->team)) continue; break;
-                case -2: if(!b.enemy[0] || !strcmp(b.enemy, player1->team)) continue; break;
-            }
+                case -1: if(!b.owner[0] || !strcmp(b.owner, cl.player1->team)) continue; break;
+                case -2: if(!b.enemy[0] || !strcmp(b.enemy, cl.player1->team)) continue; break;
+            } 
             vec dir(b.o);
             dir.sub(d->o);
             dir.z = 0.0f;
@@ -428,19 +407,15 @@ struct captureclientmode : clientmode
         }
         glEnd();
     }
-
+   
     int respawnwait(fpsent *d)
     {
+        int gamemode = cl.gamemode;
         if(m_regencapture) return -1;
-        return max(0, RESPAWNSECS-(lastmillis-d->lastpain)/1000);
+        return max(0, (m_noitemsrail ? RESPAWNSECS/2 : RESPAWNSECS)-(cl.lastmillis-d->lastpain)/1000);
     }
 
-    int clipconsole(int w, int h)
-    {
-        return w*6/40;
-    }
-
-    void drawhud(fpsent *d, int w, int h)
+    void capturehud(fpsent *d, int w, int h)
     {
         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
         int x = 1800*w/h*34/40, y = 1800*1/40, s = 1800*w/h*5/40;
@@ -449,7 +424,7 @@ struct captureclientmode : clientmode
         glBegin(GL_QUADS);
         drawradar(float(x), float(y), float(s));
         glEnd();
-        bool showenemies = lastmillis%1000 >= 500;
+        bool showenemies = cl.lastmillis%1000 >= 500;
         drawblips(d, x, y, s, 1, showenemies);
         drawblips(d, x, y, s, 0, showenemies);
         drawblips(d, x, y, s, -1, showenemies);
@@ -460,40 +435,42 @@ struct captureclientmode : clientmode
             if(wait>=0)
             {
                 glPushMatrix();
-                glScalef(2, 2, 1);
-                bool flash = wait>0 && d==player1 && lastspawnattempt>=d->lastpain && lastmillis < lastspawnattempt+100;
+                glLoadIdentity();
+                glOrtho(0, w*900/h, 900, 0, -1, 1);
+                bool flash = wait>0 && d==cl.player1 && cl.lastspawnattempt>=d->lastpain && cl.lastmillis < cl.lastspawnattempt+100;
                 draw_textf("%s%d", (x+s/2)/2-(wait>=10 ? 28 : 16), (y+s/2)/2-32, flash ? "\f3" : "", wait);
                 glPopMatrix();
             }
         }
     }
 
-    void setup()
+    void setupbases()
     {
-        resetbases();
-        loopv(entities::ents)
+        reset();
+        loopv(cl.et.ents)
         {
-            extentity *e = entities::ents[i];
-            if(e->type!=BASE) continue;
+            extentity *e = cl.et.ents[i];
+            if(e->type!=BASE) continue; 
             baseinfo &b = bases.add();
             b.o = e->o;
             b.ammopos = b.o;
             abovemodel(b.ammopos, "base/neutral");
             b.ammopos.z += FIREBALLRADIUS-2;
             b.ammotype = e->attr1;
-            defformatstring(alias)("base_%d", e->attr2);
+            s_sprintfd(alias)("base_%d", e->attr2);
             const char *name = getalias(alias);
-            if(name[0]) copystring(b.name, name); else formatstring(b.name)("base %d", bases.length());
-            b.light = e->light;
+            if(name[0]) s_strcpy(b.name, name); else s_sprintf(b.name)("base %d", bases.length());
+            b.ent = e;
         }
         vec center(0, 0, 0);
         loopv(bases) center.add(bases[i].o);
         center.div(bases.length());
         radarscale = 0;
         loopv(bases) radarscale = max(radarscale, 2*center.dist(bases[i].o));
+        lastrepammo = -1;
     }
-
-    void senditems(packetbuf &p)
+            
+    void sendbases(ucharbuf &p)
     {
         putint(p, SV_BASES);
         putint(p, bases.length());
@@ -513,40 +490,29 @@ struct captureclientmode : clientmode
         baseinfo &b = bases[i];
         if(owner[0])
         {
-            if(strcmp(b.owner, owner))
-            {
-                conoutf(CON_GAMEINFO, "%s captured %s", owner, b.name);
-                if(!strcmp(owner, player1->team)) playsound(S_V_BASECAP);
+            if(strcmp(b.owner, owner)) 
+            { 
+                conoutf(CON_GAMEINFO, "\f2%s captured %s", owner, b.name); 
+                if(!strcmp(owner, cl.player1->team)) playsound(S_V_BASECAP); 
             }
         }
-        else if(b.owner[0])
-        {
-            conoutf(CON_GAMEINFO, "%s lost %s", b.owner, b.name);
-            if(!strcmp(b.owner, player1->team)) playsound(S_V_BASELOST);
+        else if(b.owner[0]) 
+        { 
+            conoutf(CON_GAMEINFO, "\f2%s lost %s", b.owner, b.name); 
+            if(!strcmp(b.owner, cl.player1->team)) playsound(S_V_BASELOST); 
         }
-        if(strcmp(b.owner, owner)) particle_splash(PART_SPARK, 200, 250, b.ammopos, 0xB49B4B, 0.24f);
-        copystring(b.owner, owner);
-        copystring(b.enemy, enemy);
+        if(strcmp(b.owner, owner)) particle_splash(0, 200, 250, b.ammopos);
+        s_strcpy(b.owner, owner);
+        s_strcpy(b.enemy, enemy);
         b.converted = converted;
         if(ammo>b.ammo) playsound(S_ITEMSPAWN, &b.o);
         b.ammo = ammo;
     }
 
-    void setscore(int base, const char *team, int total)
+    void setscore(const char *team, int total)
     {
         findscore(team).total = total;
         if(total>=10000) conoutf(CON_GAMEINFO, "team %s captured all bases", team);
-        else if(bases.inrange(base))
-        {
-            baseinfo &b = bases[base];
-            if(!strcmp(b.owner, team))
-            {
-                defformatstring(msg)("@%d", total);
-                vec above(b.ammopos);
-                above.z += FIREBALLRADIUS+1.0f;
-                particle_text(above, msg, PART_TEXT, 2000, isteam(team, player1->team) ? 0x6496FF : 0xFF4B19, 4.0f, -8);
-            }
-        }
     }
 
     int closesttoenemy(const char *team, bool noattacked = false, bool farthest = false)
@@ -568,25 +534,25 @@ struct captureclientmode : clientmode
             else if(b.enemy[0] && b.enemies < attackers)
             {
                 attacked = i;
-                attackers = b.enemies;
+                attackers = b.enemies; 
             }
         }
         if(best < 0) return attacked;
         return best;
     }
 
-    int pickteamspawn(const char *team)
+    int pickspawn(const char *team)
     {
-        int closest = closesttoenemy(team, true, m_regencapture);
+        int gamemode = cl.gamemode, closest = closesttoenemy(team, true, m_regencapture);
         if(!m_regencapture && closest < 0) closest = closesttoenemy(team, false);
         if(closest < 0) return -1;
         baseinfo &b = bases[closest];
 
         float bestdist = 1e10f, altdist = 1e10f;
         int best = -1, alt = -1;
-        loopv(entities::ents)
+        loopv(cl.et.ents)
         {
-            extentity *e = entities::ents[i];
+            extentity *e = cl.et.ents[i];
             if(e->type!=PLAYERSTART || e->attr2) continue;
             float dist = e->o.dist(b.o);
             if(dist < bestdist)
@@ -604,113 +570,20 @@ struct captureclientmode : clientmode
         }
         return rnd(2) ? best : alt;
     }
-
-    void pickspawn(fpsent *d)
-    {
-        findplayerspawn(d, pickteamspawn(d->team));
-    }
-
-    const char *prefixnextmap() { return "capture_"; }
-
-
-	bool aicheck(fpsent *d, ai::aistate &b)
-	{
-		return false;
-	}
-
-	void aifind(fpsent *d, ai::aistate &b, vector<ai::interest> &interests)
-	{
-		vec pos = d->feetpos();
-		loopvj(bases)
-		{
-			baseinfo &f = bases[j];
-			static vector<int> targets; // build a list of others who are interested in this
-			targets.setsizenodelete(0);
-			ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, j, true);
-			fpsent *e = NULL;
-			int regen = !m_regencapture || d->health >= 100 ? 0 : 1;
-			if(m_regencapture)
-			{
-				int gun = f.ammotype-1+I_SHELLS;
-				if(f.ammo > 0 && f.ammotype > 0 && f.ammotype <= I_CARTRIDGES-I_SHELLS+1 && !d->hasmaxammo(gun))
-					regen = gun != d->ai->weappref ? 2 : 4;
-			}
-			loopi(numdynents()) if((e = (fpsent *)iterdynents(i)) && ai::targetable(d, e, false) && !e->ai && d->team == e->team)
-			{ // try to guess what non ai are doing
-				vec ep = e->feetpos();
-				if(targets.find(e->clientnum) < 0 && ep.squaredist(f.o) <= (CAPTURERADIUS*CAPTURERADIUS))
-					targets.add(e->clientnum);
-			}
-			if((regen && f.owner[0] && !strcmp(f.owner, d->team)) || (targets.empty() && (!f.owner[0] || strcmp(f.owner, d->team) || f.enemy[0])))
-			{
-				ai::interest &n = interests.add();
-				n.state = ai::AI_S_DEFEND;
-				n.node = ai::closestwaypoint(f.o, ai::NEARDIST, false);
-				n.target = j;
-				n.targtype = ai::AI_T_AFFINITY;
-				n.score = pos.squaredist(f.o)/(regen ? float(100*regen) : 1.f);
-			}
-		}
-	}
-
-	bool aidefend(fpsent *d, ai::aistate &b)
-	{
-		if(bases.inrange(b.target))
-		{
-			baseinfo &f = bases[b.target];
-			bool regen = !m_regencapture || d->health >= 100 ? false : true;
-			if(!regen && m_regencapture)
-			{
-				int gun = f.ammotype-1+I_SHELLS;
-				if(f.ammo > 0 && f.ammotype > 0 && f.ammotype <= I_CARTRIDGES-I_SHELLS+1 && !d->hasmaxammo(gun))
-					regen = true;
-			}
-			int walk = 0;
-			if(!regen && !f.enemy[0] && f.owner[0] && !strcmp(f.owner, d->team))
-			{
-				static vector<int> targets; // build a list of others who are interested in this
-				targets.setsizenodelete(0);
-				ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, b.target, true);
-				fpsent *e = NULL;
-				loopi(numdynents()) if((e = (fpsent *)iterdynents(i)) && ai::targetable(d, e, false) && !e->ai && !strcmp(d->team, e->team))
-				{ // try to guess what non ai are doing
-					vec ep = e->feetpos();
-					if(targets.find(e->clientnum) < 0 && (ep.squaredist(f.o) <= (CAPTURERADIUS*CAPTURERADIUS*4)))
-						targets.add(e->clientnum);
-				}
-				if(!targets.empty())
-				{
-					if(lastmillis-b.millis >= (201-d->skill)*33)
-					{
-						d->ai->clear = true; // re-evaluate so as not to herd
-						return true;
-					}
-					else walk = 2;
-				}
-				else walk = 1;
-				b.millis = lastmillis;
-			}
-			return ai::defend(d, b, f.o, float(CAPTURERADIUS), float(CAPTURERADIUS*(2+(walk*2))), walk); // less wander than ctf
-		}
-		return false;
-	}
-
-	bool aipursue(fpsent *d, ai::aistate &b)
-	{
-		b.type = ai::AI_S_DEFEND;
-		return aidefend(d, b);
-	}
 };
 
 #else
+
+struct captureservmode : capturestate, servmode
+{
     int scoresec;
     bool notgotbases;
-
-    captureservmode() : captures(0), scoresec(0), notgotbases(false) {}
+ 
+    captureservmode(fpsserver &sv) : servmode(sv), scoresec(0), notgotbases(false) {}
 
     void reset(bool empty)
     {
-        resetbases();
+        capturestate::reset();
         scoresec = 0;
         notgotbases = !empty;
     }
@@ -718,10 +591,10 @@ struct captureclientmode : clientmode
     void stealbase(int n, const char *team)
     {
         baseinfo &b = bases[n];
-        loopv(clients)
+        loopv(sv.clients)
         {
-            clientinfo *ci = clients[i];
-            if(ci->state.state==CS_ALIVE && ci->team[0] && !strcmp(ci->team, team) && insidebase(b, ci->state.o))
+            fpsserver::clientinfo *ci = sv.clients[i];
+            if(!ci->spectator && ci->state.state==CS_ALIVE && ci->team[0] && !strcmp(ci->team, team) && insidebase(b, ci->state.o))
                 b.enter(ci->team);
         }
         sendbaseinfo(n);
@@ -729,6 +602,7 @@ struct captureclientmode : clientmode
 
     void replenishammo(clientinfo *ci)
     {
+        int gamemode = sv.gamemode;
         if(m_noitems || notgotbases || ci->state.state!=CS_ALIVE || !ci->team[0]) return;
         loopv(bases)
         {
@@ -736,21 +610,21 @@ struct captureclientmode : clientmode
             if(b.ammotype>0 && b.ammotype<=I_CARTRIDGES-I_SHELLS+1 && insidebase(b, ci->state.o) && !ci->state.hasmaxammo(b.ammotype-1+I_SHELLS) && b.takeammo(ci->team))
             {
                 sendbaseinfo(i);
-                sendf(-1, 1, "riii", SV_REPAMMO, ci->clientnum, b.ammotype);
+                sendf(ci->clientnum, 1, "rii", SV_REPAMMO, b.ammotype);
                 ci->state.addammo(b.ammotype);
                 break;
             }
         }
     }
 
-    void movebases(const char *team, const vec &oldpos, bool oldclip, const vec &newpos, bool newclip)
+    void movebases(const char *team, const vec &oldpos, const vec &newpos)
     {
-        if(!team[0] || minremain<=0) return;
+        if(!team[0] || sv.minremain<0) return;
         loopv(bases)
         {
             baseinfo &b = bases[i];
-            bool leave = !oldclip && insidebase(b, oldpos),
-                 enter = !newclip && insidebase(b, newpos);
+            bool leave = insidebase(b, oldpos),
+                 enter = insidebase(b, newpos);
             if(leave && !enter && b.leave(team)) sendbaseinfo(i);
             else if(enter && !leave && b.enter(team)) sendbaseinfo(i);
             else if(leave && enter && b.steal(team)) stealbase(i, team);
@@ -759,36 +633,36 @@ struct captureclientmode : clientmode
 
     void leavebases(const char *team, const vec &o)
     {
-        movebases(team, o, false, vec(-1e10f, -1e10f, -1e10f), true);
+        movebases(team, o, vec(-1e10f, -1e10f, -1e10f));
     }
-
+   
     void enterbases(const char *team, const vec &o)
     {
-        movebases(team, vec(-1e10f, -1e10f, -1e10f), true, o, false);
+        movebases(team, vec(-1e10f, -1e10f, -1e10f), o);
     }
-
-    void addscore(int base, const char *team, int n)
+    
+    void addscore(const char *team, int n)
     {
         if(!n) return;
         score &cs = findscore(team);
         cs.total += n;
-        sendf(-1, 1, "riisi", SV_BASESCORE, base, team, cs.total);
+        sendf(-1, 1, "risi", SV_TEAMSCORE, team, cs.total);
     }
 
     void regenowners(baseinfo &b, int ticks)
     {
-        loopv(clients)
+        loopv(sv.clients)
         {
-            clientinfo *ci = clients[i];
-            if(ci->state.state==CS_ALIVE && ci->team[0] && !strcmp(ci->team, b.owner) && insidebase(b, ci->state.o))
+            fpsserver::clientinfo *ci = sv.clients[i];
+            if(!ci->spectator && ci->state.state==CS_ALIVE && ci->team[0] && !strcmp(ci->team, b.owner) && insidebase(b, ci->state.o))
             {
                 bool notify = false;
-                if(ci->state.health < ci->state.maxhealth)
+                if(ci->state.health < ci->state.maxhealth) 
                 {
                     ci->state.health = min(ci->state.health + ticks*REGENHEALTH, ci->state.maxhealth);
                     notify = true;
                 }
-                if(ci->state.armour < itemstats[I_GREENARMOUR-I_SHELLS].max)
+                if(ci->state.armour < itemstats[I_GREENARMOUR-I_SHELLS].max) 
                 {
                     ci->state.armour = min(ci->state.armour + ticks*REGENARMOUR, itemstats[I_GREENARMOUR-I_SHELLS].max);
                     notify = true;
@@ -796,45 +670,47 @@ struct captureclientmode : clientmode
                 if(b.ammotype>0)
                 {
                     int ammotype = b.ammotype-1+I_SHELLS;
-                    if(ammotype<=I_CARTRIDGES && !ci->state.hasmaxammo(ammotype))
+                    if(ammotype<=I_CARTRIDGES && !ci->state.hasmaxammo(ammotype)) 
                     {
                         ci->state.addammo(b.ammotype, ticks*REGENAMMO, 100);
                         notify = true;
                     }
                 }
                 if(notify)
-                    sendf(-1, 1, "ri6", SV_BASEREGEN, ci->clientnum, ci->state.health, ci->state.armour, b.ammotype, b.ammotype>0 ? ci->state.ammo[b.ammotype] : 0);
+                    sendf(ci->clientnum, 1, "ri5", SV_BASEREGEN, ci->state.health, ci->state.armour, b.ammotype, b.ammotype>0 ? ci->state.ammo[b.ammotype] : 0);
             }
         }
     }
 
     void update()
     {
-        if(minremain<=0) return;
+        if(sv.minremain<0) return;
         endcheck();
-        int t = gamemillis/1000 - (gamemillis-curtime)/1000;
+        int t = sv.gamemillis/1000 - (sv.gamemillis-sv.curtime)/1000;
         if(t<1) return;
+        int gamemode = sv.gamemode;
         loopv(bases)
         {
             baseinfo &b = bases[i];
             if(b.enemy[0])
             {
-                if(!b.owners || !b.enemies) b.occupy(b.enemy, OCCUPYBONUS*(b.enemies ? 1 : -1) + OCCUPYPOINTS*(b.enemies ? b.enemies : -(1+b.owners))*t);
+                if((!b.owners || !b.enemies) && b.occupy(b.enemy, (m_noitemsrail ? OCCUPYPOINTS*2 : OCCUPYPOINTS)*(b.enemies ? b.enemies : -(1+b.owners))*t)==1) addscore(b.owner, CAPTURESCORE);
                 sendbaseinfo(i);
             }
             else if(b.owner[0])
             {
                 b.capturetime += t;
-
                 int score = b.capturetime/SCORESECS - (b.capturetime-t)/SCORESECS;
-                if(score) addscore(i, b.owner, score);
-
-                if(m_regencapture)
+                if(score) addscore(b.owner, score);
+                if(m_noitems)
                 {
-                    int regen = b.capturetime/REGENSECS - (b.capturetime-t)/REGENSECS;
-                    if(regen) regenowners(b, regen);
+                    if(!m_noitemsrail)
+                    {
+                        int regen = b.capturetime/REGENSECS - (b.capturetime-t)/REGENSECS;
+                        if(regen) regenowners(b, regen);
+                    }
                 }
-                else
+                else 
                 {
                     int ammo = b.capturetime/AMMOSECS - (b.capturetime-t)/AMMOSECS;
                     if(ammo && b.addammo(ammo)) sendbaseinfo(i);
@@ -851,20 +727,22 @@ struct captureclientmode : clientmode
 
     void sendbases()
     {
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+        ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+        ucharbuf p(packet->data, packet->dataLength);
         initclient(NULL, p, false);
-        sendpacket(-1, 1, p.finalize());
+        enet_packet_resize(packet, p.length());
+        sendpacket(-1, 1, packet);
+        if(!packet->referenceCount) enet_packet_destroy(packet);
     }
 
-    void initclient(clientinfo *ci, packetbuf &p, bool connecting)
+    void initclient(clientinfo *ci, ucharbuf &p, bool connecting)
     {
-        if(connecting)
+        if(connecting) 
         {
             loopv(scores)
             {
                 score &cs = scores[i];
-                putint(p, SV_BASESCORE);
-                putint(p, -1);
+                putint(p, SV_TEAMSCORE);
                 sendstring(cs.team, p);
                 putint(p, cs.total);
             }
@@ -894,61 +772,61 @@ struct captureclientmode : clientmode
                 if(!lastteam) lastteam = b.owner;
                 else if(strcmp(lastteam, b.owner))
                 {
-                    lastteam = NULL;
+                    lastteam = false;
                     break;
                 }
             }
             else
             {
-                lastteam = NULL;
+                lastteam = false;
                 break;
             }
         }
 
         if(!lastteam) return;
         findscore(lastteam).total = 10000;
-        sendf(-1, 1, "riisi", SV_BASESCORE, -1, lastteam, 10000);
-        startintermission();
+        sendf(-1, 1, "risi", SV_TEAMSCORE, lastteam, 10000);
+        sv.startintermission(); 
     }
 
-    void entergame(clientinfo *ci)
+    void entergame(clientinfo *ci) 
     {
-        if(notgotbases || ci->state.state!=CS_ALIVE || ci->gameclip) return;
+        if(notgotbases || ci->state.state!=CS_ALIVE) return;
         enterbases(ci->team, ci->state.o);
-    }
+    }        
 
     void spawned(clientinfo *ci)
     {
-        if(notgotbases || ci->gameclip) return;
+        if(notgotbases) return;
         enterbases(ci->team, ci->state.o);
     }
 
     void leavegame(clientinfo *ci, bool disconnecting = false)
     {
-        if(notgotbases || ci->state.state!=CS_ALIVE || ci->gameclip) return;
+        if(notgotbases || ci->state.state!=CS_ALIVE) return;
         leavebases(ci->team, ci->state.o);
     }
 
     void died(clientinfo *ci, clientinfo *actor)
     {
-        if(notgotbases || ci->gameclip) return;
+        if(notgotbases) return;
         leavebases(ci->team, ci->state.o);
     }
 
-    void moved(clientinfo *ci, const vec &oldpos, bool oldclip, const vec &newpos, bool newclip)
+    void moved(clientinfo *ci, const vec &oldpos, const vec &newpos)
     {
         if(notgotbases) return;
-        movebases(ci->team, oldpos, oldclip, newpos, newclip);
+        movebases(ci->team, oldpos, newpos);
     }
 
     void changeteam(clientinfo *ci, const char *oldteam, const char *newteam)
     {
-        if(notgotbases || ci->gameclip) return;
+        if(notgotbases) return;
         leavebases(oldteam, ci->state.o);
         enterbases(newteam, ci->state.o);
     }
 
-    void parsebases(ucharbuf &p, bool commit)
+    void parsebases(ucharbuf &p)
     {
         int numbases = getint(p);
         loopi(numbases)
@@ -958,101 +836,16 @@ struct captureclientmode : clientmode
             o.x = getint(p)/DMF;
             o.y = getint(p)/DMF;
             o.z = getint(p)/DMF;
-            if(p.overread()) break;
-            if(commit && notgotbases) addbase(ammotype>=GUN_SG && ammotype<=GUN_PISTOL ? ammotype : min(ammotype, 0), o);
+            if(notgotbases) addbase(ammotype>=GUN_SG && ammotype<=GUN_PISTOL ? ammotype : min(ammotype, 0), o);
         }
-        if(commit && notgotbases)
+        if(notgotbases)
         {
             notgotbases = false;
             sendbases();
-            loopv(clients) if(clients[i]->state.state==CS_ALIVE) entergame(clients[i]);
+            loopv(sv.clients) if(sv.clients[i]->state.state==CS_ALIVE) entergame(sv.clients[i]);
         }
     }
-
-    bool extinfoteam(const char *team, ucharbuf &p)
-    {
-        int numbases = 0;
-        loopvj(bases) if(!strcmp(bases[j].owner, team)) numbases++;
-        putint(p, numbases);
-        loopvj(bases) if(!strcmp(bases[j].owner, team)) putint(p, j);
-        return true;
-    }
 };
 
 #endif
 
-#elif SERVMODE
-
-case SV_BASES:
-    if(smode==&capturemode) capturemode.parsebases(p, (ci->state.state!=CS_SPECTATOR || ci->privilege || ci->local) && !strcmp(ci->clientmap, smapname));
-    break;
-
-case SV_REPAMMO:
-    if(ci->state.state!=CS_SPECTATOR && cq && smode==&capturemode) capturemode.replenishammo(cq);
-    break;
-
-#else
-
-case SV_BASEINFO:
-{
-    int base = getint(p);
-    string owner, enemy;
-    getstring(text, p);
-    copystring(owner, text);
-    getstring(text, p);
-    copystring(enemy, text);
-    int converted = getint(p), ammo = getint(p);
-    if(m_capture) capturemode.updatebase(base, owner, enemy, converted, ammo);
-    break;
-}
-
-case SV_BASEREGEN:
-{
-    int rcn = getint(p), health = getint(p), armour = getint(p), ammotype = getint(p), ammo = getint(p);
-    fpsent *regen = rcn==player1->clientnum ? player1 : getclient(rcn);
-    if(regen && m_capture)
-    {
-        regen->health = health;
-        regen->armour = armour;
-        if(ammotype>=GUN_SG && ammotype<=GUN_PISTOL) regen->ammo[ammotype] = ammo;
-    }
-    break;
-}
-
-case SV_BASES:
-{
-    int numbases = getint(p);
-    loopi(numbases)
-    {
-        int ammotype = getint(p);
-        string owner, enemy;
-        getstring(text, p);
-        copystring(owner, text);
-        getstring(text, p);
-        copystring(enemy, text);
-        int converted = getint(p), ammo = getint(p);
-        capturemode.initbase(i, ammotype, owner, enemy, converted, ammo);
-    }
-    break;
-}
-
-case SV_BASESCORE:
-{
-    int base = getint(p);
-    getstring(text, p);
-    int total = getint(p);
-    if(m_capture) capturemode.setscore(base, text, total);
-    break;
-}
-
-case SV_REPAMMO:
-{
-    int rcn = getint(p), ammotype = getint(p);
-    fpsent *r = rcn==player1->clientnum ? player1 : getclient(rcn);
-    if(r && m_capture) capturemode.receiveammo(r, ammotype);
-    break;
-}
-
-#endif
-
-
diff --git a/fpsgame/client.cpp b/fpsgame/client.cpp
deleted file mode 100644
index ca44789..0000000
--- a/fpsgame/client.cpp
+++ /dev/null
@@ -1,1558 +0,0 @@
-#include "game.h"
-
-namespace game
-{
-    VARP(maxradarscale, 0, 1024, 10000);
-
-    #include "capture.h"
-    #include "ctf.h"
-
-    clientmode *cmode = NULL;
-    captureclientmode capturemode;
-    ctfclientmode ctfmode;
-
-    void setclientmode()
-    {
-        if(m_capture) cmode = &capturemode;
-        else if(m_ctf) cmode = &ctfmode;
-        else cmode = NULL;
-    }
-
-    bool senditemstoserver = false, sendcrc = false; // after a map change, since server doesn't have map data
-    int lastping = 0;
-
-    bool connected = false, remote = false, demoplayback = false, gamepaused = false;
-    int sessionid = 0;
-    string connectpass = "";
-
-    VARP(deadpush, 1, 2, 20);
-
-    void switchname(const char *name)
-    {
-        if(name[0])
-        {
-            filtertext(player1->name, name, false, MAXNAMELEN);
-            if(!player1->name[0]) copystring(player1->name, "unnamed");
-            addmsg(SV_SWITCHNAME, "rs", player1->name);
-        }
-        else conoutf("your name is: %s", colorname(player1));
-    }
-    ICOMMAND(name, "s", (char *s), switchname(s));
-    ICOMMAND(getname, "", (), result(player1->name));
-
-    void switchteam(const char *team)
-    {
-        if(team[0])
-        {
-            filtertext(player1->team, team, false, MAXTEAMLEN);
-            addmsg(SV_SWITCHTEAM, "rs", player1->team);
-        }
-        else conoutf("your team is: %s", player1->team);
-    }
-    ICOMMAND(team, "s", (char *s), switchteam(s));
-    ICOMMAND(getteam, "", (), result(player1->team));
-
-    void switchplayermodel(int playermodel)
-    {
-        player1->playermodel = playermodel;
-        addmsg(SV_SWITCHMODEL, "ri", player1->playermodel);
-    }
-
-    struct authkey
-    {
-        char *name, *key, *desc;
-        int lastauth;
-
-        authkey(const char *name, const char *key, const char *desc)
-            : name(newstring(name)), key(newstring(key)), desc(newstring(desc)),
-              lastauth(0)
-        {
-        }
-
-        ~authkey()
-        {
-            DELETEA(name);
-            DELETEA(key);
-            DELETEA(desc);
-        }
-    };
-    vector<authkey *> authkeys;
-
-    authkey *findauthkey(const char *desc)
-    {
-        loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, player1->name)) return authkeys[i];
-        loopv(authkeys) if(!strcmp(authkeys[i]->desc, desc)) return authkeys[i];
-        return NULL;
-    }
-
-    VARP(autoauth, 0, 1, 1);
-
-    void addauthkey(const char *name, const char *key, const char *desc)
-    {
-        loopvrev(authkeys) if(!strcmp(authkeys[i]->desc, desc) && !strcmp(authkeys[i]->name, name)) delete authkeys.remove(i);
-        if(name[0] && key[0]) authkeys.add(new authkey(name, key, desc));
-    }
-    ICOMMAND(authkey, "sss", (char *name, char *key, char *desc), addauthkey(name, key, desc));
-
-    void genauthkey(const char *secret)
-    {
-        if(!secret[0]) { conoutf(CON_ERROR, "you must specify a secret password"); return; }
-        vector<char> privkey, pubkey;
-        genprivkey(secret, privkey, pubkey);
-        conoutf("private key: %s", privkey.getbuf());
-        conoutf("public key: %s", pubkey.getbuf());
-    }
-    COMMAND(genauthkey, "s");
-
-    void saveauthkeys()
-    {
-        stream *f = openfile("auth.cfg", "w");
-        if(!f) { conoutf(CON_ERROR, "failed to open auth.cfg for writing"); return; }
-        loopv(authkeys)
-        {
-            authkey *a = authkeys[i];
-            f->printf("authkey \"%s\" \"%s\" \"%s\"\n", a->name, a->key, a->desc);
-        }
-        conoutf("saved authkeys to auth.cfg");
-        delete f;
-    }
-    COMMAND(saveauthkeys, "");
-
-    int numchannels() { return 3; }
-
-    void sendmapinfo()
-    {
-        sendcrc = true;
-        if(player1->state!=CS_SPECTATOR || player1->privilege || !remote) senditemstoserver = true;
-    }
-
-    void writeclientinfo(stream *f)
-    {
-        f->printf("name \"%s\"\n", player1->name);
-    }
-
-    bool allowedittoggle()
-    {
-        bool allow = !connected || !multiplayer(false) || m_edit;
-        if(!allow) conoutf(CON_ERROR, "editing in multiplayer requires coop edit mode (1)");
-        return allow;
-    }
-
-    void edittoggled(bool on)
-    {
-        addmsg(SV_EDITMODE, "ri", on ? 1 : 0);
-        if(player1->state==CS_DEAD) deathstate(player1, true);
-        else if(player1->state==CS_EDITING && player1->editstate==CS_DEAD) showscores(false);
-        disablezoom();
-        player1->suicided = player1->respawned = -2;
-    }
-
-    const char *getclientname(int cn)
-    {
-        fpsent *d = getclient(cn);
-        return d ? d->name : "";
-    }
-    ICOMMAND(getclientname, "i", (int *cn), result(getclientname(*cn)));
-
-    const char *getclientteam(int cn)
-    {
-        fpsent *d = getclient(cn);
-        return d ? d->team : "";
-    }
-    ICOMMAND(getclientteam, "i", (int *cn), result(getclientteam(*cn)));
-    
-    int getclientmodel(int cn)
-    {
-        fpsent *d = getclient(cn);
-        return d ? d->playermodel : -1;
-    }
-    ICOMMAND(getclientmodel, "i", (int *cn), intret(getclientmodel(*cn)));
-
-    const char *getclienticon(int cn)
-    {
-        fpsent *d = getclient(cn);
-        if(!d || d->state==CS_SPECTATOR) return "spectator";
-        const playermodelinfo &mdl = getplayermodelinfo(d);
-        return m_teammode ? (isteam(player1->team, d->team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon;
-    }
-    ICOMMAND(getclienticon, "i", (int *cn), result(getclienticon(*cn)));
-
-    bool isspectator(int cn)
-    {
-        fpsent *d = getclient(cn);
-        return d->state==CS_SPECTATOR;
-    }
-    ICOMMAND(isspectator, "i", (int *cn), intret(isspectator(*cn) ? 1 : 0));
-
-    int parseplayer(const char *arg)
-    {
-        char *end;
-        int n = strtol(arg, &end, 10);
-        if(*arg && !*end)
-        {
-            if(n!=player1->clientnum && !clients.inrange(n)) return -1;
-            return n;
-        }
-        // try case sensitive first
-        loopv(players)
-        {
-            fpsent *o = players[i];
-            if(!strcmp(arg, o->name)) return o->clientnum;
-        }
-        // nothing found, try case insensitive
-        loopv(players)
-        {
-            fpsent *o = players[i];
-            if(!strcasecmp(arg, o->name)) return o->clientnum;
-        }
-        return -1;
-    }
-    ICOMMAND(getclientnum, "s", (char *name), intret(name[0] ? parseplayer(name) : player1->clientnum));
-
-    void listclients(bool local)
-    {
-        vector<char> buf;
-        string cn;
-        int numclients = 0;
-        if(local)
-        {
-            formatstring(cn)("%d", player1->clientnum);
-            buf.put(cn, strlen(cn));
-            numclients++;
-        }
-        loopv(clients) if(clients[i])
-        {
-            formatstring(cn)("%d", clients[i]->clientnum);
-            if(numclients++) buf.add(' ');
-            buf.put(cn, strlen(cn));
-        }
-        buf.add('\0');
-        result(buf.getbuf());
-    }
-    ICOMMAND(listclients, "i", (int *local), listclients(*local!=0));
-
-    void clearbans()
-    {
-        addmsg(SV_CLEARBANS, "r");
-    }
-    COMMAND(clearbans, "");
-
-    void kick(const char *arg)
-    {
-        int i = parseplayer(arg);
-        if(i>=0 && i!=player1->clientnum) addmsg(SV_KICK, "ri", i);
-    }
-    COMMAND(kick, "s");
-
-    void setteam(const char *arg1, const char *arg2)
-    {
-        int i = parseplayer(arg1);
-        if(i>=0 && i!=player1->clientnum) addmsg(SV_SETTEAM, "ris", i, arg2);
-    }
-    COMMAND(setteam, "ss");
-
-    void hashpwd(const char *pwd)
-    {
-        if(player1->clientnum<0) return;
-        string hash;
-        server::hashpassword(player1->clientnum, sessionid, pwd, hash);
-        result(hash);
-    }
-    COMMAND(hashpwd, "s");
-
-    void setmaster(const char *arg)
-    {
-        if(!arg[0]) return;
-        int val = 1;
-        string hash = "";
-        if(!arg[1] && isdigit(arg[0])) val = atoi(arg);
-        else server::hashpassword(player1->clientnum, sessionid, arg, hash);
-        addmsg(SV_SETMASTER, "ris", val, hash);
-    }
-    COMMAND(setmaster, "s");
-    ICOMMAND(mastermode, "i", (int *val), addmsg(SV_MASTERMODE, "ri", *val));
-
-    bool tryauth(const char *desc)
-    {
-        authkey *a = findauthkey(desc);
-        if(!a) return false;
-        a->lastauth = lastmillis;
-        addmsg(SV_AUTHTRY, "rss", a->desc, a->name);
-        return true;
-    }
-    ICOMMAND(auth, "s", (char *desc), tryauth(desc));
-
-    void togglespectator(int val, const char *who)
-    {
-        int i = who[0] ? parseplayer(who) : player1->clientnum;
-        if(i>=0) addmsg(SV_SPECTATOR, "rii", i, val);
-    }
-    ICOMMAND(spectator, "is", (int *val, char *who), togglespectator(*val, who));
-
-    ICOMMAND(checkmaps, "", (), addmsg(SV_CHECKMAPS, "r"));
-
-    VARP(localmode, STARTGAMEMODE, 1, STARTGAMEMODE + NUMGAMEMODES - 1);
-    SVARP(localmap, "complex");
-    VARP(lobbymode, STARTGAMEMODE, 0, STARTGAMEMODE + NUMGAMEMODES - 1);
-    SVARP(lobbymap, "complex");
-
-    int gamemode = INT_MAX, nextmode = INT_MAX;
-    string clientmap = "";
-
-    void changemapserv(const char *name, int mode)        // forced map change from the server
-    {
-        if(multiplayer(false) && !m_mp(mode))
-        {
-            conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer", server::modename(gamemode), gamemode);
-            loopi(NUMGAMEMODES) if(m_mp(STARTGAMEMODE + i)) { mode = STARTGAMEMODE + i; break; }
-        }
-
-        gamemode = mode;
-        nextmode = mode;
-        minremain = -1;
-        if(editmode) toggleedit();
-        if(m_demo) { entities::resetspawns(); return; }
-        if((m_edit && !name[0]) || !load_world(name))
-        {
-            emptymap(0, true, name);
-            senditemstoserver = false;
-        }
-        startgame();
-    }
-
-    void setmode(int mode)
-    {
-        if(multiplayer(false) && !m_mp(mode))
-        {
-            conoutf(CON_ERROR, "mode %s (%d) not supported in multiplayer",  server::modename(mode), mode);
-            intret(0);
-            return;
-        }
-        nextmode = mode;
-        intret(1);
-    }
-    ICOMMAND(mode, "i", (int *val), setmode(*val));
-    ICOMMAND(getmode, "", (), intret(gamemode));
-
-    void changemap(const char *name, int mode) // request map change, server may ignore
-    {
-        if(m_checknot(mode, M_EDIT) && !name[0])
-            name = clientmap[0] ? clientmap : (remote ? lobbymap : localmap);
-        if(!remote)
-        {
-            server::forcemap(name, mode);
-            if(!connected) localconnect();
-        }
-        else if(player1->state!=CS_SPECTATOR || player1->privilege) addmsg(SV_MAPVOTE, "rsi", name, mode);
-    }
-    void changemap(const char *name)
-    {
-        changemap(name, m_valid(nextmode) ? nextmode : (remote ? lobbymode : localmode));
-    }
-    ICOMMAND(map, "s", (char *name), changemap(name));
-
-    void forceedit(const char *name)
-    {
-        changemap(name, 1);
-    }
-
-    void newmap(int size)
-    {
-        addmsg(SV_NEWMAP, "ri", size);
-    }
-
-    void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3)
-    {
-        if(m_edit) switch(op)
-        {
-            case EDIT_FLIP:
-            case EDIT_COPY:
-            case EDIT_PASTE:
-            case EDIT_DELCUBE:
-            {
-                addmsg(SV_EDITF + op, "ri9i4",
-                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
-                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner);
-                break;
-            }
-            case EDIT_ROTATE:
-            {
-                addmsg(SV_EDITF + op, "ri9i5",
-                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
-                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
-                   arg1);
-                break;
-            }
-            case EDIT_MAT:
-            case EDIT_FACE:
-            case EDIT_TEX:
-            case EDIT_REPLACE:
-            {
-                addmsg(SV_EDITF + op, "ri9i6",
-                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
-                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
-                   arg1, arg2);
-                break;
-            }
-            case EDIT_REMIP:
-            {
-                addmsg(SV_EDITF + op, "r");
-                break;
-            }
-        }
-    }
-
-    void printvar(fpsent *d, ident *id)
-    {
-        switch(id->type)
-        {
-            case ID_VAR:
-            {
-                int val = *id->storage.i;
-                string str;
-                if(id->flags&IDF_HEX && id->maxval==0xFFFFFF)
-                    formatstring(str)("0x%.6X (%d, %d, %d)", val, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF);
-                else
-                    formatstring(str)(id->flags&IDF_HEX ? "0x%X" : "%d", val);
-                conoutf("%s set map var \"%s\" to %s", colorname(d), id->name, str);
-                break;
-            }
-            case ID_FVAR:
-                conoutf("%s set map var \"%s\" to %s", colorname(d), id->name, floatstr(*id->storage.f));
-                break;
-            case ID_SVAR:
-                conoutf("%s set map var \"%s\" to \"%s\"", colorname(d), id->name, *id->storage.s);
-                break;
-        }
-    }
-
-    void vartrigger(ident *id)
-    {
-        if(!m_edit) return;
-        switch(id->type)
-        {
-            case ID_VAR:
-                addmsg(SV_EDITVAR, "risi", ID_VAR, id->name, *id->storage.i);
-                break;
-
-            case ID_FVAR:
-                addmsg(SV_EDITVAR, "risf", ID_FVAR, id->name, *id->storage.f);
-                break;
-
-            case ID_SVAR:
-                addmsg(SV_EDITVAR, "riss", ID_SVAR, id->name, *id->storage.s);
-                break;
-            default: return;
-        }
-        printvar(player1, id);
-    }
-
-    void pausegame(int *val)
-    {
-        addmsg(SV_PAUSEGAME, "ri", *val > 0 ? 1 : 0);
-    }
-    COMMAND(pausegame, "i");
-
-    bool ispaused() { return gamepaused; }
-
-    // collect c2s messages conveniently
-    vector<uchar> messages;
-    int messagecn = -1, messagereliable = false;
-
-    void addmsg(int type, const char *fmt, ...)
-    {
-        if(!connected) return;
-        static uchar buf[MAXTRANS];
-        ucharbuf p(buf, sizeof(buf));
-        putint(p, type);
-        int numi = 1, numf = 0, nums = 0, mcn = -1;
-        bool reliable = false;
-        if(fmt)
-        {
-            va_list args;
-            va_start(args, fmt);
-            while(*fmt) switch(*fmt++)
-            {
-                case 'r': reliable = true; break;
-                case 'c':
-                {
-                    fpsent *d = va_arg(args, fpsent *);
-                    mcn = !d || d == player1 ? -1 : d->clientnum;
-                    break;
-                }
-                case 'v':
-                {
-                    int n = va_arg(args, int);
-                    int *v = va_arg(args, int *);
-                    loopi(n) putint(p, v[i]);
-                    numi += n;
-                    break;
-                }
-
-                case 'i':
-                {
-                    int n = isdigit(*fmt) ? *fmt++-'0' : 1;
-                    loopi(n) putint(p, va_arg(args, int));
-                    numi += n;
-                    break;
-                }
-                case 'f':
-                {
-                    int n = isdigit(*fmt) ? *fmt++-'0' : 1;
-                    loopi(n) putfloat(p, (float)va_arg(args, double));
-                    numf += n;
-                    break;
-                }
-                case 's': sendstring(va_arg(args, const char *), p); nums++; break;
-            }
-            va_end(args);
-        }
-        int num = nums || numf ? 0 : numi, msgsize = server::msgsizelookup(type);
-        if(msgsize && num!=msgsize) { defformatstring(s)("inconsistent msg size for %d (%d != %d)", type, num, msgsize); fatal(s); }
-        if(reliable) messagereliable = true;
-        if(mcn != messagecn)
-        {
-            static uchar mbuf[16];
-            ucharbuf m(mbuf, sizeof(mbuf));
-            putint(m, SV_FROMAI);
-            putint(m, mcn);
-            messages.put(mbuf, m.length());
-            messagecn = mcn;
-        }
-        messages.put(buf, p.length());
-    }
-
-    void connectattempt(const char *name, const char *password, const ENetAddress &address)
-    {
-        copystring(connectpass, password);
-    }
-
-    void connectfail()
-    {
-        memset(connectpass, 0, sizeof(connectpass));
-    }
-
-    void gameconnect(bool _remote)
-    {
-        connected = true;
-        remote = _remote;
-        if(editmode) toggleedit();
-    }
-
-    void gamedisconnect(bool cleanup)
-    {
-        if(remote) stopfollowing();
-        connected = remote = false;
-        player1->clientnum = -1;
-        sessionid = 0;
-        messages.setsizenodelete(0);
-        messagereliable = false;
-        messagecn = -1;
-        player1->respawn();
-        player1->lifesequence = 0;
-        player1->state = CS_ALIVE;
-        player1->privilege = PRIV_NONE;
-        senditemstoserver = false;
-        demoplayback = false;
-        gamepaused = false;
-        clearclients(false);
-        if(cleanup)
-        {
-            nextmode = gamemode = INT_MAX;
-            clientmap[0] = '\0';
-        }
-    }
-
-    void toserver(char *text) { conoutf(CON_CHAT, "%s:\f0 %s", colorname(player1), text); addmsg(SV_TEXT, "rcs", player1, text); }
-    COMMANDN(say, toserver, "C");
-
-    void sayteam(char *text) { conoutf(CON_TEAMCHAT, "%s:\f1 %s", colorname(player1), text); addmsg(SV_SAYTEAM, "rcs", player1, text); }
-    COMMAND(sayteam, "C");
-
-    void sendposition(fpsent *d)
-    {
-        if(d->state != CS_ALIVE && d->state != CS_EDITING) return;
-        packetbuf q(100);
-        putint(q, SV_POS);
-        putint(q, d->clientnum);
-        putuint(q, (int)(d->o.x*DMF));              // quantize coordinates to 1/4th of a cube, between 1 and 3 bytes
-        putuint(q, (int)(d->o.y*DMF));
-        putuint(q, (int)((d->o.z-d->eyeheight)*DMF));
-        putuint(q, (int)d->yaw);
-        putint(q, (int)d->pitch);
-        putint(q, (int)d->roll);
-        putint(q, (int)(d->vel.x*DVELF));          // quantize to itself, almost always 1 byte
-        putint(q, (int)(d->vel.y*DVELF));
-        putint(q, (int)(d->vel.z*DVELF));
-        uint physstate = d->physstate | ((d->lifesequence&1)<<6);
-        if(d->falling.x || d->falling.y) physstate |= 0x20;
-        if(d->falling.z) physstate |= 0x10;
-        if((lookupmaterial(d->feetpos())&MATF_CLIP) == MAT_GAMECLIP) physstate |= 0x80;
-        putuint(q, physstate);
-        if(d->falling.x || d->falling.y)
-        {
-            putint(q, (int)(d->falling.x*DVELF));      // quantize to itself, almost always 1 byte
-            putint(q, (int)(d->falling.y*DVELF));
-        }
-        if(d->falling.z) putint(q, (int)(d->falling.z*DVELF));
-        // pack rest in almost always 1 byte: strafe:2, move:2, garmour: 1, yarmour: 1, quad: 1
-        uint flags = (d->strafe&3) | ((d->move&3)<<2);
-        putuint(q, flags);
-        sendclientpacket(q.finalize(), 0);
-    }
-
-    void sendmessages(fpsent *d)
-    {
-        packetbuf p(MAXTRANS);
-        if(sendcrc)
-        {
-            p.reliable();
-            sendcrc = false;
-            const char *mname = getclientmap();
-            putint(p, SV_MAPCRC);
-            sendstring(mname, p);
-            putint(p, mname[0] ? getmapcrc() : 0);
-        }
-        if(senditemstoserver)
-        {
-            if(!m_noitems || cmode!=NULL) p.reliable();
-            if(!m_noitems) entities::putitems(p);
-            if(cmode) cmode->senditems(p);
-            senditemstoserver = false;
-        }
-        if(messages.length())
-        {
-            p.put(messages.getbuf(), messages.length());
-            messages.setsizenodelete(0);
-            if(messagereliable) p.reliable();
-            messagereliable = false;
-            messagecn = -1;
-        }
-        if(lastmillis-lastping>250)
-        {
-            putint(p, SV_PING);
-            putint(p, lastmillis);
-            lastping = lastmillis;
-        }
-        sendclientpacket(p.finalize(), 1);
-    }
-
-    void c2sinfo() // send update to the server
-    {
-        static int lastupdate = -1000;
-        if(totalmillis - lastupdate < 33) return;    // don't update faster than 30fps
-        lastupdate = totalmillis;
-        loopv(players)
-        {
-            fpsent *d = players[i];
-            if(d == player1 || d->ai) sendposition(d);
-        }
-        sendmessages(player1);
-        flushclient();
-    }
-
-    void sendintro()
-    {
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
-        putint(p, SV_CONNECT);
-        sendstring(player1->name, p);
-        string hash = "";
-        if(connectpass[0])
-        {
-            server::hashpassword(player1->clientnum, sessionid, connectpass, hash);
-            memset(connectpass, 0, sizeof(connectpass));
-        }
-        sendstring(hash, p);
-        putint(p, player1->playermodel);
-        sendclientpacket(p.finalize(), 1);
-    }
-
-    void updatepos(fpsent *d)
-    {
-        // 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
-
-        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 && player1->state!=CS_SPECTATOR && 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)
-        {
-            if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
-            d->lastupdate = lastmillis;
-        }
-    }
-
-    void parsepositions(ucharbuf &p)
-    {
-        int type;
-        while(p.remaining()) switch(type = getint(p))
-        {
-            case SV_POS:                        // position of another client
-            {
-                int cn = getint(p);
-                vec o, vel, falling;
-                float yaw, pitch, roll;
-                int physstate, f;
-                o.x = getuint(p)/DMF;
-                o.y = getuint(p)/DMF;
-                o.z = getuint(p)/DMF;
-                yaw = (float)getuint(p);
-                pitch = (float)getint(p);
-                roll = (float)getint(p);
-                vel.x = getint(p)/DVELF;
-                vel.y = getint(p)/DVELF;
-                vel.z = getint(p)/DVELF;
-                physstate = getuint(p);
-                falling = vec(0, 0, 0);
-                if(physstate&0x20)
-                {
-                    falling.x = getint(p)/DVELF;
-                    falling.y = getint(p)/DVELF;
-                }
-                if(physstate&0x10) falling.z = getint(p)/DVELF;
-                int seqcolor = (physstate>>6)&1;
-                f = getuint(p);
-                fpsent *d = getclient(cn);
-                if(!d || d->lifesequence < 0 || seqcolor!=(d->lifesequence&1) || d->state==CS_DEAD) continue;
-                float oldyaw = d->yaw, oldpitch = d->pitch;
-                d->yaw = yaw;
-                d->pitch = pitch;
-                d->roll = roll;
-                d->strafe = (f&3)==3 ? -1 : f&3;
-                f >>= 2;
-                d->move = (f&3)==3 ? -1 : f&3;
-                vec oldpos(d->o);
-                if(allowmove(d))
-                {
-                    d->o = o;
-                    d->o.z += d->eyeheight;
-                    d->vel = vel;
-                    d->falling = falling;
-                    d->physstate = physstate & 0x0F;
-                    updatephysstate(d);
-                    updatepos(d);
-                }
-                if(smoothmove && d->smoothmillis>=0 && oldpos.dist(d->o) < smoothdist)
-                {
-                    d->newpos = d->o;
-                    d->newyaw = d->yaw;
-                    d->newpitch = d->pitch;
-                    d->o = oldpos;
-                    d->yaw = oldyaw;
-                    d->pitch = oldpitch;
-                    (d->deltapos = oldpos).sub(d->newpos);
-                    d->deltayaw = oldyaw - d->newyaw;
-                    if(d->deltayaw > 180) d->deltayaw -= 360;
-                    else if(d->deltayaw < -180) d->deltayaw += 360;
-                    d->deltapitch = oldpitch - d->newpitch;
-                    d->smoothmillis = lastmillis;
-                }
-                else d->smoothmillis = 0;
-                if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE;
-                break;
-            }
-
-            default:
-                neterr("type");
-                return;
-        }
-    }
-
-    void parsestate(fpsent *d, ucharbuf &p, bool resume = false)
-    {
-        if(!d) { static fpsent dummy; d = &dummy; }
-        if(resume)
-        {
-            if(d==player1) getint(p);
-            else d->state = getint(p);
-            d->frags = getint(p);
-            if(d==player1) getint(p);
-            else d->quadmillis = getint(p);
-        }
-        d->lifesequence = getint(p);
-        d->health = getint(p);
-        d->maxhealth = getint(p);
-        d->armour = getint(p);
-        d->armourtype = getint(p);
-        if(resume && d==player1)
-        {
-            getint(p);
-            loopi(GUN_PISTOL-GUN_SG+1) getint(p);
-        }
-        else
-        {
-            d->gunselect = getint(p);
-            loopi(GUN_PISTOL-GUN_SG+1) d->ammo[GUN_SG+i] = getint(p);
-        }
-    }
-
-    void parsemessages(int cn, fpsent *d, ucharbuf &p)
-    {
-        static char text[MAXTRANS];
-        int type;
-        bool mapchanged = false, initmap = false;
-
-        while(p.remaining()) switch(type = getint(p))
-        {
-            case SV_SERVINFO:                   // welcome messsage from the server
-            {
-                int mycn = getint(p), prot = getint(p);
-                if(prot!=PROTOCOL_VERSION)
-                {
-                    conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot);
-                    disconnect();
-                    return;
-                }
-                sessionid = getint(p);
-                player1->clientnum = mycn;      // we are now connected
-                if(getint(p) > 0) conoutf("this server is password protected");
-                sendintro();
-                break;
-            }
-
-            case SV_WELCOME:
-            {
-                int hasmap = getint(p);
-                if(!hasmap) initmap = true; // we are the first client on this server, set map
-                break;
-            }
-
-            case SV_PAUSEGAME:
-            {
-                int val = getint(p);
-                gamepaused = val > 0;
-                conoutf("game is %s", gamepaused ? "paused" : "resumed");
-                break;
-            }
-
-            case SV_CLIENT:
-            {
-                int cn = getint(p), len = getuint(p);
-                ucharbuf q = p.subbuf(len);
-                parsemessages(cn, getclient(cn), q);
-                break;
-            }
-
-            case SV_SOUND:
-                if(!d) return;
-                playsound(getint(p), &d->o);
-                break;
-
-            case SV_TEXT:
-            {
-                if(!d) return;
-                getstring(text, p);
-                filtertext(text, text);
-                if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR)
-                {
-                    defformatstring(ds)("@%s", &text);
-                    particle_text(d->abovehead(), ds, PART_TEXT, 2000, 0x32FF64, 4.0f, -8);
-                }
-                conoutf(CON_CHAT, "%s:\f0 %s", colorname(d), text);
-                break;
-            }
-
-            case SV_SAYTEAM:
-            {
-                int tcn = getint(p);
-                fpsent *t = getclient(tcn);
-                getstring(text, p);
-                filtertext(text, text);
-                if(!t) break;
-                if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR)
-                {
-                    defformatstring(ts)("@%s", &text);
-                    particle_text(t->abovehead(), ts, PART_TEXT, 2000, 0x6496FF, 4.0f, -8);
-                }
-                conoutf(CON_TEAMCHAT, "%s:\f1 %s", colorname(t), text);
-                break;
-            }
-
-            case SV_MAPCHANGE:
-                getstring(text, p);
-                changemapserv(text, getint(p));
-                mapchanged = true;
-                if(getint(p)) entities::spawnitems();
-                else senditemstoserver = false;
-                break;
-
-            case SV_FORCEDEATH:
-            {
-                int cn = getint(p);
-                fpsent *d = cn==player1->clientnum ? player1 : newclient(cn);
-                if(!d) break;
-                if(d==player1)
-                {
-                    if(editmode) toggleedit();
-                    stopfollowing();
-                    showscores(true);
-                }
-                else d->resetinterp();
-                d->state = CS_DEAD;
-                break;
-            }
-
-            case SV_ITEMLIST:
-            {
-                int n;
-                while((n = getint(p))>=0 && !p.overread())
-                {
-                    if(mapchanged) entities::setspawn(n, true);
-                    getint(p); // type
-                }
-                break;
-            }
-
-            case SV_MAPRELOAD:          // server requests next map
-            {
-                defformatstring(nextmapalias)("nextmap_%s%s", (cmode ? cmode->prefixnextmap() : ""), getclientmap());
-                const char *map = getalias(nextmapalias);     // look up map in the cycle
-                addmsg(SV_MAPCHANGE, "rsi", *map ? map : getclientmap(), nextmode);
-                break;
-            }
-
-            case SV_INITCLIENT:            // another client either connected or changed name/team
-            {
-                int cn = getint(p);
-                fpsent *d = newclient(cn);
-                if(!d)
-                {
-                    getstring(text, p);
-                    getstring(text, p);
-                    getint(p);
-                    break;
-                }
-                getstring(text, p);
-                filtertext(text, text, false, MAXNAMELEN);
-                if(!text[0]) copystring(text, "unnamed");
-                if(d->name[0])          // already connected
-                {
-                    if(strcmp(d->name, text))
-                        conoutf("%s is now known as %s", colorname(d), colorname(d, text));
-                }
-                else                    // new client
-                {
-                    conoutf("connected: %s", colorname(d, text));
-                    loopv(players)   // clear copies since new player doesn't have them
-                        freeeditinfo(players[i]->edit);
-                    freeeditinfo(localedit);
-                }
-                copystring(d->name, text, MAXNAMELEN+1);
-                getstring(text, p);
-                filtertext(d->team, text, false, MAXTEAMLEN);
-                d->playermodel = getint(p);
-                break;
-            }
-
-            case SV_SWITCHNAME:
-                getstring(text, p);
-                if(d)
-                {
-                    filtertext(text, text, false, MAXNAMELEN);
-                    if(!text[0]) copystring(text, "unnamed");
-                    conoutf("%s is now known as %s", colorname(d), colorname(d, text));
-                    copystring(d->name, text, MAXNAMELEN+1);
-                }
-                break;
-
-            case SV_SWITCHMODEL:
-            {
-                int model = getint(p);
-                if(d) d->playermodel = model;
-                break;
-            }
-
-            case SV_CDIS:
-                clientdisconnected(getint(p));
-                break;
-
-            case SV_SPAWN:
-            {
-                if(d)
-                {
-                    if(d->state==CS_DEAD && d->lastpain) saveragdoll(d);
-                    d->respawn();
-                }
-                parsestate(d, p);
-                if(!d) break;
-                d->state = CS_SPAWNING;
-                if(player1->state==CS_SPECTATOR && following==d->clientnum)
-                    lasthit = 0;
-                break;
-            }
-
-            case SV_SPAWNSTATE:
-            {
-                int scn = getint(p);
-                fpsent *s = getclient(scn);
-                if(!s) { parsestate(NULL, p); break; }
-                if(s->state==CS_DEAD && s->lastpain) saveragdoll(s);
-                if(s==player1)
-                {
-                    if(editmode) toggleedit();
-                    stopfollowing();
-                }
-                s->respawn();
-                parsestate(s, p);
-                s->state = CS_ALIVE;
-                if(cmode) cmode->pickspawn(s);
-                else findplayerspawn(s);
-                if(s == player1)
-                {
-                    showscores(false);
-                    lasthit = 0;
-                }
-                if(cmode) cmode->respawned(s);
-				ai::spawned(s);
-                addmsg(SV_SPAWN, "rcii", s, s->lifesequence, s->gunselect);
-                break;
-            }
-
-            case SV_SHOTFX:
-            {
-                int scn = getint(p), gun = getint(p);
-                vec from, to;
-                loopk(3) from[k] = getint(p)/DMF;
-                loopk(3) to[k] = getint(p)/DMF;
-                fpsent *s = getclient(scn);
-                if(!s) break;
-                if(gun>GUN_FIST && gun<=GUN_PISTOL && s->ammo[gun]) s->ammo[gun]--;
-                s->gunselect = clamp(gun, (int)GUN_FIST, (int)GUN_PISTOL);
-                s->gunwait = guns[s->gunselect].attackdelay;
-                int prevaction = s->lastaction;
-                s->lastaction = lastmillis;
-                s->lastattackgun = s->gunselect;
-                shoteffects(gun, from, to, s, false, prevaction);
-                break;
-            }
-
-            case SV_DAMAGE:
-            {
-                int tcn = getint(p),
-                    acn = getint(p),
-                    damage = getint(p),
-                    armour = getint(p),
-                    health = getint(p);
-                fpsent *target = getclient(tcn),
-                       *actor = getclient(acn);
-                if(!target || !actor) break;
-                target->armour = armour;
-                target->health = health;
-                damaged(damage, target, actor, false);
-                break;
-            }
-
-            case SV_HITPUSH:
-            {
-                int tcn = getint(p), gun = getint(p), damage = getint(p);
-                fpsent *target = getclient(tcn);
-                vec dir;
-                loopk(3) dir[k] = getint(p)/DNF;
-                if(target) target->hitpush(damage * (target->health<=0 ? deadpush : 1), dir, NULL, gun);
-                break;
-            }
-
-            case SV_DIED:
-            {
-                int vcn = getint(p), acn = getint(p), frags = getint(p);
-                fpsent *victim = getclient(vcn),
-                       *actor = getclient(acn);
-                if(!actor) break;
-                actor->frags = frags;
-                if(actor!=player1 && (!cmode || !cmode->hidefrags()))
-                {
-                    defformatstring(ds)("@%d", actor->frags);
-                    particle_text(actor->abovehead(), ds, PART_TEXT, 2000, 0x32FF64, 4.0f, -8);
-                }
-                if(!victim) break;
-                killed(victim, actor);
-                break;
-            }
-
-            case SV_GUNSELECT:
-            {
-                if(!d) return;
-                int gun = getint(p);
-                d->gunselect = max(gun, 0);
-                playsound(S_WEAPLOAD, &d->o);
-                break;
-            }
-
-            case SV_TAUNT:
-            {
-                if(!d) return;
-                d->lasttaunt = lastmillis;
-                break;
-            }
-
-            case SV_RESUME:
-            {
-                for(;;)
-                {
-                    int cn = getint(p);
-                    if(p.overread() || cn<0) break;
-                    fpsent *d = (cn == player1->clientnum ? player1 : newclient(cn));
-                    parsestate(d, p, true);
-                }
-                break;
-            }
-
-            case SV_ITEMSPAWN:
-            {
-                int i = getint(p);
-                if(!entities::ents.inrange(i)) break;
-                entities::setspawn(i, true);
-                playsound(S_ITEMSPAWN, &entities::ents[i]->o);
-                const char *name = entities::itemname(i);
-                if(name) particle_text(entities::ents[i]->o, name, PART_TEXT, 2000, 0x32FF64, 4.0f, -8);
-                break;
-            }
-
-            case SV_ITEMACC:            // server acknowledges that I picked up this item
-            {
-                int i = getint(p), cn = getint(p);
-                fpsent *d = getclient(cn);
-                entities::pickupeffects(i, d);
-                break;
-            }
-
-            case SV_EDITF:              // coop editing messages
-            case SV_EDITT:
-            case SV_EDITM:
-            case SV_FLIP:
-            case SV_COPY:
-            case SV_PASTE:
-            case SV_ROTATE:
-            case SV_REPLACE:
-            case SV_DELCUBE:
-            {
-                if(!d) return;
-                selinfo sel;
-                sel.o.x = getint(p); sel.o.y = getint(p); sel.o.z = getint(p);
-                sel.s.x = getint(p); sel.s.y = getint(p); sel.s.z = getint(p);
-                sel.grid = getint(p); sel.orient = getint(p);
-                sel.cx = getint(p); sel.cxs = getint(p); sel.cy = getint(p), sel.cys = getint(p);
-                sel.corner = getint(p);
-                int dir, mode, tex, newtex, mat, filter, allfaces;
-                ivec moveo;
-                switch(type)
-                {
-                    case SV_EDITF: dir = getint(p); mode = getint(p); mpeditface(dir, mode, sel, false); break;
-                    case SV_EDITT: tex = getint(p); allfaces = getint(p); mpedittex(tex, allfaces, sel, false); break;
-                    case SV_EDITM: mat = getint(p); filter = getint(p); mpeditmat(mat, filter, sel, false); break;
-                    case SV_FLIP: mpflip(sel, false); break;
-                    case SV_COPY: if(d) mpcopy(d->edit, sel, false); break;
-                    case SV_PASTE: if(d) mppaste(d->edit, sel, false); break;
-                    case SV_ROTATE: dir = getint(p); mprotate(dir, sel, false); break;
-                    case SV_REPLACE: tex = getint(p); newtex = getint(p); mpreplacetex(tex, newtex, sel, false); break;
-                    case SV_DELCUBE: mpdelcube(sel, false); break;
-                }
-                break;
-            }
-            case SV_REMIP:
-            {
-                if(!d) return;
-                conoutf("%s remipped", colorname(d));
-                mpremip(false);
-                break;
-            }
-            case SV_EDITENT:            // coop edit of ent
-            {
-                if(!d) return;
-                int i = getint(p);
-                float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF;
-                int type = getint(p);
-                int attr1 = getint(p), attr2 = getint(p), attr3 = getint(p), attr4 = getint(p), attr5 = getint(p);
-
-                mpeditent(i, vec(x, y, z), type, attr1, attr2, attr3, attr4, attr5, false);
-                break;
-            }
-            case SV_EDITVAR:
-            {
-                if(!d) return;
-                int type = getint(p);
-                getstring(text, p);
-                string name;
-                filtertext(name, text, false, MAXSTRLEN-1);
-                ident *id = getident(name);
-                switch(type)
-                {
-                    case ID_VAR:
-                    {
-                        int val = getint(p);
-                        if(id && !(id->flags&IDF_READONLY)) setvar(name, val);
-                        break;
-                    }
-                    case ID_FVAR:
-                    {
-                        float val = getfloat(p);
-                        if(id && !(id->flags&IDF_READONLY)) setfvar(name, val);
-                        break;
-                    }
-                    case ID_SVAR:
-                    {
-                        getstring(text, p);
-                        if(id && !(id->flags&IDF_READONLY)) setsvar(name, text);
-                        break;
-                    }
-                }
-                printvar(d, id);
-                break;
-            }
-
-            case SV_PONG:
-                addmsg(SV_CLIENTPING, "i", player1->ping = (player1->ping*5+lastmillis-getint(p))/6);
-                break;
-
-            case SV_CLIENTPING:
-                if(!d) return;
-                d->ping = getint(p);
-                break;
-
-            case SV_TIMEUP:
-                timeupdate(getint(p));
-                break;
-
-            case SV_SERVMSG:
-                getstring(text, p);
-                conoutf("%s", text);
-                break;
-
-            case SV_SENDDEMOLIST:
-            {
-                int demos = getint(p);
-                if(!demos) conoutf("no demos available");
-                else loopi(demos)
-                {
-                    getstring(text, p);
-                    conoutf("%d. %s", i+1, text);
-                }
-                break;
-            }
-
-            case SV_DEMOPLAYBACK:
-            {
-                int on = getint(p);
-                if(on) player1->state = CS_SPECTATOR;
-                else clearclients();
-                demoplayback = on!=0;
-                player1->clientnum = getint(p);
-                gamepaused = false;
-                const char *alias = on ? "demostart" : "demoend";
-                if(identexists(alias)) execute(alias);
-                break;
-            }
-
-            case SV_CURRENTMASTER:
-            {
-                int mn = getint(p), priv = getint(p);
-                loopv(players) players[i]->privilege = PRIV_NONE;
-                if(mn>=0)
-                {
-                    fpsent *m = mn==player1->clientnum ? player1 : newclient(mn);
-                    if(m) m->privilege = priv;
-                }
-                break;
-            }
-
-            case SV_EDITMODE:
-            {
-                int val = getint(p);
-                if(!d) break;
-                if(val)
-                {
-                    d->editstate = d->state;
-                    d->state = CS_EDITING;
-                }
-                else
-                {
-                    d->state = d->editstate;
-                    if(d->state==CS_DEAD) deathstate(d, true);
-                }
-                break;
-            }
-
-            case SV_SPECTATOR:
-            {
-                int sn = getint(p), val = getint(p);
-                fpsent *s;
-                if(sn==player1->clientnum)
-                {
-                    s = player1;
-                    if(val && remote && !player1->privilege) senditemstoserver = false;
-                }
-                else s = newclient(sn);
-                if(!s) return;
-                if(val)
-                {
-                    if(s==player1)
-                    {
-                        if(editmode) toggleedit();
-                        if(s->state==CS_DEAD) showscores(false);
-                        disablezoom();
-                    }
-                    s->state = CS_SPECTATOR;
-                }
-                else if(s->state==CS_SPECTATOR)
-                {
-                    if(s==player1) stopfollowing();
-                    deathstate(s, true);
-                }
-                break;
-            }
-
-            case SV_SETTEAM:
-            {
-                int wn = getint(p);
-                getstring(text, p);
-                fpsent *w = getclient(wn);
-                if(!w) return;
-                filtertext(w->team, text, false, MAXTEAMLEN);
-                break;
-            }
-
-            #define PARSEMESSAGES 1
-            #include "capture.h"
-            #include "ctf.h"
-            #undef PARSEMESSAGES
-
-            case SV_ANNOUNCE:
-            {
-                int t = getint(p);
-                if     (t==I_QUAD)  { playsound(S_V_QUAD10);  conoutf(CON_GAMEINFO, "\f2quad damage will spawn in 10 seconds!"); }
-                else if(t==I_BOOST) { playsound(S_V_BOOST10); conoutf(CON_GAMEINFO, "\f2+10 health will spawn in 10 seconds!"); }
-                break;
-            }
-
-            case SV_NEWMAP:
-            {
-                int size = getint(p);
-                if(size>=0) emptymap(size, true, NULL);
-                else enlargemap(true);
-                if(d && d!=player1)
-                {
-                    int newsize = 0;
-                    while(1<<newsize < getworldsize()) newsize++;
-                    conoutf(size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", colorname(d), newsize);
-                }
-                break;
-            }
-
-            case SV_REQAUTH:
-            {
-                getstring(text, p);
-                if(autoauth && text[0] && tryauth(text)) conoutf("server requested authkey \"%s\"", text);
-                break;
-            }
-
-            case SV_AUTHCHAL:
-            {
-                getstring(text, p);
-                authkey *a = findauthkey(text);
-                uint id = (uint)getint(p);
-                getstring(text, p);
-                if(a && a->lastauth && lastmillis - a->lastauth < 60*1000)
-                {
-                    vector<char> buf;
-                    answerchallenge(a->key, text, buf);
-                    //conoutf(CON_DEBUG, "answering %u, challenge %s with %s", id, text, buf.getbuf());
-                    addmsg(SV_AUTHANS, "rsis", a->desc, id, buf.getbuf());
-                }
-                break;
-            }
-
-            case SV_INITAI:
-            {
-                int bn = getint(p), on = getint(p), at = getint(p), sk = clamp(getint(p), 1, 101), pm = getint(p);
-                string name, team;
-                getstring(text, p);
-                filtertext(name, text, false, MAXNAMELEN);
-                getstring(text, p);
-                filtertext(team, text, false, MAXTEAMLEN);
-                fpsent *b = newclient(bn);
-                if(!b) break;
-                ai::init(b, at, on, sk, bn, pm, name, team);
-                break;
-            }
-
-            default:
-                neterr("type", cn < 0);
-                return;
-        }
-        if(initmap)
-        {
-            int mode = gamemode;
-            const char *map = getclientmap();
-            if((multiplayer(false) && !m_mp(mode)) || (mode!=1 && !map[0]))
-            {
-                mode = remote ? lobbymode : localmode;
-                map = remote ? lobbymap : localmap;
-            }
-            changemap(map, mode);
-        }
-    }
-
-    void receivefile(uchar *data, int len)
-    {
-        ucharbuf p(data, len);
-        int type = getint(p);
-        data += p.length();
-        len -= p.length();
-        switch(type)
-        {
-            case SV_SENDDEMO:
-            {
-                defformatstring(fname)("%d.dmo", lastmillis);
-                stream *demo = openrawfile(fname, "wb");
-                if(!demo) return;
-                conoutf("received demo \"%s\"", fname);
-                demo->write(data, len);
-                delete demo;
-                break;
-            }
-
-            case SV_SENDMAP:
-            {
-                if(gamemode!=1) return;
-                string oldname;
-                copystring(oldname, getclientmap());
-                defformatstring(mname)("getmap_%d", lastmillis);
-                defformatstring(fname)("packages/base/%s.ogz", mname);
-                stream *map = openrawfile(fname, "wb");
-                if(!map) return;
-                conoutf("received map");
-                map->write(data, len);
-                delete map;
-                load_world(mname, oldname[0] ? oldname : NULL);
-                remove(findfile(fname, "rb"));
-                break;
-            }
-        }
-    }
-
-    void parsepacketclient(int chan, packetbuf &p)   // processes any updates from the server
-    {
-        switch(chan)
-        {
-            case 0:
-                parsepositions(p);
-                break;
-
-            case 1:
-                parsemessages(-1, NULL, p);
-                break;
-
-            case 2:
-                receivefile(p.buf, p.maxlen);
-                break;
-        }
-    }
-
-    void getmap()
-    {
-        if(gamemode!=1) { conoutf(CON_ERROR, "\"getmap\" only works in coop edit mode"); return; }
-        conoutf("getting map...");
-        addmsg(SV_GETMAP, "r");
-    }
-    COMMAND(getmap, "");
-
-    void stopdemo()
-    {
-        if(remote)
-        {
-            if(player1->privilege<PRIV_ADMIN) return;
-            addmsg(SV_STOPDEMO, "r");
-        }
-        else server::stopdemo();
-    }
-    COMMAND(stopdemo, "");
-
-    void recorddemo(int val)
-    {
-        if(remote && player1->privilege<PRIV_ADMIN) return;
-        addmsg(SV_RECORDDEMO, "ri", val);
-    }
-    ICOMMAND(recorddemo, "i", (int *val), recorddemo(*val));
-
-    void cleardemos(int val)
-    {
-        if(remote && player1->privilege<PRIV_ADMIN) return;
-        addmsg(SV_CLEARDEMOS, "ri", val);
-    }
-    ICOMMAND(cleardemos, "i", (int *val), cleardemos(*val));
-
-    void getdemo(int i)
-    {
-        if(i<=0) conoutf("getting demo...");
-        else conoutf("getting demo %d...", i);
-        addmsg(SV_GETDEMO, "ri", i);
-    }
-    ICOMMAND(getdemo, "i", (int *val), getdemo(*val));
-
-    void listdemos()
-    {
-        conoutf("listing demos...");
-        addmsg(SV_LISTDEMOS, "r");
-    }
-    COMMAND(listdemos, "");
-
-    void sendmap()
-    {
-        if(gamemode!=1 || (player1->state==CS_SPECTATOR && remote && !player1->privilege)) { conoutf(CON_ERROR, "\"sendmap\" only works in coop edit mode"); return; }
-        conoutf("sending map...");
-        defformatstring(mname)("sendmap_%d", lastmillis);
-        save_world(mname, true);
-        defformatstring(fname)("packages/base/%s.ogz", mname);
-        stream *map = openrawfile(fname, "rb");
-        if(map)
-        {
-            int len = map->size();
-            if(len > 1024*1024) conoutf(CON_ERROR, "map is too large");
-            else if(len <= 0) conoutf(CON_ERROR, "could not read map");
-            else sendfile(-1, 2, map);
-            delete map;
-        }
-        else conoutf(CON_ERROR, "could not read map");
-        remove(findfile(fname, "rb"));
-    }
-    COMMAND(sendmap, "");
-
-    void gotoplayer(const char *arg)
-    {
-        if(player1->state!=CS_SPECTATOR && player1->state!=CS_EDITING) return;
-        int i = parseplayer(arg);
-        if(i>=0)
-        {
-            fpsent *d = getclient(i);
-            if(!d || d==player1) return;
-            player1->o = d->o;
-            vec dir;
-            vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir);
-            player1->o.add(dir.mul(-32));
-            player1->resetinterp();
-        }
-    }
-    COMMANDN(goto, gotoplayer, "s");
-
-    void gotosel()
-    {
-        if(player1->state!=CS_EDITING) return;
-        player1->o = getselpos();
-        vec dir;
-        vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir);
-        player1->o.add(dir.mul(-32));
-        player1->resetinterp();
-    }
-    COMMAND(gotosel, "");
-}
-
diff --git a/fpsgame/client.h b/fpsgame/client.h
new file mode 100644
index 0000000..1c65752
--- /dev/null
+++ b/fpsgame/client.h
@@ -0,0 +1,1295 @@
+struct clientcom : iclientcom
+{
+    fpsclient &cl;
+
+    bool c2sinit;       // whether we need to tell the other clients our stats
+
+    bool senditemstoserver;     // after a map change, since server doesn't have map data
+    int lastping;
+
+    bool connected, remote, demoplayback;
+
+    bool spectator;
+
+    fpsent *player1;
+
+    clientcom(fpsclient &_cl) : cl(_cl), c2sinit(false), senditemstoserver(false), lastping(0), connected(false), remote(false), demoplayback(false), spectator(false), player1(_cl.player1)
+    {
+        CCOMMAND(say, "C", (clientcom *self, char *s), self->toserver(s));
+        CCOMMAND(sayteam, "C", (clientcom *self, char *s), self->sayteam(s));
+        CCOMMAND(name, "s", (clientcom *self, char *s), self->switchname(s));
+        CCOMMAND(team, "s", (clientcom *self, char *s), self->switchteam(s));
+        CCOMMAND(map, "s", (clientcom *self, char *s), self->changemap(s));
+        CCOMMAND(clearbans, "", (clientcom *self, char *s), self->clearbans());
+        CCOMMAND(kick, "s", (clientcom *self, char *s), self->kick(s));
+        CCOMMAND(goto, "s", (clientcom *self, char *s), self->gotoplayer(s));
+        CCOMMAND(spectator, "is", (clientcom *self, int *val, char *who), self->togglespectator(*val, who));
+        CCOMMAND(mastermode, "i", (clientcom *self, int *val), if(self->remote) self->addmsg(SV_MASTERMODE, "ri", *val));
+        CCOMMAND(setmaster, "s", (clientcom *self, char *s), self->setmaster(s));
+        CCOMMAND(approve, "s", (clientcom *self, char *s), self->approvemaster(s));
+        CCOMMAND(setteam, "ss", (clientcom *self, char *who, char *team), self->setteam(who, team));
+        CCOMMAND(getmap, "", (clientcom *self), self->getmap());
+        CCOMMAND(sendmap, "", (clientcom *self), self->sendmap());
+        CCOMMAND(listdemos, "", (clientcom *self), self->listdemos());
+        CCOMMAND(getdemo, "i", (clientcom *self, int *val), self->getdemo(*val));
+        CCOMMAND(recorddemo, "i", (clientcom *self, int *val), self->recorddemo(*val));
+        CCOMMAND(stopdemo, "", (clientcom *self), self->stopdemo());
+        CCOMMAND(cleardemos, "i", (clientcom *self, int *val), self->cleardemos(*val));
+
+        CCOMMAND(getmode, "", (clientcom *self), intret(self->cl.gamemode));
+        CCOMMAND(getname, "", (clientcom *self), result(self->player1->name));
+        CCOMMAND(getteam, "", (clientcom *self), result(self->player1->team));
+        CCOMMAND(getclientfocus, "", (clientcom *self), intret(self->getclientfocus()));
+        CCOMMAND(getclientname, "i", (clientcom *self, int *cn), result(self->getclientname(*cn)));
+        CCOMMAND(getclientnum, "s", (clientcom *self, char *name), intret(name[0] ? self->parseplayer(name) : self->player1->clientnum));
+        CCOMMAND(listclients, "i", (clientcom *self, int *local), self->listclients(*local!=0));
+    }
+
+    void switchname(const char *name)
+    {
+        if(name[0]) 
+        { 
+            c2sinit = false; 
+            filtertext(player1->name, name, false, MAXNAMELEN);
+            if(!player1->name[0]) s_strcpy(player1->name, "unnamed");
+        }
+        else conoutf("your name is: %s", cl.colorname(player1));
+    }
+
+    void switchteam(const char *team)
+    {
+        if(team[0]) 
+        { 
+            c2sinit = false; 
+            filtertext(player1->team, team, false, MAXTEAMLEN);
+        }
+        else conoutf("your team is: %s", player1->team);
+    }
+
+    int numchannels() { return 3; }
+
+    void mapstart() { if(!spectator || player1->privilege) senditemstoserver = true; }
+
+    void initclientnet()
+    {
+    }
+
+    void writeclientinfo(FILE *f)
+    {
+        fprintf(f, "name \"%s\"\nteam \"%s\"\n", player1->name, player1->team);
+    }
+
+    void gameconnect(bool _remote)
+    {
+        connected = true;
+        remote = _remote;
+        if(editmode) toggleedit();
+    }
+
+    void gamedisconnect()
+    {
+        if(remote) cl.stopfollowing();
+        connected = false;
+        player1->clientnum = -1;
+        c2sinit = false;
+        player1->lifesequence = 0;
+        player1->privilege = PRIV_NONE;
+        spectator = false;
+        loopv(cl.players) if(cl.players[i]) cl.clientdisconnected(i, false);
+    }
+
+    bool allowedittoggle()
+    {
+        bool allow = !connected || !remote || cl.gamemode==1;
+        if(!allow) conoutf(CON_ERROR, "editing in multiplayer requires coopedit mode (1)");
+        if(allow && spectator) return false;
+        return allow;
+    }
+
+    void edittoggled(bool on)
+    {
+        addmsg(SV_EDITMODE, "ri", on ? 1 : 0);
+        if(player1->state==CS_DEAD) cl.deathstate(player1, true);
+        else if(player1->state==CS_EDITING && player1->editstate==CS_DEAD) cl.sb.showscores(false);
+        setvar("zoom", -1, true);
+    }
+
+    int getclientfocus()
+    {
+        fpsent *d = cl.pointatplayer();
+        return d ? d->clientnum : -1;
+    }
+
+    const char *getclientname(int cn)
+    {
+        if(cn == cl.player1->clientnum) return cl.player1->name;
+
+        fpsent *d = cl.getclient(cn);
+        return d ? d->name : "";
+    }
+
+    int parseplayer(const char *arg)
+    {
+        char *end;
+        int n = strtol(arg, &end, 10);
+        if(*arg && !*end) 
+        {
+            if(n!=player1->clientnum && !cl.players.inrange(n)) return -1;
+            return n;
+        }
+        // try case sensitive first
+        loopi(cl.numdynents())
+        {
+            fpsent *o = (fpsent *)cl.iterdynents(i);
+            if(o && !strcmp(arg, o->name)) return o->clientnum;
+        }
+        // nothing found, try case insensitive
+        loopi(cl.numdynents())
+        {
+            fpsent *o = (fpsent *)cl.iterdynents(i);
+            if(o && !strcasecmp(arg, o->name)) return o->clientnum;
+        }
+        return -1;
+    }
+
+    void listclients(bool local)
+    {
+        vector<char> buf;
+        string cn;
+        int numclients = 0;
+        if(local)
+        {
+            s_sprintf(cn)("%d", player1->clientnum);
+            buf.put(cn, strlen(cn));
+            numclients++;
+        }
+        loopv(cl.players) if(cl.players[i])
+        {
+            s_sprintf(cn)("%d", cl.players[i]->clientnum);
+            if(numclients++) buf.add(' ');
+            buf.put(cn, strlen(cn));
+        }
+        buf.add('\0');
+        result(buf.getbuf());
+    }
+
+    void clearbans()
+    {
+        if(!remote) return;
+        addmsg(SV_CLEARBANS, "r");
+    }
+
+    void kick(const char *arg)
+    {
+        if(!remote) return;
+        int i = parseplayer(arg);
+        if(i>=0 && i!=player1->clientnum) addmsg(SV_KICK, "ri", i);
+    }
+
+    void setteam(const char *arg1, const char *arg2)
+    {
+        if(!remote) return;
+        int i = parseplayer(arg1);
+        if(i>=0 && i!=player1->clientnum) addmsg(SV_SETTEAM, "ris", i, arg2);
+    }
+
+    void setmaster(const char *arg)
+    {
+        if(!remote || !arg[0]) return;
+        int val = 1;
+        const char *passwd = "";
+        if(!arg[1] && isdigit(arg[0])) val = atoi(arg); 
+        else passwd = arg;
+        addmsg(SV_SETMASTER, "ris", val, passwd);
+    }
+
+    void approvemaster(const char *who)
+    {
+        if(!remote) return;
+        int i = parseplayer(who);
+        if(i>=0) addmsg(SV_APPROVEMASTER, "ri", i);
+    }
+
+    void togglespectator(int val, const char *who)
+    {
+        if(!remote) return;
+        int i = who[0] ? parseplayer(who) : player1->clientnum;
+        if(i>=0) addmsg(SV_SPECTATOR, "rii", i, val);
+    }
+
+    // collect c2s messages conveniently
+    vector<uchar> messages;
+
+    void addmsg(int type, const char *fmt = NULL, ...)
+    {
+        if(remote && spectator && (!player1->privilege || type<SV_MASTERMODE))
+        {
+            static int spectypes[] = { SV_MAPVOTE, SV_GETMAP, SV_TEXT, SV_SETMASTER };
+            bool allowed = false;
+            loopi(sizeof(spectypes)/sizeof(spectypes[0])) if(type==spectypes[i]) 
+            {
+                allowed = true;
+                break;
+            }
+            if(!allowed) return;
+        }
+        static uchar buf[MAXTRANS];
+        ucharbuf p(buf, MAXTRANS);
+        putint(p, type);
+        int numi = 1, nums = 0;
+        bool reliable = false;
+        if(fmt)
+        {
+            va_list args;
+            va_start(args, fmt);
+            while(*fmt) switch(*fmt++)
+            {
+                case 'r': reliable = true; break;
+                case 'v':
+                {
+                    int n = va_arg(args, int);
+                    int *v = va_arg(args, int *);
+                    loopi(n) putint(p, v[i]);
+                    numi += n;
+                    break;
+                }
+
+                case 'i': 
+                {
+                    int n = isdigit(*fmt) ? *fmt++-'0' : 1;
+                    loopi(n) putint(p, va_arg(args, int));
+                    numi += n;
+                    break;
+                }
+                case 's': sendstring(va_arg(args, const char *), p); nums++; break;
+            }
+            va_end(args);
+        } 
+        int num = nums?0:numi, msgsize = msgsizelookup(type);
+        if(msgsize && num!=msgsize) { s_sprintfd(s)("inconsistent msg size for %d (%d != %d)", type, num, msgsize); fatal(s); }
+        int len = p.length();
+        messages.add(len&0xFF);
+        messages.add((len>>8)|(reliable ? 0x80 : 0));
+        loopi(len) messages.add(buf[i]);
+    }
+
+    void toserver(char *text) { conoutf(CON_CHAT, "%s:\f0 %s", cl.colorname(player1), text); addmsg(SV_TEXT, "rs", text); }
+    void sayteam(char *text) { conoutf(CON_TEAMCHAT, "%s:\f1 %s", cl.colorname(player1), text); addmsg(SV_SAYTEAM, "rs", text); }
+
+    int sendpacketclient(ucharbuf &p, bool &reliable, dynent *d)
+    {
+        if(d->state==CS_ALIVE || d->state==CS_EDITING)
+        {
+            // send position updates separately so as to not stall out aiming
+            ENetPacket *packet = enet_packet_create(NULL, 100, 0);
+            ucharbuf q(packet->data, packet->dataLength);
+            putint(q, SV_POS);
+            putint(q, player1->clientnum);
+            putuint(q, (int)(d->o.x*DMF));              // quantize coordinates to 1/4th of a cube, between 1 and 3 bytes
+            putuint(q, (int)(d->o.y*DMF));
+            putuint(q, (int)(d->o.z*DMF));
+            putuint(q, (int)d->yaw);
+            putint(q, (int)d->pitch);
+            putint(q, (int)d->roll);
+            putint(q, (int)(d->vel.x*DVELF));          // quantize to itself, almost always 1 byte
+            putint(q, (int)(d->vel.y*DVELF));
+            putint(q, (int)(d->vel.z*DVELF));
+            putuint(q, d->physstate | (d->falling.x || d->falling.y ? 0x20 : 0) | (d->falling.z ? 0x10 : 0) | ((((fpsent *)d)->lifesequence&1)<<6));
+            if(d->falling.x || d->falling.y)
+            {
+                putint(q, (int)(d->falling.x*DVELF));      // quantize to itself, almost always 1 byte
+                putint(q, (int)(d->falling.y*DVELF));
+            }
+            if(d->falling.z) putint(q, (int)(d->falling.z*DVELF));
+            // pack rest in almost always 1 byte: strafe:2, move:2, garmour: 1, yarmour: 1, quad: 1
+            uint flags = (d->strafe&3) | ((d->move&3)<<2);
+            putuint(q, flags);
+            enet_packet_resize(packet, q.length());
+            sendpackettoserv(packet, 0);
+        }
+        if(senditemstoserver)
+        {
+            int gamemode = cl.gamemode;
+            reliable = !m_noitems || m_capture || m_ctf;
+            if(!m_noitems) cl.et.putitems(p, gamemode);
+            if(m_capture) cl.cpc.sendbases(p);
+            else if(m_ctf) cl.ctf.sendflags(p);
+            senditemstoserver = false;
+        }
+        if(!c2sinit)    // tell other clients who I am
+        {
+            reliable = true;
+            c2sinit = true;
+            putint(p, SV_INITC2S);
+            sendstring(player1->name, p);
+            sendstring(player1->team, p);
+        }
+        int i = 0;
+        while(i < messages.length()) // send messages collected during the previous frames
+        {
+            int len = messages[i] | ((messages[i+1]&0x7F)<<8);
+            if(p.remaining() < len) break;
+            if(messages[i+1]&0x80) reliable = true;
+            p.put(&messages[i+2], len);
+            i += 2 + len;
+        }
+        messages.remove(0, i);
+        if(!spectator && p.remaining()>=10 && cl.lastmillis-lastping>250)
+        {
+            putint(p, SV_PING);
+            putint(p, cl.lastmillis);
+            lastping = cl.lastmillis;
+        }
+        return 1;
+    }
+
+    void updatepos(fpsent *d)
+    {
+        // 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
+
+        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 && player1->state!=CS_SPECTATOR && 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 = cl.lastmillis-d->lastupdate;
+        if(lagtime)
+        {
+            if(d->state!=CS_SPAWNING && d->lastupdate) d->plag = (d->plag*5+lagtime)/6;
+            d->lastupdate = cl.lastmillis;
+        }
+    }
+
+    void parsepositions(ucharbuf &p)
+    {
+        int type;
+        while(p.remaining()) switch(type = getint(p))
+        {
+            case SV_POS:                        // position of another client
+            {
+                int cn = getint(p);
+                vec o, vel, falling;
+                float yaw, pitch, roll;
+                int physstate, f;
+                o.x = getuint(p)/DMF;
+                o.y = getuint(p)/DMF;
+                o.z = getuint(p)/DMF;
+                yaw = (float)getuint(p);
+                pitch = (float)getint(p);
+                roll = (float)getint(p);
+                vel.x = getint(p)/DVELF;
+                vel.y = getint(p)/DVELF;
+                vel.z = getint(p)/DVELF;
+                physstate = getuint(p);
+                falling = vec(0, 0, 0);
+                if(physstate&0x20)
+                {
+                    falling.x = getint(p)/DVELF;
+                    falling.y = getint(p)/DVELF;
+                }
+                if(physstate&0x10) falling.z = getint(p)/DVELF;
+                int seqcolor = (physstate>>6)&1;
+                f = getuint(p);
+                fpsent *d = cl.getclient(cn);
+                if(!d || seqcolor!=(d->lifesequence&1)) continue;
+                float oldyaw = d->yaw, oldpitch = d->pitch;
+                d->yaw = yaw;
+                d->pitch = pitch;
+                d->roll = roll;
+                d->strafe = (f&3)==3 ? -1 : f&3;
+                f >>= 2;
+                d->move = (f&3)==3 ? -1 : f&3;
+                vec oldpos(d->o);
+                if(cl.allowmove(d))
+                {
+                    d->o = o;
+                    d->vel = vel;
+                    d->falling = falling;
+                    d->physstate = physstate & 0x0F;
+                    updatephysstate(d);
+                    updatepos(d);
+                }
+                if(d->state==CS_DEAD)
+                {
+                    d->resetinterp();
+                    d->smoothmillis = 0;
+                }
+                else if(cl.smoothmove() && d->smoothmillis>=0 && oldpos.dist(d->o) < cl.smoothdist())
+                {
+                    d->newpos = d->o;
+                    d->newyaw = d->yaw;
+                    d->newpitch = d->pitch;
+                    d->o = oldpos;
+                    d->yaw = oldyaw;
+                    d->pitch = oldpitch;
+                    (d->deltapos = oldpos).sub(d->newpos);
+                    d->deltayaw = oldyaw - d->newyaw;
+                    if(d->deltayaw > 180) d->deltayaw -= 360;
+                    else if(d->deltayaw < -180) d->deltayaw += 360;
+                    d->deltapitch = oldpitch - d->newpitch;
+                    d->smoothmillis = cl.lastmillis;
+                }
+                else d->smoothmillis = 0;
+                if(d->state==CS_LAGGED || d->state==CS_SPAWNING) d->state = CS_ALIVE;
+                break;
+            }
+
+            default:
+                neterr("type");
+                return;
+        }
+    }
+
+    void parsepacketclient(int chan, ucharbuf &p)   // processes any updates from the server
+    {
+        switch(chan)
+        {
+            case 0: 
+                parsepositions(p); 
+                break;
+
+            case 1:
+                parsemessages(-1, NULL, p);
+                break;
+
+            case 2: 
+                receivefile(p.buf, p.maxlen);
+                break;
+        }
+    }
+
+    void parsestate(fpsent *d, ucharbuf &p, bool resume = false)
+    {
+        if(!d) { static fpsent dummy; d = &dummy; }
+        if(resume) 
+        {
+            if(d==cl.player1) getint(p);
+            else d->state = getint(p);
+            d->frags = getint(p);
+            if(d==cl.player1) getint(p);
+            else d->quadmillis = getint(p);
+        }
+        d->lifesequence = getint(p);
+        d->health = getint(p);
+        d->maxhealth = getint(p);
+        d->armour = getint(p);
+        d->armourtype = getint(p);
+        if(resume && d==cl.player1) 
+        {
+            getint(p);
+            loopi(GUN_PISTOL-GUN_SG+1) getint(p);
+        }
+        else
+        {
+            d->gunselect = getint(p);
+            loopi(GUN_PISTOL-GUN_SG+1) d->ammo[GUN_SG+i] = getint(p);
+        }
+    }
+
+    void parsemessages(int cn, fpsent *d, ucharbuf &p)
+    {
+        int gamemode = cl.gamemode;
+        static char text[MAXTRANS];
+        int type;
+        bool mapchanged = false;
+
+        while(p.remaining()) switch(type = getint(p))
+        {
+            case SV_INITS2C:                    // welcome messsage from the server
+            {
+                int mycn = getint(p), prot = getint(p), hasmap = getint(p);
+                if(prot!=PROTOCOL_VERSION)
+                {
+                    conoutf(CON_ERROR, "you are using a different game protocol (you: %d, server: %d)", PROTOCOL_VERSION, prot);
+                    disconnect();
+                    return;
+                }
+                player1->clientnum = mycn;      // we are now fully connected
+                if(!hasmap && (cl.gamemode==1 || cl.getclientmap()[0])) changemap(cl.getclientmap()); // we are the first client on this server, set map
+                gamemode = cl.gamemode;
+                break;
+            }
+
+            case SV_CLIENT:
+            {
+                int cn = getint(p), len = getuint(p);
+                ucharbuf q = p.subbuf(len);
+                parsemessages(cn, cl.getclient(cn), q);
+                break;
+            }
+
+            case SV_SOUND:
+                if(!d) return;
+                playsound(getint(p), &d->o);
+                break;
+
+            case SV_TEXT:
+            {
+                if(!d) return;
+                getstring(text, p);
+                filtertext(text, text);
+                if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR) 
+                {
+                    s_sprintfd(ds)("@%s", &text);
+                    particle_text(d->abovehead(), ds, 9);
+                }
+                conoutf(CON_CHAT, "%s:\f0 %s", cl.colorname(d), text);
+                break;
+            }
+
+            case SV_SAYTEAM:
+            {
+                int tcn = getint(p);
+                fpsent *t = tcn==cl.player1->clientnum ? cl.player1 : cl.getclient(tcn);
+                getstring(text, p);
+                filtertext(text, text);
+                if(!t) break;
+                if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR)
+                {
+                    s_sprintfd(ts)("@%s", &text);
+                    particle_text(t->abovehead(), ts, 34);
+                }
+                conoutf(CON_TEAMCHAT, "%s:\f1 %s", cl.colorname(t), text);
+                break;
+            }
+
+            case SV_MAPCHANGE:
+                getstring(text, p);
+                changemapserv(text, getint(p));
+                mapchanged = true;
+                gamemode = cl.gamemode;
+                if(getint(p)) cl.et.spawnitems(gamemode);
+                else senditemstoserver = false;
+                break;
+
+            case SV_ARENAWIN:
+            {
+                int acn = getint(p);
+                fpsent *alive = acn<0 ? NULL : (acn==player1->clientnum ? player1 : cl.getclient(acn));
+                conoutf(CON_GAMEINFO, "arena round is over! next round in 5 seconds...");
+                if(!alive) conoutf(CON_GAMEINFO, "everyone died!");
+                else if(m_teammode) conoutf(CON_GAMEINFO, "team %s has won the round", alive->team);
+                else if(alive==player1) conoutf(CON_GAMEINFO, "you are the last man standing!");
+                else conoutf(CON_GAMEINFO, "%s is the last man standing", cl.colorname(alive));
+                break;
+            }
+
+            case SV_FORCEDEATH:
+            {
+                int cn = getint(p);
+                fpsent *d = cn==player1->clientnum ? player1 : cl.newclient(cn);
+                if(!d) break;
+                if(d==player1)
+                {
+                    if(editmode) toggleedit();
+                    cl.stopfollowing();
+                    cl.sb.showscores(true);
+                }
+                else d->resetinterp();
+                d->state = CS_DEAD;
+                break;
+            }
+
+            case SV_ITEMLIST:
+            {
+                int n;
+                while((n = getint(p))>=0 && !p.overread())
+                {
+                    if(mapchanged) cl.et.setspawn(n, true);
+                    getint(p); // type
+                }
+                break;
+            }
+
+            case SV_MAPRELOAD:          // server requests next map
+            {
+                s_sprintfd(nextmapalias)("nextmap_%s%s", m_capture ? "capture_" : (m_ctf ? "ctf_" : ""), cl.getclientmap());
+                const char *map = getalias(nextmapalias);     // look up map in the cycle
+                addmsg(SV_MAPCHANGE, "rsi", *map ? map : cl.getclientmap(), cl.nextmode);
+                break;
+            }
+
+            case SV_INITC2S:            // another client either connected or changed name/team
+            {
+                d = cl.newclient(cn);
+                if(!d)
+                {
+                    getstring(text, p);
+                    getstring(text, p);
+                    break;
+                }
+                getstring(text, p);
+                filtertext(text, text, false, MAXNAMELEN);
+                if(!text[0]) s_strcpy(text, "unnamed");
+                if(d->name[0])          // already connected
+                {
+                    if(strcmp(d->name, text))
+                    {
+                        string oldname, newname;
+                        s_strcpy(oldname, cl.colorname(d));
+                        s_strcpy(newname, cl.colorname(d, text));
+                        conoutf("%s is now known as %s", oldname, newname);
+                    }
+                }
+                else                    // new client
+                {
+                    c2sinit = false;    // send new players my info again
+                    conoutf("connected: %s", cl.colorname(d, text));
+                    loopv(cl.players)   // clear copies since new player doesn't have them
+                        if(cl.players[i]) freeeditinfo(cl.players[i]->edit);
+                    extern editinfo *localedit;
+                    freeeditinfo(localedit);
+                }
+                s_strncpy(d->name, text, MAXNAMELEN+1);
+                getstring(text, p);
+                filtertext(d->team, text, false, MAXTEAMLEN);
+                break;
+            }
+
+            case SV_CDIS:
+                cl.clientdisconnected(getint(p));
+                break;
+
+            case SV_SPAWN:
+            {
+                if(d) d->respawn();
+                parsestate(d, p);
+                if(!d) break;
+                d->state = CS_SPAWNING;
+                if(cl.player1->state==CS_SPECTATOR && cl.following==d->clientnum)
+                    cl.lasthit = 0;
+                break;
+            }
+
+            case SV_SPAWNSTATE:
+            {
+                if(editmode) toggleedit();
+                cl.stopfollowing();
+                player1->respawn();
+                parsestate(player1, p);
+                player1->state = CS_ALIVE;
+                findplayerspawn(player1, m_capture ? cl.cpc.pickspawn(player1->team) : -1, m_ctf ? ctfteamflag(player1->team) : 0);
+                cl.sb.showscores(false);
+                cl.lasthit = 0;
+                if(m_arena) conoutf(CON_GAMEINFO, "new round starting... fight!");
+                else if(m_capture) cl.cpc.lastrepammo = -1;
+                addmsg(SV_SPAWN, "rii", player1->lifesequence, player1->gunselect);
+                break;
+            }
+
+            case SV_SHOTFX:
+            {
+                int scn = getint(p), gun = getint(p);
+                vec from, to;
+                loopk(3) from[k] = getint(p)/DMF;
+                loopk(3) to[k] = getint(p)/DMF;
+                fpsent *s = cl.getclient(scn);
+                if(!s) break;
+                if(gun>GUN_FIST && gun<=GUN_PISTOL && s->ammo[gun]) s->ammo[gun]--; 
+                if(gun==GUN_SG) cl.ws.createrays(from, to);
+                s->gunselect = clamp(gun, (int)GUN_FIST, (int)GUN_PISTOL);
+                s->gunwait = guns[s->gunselect].attackdelay;
+                s->lastaction = cl.lastmillis;
+                s->lastattackgun = s->gunselect;
+                cl.ws.shootv(gun, from, to, s, false);
+                break;
+            }
+
+            case SV_DAMAGE:
+            {
+                int tcn = getint(p), 
+                    acn = getint(p),
+                    damage = getint(p), 
+                    armour = getint(p),
+                    health = getint(p);
+                fpsent *target = tcn==player1->clientnum ? player1 : cl.getclient(tcn),
+                       *actor = acn==player1->clientnum ? player1 : cl.getclient(acn);
+                if(!target || !actor) break;
+                target->armour = armour;
+                target->health = health;
+                cl.damaged(damage, target, actor, false);
+                break;
+            }
+
+            case SV_HITPUSH:
+            {
+                int gun = getint(p), damage = getint(p);
+                vec dir;
+                loopk(3) dir[k] = getint(p)/DNF;
+                player1->hitpush(damage, dir, NULL, gun);
+                break;
+            }
+
+            case SV_DIED:
+            {
+                int vcn = getint(p), acn = getint(p), frags = getint(p);
+                fpsent *victim = vcn==player1->clientnum ? player1 : cl.getclient(vcn),
+                       *actor = acn==player1->clientnum ? player1 : cl.getclient(acn);
+                if(!actor) break;
+                actor->frags = frags;
+                if(actor!=player1 && !m_capture && !m_ctf)
+                {
+                    s_sprintfd(ds)("@%d", actor->frags);
+                    particle_text(actor->abovehead(), ds, 9);
+                }
+                if(!victim) break;
+                cl.killed(victim, actor);
+                break;
+            }
+
+            case SV_GUNSELECT:
+            {
+                if(!d) return;
+                int gun = getint(p);
+                d->gunselect = max(gun, 0);
+                playsound(S_WEAPLOAD, &d->o);
+                break;
+            }
+
+            case SV_TAUNT:
+            {
+                if(!d) return;
+                d->lasttaunt = cl.lastmillis;
+                break;
+            }
+
+            case SV_RESUME:
+            {
+                for(;;)
+                {
+                    int cn = getint(p);
+                    if(p.overread() || cn<0) break;
+                    fpsent *d = (cn == player1->clientnum ? player1 : cl.newclient(cn));
+                    parsestate(d, p, true);
+                }
+                break;
+            }
+
+            case SV_ITEMSPAWN:
+            {
+                int i = getint(p);
+                if(!cl.et.ents.inrange(i)) break;
+                cl.et.setspawn(i, true);
+                playsound(S_ITEMSPAWN, &cl.et.ents[i]->o);
+                const char *name = cl.et.itemname(i);
+                if(name) particle_text(cl.et.ents[i]->o, name, 9);
+                break;
+            }
+
+            case SV_ITEMACC:            // server acknowledges that I picked up this item
+            {
+                int i = getint(p), cn = getint(p);
+                fpsent *d = cn==player1->clientnum ? player1 : cl.getclient(cn);
+                cl.et.pickupeffects(i, d);
+                break;
+            }
+
+            case SV_EDITF:              // coop editing messages
+            case SV_EDITT:
+            case SV_EDITM:
+            case SV_FLIP:
+            case SV_COPY:
+            case SV_PASTE:
+            case SV_ROTATE:
+            case SV_REPLACE:
+            case SV_DELCUBE:
+            {
+                if(!d) return;
+                selinfo sel;
+                sel.o.x = getint(p); sel.o.y = getint(p); sel.o.z = getint(p);
+                sel.s.x = getint(p); sel.s.y = getint(p); sel.s.z = getint(p);
+                sel.grid = getint(p); sel.orient = getint(p);
+                sel.cx = getint(p); sel.cxs = getint(p); sel.cy = getint(p), sel.cys = getint(p);
+                sel.corner = getint(p);
+                int dir, mode, tex, newtex, mat, allfaces;
+                ivec moveo;
+                switch(type)
+                {
+                    case SV_EDITF: dir = getint(p); mode = getint(p); mpeditface(dir, mode, sel, false); break;
+                    case SV_EDITT: tex = getint(p); allfaces = getint(p); mpedittex(tex, allfaces, sel, false); break;
+                    case SV_EDITM: mat = getint(p); mpeditmat(mat, sel, false); break;
+                    case SV_FLIP: mpflip(sel, false); break;
+                    case SV_COPY: if(d) mpcopy(d->edit, sel, false); break;
+                    case SV_PASTE: if(d) mppaste(d->edit, sel, false); break;
+                    case SV_ROTATE: dir = getint(p); mprotate(dir, sel, false); break;
+                    case SV_REPLACE: tex = getint(p); newtex = getint(p); mpreplacetex(tex, newtex, sel, false); break;
+                    case SV_DELCUBE: mpdelcube(sel, false); break;
+                }
+                break;
+            }
+            case SV_REMIP:
+            {
+                if(!d) return;
+                conoutf("%s remipped", cl.colorname(d));
+                mpremip(false);
+                break;
+            }
+            case SV_EDITENT:            // coop edit of ent
+            {
+                if(!d) return;
+                int i = getint(p);
+                float x = getint(p)/DMF, y = getint(p)/DMF, z = getint(p)/DMF;
+                int type = getint(p);
+                int attr1 = getint(p), attr2 = getint(p), attr3 = getint(p), attr4 = getint(p);
+
+                mpeditent(i, vec(x, y, z), type, attr1, attr2, attr3, attr4, false);
+                break;
+            }
+
+            case SV_PONG:
+                addmsg(SV_CLIENTPING, "i", player1->ping = (player1->ping*5+cl.lastmillis-getint(p))/6);
+                break;
+
+            case SV_CLIENTPING:
+                if(!d) return;
+                d->ping = getint(p);
+                break;
+
+            case SV_TIMEUP:
+                cl.timeupdate(getint(p));
+                break;
+
+            case SV_SERVMSG:
+                getstring(text, p);
+                conoutf("%s", text);
+                break;
+
+            case SV_SENDDEMOLIST:
+            {
+                int demos = getint(p);
+                if(!demos) conoutf("no demos available");
+                else loopi(demos)
+                {
+                    getstring(text, p);
+                    conoutf("%d. %s", i+1, text);
+                }
+                break;
+            }
+
+            case SV_DEMOPLAYBACK:
+            {
+                int on = getint(p);
+                if(on) player1->state = CS_SPECTATOR;
+                else stopdemo();
+                demoplayback = on!=0;
+                const char *alias = on ? "demostart" : "demoend";
+                if(identexists(alias)) execute(alias);
+                break;
+            }
+
+            case SV_CURRENTMASTER:
+            {
+                int mn = getint(p), priv = getint(p);
+                player1->privilege = PRIV_NONE;
+                loopv(cl.players) if(cl.players[i]) cl.players[i]->privilege = PRIV_NONE;
+                if(mn>=0)
+                {
+                    fpsent *m = mn==player1->clientnum ? player1 : cl.newclient(mn);
+                    m->privilege = priv;
+                }
+                break;
+            }
+
+            case SV_EDITMODE:
+            {
+                int val = getint(p);
+                if(!d) break;
+                if(val) 
+                {
+                    d->editstate = d->state;
+                    d->state = CS_EDITING;
+                }
+                else 
+                {
+                    d->state = d->editstate;
+                    if(d->state==CS_DEAD) cl.deathstate(d, true);
+                }
+                break;
+            }
+
+            case SV_SPECTATOR:
+            {
+                int sn = getint(p), val = getint(p);
+                fpsent *s;
+                if(sn==player1->clientnum)
+                {
+                    spectator = val!=0;
+                    s = player1;
+                }
+                else s = cl.newclient(sn);
+                if(!s) return;
+                if(val)
+                {
+                    if(s==player1)
+                    {
+                        if(editmode) toggleedit();
+                        if(s->state==CS_DEAD) cl.sb.showscores(false);
+                    }
+                    s->state = CS_SPECTATOR;
+                }
+                else if(s->state==CS_SPECTATOR) 
+                {
+                    s->state = CS_DEAD;
+                    if(s==player1) 
+                    {
+                        cl.stopfollowing();
+                        cl.sb.showscores(true);
+                    }
+                    else s->resetinterp();
+                }
+                break;
+            }
+
+            case SV_SETTEAM:
+            {
+                int wn = getint(p);
+                getstring(text, p);
+                fpsent *w = wn==player1->clientnum ? player1 : cl.getclient(wn);
+                if(!w) return;
+                filtertext(w->team, text, false, MAXTEAMLEN);
+                break;
+            }
+
+            case SV_BASEINFO:
+            {
+                int base = getint(p);
+                string owner, enemy;
+                getstring(text, p);
+                s_strcpy(owner, text);
+                getstring(text, p);
+                s_strcpy(enemy, text);
+                int converted = getint(p), ammo = getint(p);
+                if(m_capture) cl.cpc.updatebase(base, owner, enemy, converted, ammo);
+                break;
+            }
+
+            case SV_BASEREGEN:
+            {
+                int health = getint(p), armour = getint(p), ammotype = getint(p), ammo = getint(p);
+                if(m_capture)
+                {
+                    player1->health = health;
+                    player1->armour = armour;
+                    if(ammotype>=GUN_SG && ammotype<=GUN_PISTOL) player1->ammo[ammotype] = ammo;
+                }
+                break;
+            }
+
+            case SV_BASES:
+            {
+                int numbases = getint(p);
+                loopi(numbases)
+                {
+                    int ammotype = getint(p);
+                    string owner, enemy;
+                    getstring(text, p);
+                    s_strcpy(owner, text);
+                    getstring(text, p);
+                    s_strcpy(enemy, text);
+                    int converted = getint(p), ammo = getint(p);
+                    cl.cpc.initbase(i, ammotype, owner, enemy, converted, ammo);
+                }
+                break;
+            }
+
+            case SV_TEAMSCORE:
+            {
+                getstring(text, p);
+                int total = getint(p);
+                if(m_capture) cl.cpc.setscore(text, total);
+                break;
+            }
+
+            case SV_REPAMMO:
+            {
+                int ammotype = getint(p);
+                if(m_capture) cl.cpc.receiveammo(ammotype);
+                break;
+            }
+
+            case SV_INITFLAGS:
+            {
+                cl.ctf.parseflags(p, m_ctf);
+                break;
+            }
+
+            case SV_DROPFLAG:
+            {
+                int ocn = getint(p), flag = getint(p);
+                vec droploc;
+                loopk(3) droploc[k] = getint(p)/DMF;
+                fpsent *o = ocn==cl.player1->clientnum ? cl.player1 : cl.newclient(ocn);  
+                if(m_ctf) cl.ctf.dropflag(o, flag, droploc);
+                break;
+            }
+
+            case SV_SCOREFLAG:
+            {
+                int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), score = getint(p);
+                fpsent *o = ocn==cl.player1->clientnum ? cl.player1 : cl.newclient(ocn);
+                if(m_ctf) cl.ctf.scoreflag(o, relayflag, goalflag, score);
+                break;
+            }
+
+            case SV_RETURNFLAG:
+            {
+                int ocn = getint(p), flag = getint(p);
+                fpsent *o = ocn==cl.player1->clientnum ? cl.player1 : cl.newclient(ocn);
+                if(m_ctf) cl.ctf.returnflag(o, flag);
+                break;
+            }
+
+            case SV_TAKEFLAG:
+            {
+                int ocn = getint(p), flag = getint(p);
+                fpsent *o = ocn==cl.player1->clientnum ? cl.player1 : cl.newclient(ocn);
+                if(m_ctf) cl.ctf.takeflag(o, flag);
+                break;
+            }
+
+            case SV_RESETFLAG:
+            {
+                int flag = getint(p);
+                if(m_ctf) cl.ctf.resetflag(flag);
+                break;
+            }
+
+            case SV_ANNOUNCE:
+            {
+                int t = getint(p);
+                if     (t==I_QUAD)  { playsound(S_V_QUAD10);  conoutf(CON_GAMEINFO, "\f2quad damage will spawn in 10 seconds!"); }
+                else if(t==I_BOOST) { playsound(S_V_BOOST10); conoutf(CON_GAMEINFO, "\f2+10 health will spawn in 10 seconds!"); }
+                break;
+            }
+
+            case SV_CLEARTARGETS:
+                if(m_assassin) cl.asc.targets.setsize(0);
+                break;
+
+            case SV_CLEARHUNTERS:
+                if(m_assassin) cl.asc.hunters.setsize(0);
+                break;
+
+            case SV_ADDTARGET:
+            {
+                int tcn = getint(p);
+                if(m_assassin) 
+                {
+                    fpsent *t = cl.newclient(tcn);
+                    if(cl.asc.targets.find(t)<0) cl.asc.targets.add(t);
+                }
+                break;
+            }
+
+            case SV_REMOVETARGET:
+            {
+                int tcn = getint(p);
+                if(m_assassin)
+                {
+                    fpsent *t = cl.getclient(tcn);
+                    if(t) cl.asc.targets.removeobj(t);
+                }
+                break;
+            }
+
+            case SV_ADDHUNTER:
+            {
+                int hcn = getint(p);
+                if(m_assassin)
+                {
+                    fpsent *h = cl.newclient(hcn);
+                    if(cl.asc.hunters.find(h)<0) cl.asc.hunters.add(h);
+                }
+                break;
+            }
+
+            case SV_REMOVEHUNTER:
+            {
+                int hcn = getint(p);
+                if(m_assassin)
+                {
+                    fpsent *h = cl.getclient(hcn);
+                    if(h) cl.asc.hunters.removeobj(h);
+                }
+                break;
+            }    
+
+            case SV_NEWMAP:
+            {
+                int size = getint(p);
+                if(size>=0) emptymap(size, true);
+                else enlargemap(true);
+                if(d && d!=player1)
+                {
+                    int newsize = 0;
+                    while(1<<newsize < getworldsize()) newsize++;
+                    conoutf(size>=0 ? "%s started a new map of size %d" : "%s enlarged the map to size %d", cl.colorname(d), newsize);
+                }
+                break;
+            }
+
+            default:
+                neterr("type");
+                return;
+        }
+    }
+
+    void changemapserv(const char *name, int gamemode)        // forced map change from the server
+    {
+        if(remote && !m_mp(gamemode)) gamemode = 0;
+        cl.gamemode = gamemode;
+        cl.nextmode = gamemode;
+        cl.minremain = -1;
+        if(editmode) toggleedit();
+        if(m_demo) { cl.et.resetspawns(); return; }
+        if((gamemode==1 && !name[0]) || (!load_world(name) && remote)) 
+        {
+            emptymap(0, true, name);
+            senditemstoserver = false;
+        }
+        if(m_capture) cl.cpc.setupbases();
+        else if(m_assassin) cl.asc.reset();
+        else if(m_ctf) cl.ctf.setupflags();
+    }
+
+    void changemap(const char *name) // request map change, server may ignore
+    {
+        if(spectator && !player1->privilege) return;
+        int nextmode = cl.nextmode; // in case stopdemo clobbers cl.nextmode
+        if(!remote) stopdemo();
+        addmsg(SV_MAPVOTE, "rsi", name, nextmode);
+    }
+        
+    void receivefile(uchar *data, int len)
+    {
+        ucharbuf p(data, len);
+        int type = getint(p);
+        data += p.length();
+        len -= p.length();
+        switch(type)
+        {
+            case SV_SENDDEMO:
+            {
+                s_sprintfd(fname)("%d.dmo", cl.lastmillis);
+                FILE *demo = openfile(fname, "wb");
+                if(!demo) return;
+                conoutf("received demo \"%s\"", fname);
+                fwrite(data, 1, len, demo);
+                fclose(demo);
+                break;
+            }
+
+            case SV_SENDMAP:
+            {
+                if(cl.gamemode!=1) return;
+                string oldname;
+                s_strcpy(oldname, cl.getclientmap());
+                s_sprintfd(mname)("getmap_%d", cl.lastmillis);
+                s_sprintfd(fname)("packages/base/%s.ogz", mname);
+                const char *file = findfile(fname, "wb");
+                FILE *map = fopen(file, "wb");
+                if(!map) return;
+                conoutf("received map");
+                fwrite(data, 1, len, map);
+                fclose(map);
+                load_world(mname, oldname[0] ? oldname : NULL);
+                remove(file);
+                break;
+            }
+        }
+    }
+
+    void getmap()
+    {
+        if(cl.gamemode!=1) { conoutf(CON_ERROR, "\"getmap\" only works in coopedit mode"); return; }
+        conoutf("getting map...");
+        addmsg(SV_GETMAP, "r");
+    }
+
+    void stopdemo()
+    {
+        if(remote)
+        {
+            if(player1->privilege<PRIV_ADMIN) return;
+            addmsg(SV_STOPDEMO, "r");
+        }
+        else
+        {
+            loopv(cl.players) if(cl.players[i]) cl.clientdisconnected(i);
+
+            extern igameserver *sv;
+            ((fpsserver *)sv)->enddemoplayback();
+        }
+    }
+
+    void recorddemo(int val)
+    {
+        if(player1->privilege<PRIV_ADMIN) return;
+        addmsg(SV_RECORDDEMO, "ri", val);
+    }
+
+    void cleardemos(int val)
+    {
+        if(player1->privilege<PRIV_ADMIN) return;
+        addmsg(SV_CLEARDEMOS, "ri", val);
+    }
+
+    void getdemo(int i)
+    {
+        if(i<=0) conoutf("getting demo...");
+        else conoutf("getting demo %d...", i);
+        addmsg(SV_GETDEMO, "ri", i);
+    }
+
+    void listdemos()
+    {
+        conoutf("listing demos...");
+        addmsg(SV_LISTDEMOS, "r");
+    }
+
+    void sendmap()
+    {
+        if(cl.gamemode!=1 || (spectator && !player1->privilege)) { conoutf(CON_ERROR, "\"sendmap\" only works in coopedit mode"); return; }
+        conoutf("sending map...");
+        s_sprintfd(mname)("sendmap_%d", cl.lastmillis);
+        save_world(mname, true);
+        s_sprintfd(fname)("packages/base/%s.ogz", mname);
+        const char *file = findfile(fname, "rb");
+        FILE *map = fopen(file, "rb");
+        if(map)
+        {
+            fseek(map, 0, SEEK_END);
+            int len = ftell(map);
+            if(len > 1024*1024) conoutf(CON_ERROR, "map is too large");
+            else if(!len) conoutf(CON_ERROR, "could not read map");
+            else sendfile(-1, 2, map);
+            fclose(map);
+        }
+        else conoutf(CON_ERROR, "could not read map");
+        remove(file);
+    }
+
+    void gotoplayer(const char *arg)
+    {
+        if(player1->state!=CS_SPECTATOR && player1->state!=CS_EDITING) return;
+        int i = parseplayer(arg);
+        if(i>=0 && i!=player1->clientnum) 
+        {
+            fpsent *d = cl.getclient(i);
+            if(!d) return;
+            player1->o = d->o;
+            vec dir;
+            vecfromyawpitch(player1->yaw, player1->pitch, 1, 0, dir);
+            player1->o.add(dir.mul(-32));
+            player1->resetinterp();
+        }
+    }
+};
diff --git a/fpsgame/ctf.h b/fpsgame/ctf.h
index 192b79e..2f99f74 100644
--- a/fpsgame/ctf.h
+++ b/fpsgame/ctf.h
@@ -1,32 +1,25 @@
-#ifndef PARSEMESSAGES
-
 #define ctfteamflag(s) (!strcmp(s, "good") ? 1 : (!strcmp(s, "evil") ? 2 : 0))
 #define ctfflagteam(i) (i==1 ? "good" : (i==2 ? "evil" : NULL))
-
-#ifdef SERVMODE
-struct ctfservmode : servmode
-#else
-struct ctfclientmode : clientmode
-#endif
+ 
+struct ctfstate
 {
-    static const int MAXFLAGS = 20;
+    static const int MAXFLAGS = 100;
     static const int FLAGRADIUS = 16;
     static const int FLAGLIMIT = 10;
 
     struct flag
     {
-        int id;
         vec droploc, spawnloc;
-        int team, droptime;
-#ifdef SERVMODE
-        int owner, invistime;
+        int team, score, droptime;
+#ifdef CTFSERV
+        int owner;
 #else
+        bool pickup;
         fpsent *owner;
-        float dropangle, spawnangle;
-        entitylight light;
+        extentity *ent;
         vec interploc;
         float interpangle;
-        int interptime, vistime;
+        int interptime;
 #endif
 
         flag() { reset(); }
@@ -34,154 +27,95 @@ struct ctfclientmode : clientmode
         void reset()
         {
             droploc = spawnloc = vec(0, 0, 0);
-#ifdef SERVMODE
+#ifdef CTFSERV
             owner = -1;
-            invistime = 0;
 #else
-            loopv(players) players[i]->flagpickup &= ~(1<<id);
+            pickup = false;
             owner = NULL;
-            dropangle = spawnangle = 0;
             interploc = vec(0, 0, 0);
             interpangle = 0;
             interptime = 0;
-            vistime = -1000;
 #endif
             team = 0;
+            score = 0;
             droptime = 0;
         }
-
-#ifndef SERVMODE
-        const vec &pos()
-        {
-        	if(owner) return vec(owner->o).sub(owner->eyeheight);
-        	if(droptime) return droploc;
-        	return spawnloc;
-        }
-#endif
     };
     vector<flag> flags;
-    int scores[2];
 
-    void resetflags()
+    void reset()
     {
         flags.setsize(0);
-        loopk(2) scores[k] = 0;
     }
 
-#ifdef SERVMODE
-    void addflag(int i, const vec &o, int team, int invistime = 0)
-#else
-    void addflag(int i, const vec &o, int team, int vistime = -1000)
-#endif
+    void addflag(int i, const vec &o, int team)
     {
         if(i<0 || i>=MAXFLAGS) return;
         while(flags.length()<=i) flags.add();
         flag &f = flags[i];
         f.reset();
-        f.id = i;
         f.team = team;
         f.spawnloc = o;
-#ifdef SERVMODE
-        f.invistime = invistime;
-#else
-        f.vistime = vistime;
-#endif
     }
 
-#ifdef SERVMODE
-    void ownflag(int i, int owner)
+#ifdef CTFSERV
+    void takeflag(int i, int owner)
 #else
-    void ownflag(int i, fpsent *owner, int owntime)
+    void takeflag(int i, fpsent *owner)
 #endif
     {
         flag &f = flags[i];
         f.owner = owner;
-#ifdef SERVMODE
-        f.invistime = 0;
-#else
-        loopv(players) players[i]->flagpickup &= ~(1<<f.id);
-        if(!f.vistime) f.vistime = owntime;
+#ifndef CTFSERV
+        f.pickup = false;
 #endif
     }
 
-#ifdef SERVMODE
     void dropflag(int i, const vec &o, int droptime)
-#else
-    void dropflag(int i, const vec &o, float yaw, int droptime)
-#endif
     {
         flag &f = flags[i];
         f.droploc = o;
         f.droptime = droptime;
-#ifdef SERVMODE
+#ifdef CTFSERV
         f.owner = -1;
-        f.invistime = 0;
 #else
-        loopv(players) players[i]->flagpickup &= ~(1<<f.id);
+        f.pickup = false;
         f.owner = NULL;
-        f.dropangle = yaw;
-        if(!f.vistime) f.vistime = droptime;
 #endif
     }
 
-#ifdef SERVMODE
-    void returnflag(int i, int invistime = 0)
-#else
-    void returnflag(int i, int vistime = -1000)
-#endif
+    void returnflag(int i)
     {
         flag &f = flags[i];
         f.droptime = 0;
-#ifdef SERVMODE
+#ifdef CTFSERV
         f.owner = -1;
-        f.invistime = invistime;
 #else
-        loopv(players) players[i]->flagpickup &= ~(1<<f.id);
-        f.vistime = vistime;
+        f.pickup = false;
         f.owner = NULL;
 #endif
     }
 
     int totalscore(int team)
     {
-        return team >= 1 && team <= 2 ? scores[team-1] : 0;
-    }
-
-    int setscore(int team, int score)
-    {
-        if(team >= 1 && team <= 2) return scores[team-1] = score;
-        return 0;
-    }
-
-    int addscore(int team, int score)
-    {
-        if(team >= 1 && team <= 2) return scores[team-1] += score;
-        return 0;
-    }
-
-    bool hidefrags() { return true; }
-
-    int getteamscore(const char *team)
-    {
-        return totalscore(ctfteamflag(team));
-    }
-
-    void getteamscores(vector<teamscore> &tscores)
-    {
-        loopk(2) if(scores[k]) tscores.add(teamscore(ctfflagteam(k+1), scores[k]));
+        int score = 0;
+        loopv(flags) if(flags[i].team==team) score += flags[i].score;
+        return score;
     }
+};
 
-#ifdef SERVMODE
+#ifdef CTFSERV
+struct ctfservmode : ctfstate, servmode
+{
     static const int RESETFLAGTIME = 10000;
-    static const int INVISFLAGTIME = 15000;
 
     bool notgotflags;
 
-    ctfservmode() : notgotflags(false) {}
+    ctfservmode(fpsserver &sv) : servmode(sv), notgotflags(false) {}
 
     void reset(bool empty)
     {
-        resetflags();
+        ctfstate::reset();
         notgotflags = !empty;
     }
 
@@ -191,10 +125,10 @@ struct ctfclientmode : clientmode
         loopv(flags) if(flags[i].owner==ci->clientnum)
         {
             ivec o(vec(ci->state.o).mul(DMF));
-            sendf(-1, 1, "ri6", SV_DROPFLAG, ci->clientnum, i, o.x, o.y, o.z);
-            dropflag(i, o.tovec().div(DMF), lastmillis);
+            sendf(-1, 1, "ri6", SV_DROPFLAG, ci->clientnum, i, o.x, o.y, o.z); 
+            ctfstate::dropflag(i, o.tovec().div(DMF), sv.lastmillis);
         }
-    }
+    } 
 
     void leavegame(clientinfo *ci, bool disconnecting = false)
     {
@@ -208,7 +142,7 @@ struct ctfclientmode : clientmode
 
     bool canchangeteam(clientinfo *ci, const char *oldteam, const char *newteam)
     {
-        return ctfteamflag(newteam) > 0;
+        return !strcmp(newteam, "good") || !strcmp(newteam, "evil");
     }
 
     void changeteam(clientinfo *ci, const char *oldteam, const char *newteam)
@@ -216,71 +150,71 @@ struct ctfclientmode : clientmode
         dropflag(ci);
     }
 
-    void scoreflag(clientinfo *ci, int goal, int relay = -1)
+    void moved(clientinfo *ci, const vec &oldpos, const vec &newpos)
     {
-        returnflag(relay >= 0 ? relay : goal, relay >= 0 ? 0 : lastmillis);
-        ci->state.flags++;
-        int team = ctfteamflag(ci->team), score = addscore(team, 1);
-        sendf(-1, 1, "ri6", SV_SCOREFLAG, ci->clientnum, relay, goal, team, score);
-        if(score >= FLAGLIMIT) startintermission();
+        if(notgotflags) return;
+        static const dynent dummy;
+        vec o(newpos);
+        o.z -= dummy.eyeheight;
+        int relay = -1;
+        loopv(flags) if(flags[i].owner==ci->clientnum) { relay = i; break; } 
+        if(relay<0) return;
+        loopv(flags) if(flags[i].team==ctfteamflag(ci->team)) 
+        {
+            flag &goal = flags[i];
+            if(goal.owner<0 && !goal.droptime && o.dist(goal.spawnloc) < FLAGRADIUS)
+            {
+                returnflag(relay);
+                goal.score++;
+                sendf(-1, 1, "ri5", SV_SCOREFLAG, ci->clientnum, relay, i, goal.score);
+                if(totalscore(goal.team) >= FLAGLIMIT) sv.startintermission();
+            }
+        }
     }
 
     void takeflag(clientinfo *ci, int i)
     {
         if(notgotflags || !flags.inrange(i) || ci->state.state!=CS_ALIVE || !ci->team[0]) return;
         flag &f = flags[i];
-        if(!ctfflagteam(f.team) || f.owner>=0) return;
-        int team = ctfteamflag(ci->team);
-        if(m_protect == (f.team==team))
+        if(!ctfflagteam(f.team)) return;
+        if(f.team==ctfteamflag(ci->team))
         {
-            loopvj(flags) if(flags[j].owner==ci->clientnum) return;
-            ownflag(i, ci->clientnum);
-            sendf(-1, 1, "ri3", SV_TAKEFLAG, ci->clientnum, i);
-        }
-        else if(m_protect)
-        {
-            if(!f.invistime) scoreflag(ci, i);
-        }
-        else if(f.droptime)
-        {
-            returnflag(i);
+            if(!f.droptime || f.owner>=0) return;
+            ctfstate::returnflag(i);
             sendf(-1, 1, "ri3", SV_RETURNFLAG, ci->clientnum, i);
         }
         else
         {
-            loopvj(flags) if(flags[j].owner==ci->clientnum) { scoreflag(ci, i, j); break; }
+            if(f.owner>=0) return;
+            loopv(flags) if(flags[i].owner==ci->clientnum) return;
+            ctfstate::takeflag(i, ci->clientnum);
+            sendf(-1, 1, "ri3", SV_TAKEFLAG, ci->clientnum, i);
         }
     }
 
     void update()
     {
-        if(minremain<=0 || notgotflags) return;
-        loopv(flags)
+        if(sv.minremain<0 || notgotflags) return;
+        loopv(flags) 
         {
             flag &f = flags[i];
-            if(f.owner<0 && f.droptime && lastmillis - f.droptime >= RESETFLAGTIME)
+            if(f.owner<0 && f.droptime && sv.lastmillis - f.droptime >= RESETFLAGTIME)
             {
-                returnflag(i, m_protect ? lastmillis : 0);
-                sendf(-1, 1, "ri4", SV_RESETFLAG, i, f.team, addscore(f.team, m_protect ? -1 : 0));
-            }
-            if(f.invistime && lastmillis - f.invistime >= INVISFLAGTIME)
-            {
-                f.invistime = 0;
-                sendf(-1, 1, "ri3", SV_INVISFLAG, i, 0);
+                returnflag(i);
+                sendf(-1, 1, "ri2", SV_RESETFLAG, i);
             }
         }
     }
 
-    void initclient(clientinfo *ci, packetbuf &p, bool connecting)
+    void initclient(clientinfo *ci, ucharbuf &p, bool connecting)
     {
         putint(p, SV_INITFLAGS);
-        loopk(2) putint(p, scores[k]);
         putint(p, flags.length());
         loopv(flags)
         {
             flag &f = flags[i];
+            putint(p, f.score);
             putint(p, f.owner);
-            putint(p, f.invistime ? 1 : 0);
             if(f.owner<0)
             {
                 putint(p, f.droptime ? 1 : 0);
@@ -294,7 +228,7 @@ struct ctfclientmode : clientmode
         }
     }
 
-    void parseflags(ucharbuf &p, bool commit)
+    void parseflags(ucharbuf &p)
     {
         int numflags = getint(p);
         loopi(numflags)
@@ -302,26 +236,27 @@ struct ctfclientmode : clientmode
             int team = getint(p);
             vec o;
             loopk(3) o[k] = getint(p)/DMF;
-            if(p.overread()) break;
-            if(commit && notgotflags) addflag(i, o, team, m_protect ? lastmillis : 0);
+            if(notgotflags) addflag(i, o, team);
         }
-        if(commit) notgotflags = false;
+        notgotflags = false;
     }
 };
 #else
+struct ctfclient : ctfstate
+{
     static const int RESPAWNSECS = 5;
 
+    fpsclient &cl;
     float radarscale;
 
-    ctfclientmode() : radarscale(0)
+    ctfclient(fpsclient &cl) : cl(cl), radarscale(0)
     {
-        CCOMMAND(dropflag, "", (ctfclientmode *self), { self->trydropflag(); });
     }
 
-    void preload()
+    void preloadflags()
     {
         static const char *flagmodels[2] = { "flags/red", "flags/blue" };
-        loopi(2) preloadmodel(flagmodels[i]);
+        loopi(2) loadmodel(flagmodels[i], -1, true);
     }
 
     void drawradar(float x, float y, float s)
@@ -335,10 +270,10 @@ struct ctfclientmode : clientmode
     void drawblips(fpsent *d, int x, int y, int s, int i, bool flagblip)
     {
         flag &f = flags[i];
-        settexture(f.team==ctfteamflag(player1->team) ?
-                    (flagblip ? "packages/hud/blip_blue_flag.png" : "packages/hud/blip_blue.png") :
+        settexture(f.team==ctfteamflag(cl.player1->team) ? 
+                    (flagblip ? "packages/hud/blip_blue_flag.png" : "packages/hud/blip_blue.png") : 
                     (flagblip ? "packages/hud/blip_red_flag.png" : "packages/hud/blip_red.png"));
-        float scale = radarscale<=0 || radarscale>maxradarscale ? maxradarscale : radarscale;
+        float scale = radarscale<=0 || radarscale>cl.maxradarscale() ? cl.maxradarscale() : radarscale;
         vec dir;
         if(flagblip) dir = f.owner ? f.owner->o : (f.droptime ? f.droploc : f.spawnloc);
         else dir = f.spawnloc;
@@ -355,18 +290,13 @@ struct ctfclientmode : clientmode
         glEnd();
     }
 
-    int clipconsole(int w, int h)
-    {
-        return w*6/40;
-    }
-
     void drawhud(fpsent *d, int w, int h)
     {
         if(d->state == CS_ALIVE)
         {
             loopv(flags) if(flags[i].owner == d)
             {
-                drawicon(flags[i].team==ctfteamflag(d->team) ? HICON_BLUE_FLAG : HICON_RED_FLAG, HICON_X + 3*HICON_STEP + (d->quadmillis ? HICON_SIZE + HICON_SPACE : 0), HICON_Y);
+                cl.drawicon(320, 0, 1820, 1650);
                 break;
             }
         }
@@ -383,21 +313,23 @@ struct ctfclientmode : clientmode
             flag &f = flags[i];
             if(!ctfflagteam(f.team)) continue;
             drawblips(d, x, y, s, i, false);
+            if(!f.ent) continue;
             if(f.owner)
             {
-                if(lastmillis%1000 >= 500) continue;
+                if(cl.lastmillis%1000 >= 500) continue;
             }
-            else if(f.droptime && (f.droploc.x < 0 || lastmillis%300 >= 150)) continue;
+            else if(f.droptime && (f.droploc.x < 0 || cl.lastmillis%300 >= 150)) continue;
             drawblips(d, x, y, s, i, true);
         }
-        if(d->state == CS_DEAD && !m_protect)
+        if(d->state == CS_DEAD)
         {
             int wait = respawnwait(d);
             if(wait>=0)
             {
                 glPushMatrix();
-                glScalef(2, 2, 1);
-                bool flash = wait>0 && d==player1 && lastspawnattempt>=d->lastpain && lastmillis < lastspawnattempt+100;
+                glLoadIdentity();
+                glOrtho(0, w*900/h, 900, 0, -1, 1);
+                bool flash = wait>0 && d==cl.player1 && cl.lastspawnattempt>=d->lastpain && cl.lastmillis < cl.lastspawnattempt+100;
                 draw_textf("%s%d", (x+s/2)/2-(wait>=10 ? 28 : 16), (y+s/2)/2-32, flash ? "\f3" : "", wait);
                 glPopMatrix();
             }
@@ -406,23 +338,23 @@ struct ctfclientmode : clientmode
 
     void removeplayer(fpsent *d)
     {
-        loopv(flags) if(flags[i].owner == d)
+        loopv(flags) if(flags[i].owner == d) 
         {
             flag &f = flags[i];
             f.interploc.x = -1;
             f.interptime = 0;
-            dropflag(i, f.owner->o, f.owner->yaw, 1);
+            ctfstate::dropflag(i, f.owner->o, 1);
         }
     }
 
     vec interpflagpos(flag &f, float &angle)
     {
         vec pos = f.owner ? vec(f.owner->abovehead()).add(vec(0, 0, 1)) : (f.droptime ? f.droploc : f.spawnloc);
-        angle = f.owner ? f.owner->yaw : (f.droptime ? f.dropangle : f.spawnangle);
+        angle = f.owner ? f.owner->yaw : (f.ent ? f.ent->attr1 : 0);
         if(pos.x < 0) return pos;
-        if(f.interptime && f.interploc.x >= 0)
+        if(f.interptime && f.interploc.x >= 0) 
         {
-            float t = min((lastmillis - f.interptime)/500.0f, 1.0f);
+            float t = min((cl.lastmillis - f.interptime)/500.0f, 1.0f);
             pos.lerp(f.interploc, pos, t);
             angle += (1-t)*(f.interpangle - angle);
         }
@@ -431,33 +363,31 @@ struct ctfclientmode : clientmode
 
     vec interpflagpos(flag &f) { float angle; return interpflagpos(f, angle); }
 
-    void rendergame()
+    void renderflags()
     {
         loopv(flags)
         {
             flag &f = flags[i];
-            if(!f.owner && f.droptime && f.droploc.x < 0) continue;
-            const char *flagname = f.team==ctfteamflag(player1->team) ? "flags/blue" : "flags/red";
+            if(!f.ent || (!f.owner && f.droptime && f.droploc.x < 0)) continue;
+            const char *flagname = f.team==ctfteamflag(cl.player1->team) ? "flags/blue" : "flags/red";
             float angle;
             vec pos = interpflagpos(f, angle);
-            rendermodel(!f.droptime && !f.owner ? &f.light : NULL, flagname, ANIM_MAPMODEL|ANIM_LOOP,
-                        interpflagpos(f), angle, 0,
-                        MDL_DYNSHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED | (f.droptime || f.owner ? MDL_LIGHT : 0),
-                        NULL, NULL, 0, 0, 0.3f + (f.vistime ? 0.7f*min((lastmillis - f.vistime)/1000.0f, 1.0f) : 0.0f));
+            rendermodel(!f.droptime && !f.owner ? &f.ent->light : NULL, flagname, ANIM_MAPMODEL|ANIM_LOOP,
+                        interpflagpos(f), angle, 0,  
+                        MDL_DYNSHADOW | MDL_CULL_VFC | MDL_CULL_OCCLUDED | (f.droptime || f.owner ? MDL_LIGHT : 0));
         }
     }
 
-    void setup()
+    void setupflags()
     {
-        resetflags();
-        loopv(entities::ents)
+        reset();
+        loopv(cl.et.ents)
         {
-            extentity *e = entities::ents[i];
+            extentity *e = cl.et.ents[i];
             if(e->type!=FLAG || e->attr2<1 || e->attr2>2) continue;
             int index = flags.length();
-            addflag(index, e->o, e->attr2, m_protect ? 0 : -1000);
-            flags[index].spawnangle = e->attr1;
-            flags[index].light = e->light;
+            addflag(index, e->o, e->attr2);
+            flags[index].ent = e;
         }
         vec center(0, 0, 0);
         loopv(flags) center.add(flags[i].spawnloc);
@@ -466,7 +396,7 @@ struct ctfclientmode : clientmode
         loopv(flags) radarscale = max(radarscale, 2*center.dist(flags[i].spawnloc));
     }
 
-    void senditems(packetbuf &p)
+    void sendflags(ucharbuf &p)
     {
         putint(p, SV_INITFLAGS);
         putint(p, flags.length());
@@ -476,19 +406,14 @@ struct ctfclientmode : clientmode
             putint(p, f.team);
             loopk(3) putint(p, int(f.spawnloc[k]*DMF));
         }
-    }
+    } 
 
     void parseflags(ucharbuf &p, bool commit)
     {
-        loopk(2)
-        {
-            int score = getint(p);
-            if(commit) scores[k] = score;
-        }
         int numflags = getint(p);
         loopi(numflags)
         {
-            int owner = getint(p), invis = getint(p), dropped = 0;
+            int score = getint(p), owner = getint(p), dropped = 0;
             vec droploc(0, 0, 0);
             if(owner<0)
             {
@@ -498,12 +423,12 @@ struct ctfclientmode : clientmode
             if(commit && flags.inrange(i))
             {
                 flag &f = flags[i];
-                f.owner = owner>=0 ? (owner==player1->clientnum ? player1 : newclient(owner)) : NULL;
+                f.score = score;
+                f.owner = owner>=0 ? (owner==cl.player1->clientnum ? cl.player1 : cl.newclient(owner)) : NULL;
                 f.droptime = dropped;
                 f.droploc = dropped ? droploc : f.spawnloc;
-                f.vistime = invis>0 ? 0 : -1000;
                 f.interptime = 0;
-
+                
                 if(dropped)
                 {
                     f.droploc.z += 4;
@@ -513,108 +438,91 @@ struct ctfclientmode : clientmode
         }
     }
 
-    void trydropflag()
-    {
-        if(!m_ctf) return;
-        loopv(flags) if(flags[i].owner == player1)
-        {
-            addmsg(SV_TRYDROPFLAG, "rc", player1);
-            return;
-        }
-    }
-
     void dropflag(fpsent *d, int i, const vec &droploc)
     {
         if(!flags.inrange(i)) return;
         flag &f = flags[i];
         f.interploc = interpflagpos(f, f.interpangle);
-        f.interptime = lastmillis;
-        dropflag(i, droploc, d->yaw, 1);
+        f.interptime = cl.lastmillis;
+        ctfstate::dropflag(i, droploc, 1);
         f.droploc.z += 4;
-        d->flagpickup |= 1<<f.id;
-        if(!droptofloor(f.droploc, 4, 0))
+        if(!droptofloor(f.droploc, 4, 0)) 
         {
             f.droploc = vec(-1, -1, -1);
             f.interptime = 0;
         }
-        conoutf(CON_GAMEINFO, "%s dropped %s flag", d==player1 ? "you" : colorname(d), f.team==ctfteamflag(player1->team) ? "your" : "the enemy");
+        conoutf(CON_GAMEINFO, "%s dropped %s flag", d==cl.player1 ? "you" : cl.colorname(d), f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy");
         playsound(S_FLAGDROP);
     }
 
-    void flagexplosion(int i, int team, const vec &loc)
+    void flagexplosion(int i, const vec &loc)
     {
-        int fcolor;
+        int ftype;
         vec color;
-        if(team==ctfteamflag(player1->team)) { fcolor = 0x2020FF; color = vec(0.25f, 0.25f, 1); }
-        else { fcolor = 0x802020; color = vec(1, 0.25f, 0.25f); }
-        particle_fireball(loc, 30, PART_EXPLOSION, -1, fcolor, 4.8f);
+        if(flags[i].team==ctfteamflag(cl.player1->team)) { ftype = 36; color = vec(0.25f, 0.25f, 1); }
+        else { ftype = 35; color = vec(1, 0.25f, 0.25f); }
+        particle_fireball(loc, 30, ftype);
         adddynlight(loc, 35, color, 900, 100);
-        particle_splash(PART_SPARK, 150, 300, loc, 0xB49B4B, 0.24f);
+        particle_splash(0, 150, 300, loc);
     }
 
-    void flageffect(int i, int team, const vec &from, const vec &to)
+    void flageffect(int i, const vec &from, const vec &to)
     {
         vec fromexp(from), toexp(to);
-        if(from.x >= 0)
+        if(from.x >= 0) 
         {
             fromexp.z += 8;
-            flagexplosion(i, team, fromexp);
+            flagexplosion(i, fromexp);
         }
-        if(from==to) return;
-        if(to.x >= 0)
+        if(to.x >= 0) 
         {
             toexp.z += 8;
-            flagexplosion(i, team, toexp);
+            flagexplosion(i, toexp);
         }
         if(from.x >= 0 && to.x >= 0)
-            particle_flare(fromexp, toexp, 600, PART_LIGHTNING, team==ctfteamflag(player1->team) ? 0x2222FF : 0xFF2222, 0.28f);
+            particle_flare(fromexp, toexp, 600, flags[i].team==ctfteamflag(cl.player1->team) ? 30 : 29);
     }
-
+ 
     void returnflag(fpsent *d, int i)
     {
         if(!flags.inrange(i)) return;
         flag &f = flags[i];
-        flageffect(i, f.team, interpflagpos(f), f.spawnloc);
+        flageffect(i, interpflagpos(f), f.spawnloc);
         f.interptime = 0;
-        returnflag(i);
-        conoutf(CON_GAMEINFO, "%s returned %s flag", d==player1 ? "you" : colorname(d), f.team==ctfteamflag(player1->team) ? "your" : "the enemy");
+        ctfstate::returnflag(i);
+        conoutf(CON_GAMEINFO, "%s returned %s flag", d==cl.player1 ? "you" : cl.colorname(d), f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy");
         playsound(S_FLAGRETURN);
     }
 
-    void resetflag(int i, int team, int score)
+    void resetflag(int i)
     {
-        setscore(team, score);
         if(!flags.inrange(i)) return;
         flag &f = flags[i];
-        flageffect(i, team, interpflagpos(f), f.spawnloc);
+        flageffect(i, interpflagpos(f), f.spawnloc);
         f.interptime = 0;
-        returnflag(i, m_protect ? 0 : -1000);
-        conoutf(CON_GAMEINFO, "%s flag reset", f.team==ctfteamflag(player1->team) ? "your" : "the enemy");
+        ctfstate::returnflag(i);
+        conoutf(CON_GAMEINFO, "%s flag reset", f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy");
         playsound(S_FLAGRESET);
     }
 
-    void scoreflag(fpsent *d, int relay, int goal, int team, int score)
+    void scoreflag(fpsent *d, int relay, int goal, int score)
     {
-        setscore(team, score);
-        if(flags.inrange(goal))
-        {
-            flag &f = flags[goal];
-            if(relay >= 0) flageffect(goal, team, f.spawnloc, flags[relay].spawnloc);
-            else flageffect(goal, team, interpflagpos(f), f.spawnloc);
-            f.interptime = 0;
-            returnflag(relay >= 0 ? relay : goal, relay < 0 ? 0 : -1000);
-            d->flagpickup &= ~(1<<f.id);
-            if(d->feetpos().dist(f.spawnloc) < FLAGRADIUS) d->flagpickup |= 1<<f.id;
-        }
-        if(d!=player1)
+        if(!flags.inrange(goal) || !flags.inrange(relay)) return;
+        flag &f = flags[goal];
+        flageffect(goal, flags[goal].spawnloc, flags[relay].spawnloc);
+        f.score = score;
+        f.interptime = 0;
+        ctfstate::returnflag(relay);
+        if(d!=cl.player1)
         {
-            defformatstring(ds)("@%d", score);
-            particle_text(d->abovehead(), ds, PART_TEXT, 2000, 0x32FF64, 4.0f, -8);
+            s_sprintfd(ds)("@%d", score);
+            particle_text(d->abovehead(), ds, 9);
         }
-        conoutf(CON_GAMEINFO, "%s scored for %s team", d==player1 ? "you" : colorname(d), team==ctfteamflag(player1->team) ? "your" : "the enemy");
+        conoutf(CON_GAMEINFO, "%s scored for %s team", d==cl.player1 ? "you" : cl.colorname(d), f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy");
         playsound(S_FLAGSCORE);
 
-        if(score >= FLAGLIMIT) conoutf(CON_GAMEINFO, "%s team captured %d flags", team==ctfteamflag(player1->team) ? "your" : "the enemy", score);
+        int total = totalscore(f.team);
+        if(total >= FLAGLIMIT) conoutf(CON_GAMEINFO, "%s team captured %d flags", f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy", total);
     }
 
     void takeflag(fpsent *d, int i)
@@ -622,369 +530,34 @@ struct ctfclientmode : clientmode
         if(!flags.inrange(i)) return;
         flag &f = flags[i];
         f.interploc = interpflagpos(f, f.interpangle);
-        f.interptime = lastmillis;
-        conoutf(CON_GAMEINFO, "%s %s %s flag", d==player1 ? "you" : colorname(d), m_protect || f.droptime ? "picked up" : "stole", f.team==ctfteamflag(player1->team) ? "your" : "the enemy");
-        ownflag(i, d, lastmillis);
+        f.interptime = cl.lastmillis;
+        conoutf(CON_GAMEINFO, "%s %s %s flag", d==cl.player1 ? "you" : cl.colorname(d), f.droptime ? "picked up" : "stole", f.team==ctfteamflag(cl.player1->team) ? "your" : "the enemy");
+        ctfstate::takeflag(i, d);
         playsound(S_FLAGPICKUP);
     }
 
-    void invisflag(int i, int invis)
-    {
-        if(!flags.inrange(i)) return;
-        flag &f = flags[i];
-        if(invis>0) f.vistime = 0;
-        else if(!f.vistime) f.vistime = lastmillis;
-    }
-
-    void checkitems(fpsent *d)
+    void checkflags(fpsent *d)
     {
-        vec o = d->feetpos();
+        vec o = d->o;
+        o.z -= d->eyeheight;
         loopv(flags)
         {
             flag &f = flags[i];
-            if(!ctfflagteam(f.team) || f.owner || (f.droptime && f.droploc.x<0)) continue;
-            const vec &loc = f.droptime ? f.droploc : f.spawnloc;
-            if(o.dist(loc) < FLAGRADIUS)
+            if(!f.ent || !ctfflagteam(f.team) || f.owner || (f.droptime ? f.droploc.x<0 : f.team==ctfteamflag(d->team))) continue;
+            if(o.dist(f.droptime ? f.droploc : f.spawnloc) < FLAGRADIUS)
             {
-                if(d->flagpickup&(1<<f.id)) continue;
-                if((lookupmaterial(o)&MATF_CLIP) != MAT_GAMECLIP && (lookupmaterial(loc)&MATF_CLIP) != MAT_GAMECLIP) 
-                    addmsg(SV_TAKEFLAG, "rci", d, i);
-                d->flagpickup |= 1<<f.id;
+                if(f.pickup) continue;
+                cl.cc.addmsg(SV_TAKEFLAG, "ri", i);
+                f.pickup = true;
             }
-            else d->flagpickup &= ~(1<<f.id);
-       }
-    }
-
-    void respawned(fpsent *d)
-    {
-        vec o = d->feetpos();
-        d->flagpickup = 0;
-        loopv(flags)
-        {
-            flag &f = flags[i];
-            if(!ctfflagteam(f.team) || f.owner || (f.droptime && f.droploc.x<0)) continue;
-            if(o.dist(f.droptime ? f.droploc : f.spawnloc) < FLAGRADIUS) d->flagpickup |= 1<<f.id;
+            else f.pickup = false;
        }
     }
 
     int respawnwait(fpsent *d)
     {
-        return m_protect ? 0 : max(0, RESPAWNSECS-(lastmillis-d->lastpain)/1000);
-    }
-
-    void pickspawn(fpsent *d)
-    {
-        findplayerspawn(d, -1, ctfteamflag(d->team));
-    }
-
-    const char *prefixnextmap() { return "ctf_"; }
-
-	bool aihomerun(fpsent *d, ai::aistate &b)
-	{
-		vec pos = d->feetpos();
-		loopk(2)
-		{
-			int goal = -1;
-			loopv(flags)
-			{
-				flag &g = flags[i];
-				if(g.team == ctfteamflag(d->team) && (k || (!g.owner && !g.droptime)) &&
-					(!flags.inrange(goal) || g.spawnloc.squaredist(pos) < flags[goal].spawnloc.squaredist(pos)))
-				{
-					goal = i;
-				}
-			}
-			if(flags.inrange(goal) && ai::makeroute(d, b, flags[goal].spawnloc, false))
-			{
-				d->ai->addstate(ai::AI_S_PURSUE, ai::AI_T_AFFINITY, goal);
-				return true;
-			}
-		}
-		return false;
-	}
-
-	bool aicheck(fpsent *d, ai::aistate &b)
-	{
-		if(!m_protect)
-		{
-			static vector<int> hasflags, takenflags;
-			hasflags.setsizenodelete(0);
-			takenflags.setsizenodelete(0);
-			loopv(flags)
-			{
-				flag &g = flags[i];
-				if(g.owner == d) hasflags.add(i);
-				else if(g.team == ctfteamflag(d->team) && ((g.owner && g.team != ctfteamflag(g.owner->team)) || g.droptime))
-					takenflags.add(i);
-			}
-			if(!hasflags.empty())
-			{
-				aihomerun(d, b);
-				return true;
-			}
-			if(!ai::badhealth(d) && !takenflags.empty())
-			{
-				int flag = takenflags.length() > 2 ? rnd(takenflags.length()) : 0;
-				d->ai->addstate(ai::AI_S_PURSUE, ai::AI_T_AFFINITY, takenflags[flag]);
-				return true;
-			}
-		}
-		return false;
-	}
-
-	void aifind(fpsent *d, ai::aistate &b, vector<ai::interest> &interests)
-	{
-		vec pos = d->feetpos();
-		loopvj(flags)
-		{
-			flag &f = flags[j];
-			if(!m_protect || f.owner != d)
-			{
-				static vector<int> targets; // build a list of others who are interested in this
-				targets.setsizenodelete(0);
-				bool home = f.team == ctfteamflag(d->team);
-				ai::checkothers(targets, d, home ? ai::AI_S_DEFEND : ai::AI_S_PURSUE, ai::AI_T_AFFINITY, j, true);
-				fpsent *e = NULL;
-				loopi(numdynents()) if((e = (fpsent *)iterdynents(i)) && ai::targetable(d, e, false) && !e->ai && !strcmp(d->team, e->team))
-				{ // try to guess what non ai are doing
-					vec ep = e->feetpos();
-					if(targets.find(e->clientnum) < 0 && (ep.squaredist(f.pos()) <= (FLAGRADIUS*FLAGRADIUS*4) || f.owner == e))
-						targets.add(e->clientnum);
-				}
-				if(home)
-				{
-					bool guard = false;
-					if((f.owner && f.team != ctfteamflag(f.owner->team)) || f.droptime || targets.empty()) guard = true;
-					else if(d->hasammo(d->ai->weappref))
-					{ // see if we can relieve someone who only has a piece of crap
-						fpsent *t;
-						loopvk(targets) if((t = getclient(targets[k])))
-						{
-							if((t->ai && !t->hasammo(t->ai->weappref)) || (!t->ai && (t->gunselect == GUN_FIST || t->gunselect == GUN_PISTOL)))
-							{
-								guard = true;
-								break;
-							}
-						}
-					}
-					if(guard)
-					{ // defend the flag
-						ai::interest &n = interests.add();
-						n.state = ai::AI_S_DEFEND;
-						n.node = ai::closestwaypoint(f.pos(), ai::NEARDIST, true);
-						n.target = j;
-						n.targtype = ai::AI_T_AFFINITY;
-						n.score = pos.squaredist(f.pos())/100.f;
-					}
-				}
-				else
-				{
-					if(targets.empty())
-					{ // attack the flag
-						ai::interest &n = interests.add();
-						n.state = ai::AI_S_PURSUE;
-						n.node = ai::closestwaypoint(f.pos(), ai::NEARDIST, true);
-						n.target = j;
-						n.targtype = ai::AI_T_AFFINITY;
-						n.score = pos.squaredist(f.pos());
-					}
-					else
-					{ // help by defending the attacker
-						fpsent *t;
-						loopvk(targets) if((t = getclient(targets[k])))
-						{
-							ai::interest &n = interests.add();
-							n.state = ai::AI_S_DEFEND;
-							n.node = t->lastnode;
-							n.target = t->clientnum;
-							n.targtype = ai::AI_T_PLAYER;
-							n.score = d->o.squaredist(t->o);
-						}
-					}
-				}
-			}
-		}
-	}
-
-	bool aidefend(fpsent *d, ai::aistate &b)
-	{
-		if(flags.inrange(b.target))
-		{
-			flag &f = flags[b.target];
-			if(f.droptime && ai::makeroute(d, b, f.pos())) return true;
-			if(!m_protect)
-			{
-				static vector<int> hasflags;
-				hasflags.setsizenodelete(0);
-				loopv(flags)
-				{
-					flag &g = flags[i];
-					if(g.owner == d) hasflags.add(i);
-				}
-				if(!hasflags.empty())
-				{
-					aihomerun(d, b);
-					return true;
-				}
-				if(f.owner && ai::violence(d, b, f.owner, true)) return true;
-			}
-			else if(f.owner == d) return false; // pop the state and do something else
-			int walk = 0;
-			if(lastmillis-b.millis >= (201-d->skill)*33)
-			{
-				static vector<int> targets; // build a list of others who are interested in this
-				targets.setsizenodelete(0);
-				ai::checkothers(targets, d, ai::AI_S_DEFEND, ai::AI_T_AFFINITY, b.target, true);
-				fpsent *e = NULL;
-				loopi(numdynents()) if((e = (fpsent *)iterdynents(i)) && ai::targetable(d, e, false) && !e->ai && !strcmp(d->team, e->team))
-				{ // try to guess what non ai are doing
-					vec ep = e->feetpos();
-					if(targets.find(e->clientnum) < 0 && (ep.squaredist(f.pos()) <= (FLAGRADIUS*FLAGRADIUS*4) || f.owner == e))
-						targets.add(e->clientnum);
-				}
-				if(!targets.empty())
-				{
-					d->ai->clear = true; // re-evaluate so as not to herd
-					return true;
-				}
-				else
-				{
-					walk = 2;
-					b.millis = lastmillis;
-				}
-			}
-			vec pos = d->feetpos();
-			float mindist = float(FLAGRADIUS*FLAGRADIUS*8);
-			loopv(flags)
-			{ // get out of the way of the returnee!
-				flag &g = flags[i];
-				if(pos.squaredist(g.pos()) <= mindist)
-				{
-					if(!m_protect && g.owner && !strcmp(g.owner->team, d->team)) walk = 1;
-					if(g.droptime && ai::makeroute(d, b, g.pos())) return true;
-				}
-			}
-			return ai::defend(d, b, f.pos(), float(FLAGRADIUS*2), float(FLAGRADIUS*(2+(walk*2))), walk);
-		}
-		return false;
-	}
-
-	bool aipursue(fpsent *d, ai::aistate &b)
-	{
-		if(flags.inrange(b.target))
-		{
-			flag &f = flags[b.target];
-			if(!m_protect && f.owner && f.owner == d)
-			{
-				aihomerun(d, b);
-				return true;
-			}
-			if(f.team == ctfteamflag(d->team))
-			{
-				if(f.droptime && ai::makeroute(d, b, f.pos())) return true;
-				if(!m_protect)
-				{
-					static vector<int> hasflags;
-					hasflags.setsizenodelete(0);
-					loopv(flags)
-					{
-						flag &g = flags[i];
-						if(g.owner == d) hasflags.add(i);
-					}
-					if(!hasflags.empty())
-					{
-						ai::makeroute(d, b, f.spawnloc);
-						return true;
-					}
-					if(f.owner && ai::violence(d, b, f.owner, true)) return true;
-				}
-				else if(f.owner == d) return false; // pop and do something else
-			}
-			else
-			{
-				if(m_protect && f.owner && ai::violence(d, b, f.owner, true)) return true;
-				return ai::makeroute(d, b, f.pos());
-			}
-		}
-		return false;
-	}
+        return max(0, RESPAWNSECS-(cl.lastmillis-d->lastpain)/1000);
+    }
 };
 #endif
 
-#elif SERVMODE
-
-case SV_TRYDROPFLAG:
-{
-    if(ci->state.state!=CS_SPECTATOR && cq && smode==&ctfmode) ctfmode.dropflag(cq);
-    break;
-}
-
-case SV_TAKEFLAG:
-{
-    int flag = getint(p);
-    if(ci->state.state!=CS_SPECTATOR && cq && smode==&ctfmode) ctfmode.takeflag(cq, flag);
-    break;
-}
-
-case SV_INITFLAGS:
-    if(smode==&ctfmode) ctfmode.parseflags(p, (ci->state.state!=CS_SPECTATOR || ci->privilege || ci->local) && !strcmp(ci->clientmap, smapname));
-    break;
-
-#else
-
-case SV_INITFLAGS:
-{
-    ctfmode.parseflags(p, m_ctf);
-    break;
-}
-
-case SV_DROPFLAG:
-{
-    int ocn = getint(p), flag = getint(p);
-    vec droploc;
-    loopk(3) droploc[k] = getint(p)/DMF;
-    fpsent *o = ocn==player1->clientnum ? player1 : newclient(ocn);
-    if(o && m_ctf) ctfmode.dropflag(o, flag, droploc);
-    break;
-}
-
-case SV_SCOREFLAG:
-{
-    int ocn = getint(p), relayflag = getint(p), goalflag = getint(p), team = getint(p), score = getint(p);
-    fpsent *o = ocn==player1->clientnum ? player1 : newclient(ocn);
-    if(o && m_ctf) ctfmode.scoreflag(o, relayflag, goalflag, team, score);
-    break;
-}
-
-case SV_RETURNFLAG:
-{
-    int ocn = getint(p), flag = getint(p);
-    fpsent *o = ocn==player1->clientnum ? player1 : newclient(ocn);
-    if(o && m_ctf) ctfmode.returnflag(o, flag);
-    break;
-}
-
-case SV_TAKEFLAG:
-{
-    int ocn = getint(p), flag = getint(p);
-    fpsent *o = ocn==player1->clientnum ? player1 : newclient(ocn);
-    if(o && m_ctf) ctfmode.takeflag(o, flag);
-    break;
-}
-
-case SV_RESETFLAG:
-{
-    int flag = getint(p), team = getint(p), score = getint(p);
-    if(m_ctf) ctfmode.resetflag(flag, team, score);
-    break;
-}
-
-case SV_INVISFLAG:
-{
-    int flag = getint(p), invis = getint(p);
-    if(m_ctf) ctfmode.invisflag(flag, invis);
-    break;
-}
-
-#endif
-
diff --git a/fpsgame/entities.cpp b/fpsgame/entities.h
similarity index 64%
rename from fpsgame/entities.cpp
rename to fpsgame/entities.h
index 1dc7eb0..457c3d9 100644
--- a/fpsgame/entities.cpp
+++ b/fpsgame/entities.h
@@ -1,23 +1,23 @@
-#include "game.h"
 
-namespace entities
+struct entities : icliententities
 {
-    using namespace game;
-
+    fpsclient &cl;
+    
     vector<extentity *> ents;
 
-    vector<extentity *> &getents() { return ents; }
-
-    bool mayattach(extentity &e) { return false; }
-    bool attachent(extentity &e, extentity &a) { return false; }
+    entities(fpsclient &_cl) : cl(_cl)
+    {
+    }
 
+    vector<extentity *> &getents() { return ents; }
+    
     const char *itemname(int i)
     {
         int t = ents[i]->type;
         if(t<I_SHELLS || t>I_QUAD) return NULL;
         return itemstats[t-I_SHELLS].name;
     }
-
+   
     const char *entmdlname(int type)
     {
         static const char *entmdlnames[] =
@@ -36,63 +36,52 @@ namespace entities
         return entmdlnames[type];
     }
 
-    const char *entmodel(const entity &e)
-    {
-        if(e.type == TELEPORT)
-        {
-            if(e.attr2 > 0) return mapmodelname(e.attr2);
-            if(e.attr2 < 0) return NULL;
-        }
-        return e.type < MAXENTTYPES ? entmdlname(e.type) : NULL;
-    }
-
     void preloadentities()
     {
         loopi(MAXENTTYPES)
         {
-            switch(i)
-            {
-                case I_SHELLS: case I_BULLETS: case I_ROCKETS: case I_ROUNDS: case I_GRENADES: case I_CARTRIDGES:
-                    if(m_noammo) continue;
-                    break;
-                case I_HEALTH: case I_BOOST: case I_GREENARMOUR: case I_YELLOWARMOUR: case I_QUAD:
-                    if(m_noitems) continue;
-                    break;
-                case CARROT: case RESPAWNPOINT:
-                    if(!m_classicsp) continue;
-                    break;
-            }
             const char *mdl = entmdlname(i);
             if(!mdl) continue;
-            preloadmodel(mdl);
+            loadmodel(mdl, -1, true);
         }
     }
 
+    void renderent(extentity &e, const char *mdlname, float z, float yaw)
+    {
+        if(!mdlname) return;
+        rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, vec(e.o).add(vec(0, 0, z)), yaw, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED);
+    }
+
+    void renderent(extentity &e, int type, float z, float yaw)
+    {
+        renderent(e, entmdlname(type), z, yaw);
+    }
+
     void renderentities()
     {
         loopv(ents)
         {
             extentity &e = *ents[i];
-            int revs = 10;
-            switch(e.type)
+            if(e.type==CARROT || e.type==RESPAWNPOINT)
             {
-                case CARROT:
-                case RESPAWNPOINT:
-                    if(e.attr2) revs = 1;
-                    break;
-                case TELEPORT:
-                    if(e.attr2 < 0) continue;
-                    break;
-                default:
-                    if(!e.spawned || e.type < I_SHELLS || e.type > I_QUAD) continue;
+                renderent(e, e.type, (float)(1+sin(cl.lastmillis/100.0+e.o.x+e.o.y)/20), cl.lastmillis/(e.attr2 ? 1.0f : 10.0f));
+                continue;
+            }
+            if(e.type==TELEPORT)
+            {
+                if(e.attr2 < 0) continue;
+                if(e.attr2 > 0)
+                {
+                    renderent(e, mapmodelname(e.attr2), (float)(1+sin(cl.lastmillis/100.0+e.o.x+e.o.y)/20), cl.lastmillis/10.0f);        
+                    continue;
+                }
             }
-            const char *mdlname = entmodel(e);
-            if(mdlname)
+            else
             {
-                vec p = e.o;
-                p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20;
-                rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED);
+                if(!e.spawned) continue;
+                if(e.type<I_SHELLS || e.type>I_QUAD) continue;
             }
+            renderent(e, e.type, (float)(1+sin(cl.lastmillis/100.0+e.o.x+e.o.y)/20), cl.lastmillis/10.0f);
         }
     }
 
@@ -106,22 +95,22 @@ namespace entities
         switch(e.attr3)
         {
             case 29:
-                endsp(false);
+                cl.ms.endsp(false);
                 break;
         }
     }
 
-    void addammo(int type, int &v, bool local)
+    void addammo(int type, int &v, bool local = true)
     {
         itemstat &is = itemstats[type-I_SHELLS];
         v += is.add;
         if(v>is.max) v = is.max;
-        if(local) msgsound(is.sound);
+        if(local) cl.playsoundc(is.sound);
     }
 
-    void repammo(fpsent *d, int type, bool local)
+    void repammo(fpsent *d, int type)
     {
-        addammo(type, d->ammo[type-I_SHELLS+GUN_SG], local);
+        addammo(type, d->ammo[type-I_SHELLS+GUN_SG]);
     }
 
     // these two functions are called when the server acknowledges that you really
@@ -135,10 +124,10 @@ namespace entities
         ents[n]->spawned = false;
         if(!d) return;
         itemstat &is = itemstats[type-I_SHELLS];
-        if(d!=player1 || isthirdperson()) particle_text(d->abovehead(), is.name, PART_TEXT, 2000, 0xFFC864, 4.0f, -8);
-        playsound(itemstats[type-I_SHELLS].sound, d!=player1 ? &d->o : NULL);
+        if(d!=cl.player1 || isthirdperson()) particle_text(d->abovehead(), is.name, 15);
+        playsound(itemstats[type-I_SHELLS].sound, d!=cl.player1 ? &d->o : NULL); 
         d->pickup(type);
-        if(d==player1) switch(type)
+        if(d==cl.player1) switch(type)
         {
             case I_BOOST:
                 conoutf(CON_GAMEINFO, "\f2you have a permanent +10 health bonus! (%d)", d->maxhealth);
@@ -150,7 +139,6 @@ namespace entities
                 playsound(S_V_QUAD);
                 break;
         }
-        else if(d->ai) ai::pickup(d, *ents[n]);
     }
 
     // these functions are called when the client touches the item
@@ -171,8 +159,7 @@ namespace entities
                 d->vel = vec(0, 0, 0);//vec(cosf(RAD*(d->yaw-90)), sinf(RAD*(d->yaw-90)), 0);
                 entinmap(d);
                 updatedynentcache(d);
-                ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f);
-                msgsound(S_TELEPORT, d);
+                cl.playsoundc(S_TELEPORT, d);
                 break;
             }
         }
@@ -185,40 +172,40 @@ namespace entities
             default:
                 if(d->canpickup(ents[n]->type))
                 {
-                    addmsg(SV_ITEMPICKUP, "rci", d, n);
+                    cl.cc.addmsg(SV_ITEMPICKUP, "ri", n);
                     ents[n]->spawned = false; // even if someone else gets it first
                 }
                 break;
-
+                    
             case TELEPORT:
             {
-                if(d->lastpickup==ents[n]->type && lastmillis-d->lastpickupmillis<500) break;
+                if(d->lastpickup==ents[n]->type && cl.lastmillis-d->lastpickupmillis<500) break;
                 d->lastpickup = ents[n]->type;
-                d->lastpickupmillis = lastmillis;
+                d->lastpickupmillis = cl.lastmillis;
                 teleport(n, d);
                 break;
             }
 
             case RESPAWNPOINT:
-                if(d!=player1) break;
-                if(n==respawnent) break;
-                respawnent = n;
+                if(d!=cl.player1) break;
+                if(n==cl.respawnent) break;
+                cl.respawnent = n;
                 conoutf(CON_GAMEINFO, "\f2respawn point set!");
                 playsound(S_V_RESPAWNPOINT);
                 break;
 
             case JUMPPAD:
             {
-                if(d->lastpickup==ents[n]->type && lastmillis-d->lastpickupmillis<300) break;
+                if(d->lastpickup==ents[n]->type && cl.lastmillis-d->lastpickupmillis<300) break;
                 d->lastpickup = ents[n]->type;
-                d->lastpickupmillis = lastmillis;
+                d->lastpickupmillis = cl.lastmillis;
                 vec v((int)(char)ents[n]->attr3*10.0f, (int)(char)ents[n]->attr2*10.0f, ents[n]->attr1*12.5f);
                 d->timeinair = 0;
                 d->falling = vec(0, 0, 0);
                 d->vel = v;
 //                d->vel.z = 0;
 //                d->vel.add(v);
-                msgsound(S_JUMPPAD, d);
+                cl.playsoundc(S_JUMPPAD, d);
                 break;
             }
         }
@@ -226,8 +213,9 @@ namespace entities
 
     void checkitems(fpsent *d)
     {
-        if(d->state!=CS_ALIVE) return;
-        vec o = d->feetpos();
+        if(d==cl.player1 && (editmode || cl.cc.spectator)) return;
+        vec o = d->o;
+        o.z -= d->eyeheight;
         loopv(ents)
         {
             extentity &e = *ents[i];
@@ -243,15 +231,15 @@ namespace entities
         if(d->quadmillis && (d->quadmillis -= time)<=0)
         {
             d->quadmillis = 0;
-            playsound(S_PUPOUT, d==player1 ? NULL : &d->o);
-            if(d==player1) conoutf(CON_GAMEINFO, "\f2quad damage is over");
+            playsound(S_PUPOUT, d==cl.player1 ? NULL : &d->o);
+            if(d==cl.player1) conoutf(CON_GAMEINFO, "\f2quad damage is over");
         }
     }
 
-    void putitems(packetbuf &p)            // puts items in network stream and also spawns them locally
+    void putitems(ucharbuf &p, int gamemode)            // puts items in network stream and also spawns them locally
     {
         putint(p, SV_ITEMLIST);
-        loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->type<I_SHELLS || ents[i]->type>I_CARTRIDGES))
+        loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_capture || ents[i]->type<I_SHELLS || ents[i]->type>I_CARTRIDGES))
         {
             putint(p, i);
             putint(p, ents[i]->type);
@@ -261,24 +249,18 @@ namespace entities
 
     void resetspawns() { loopv(ents) ents[i]->spawned = false; }
 
-    void spawnitems()
+    void spawnitems(int gamemode)
     {
         if(m_noitems) return;
-        loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->type<I_SHELLS || ents[i]->type>I_CARTRIDGES))
+        loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_capture || ents[i]->type<I_SHELLS || ents[i]->type>I_CARTRIDGES))
         {
-            ents[i]->spawned = m_sp || (ents[i]->type!=I_QUAD && ents[i]->type!=I_BOOST);
+            ents[i]->spawned = (m_sp || (ents[i]->type!=I_QUAD && ents[i]->type!=I_BOOST));
         }
     }
 
     void setspawn(int i, bool on) { if(ents.inrange(i)) ents[i]->spawned = on; }
 
     extentity *newentity() { return new fpsentity(); }
-    void deleteentity(extentity *e) { delete (fpsentity *)e; }
-
-    void clearents()
-    {
-        while(ents.length()) deleteentity(ents.pop());
-    }
 
     void fixentity(extentity &e)
     {
@@ -289,32 +271,32 @@ namespace entities
             case BARREL:
             case PLATFORM:
             case ELEVATOR:
-                e.attr5 = e.attr4;
                 e.attr4 = e.attr3;
                 e.attr3 = e.attr2;
             case MONSTER:
             case TELEDEST:
                 e.attr2 = e.attr1;
             case RESPAWNPOINT:
-                e.attr1 = (int)player1->yaw;
-                break;
+                e.attr1 = (int)cl.player1->yaw;
         }
     }
 
-    void entradius(extentity &e, bool color)
+    void entradius(extentity &e, float &radius, float &angle, vec &dir)
     {
         switch(e.type)
         {
             case TELEPORT:
                 loopv(ents) if(ents[i]->type == TELEDEST && e.attr1==ents[i]->attr2)
                 {
-                    renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o));
+                    radius = e.o.dist(ents[i]->o);
+                    dir = vec(ents[i]->o).sub(e.o).normalize();
                     break;
                 }
                 break;
 
             case JUMPPAD:
-                renderentarrow(e, vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize(), 4);
+                radius = 4;
+                dir = vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize();
                 break;
 
             case FLAG:
@@ -326,20 +308,12 @@ namespace entities
             case BARREL:
             case PLATFORM:
             case ELEVATOR:
-            {
-                vec dir;
+                radius = 4;
                 vecfromyawpitch(e.attr1, 0, 1, 0, dir);
-                renderentarrow(e, dir, 4);
                 break;
-            }
         }
     }
 
-    bool printent(extentity &e, char *buf)
-    {
-        return false;
-    }
-
     const char *entnameinfo(entity &e) { return ""; }
     const char *entname(int i)
     {
@@ -358,7 +332,7 @@ namespace entities
         };
         return i>=0 && size_t(i)<sizeof(entnames)/sizeof(entnames[0]) ? entnames[i] : "";
     }
-
+    
     int extraentinfosize() { return 0; }       // size in bytes of what the 2 methods below read/write... so it can be skipped by other games
 
     void writeent(entity &e, char *buf)   // write any additional data to disk (except for ET_ ents)
@@ -381,13 +355,12 @@ namespace entities
     void editent(int i)
     {
         extentity &e = *ents[i];
-        addmsg(SV_EDITENT, "rii3ii5", i, (int)(e.o.x*DMF), (int)(e.o.y*DMF), (int)(e.o.z*DMF), e.type, e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
+        cl.cc.addmsg(SV_EDITENT, "ri9", i, (int)(e.o.x*DMF), (int)(e.o.y*DMF), (int)(e.o.z*DMF), e.type, e.attr1, e.attr2, e.attr3, e.attr4);
     }
 
     float dropheight(entity &e)
     {
-        if(e.type==BASE || e.type==FLAG) return 0.0f;
+        if(e.type==MAPMODEL || e.type==BASE || e.type==FLAG) return 0.0f;
         return 4.0f;
     }
-}
-
+};
diff --git a/fpsgame/extinfo.h b/fpsgame/extinfo.h
index 1cc5fca..dcb8f23 100644
--- a/fpsgame/extinfo.h
+++ b/fpsgame/extinfo.h
@@ -1,6 +1,6 @@
 
 #define EXT_ACK                         -1
-#define EXT_VERSION                     104
+#define EXT_VERSION                     103
 #define EXT_NO_ERROR                    0
 #define EXT_ERROR                       1
 #define EXT_PLAYERSTATS_RESP_IDS        -10
@@ -34,11 +34,9 @@
         ucharbuf q = p;
         putint(q, EXT_PLAYERSTATS_RESP_STATS); // send player stats following
         putint(q, ci->clientnum); //add player id
-        putint(q, ci->ping);
         sendstring(ci->name, q);
         sendstring(ci->team, q);
         putint(q, ci->state.frags);
-        putint(q, ci->state.flags);
         putint(q, ci->state.deaths);
         putint(q, ci->state.teamkills);
         putint(q, ci->state.damage*100/max(ci->state.shotdamage,1));
@@ -52,6 +50,14 @@
         sendserverinforeply(q);
     }
 
+    struct teamscore
+    {
+        const char *name;
+        int score;
+
+        teamscore(const char *name, int score) : name(name), score(score) {}
+    };
+
     void extinfoteams(ucharbuf &p)
     {
         putint(p, m_teammode ? 0 : 1);
@@ -62,15 +68,26 @@
         vector<teamscore> scores;
 
         //most taken from scoreboard.h
-        if(smode && smode->hidefrags()) 
+        if(m_capture)
         {
-            smode->getteamscores(scores);
-            loopv(clients) if(clients[i]->team[0])
+            loopv(capturemode.scores) scores.add(teamscore(capturemode.scores[i].team, capturemode.scores[i].total));
+            loopv(clients) if(clients[i]->team[0]) //check all teams available, since capturemode.scores contains only teams with scores
             {
-                clientinfo *ci = clients[i];
                 teamscore *ts = NULL;
-                loopvj(scores) if(!strcmp(scores[j].team, ci->team)) { ts = &scores[j]; break; }
-                if(!ts) scores.add(teamscore(ci->team, 0));
+                loopvj(scores) if(!strcmp(scores[j].name, clients[i]->team)) { ts = &scores[i]; break; }
+                if(!ts) scores.add(teamscore(clients[i]->team, 0));
+            }
+        }
+        else if(m_ctf)
+        {
+            loopv(ctfmode.flags)
+            {
+                const char *team = ctfflagteam(ctfmode.flags[i].team);
+                if(!team) continue;
+                teamscore *ts = NULL;
+                loopvj(scores) if(!strcmp(scores[j].name, team)) { ts = &scores[i]; break; }
+                if(!ts) scores.add(teamscore(team, ctfmode.flags[i].score));
+                else ts->score += ctfmode.flags[i].score;
             }
         }
         else
@@ -79,7 +96,7 @@
             {
                 clientinfo *ci = clients[i];
                 teamscore *ts = NULL;
-                loopvj(scores) if(!strcmp(scores[j].team, ci->team)) { ts = &scores[j]; break; }
+                loopvj(scores) if(!strcmp(scores[j].name, ci->team)) { ts = &scores[i]; break; }
                 if(!ts) scores.add(teamscore(ci->team, ci->state.frags));
                 else ts->score += ci->state.frags;
             }
@@ -87,11 +104,17 @@
 
         loopv(scores)
         {
-            sendstring(scores[i].team, p);
+            sendstring(scores[i].name, p);
             putint(p, scores[i].score);
 
-            if(!smode || !smode->extinfoteam(scores[i].team, p))
-                putint(p,-1); //no bases follow
+            if(m_capture)
+            {
+                int bases = 0;
+                loopvj(capturemode.bases) if(!strcmp(capturemode.bases[j].owner, scores[i].name)) bases++;
+                putint(p, bases);
+                loopvj(capturemode.bases) if(!strcmp(capturemode.bases[j].owner, scores[i].name)) putint(p, j);
+            }
+            else putint(p,-1); //no bases follow
         }
     }
 
diff --git a/fpsgame/fps.cpp b/fpsgame/fps.cpp
index 5f82fe0..f5b8c5f 100644
--- a/fpsgame/fps.cpp
+++ b/fpsgame/fps.cpp
@@ -1,53 +1,122 @@
+#include "pch.h"
+
+#include "cube.h"
+
+#include "iengine.h"
+#include "igame.h"
+
 #include "game.h"
 
-namespace game
-{
-    bool intermission = false;
-    int maptime = 0, maprealtime = 0, minremain = 0;
-    int respawnent = -1;
-    int lasthit = 0, lastspawnattempt = 0;
+#include "fpsserver.h"
 
-    int following = -1, followdir = 0;
+#ifndef STANDALONE
 
-    fpsent *player1 = NULL;         // our client
+struct fpsclient : igameclient
+{
+    // these define classes local to fpsclient
+    #include "weapon.h"
+    #include "monster.h"
+    #include "movable.h"
+    #include "scoreboard.h"
+    #include "fpsrender.h"
+    #include "entities.h"
+    #include "client.h"
+    #include "capture.h"
+    #include "assassin.h"
+    #include "ctf.h"
+
+    int nextmode, gamemode;         // nextmode becomes gamemode after next map load
+    bool intermission;
+    int lastmillis;
+    string clientmap;
+    int maptime, minremain;
+    int respawnent;
+    int swaymillis;
+    vec swaydir;
+    int respawned, suicided;
+    int lastslowmohealth, slowmorealtimestart;
+    int lasthit, lastspawnattempt;
+
+    int following, followdir;
+
+    bool openmainmenu;
+
+    fpsent *player1;                // our client
     vector<fpsent *> players;       // other clients
-    int savedammo[NUMGUNS];
+    fpsent lastplayerstate;
+
+    weaponstate ws;
+    monsterset  ms;
+    movableset  mo;
+    scoreboard  sb;
+    fpsrender   fr;
+    entities    et;
+    clientcom   cc;
+
+    IVARP(maxradarscale, 0, 1024, 10000);
+
+    captureclient cpc;
+    assassinclient asc;
+    ctfclient ctf;
+
+    fpsclient()
+        : nextmode(0), gamemode(0), intermission(false), lastmillis(0),
+          maptime(0), minremain(0), respawnent(-1), 
+          swaymillis(0), swaydir(0, 0, 0),
+          respawned(-1), suicided(-1), 
+          lasthit(0), lastspawnattempt(0),
+          following(-1), followdir(0), openmainmenu(true),
+          player1(spawnstate(new fpsent())),
+          ws(*this), ms(*this), mo(*this), sb(*this), fr(*this), et(*this), cc(*this), 
+          cpc(*this), asc(*this), ctf(*this)
+    {
+        CCOMMAND(mode, "i", (fpsclient *self, int *val), { self->setmode(*val); });
+        CCOMMAND(kill, "",  (fpsclient *self), { self->suicide(self->player1); });
+        CCOMMAND(taunt, "", (fpsclient *self), { self->taunt(); });
+        CCOMMAND(follow, "s", (fpsclient *self, char *s), { self->follow(s); });
+        CCOMMAND(nextfollow, "i", (fpsclient *self, int *dir), { self->nextfollow(*dir < 0 ? -1 : 1); });
+    }
 
-    bool clientoption(const char *arg) { return false; }
+    iclientcom      *getcom()  { return &cc; }
+    icliententities *getents() { return &et; }
+
+    void setmode(int mode)
+    {
+        if(multiplayer(false) && !m_mp(mode)) { conoutf(CON_ERROR, "mode %d not supported in multiplayer", mode); return; }
+        nextmode = mode;
+    }
 
     void taunt()
     {
         if(player1->state!=CS_ALIVE || player1->physstate<PHYS_SLOPE) return;
         if(lastmillis-player1->lasttaunt<1000) return;
         player1->lasttaunt = lastmillis;
-        addmsg(SV_TAUNT, "rc", player1);
+        cc.addmsg(SV_TAUNT, "r");
     }
-    COMMAND(taunt, "");
 
 	void follow(char *arg)
     {
         if(arg[0] ? player1->state==CS_SPECTATOR : following>=0)
         {
-            following = arg[0] ? parseplayer(arg) : -1;
+            following = arg[0] ? cc.parseplayer(arg) : -1;
             if(following==player1->clientnum) following = -1;
             followdir = 0;
             conoutf("follow %s", following>=0 ? "on" : "off");
         }
 	}
-    COMMAND(follow, "s");
 
     void nextfollow(int dir)
     {
-        if(player1->state!=CS_SPECTATOR || clients.empty())
+        if(player1->state!=CS_SPECTATOR || players.empty())
         {
             stopfollowing();
             return;
         }
-        int cur = following >= 0 ? following : (dir < 0 ? clients.length() - 1 : 0);
-        loopv(clients)
+        int cur = following >= 0 ? following : (dir < 0 ? players.length() - 1 : 0);
+        loopv(players) 
         {
-            cur = (cur + dir + clients.length()) % clients.length();
-            if(clients[cur] && clients[cur]->state!=CS_SPECTATOR)
+            cur = (cur + dir + players.length()) % players.length();
+            if(players[cur])
             {
                 if(following<0) conoutf("follow on");
                 following = cur;
@@ -57,21 +126,22 @@ namespace game
         }
         stopfollowing();
     }
-    ICOMMAND(nextfollow, "i", (int *dir), nextfollow(*dir < 0 ? -1 : 1));
 
+    char *getclientmap() { return clientmap; }
 
-    const char *getclientmap() { return clientmap; }
+    void adddynlights() { ws.adddynlights(); }
+
+    void rendergame() { fr.rendergame(gamemode); }
 
     void resetgamestate()
     {
-        if(m_classicsp)
+        if(m_classicsp) 
         {
-            clearmovables();
-            clearmonsters();                 // all monsters back at their spawns for editing
+            mo.clear(gamemode);
+            ms.monsterclear(gamemode);                 // all monsters back at their spawns for editing
             resettriggers();
         }
-        clearprojectiles();
-        clearbouncers();
+        ws.projreset();
     }
 
     fpsent *spawnstate(fpsent *d)              // reset player state not persistent accross spawns
@@ -83,27 +153,31 @@ namespace game
 
     void respawnself()
     {
-        if(paused || ispaused()) return;
-        if(m_mp(gamemode))
+        if(m_mp(gamemode)) 
         {
-            if(player1->respawned!=player1->lifesequence)
+            if(respawned!=player1->lifesequence)
             {
-                addmsg(SV_TRYSPAWN, "rc", player1);
-                player1->respawned = player1->lifesequence;
+                cc.addmsg(SV_TRYSPAWN, "r");
+                respawned = player1->lifesequence;
             }
         }
         else
         {
             spawnplayer(player1);
-            showscores(false);
+            sb.showscores(false);
             lasthit = 0;
-            if(cmode) cmode->respawned(player1);
+            if(m_capture) cpc.lastrepammo = -1;
         }
     }
 
     fpsent *pointatplayer()
     {
-        loopv(players) if(players[i] != player1 && intersect(players[i], player1->o, worldpos)) return players[i];
+        loopv(players)
+        {
+            fpsent *o = players[i];
+            if(!o) continue;
+            if(intersect(o, player1->o, worldpos)) return o;
+        }
         return NULL;
     }
 
@@ -125,6 +199,7 @@ namespace game
 
     fpsent *hudplayer()
     {
+        extern int thirdperson;
         if(thirdperson) return player1;
         fpsent *target = followingplayer();
         return target ? target : player1;
@@ -133,9 +208,9 @@ namespace game
     void setupcamera()
     {
         fpsent *target = followingplayer();
-        if(target)
+        if(target) 
         {
-            player1->yaw = target->yaw;
+            player1->yaw = target->yaw;    
             player1->pitch = target->state==CS_DEAD ? 0 : target->pitch;
             player1->o = target->o;
             player1->resetinterp();
@@ -148,53 +223,20 @@ namespace game
         return d->state==CS_DEAD;
     }
 
-    bool collidecamera()
-    {
-        switch(player1->state)
-        {
-            case CS_EDITING: return false;
-            case CS_SPECTATOR: return followingplayer()!=NULL;
-        }
-        return true;
-    }
-
-    VARP(smoothmove, 0, 75, 100);
-    VARP(smoothdist, 0, 32, 64);
-
-    void predictplayer(fpsent *d, bool move)
-    {
-        d->o = d->newpos;
-        d->yaw = d->newyaw;
-        d->pitch = d->newpitch;
-        if(move)
-        {
-            moveplayer(d, 1, false);
-            d->newpos = d->o;
-        }
-        float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove;
-        if(k>0)
-        {
-            d->o.add(vec(d->deltapos).mul(k));
-            d->yaw += d->deltayaw*k;
-            if(d->yaw<0) d->yaw += 360;
-            else if(d->yaw>=360) d->yaw -= 360;
-            d->pitch += d->deltapitch*k;
-        }
-    }
+    IVARP(smoothmove, 0, 75, 100);
+    IVARP(smoothdist, 0, 32, 64);
 
     void otherplayers(int curtime)
     {
-        loopv(players)
+        loopv(players) if(players[i])
         {
             fpsent *d = players[i];
-            if(d == player1 || d->ai) continue;
-
+            
             if(d->state==CS_ALIVE)
             {
-                if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0;
-                if(d->quadmillis) entities::checkquad(curtime, d);
+                if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0; 
+                if(d->quadmillis) et.checkquad(curtime, d);
             }
-            else if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d);
 
             const int lagtime = lastmillis-d->lastupdate;
             if(!lagtime || intermission) continue;
@@ -205,104 +247,128 @@ namespace game
             }
             if(d->state==CS_ALIVE || d->state==CS_EDITING)
             {
-                if(smoothmove && d->smoothmillis>0) predictplayer(d, true);
+                if(smoothmove() && d->smoothmillis>0)
+                {
+                    d->o = d->newpos;
+                    d->yaw = d->newyaw;
+                    d->pitch = d->newpitch;
+                    moveplayer(d, 1, false);
+                    d->newpos = d->o;
+                    float k = 1.0f - float(lastmillis - d->smoothmillis)/smoothmove();
+                    if(k>0)
+                    {
+                        d->o.add(vec(d->deltapos).mul(k));
+                        d->yaw += d->deltayaw*k;
+                        if(d->yaw<0) d->yaw += 360;
+                        else if(d->yaw>=360) d->yaw -= 360;
+                        d->pitch += d->deltapitch*k;
+                    }
+                }
                 else moveplayer(d, 1, false);
             }
-            else if(d->state==CS_DEAD && !d->ragdoll && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
+            else if(d->state==CS_DEAD && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
         }
     }
 
-    VARFP(slowmosp, 0, 0, 1,
+    void addsway(int curtime)
     {
-        if(m_sp && !slowmosp) setvar("gamespeed", 100);
-    });
-
-    void checkslowmo()
-    {
-        static int lastslowmohealth = 0;
-        setvar("gamespeed", intermission ? 100 : clamp(player1->health, 25, 200), true, false);
-        if(player1->health<player1->maxhealth && lastmillis-max(maptime, lastslowmohealth)>player1->health*player1->health/2)
+        fpsent *d = hudplayer();
+        if(d->state!=CS_SPECTATOR)
         {
-            lastslowmohealth = lastmillis;
-            player1->health++;
+            if(d->physstate>=PHYS_SLOPE) swaymillis += curtime;
+            float k = pow(0.7f, curtime/10.0f);
+            swaydir.mul(k);
+            vec vel(d->vel);
+            vel.add(d->falling);
+            swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed))));
         }
     }
 
-    void updateworld()        // main game update loop
+    void updateworld(vec &pos, int curtime, int lm)        // main game update loop
     {
-        if(!maptime) { maptime = lastmillis; maprealtime = totalmillis; return; }
-        if(!curtime) { gets2c(); if(player1->clientnum>=0) c2sinfo(); return; }
+        if(!maptime)
+        {
+            maptime = lm + curtime;
+            extern int totalmillis;
+            slowmorealtimestart = totalmillis;
+            return;
+        }
+        lastmillis = lm;
+
+        if(m_slowmo)
+        {
+            setvar("gamespeed", intermission ? 100 : player1->health);
+            if(player1->health<player1->maxhealth && lastmillis-lastslowmohealth>player1->health*player1->health/2)
+            {
+                lastslowmohealth = lastmillis;
+                player1->health++;
+            }
+        }
+
+        if(!curtime) return;
 
         physicsframe();
-        ai::trydropwaypoints();
-        entities::checkquad(curtime, player1);
-        updateweapons(curtime);
+        et.checkquad(curtime, player1);
+        ws.moveprojectiles(curtime);
+        if(player1->clientnum>=0 && player1->state==CS_ALIVE) ws.shoot(player1, pos); // only shoot when connected to server
+        ws.bounceupdate(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame
         otherplayers(curtime);
-        ai::update();
-        moveragdolls();
         gets2c();
-        updatemovables(curtime);
-        updatemonsters(curtime);
+        mo.update(curtime);
+        ms.monsterthink(curtime, gamemode);
         if(player1->state==CS_DEAD)
         {
-            if(player1->ragdoll) moveragdoll(player1);
-            else if(lastmillis-player1->lastpain<2000)
+            if(lastmillis-player1->lastpain<2000)
             {
                 player1->move = player1->strafe = 0;
                 moveplayer(player1, 10, false);
             }
+            if(m_assassin && asc.respawnwait()<=0) respawnself();
         }
         else if(!intermission)
         {
-            if(player1->ragdoll) cleanragdoll(player1);
             moveplayer(player1, 10, true);
-            swayhudgun(curtime);
-            entities::checkitems(player1);
-            if(m_sp)
-            {
-                if(slowmosp) checkslowmo();
-                if(m_classicsp) checktriggers();
-            }
-            else if(cmode) cmode->checkitems(player1);
+            addsway(curtime);
+            et.checkitems(player1);
+            if(m_classicsp) checktriggers();
+            else if(m_capture) cpc.checkbaseammo(player1);
+            else if(m_ctf) ctf.checkflags(player1);
         }
-        if(player1->clientnum>=0) c2sinfo();   // do this last, to reduce the effective frame lag
+        if(player1->clientnum>=0) c2sinfo(player1);   // do this last, to reduce the effective frame lag
     }
 
-    void spawnplayer(fpsent *d)   // place at random spawn
+    void spawnplayer(fpsent *d)   // place at random spawn. also used by monsters!
     {
-        if(cmode) cmode->pickspawn(d);
-        else findplayerspawn(d, d==player1 && respawnent>=0 ? respawnent : -1);
+        findplayerspawn(d, m_capture ? cpc.pickspawn(d->team) : (respawnent>=0 ? respawnent : -1), m_ctf ? ctfteamflag(player1->team) : 0);
         spawnstate(d);
-        if(d==player1)
-        {
-            if(editmode) d->state = CS_EDITING;
-            else if(d->state != CS_SPECTATOR) d->state = CS_ALIVE;
-        }
-        else d->state = CS_ALIVE;
+        d->state = cc.spectator ? CS_SPECTATOR : (d==player1 && editmode ? CS_EDITING : CS_ALIVE);
     }
 
-    VARP(spawnwait, 0, 0, 1000);
-
     void respawn()
     {
         if(player1->state==CS_DEAD)
         {
             player1->attacking = false;
-            int wait = cmode ? cmode->respawnwait(player1) : 0;
-            if(wait>0)
+            if(m_capture || m_ctf)
             {
-                lastspawnattempt = lastmillis;
-                //conoutf(CON_GAMEINFO, "\f2you must wait %d second%s before respawn!", wait, wait!=1 ? "s" : "");
-                return;
+                int wait = m_capture ? cpc.respawnwait(player1) : ctf.respawnwait(player1);
+                if(wait>0)
+                {
+                    lastspawnattempt = lastmillis; 
+                    //conoutf(CON_GAMEINFO, "\f2you must wait %d second%s before respawn!", wait, wait!=1 ? "s" : "");
+                    return;
+                }
             }
-            if(lastmillis < player1->lastpain + spawnwait) return;
-            if(m_dmsp) { changemap(clientmap, gamemode); return; }    // if we die in SP we try the same map again
-            respawnself();
+            if(m_arena) { conoutf(CON_GAMEINFO, "\f2waiting for new round to start..."); return; }
+            if(m_dmsp) { nextmode = gamemode; cc.changemap(clientmap); return; }    // if we die in SP we try the same map again
             if(m_classicsp)
             {
+                respawnself();
                 conoutf(CON_GAMEINFO, "\f2You wasted another life! The monsters stole your armour and some ammo...");
-                loopi(NUMGUNS) if(i!=GUN_PISTOL && (player1->ammo[i] = savedammo[i]) > 5) player1->ammo[i] = max(player1->ammo[i]/3, 5);
+                loopi(NUMGUNS) if(i!=GUN_PISTOL && (player1->ammo[i] = lastplayerstate.ammo[i])>5) player1->ammo[i] = max(player1->ammo[i]/3, 5); 
+                return;
             }
+            respawnself();
         }
     }
 
@@ -314,10 +380,10 @@ namespace game
         if((player1->attacking = on)) respawn();
     }
 
-    bool canjump()
-    {
-        if(!intermission) respawn();
-        return player1->state!=CS_DEAD && !intermission;
+    bool canjump() 
+    { 
+        if(!intermission) respawn(); 
+        return player1->state!=CS_DEAD && !intermission; 
     }
 
     bool allowmove(physent *d)
@@ -326,50 +392,43 @@ namespace game
         return !((fpsent *)d)->lasttaunt || lastmillis-((fpsent *)d)->lasttaunt>=1000;
     }
 
-    VARP(hitsound, 0, 0, 1);
-
-    void damaged(int damage, fpsent *d, fpsent *actor, bool local)
+    void damaged(int damage, fpsent *d, fpsent *actor, bool local = true)
     {
         if(d->state!=CS_ALIVE || intermission) return;
 
+        fpsent *h = local ? player1 : hudplayer();
+        if(actor==h && d!=actor) lasthit = lastmillis;
+
         if(local) damage = d->dodamage(damage);
         else if(actor==player1) return;
-  
-        fpsent *h = hudplayer();
-        if(h!=player1 && actor==h && d!=actor)
-        {
-            if(hitsound && lasthit != lastmillis) playsound(S_HIT);
-            lasthit = lastmillis;
-        }
+
         if(d==h)
         {
             damageblend(damage);
             damagecompass(damage, actor->o);
+            d->damageroll(damage);
+            if(m_slowmo && player1->health<1) player1->health = 1;
         }
-        damageeffect(damage, d, d!=h);
-
-		ai::damaged(d, actor);
-
-        if(m_sp && slowmosp && d==player1 && d->health < 1) d->health = 1;
+        ws.damageeffect(damage, d, d!=h);
 
         if(d->health<=0) { if(local) killed(d, actor); }
-        else if(d==h) playsound(S_PAIN6);
+        else if(d==player1) playsound(S_PAIN6);
         else playsound(S_PAIN1+rnd(5), &d->o);
     }
 
-    void deathstate(fpsent *d, bool restore)
+    void deathstate(fpsent *d, bool restore = false)
     {
         d->state = CS_DEAD;
         d->lastpain = lastmillis;
         d->superdamage = restore ? 0 : max(-d->health, 0);
         if(d==player1)
         {
-            showscores(true);
-            disablezoom();
-            if(!restore) loopi(NUMGUNS) savedammo[i] = player1->ammo[i];
+            sb.showscores(true);
+            setvar("zoom", -1, true);
+            if(!restore) lastplayerstate = *player1;
             d->attacking = false;
             if(!restore) d->deaths++;
-            //d->pitch = 0;
+            d->pitch = 0;
             d->roll = 0;
             playsound(S_DIE1+rnd(2));
         }
@@ -377,9 +436,8 @@ namespace game
         {
             d->move = d->strafe = 0;
             d->resetinterp();
-            d->smoothmillis = 0;
             playsound(S_DIE1+rnd(2), &d->o);
-            if(!restore) superdamageeffect(d->vel, d);
+            if(!restore) ws.superdamageeffect(d->vel, d);
         }
     }
 
@@ -398,25 +456,37 @@ namespace game
         if(!h) h = player1;
         int contype = d==h || actor==h ? CON_FRAG_SELF : CON_FRAG_OTHER;
         string dname, aname;
-        copystring(dname, d==player1 ? "you" : colorname(d));
-        copystring(aname, actor==player1 ? "you" : colorname(actor));
+        s_strcpy(dname, d==player1 ? "you" : colorname(d));
+        s_strcpy(aname, actor==player1 ? "you" : (actor->type!=ENT_INANIMATE ? colorname(actor) : ""));
         if(actor->type==ENT_AI)
             conoutf(contype, "\f2%s got killed by %s!", dname, aname);
         else if(d==actor || actor->type==ENT_INANIMATE)
             conoutf(contype, "\f2%s suicided%s", dname, d==player1 ? "!" : "");
         else if(isteam(d->team, actor->team))
         {
-            if(actor==player1) conoutf(contype, "\f3you fragged a teammate (%s)", dname);
-            else if(d==player1) conoutf(contype, "\f3you got fragged by a teammate (%s)", aname);
+            if(d==player1) conoutf(contype, "\f2you got fragged by a teammate (%s)", aname);
             else conoutf(contype, "\f2%s fragged a teammate (%s)", aname, dname);
         }
+        else if(m_assassin && (d==player1 || actor==player1))
+        {
+            if(d==player1) 
+            {   
+                conoutf(contype, "\f2you got fragged by %s (%s)", aname, asc.hunters.find(actor)>=0 ? "assassin" : (asc.targets.find(actor)>=0 ? "target" : "friend"));
+                if(asc.hunters.find(actor)>=0) asc.hunters.removeobj(actor);
+            }
+            else 
+            {
+                conoutf(contype, "\f2you fragged %s (%s)", dname, asc.targets.find(d)>=0 ? "target +1" : (asc.hunters.find(d)>=0 ? "assassin +0" : "friend -1")); 
+                if(asc.targets.find(d)>=0) asc.targets.removeobj(d);
+            }
+        }
         else
         {
             if(d==player1) conoutf(contype, "\f2you got fragged by %s", aname);
             else conoutf(contype, "\f2%s fragged %s", aname, dname);
         }
+
         deathstate(d);
-		ai::killed(d, actor);
     }
 
     void timeupdate(int timeremain)
@@ -426,16 +496,31 @@ namespace game
         {
             intermission = true;
             player1->attacking = false;
-            if(cmode) cmode->gameover();
             conoutf(CON_GAMEINFO, "\f2intermission:");
             conoutf(CON_GAMEINFO, "\f2game has ended!");
             conoutf(CON_GAMEINFO, "\f2player frags: %d, deaths: %d", player1->frags, player1->deaths);
             int accuracy = player1->totaldamage*100/max(player1->totalshots, 1);
-            conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy);
-            if(m_sp) spsummary(accuracy);
-
-            showscores(true);
-            disablezoom();
+            conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy);               
+            if(m_sp)
+            {
+                conoutf(CON_GAMEINFO, "\f2--- single player time score: ---");
+                int pen, score = 0;
+                extern int totalmillis;
+                pen = (totalmillis-slowmorealtimestart)/1000; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time taken: %d seconds (%d simulated seconds)", pen, (lastmillis-maptime)/1000); 
+                pen = player1->deaths*60; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d deaths (1 minute each): %d seconds", player1->deaths, pen);
+                pen = ms.remain*10;       score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d monsters remaining (10 seconds each): %d seconds", ms.remain, pen);
+                pen = (10-ms.skill())*20; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for lower skill level (20 seconds each): %d seconds", pen);
+                pen = 100-accuracy;       score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for missed shots (1 second each %%): %d seconds", pen);
+                s_sprintfd(aname)("bestscore_%s", getclientmap());
+                const char *bestsc = getalias(aname);
+                int bestscore = *bestsc ? atoi(bestsc) : score;
+                if(score<bestscore) bestscore = score;
+                s_sprintfd(nscore)("%d", bestscore);
+                alias(aname, nscore);
+                conoutf(CON_GAMEINFO, "\f2TOTAL SCORE (time + time penalties): %d seconds (best so far: %d seconds)", score, bestscore);
+            }
+            sb.showscores(true);
+            setvar("zoom", -1, true);
         }
         else if(timeremain > 0)
         {
@@ -443,347 +528,295 @@ namespace game
         }
     }
 
-    vector<fpsent *> clients;
-
     fpsent *newclient(int cn)   // ensure valid entity
     {
-        if(cn < 0 || cn > max(0xFF, MAXCLIENTS + MAXBOTS))
+        if(cn<0 || cn>=MAXCLIENTS)
         {
-            neterr("clientnum", false);
+            neterr("clientnum");
             return NULL;
         }
-
-        if(cn == player1->clientnum) return player1;
-
-        while(cn >= clients.length()) clients.add(NULL);
-        if(!clients[cn])
+        while(cn>=players.length()) players.add(NULL);
+        if(!players[cn])
         {
-            fpsent *d = new fpsent;
+            fpsent *d = new fpsent();
             d->clientnum = cn;
-            clients[cn] = d;
-            players.add(d);
+            players[cn] = d;
         }
-        return clients[cn];
+        return players[cn];
     }
 
     fpsent *getclient(int cn)   // ensure valid entity
     {
-        if(cn == player1->clientnum) return player1;
-        return clients.inrange(cn) ? clients[cn] : NULL;
+        return players.inrange(cn) ? players[cn] : NULL;
     }
 
-    void clientdisconnected(int cn, bool notify)
+    void clientdisconnected(int cn, bool notify = true)
     {
-        if(!clients.inrange(cn)) return;
-        if(following==cn)
+        if(!players.inrange(cn)) return;
+        if(following==cn) 
         {
             if(followdir) nextfollow(followdir);
             else stopfollowing();
         }
-        fpsent *d = clients[cn];
-        if(!d) return;
+        fpsent *d = players[cn];
+        if(!d) return; 
         if(notify && d->name[0]) conoutf("player %s disconnected", colorname(d));
-        removeweapons(d);
+        ws.removebouncers(d);
+        ws.removeprojectiles(d);
         removetrackedparticles(d);
-        removetrackeddynlights(d);
-        if(cmode) cmode->removeplayer(d);
-        players.removeobj(d);
-        DELETEP(clients[cn]);
+        if(m_assassin) asc.removeplayer(d);
+        else if(m_ctf) ctf.removeplayer(d); 
+        DELETEP(players[cn]);
         cleardynentcache();
     }
 
-    void clearclients(bool notify)
+    void initclient()
     {
-        loopv(clients) if(clients[i]) clientdisconnected(i, notify);
+        clientmap[0] = 0;
+        cc.initclientnet();
     }
 
-    void initclient()
+    void preloadweapons()
+    {
+        loopi(NUMGUNS)
+        {
+            const char *file = guns[i].file;
+            if(!file) continue;
+            s_sprintfd(mdl)("hudguns/%s", file);
+            loadmodel(mdl, -1, true);
+            s_sprintf(mdl)("hudguns/%s/blue", file);
+            loadmodel(mdl, -1, true);
+            s_sprintf(mdl)("vwep/%s", file);
+            loadmodel(mdl, -1, true);
+        }
+    }
+
+    void preloadbouncers()
+    {
+        const char *mdls[] =
+        {
+            "gibc", "gibh",
+            "projectiles/grenade", "projectiles/rocket",
+            "debris/debris01", "debris/debris02", "debris/debris03", "debris/debris04",
+            "barreldebris/debris01", "barreldebris/debris02", "barreldebris/debris03", "barreldebris/debris04"
+        };
+        loopi(sizeof(mdls)/sizeof(mdls[0]))
+        {
+            loadmodel(mdls[i], -1, true);
+        }
+    }
+
+    void preload()
     {
-        player1 = spawnstate(new fpsent);
-        players.add(player1);
+        preloadweapons();
+        preloadbouncers();
+        fr.preloadplayermodel();
+        et.preloadentities();
+        if(m_sp) ms.preloadmonsters();
+        else if(m_capture) cpc.preloadbases();
+        else if(m_ctf) ctf.preloadflags(); 
     }
 
-    VARP(showmodeinfo, 0, 1, 1);
+    IVARP(startmenu, 0, 1, 1);
 
-    void startgame()
+    void startmap(const char *name)   // called just after a map load
     {
+        respawned = suicided = -1;
         respawnent = -1;
-        clearmovables();
-        clearmonsters();
-
-        clearprojectiles();
-        clearbouncers();
-        clearragdolls();
+        lasthit = 0;
+        if(multiplayer(false) && m_sp) { gamemode = 0; conoutf(CON_ERROR, "coop sp not supported yet"); }
+        cc.mapstart();
+        mo.clear(gamemode);
+        ms.monsterclear(gamemode);
+        ws.projreset();
 
         // reset perma-state
-        loopv(players)
+        player1->frags = 0;
+        player1->deaths = 0;
+        player1->totaldamage = 0;
+        player1->totalshots = 0;
+        player1->maxhealth = 100;
+        loopv(players) if(players[i])
         {
-            fpsent *d = players[i];
-            d->frags = 0;
-            d->deaths = 0;
-            d->totaldamage = 0;
-            d->totalshots = 0;
-            d->maxhealth = 100;
-            d->lifesequence = -1;
-            d->respawned = d->suicided = -2;
+            players[i]->frags = 0;
+            players[i]->deaths = 0;
+            players[i]->totaldamage = 0;
+            players[i]->totalshots = 0;
+            players[i]->maxhealth = 100;
         }
 
-        setclientmode();
-
+        if(!m_mp(gamemode)) spawnplayer(player1);
+        else findplayerspawn(player1, -1);
+        et.resetspawns();
+        s_strcpy(clientmap, name);
+        sb.showscores(false);
+        setvar("zoom", -1, true);
         intermission = false;
         maptime = 0;
-
-        if(cmode) 
-        {
-            cmode->preload();
-            cmode->setup();
-        }
-
-        conoutf(CON_GAMEINFO, "\f2game mode is %s", server::modename(gamemode));
-
+        if(*name) conoutf(CON_GAMEINFO, "\f2game mode is %s", fpsserver::modestr(gamemode));
         if(m_sp)
         {
-            defformatstring(scorename)("bestscore_%s", getclientmap());
-            const char *best = getalias(scorename);
+            s_sprintfd(aname)("bestscore_%s", getclientmap());
+            const char *best = getalias(aname);
             if(*best) conoutf(CON_GAMEINFO, "\f2try to beat your best score so far: %s", best);
+            lastslowmohealth = lastmillis;
         }
-        else
+
+        if(*name && openmainmenu && startmenu())
         {
-            const char *info = m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL;
-            if(showmodeinfo && info) conoutf(CON_GAMEINFO, "\f0%s", info);
+            showgui("main");
+            openmainmenu = false;
         }
 
-        if(player1->playermodel != playermodel) switchplayermodel(playermodel);
-
-        showscores(false);
-        disablezoom();
-        lasthit = 0;
-
         if(identexists("mapstart")) execute("mapstart");
     }
 
-    void startmap(const char *name)   // called just after a map load
-    {
-        ai::savewaypoints();
-        ai::clearwaypoints(true);
-
-        if(!m_mp(gamemode)) spawnplayer(player1);
-        else findplayerspawn(player1, -1);
-        entities::resetspawns();
-        copystring(clientmap, name ? name : "");
-
-        sendmapinfo();
-    }
-
-    const char *getmapinfo()
-    {
-        return showmodeinfo && m_valid(gamemode) ? gamemodes[gamemode - STARTGAMEMODE].info : NULL;
-    }
-
     void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material)
     {
         if(d->type==ENT_INANIMATE) return;
         if     (waterlevel>0) { if(material!=MAT_LAVA) playsound(S_SPLASH1, d==player1 ? NULL : &d->o); }
         else if(waterlevel<0) playsound(material==MAT_LAVA ? S_BURN : S_SPLASH2, d==player1 ? NULL : &d->o);
-        if     (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_JUMP, d); }
-        else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER || ((fpsent *)d)->ai) msgsound(S_LAND, d); }
+        if     (floorlevel>0) { if(d==player1 || d->type!=ENT_PLAYER) playsoundc(S_JUMP, (fpsent *)d); }
+        else if(floorlevel<0) { if(d==player1 || d->type!=ENT_PLAYER) playsoundc(S_LAND, (fpsent *)d); }
     }
 
-    void msgsound(int n, physent *d)
-    {
+    void playsoundc(int n, fpsent *d = NULL) 
+    { 
         if(!d || d==player1)
         {
-            addmsg(SV_SOUND, "ci", d, n);
-            playsound(n);
-        }
-        else 
-        {
-            if(d->type==ENT_PLAYER && ((fpsent *)d)->ai)
-                addmsg(SV_SOUND, "ci", d, n);
-            playsound(n, &d->o);
+            cc.addmsg(SV_SOUND, "i", n); 
+            playsound(n); 
         }
+        else playsound(n, &d->o);
     }
 
-    int numdynents() { return players.length()+monsters.length()+movables.length(); }
+    int numdynents() { return 1+players.length()+ms.monsters.length()+mo.movables.length(); }
 
     dynent *iterdynents(int i)
     {
+        if(!i) return player1;
+        i--;
         if(i<players.length()) return players[i];
         i -= players.length();
-        if(i<monsters.length()) return (dynent *)monsters[i];
-        i -= monsters.length();
-        if(i<movables.length()) return (dynent *)movables[i];
+        if(i<ms.monsters.length()) return ms.monsters[i];
+        i -= ms.monsters.length(); 
+        if(i<mo.movables.length()) return mo.movables[i];
         return NULL;
     }
 
-    bool duplicatename(fpsent *d, const char *name = NULL)
+    bool duplicatename(fpsent *d, char *name = NULL)
     {
         if(!name) name = d->name;
-        loopv(players) if(d!=players[i] && !strcmp(name, players[i]->name)) return true;
+        if(d!=player1 && !strcmp(name, player1->name)) return true;
+        loopv(players) if(players[i] && d!=players[i] && !strcmp(name, players[i]->name)) return true;
         return false;
     }
 
-    const char *colorname(fpsent *d, const char *name, const char *prefix)
+    char *colorname(fpsent *d, char *name = NULL, const char *prefix = "")
     {
         if(!name) name = d->name;
-        if(name[0] && !duplicatename(d, name) && d->aitype == AI_NONE) return name;
-        static string cname[3];
-        static int cidx = 0;
-        cidx = (cidx+1)%3;
-        formatstring(cname[cidx])(d->aitype == AI_NONE ? "%s%s \fs\f5(%d)\fr" : "%s%s \fs\f5[%d]\fr", prefix, name, d->clientnum);
-        return cname[cidx];
+        if(name[0] && !duplicatename(d, name)) return name;
+        static string cname;
+        s_sprintf(cname)("%s%s \fs\f5(%d)\fr", prefix, name, d->clientnum);
+        return cname;
     }
 
     void suicide(physent *d)
     {
-        if(d==player1 || (d->type==ENT_PLAYER && ((fpsent *)d)->ai))
+        if(d==player1)
         {
             if(d->state!=CS_ALIVE) return;
-            fpsent *pl = (fpsent *)d;
-            if(!m_mp(gamemode)) killed(pl, pl);
-            else if(pl->suicided!=pl->lifesequence)
+            if(!m_mp(gamemode)) killed(player1, player1);
+            else if(suicided!=player1->lifesequence)
             {
-                addmsg(SV_SUICIDE, "rc", pl);
-                pl->suicided = pl->lifesequence;
+                cc.addmsg(SV_SUICIDE, "r");
+                suicided = player1->lifesequence;
             }
         }
-        else if(d->type==ENT_AI) suicidemonster((monster *)d);
-        else if(d->type==ENT_INANIMATE) suicidemovable((movable *)d);
+        else if(d->type==ENT_AI) ((monsterset::monster *)d)->monsterpain(400, player1);
+        else if(d->type==ENT_INANIMATE) ((movableset::movable *)d)->suicide();
     }
-    ICOMMAND(kill, "", (), suicide(player1));
 
-    void drawicon(int icon, float x, float y, float sz)
+    IVARP(hudgun, 0, 1, 1);
+    IVARP(hudgunsway, 0, 1, 1);
+    IVARP(teamhudguns, 0, 1, 1);
+   
+    void drawhudmodel(fpsent *d, int anim, float speed = 0, int base = 0)
     {
-        settexture("packages/hud/items.png");
-        glBegin(GL_QUADS);
-        float tsz = 0.25f, tx = tsz*(icon%4), ty = tsz*(icon/4);
-        glTexCoord2f(tx,     ty);     glVertex2f(x,    y);
-        glTexCoord2f(tx+tsz, ty);     glVertex2f(x+sz, y);
-        glTexCoord2f(tx+tsz, ty+tsz); glVertex2f(x+sz, y+sz);
-        glTexCoord2f(tx,     ty+tsz); glVertex2f(x,    y+sz);
-        glEnd();
-    }
+        if(d->gunselect>GUN_PISTOL) return;
+
+        vec sway;
+        vecfromyawpitch(d->yaw, d->pitch, 1, 0, sway);
+        float swayspeed = sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y);
+        swayspeed = min(4.0f, swayspeed);
+        sway.mul(swayspeed);
+        float swayxy = sinf(swaymillis/115.0f)/100.0f,
+              swayz = cosf(swaymillis/115.0f)/100.0f;
+        swap(sway.x, sway.y);
+        sway.x *= -swayxy;
+        sway.y *= swayxy;
+        sway.z = -fabs(swayspeed*swayz);
+        sway.add(swaydir).add(d->o);
+        if(!hudgunsway()) sway = d->o;
 
-    float abovegameplayhud()
-    {
-        switch(hudplayer()->state)
+#if 0
+        if(player1->state!=CS_DEAD && player1->quadmillis)
         {
-            case CS_EDITING:
-            case CS_SPECTATOR:
-                return 1;
-            default:
-                return 1650.0f/1800.0f;
+            float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f);
+            color.y = color.y*(1-t) + t;
         }
-    }
-
-    int ammohudup[3] = { GUN_CG, GUN_RL, GUN_GL },
-        ammohuddown[3] = { GUN_RIFLE, GUN_SG, GUN_PISTOL },
-        ammohudcycle[7] = { -1, -1, -1, -1, -1, -1, -1 };
-
-    ICOMMAND(ammohudup, "sss", (char *w1, char *w2, char *w3),
-    {
-        int i = 0;
-        if(w1[0]) ammohudup[i++] = atoi(w1);
-        if(w2[0]) ammohudup[i++] = atoi(w2);
-        if(w3[0]) ammohudup[i++] = atoi(w3);
-        while(i < 3) ammohudup[i++] = -1;
-    });
+#endif
 
-    ICOMMAND(ammohuddown, "sss", (char *w1, char *w2, char *w3),
-    {
-        int i = 0;
-        if(w1[0]) ammohuddown[i++] = atoi(w1);
-        if(w2[0]) ammohuddown[i++] = atoi(w2);
-        if(w3[0]) ammohuddown[i++] = atoi(w3);
-        while(i < 3) ammohuddown[i++] = -1;
-    });
+        s_sprintfd(gunname)("hudguns/%s", guns[d->gunselect].file);
+        if((m_teamskins || fr.teamskins()) && teamhudguns()) 
+            s_strcat(gunname, d==player1 || isteam(d->team, player1->team) ? "/blue" : "/red");
+        rendermodel(NULL, gunname, anim, sway, d->yaw+90, d->pitch, MDL_LIGHT, NULL, NULL, base, speed);
+    }
 
-    ICOMMAND(ammohudcycle, "sssssss", (char *w1, char *w2, char *w3, char *w4, char *w5, char *w6, char *w7),
+    void drawhudgun()
     {
-        int i = 0;
-        if(w1[0]) ammohudcycle[i++] = atoi(w1);
-        if(w2[0]) ammohudcycle[i++] = atoi(w2);
-        if(w3[0]) ammohudcycle[i++] = atoi(w3);
-        if(w4[0]) ammohudcycle[i++] = atoi(w4);
-        if(w5[0]) ammohudcycle[i++] = atoi(w5);
-        if(w6[0]) ammohudcycle[i++] = atoi(w6);
-        if(w7[0]) ammohudcycle[i++] = atoi(w7);
-        while(i < 7) ammohudcycle[i++] = -1;
-    });
+        if(!hudgun() || editmode) return;
 
-    VARP(ammohud, 0, 1, 1);
+        fpsent *d = hudplayer();
+        if(d->state==CS_SPECTATOR || d->state==CS_EDITING) return;
 
-    void drawammohud(fpsent *d)
-    {
-        float x = HICON_X + 2*HICON_STEP, y = HICON_Y, sz = HICON_SIZE;
-        glPushMatrix();
-        glScalef(1/3.2f, 1/3.2f, 1);
-        float xup = (x+sz)*3.2f, yup = y*3.2f + 0.1f*sz;
-        loopi(3)
-        {
-            int gun = ammohudup[i];
-            if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue;
-            drawicon(HICON_FIST+gun, xup, yup, sz);
-            yup += sz;
-        }
-        float xdown = x*3.2f - sz, ydown = (y+sz)*3.2f - 0.1f*sz;
-        loopi(3)
-        {
-            int gun = ammohuddown[3-i-1];
-            if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue;
-            ydown -= sz;
-            drawicon(HICON_FIST+gun, xdown, ydown, sz);
-        }
-        int offset = 0, num = 0;
-        loopi(7)
+        int rtime = ws.reloadtime(d->gunselect);
+        if(d->lastaction && d->lastattackgun==d->gunselect && lastmillis-d->lastaction<rtime)
         {
-            int gun = ammohudcycle[i];
-            if(gun < GUN_FIST || gun > GUN_PISTOL) continue;
-            if(gun == d->gunselect) offset = i + 1;
-            else if(d->ammo[gun]) num++;
+            drawhudmodel(d, ANIM_GUNSHOOT, rtime/17.0f, d->lastaction);
         }
-        float xcycle = (x+sz/2)*3.2f + 0.5f*num*sz, ycycle = y*3.2f-sz;
-        loopi(7)
+        else
         {
-            int gun = ammohudcycle[(i + offset)%7];
-            if(gun < GUN_FIST || gun > GUN_PISTOL || gun == d->gunselect || !d->ammo[gun]) continue;
-            xcycle -= sz;
-            drawicon(HICON_FIST+gun, xcycle, ycycle, sz);
+            drawhudmodel(d, ANIM_GUNIDLE|ANIM_LOOP);
         }
-        glPopMatrix();
     }
 
-    void drawhudicons(fpsent *d)
+    void drawicon(float tx, float ty, int x, int y)
     {
-        glPushMatrix();
-        glScalef(2, 2, 1);
-
-        draw_textf("%d", (HICON_X + HICON_SIZE + HICON_SPACE)/2, HICON_TEXTY/2, d->state==CS_DEAD ? 0 : d->health);
-        if(d->state!=CS_DEAD)
-        {
-            if(d->armour) draw_textf("%d", (HICON_X + HICON_STEP + HICON_SIZE + HICON_SPACE)/2, HICON_TEXTY/2, d->armour);
-            draw_textf("%d", (HICON_X + 2*HICON_STEP + HICON_SIZE + HICON_SPACE)/2, HICON_TEXTY/2, d->ammo[d->gunselect]);
-        }
-
-        glPopMatrix();
-
-        drawicon(HICON_HEALTH, HICON_X, HICON_Y);
-        if(d->state!=CS_DEAD)
-        {
-            if(d->armour) drawicon(HICON_BLUE_ARMOUR+d->armourtype, HICON_X + HICON_STEP, HICON_Y);
-            drawicon(HICON_FIST+d->gunselect, HICON_X + 2*HICON_STEP, HICON_Y);
-            if(d->quadmillis) drawicon(HICON_QUAD, HICON_X + 3*HICON_STEP, HICON_Y);
-            if(ammohud) drawammohud(d);
-        }
+        settexture("packages/hud/items.png");
+        glBegin(GL_QUADS);
+        tx /= 384;
+        ty /= 128;
+        int s = 120;
+        glTexCoord2f(tx,        ty);        glVertex2f(x,   y);
+        glTexCoord2f(tx+1/6.0f, ty);        glVertex2f(x+s, y);
+        glTexCoord2f(tx+1/6.0f, ty+1/2.0f); glVertex2f(x+s, y+s);
+        glTexCoord2f(tx,        ty+1/2.0f); glVertex2f(x,   y+s);
+        glEnd();
+    }
+ 
+    float abovegameplayhud()
+    {
+        return 1650.0f/1800.0f;
     }
 
     void gameplayhud(int w, int h)
     {
-        glPushMatrix();
-        glScalef(h/1800.0f, h/1800.0f, 1);
-
         if(player1->state==CS_SPECTATOR)
         {
+            glLoadIdentity();
+            glOrtho(0, w*1800/h, 1800, 0, -1, 1);
+
             int pw, ph, tw, th, fw, fh;
             text_bounds("  ", pw, ph);
             text_bounds("SPECTATOR", tw, th);
@@ -796,23 +829,53 @@ namespace game
         }
 
         fpsent *d = hudplayer();
-        if(d->state!=CS_EDITING)
+        if(d->state==CS_EDITING) return;
+
+        if(d->state==CS_SPECTATOR)
         {
-            if(d->state!=CS_SPECTATOR) drawhudicons(d);
-            if(cmode) cmode->drawhud(d, w, h);
+            if(m_capture || m_ctf)
+            {
+                glLoadIdentity();
+                glOrtho(0, w*1800/h, 1800, 0, -1, 1);
+                if(m_capture) cpc.capturehud(d, w, h);
+                else if(m_ctf) ctf.drawhud(d, w, h);
+            }
+            return;
         }
 
-        glPopMatrix();
-    }
+        glLoadIdentity();
+        glOrtho(0, w*900/h, 900, 0, -1, 1);
 
-    int clipconsole(int w, int h)
-    {
-        if(cmode) return cmode->clipconsole(w, h);
-        return 0;
+        draw_textf("%d",  90, 822, d->state==CS_DEAD ? 0 : d->health);
+        if(d->state!=CS_DEAD)
+        {
+            if(d->armour) draw_textf("%d", 390, 822, d->armour);
+            draw_textf("%d", 690, 822, d->ammo[d->gunselect]);        
+        }
+
+        glLoadIdentity();
+        glOrtho(0, w*1800/h, 1800, 0, -1, 1);
+
+        glDisable(GL_BLEND);
+
+        drawicon(192, 0, 20, 1650);
+        if(d->state!=CS_DEAD)
+        {
+            if(d->armour) drawicon((float)(d->armourtype*64), 0, 620, 1650);
+            int g = d->gunselect, r = 64;
+            if(g==GUN_PISTOL) { g = 4; r = 0; }
+            drawicon((float)(g*64), (float)r, 1220, 1650);
+        }
+
+        glEnable(GL_BLEND);
+
+        if(m_capture) cpc.capturehud(d, w, h);
+        else if(m_ctf) ctf.drawhud(d, w, h);
+        else if(m_assassin && d==player1) asc.drawhud(w, h);
     }
 
-    VARP(teamcrosshair, 0, 1, 1);
-    VARP(hitcrosshair, 0, 425, 1000);
+    IVARP(teamcrosshair, 0, 1, 1);
+    IVARP(hitcrosshair, 0, 425, 1000);
 
     const char *defaultcrosshair(int index)
     {
@@ -832,10 +895,10 @@ namespace game
         if(d->state!=CS_ALIVE) return 0;
 
         int crosshair = 0;
-        if(lasthit && lastmillis - lasthit < hitcrosshair) crosshair = 2;
-        else if(teamcrosshair)
+        if(lasthit && lastmillis - lasthit < hitcrosshair()) crosshair = 2;
+        else if(teamcrosshair())
         {
-            dynent *o = intersectclosest(d->o, worldpos, d);
+            dynent *o = ws.intersectclosest(d->o, worldpos, d);
             if(o && o->type==ENT_PLAYER && isteam(((fpsent *)o)->team, d->team))
             {
                 crosshair = 1;
@@ -843,12 +906,12 @@ namespace game
             }
         }
 
-        if(crosshair!=1 && !editmode && !m_insta)
+        if(d->gunwait) { r *= 0.5f; g *= 0.5f; b *= 0.5f; }
+        else if(!crosshair && r && g && b && !editmode && !m_noitemsrail)
         {
             if(d->health<=25) { r = 1.0f; g = b = 0; }
             else if(d->health<=50) { r = 1.0f; g = 0.5f; b = 0; }
         }
-        if(d->gunwait) { r *= 0.5f; g *= 0.5f; b *= 0.5f; }
         return crosshair;
     }
 
@@ -864,14 +927,68 @@ namespace game
 #endif
     }
 
+    void particletrack(physent *owner, vec &o, vec &d)
+    {
+        if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
+        float dist = o.dist(d);
+        vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
+        float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
+        d.mul(min(newdist, dist)).add(owner->o);
+        o = ws.hudgunorigin(GUN_PISTOL, owner->o, d, (fpsent *)owner);
+    }
+
+    void newmap(int size)
+    {
+        cc.addmsg(SV_NEWMAP, "ri", size);
+    }
+
+    void edittrigger(const selinfo &sel, int op, int arg1, int arg2, int arg3)
+    {
+        if(gamemode==1) switch(op)
+        {
+            case EDIT_FLIP:
+            case EDIT_COPY:
+            case EDIT_PASTE:
+            case EDIT_DELCUBE:
+            {
+                cc.addmsg(SV_EDITF + op, "ri9i4",
+                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
+                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner);
+                break;
+            }
+            case EDIT_MAT:
+            case EDIT_ROTATE:
+            {
+                cc.addmsg(SV_EDITF + op, "ri9i5",
+                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
+                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
+                   arg1);
+                break;
+            }
+            case EDIT_FACE:
+            case EDIT_TEX:
+            case EDIT_REPLACE:
+            {
+                cc.addmsg(SV_EDITF + op, "ri9i6",
+                   sel.o.x, sel.o.y, sel.o.z, sel.s.x, sel.s.y, sel.s.z, sel.grid, sel.orient,
+                   sel.cx, sel.cxs, sel.cy, sel.cys, sel.corner,
+                   arg1, arg2);
+                break;
+            }
+            case EDIT_REMIP:
+            {
+                cc.addmsg(SV_EDITF + op, "r");
+                break;
+            }
+        }
+    }
+
     bool serverinfostartcolumn(g3d_gui *g, int i)
     {
-        static const char *names[] = { "ping ", "players ", "map ", "mode ", "master ", "host ", "port ", "description " };
-        static const int struts[] =  { 0,       0,          12,     12,      8,         13,      6,       24 };
+        static const char *names[] = { "ping ", "players ", "map ", "mode ", "master ", "host ", "description " };
         if(size_t(i) >= sizeof(names)/sizeof(names[0])) return false;
         g->pushlist();
         g->text(names[i], 0xFFFF80, !i ? "server" : NULL);
-        if(struts[i]) g->strut(struts[i]);
         g->mergehits(true);
         return true;
     }
@@ -882,7 +999,7 @@ namespace game
         g->poplist();
     }
 
-    bool serverinfoentry(g3d_gui *g, int i, const char *name, int port, const char *sdesc, const char *map, int ping, const vector<int> &attr, int np)
+    bool serverinfoentry(g3d_gui *g, int i, const char *name, const char *sdesc, const char *map, int ping, const vector<int> &attr, int np)
     {
         if(ping < 0 || attr.empty() || attr[0]!=PROTOCOL_VERSION)
         {
@@ -896,7 +1013,7 @@ namespace game
                 case 2:
                 case 3:
                 case 4:
-                    if(g->button(" ", 0xFFFFDD)&G3D_UP) return true;
+                    if(g->button(" ", 0xFFFFDD)&G3D_UP) return true; 
                     break;
 
                 case 5:
@@ -904,20 +1021,16 @@ namespace game
                     break;
 
                 case 6:
-                    if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true;
-                    break;
-
-                case 7:
                     if(ping < 0)
                     {
                         if(g->button(sdesc, 0xFFFFDD)&G3D_UP) return true;
                     }
                     else if(g->buttonf("[%s protocol] ", 0xFFFFDD, NULL, attr.empty() ? "unknown" : (attr[0] < PROTOCOL_VERSION ? "older" : "newer"))&G3D_UP) return true;
                     break;
-            }
+            }        
             return false;
         }
-
+    
         switch(i)
         {
             case 0:
@@ -937,41 +1050,47 @@ namespace game
                 break;
 
             case 3:
-                if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=2 ? server::modename(attr[1], "") : "")&G3D_UP) return true;
+                if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=2 ? fpsserver::modestr(attr[1], "") : "")&G3D_UP) return true;
                 break;
 
             case 4:
-                if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=5 ? server::mastermodename(attr[4], "") : "")&G3D_UP) return true;
+                if(g->buttonf("%s ", 0xFFFFDD, NULL, attr.length()>=5 ? fpsserver::mastermodestr(attr[4], "") : "")&G3D_UP) return true;
                 break;
-
+            
             case 5:
                 if(g->buttonf("%s ", 0xFFFFDD, NULL, name)&G3D_UP) return true;
                 break;
 
             case 6:
-                if(g->buttonf("%d ", 0xFFFFDD, NULL, port)&G3D_UP) return true;
-                break;
-
-            case 7:
+            {
                 if(g->buttonf("%.25s", 0xFFFFDD, NULL, sdesc)&G3D_UP) return true;
                 break;
+            }
         }
         return false;
     }
+ 
+    void g3d_gamemenus() { sb.show(); }
 
     // any data written into this vector will get saved with the map data. Must take care to do own versioning, and endianess if applicable. Will not get called when loading maps from other games, so provide defaults.
     void writegamedata(vector<char> &extras) {}
     void readgamedata(vector<char> &extras) {}
 
     const char *gameident() { return "fps"; }
+    const char *defaultmap() { return "metl4"; }
     const char *savedconfig() { return "config.cfg"; }
     const char *defaultconfig() { return "data/defaults.cfg"; }
     const char *autoexec() { return "autoexec.cfg"; }
     const char *savedservers() { return "servers.cfg"; }
+};
+
+REGISTERGAME(fpsgame, "fps", new fpsclient(), new fpsserver());
+
+#else
+
+REGISTERGAME(fpsgame, "fps", NULL, new fpsserver());
+
+#endif
+
 
-    void loadconfigs()
-    {
-        execfile("auth.cfg", false);
-    }
-}
 
diff --git a/fpsgame/fpsrender.h b/fpsgame/fpsrender.h
new file mode 100644
index 0000000..28fa438
--- /dev/null
+++ b/fpsgame/fpsrender.h
@@ -0,0 +1,151 @@
+struct playermodelinfo
+{
+    const char *ffa, *blueteam, *redteam, 
+               *vwep, *quad, *armour[3],
+               *ffaicon, *blueicon, *redicon; 
+};
+
+struct fpsrender
+{      
+    fpsclient &cl;
+
+    fpsrender(fpsclient &_cl) : cl(_cl) {}
+
+    vector<fpsent *> bestplayers;
+    vector<const char *> bestteams;
+
+    IVARP(playermodel, 0, 0, 2);
+
+    const playermodelinfo &getplayermodelinfo()
+    {
+        static const playermodelinfo playermodels[3] =
+        {
+            { "mrfixit", "mrfixit/blue", "mrfixit/red", NULL, "mrfixit/horns", { "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, "mrfixit", "mrfixit_blue", "mrfixit_red" },
+            { "ironsnout", "ironsnout/blue", "ironsnout/red", NULL, "quadspheres", { "shield/blue", "shield/green", "shield/yellow" }, "ironsnout", "ironsnout_blue", "ironsnout_red" },
+            { "monster/ogro", "monster/ogro/blue", "monster/ogro/red", "monster/ogro/vwep", NULL, { NULL, NULL, NULL }, "ogro", "ogro", "ogro" }
+        };
+        return playermodels[playermodel()];
+    }
+
+    void preloadplayermodel()
+    {
+        const playermodelinfo &mdl = getplayermodelinfo();
+        loadmodel(mdl.ffa, -1, true);
+        loadmodel(mdl.blueteam, -1, true);
+        loadmodel(mdl.redteam, -1, true);
+        loadmodel(mdl.vwep, -1, true);
+        loadmodel(mdl.quad, -1, true);
+        loopi(3) loadmodel(mdl.armour[i], -1, true);
+    }
+    
+    IVAR(testquad, 0, 0, 1);
+    IVAR(testarmour, 0, 0, 1);
+
+    void renderplayer(fpsent *d, const playermodelinfo &mdl, int team)
+    {
+        int lastaction = d->lastaction, attack = d->gunselect==GUN_FIST ? ANIM_PUNCH : ANIM_SHOOT, delay = mdl.vwep ? 300 : cl.ws.reloadtime(d->gunselect)+50;
+        if(cl.intermission && d->state!=CS_DEAD)
+        {
+            lastaction = cl.lastmillis;
+            attack = ANIM_LOSE|ANIM_LOOP;
+            delay = 1000;
+            int gamemode = cl.gamemode;
+            if(m_teammode) loopv(bestteams) { if(!strcmp(bestteams[i], d->team)) { attack = ANIM_WIN|ANIM_LOOP; break; } }
+            else if(bestplayers.find(d)>=0) attack = ANIM_WIN|ANIM_LOOP;
+        }
+        else if(d->state==CS_ALIVE && d->lasttaunt && cl.lastmillis-d->lasttaunt<1000 && cl.lastmillis-d->lastaction>delay)
+        {
+            lastaction = d->lasttaunt;
+            attack = ANIM_TAUNT;
+            delay = 1000;
+        }
+        modelattach a[4] = { { NULL }, { NULL }, { NULL }, { NULL } };
+        static const char *vweps[] = {"vwep/fist", "vwep/shotg", "vwep/chaing", "vwep/rocket", "vwep/rifle", "vwep/gl", "vwep/pistol"};
+        int ai = 0;
+        if((!mdl.vwep || d->gunselect!=GUN_FIST) && d->gunselect<=GUN_PISTOL)
+        {
+            a[ai].name = mdl.vwep ? mdl.vwep : vweps[d->gunselect];
+            a[ai].tag = "tag_weapon";
+            a[ai].anim = ANIM_VWEP|ANIM_LOOP;
+            a[ai].basetime = 0;
+            ai++;
+        }
+        if(d->state==CS_ALIVE)
+        {
+            if((testquad() || d->quadmillis) && mdl.quad)
+            {
+                a[ai].name = mdl.quad;
+                a[ai].tag = "tag_powerup";
+                a[ai].anim = ANIM_POWERUP|ANIM_LOOP;
+                a[ai].basetime = 0;
+                ai++;
+            }
+            if(testarmour() || d->armour)
+            {
+                int type = clamp(d->armourtype, (int)A_BLUE, (int)A_YELLOW);
+                if(mdl.armour[type])
+                {
+                    a[ai].name = mdl.armour[type];
+                    a[ai].tag = "tag_shield";
+                    a[ai].anim = ANIM_SHIELD|ANIM_LOOP;
+                    a[ai].basetime = 0;
+                    ai++;
+                }
+            }
+        }
+        const char *mdlname = mdl.ffa;
+        switch(team)
+        {
+            case 1: mdlname = mdl.blueteam; break;
+            case 2: mdlname = mdl.redteam; break;
+        }
+        renderclient(d, mdlname, a[0].name ? a : NULL, attack, delay, lastaction, cl.intermission ? 0 : d->lastpain);
+#if 0
+        if(d->state!=CS_DEAD && d->quadmillis) 
+        {
+            vec color(1, 1, 1), dir(0, 0, 1);
+            rendermodel(color, dir, "quadrings", ANIM_MAPMODEL|ANIM_LOOP, vec(d->o).sub(vec(0, 0, d->eyeheight/2)), 360*cl.lastmillis/1000.0f, 0, MDL_DYNSHADOW | MDL_CULL_VFC | MDL_CULL_DIST);
+        }
+#endif
+    }
+
+    IVARP(teamskins, 0, 0, 1);
+
+    void rendergame(int gamemode)
+    {
+        if(cl.intermission)
+        {
+            if(m_teammode) { bestteams.setsize(0); cl.sb.bestteams(bestteams); }
+            else { bestplayers.setsize(0); cl.sb.bestplayers(bestplayers); }
+        }
+
+        startmodelbatches();
+
+        const playermodelinfo &mdl = getplayermodelinfo();
+
+        fpsent *exclude = NULL;
+        if(cl.player1->state==CS_SPECTATOR && cl.following>=0 && !isthirdperson())
+            exclude = cl.getclient(cl.following);
+
+        fpsent *d;
+        loopv(cl.players) if((d = cl.players[i]) && d->state!=CS_SPECTATOR && d->state!=CS_SPAWNING && d!=exclude)
+        {
+            int team = 0;
+            if(m_assassin) team = cl.asc.targets.find(d)>=0 ? 2 : (cl.asc.hunters.find(d)>=0 ? 0 : 1);
+            else if(teamskins() || m_teammode) team = isteam(cl.player1->team, d->team) ? 1 : 2;
+            if(d->state!=CS_DEAD || d->superdamage<50) renderplayer(d, mdl, team);
+            s_strcpy(d->info, cl.colorname(d, NULL, "@"));
+            if(d->maxhealth>100) { s_sprintfd(sn)(" +%d", d->maxhealth-100); s_strcat(d->info, sn); }
+            if(d->state!=CS_DEAD) particle_text(d->abovehead(), d->info, team ? (team==1 ? 16 : 13) : 11, 1);
+        }
+        if(isthirdperson() && !cl.followingplayer()) renderplayer(cl.player1, mdl, teamskins() || m_teamskins ? 1 : 0);
+        cl.ms.monsterrender();
+        cl.mo.render();
+        cl.et.renderentities();
+        cl.ws.renderprojectiles();
+        if(m_capture) cl.cpc.renderbases();
+        else if(m_ctf) cl.ctf.renderflags();
+
+        endmodelbatches();
+    }
+};
diff --git a/fpsgame/fpsserver.h b/fpsgame/fpsserver.h
new file mode 100644
index 0000000..5e7e6ac
--- /dev/null
+++ b/fpsgame/fpsserver.h
@@ -0,0 +1,2099 @@
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#define _dup    dup
+#define _fileno fileno
+#endif
+
+struct fpsserver : igameserver
+{
+    struct server_entity            // server side version of "entity" type
+    {
+        int type;
+        int spawntime;
+        char spawned;
+    };
+
+    static const int DEATHMILLIS = 300;
+
+    enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_SUICIDE, GE_PICKUP };
+
+    struct shotevent
+    {
+        int type;
+        int millis, id;
+        int gun;
+        float from[3], to[3];
+    };
+
+    struct explodeevent
+    {
+        int type;
+        int millis, id;
+        int gun;
+    };
+
+    struct hitevent
+    {
+        int type;
+        int target;
+        int lifesequence;
+        union
+        {
+            int rays;
+            float dist;
+        };
+        float dir[3];
+    };
+
+    struct suicideevent
+    {
+        int type;
+    };
+
+    struct pickupevent
+    {
+        int type;
+        int ent;
+    };
+
+    union gameevent
+    {
+        int type;
+        shotevent shot;
+        explodeevent explode;
+        hitevent hit;
+        suicideevent suicide;
+        pickupevent pickup;
+    };
+
+    template <int N>
+    struct projectilestate
+    {
+        int projs[N];
+        int numprojs;
+
+        projectilestate() : numprojs(0) {}
+
+        void reset() { numprojs = 0; }
+
+        void add(int val)
+        {
+            if(numprojs>=N) numprojs = 0;
+            projs[numprojs++] = val;
+        }
+
+        bool remove(int val)
+        {
+            loopi(numprojs) if(projs[i]==val)
+            {
+                projs[i] = projs[--numprojs];
+                return true;
+            }
+            return false;
+        }
+    };
+
+    struct gamestate : fpsstate
+    {
+        vec o;
+        int state, editstate;
+        int lastdeath, lastspawn, lifesequence;
+        int lastshot;
+        projectilestate<8> rockets, grenades;
+        int frags, deaths, teamkills, shotdamage, damage;
+        int lasttimeplayed, timeplayed;
+        float effectiveness;
+
+        gamestate() : state(CS_DEAD), editstate(CS_DEAD) {}
+    
+        bool isalive(int gamemillis)
+        {
+            return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS);
+        }
+
+        bool waitexpired(int gamemillis)
+        {
+            return gamemillis - lastshot >= gunwait;
+        }
+
+        void reset()
+        {
+            if(state!=CS_SPECTATOR) state = editstate = CS_DEAD;
+            lifesequence = 0;
+            maxhealth = 100;
+            rockets.reset();
+            grenades.reset();
+
+            timeplayed = 0;
+            effectiveness = 0;
+            frags = deaths = teamkills = shotdamage = damage = 0;
+
+            respawn();
+        }
+
+        void respawn()
+        {
+            fpsstate::respawn();
+            o = vec(-1e10f, -1e10f, -1e10f);
+            lastdeath = 0;
+            lastspawn = -1;
+            lastshot = 0;
+        }
+    };
+
+    struct savedscore
+    {
+        uint ip;
+        string name;
+        int maxhealth, frags, deaths, teamkills, shotdamage, damage;
+        int timeplayed;
+        float effectiveness;
+
+        void save(gamestate &gs)
+        {
+            maxhealth = gs.maxhealth;
+            frags = gs.frags;
+            deaths = gs.deaths;
+            teamkills = gs.teamkills;
+            shotdamage = gs.shotdamage;
+            damage = gs.damage;
+            timeplayed = gs.timeplayed;
+            effectiveness = gs.effectiveness;
+        }
+
+        void restore(gamestate &gs)
+        {
+            if(gs.health==gs.maxhealth) gs.health = maxhealth;
+            gs.maxhealth = maxhealth;
+            gs.frags = frags;
+            gs.deaths = deaths;
+            gs.teamkills = teamkills;
+            gs.shotdamage = shotdamage;
+            gs.damage = damage;
+            gs.timeplayed = timeplayed;
+            gs.effectiveness = effectiveness;
+        }
+    };
+
+    struct clientinfo
+    {
+        int clientnum;
+        string name, team, mapvote;
+        int modevote;
+        int privilege;
+        bool spectator, local, timesync, wantsmaster;
+        int gameoffset, lastevent;
+        gamestate state;
+        vector<gameevent> events;
+        vector<uchar> position, messages;
+        vector<clientinfo *> targets;
+
+        clientinfo() { reset(); }
+
+        gameevent &addevent()
+        {
+            static gameevent dummy;
+            if(state.state==CS_SPECTATOR || events.length()>100) return dummy;
+            return events.add();
+        }
+
+        void mapchange()
+        {
+            mapvote[0] = 0;
+            state.reset();
+            events.setsizenodelete(0);
+            targets.setsizenodelete(0);
+            timesync = false;
+            lastevent = 0;
+        }
+
+        void reset()
+        {
+            name[0] = team[0] = 0;
+            privilege = PRIV_NONE;
+            spectator = local = wantsmaster = false;
+            position.setsizenodelete(0);
+            messages.setsizenodelete(0);
+            mapchange();
+        }
+    };
+
+    struct worldstate
+    {
+        int uses;
+        vector<uchar> positions, messages;
+    };
+
+    struct ban
+    {
+        int time;
+        uint ip;
+    };
+  
+    #define MM_MODE 0xF
+    #define MM_AUTOAPPROVE 0x1000
+    #define MM_DEFAULT (MM_MODE | MM_AUTOAPPROVE)
+
+    enum { MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE };
+ 
+    bool notgotitems, notgotbases;        // true when map has changed and waiting for clients to send item
+    int gamemode;
+    int gamemillis, gamelimit;
+
+    string serverdesc;
+    string smapname;
+    int lastmillis, totalmillis, curtime;
+    int interm, minremain;
+    bool mapreload;
+    enet_uint32 lastsend;
+    int mastermode, mastermask;
+    int currentmaster;
+    bool masterupdate;
+    string masterpass;
+    FILE *mapdata;
+
+    vector<uint> allowedips;
+    vector<ban> bannedips;
+    vector<clientinfo *> clients;
+    vector<worldstate *> worldstates;
+    bool reliablemessages;
+
+    struct demofile
+    {
+        string info;
+        uchar *data;
+        int len;
+    };
+
+    #define MAXDEMOS 5
+    vector<demofile> demos;
+
+    bool demonextmatch;
+    FILE *demotmp;
+    gzFile demorecord, demoplayback;
+    int nextplayback;
+
+    struct servmode
+    {
+        fpsserver &sv;
+
+        servmode(fpsserver &sv) : sv(sv) {}
+        virtual ~servmode() {}
+
+        virtual void entergame(clientinfo *ci) {}
+        virtual void leavegame(clientinfo *ci, bool disconnecting = false) {}
+
+        virtual void moved(clientinfo *ci, const vec &oldpos, const vec &newpos) {}
+        virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; }
+        virtual void spawned(clientinfo *ci) {}
+        virtual int fragvalue(clientinfo *victim, clientinfo *actor)
+        {
+            int gamemode = sv.gamemode;
+            if(victim==actor || isteam(victim->team, actor->team)) return -1;
+            return 1;
+        }
+        virtual void died(clientinfo *victim, clientinfo *actor) {}
+        virtual bool canchangeteam(clientinfo *ci, const char *oldteam, const char *newteam) { return true; }
+        virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {}
+        virtual void initclient(clientinfo *ci, ucharbuf &p, bool connecting) {}
+        virtual void update() {}
+        virtual void reset(bool empty) {}
+        virtual void intermission() {}
+    };
+
+    struct arenaservmode : servmode
+    {
+        int arenaround;
+
+        arenaservmode(fpsserver &sv) : servmode(sv), arenaround(0) {}
+
+        bool canspawn(clientinfo *ci, bool connecting = false) 
+        { 
+            if(connecting && sv.nonspectators(ci->clientnum)<=1) return true;
+            return false; 
+        }
+
+        void reset(bool empty)
+        {
+            arenaround = 0;
+        }
+    
+        void update()
+        {
+            if(sv.interm || sv.gamemillis<arenaround || !sv.nonspectators()) return;
+    
+            if(arenaround)
+            {
+                arenaround = 0;
+                loopv(sv.clients) if(sv.clients[i]->state.state==CS_DEAD || sv.clients[i]->state.state==CS_ALIVE) 
+                {
+                    sv.clients[i]->state.respawn();
+                    sv.sendspawn(sv.clients[i]);
+                }
+                return;
+            }
+
+            int gamemode = sv.gamemode;
+            clientinfo *alive = NULL;
+            bool dead = false;
+            loopv(sv.clients)
+            {
+                clientinfo *ci = sv.clients[i];
+                if(ci->state.state==CS_ALIVE || (ci->state.state==CS_DEAD && ci->state.lastspawn>=0))
+                {
+                    if(!alive) alive = ci;
+                    else if(!m_teammode || strcmp(alive->team, ci->team)) return;
+                }
+                else if(ci->state.state==CS_DEAD) dead = true;
+            }
+            if(!dead) return;
+            sendf(-1, 1, "ri2", SV_ARENAWIN, !alive ? -1 : alive->clientnum);
+            arenaround = sv.gamemillis+5000;
+        }
+    };
+
+    #define CAPTURESERV 1
+    #include "capture.h"
+    #undef CAPTURESERV
+
+    #define ASSASSINSERV 1
+    #include "assassin.h"
+    #undef ASSASSINSERV
+
+    #define CTFSERV 1
+    #include "ctf.h"
+    #undef CTFSERV
+
+    arenaservmode arenamode;
+    captureservmode capturemode;
+    assassinservmode assassinmode;
+    ctfservmode ctfmode;
+    servmode *smode;
+
+    fpsserver() : notgotitems(true), notgotbases(false), gamemode(0), interm(0), minremain(0), mapreload(false), lastsend(0), mastermode(MM_OPEN), mastermask(MM_DEFAULT), currentmaster(-1), masterupdate(false), mapdata(NULL), reliablemessages(false), demonextmatch(false), demotmp(NULL), demorecord(NULL), demoplayback(NULL), nextplayback(0), arenamode(*this), capturemode(*this), assassinmode(*this), ctfmode(*this), smode(NULL) 
+    {
+        serverdesc[0] = '\0';
+        masterpass[0] = '\0';
+    }
+
+    void *newinfo() { return new clientinfo; }
+    void deleteinfo(void *ci) { delete (clientinfo *)ci; } 
+    
+    vector<server_entity> sents;
+    vector<savedscore> scores;
+
+    static const char *modestr(int n, const char *unknown = "unknown")
+    {
+        static const char *modenames[] =
+        {
+            "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay",
+            "instagib", "instagib team", "efficiency", "efficiency team",
+            "insta arena", "insta clan arena", "tactics arena", "tactics clan arena",
+            "capture", "insta capture", "regen capture", "assassin", "insta assassin",
+            "ctf", "insta ctf"
+        };
+        return (n>=-5 && size_t(n+5)<sizeof(modenames)/sizeof(modenames[0])) ? modenames[n+5] : unknown;
+    }
+
+    static const char *mastermodestr(int n, const char *unknown = "unknown")
+    {
+        static const char *mastermodenames[] =
+        {
+            "open", "veto", "locked", "private"
+        };
+        return (n>=0 && size_t(n)<sizeof(mastermodenames)/sizeof(mastermodenames[0])) ? mastermodenames[n] : unknown;
+    }
+
+    void sendservmsg(const char *s) { sendf(-1, 1, "ris", SV_SERVMSG, s); }
+
+    void resetitems() 
+    { 
+        sents.setsize(0);
+        //cps.reset(); 
+    }
+
+    int spawntime(int type)
+    {
+        if(m_classicsp) return INT_MAX;
+        int np = nonspectators();
+        np = np<3 ? 4 : (np>4 ? 2 : 3);         // spawn times are dependent on number of players
+        int sec = 0;
+        switch(type)
+        {
+            case I_SHELLS:
+            case I_BULLETS:
+            case I_ROCKETS:
+            case I_ROUNDS:
+            case I_GRENADES:
+            case I_CARTRIDGES: sec = np*4; break;
+            case I_HEALTH: sec = np*5; break;
+            case I_GREENARMOUR:
+            case I_YELLOWARMOUR: sec = 20; break;
+            case I_BOOST:
+            case I_QUAD: sec = 40+rnd(40); break;
+        }
+        return sec*1000;
+    }
+        
+    bool pickup(int i, int sender)         // server side item pickup, acknowledge first client that gets it
+    {
+        if(minremain<=0 || !sents.inrange(i) || !sents[i].spawned) return false;
+        clientinfo *ci = (clientinfo *)getinfo(sender);
+        if(!ci || (!ci->local && !ci->state.canpickup(sents[i].type))) return false;
+        sents[i].spawned = false;
+        sents[i].spawntime = spawntime(sents[i].type);
+        sendf(-1, 1, "ri3", SV_ITEMACC, i, sender);
+        ci->state.pickup(sents[i].type);
+        return true;
+    }
+
+    void vote(char *map, int reqmode, int sender)
+    {
+        clientinfo *ci = (clientinfo *)getinfo(sender);
+        if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege)) return;
+        s_strcpy(ci->mapvote, map);
+        ci->modevote = reqmode;
+        if(!ci->mapvote[0]) return;
+        if(ci->local || mapreload || (ci->privilege && mastermode>=MM_VETO))
+        {
+            if(demorecord) enddemorecord();
+            if(!ci->local && !mapreload) 
+            {
+                s_sprintfd(msg)("%s forced %s on map %s", privname(ci->privilege), modestr(reqmode), map);
+                sendservmsg(msg);
+            }
+            sendf(-1, 1, "risii", SV_MAPCHANGE, ci->mapvote, ci->modevote, 1);
+            changemap(ci->mapvote, ci->modevote);
+        }
+        else 
+        {
+            s_sprintfd(msg)("%s suggests %s on map %s (select map to vote)", colorname(ci), modestr(reqmode), map);
+            sendservmsg(msg);
+            checkvotes();
+        }
+    }
+
+    clientinfo *choosebestclient(float &bestrank)
+    {
+        clientinfo *best = NULL;
+        bestrank = -1;
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            if(ci->state.timeplayed<0) continue;
+            float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1;
+            if(!best || rank > bestrank) { best = ci; bestrank = rank; }
+        }
+        return best;
+    }  
+
+    void autoteam()
+    {
+        static const char *teamnames[2] = {"good", "evil"};
+        vector<clientinfo *> team[2];
+        float teamrank[2] = {0, 0};
+        for(int round = 0, remaining = clients.length(); remaining>=0; round++)
+        {
+            int first = round&1, second = (round+1)&1, selected = 0;
+            while(teamrank[first] <= teamrank[second])
+            {
+                float rank;
+                clientinfo *ci = choosebestclient(rank);
+                if(!ci) break;
+                if(m_capture || m_ctf) rank = 1;
+                else if(selected && rank<=0) break;    
+                ci->state.timeplayed = -1;
+                team[first].add(ci);
+                if(rank>0) teamrank[first] += rank;
+                selected++;
+                if(rank<=0) break;
+            }
+            if(!selected) break;
+            remaining -= selected;
+        }
+        loopi(sizeof(team)/sizeof(team[0]))
+        {
+            loopvj(team[i])
+            {
+                clientinfo *ci = team[i][j];
+                if(!strcmp(ci->team, teamnames[i])) continue;
+                s_strncpy(ci->team, teamnames[i], MAXTEAMLEN+1);
+                sendf(-1, 1, "riis", SV_SETTEAM, ci->clientnum, teamnames[i]);
+            }
+        }
+    }
+
+    struct teamrank
+    {
+        const char *name;
+        float rank;
+        int clients;
+
+        teamrank(const char *name) : name(name), rank(0), clients(0) {}
+    };
+    
+    const char *chooseworstteam(const char *suggest = NULL, clientinfo *exclude = NULL)
+    {
+        teamrank teamranks[2] = { teamrank("good"), teamrank("evil") };
+        const int numteams = sizeof(teamranks)/sizeof(teamranks[0]);
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            if(ci==exclude || ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
+            ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
+            ci->state.lasttimeplayed = lastmillis;
+
+            loopj(numteams) if(!strcmp(ci->team, teamranks[j].name)) 
+            { 
+                teamrank &ts = teamranks[j];
+                ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
+                ts.clients++;
+                break;
+            }
+        }
+        teamrank *worst = &teamranks[numteams-1];
+        loopi(numteams-1)
+        {
+            teamrank &ts = teamranks[i];
+            if(m_capture || m_ctf)
+            {
+                if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
+            }
+            else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
+        }
+        return worst->name;
+    }
+
+    void writedemo(int chan, void *data, int len)
+    {
+        if(!demorecord) return;
+        int stamp[3] = { gamemillis, chan, len };
+        endianswap(stamp, sizeof(int), 3);
+        gzwrite(demorecord, stamp, sizeof(stamp));
+        gzwrite(demorecord, data, len);
+    }
+
+    void recordpacket(int chan, void *data, int len)
+    {
+        writedemo(chan, data, len);
+    }
+
+    void enddemorecord()
+    {
+        if(!demorecord) return;
+
+        gzclose(demorecord);
+        demorecord = NULL;
+
+#ifdef WIN32
+        demotmp = fopen("demorecord", "rb");
+#endif    
+        if(!demotmp) return;
+
+        fseek(demotmp, 0, SEEK_END);
+        int len = ftell(demotmp);
+        rewind(demotmp);
+        if(demos.length()>=MAXDEMOS)
+        {
+            delete[] demos[0].data;
+            demos.remove(0);
+        }
+        demofile &d = demos.add();
+        time_t t = time(NULL);
+        char *timestr = ctime(&t), *trim = timestr + strlen(timestr);
+        while(trim>timestr && isspace(*--trim)) *trim = '\0';
+        s_sprintf(d.info)("%s: %s, %s, %.2f%s", timestr, modestr(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
+        s_sprintfd(msg)("demo \"%s\" recorded", d.info);
+        sendservmsg(msg);
+        d.data = new uchar[len];
+        d.len = len;
+        fread(d.data, 1, len, demotmp);
+        fclose(demotmp);
+        demotmp = NULL;
+    }
+
+    void setupdemorecord()
+    {
+        if(haslocalclients() || !m_mp(gamemode) || gamemode==1) return;
+
+#ifdef WIN32
+        gzFile f = gzopen("demorecord", "wb9");
+        if(!f) return;
+#else
+        demotmp = tmpfile();
+        if(!demotmp) return;
+        setvbuf(demotmp, NULL, _IONBF, 0);
+
+        gzFile f = gzdopen(_dup(_fileno(demotmp)), "wb9");
+        if(!f)
+        {
+            fclose(demotmp);
+            demotmp = NULL;
+            return;
+        }
+#endif
+
+        sendservmsg("recording demo");
+
+        demorecord = f;
+
+        demoheader hdr;
+        memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
+        hdr.version = DEMO_VERSION;
+        hdr.protocol = PROTOCOL_VERSION;
+        endianswap(&hdr.version, sizeof(int), 1);
+        endianswap(&hdr.protocol, sizeof(int), 1);
+        gzwrite(demorecord, &hdr, sizeof(demoheader));
+
+        ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0);
+        ucharbuf p(packet->data, packet->dataLength);
+        welcomepacket(p, -1, packet);
+        writedemo(1, p.buf, p.len);
+        enet_packet_destroy(packet);
+
+        uchar buf[MAXTRANS];
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            uchar header[16];
+            ucharbuf q(&buf[sizeof(header)], sizeof(buf)-sizeof(header));
+            putint(q, SV_INITC2S);
+            sendstring(ci->name, q);
+            sendstring(ci->team, q);
+
+            ucharbuf h(header, sizeof(header));
+            putint(h, SV_CLIENT);
+            putint(h, ci->clientnum);
+            putuint(h, q.len);
+
+            memcpy(&buf[sizeof(header)-h.len], header, h.len);
+
+            writedemo(1, &buf[sizeof(header)-h.len], h.len+q.len);
+        }
+    }
+
+    void listdemos(int cn)
+    {
+        ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+        if(!packet) return;
+        ucharbuf p(packet->data, packet->dataLength);
+        putint(p, SV_SENDDEMOLIST);
+        putint(p, demos.length());
+        loopv(demos) sendstring(demos[i].info, p);
+        enet_packet_resize(packet, p.length());
+        sendpacket(cn, 1, packet);
+        if(!packet->referenceCount) enet_packet_destroy(packet);
+    }
+
+    void cleardemos(int n)
+    {
+        if(!n)
+        {
+            loopv(demos) delete[] demos[i].data;
+            demos.setsize(0);
+            sendservmsg("cleared all demos");
+        }
+        else if(demos.inrange(n-1))
+        {
+            delete[] demos[n-1].data;
+            demos.remove(n-1);
+            s_sprintfd(msg)("cleared demo %d", n);
+            sendservmsg(msg);
+        }
+    }
+
+    void senddemo(int cn, int num)
+    {
+        if(!num) num = demos.length();
+        if(!demos.inrange(num-1)) return;
+        demofile &d = demos[num-1];
+        sendf(cn, 2, "rim", SV_SENDDEMO, d.len, d.data); 
+    }
+
+    void setupdemoplayback()
+    {
+        demoheader hdr;
+        string msg;
+        msg[0] = '\0';
+        s_sprintfd(file)("%s.dmo", smapname);
+        demoplayback = opengzfile(file, "rb9");
+        if(!demoplayback) s_sprintf(msg)("could not read demo \"%s\"", file);
+        else if(gzread(demoplayback, &hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
+            s_sprintf(msg)("\"%s\" is not a demo file", file);
+        else 
+        { 
+            endianswap(&hdr.version, sizeof(int), 1);
+            endianswap(&hdr.protocol, sizeof(int), 1);
+            if(hdr.version!=DEMO_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer");
+            else if(hdr.protocol!=PROTOCOL_VERSION) s_sprintf(msg)("demo \"%s\" requires an %s version of Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
+        }
+        if(msg[0])
+        {
+            if(demoplayback) { gzclose(demoplayback); demoplayback = NULL; }
+            sendservmsg(msg);
+            return;
+        }
+
+        s_sprintf(msg)("playing demo \"%s\"", file);
+        sendservmsg(msg);
+
+        sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 1);
+
+        if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
+        {
+            enddemoplayback();
+            return;
+        }
+        endianswap(&nextplayback, sizeof(nextplayback), 1);
+    }
+
+    void enddemoplayback()
+    {
+        if(!demoplayback) return;
+        gzclose(demoplayback);
+        demoplayback = NULL;
+
+        sendf(-1, 1, "rii", SV_DEMOPLAYBACK, 0);
+
+        sendservmsg("demo playback finished");
+
+        loopv(clients)
+        {
+            ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
+            ucharbuf p(packet->data, packet->dataLength);
+            welcomepacket(p, clients[i]->clientnum, packet);
+            enet_packet_resize(packet, p.length());
+            sendpacket(clients[i]->clientnum, 1, packet);
+            if(!packet->referenceCount) enet_packet_destroy(packet);
+        }
+    }
+
+    void readdemo()
+    {
+        if(!demoplayback) return;
+        while(gamemillis>=nextplayback)
+        {
+            int chan, len;
+            if(gzread(demoplayback, &chan, sizeof(chan))!=sizeof(chan) ||
+               gzread(demoplayback, &len, sizeof(len))!=sizeof(len))
+            {
+                enddemoplayback();
+                return;
+            }
+            endianswap(&chan, sizeof(chan), 1);
+            endianswap(&len, sizeof(len), 1);
+            ENetPacket *packet = enet_packet_create(NULL, len, 0);
+            if(!packet || gzread(demoplayback, packet->data, len)!=len)
+            {
+                if(packet) enet_packet_destroy(packet);
+                enddemoplayback();
+                return;
+            }
+            sendpacket(-1, chan, packet);
+            if(!packet->referenceCount) enet_packet_destroy(packet);
+            if(gzread(demoplayback, &nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
+            {
+                enddemoplayback();
+                return;
+            }
+            endianswap(&nextplayback, sizeof(nextplayback), 1);
+        }
+    }
+ 
+    void changemap(const char *s, int mode)
+    {
+        if(m_demo) enddemoplayback();
+        else enddemorecord();
+
+        mapreload = false;
+        gamemode = mode;
+        gamemillis = 0;
+        minremain = m_teammode && !m_ctf ? 15 : 10;
+        gamelimit = minremain*60000;
+        interm = 0;
+        s_strcpy(smapname, s);
+        resetitems();
+        notgotitems = true;
+        scores.setsize(0);
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
+        }
+        if(m_teammode) autoteam();
+
+        if(m_arena) smode = &arenamode;
+        else if(m_capture) smode = &capturemode;
+        else if(m_assassin) smode = &assassinmode;
+        else if(m_ctf) smode = &ctfmode;
+        else smode = NULL;
+        if(smode) smode->reset(false);
+
+        if(gamemode>1 || (gamemode==0 && hasnonlocalclients())) sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            ci->mapchange();
+            ci->state.lasttimeplayed = lastmillis;
+            if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
+        }
+
+        if(m_demo) setupdemoplayback();
+        else if(demonextmatch)
+        {
+            demonextmatch = false;
+            setupdemorecord();
+        }
+    }
+
+    savedscore &findscore(clientinfo *ci, bool insert)
+    {
+        uint ip = getclientip(ci->clientnum);
+        if(!ip) return *(savedscore *)0;
+        if(!insert) 
+        {
+            loopv(clients)
+            {
+                clientinfo *oi = clients[i];
+                if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name))
+                {
+                    oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed;
+                    oi->state.lasttimeplayed = lastmillis;
+                    static savedscore curscore;
+                    curscore.save(oi->state);
+                    return curscore;
+                }
+            }
+        }
+        loopv(scores)
+        {
+            savedscore &sc = scores[i];
+            if(sc.ip == ip && !strcmp(sc.name, ci->name)) return sc;
+        }
+        if(!insert) return *(savedscore *)0;
+        savedscore &sc = scores.add();
+        sc.ip = ip;
+        s_strcpy(sc.name, ci->name);
+        return sc;
+    }
+
+    void savescore(clientinfo *ci)
+    {
+        savedscore &sc = findscore(ci, true);
+        if(&sc) sc.save(ci->state);
+    }
+
+    struct votecount
+    {
+        char *map;
+        int mode, count;
+        votecount() {}
+        votecount(char *s, int n) : map(s), mode(n), count(0) {}
+    };
+
+    void checkvotes(bool force = false)
+    {
+        vector<votecount> votes;
+        int maxvotes = 0;
+        loopv(clients)
+        {
+            clientinfo *oi = clients[i];
+            if(oi->state.state==CS_SPECTATOR && !oi->privilege) continue;
+            maxvotes++;
+            if(!oi->mapvote[0]) continue;
+            votecount *vc = NULL;
+            loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode)
+            { 
+                vc = &votes[j];
+                break;
+            }
+            if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote));
+            vc->count++;
+        }
+        votecount *best = NULL;
+        loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i];
+        if(force || (best && best->count > maxvotes/2))
+        {
+            if(demorecord) enddemorecord();
+            if(best && (best->count > (force ? 1 : maxvotes/2)))
+            { 
+                sendservmsg(force ? "vote passed by default" : "vote passed by majority");
+                sendf(-1, 1, "risii", SV_MAPCHANGE, best->map, best->mode, 1);
+                changemap(best->map, best->mode); 
+            }
+            else
+            {
+                mapreload = true;
+                if(clients.length()) sendf(-1, 1, "ri", SV_MAPRELOAD);
+            }
+        }
+    }
+
+    int nonspectators(int exclude = -1)
+    {
+        int n = 0;
+        loopv(clients) if(i!=exclude && clients[i]->state.state!=CS_SPECTATOR) n++;
+        return n;
+    }
+
+    int checktype(int type, clientinfo *ci)
+    {
+        if(ci && ci->local) return type;
+#if 0
+        // other message types can get sent by accident if a master forces spectator on someone, so disabling this case for now and checking for spectator state in message handlers
+        // spectators can only connect and talk
+        static int spectypes[] = { SV_INITC2S, SV_POS, SV_TEXT, SV_PING, SV_CLIENTPING, SV_GETMAP, SV_SETMASTER };
+        if(ci && ci->state.state==CS_SPECTATOR && !ci->privilege)
+        {
+            loopi(sizeof(spectypes)/sizeof(int)) if(type == spectypes[i]) return type;
+            return -1;
+        }
+#endif
+        // only allow edit messages in coop-edit mode
+        if(type>=SV_EDITENT && type<=SV_GETMAP && gamemode!=1) return -1;
+        // server only messages
+        static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ARENAWIN, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_CURRENTMASTER, SV_PONG, SV_RESUME, SV_TEAMSCORE, SV_BASEINFO, SV_BASEREGEN, SV_ANNOUNCE, SV_CLEARTARGETS, SV_CLEARHUNTERS, SV_ADDTARGET, SV_REMOVETARGET, SV_ADDHUNTER, SV_REMOVEHUNTER, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_SENDMAP, SV_DROPFLAG, SV_SCOREFLAG, SV_RETURNFLAG, SV_CLIENT };
+        if(ci) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1;
+        return type;
+    }
+
+    static void freecallback(ENetPacket *packet)
+    {
+        extern igameserver *sv;
+        ((fpsserver *)sv)->cleanworldstate(packet);
+    }
+
+    void cleanworldstate(ENetPacket *packet)
+    {
+        loopv(worldstates)
+        {
+            worldstate *ws = worldstates[i];
+            if(packet->data >= ws->positions.getbuf() && packet->data <= &ws->positions.last()) ws->uses--;
+            else if(packet->data >= ws->messages.getbuf() && packet->data <= &ws->messages.last()) ws->uses--;
+            else continue;
+            if(!ws->uses)
+            {
+                delete ws;
+                worldstates.remove(i);
+            }
+            break;
+        }
+    }
+
+    bool buildworldstate()
+    {
+        static struct { int posoff, msgoff, msglen; } pkt[MAXCLIENTS];
+        worldstate &ws = *new worldstate;
+        loopv(clients)
+        {
+            clientinfo &ci = *clients[i];
+            if(ci.position.empty()) pkt[i].posoff = -1;
+            else
+            {
+                pkt[i].posoff = ws.positions.length();
+                loopvj(ci.position) ws.positions.add(ci.position[j]);
+            }
+            if(ci.messages.empty()) pkt[i].msgoff = -1;
+            else
+            {
+                pkt[i].msgoff = ws.messages.length();
+                ucharbuf p = ws.messages.reserve(16);
+                putint(p, SV_CLIENT);
+                putint(p, ci.clientnum);
+                putuint(p, ci.messages.length());
+                ws.messages.addbuf(p);
+                loopvj(ci.messages) ws.messages.add(ci.messages[j]);
+                pkt[i].msglen = ws.messages.length() - pkt[i].msgoff;
+            }
+        }
+        int psize = ws.positions.length(), msize = ws.messages.length();
+        if(psize) recordpacket(0, ws.positions.getbuf(), psize);
+        if(msize) recordpacket(1, ws.messages.getbuf(), msize);
+        loopi(psize) { uchar c = ws.positions[i]; ws.positions.add(c); }
+        loopi(msize) { uchar c = ws.messages[i]; ws.messages.add(c); }
+        ws.uses = 0;
+        loopv(clients)
+        {
+            clientinfo &ci = *clients[i];
+            ENetPacket *packet;
+            if(psize && (pkt[i].posoff<0 || psize-ci.position.length()>0))
+            {
+                packet = enet_packet_create(&ws.positions[pkt[i].posoff<0 ? 0 : pkt[i].posoff+ci.position.length()], 
+                                            pkt[i].posoff<0 ? psize : psize-ci.position.length(), 
+                                            ENET_PACKET_FLAG_NO_ALLOCATE);
+                sendpacket(ci.clientnum, 0, packet);
+                if(!packet->referenceCount) enet_packet_destroy(packet);
+                else { ++ws.uses; packet->freeCallback = freecallback; }
+            }
+            ci.position.setsizenodelete(0);
+
+            if(msize && (pkt[i].msgoff<0 || msize-pkt[i].msglen>0))
+            {
+                packet = enet_packet_create(&ws.messages[pkt[i].msgoff<0 ? 0 : pkt[i].msgoff+pkt[i].msglen], 
+                                            pkt[i].msgoff<0 ? msize : msize-pkt[i].msglen, 
+                                            (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
+                sendpacket(ci.clientnum, 1, packet);
+                if(!packet->referenceCount) enet_packet_destroy(packet);
+                else { ++ws.uses; packet->freeCallback = freecallback; }
+            }
+            ci.messages.setsizenodelete(0);
+        }
+        reliablemessages = false;
+        if(!ws.uses) 
+        {
+            delete &ws;
+            return false;
+        }
+        else 
+        {
+            worldstates.add(&ws); 
+            return true;
+        }
+    }
+
+    bool sendpackets()
+    {
+        if(clients.empty()) return false;
+        enet_uint32 curtime = enet_time_get()-lastsend;
+        if(curtime<33) return false;
+        bool flush = buildworldstate();
+        lastsend += curtime - (curtime%33);
+        return flush;
+    }
+
+    void parsepacket(int sender, int chan, bool reliable, ucharbuf &p)     // has to parse exactly each byte of the packet
+    {
+        if(sender<0) return;
+        if(chan==2)
+        {
+            receivefile(sender, p.buf, p.maxlen);
+            return;
+        }
+        if(reliable) reliablemessages = true;
+        char text[MAXTRANS];
+        int cn = -1, type;
+        clientinfo *ci = sender>=0 ? (clientinfo *)getinfo(sender) : NULL;
+        #define QUEUE_MSG { if(!ci->local) while(curmsg<p.length()) ci->messages.add(p.buf[curmsg++]); }
+        #define QUEUE_BUF(size, body) { \
+            if(!ci->local) \
+            { \
+                curmsg = p.length(); \
+                ucharbuf buf = ci->messages.reserve(size); \
+                { body; } \
+                ci->messages.addbuf(buf); \
+            } \
+        }
+        #define QUEUE_INT(n) QUEUE_BUF(5, putint(buf, n))
+        #define QUEUE_UINT(n) QUEUE_BUF(4, putuint(buf, n))
+        #define QUEUE_STR(text) QUEUE_BUF(2*strlen(text)+1, sendstring(text, buf))
+        int curmsg;
+        while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci))
+        {
+            case SV_POS:
+            {
+                cn = getint(p);
+                if(cn<0 || cn>=getnumclients() || cn!=sender)
+                {
+                    disconnect_client(sender, DISC_CN);
+                    return;
+                }
+                vec oldpos(ci->state.o);
+                loopi(3) ci->state.o[i] = getuint(p)/DMF;
+                getuint(p);
+                loopi(5) getint(p);
+                int physstate = getuint(p);
+                if(physstate&0x20) loopi(2) getint(p);
+                if(physstate&0x10) getint(p);
+                getuint(p);
+                if(!ci->local && (ci->state.state==CS_ALIVE || ci->state.state==CS_EDITING))
+                {
+                    ci->position.setsizenodelete(0);
+                    while(curmsg<p.length()) ci->position.add(p.buf[curmsg++]);
+                }
+                if(smode && ci->state.state==CS_ALIVE) smode->moved(ci, oldpos, ci->state.o);
+                break;
+            }
+
+            case SV_EDITMODE:
+            {
+                int val = getint(p);
+                if(!ci->local && gamemode!=1) break;
+                if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break;
+                if(smode)
+                {
+                    if(val) smode->leavegame(ci);
+                    else smode->entergame(ci);
+                }
+                if(val)
+                {
+                    ci->state.editstate = ci->state.state;
+                    ci->state.state = CS_EDITING;
+                }
+                else ci->state.state = ci->state.editstate;
+                if(val)
+                {
+                    ci->events.setsizenodelete(0);
+                    ci->state.rockets.reset();
+                    ci->state.grenades.reset();
+                }
+                QUEUE_MSG;
+                break;
+            }
+
+            case SV_TRYSPAWN:
+                if(ci->state.state!=CS_DEAD || ci->state.lastspawn>=0 || (smode && !smode->canspawn(ci))) break;
+                if(ci->state.lastdeath) ci->state.respawn();
+                sendspawn(ci);
+                break;
+
+            case SV_GUNSELECT:
+            {
+                int gunselect = getint(p);
+                if(ci->state.state!=CS_ALIVE) break;
+                ci->state.gunselect = gunselect;
+                QUEUE_MSG;
+                break;
+            }
+
+            case SV_SPAWN:
+            {
+                int ls = getint(p), gunselect = getint(p);
+                if((ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD) || ls!=ci->state.lifesequence || ci->state.lastspawn<0) break; 
+                ci->state.lastspawn = -1;
+                ci->state.state = CS_ALIVE;
+                ci->state.gunselect = gunselect;
+                if(smode) smode->spawned(ci);
+                QUEUE_BUF(100,
+                {
+                    putint(buf, SV_SPAWN);
+                    sendstate(ci->state, buf);
+                });
+                break;
+            }
+
+            case SV_SUICIDE:
+            {
+                gameevent &suicide = ci->addevent();
+                suicide.type = GE_SUICIDE;
+                break;
+            }
+
+            case SV_SHOOT:
+            {
+                gameevent &shot = ci->addevent();
+                shot.type = GE_SHOT;
+                #define seteventmillis(event) \
+                { \
+                    event.id = getint(p); \
+                    if(!ci->timesync || (ci->events.length()==1 && ci->state.waitexpired(gamemillis))) \
+                    { \
+                        ci->timesync = true; \
+                        ci->gameoffset = gamemillis - event.id; \
+                        event.millis = gamemillis; \
+                    } \
+                    else event.millis = ci->gameoffset + event.id; \
+                }
+                seteventmillis(shot.shot);
+                shot.shot.gun = getint(p);
+                loopk(3) shot.shot.from[k] = getint(p)/DMF;
+                loopk(3) shot.shot.to[k] = getint(p)/DMF;
+                int hits = getint(p);
+                loopk(hits)
+                {
+                    gameevent &hit = ci->addevent();
+                    hit.type = GE_HIT;
+                    hit.hit.target = getint(p);
+                    hit.hit.lifesequence = getint(p);
+                    hit.hit.rays = getint(p);
+                    loopk(3) hit.hit.dir[k] = getint(p)/DNF;
+                }
+                break;
+            }
+
+            case SV_EXPLODE:
+            {
+                gameevent &exp = ci->addevent();
+                exp.type = GE_EXPLODE;
+                seteventmillis(exp.explode);
+                exp.explode.gun = getint(p);
+                exp.explode.id = getint(p);
+                int hits = getint(p);
+                loopk(hits)
+                {
+                    gameevent &hit = ci->addevent();
+                    hit.type = GE_HIT;
+                    hit.hit.target = getint(p);
+                    hit.hit.lifesequence = getint(p);
+                    hit.hit.dist = getint(p)/DMF;
+                    loopk(3) hit.hit.dir[k] = getint(p)/DNF;
+                }
+                break;
+            }
+
+            case SV_ITEMPICKUP:
+            {
+                int n = getint(p);
+                gameevent &pickup = ci->addevent();
+                pickup.type = GE_PICKUP;
+                pickup.pickup.ent = n;
+                break;
+            }
+
+            case SV_TEXT:
+                QUEUE_MSG;
+                getstring(text, p);
+                filtertext(text, text);
+                QUEUE_STR(text);
+                break;
+
+            case SV_SAYTEAM:
+            {
+                getstring(text, p);
+                if(ci->state.state==CS_SPECTATOR || !m_teammode || !ci->team[0]) break;
+                loopv(clients)
+                {
+                    clientinfo *t = clients[i];
+                    if(t==ci || t->state.state==CS_SPECTATOR || strcmp(ci->team, t->team)) continue;
+                    sendf(t->clientnum, 1, "riis", SV_SAYTEAM, ci->clientnum, text);
+                }
+                break;
+            }
+
+            case SV_INITC2S:
+            {
+                QUEUE_MSG;
+                bool connected = !ci->name[0];
+                getstring(text, p);
+                filtertext(text, text, false, MAXNAMELEN);
+                if(!text[0]) s_strcpy(text, "unnamed");
+                QUEUE_STR(text);
+                s_strncpy(ci->name, text, MAXNAMELEN+1);
+                if(!ci->local && connected)
+                {
+                    savedscore &sc = findscore(ci, false);
+                    if(&sc) 
+                    {
+                        sc.restore(ci->state);
+                        gamestate &gs = ci->state;
+                        sendf(-1, 1, "ri2i9vi", SV_RESUME, sender,
+                            gs.state, gs.frags, gs.quadmillis, 
+                            gs.lifesequence,
+                            gs.health, gs.maxhealth,
+                            gs.armour, gs.armourtype,
+                            gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG], -1);
+                    }
+                }
+                getstring(text, p);
+                filtertext(text, text, false, MAXTEAMLEN);
+                if(!ci->local && (smode && !smode->canchangeteam(ci, ci->team, text)) && m_teammode)
+                {
+                    const char *worst = chooseworstteam(text, ci);
+                    if(worst)
+                    {
+                        s_strcpy(text, worst);
+                        sendf(sender, 1, "riis", SV_SETTEAM, sender, worst);
+                        QUEUE_STR(worst);
+                    }
+                    else QUEUE_STR(text);
+                }
+                else QUEUE_STR(text);
+                if(smode && ci->state.state==CS_ALIVE && strcmp(ci->team, text)) smode->changeteam(ci, ci->team, text);
+                s_strncpy(ci->team, text, MAXTEAMLEN+1);
+                QUEUE_MSG;
+                break;
+            }
+
+            case SV_MAPVOTE:
+            case SV_MAPCHANGE:
+            {
+                getstring(text, p);
+                filtertext(text, text);
+                int reqmode = getint(p);
+                if(type!=SV_MAPVOTE && !mapreload) break;
+                if(!ci->local && !m_mp(reqmode)) reqmode = 0;
+                vote(text, reqmode, sender);
+                break;
+            }
+
+            case SV_ITEMLIST:
+            {
+                if((ci->state.state==CS_SPECTATOR && !ci->privilege) || !notgotitems) { while(getint(p)>=0 && !p.overread()) getint(p); break; }
+                int n;
+                while((n = getint(p))>=0 && !p.overread())
+                {
+                    server_entity se = { getint(p), false, 0 };
+                    while(sents.length()<=n) sents.add(se);
+                    if(gamemode>=0 && (sents[n].type==I_QUAD || sents[n].type==I_BOOST)) sents[n].spawntime = spawntime(sents[n].type);
+                    else sents[n].spawned = true;
+                }
+                notgotitems = false;
+                break;
+            }
+
+            case SV_TEAMSCORE:
+                getstring(text, p);
+                getint(p);
+                QUEUE_MSG;
+                break;
+
+            case SV_BASEINFO:
+                getint(p);
+                getstring(text, p);
+                getstring(text, p);
+                getint(p);
+                QUEUE_MSG;
+                break;
+
+            case SV_BASES:
+                if((ci->state.state!=CS_SPECTATOR || ci->privilege) && smode==&capturemode) capturemode.parsebases(p);
+                break;
+
+            case SV_REPAMMO:
+                if(ci->state.state!=CS_SPECTATOR && smode==&capturemode) capturemode.replenishammo(ci);
+                break;
+
+            case SV_TAKEFLAG:
+            {
+                int flag = getint(p);
+                if(ci->state.state!=CS_SPECTATOR && smode==&ctfmode) ctfmode.takeflag(ci, flag); 
+                break;
+            }
+
+            case SV_INITFLAGS:
+                if((ci->state.state!=CS_SPECTATOR || ci->privilege) && smode==&ctfmode) ctfmode.parseflags(p);
+                break;
+
+            case SV_PING:
+                sendf(sender, 1, "i2", SV_PONG, getint(p));
+                break;
+
+            case SV_CLIENTPING:
+                getint(p);
+                QUEUE_MSG;
+                break;
+
+            case SV_MASTERMODE:
+            {
+                int mm = getint(p);
+                if(ci->privilege && mm>=MM_OPEN && mm<=MM_PRIVATE)
+                {
+                    if(ci->privilege>=PRIV_ADMIN || (mastermask&(1<<mm)))
+                    {
+                        mastermode = mm;
+                        allowedips.setsize(0);
+                        if(mm>=MM_PRIVATE) 
+                        {
+                            loopv(clients) allowedips.add(getclientip(clients[i]->clientnum));
+                        }
+                        s_sprintfd(s)("mastermode is now %s (%d)", mastermodestr(mastermode), mastermode);
+                        sendservmsg(s);
+                    }
+                    else
+                    {
+                        s_sprintfd(s)("mastermode %d is disabled on this server", mm);
+                        sendf(sender, 1, "ris", SV_SERVMSG, s); 
+                    }
+                }   
+                break;
+            }
+           
+            case SV_CLEARBANS:
+            {
+                if(ci->privilege)
+                {
+                    bannedips.setsize(0);
+                    sendservmsg("cleared all bans");
+                }
+                break;
+            }
+
+            case SV_KICK:
+            {
+                int victim = getint(p);
+                if(ci->privilege && victim>=0 && victim<getnumclients() && ci->clientnum!=victim && getinfo(victim))
+                {
+                    ban &b = bannedips.add();
+                    b.time = totalmillis;
+                    b.ip = getclientip(victim);
+                    allowedips.removeobj(b.ip);
+                    disconnect_client(victim, DISC_KICK);
+                }
+                break;
+            }
+
+            case SV_SPECTATOR:
+            {
+                int spectator = getint(p), val = getint(p);
+                if(!ci->privilege && (ci->state.state==CS_SPECTATOR || spectator!=sender)) break;
+                clientinfo *spinfo = (clientinfo *)getinfo(spectator);
+                if(!spinfo) break;
+
+                sendf(-1, 1, "ri3", SV_SPECTATOR, spectator, val);
+
+                if(spinfo->state.state!=CS_SPECTATOR && val)
+                {
+                    if(smode) smode->leavegame(spinfo);
+                    spinfo->state.state = CS_SPECTATOR;
+                    spinfo->state.timeplayed += lastmillis - spinfo->state.lasttimeplayed;
+                }
+                else if(spinfo->state.state==CS_SPECTATOR && !val)
+                {
+                    spinfo->state.state = CS_DEAD;
+                    spinfo->state.respawn();
+                    if(!smode || smode->canspawn(spinfo)) sendspawn(spinfo);
+                    spinfo->state.lasttimeplayed = lastmillis;
+                }
+                break;
+            }
+
+            case SV_SETTEAM:
+            {
+                int who = getint(p);
+                getstring(text, p);
+                filtertext(text, text, false, MAXTEAMLEN);
+                if(!ci->privilege || who<0 || who>=getnumclients()) break;
+                clientinfo *wi = (clientinfo *)getinfo(who);
+                if(!wi) break;
+                if(!smode || smode->canchangeteam(wi, wi->team, text))
+                {
+                    if(smode && wi->state.state==CS_ALIVE && strcmp(wi->team, text)) 
+                        smode->changeteam(wi, wi->team, text);
+                    s_strncpy(wi->team, text, MAXTEAMLEN+1);
+                }
+                sendf(sender, 1, "riis", SV_SETTEAM, who, wi->team);
+                QUEUE_INT(SV_SETTEAM);
+                QUEUE_INT(who);
+                QUEUE_STR(wi->team);
+                break;
+            } 
+
+            case SV_FORCEINTERMISSION:
+                if(m_sp) startintermission();
+                break;
+
+            case SV_RECORDDEMO:
+            {
+                int val = getint(p);
+                if(ci->privilege<PRIV_ADMIN) break;
+                demonextmatch = val!=0;
+                s_sprintfd(msg)("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled"); 
+                sendservmsg(msg);
+                break;
+            }
+
+            case SV_STOPDEMO:
+            {
+                if(!ci->local && ci->privilege<PRIV_ADMIN) break;
+                if(m_demo) enddemoplayback();
+                else enddemorecord();
+                break;
+            }
+
+            case SV_CLEARDEMOS:
+            {
+                int demo = getint(p);
+                if(ci->privilege<PRIV_ADMIN) break;
+                cleardemos(demo);
+                break;
+            }
+
+            case SV_LISTDEMOS:
+                if(!ci->privilege && ci->state.state==CS_SPECTATOR) break;
+                listdemos(sender);
+                break;
+
+            case SV_GETDEMO:
+            {
+                int n = getint(p);
+                if(!ci->privilege && ci->state.state==CS_SPECTATOR) break;
+                senddemo(sender, n);
+                break;
+            }
+
+            case SV_GETMAP:
+                if(mapdata)
+                {
+                    sendf(sender, 1, "ris", SV_SERVMSG, "server sending map...");
+                    sendfile(sender, 2, mapdata, "ri", SV_SENDMAP);
+                }
+                else sendf(sender, 1, "ris", SV_SERVMSG, "no map to send"); 
+                break;
+
+            case SV_NEWMAP:
+            {
+                int size = getint(p);
+                if(!ci->privilege && ci->state.state==CS_SPECTATOR) break;
+                if(size>=0)
+                {
+                    smapname[0] = '\0';
+                    resetitems();
+                    notgotitems = false;
+                    if(smode) smode->reset(true);
+                }
+                QUEUE_MSG;
+                break;
+            }
+
+            case SV_SETMASTER:
+            {
+                int val = getint(p);
+                getstring(text, p);
+                setmaster(ci, val!=0, text);
+                // don't broadcast the master password
+                break;
+            }
+
+            case SV_APPROVEMASTER:
+            {
+                int mn = getint(p);
+                if(mastermask&MM_AUTOAPPROVE || ci->state.state==CS_SPECTATOR) break;
+                clientinfo *candidate = (clientinfo *)getinfo(mn);
+                if(!candidate || !candidate->wantsmaster || mn==sender || getclientip(mn)==getclientip(sender)) break;
+                setmaster(candidate, true, "", true);
+                break;
+            }
+
+            default:
+            {
+                int size = msgsizelookup(type);
+                if(size==-1) { disconnect_client(sender, DISC_TAGT); return; }
+                if(size>0) loopi(size-1) getint(p);
+                if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG;
+                break;
+            }
+        }
+    }
+
+    void sendstate(gamestate &gs, ucharbuf &p)
+    {
+        putint(p, gs.lifesequence);
+        putint(p, gs.health);
+        putint(p, gs.maxhealth);
+        putint(p, gs.armour);
+        putint(p, gs.armourtype);
+        putint(p, gs.gunselect);
+        loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]);
+    }
+
+    int welcomepacket(ucharbuf &p, int n, ENetPacket *packet)
+    {
+        clientinfo *ci = (clientinfo *)getinfo(n);
+        int hasmap = (gamemode==1 && clients.length()>1) || (smapname[0] && (minremain>0 || (ci && ci->state.state==CS_SPECTATOR) || nonspectators(n)));
+        putint(p, SV_INITS2C);
+        putint(p, n);
+        putint(p, PROTOCOL_VERSION);
+        putint(p, hasmap);
+        if(hasmap)
+        {
+            putint(p, SV_MAPCHANGE);
+            sendstring(smapname, p);
+            putint(p, gamemode);
+            putint(p, notgotitems ? 1 : 0);
+            if(!ci || gamemode>1 || (gamemode==0 && hasnonlocalclients()))
+            {
+                putint(p, SV_TIMEUP);
+                putint(p, minremain);
+            }
+            if(!notgotitems)
+            {
+                putint(p, SV_ITEMLIST);
+                loopv(sents) if(sents[i].spawned)
+                {
+                    putint(p, i);
+                    putint(p, sents[i].type);
+                    if(p.remaining() < 256)
+                    {
+                        enet_packet_resize(packet, packet->dataLength + MAXTRANS);
+                        p.buf = packet->data;
+                    }
+                }
+                putint(p, -1);
+            }
+        }
+        if(ci && !ci->local && m_teammode)
+        {
+            const char *worst = chooseworstteam();
+            if(worst)
+            {
+                putint(p, SV_SETTEAM);
+                putint(p, ci->clientnum);
+                sendstring(worst, p);
+            }
+        }
+        if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR)
+        {
+            if(smode && !smode->canspawn(ci, true))
+            {
+                ci->state.state = CS_DEAD;
+                putint(p, SV_FORCEDEATH);
+                putint(p, n);
+                sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n);
+            }
+            else
+            {
+                gamestate &gs = ci->state;
+                spawnstate(ci);
+                putint(p, SV_SPAWNSTATE);
+                sendstate(gs, p);
+                gs.lastspawn = gamemillis; 
+            }
+        }
+        if(ci && ci->state.state==CS_SPECTATOR)
+        {
+            putint(p, SV_SPECTATOR);
+            putint(p, n);
+            putint(p, 1);
+            sendf(-1, 1, "ri3x", SV_SPECTATOR, n, 1, n);   
+        }
+        if(clients.length()>1)
+        {
+            putint(p, SV_RESUME);
+            loopv(clients)
+            {
+                clientinfo *oi = clients[i];
+                if(oi->clientnum==n) continue;
+                if(p.remaining() < 256)
+                {
+                    enet_packet_resize(packet, packet->dataLength + MAXTRANS);
+                    p.buf = packet->data;
+                }
+                putint(p, oi->clientnum);
+                putint(p, oi->state.state);
+                putint(p, oi->state.frags);
+                putint(p, oi->state.quadmillis);
+                sendstate(oi->state, p);
+            }
+            putint(p, -1);
+        }
+        if(smode) 
+        {
+            enet_packet_resize(packet, packet->dataLength + MAXTRANS);
+            p.buf = packet->data;
+            smode->initclient(ci, p, true);
+        }
+        return 1;
+    }
+
+    void checkintermission()
+    {
+        if(minremain>0)
+        {
+            minremain = gamemillis>=gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
+            sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
+            if(!minremain && smode) smode->intermission();
+        }
+        if(!interm && minremain<=0) interm = gamemillis+10000;
+    }
+
+    void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); }
+
+    void clearevent(clientinfo *ci)
+    {
+        int n = 1;
+        while(n<ci->events.length() && ci->events[n].type==GE_HIT) n++;
+        ci->events.remove(0, n);
+    }
+
+    void spawnstate(clientinfo *ci)
+    {
+        gamestate &gs = ci->state;
+        gs.spawnstate(gamemode);
+        gs.lifesequence++;
+    }
+
+    void sendspawn(clientinfo *ci)
+    {
+        gamestate &gs = ci->state;
+        spawnstate(ci);
+        sendf(ci->clientnum, 1, "ri7v", SV_SPAWNSTATE, gs.lifesequence,
+            gs.health, gs.maxhealth,
+            gs.armour, gs.armourtype,
+            gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]);
+        gs.lastspawn = gamemillis;
+    }
+
+    void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0))
+    {
+        gamestate &ts = target->state;
+        ts.dodamage(damage);
+        actor->state.damage += damage;
+        sendf(-1, 1, "ri6", SV_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health); 
+        if(target!=actor && !hitpush.iszero()) 
+        {
+            vec v(hitpush);
+            if(!v.iszero()) v.normalize();
+            sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage,
+                int(v.x*DNF), int(v.y*DNF), int(v.z*DNF));
+        }
+        if(ts.health<=0)
+        {
+            target->state.deaths++;
+            if(actor!=target && isteam(actor->team, target->team)) actor->state.teamkills++;
+            int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
+            actor->state.frags += fragvalue;
+            if(fragvalue>0)
+            {
+                int friends = 0, enemies = 0; // note: friends also includes the fragger
+                if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++;
+                else { friends = 1; enemies = clients.length()-1; }
+                actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
+            }
+            sendf(-1, 1, "ri4", SV_DIED, target->clientnum, actor->clientnum, actor->state.frags);
+            target->position.setsizenodelete(0);
+            if(smode) smode->died(target, actor);
+            ts.state = CS_DEAD;
+            ts.lastdeath = gamemillis;
+            // don't issue respawn yet until DEATHMILLIS has elapsed
+            // ts.respawn();
+        }
+    }
+
+    void processevent(clientinfo *ci, suicideevent &e)
+    {
+        gamestate &gs = ci->state;
+        if(gs.state!=CS_ALIVE) return;
+        ci->state.frags += smode ? smode->fragvalue(ci, ci) : -1;
+        ci->state.deaths++;
+        sendf(-1, 1, "ri4", SV_DIED, ci->clientnum, ci->clientnum, gs.frags);
+        ci->position.setsizenodelete(0);
+        if(smode) smode->died(ci, NULL);
+        gs.state = CS_DEAD;
+        gs.respawn();
+    }
+
+    void processevent(clientinfo *ci, explodeevent &e)
+    {
+        gamestate &gs = ci->state;
+        switch(e.gun)
+        {
+            case GUN_RL:
+                if(!gs.rockets.remove(e.id)) return;
+                break;
+
+            case GUN_GL:
+                if(!gs.grenades.remove(e.id)) return;
+                break;
+
+            default:
+                return;
+        }
+        for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++)
+        {
+            hitevent &h = ci->events[i].hit;
+            clientinfo *target = (clientinfo *)getinfo(h.target);
+            if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>RL_DAMRAD) continue;
+
+            int j = 1;
+            for(j = 1; j<i; j++) if(ci->events[j].hit.target==h.target) break;
+            if(j<i) continue;
+
+            int damage = guns[e.gun].damage;
+            if(gs.quadmillis) damage *= 4;        
+            damage = int(damage*(1-h.dist/RL_DISTSCALE/RL_DAMRAD));
+            if(e.gun==GUN_RL && target==ci) damage /= RL_SELFDAMDIV;
+            dodamage(target, ci, damage, e.gun, h.dir);
+        }
+    }
+        
+    void processevent(clientinfo *ci, shotevent &e)
+    {
+        gamestate &gs = ci->state;
+        int wait = e.millis - gs.lastshot;
+        if(!gs.isalive(gamemillis) ||
+           wait<gs.gunwait ||
+           e.gun<GUN_FIST || e.gun>GUN_PISTOL ||
+           gs.ammo[e.gun]<=0)
+            return;
+        if(e.gun!=GUN_FIST) gs.ammo[e.gun]--;
+        gs.lastshot = e.millis; 
+        gs.gunwait = guns[e.gun].attackdelay; 
+        sendf(-1, 1, "ri9x", SV_SHOTFX, ci->clientnum, e.gun,
+                int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF),
+                int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF),
+                ci->clientnum);
+        gs.shotdamage += guns[e.gun].damage*(gs.quadmillis ? 4 : 1)*(e.gun==GUN_SG ? SGRAYS : 1);
+        switch(e.gun)
+        {
+            case GUN_RL: gs.rockets.add(e.id); break;
+            case GUN_GL: gs.grenades.add(e.id); break;
+            default:
+            {
+                int totalrays = 0, maxrays = e.gun==GUN_SG ? SGRAYS : 1;
+                for(int i = 1; i<ci->events.length() && ci->events[i].type==GE_HIT; i++)
+                {
+                    hitevent &h = ci->events[i].hit;
+                    clientinfo *target = (clientinfo *)getinfo(h.target);
+                    if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1) continue;
+
+                    totalrays += h.rays;
+                    if(totalrays>maxrays) continue;
+                    int damage = h.rays*guns[e.gun].damage;
+                    if(gs.quadmillis) damage *= 4;
+                    dodamage(target, ci, damage, e.gun, h.dir);
+                }
+                break;
+            }
+        }
+    }
+
+    void processevent(clientinfo *ci, pickupevent &e)
+    {
+        gamestate &gs = ci->state;
+        if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
+        pickup(e.ent, ci->clientnum);
+    }
+
+    void processevents()
+    {
+        loopv(clients)
+        {
+            clientinfo *ci = clients[i];
+            if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0);
+            while(ci->events.length())
+            {
+                gameevent &e = ci->events[0];
+                if(e.type<GE_SUICIDE)
+                {
+                    if(e.shot.millis>gamemillis) break;
+                    if(e.shot.millis<ci->lastevent) { clearevent(ci); continue; }
+                    ci->lastevent = e.shot.millis;
+                }
+                switch(e.type)
+                {
+                    case GE_SHOT: processevent(ci, e.shot); break;
+                    case GE_EXPLODE: processevent(ci, e.explode); break;
+                    // untimed events
+                    case GE_SUICIDE: processevent(ci, e.suicide); break;
+                    case GE_PICKUP: processevent(ci, e.pickup); break;
+                }
+                clearevent(ci);
+            }
+        }
+    }
+                         
+    void serverupdate(int _lastmillis, int _totalmillis)
+    {
+        curtime = _lastmillis - lastmillis;
+        gamemillis += curtime;
+        lastmillis = _lastmillis;
+        totalmillis = _totalmillis;
+
+        if(m_demo) readdemo();
+        else if(minremain>0)
+        {
+            processevents();
+            if(curtime) 
+            {
+                loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
+                {
+                    int oldtime = sents[i].spawntime;
+                    sents[i].spawntime -= curtime;
+                    if(sents[i].spawntime<=0)
+                    {
+                        sents[i].spawntime = 0;
+                        sents[i].spawned = true;
+                        sendf(-1, 1, "ri2", SV_ITEMSPAWN, i);
+                    }
+                    else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST))
+                    {
+                        sendf(-1, 1, "ri2", SV_ANNOUNCE, sents[i].type);
+                    }
+                }
+            }
+            if(smode) smode->update();
+        }
+
+        while(bannedips.length() && bannedips[0].time-totalmillis>4*60*60000) bannedips.remove(0);
+        
+        if(masterupdate) 
+        { 
+            clientinfo *m = currentmaster>=0 ? (clientinfo *)getinfo(currentmaster) : NULL;
+            sendf(-1, 1, "ri3", SV_CURRENTMASTER, currentmaster, m ? m->privilege : 0); 
+            masterupdate = false; 
+        } 
+    
+        if((gamemode>1 || (gamemode==0 && hasnonlocalclients())) && gamemillis-curtime>0 && gamemillis/60000!=(gamemillis-curtime)/60000) checkintermission();
+        if(interm && gamemillis>interm)
+        {
+            if(demorecord) enddemorecord();
+            interm = 0;
+            checkvotes(true);
+        }
+    }
+
+    bool serveroption(char *arg)
+    {
+        if(arg[0]=='-') switch(arg[1])
+        {
+            case 'n': s_strcpy(serverdesc, &arg[2]); return true;
+            case 'p': s_strcpy(masterpass, &arg[2]); return true;
+            case 'o': if(atoi(&arg[2])) mastermask = (1<<MM_OPEN) | (1<<MM_VETO); return true;
+        }
+        return false;
+    }
+
+    void serverinit()
+    {
+        smapname[0] = '\0';
+        resetitems();
+    }
+   
+    const char *privname(int type)
+    {
+        switch(type)
+        {
+            case PRIV_ADMIN: return "admin";
+            case PRIV_MASTER: return "master";
+            default: return "unknown";
+        }
+    }
+
+    void setmaster(clientinfo *ci, bool val, const char *pass = "", bool approved = false)
+    {
+        if(approved && (!val || !ci->wantsmaster)) return;
+        const char *name = "";
+        if(val)
+        {
+            if(ci->privilege)
+            {
+                if(!masterpass[0] || !pass[0]==(ci->privilege!=PRIV_ADMIN)) return;
+            }
+            else if(ci->state.state==CS_SPECTATOR && (!masterpass[0] || strcmp(masterpass, pass))) return;
+            loopv(clients) if(ci!=clients[i] && clients[i]->privilege)
+            {
+                if(masterpass[0] && !strcmp(masterpass, pass)) clients[i]->privilege = PRIV_NONE;
+                else return;
+            }
+            if(masterpass[0] && !strcmp(masterpass, pass)) ci->privilege = PRIV_ADMIN;
+            else if(!approved && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege)
+            {
+                ci->wantsmaster = true;
+                s_sprintfd(msg)("%s wants master. Type \"/approve %d\" to approve.", colorname(ci), ci->clientnum);
+                sendservmsg(msg);
+                return;
+            }
+            else ci->privilege = PRIV_MASTER;
+            name = privname(ci->privilege);
+        }
+        else
+        {
+            if(!ci->privilege) return;
+            name = privname(ci->privilege);
+            ci->privilege = 0;
+        }
+        mastermode = MM_OPEN;
+        allowedips.setsize(0);
+        s_sprintfd(msg)("%s %s %s", colorname(ci), val ? (approved ? "approved for" : "claimed") : "relinquished", name);
+        sendservmsg(msg);
+        currentmaster = val ? ci->clientnum : -1;
+        masterupdate = true;
+        loopv(clients) clients[i]->wantsmaster = false;
+    }
+
+    void localconnect(int n)
+    {
+        clientinfo *ci = (clientinfo *)getinfo(n);
+        ci->clientnum = n;
+        ci->local = true;
+        clients.add(ci);
+    }
+
+    void localdisconnect(int n)
+    {
+        clientinfo *ci = (clientinfo *)getinfo(n);
+        if(smode) smode->leavegame(ci, true);
+        clients.removeobj(ci);
+    }
+
+    int clientconnect(int n, uint ip)
+    {
+        clientinfo *ci = (clientinfo *)getinfo(n);
+        ci->clientnum = n;
+        clients.add(ci);
+        loopv(bannedips) if(bannedips[i].ip==ip) return DISC_IPBAN;
+        if(mastermode>=MM_PRIVATE) 
+        {
+            if(allowedips.find(ip)<0) return DISC_PRIVATE;
+        }
+        if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
+        if(currentmaster>=0) masterupdate = true;
+        ci->state.lasttimeplayed = lastmillis;
+        return DISC_NONE;
+    }
+
+    void clientdisconnect(int n) 
+    { 
+        clientinfo *ci = (clientinfo *)getinfo(n);
+        if(ci->privilege) setmaster(ci, false);
+        if(smode) smode->leavegame(ci, true);
+        ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed; 
+        savescore(ci);
+        sendf(-1, 1, "ri2", SV_CDIS, n); 
+        clients.removeobj(ci);
+        if(clients.empty()) bannedips.setsize(0); // bans clear when server empties
+    }
+
+    const char *servername() { return "sauerbratenserver"; }
+    int serverinfoport() { return SAUERBRATEN_SERVINFO_PORT; }
+    int serverport() { return SAUERBRATEN_SERVER_PORT; }
+    const char *getdefaultmaster() { return "sauerbraten.org/masterserver/"; } 
+
+    #include "extinfo.h"
+
+    void serverinforeply(ucharbuf &req, ucharbuf &p)
+    {
+        if(!getint(req))
+        {
+            extserverinforeply(req, p);
+            return;
+        }
+
+        putint(p, clients.length());
+        putint(p, 5);                   // number of attrs following
+        putint(p, PROTOCOL_VERSION);    // a // generic attributes, passed back below
+        putint(p, gamemode);            // b
+        putint(p, minremain);           // c
+        putint(p, maxclients);
+        putint(p, mastermode);
+        sendstring(smapname, p);
+        sendstring(serverdesc, p);
+        sendserverinforeply(p);
+    }
+
+    bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np)
+    {
+        return attr.length() && attr[0]==PROTOCOL_VERSION;
+    }
+
+    void receivefile(int sender, uchar *data, int len)
+    {
+        if(gamemode != 1 || len > 1024*1024) return;
+        clientinfo *ci = (clientinfo *)getinfo(sender);
+        if(ci->state.state==CS_SPECTATOR && !ci->privilege) return;
+        if(mapdata) { fclose(mapdata); mapdata = NULL; }
+        if(!len) return;
+        mapdata = tmpfile();
+        if(!mapdata) { sendf(sender, 1, "ris", SV_SERVMSG, "failed to open temporary file for map"); return; }
+        fwrite(data, 1, len, mapdata);
+        s_sprintfd(msg)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci));
+        sendservmsg(msg);
+    }
+
+    bool duplicatename(clientinfo *ci, char *name)
+    {
+        if(!name) name = ci->name;
+        loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true;
+        return false;
+    }
+
+    char *colorname(clientinfo *ci, char *name = NULL)
+    {
+        if(!name) name = ci->name;
+        if(name[0] && !duplicatename(ci, name)) return name;
+        static string cname;
+        s_sprintf(cname)("%s \fs\f5(%d)\fr", name, ci->clientnum);
+        return cname;
+    }   
+};
diff --git a/fpsgame/game.h b/fpsgame/game.h
index f186418..8b661ec 100644
--- a/fpsgame/game.h
+++ b/fpsgame/game.h
@@ -1,8 +1,3 @@
-#ifndef __GAME_H__
-#define __GAME_H__
-
-#include "cube.h"
-
 // console message types
 
 enum
@@ -57,90 +52,24 @@ enum { GUN_FIST = 0, GUN_SG, GUN_CG, GUN_RL, GUN_RIFLE, GUN_GL, GUN_PISTOL, GUN_
 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
 
-enum
-{
-    M_TEAM       = 1<<0,
-    M_NOITEMS    = 1<<1,
-    M_NOAMMO     = 1<<2,
-    M_INSTA      = 1<<3,
-    M_EFFICIENCY = 1<<4,
-    M_TACTICS    = 1<<5,
-    M_CAPTURE    = 1<<6,
-    M_REGEN      = 1<<7,
-    M_CTF        = 1<<8,
-    M_PROTECT    = 1<<9,
-    M_OVERTIME   = 1<<10,
-    M_EDIT       = 1<<11,
-    M_DEMO       = 1<<12,
-    M_LOCAL      = 1<<13,
-    M_LOBBY      = 1<<14,
-    M_DMSP       = 1<<15,
-    M_CLASSICSP  = 1<<16,
-    M_SLOWMO     = 1<<17
-};
-
-static struct gamemodeinfo
-{
-    const char *name;
-    int flags;
-    const char *info;
-} gamemodes[] =
-{
-    { "SP", M_LOCAL | M_CLASSICSP, NULL },
-    { "DMSP", M_LOCAL | M_DMSP, NULL },
-    { "demo", M_DEMO | M_LOCAL, NULL},
-    { "ffa", M_LOBBY, "Free For All: Collect items for ammo. Frag everyone to score points." },
-    { "coop edit", M_EDIT, "Cooperative Editing: Edit maps with multiple players simultaneously." },
-    { "teamplay", M_TEAM | M_OVERTIME, "Teamplay: Collect items for ammo. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." },
-    { "instagib", M_NOITEMS | M_INSTA, "Instagib: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag everyone to score points." },
-    { "instagib team", M_NOITEMS | M_INSTA | M_TEAM | M_OVERTIME, "Instagib: You spawn with full rifle ammo and die instantly from one shot. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." },
-    { "efficiency", M_NOITEMS | M_EFFICIENCY, "Efficiency: You spawn with all weapons and armour. There are no items. Frag everyone to score points." },
-    { "efficiency team", M_NOITEMS | M_EFFICIENCY | M_TEAM | M_OVERTIME, "Efficiency: You spawn with all weapons and armour. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." },
-    { "tactics", M_NOITEMS | M_TACTICS, "Tactics: You spawn with two random weapons and armour. There are no items. Frag everyone to score points." },
-    { "tactics team", M_NOITEMS | M_TACTICS | M_TEAM | M_OVERTIME, "Tactics Team: You spawn with two random weapons and armour. There are no items. Frag \fs\f3the enemy team\fr to score points for \fs\f1your team\fr." },
-    { "capture", M_NOAMMO | M_TACTICS | M_CAPTURE | M_TEAM | M_OVERTIME, "Capture: Capture neutral bases or steal \fs\f3enemy bases\fr by standing next to them.  \fs\f1Your team\fr scores points for every 10 seconds it holds a base. You spawn with two random weapons and armour. Collect extra ammo that spawns at \fs\f1your bases\fr. There are no ammo items." },
-    { "regen capture", M_NOITEMS | M_CAPTURE | M_REGEN | M_TEAM | M_OVERTIME, "Regen Capture: Capture neutral bases or steal \fs\f3enemy bases\fr by standing next to them. \fs\f1Your team\fr scores points for every 10 seconds it holds a base. Regenerate health and ammo by standing next to \fs\f1your bases\fr. There are no items." },
-    { "ctf", M_CTF | M_TEAM, "Capture The Flag: Capture \fs\f3the enemy flag\fr and bring it back to \fs\f1your flag\fr to score points for \fs\f1your team\fr. Collect items for ammo." },
-    { "insta ctf", M_NOITEMS | M_INSTA | M_CTF | M_TEAM, "Instagib Capture The Flag: Capture \fs\f3the enemy flag\fr and bring it back to \fs\f1your flag\fr to score points for \fs\f1your team\fr. You spawn with full rifle ammo and die instantly from one shot. There are no items." },
-    { "protect", M_CTF | M_PROTECT | M_TEAM, "Protect The Flag: Touch \fs\f3the enemy flag\fr to score points for \fs\f1your team\fr. Pick up \fs\f1your flag\fr to protect it. \fs\f1Your team\fr loses points if a dropped flag resets. Collect items for ammo." },
-    { "insta protect", M_NOITEMS | M_INSTA | M_CTF | M_PROTECT | M_TEAM, "Instagib Protect The Flag: Touch \fs\f3the enemy flag\fr to score points for \fs\f1your team\fr. Pick up \fs\f1your flag\fr to protect it. \fs\f1Your team\fr loses points if a dropped flag resets. You spawn with full rifle ammo and die instantly from one shot. There are no items." }
-};
-
-#define STARTGAMEMODE (-3)
-#define NUMGAMEMODES ((int)(sizeof(gamemodes)/sizeof(gamemodes[0])))
-
-#define m_valid(mode)          ((mode) >= STARTGAMEMODE && (mode) < STARTGAMEMODE + NUMGAMEMODES)
-#define m_check(mode, flag)    (m_valid(mode) && gamemodes[(mode) - STARTGAMEMODE].flags&(flag))
-#define m_checknot(mode, flag) (m_valid(mode) && !(gamemodes[(mode) - STARTGAMEMODE].flags&(flag)))
-#define m_checkall(mode, flag) (m_valid(mode) && (gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) == (flag))
-
-#define m_noitems      (m_check(gamemode, M_NOITEMS))
-#define m_noammo       (m_check(gamemode, M_NOAMMO|M_NOITEMS))
-#define m_insta        (m_check(gamemode, M_INSTA))
-#define m_tactics      (m_check(gamemode, M_TACTICS))
-#define m_efficiency   (m_check(gamemode, M_EFFICIENCY))
-#define m_capture      (m_check(gamemode, M_CAPTURE))
-#define m_regencapture (m_checkall(gamemode, M_CAPTURE | M_REGEN))
-#define m_ctf          (m_check(gamemode, M_CTF))
-#define m_protect      (m_checkall(gamemode, M_CTF | M_PROTECT))
-#define m_teammode     (m_check(gamemode, M_TEAM))
-#define m_overtime     (m_check(gamemode, M_OVERTIME))
+#define m_noitems      ((gamemode>=4 && gamemode<=11) || gamemode==13 || gamemode==14 || gamemode==16 || gamemode==18 || gamemode==-3)
+#define m_noitemsrail  ((gamemode>=4 && gamemode<=5) || (gamemode>=8 && gamemode<=9) || gamemode==13 || gamemode==16 || gamemode==18)
+#define m_arena        (gamemode>=8 && gamemode<=11)
+#define m_tarena       (gamemode>=10 && gamemode<=11)
+#define m_capture      (gamemode>=12 && gamemode<=14)
+#define m_regencapture (gamemode==14)
+#define m_assassin     (gamemode>=15 && gamemode<=16)
+#define m_ctf          (gamemode>=17 && gamemode<=18)
+#define m_teammode     ((gamemode>2 && gamemode<12 && gamemode&1) || m_capture || m_ctf)
+#define m_teamskins    (m_teammode || m_assassin)
+#define m_sp           (m_dmsp || m_classicsp)
+#define m_dmsp         (gamemode==-1 || gamemode==-4)
+#define m_classicsp    (gamemode==-2 || gamemode==-5)
+#define m_slowmo       (gamemode==-4 || gamemode==-5)
+#define m_demo         (gamemode==-3)
 #define isteam(a,b)    (m_teammode && strcmp(a, b)==0)
 
-#define m_demo         (m_check(gamemode, M_DEMO))
-#define m_edit         (m_check(gamemode, M_EDIT))
-#define m_lobby        (m_check(gamemode, M_LOBBY))
-#define m_timed        (m_checknot(gamemode, M_DEMO|M_EDIT|M_LOCAL))
-#define m_botmode      (m_checknot(gamemode, M_DEMO|M_LOCAL))
-#define m_mp(mode)     (m_checknot(mode, M_LOCAL))
-
-#define m_sp           (m_check(gamemode, M_DMSP | M_CLASSICSP))
-#define m_dmsp         (m_check(gamemode, M_DMSP))
-#define m_classicsp    (m_check(gamemode, M_CLASSICSP))
-
-enum { MM_AUTH = -1, MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE, MM_PASSWORD, MM_START = MM_AUTH };
-
-static const char * const mastermodenames[] = { "auth", "open", "veto", "locked", "private", "password" };
+#define m_mp(mode)    (mode>=0 && mode<=18)
 
 // hardcoded sounds, defined in sounds.cfg
 enum
@@ -163,12 +92,12 @@ enum
     S_PAIND, S_DEATHD,
     S_PIGR1, S_ICEBALL, S_SLIMEBALL,
     S_JUMPPAD, S_PISTOL,
-
+    
     S_V_BASECAP, S_V_BASELOST,
     S_V_FIGHT,
     S_V_BOOST, S_V_BOOST10,
     S_V_QUAD, S_V_QUAD10,
-    S_V_RESPAWNPOINT,
+    S_V_RESPAWNPOINT, 
 
     S_FLAGPICKUP,
     S_FLAGDROP,
@@ -176,11 +105,7 @@ enum
     S_FLAGSCORE,
     S_FLAGRESET,
 
-    S_BURN,
-    S_CHAINSAW_ATTACK,
-    S_CHAINSAW_IDLE,
-
-    S_HIT
+    S_BURN
 };
 
 // network messages codes, c2s, c2c, s2c
@@ -189,69 +114,63 @@ enum { PRIV_NONE = 0, PRIV_MASTER, PRIV_ADMIN };
 
 enum
 {
-    SV_CONNECT = 0, SV_SERVINFO, SV_WELCOME, SV_INITCLIENT, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS,
-    SV_SHOOT, SV_EXPLODE, SV_SUICIDE,
+    SV_INITS2C = 0, SV_INITC2S, SV_POS, SV_TEXT, SV_SOUND, SV_CDIS,
+    SV_SHOOT, SV_EXPLODE, SV_SUICIDE, 
     SV_DIED, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX,
-    SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_FORCEDEATH,
+    SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_FORCEDEATH, SV_ARENAWIN,
     SV_GUNSELECT, SV_TAUNT,
-    SV_MAPCHANGE, SV_MAPVOTE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_ITEMACC,
+    SV_MAPCHANGE, SV_MAPVOTE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_DENIED,
     SV_PING, SV_PONG, SV_CLIENTPING,
-    SV_TIMEUP, SV_MAPRELOAD, SV_FORCEINTERMISSION,
+    SV_TIMEUP, SV_MAPRELOAD, SV_ITEMACC,
     SV_SERVMSG, SV_ITEMLIST, SV_RESUME,
-    SV_EDITMODE, SV_EDITENT, SV_EDITF, SV_EDITT, SV_EDITM, SV_FLIP, SV_COPY, SV_PASTE, SV_ROTATE, SV_REPLACE, SV_DELCUBE, SV_REMIP, SV_NEWMAP, SV_GETMAP, SV_SENDMAP, SV_EDITVAR,
-    SV_MASTERMODE, SV_KICK, SV_CLEARBANS, SV_CURRENTMASTER, SV_SPECTATOR, SV_SETMASTER, SV_SETTEAM,
-    SV_BASES, SV_BASEINFO, SV_BASESCORE, SV_REPAMMO, SV_BASEREGEN, SV_ANNOUNCE,
+    SV_EDITMODE, SV_EDITENT, SV_EDITF, SV_EDITT, SV_EDITM, SV_FLIP, SV_COPY, SV_PASTE, SV_ROTATE, SV_REPLACE, SV_DELCUBE, SV_REMIP, SV_NEWMAP, SV_GETMAP, SV_SENDMAP,
+    SV_MASTERMODE, SV_KICK, SV_CLEARBANS, SV_CURRENTMASTER, SV_SPECTATOR, SV_SETMASTER, SV_SETTEAM, SV_APPROVEMASTER,
+    SV_BASES, SV_BASEINFO, SV_TEAMSCORE, SV_REPAMMO, SV_BASEREGEN, SV_FORCEINTERMISSION, SV_ANNOUNCE,
+    SV_CLEARTARGETS, SV_CLEARHUNTERS, SV_ADDTARGET, SV_REMOVETARGET, SV_ADDHUNTER, SV_REMOVEHUNTER,
     SV_LISTDEMOS, SV_SENDDEMOLIST, SV_GETDEMO, SV_SENDDEMO,
     SV_DEMOPLAYBACK, SV_RECORDDEMO, SV_STOPDEMO, SV_CLEARDEMOS,
-    SV_TAKEFLAG, SV_RETURNFLAG, SV_RESETFLAG, SV_INVISFLAG, SV_TRYDROPFLAG, SV_DROPFLAG, SV_SCOREFLAG, SV_INITFLAGS,
+    SV_TAKEFLAG, SV_RETURNFLAG, SV_RESETFLAG, SV_DROPFLAG, SV_SCOREFLAG, SV_INITFLAGS,
     SV_SAYTEAM,
     SV_CLIENT,
-    SV_AUTHTRY, SV_AUTHCHAL, SV_AUTHANS, SV_REQAUTH,
-    SV_PAUSEGAME,
-    SV_ADDBOT, SV_DELBOT, SV_INITAI, SV_FROMAI, SV_BOTLIMIT, SV_BOTBALANCE,
-    SV_MAPCRC, SV_CHECKMAPS,
-    SV_SWITCHNAME, SV_SWITCHMODEL, SV_SWITCHTEAM,
-    NUMSV
 };
 
-static const int msgsizes[] =               // size inclusive message token, 0 for variable or not-checked sizes
+static char msgsizelookup(int msg)
 {
-    SV_CONNECT, 0, SV_SERVINFO, 5, SV_WELCOME, 2, SV_INITCLIENT, 0, SV_POS, 0, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2,
-    SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1,
-    SV_DIED, 4, SV_DAMAGE, 6, SV_HITPUSH, 7, SV_SHOTFX, 9,
-    SV_TRYSPAWN, 1, SV_SPAWNSTATE, 14, SV_SPAWN, 3, SV_FORCEDEATH, 2,
-    SV_GUNSELECT, 2, SV_TAUNT, 1,
-    SV_MAPCHANGE, 0, SV_MAPVOTE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, SV_ITEMACC, 3,
-    SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2,
-    SV_TIMEUP, 2, SV_MAPRELOAD, 1, SV_FORCEINTERMISSION, 1,
-    SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_RESUME, 0,
-    SV_EDITMODE, 2, SV_EDITENT, 11, SV_EDITF, 16, SV_EDITT, 16, SV_EDITM, 16, SV_FLIP, 14, SV_COPY, 14, SV_PASTE, 14, SV_ROTATE, 15, SV_REPLACE, 16, SV_DELCUBE, 14, SV_REMIP, 1, SV_NEWMAP, 2, SV_GETMAP, 1, SV_SENDMAP, 0, SV_EDITVAR, 0,
-    SV_MASTERMODE, 2, SV_KICK, 2, SV_CLEARBANS, 1, SV_CURRENTMASTER, 3, SV_SPECTATOR, 3, SV_SETMASTER, 0, SV_SETTEAM, 0,
-    SV_BASES, 0, SV_BASEINFO, 0, SV_BASESCORE, 0, SV_REPAMMO, 1, SV_BASEREGEN, 6, SV_ANNOUNCE, 2,
-    SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0,
-    SV_DEMOPLAYBACK, 3, SV_RECORDDEMO, 2, SV_STOPDEMO, 1, SV_CLEARDEMOS, 2,
-    SV_TAKEFLAG, 2, SV_RETURNFLAG, 3, SV_RESETFLAG, 4, SV_INVISFLAG, 3, SV_TRYDROPFLAG, 1, SV_DROPFLAG, 6, SV_SCOREFLAG, 6, SV_INITFLAGS, 6,
-    SV_SAYTEAM, 0,
-    SV_CLIENT, 0,
-    SV_AUTHTRY, 0, SV_AUTHCHAL, 0, SV_AUTHANS, 0, SV_REQAUTH, 0,
-    SV_PAUSEGAME, 2,
-    SV_ADDBOT, 2, SV_DELBOT, 1, SV_INITAI, 0, SV_FROMAI, 2, SV_BOTLIMIT, 2, SV_BOTBALANCE, 2,
-    SV_MAPCRC, 0, SV_CHECKMAPS, 1,
-    SV_SWITCHNAME, 0, SV_SWITCHMODEL, 2, SV_SWITCHTEAM, 0,
-    -1
-};
+    static char msgsizesl[] =               // size inclusive message token, 0 for variable or not-checked sizes
+    {
+        SV_INITS2C, 4, SV_INITC2S, 0, SV_POS, 0, SV_TEXT, 0, SV_SOUND, 2, SV_CDIS, 2,
+        SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1,
+        SV_DIED, 4, SV_DAMAGE, 6, SV_HITPUSH, 6, SV_SHOTFX, 9,
+        SV_TRYSPAWN, 1, SV_SPAWNSTATE, 13, SV_SPAWN, 3, SV_FORCEDEATH, 2, SV_ARENAWIN, 2,
+        SV_GUNSELECT, 2, SV_TAUNT, 1,
+        SV_MAPCHANGE, 0, SV_MAPVOTE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, SV_DENIED, 2,
+        SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2,
+        SV_TIMEUP, 2, SV_MAPRELOAD, 1, SV_ITEMACC, 3,
+        SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_RESUME, 0,
+        SV_EDITMODE, 2, SV_EDITENT, 10, SV_EDITF, 16, SV_EDITT, 16, SV_EDITM, 15, SV_FLIP, 14, SV_COPY, 14, SV_PASTE, 14, SV_ROTATE, 15, SV_REPLACE, 16, SV_DELCUBE, 14, SV_REMIP, 1, SV_NEWMAP, 2, SV_GETMAP, 1, SV_SENDMAP, 0,
+        SV_MASTERMODE, 2, SV_KICK, 2, SV_CLEARBANS, 1, SV_CURRENTMASTER, 3, SV_SPECTATOR, 3, SV_SETMASTER, 0, SV_SETTEAM, 0, SV_APPROVEMASTER, 2,
+        SV_BASES, 0, SV_BASEINFO, 0, SV_TEAMSCORE, 0, SV_REPAMMO, 1, SV_BASEREGEN, 5, SV_FORCEINTERMISSION, 1,  SV_ANNOUNCE, 2,
+        SV_CLEARTARGETS, 1, SV_CLEARHUNTERS, 1, SV_ADDTARGET, 2, SV_REMOVETARGET, 2, SV_ADDHUNTER, 2, SV_REMOVEHUNTER, 2,
+        SV_LISTDEMOS, 1, SV_SENDDEMOLIST, 0, SV_GETDEMO, 2, SV_SENDDEMO, 0,
+        SV_DEMOPLAYBACK, 2, SV_RECORDDEMO, 2, SV_STOPDEMO, 1, SV_CLEARDEMOS, 2,
+        SV_DROPFLAG, 6, SV_SCOREFLAG, 5, SV_RETURNFLAG, 3, SV_TAKEFLAG, 2, SV_RESETFLAG, 2, SV_INITFLAGS, 6,   
+        SV_SAYTEAM, 0, 
+        SV_CLIENT, 0,
+        -1
+    };
+    for(char *p = msgsizesl; *p>=0; p += 2) if(*p==msg) return p[1];
+    return -1;
+}
 
-#define SAUERBRATEN_LANINFO_PORT 28784
 #define SAUERBRATEN_SERVER_PORT 28785
 #define SAUERBRATEN_SERVINFO_PORT 28786
-#define SAUERBRATEN_MASTER_PORT 28787
-#define PROTOCOL_VERSION 257            // bump when protocol changes
+#define PROTOCOL_VERSION 256            // bump when protocol changes
 #define DEMO_VERSION 1                  // bump when demo format changes
 #define DEMO_MAGIC "SAUERBRATEN_DEMO"
 
 struct demoheader
 {
-    char magic[16];
+    char magic[16]; 
     int version, protocol;
 };
 
@@ -279,24 +198,22 @@ static struct itemstat { int add, max, sound; const char *name; int info; } item
 #define RL_SELFDAMDIV 2
 #define RL_DISTSCALE 1.5f
 
-static const struct guninfo { short sound, attackdelay, damage, projspeed, part, kickamount, range; const char *name, *file; } guns[NUMGUNS] =
+static struct guninfo { short sound, attackdelay, damage, projspeed, part, kickamount, range; const char *name, *file; } guns[NUMGUNS] =
 {
-    { S_PUNCH1,    250,  50, 0,   0, 0,   14,  "fist",            "fist"  },
+    { S_PUNCH1,    250,  50, 0,   0,  1,   12, "fist",            "fist"  },
     { S_SG,       1400,  10, 0,   0, 20, 1024, "shotgun",         "shotg" },  // *SGRAYS
-    { S_CG,        100,  30, 0,   0, 7, 1024,  "chaingun",        "chaing"},
+    { S_CG,        100,  30, 0,   0,  7, 1024, "chaingun",        "chaing"},
     { S_RLFIRE,    800, 120, 80,  0, 10, 1024, "rocketlauncher",  "rocket"},
     { S_RIFLE,    1500, 100, 0,   0, 30, 2048, "rifle",           "rifle" },
     { S_FLAUNCH,   500,  75, 80,  0, 10, 1024, "grenadelauncher", "gl" },
     { S_PISTOL,    500,  25, 0,   0,  7, 1024, "pistol",          "pistol" },
-    { S_FLAUNCH,   200,  20, 50,  PART_FIREBALL1,  1, 1024, "fireball",  NULL },
-    { S_ICEBALL,   200,  40, 30,  PART_FIREBALL2,  1, 1024, "iceball",   NULL },
-    { S_SLIMEBALL, 200,  30, 160, PART_FIREBALL3,  1, 1024, "slimeball", NULL },
+    { S_FLAUNCH,   200,  20, 50,  4,  1, 1024, "fireball",        NULL },
+    { S_ICEBALL,   200,  40, 30,  6,  1, 1024, "iceball",         NULL },
+    { S_SLIMEBALL, 200,  30, 160, 7,  1, 1024, "slimeball",       NULL },
     { S_PIGR1,     250,  50, 0,   0,  1,   12, "bite",            NULL },
     { -1,            0, 120, 0,   0,  0,    0, "barrel",          NULL }
 };
 
-#include "ai.h"
-
 // inherited by fpsent and server clients
 struct fpsstate
 {
@@ -305,9 +222,8 @@ struct fpsstate
     int quadmillis;
     int gunselect, gunwait;
     int ammo[NUMGUNS];
-    int aitype, skill;
 
-    fpsstate() : maxhealth(100), aitype(AI_NONE), skill(0) {}
+    fpsstate() : maxhealth(100) {}
 
     void baseammo(int gun, int k = 2, int scale = 1)
     {
@@ -342,7 +258,7 @@ struct fpsstate
             default: return ammo[is.info]<is.max;
         }
     }
-
+ 
     void pickup(int type)
     {
         if(type<I_SHELLS || type>I_QUAD) return;
@@ -386,40 +302,43 @@ struct fpsstate
         {
             gunselect = GUN_FIST;
         }
-        else if(m_insta)
+        else if(m_noitems || m_capture)
         {
             armour = 0;
-            health = 1;
-            gunselect = GUN_RIFLE;
-            ammo[GUN_RIFLE] = 100;
-        }
-        else if(m_regencapture)
-        {
-            armourtype = A_GREEN;
-            armour = 0;
-            gunselect = GUN_PISTOL;
-            ammo[GUN_PISTOL] = 40;
-            ammo[GUN_GL] = 1;
-        }
-        else if(m_tactics)
-        {
-            armourtype = A_GREEN;
-            armour = 100;
-            ammo[GUN_PISTOL] = 40;
-            int spawngun1 = rnd(5)+1, spawngun2;
-            gunselect = spawngun1;
-            baseammo(spawngun1, m_noitems ? 2 : 1);
-            do spawngun2 = rnd(5)+1; while(spawngun1==spawngun2);
-            baseammo(spawngun2, m_noitems ? 2 : 1);
-            if(m_noitems) ammo[GUN_GL] += 1;
-        }
-        else if(m_efficiency)
-        {
-            armourtype = A_GREEN;
-            armour = 100;
-            loopi(5) baseammo(i+1);
-            gunselect = GUN_CG;
-            ammo[GUN_CG] /= 2;
+            if(m_noitemsrail)
+            {
+                health = 1;
+                gunselect = GUN_RIFLE;
+                ammo[GUN_RIFLE] = 100;
+            }
+            else if(m_regencapture)
+            {
+                armourtype = A_GREEN;
+                gunselect = GUN_PISTOL;
+                ammo[GUN_PISTOL] = 40;
+                ammo[GUN_GL] = 1;
+            }
+            else
+            {
+                armourtype = A_GREEN;
+                armour = 100;
+                if(m_tarena || m_capture)
+                {
+                    ammo[GUN_PISTOL] = 80;
+                    int spawngun1 = rnd(5)+1, spawngun2;
+                    gunselect = spawngun1;
+                    baseammo(spawngun1, m_capture ? 1 : 2);
+                    do spawngun2 = rnd(5)+1; while(spawngun1==spawngun2);
+                    baseammo(spawngun2, m_capture ? 1 : 2);
+                    if(!m_capture) ammo[GUN_GL] += 1;
+                }
+                else // efficiency 
+                {
+                    loopi(5) baseammo(i+1);
+                    gunselect = GUN_CG;
+                    ammo[GUN_CG] /= 2;
+                }
+            }
         }
         else if(m_ctf)
         {
@@ -435,7 +354,7 @@ struct fpsstate
         }
     }
 
-    // just subtract damage here, can set death, etc. later in code calling this
+    // just subtract damage here, can set death, etc. later in code calling this 
     int dodamage(int damage)
     {
         int ad = damage*(armourtype+1)*25/100; // let armour absorb when possible
@@ -443,27 +362,20 @@ struct fpsstate
         armour -= ad;
         damage -= ad;
         health -= damage;
-        return damage;
-    }
-
-    int hasammo(int gun, int exclude = -1)
-    {
-        return gun >= 0 && gun <= NUMGUNS && gun != exclude && ammo[gun] > 0;
+        return damage;        
     }
 };
 
 struct fpsent : dynent, fpsstate
-{
+{   
     int weight;                         // affects the effectiveness of hitpush
     int clientnum, privilege, lastupdate, plag, ping;
     int lifesequence;                   // sequence id for each respawn, used in damage test
-    int respawned, suicided;
     int lastpain;
     int lastaction, lastattackgun;
     bool attacking;
-    int attacksound, attackchan, idlesound, idlechan;
     int lasttaunt;
-    int lastpickup, lastpickupmillis, lastbase, lastrepammo, flagpickup;
+    int lastpickup, lastpickupmillis, lastbase;
     int superdamage;
     int frags, deaths, totaldamage, totalshots;
     editinfo *edit;
@@ -471,23 +383,15 @@ struct fpsent : dynent, fpsstate
     int smoothmillis;
 
     string name, team, info;
-    int playermodel;
-    ai::aiinfo *ai;
-    int ownernum, lastnode;
 
-    vec muzzle;
+    fpsent() : weight(100), clientnum(-1), privilege(PRIV_NONE), lastupdate(0), plag(0), ping(0), lifesequence(0), lastpain(0), frags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1)
+               { name[0] = team[0] = info[0] = 0; respawn(); }
+    ~fpsent() { freeeditinfo(edit); }
 
-    fpsent() : weight(100), clientnum(-1), privilege(PRIV_NONE), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), attacksound(-1), attackchan(-1), idlesound(-1), idlechan(-1), frags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), playermodel(-1), ai(NULL), ownernum(-1), muzzle(-1, -1, -1)
+    void damageroll(float damage)
     {
-        name[0] = team[0] = info[0] = 0;
-        respawn();
-    }
-    ~fpsent()
-    {
-        freeeditinfo(edit);
-        if(attackchan >= 0) stopsound(attacksound, attackchan);
-        if(idlechan >= 0) stopsound(idlesound, idlechan);
-        if(ai) delete ai;
+        float damroll = 2.0f*damage;
+        roll += roll>0 ? damroll : (roll<0 ? -damroll : (rnd(2) ? damroll : -damroll)); // give player a kick
     }
 
     void hitpush(int damage, const vec &dir, fpsent *actor, int gun)
@@ -498,264 +402,18 @@ struct fpsent : dynent, fpsstate
         vel.add(push);
     }
 
-    void stopattacksound()
-    {
-        if(attackchan >= 0) stopsound(attacksound, attackchan, 250);
-        attacksound = attackchan = -1;
-    }
-
-    void stopidlesound()
-    {
-        if(idlechan >= 0) stopsound(idlesound, idlechan, 100);
-        idlesound = idlechan = -1;
-    }
-
     void respawn()
     {
         dynent::reset();
         fpsstate::respawn();
-        respawned = suicided = -1;
         lastaction = 0;
         lastattackgun = gunselect;
         attacking = false;
         lasttaunt = 0;
         lastpickup = -1;
         lastpickupmillis = 0;
-        lastbase = lastrepammo = -1;
-        flagpickup = 0;
+        lastbase = -1;
         superdamage = 0;
-        stopattacksound();
-        lastnode = -1;
-    }
-};
-
-struct teamscore
-{
-    const char *team;
-    int score;
-    teamscore() {}
-    teamscore(const char *s, int n) : team(s), score(n) {}
-
-    static int compare(const teamscore *x, const teamscore *y)
-    {
-        if(x->score > y->score) return -1;
-        if(x->score < y->score) return 1;
-        return strcmp(x->team, y->team);
     }
 };
 
-namespace entities
-{
-    extern vector<extentity *> ents;
-
-    extern const char *entmdlname(int type);
-    extern const char *itemname(int i);
-
-    extern void preloadentities();
-    extern void renderentities();
-    extern void checkitems(fpsent *d);
-    extern void checkquad(int time, fpsent *d);
-    extern void resetspawns();
-    extern void spawnitems();
-    extern void putitems(packetbuf &p);
-    extern void setspawn(int i, bool on);
-    extern void teleport(int n, fpsent *d);
-    extern void pickupeffects(int n, fpsent *d);
-
-    extern void repammo(fpsent *d, int type, bool local = true);
-}
-
-namespace game
-{
-    struct clientmode
-    {
-        virtual ~clientmode() {}
-
-        virtual void preload() {}
-        virtual int clipconsole(int w, int h) { return 0; }
-        virtual void drawhud(fpsent *d, int w, int h) {}
-        virtual void rendergame() {}
-        virtual void respawned(fpsent *d) {}
-        virtual void setup() {}
-        virtual void checkitems(fpsent *d) {}
-        virtual int respawnwait(fpsent *d) { return 0; }
-        virtual void pickspawn(fpsent *d) { findplayerspawn(d); }
-        virtual void senditems(packetbuf &p) {}
-        virtual const char *prefixnextmap() { return ""; }
-        virtual void removeplayer(fpsent *d) {}
-        virtual void gameover() {}
-        virtual bool hidefrags() { return false; }
-        virtual int getteamscore(const char *team) { return 0; }
-        virtual void getteamscores(vector<teamscore> &scores) {}
-        virtual void aifind(fpsent *d, ai::aistate &b, vector<ai::interest> &interests) {}
-        virtual bool aicheck(fpsent *d, ai::aistate &b) { return false; }
-        virtual bool aidefend(fpsent *d, ai::aistate &b) { return false; }
-        virtual bool aipursue(fpsent *d, ai::aistate &b) { return false; }
-
-    };
-
-    extern clientmode *cmode;
-    extern void setclientmode();
-
-    // fps
-    extern int gamemode, nextmode;
-    extern string clientmap;
-    extern int minremain;
-    extern bool intermission;
-    extern int maptime, maprealtime;
-    extern fpsent *player1;
-    extern vector<fpsent *> players, clients;
-    extern int lastspawnattempt;
-    extern int lasthit;
-    extern int respawnent;
-    extern int following;
-    extern int smoothmove, smoothdist;
-
-    extern bool clientoption(const char *arg);
-    extern fpsent *getclient(int cn);
-    extern fpsent *newclient(int cn);
-    extern const char *colorname(fpsent *d, const char *name = NULL, const char *prefix = "");
-    extern fpsent *pointatplayer();
-    extern fpsent *hudplayer();
-    extern fpsent *followingplayer();
-    extern void stopfollowing();
-    extern void clientdisconnected(int cn, bool notify = true);
-    extern void clearclients(bool notify = true);
-    extern void startgame();
-    extern void spawnplayer(fpsent *);
-    extern void deathstate(fpsent *d, bool restore = false);
-    extern void damaged(int damage, fpsent *d, fpsent *actor, bool local = true);
-    extern void killed(fpsent *d, fpsent *actor);
-    extern void timeupdate(int timeremain);
-    extern void msgsound(int n, physent *d = NULL);
-
-    enum
-    {
-        HICON_BLUE_ARMOUR = 0,
-        HICON_GREEN_ARMOUR,
-        HICON_YELLOW_ARMOUR,
-
-        HICON_HEALTH,
-
-        HICON_FIST,
-        HICON_SG,
-        HICON_CG,
-        HICON_RL,
-        HICON_RIFLE,
-        HICON_GL,
-        HICON_PISTOL,
-
-        HICON_QUAD,
-
-        HICON_RED_FLAG,
-        HICON_BLUE_FLAG,
-
-        HICON_X       = 20,
-        HICON_Y       = 1650,
-        HICON_TEXTY   = 1644,
-        HICON_STEP    = 490,
-        HICON_SIZE    = 120,
-        HICON_SPACE   = 40
-    };
-
-    extern void drawicon(int icon, float x, float y, float sz = 120);
-
-    // client
-    extern bool connected, remote, demoplayback;
-
-    extern int parseplayer(const char *arg);
-    extern void addmsg(int type, const char *fmt = NULL, ...);
-    extern void switchname(const char *name);
-    extern void switchteam(const char *name);
-    extern void switchplayermodel(int playermodel);
-    extern void sendmapinfo();
-    extern void stopdemo();
-    extern void changemap(const char *name, int mode);
-    extern void c2sinfo();
-
-    // monster
-    struct monster;
-    extern vector<monster *> monsters;
-
-    extern void clearmonsters();
-    extern void preloadmonsters();
-    extern void updatemonsters(int curtime);
-    extern void rendermonsters();
-    extern void suicidemonster(monster *m);
-    extern void hitmonster(int damage, monster *m, fpsent *at, const vec &vel, int gun);
-    extern void monsterkilled();
-    extern void endsp(bool allkilled);
-    extern void spsummary(int accuracy);
-
-    // movable
-    struct movable;
-    extern vector<movable *> movables;
-
-    extern void clearmovables();
-    extern void updatemovables(int curtime);
-    extern void rendermovables();
-    extern void suicidemovable(movable *m);
-    extern void hitmovable(int damage, movable *m, fpsent *at, const vec &vel, int gun);
-
-    // weapon
-    extern void shoot(fpsent *d, const vec &targ);
-    extern void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int prevaction);
-    extern void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int dam, int gun);
-    extern void damageeffect(int damage, fpsent *d, bool thirdperson = true);
-    extern void superdamageeffect(const vec &vel, fpsent *d);
-    extern bool intersect(dynent *d, const vec &from, const vec &to);
-    extern dynent *intersectclosest(const vec &from, const vec &to, fpsent *at);
-    extern void clearbouncers();
-    extern void updatebouncers(int curtime);
-    extern void removebouncers(fpsent *owner);
-    extern void renderbouncers();
-    extern void clearprojectiles();
-    extern void updateprojectiles(int curtime);
-    extern void removeprojectiles(fpsent *owner);
-    extern void renderprojectiles();
-    extern void preloadbouncers();
-    extern void removeweapons(fpsent *owner);
-    extern void updateweapons(int curtime);
-    extern void gunselect(int gun, fpsent *d);
-    extern void weaponswitch(fpsent *d);
-    extern void avoidweapons(ai::avoidset &obstacles, float radius);
-
-    // scoreboard
-    extern void showscores(bool on);
-    extern void getbestplayers(vector<fpsent *> &best);
-    extern void getbestteams(vector<const char *> &best);
-
-    // render
-    struct playermodelinfo
-    {
-        const char *ffa, *blueteam, *redteam, *hudguns,
-                   *vwep, *quad, *armour[3],
-                   *ffaicon, *blueicon, *redicon;
-        bool ragdoll, selectable;
-    };
-
-    extern int playermodel, teamskins, testteam;
-
-    extern void saveragdoll(fpsent *d);
-    extern void clearragdolls();
-    extern void moveragdolls();
-    extern const playermodelinfo &getplayermodelinfo(fpsent *d);
-    extern int chooserandomplayermodel(int seed);
-    extern void swayhudgun(int curtime);
-    extern vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d);
-}
-
-namespace server
-{
-    extern const char *modename(int n, const char *unknown = "unknown");
-    extern const char *mastermodename(int n, const char *unknown = "unknown");
-    extern void startintermission();
-    extern void stopdemo();
-    extern void forcemap(const char *map, int mode);
-    extern void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen = MAXSTRLEN);
-    extern int msgsizelookup(int msg);
-    extern bool serveroption(const char *arg);
-}
-
-#endif
-
diff --git a/fpsgame/monster.cpp b/fpsgame/monster.h
similarity index 60%
rename from fpsgame/monster.cpp
rename to fpsgame/monster.h
index 4f11329..9fd9de1 100644
--- a/fpsgame/monster.cpp
+++ b/fpsgame/monster.h
@@ -1,51 +1,40 @@
 // monster.h: implements AI for single player monsters, currently client only
-#include "game.h"
 
-namespace game
+struct monsterset
 {
-    static vector<int> teleports;
+    fpsclient &cl;
+    vector<extentity *> &ents;
+    vector<int> teleports;
 
     static const int TOTMFREQ = 14;
     static const int NUMMONSTERTYPES = 9;
 
+    IVAR(killsendsp, 0, 1, 1);
+
     struct monstertype      // see docs for how these values modify behaviour
     {
-        short gun, speed, health, freq, lag, rate, pain, loyalty, bscale, weight;
+        short gun, speed, health, freq, lag, rate, pain, loyalty, bscale, weight; 
         short painsound, diesound;
         const char *name, *mdlname, *vwepname;
     };
-
-    static const monstertype monstertypes[NUMMONSTERTYPES] =
-    {
-        { GUN_FIREBALL,  15, 100, 3, 0,   100, 800, 1, 10,  90, S_PAINO, S_DIE1,   "an ogro",     "ogro",       "ogro/vwep"},
-        { GUN_CG,        18,  70, 2, 70,   10, 400, 2, 10,  50, S_PAINR, S_DEATHR, "a rhino",     "monster/rhino",      NULL},
-        { GUN_SG,        13, 120, 1, 100, 300, 400, 4, 14, 115, S_PAINE, S_DEATHE, "ratamahatta", "monster/rat",        "monster/rat/vwep"},
-        { GUN_RIFLE,     14, 200, 1, 80,  400, 300, 4, 18, 145, S_PAINS, S_DEATHS, "a slith",     "monster/slith",      "monster/slith/vwep"},
-        { GUN_RL,        12, 500, 1, 0,   200, 200, 6, 24, 210, S_PAINB, S_DEATHB, "bauul",       "monster/bauul",      "monster/bauul/vwep"},
-        { GUN_BITE,      24,  50, 3, 0,   100, 400, 1, 15,  75, S_PAINP, S_PIGGR2, "a hellpig",   "monster/hellpig",    NULL},
-        { GUN_ICEBALL,   11, 250, 1, 0,    10, 400, 6, 18, 160, S_PAINH, S_DEATHH, "a knight",    "monster/knight",     "monster/knight/vwep"},
-        { GUN_SLIMEBALL, 15, 100, 1, 0,   200, 400, 2, 10,  60, S_PAIND, S_DEATHD, "a goblin",    "monster/goblin",     "monster/goblin/vwep"},
-        { GUN_GL,        22,  50, 1, 0,   200, 400, 1, 10,  40, S_PAIND, S_DEATHD, "a spider",    "monster/spider",      NULL },
-    };
-
-    VAR(skill, 1, 3, 10);
-    VAR(killsendsp, 0, 1, 1);
-
-    bool monsterhurt;
-    vec monsterhurtpos;
     
     struct monster : fpsent
     {
+        fpsclient &cl;
+
+        monstertype *monstertypes;
+        monsterset *ms;
+        
         int monsterstate;                   // one of M_*, M_NONE means human
     
-        int mtype, tag;                     // see monstertypes table
+        int mtype;                          // see monstertypes table
         fpsent *enemy;                      // monster wants to kill this entity
         float targetyaw;                    // monster wants to look in this direction
         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
     
-        monster(int _type, int _yaw, int _tag, int _state, int _trigger, int _move) : monsterstate(_state), tag(_tag)
+        monster(int _type, int _yaw, int _state, int _trigger, int _move, monsterset *_ms) : cl(_ms->cl), monstertypes(_ms->monstertypes), ms(_ms), monsterstate(_state)
         {
             type = ENT_AI;
             respawn();
@@ -54,30 +43,29 @@ namespace game
                 conoutf(CON_WARN, "warning: unknown monster in spawn: %d", _type);
                 _type = 0;
             }
-            mtype = _type;
-            const monstertype &t = monstertypes[mtype];
+            monstertype *t = monstertypes+(mtype = _type);
             eyeheight = 8.0f;
             aboveeye = 7.0f;
-            radius *= t.bscale/10.0f;
+            radius *= t->bscale/10.0f;
             xradius = yradius = radius;
-            eyeheight *= t.bscale/10.0f;
-            aboveeye *= t.bscale/10.0f;
-            weight = t.weight;
-            if(_state!=M_SLEEP) spawnplayer(this);
-            trigger = lastmillis+_trigger;
+            eyeheight *= t->bscale/10.0f;
+            aboveeye *= t->bscale/10.0f;
+            weight = t->weight;
+            if(_state!=M_SLEEP) cl.spawnplayer(this);
+            trigger = cl.lastmillis+_trigger;
             targetyaw = yaw = (float)_yaw;
             move = _move;
-            enemy = player1;
-            gunselect = t.gun;
-            maxspeed = (float)t.speed*4;
-            health = t.health;
+            enemy = cl.player1;
+            gunselect = t->gun;
+            maxspeed = (float)t->speed*4;
+            health = t->health;
             armour = 0;
             loopi(NUMGUNS) ammo[i] = 10000;
             pitch = 0;
             roll = 0;
             state = CS_ALIVE;
             anger = 0;
-            copystring(name, t.name);
+            s_strcpy(name, t->name);
         }
         
         // monster AI is sequenced using transitions: they are in a particular state where
@@ -91,12 +79,12 @@ namespace game
             monsterstate = _state;
             move = _moving;
             n = n*130/100;
-            trigger = lastmillis+n-skill*(n/16)+rnd(r+1);
+            trigger = cl.lastmillis+n-ms->skill()*(n/16)+rnd(r+1);
         }
 
         void monsteraction(int curtime)           // main AI thinking routine, called every frame for every monster
         {
-            if(enemy->state==CS_DEAD) { enemy = player1; anger = 0; }
+            if(enemy->state==CS_DEAD) { enemy = cl.player1; anger = 0; }
             normalize_yaw(targetyaw);
             if(targetyaw>yaw)             // slowly turn monster towards his target
             {
@@ -116,9 +104,9 @@ namespace game
                 blocked = false;
                 if(!rnd(20000/monstertypes[mtype].speed))                            // try to jump over obstackle (rare)
                 {
-                    jumping = true;
+                    jumpnext = true;
                 }
-                else if(trigger<lastmillis && (monsterstate!=M_HOME || !rnd(5)))  // search for a way around (common)
+                else if(trigger<cl.lastmillis && (monsterstate!=M_HOME || !rnd(5)))  // search for a way around (common)
                 {
                     targetyaw += 90+rnd(180);                                        // patented "random walk" AI pathfinding (tm) ;)
                     transition(M_SEARCH, 1, 100, 1000);
@@ -132,7 +120,7 @@ namespace game
                 case M_PAIN:
                 case M_ATTACKING:
                 case M_SEARCH:
-                    if(trigger<lastmillis) transition(M_HOME, 1, 100, 200);
+                    if(trigger<cl.lastmillis) transition(M_HOME, 1, 100, 200);
                     break;
                     
                 case M_SLEEP:                       // state classic sp monster start in, wait for visual contact
@@ -145,7 +133,7 @@ namespace game
                     ||(dist<128 && angle<90)
                     ||(dist<256 && angle<45)
                     || angle<10
-                    || (monsterhurt && o.dist(monsterhurtpos)<128))
+                    || (ms->monsterhurt && o.dist(ms->monsterhurtpos)<128))
                     {
                         vec target;
                         if(raycubelos(o, enemy->o, target))
@@ -158,18 +146,18 @@ namespace game
                 }
                 
                 case M_AIMING:                      // this state is the delay between wanting to shoot and actually firing
-                    if(trigger<lastmillis)
+                    if(trigger<cl.lastmillis)
                     {
                         lastaction = 0;
                         attacking = true;
-                        shoot(this, attacktarget);
+                        cl.ws.shoot(this, attacktarget);
                         transition(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
                     targetyaw = enemyyaw;
-                    if(trigger<lastmillis)
+                    if(trigger<cl.lastmillis)
                     {
                         vec target;
                         if(!raycubelos(o, enemy->o, target))    // no visual contact anymore, let monster get as close as possible then search for player
@@ -203,12 +191,13 @@ namespace game
 
             if(move || moving || (onplayer && (onplayer->state!=CS_ALIVE || lastmoveattempt <= onplayer->lastmove)))
             {
-                vec pos = feetpos();
-                loopv(teleports) // equivalent of player entity touch, but only teleports are used
+                vec pos(o);
+                pos.sub(eyeheight);
+                loopv(ms->teleports) // equivalent of player entity touch, but only teleports are used
                 {
-                    entity &e = *entities::ents[teleports[i]];
+                    entity &e = *ms->ents[ms->teleports[i]];
                     float dist = e.o.dist(pos);
-                    if(dist<16) entities::teleport(teleports[i], this);
+                    if(dist<16) cl.et.teleport(ms->teleports[i], this);
                 }
 
                 moveplayer(this, 1, true);        // use physics to move monster
@@ -230,21 +219,18 @@ namespace game
             {
                 anger = 0;
                 enemy = d;
-                monsterhurt = true;
-                monsterhurtpos = o;
+                ms->monsterhurt = true;
+                ms->monsterhurtpos = o;
             }
-            damageeffect(damage, this);
+            cl.ws.damageeffect(damage, this);
             if((health -= damage)<=0)
             {
                 state = CS_DEAD;
-                lastpain = lastmillis;
+                lastpain = cl.lastmillis;
                 playsound(monstertypes[mtype].diesound, &o);
-                monsterkilled();
+                ms->monsterkilled();
                 superdamage = -health;
-                superdamageeffect(vel, this);
-
-                defformatstring(id)("monster_dead_%d", tag);
-                if(identexists(id)) execute(id);
+                cl.ws.superdamageeffect(vel, this);
             }
             else
             {
@@ -254,34 +240,51 @@ namespace game
         }
     };
 
-    int nummonsters(int tag, int state)
+    monstertype *monstertypes;
+        
+    monsterset(fpsclient &_cl) : cl(_cl), ents(_cl.et.ents)
     {
-        int n = 0;
-        loopv(monsters) if(monsters[i]->tag==tag && (monsters[i]->state==CS_ALIVE ? state!=1 : state>=1)) n++;
-        return n;
-    }
-    ICOMMAND(nummonsters, "ii", (int *tag, int *state), intret(nummonsters(*tag, *state)));
+        static monstertype _monstertypes[NUMMONSTERTYPES] =
+        {   
+            { GUN_FIREBALL,  15, 100, 3, 0,   100, 800, 1, 10,  90, S_PAINO, S_DIE1,   "an ogro",     "monster/ogro",       "monster/ogro/vwep"},
+            { GUN_CG,        18,  70, 2, 70,   10, 400, 2, 10,  50, S_PAINR, S_DEATHR, "a rhino",     "monster/rhino",      NULL},
+            { GUN_SG,        13, 120, 1, 100, 300, 400, 4, 14, 115, S_PAINE, S_DEATHE, "ratamahatta", "monster/rat",        "monster/rat/vwep"},
+            { GUN_RIFLE,     14, 200, 1, 80,  400, 300, 4, 18, 145, S_PAINS, S_DEATHS, "a slith",     "monster/slith",      "monster/slith/vwep"},
+            { GUN_RL,        12, 500, 1, 0,   200, 200, 6, 24, 210, S_PAINB, S_DEATHB, "bauul",       "monster/bauul",      "monster/bauul/vwep"},
+            { GUN_BITE,      24,  50, 3, 0,   100, 400, 1, 15,  75, S_PAINP, S_PIGGR2, "a hellpig",   "monster/hellpig",    NULL},
+            { GUN_ICEBALL,   11, 250, 1, 0,    10, 400, 6, 18, 160, S_PAINH, S_DEATHH, "a knight",    "monster/knight",     "monster/knight/vwep"},
+            { GUN_SLIMEBALL, 15, 100, 1, 0,   200, 400, 2, 10,  60, S_PAIND, S_DEATHD, "a goblin",    "monster/goblin",     "monster/goblin/vwep"},
+            { GUN_GL,        22,  50, 1, 0,   200, 400, 1, 10,  40, S_PAIND, S_DEATHD, "a spider",    "monster/spider",      NULL }, 
+        };
+        monstertypes = _monstertypes;
 
+        CCOMMAND(endsp, "", (monsterset *self), self->endsp(false));
+    }
+   
     void preloadmonsters()
     {
-        loopi(NUMMONSTERTYPES) preloadmodel(monstertypes[i].mdlname);
+        loopi(NUMMONSTERTYPES) loadmodel(monstertypes[i].mdlname, -1, true);
     }
 
     vector<monster *> monsters;
     
     int nextmonster, spawnremain, numkilled, monstertotal, mtimestart, remain;
     
+    bool monsterhurt;
+    vec monsterhurtpos;
+
+    IVAR(skill, 1, 3, 10);
+
     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; }
-        monsters.add(new monster(type, rnd(360), 0, M_SEARCH, 1000, 1));
+        monsters.add(new monster(type, rnd(360), M_SEARCH, 1000, 1, this));
     }
 
-    void clearmonsters()     // called after map start or when toggling edit mode to reset/spawn all monsters to initial state
+    void monsterclear(int gamemode)     // called after map start or when toggling edit mode to reset/spawn all monsters to initial state
     {
         removetrackedparticles();
-        removetrackeddynlights();
         loopv(monsters) delete monsters[i]; 
         cleardynentcache();
         monsters.setsize(0);
@@ -292,19 +295,17 @@ namespace game
         monsterhurt = false;
         if(m_dmsp)
         {
-            nextmonster = mtimestart = lastmillis+10000;
-            monstertotal = spawnremain = skill*10;
+            nextmonster = mtimestart = cl.lastmillis+10000;
+            monstertotal = spawnremain = gamemode<0 ? skill()*10 : 0;
         }
         else if(m_classicsp)
         {
-            mtimestart = lastmillis;
-            loopv(entities::ents)
+            mtimestart = cl.lastmillis;
+            loopv(ents) if(ents[i]->type==MONSTER)
             {
-                extentity &e = *entities::ents[i];
-                if(e.type!=MONSTER) continue;
-                monster *m = new monster(e.attr2, e.attr1, e.attr3, M_SLEEP, 100, 0);  
+                monster *m = new monster(ents[i]->attr2, ents[i]->attr1, M_SLEEP, 100, 0, this);  
                 monsters.add(m);
-                m->o = e.o;
+                m->o = ents[i]->o;
                 entinmap(m);
                 updatedynentcache(m);
                 monstertotal++;
@@ -313,7 +314,7 @@ namespace game
         teleports.setsizenodelete(0);
         if(m_dmsp || m_classicsp)
         {
-            loopv(entities::ents) if(entities::ents[i]->type==TELEPORT) teleports.add(i);
+            loopv(ents) if(ents[i]->type==TELEPORT) teleports.add(i);
         }
     }
 
@@ -321,29 +322,27 @@ namespace game
     {
         conoutf(CON_GAMEINFO, allkilled ? "\f2you have cleared the map!" : "\f2you reached the exit!");
         monstertotal = 0;
-        game::addmsg(SV_FORCEINTERMISSION, "r");
+        cl.cc.addmsg(SV_FORCEINTERMISSION, "r");
     }
-    ICOMMAND(endsp, "", (), endsp(false));
-
     
     void monsterkilled()
     {
         numkilled++;
-        player1->frags = numkilled;
+        cl.player1->frags = numkilled;
         remain = monstertotal-numkilled;
         if(remain>0 && remain<=5) conoutf(CON_GAMEINFO, "\f2only %d monster(s) remaining", remain);
     }
 
-    void updatemonsters(int curtime)
+    void monsterthink(int curtime, int gamemode)
     {
-        if(m_dmsp && spawnremain && lastmillis>nextmonster)
+        if(m_dmsp && spawnremain && cl.lastmillis>nextmonster)
         {
             if(spawnremain--==monstertotal) { conoutf(CON_GAMEINFO, "\f2The invasion has begun!"); playsound(S_V_FIGHT); }
-            nextmonster = lastmillis+1000;
+            nextmonster = cl.lastmillis+1000;
             spawnmonster();
         }
         
-        if(killsendsp && monstertotal && !spawnremain && numkilled==monstertotal) endsp(true);
+        if(killsendsp() && monstertotal && !spawnremain && numkilled==monstertotal) endsp(true);
         
         bool monsterwashurt = monsterhurt;
         
@@ -352,7 +351,7 @@ namespace game
             if(monsters[i]->state==CS_ALIVE) monsters[i]->monsteraction(curtime);
             else if(monsters[i]->state==CS_DEAD)
             {
-                if(lastmillis-monsters[i]->lastpain<2000)
+                if(cl.lastmillis-monsters[i]->lastpain<2000)
                 {
                     //monsters[i]->move = 0;
                     monsters[i]->move = monsters[i]->strafe = 0;
@@ -364,48 +363,16 @@ namespace game
         if(monsterwashurt) monsterhurt = false;
     }
 
-    void rendermonsters()
+    void monsterrender()
     {
         loopv(monsters)
         {
             monster &m = *monsters[i];
-            if(m.state!=CS_DEAD || lastmillis-m.lastpain<10000)//m.superdamage<50) 
+            if(m.state!=CS_DEAD || m.superdamage<50) 
             {
-                modelattach vwep[2];
-                vwep[0] = modelattach("tag_weapon", monstertypes[m.mtype].vwepname, ANIM_VWEP_IDLE|ANIM_LOOP, 0);
-                float fade = 1;
-                if(m.state==CS_DEAD) fade -= clamp(float(lastmillis - (m.lastpain + 9000))/1000, 0.0f, 1.0f);
-                renderclient(&m, monstertypes[m.mtype].mdlname, vwep, 0, m.monsterstate==M_ATTACKING ? -ANIM_ATTACK1 : 0, 300, m.lastaction, m.lastpain, fade);
+                modelattach vwep[] = { { monstertypes[m.mtype].vwepname, "tag_weapon", ANIM_VWEP|ANIM_LOOP, 0 }, { NULL } };
+                renderclient(&m, monstertypes[m.mtype].mdlname, vwep, m.monsterstate==M_ATTACKING ? -ANIM_SHOOT : 0, 300, m.lastaction, m.lastpain);
             }
         }
     }
-
-    void suicidemonster(monster *m)
-    {
-        m->monsterpain(400, player1);
-    }
-
-    void hitmonster(int damage, monster *m, fpsent *at, const vec &vel, int gun)
-    {
-        m->monsterpain(damage, at);
-    }
-
-    void spsummary(int accuracy)
-    {
-        conoutf(CON_GAMEINFO, "\f2--- single player time score: ---");
-        int pen, score = 0;
-        pen = (totalmillis-maprealtime)/1000; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time taken: %d seconds (%d simulated seconds)", pen, (lastmillis-maptime)/1000);
-        pen = player1->deaths*60; score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d deaths (1 minute each): %d seconds", player1->deaths, pen);
-        pen = remain*10;          score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for %d monsters remaining (10 seconds each): %d seconds", remain, pen);
-        pen = (10-skill)*20;      score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for lower skill level (20 seconds each): %d seconds", pen);
-        pen = 100-accuracy;       score += pen; if(pen) conoutf(CON_GAMEINFO, "\f2time penalty for missed shots (1 second each %%): %d seconds", pen);
-        defformatstring(aname)("bestscore_%s", getclientmap());
-        const char *bestsc = getalias(aname);
-        int bestscore = *bestsc ? atoi(bestsc) : score;
-        if(score<bestscore) bestscore = score;
-        defformatstring(nscore)("%d", bestscore);
-        alias(aname, nscore);
-        conoutf(CON_GAMEINFO, "\f2TOTAL SCORE (time + time penalties): %d seconds (best so far: %d seconds)", score, bestscore);
-    }
-}
-
+};
diff --git a/fpsgame/movable.cpp b/fpsgame/movable.h
similarity index 74%
rename from fpsgame/movable.cpp
rename to fpsgame/movable.h
index 37011b5..fb8d4f5 100644
--- a/fpsgame/movable.cpp
+++ b/fpsgame/movable.h
@@ -1,8 +1,9 @@
-// movable.cpp: implements physics for inanimate models
-#include "game.h"
+// movable.h: implements physics for inanimate models
 
-namespace game
+struct movableset
 {
+    fpsclient &cl;
+
     enum
     {
         BOXWEIGHT = 25,
@@ -15,16 +16,18 @@ namespace game
 
     struct movable : dynent
     {
-        int etype, mapmodel, health, weight, exploding, tag, dir;
+        int etype, mapmodel, health, weight, explode, tag, dir;
+        movableset *ms;
 
-        movable(const entity &e) : 
+        movable(const entity &e, movableset *ms) : 
             etype(e.type),
             mapmodel(e.attr2),
             health(e.type==BARREL ? (e.attr4 ? e.attr4 : BARRELHEALTH) : 0), 
-            weight(e.type==PLATFORM || e.type==ELEVATOR ? PLATFORMWEIGHT : (e.attr3 ? e.attr3 : (e.type==BARREL ? BARRELWEIGHT : BOXWEIGHT))), 
-            exploding(0),
+            weight(e.type==PLATFORM || e.type==ELEVATOR ? PLATFORMWEIGHT : (e.type==e.attr3 ? e.attr3 : (e.type==BARREL ? BARRELWEIGHT : BOXWEIGHT))), 
+            explode(0),
             tag(e.type==PLATFORM || e.type==ELEVATOR ? e.attr3 : 0),
-            dir(e.type==PLATFORM || e.type==ELEVATOR ? (e.attr4 < 0 ? -1 : 1) : 0)
+            dir(e.type==PLATFORM || e.type==ELEVATOR ? (e.attr4 < 0 ? -1 : 1) : 0),
+            ms(ms)
         {
             state = CS_ALIVE;
             type = ENT_INANIMATE;
@@ -49,33 +52,35 @@ namespace game
             vel.add(push);
             moving = true;
         }
-
-        void explode(dynent *at)
-        {
-            state = CS_DEAD;
-            exploding = 0;
-            game::explode(true, (fpsent *)at, o, this, guns[GUN_BARREL].damage, GUN_BARREL);
-        }
  
         void damaged(int damage, fpsent *at, int gun = -1)
         {
-            if(etype!=BARREL || state!=CS_ALIVE || exploding) return;
+            if(etype!=BARREL || state!=CS_ALIVE || explode) return;
             health -= damage;
             if(health>0) return;
-            if(gun==GUN_BARREL) exploding = lastmillis + EXPLODEDELAY;
-            else explode(at); 
+            if(gun==GUN_BARREL) explode = ms->cl.lastmillis + EXPLODEDELAY;
+            else 
+            {
+                state = CS_DEAD;
+                ms->cl.ws.explode(true, at, o, this, guns[GUN_BARREL].damage, GUN_BARREL);
+            }
         }
 
         void suicide()
         {
             state = CS_DEAD;
-            if(etype==BARREL) explode(player1);
+            if(etype==BARREL) ms->cl.ws.explode(true, ms->cl.player1, o, this, guns[GUN_BARREL].damage, GUN_BARREL);
         }
     };
 
+    movableset(fpsclient &cl) : cl(cl)
+    {
+        CCOMMAND(platform, "ii", (movableset *self, int *tag, int *newdir), self->triggerplatform(*tag, *newdir));
+    }
+    
     vector<movable *> movables;
    
-    void clearmovables()
+    void clear(int gamemode)
     {
         if(movables.length())
         {
@@ -83,11 +88,11 @@ namespace game
             movables.deletecontentsp();
         }
         if(!m_dmsp && !m_classicsp) return;
-        loopv(entities::ents) 
+        loopv(cl.et.ents) 
         {
-            const entity &e = *entities::ents[i];
+            const entity &e = *cl.et.ents[i];
             if(e.type!=BOX && e.type!=BARREL && e.type!=PLATFORM && e.type!=ELEVATOR) continue;
-            movable *m = new movable(e);
+            movable *m = new movable(e, this);
             movables.add(m);
             m->o = e.o;
             entinmap(m);
@@ -114,9 +119,8 @@ namespace game
             }
         }
     }
-    ICOMMAND(platform, "ii", (int *tag, int *newdir), triggerplatform(*tag, *newdir));
 
-    void updatemovables(int curtime)
+    void update(int curtime)
     {
         if(!curtime) return;
         loopv(movables)
@@ -137,37 +141,28 @@ namespace game
                     }
                 }
             }
-            else if(m->exploding && lastmillis >= m->exploding)
+            else if(m->explode && cl.lastmillis >= m->explode)
             {
-                m->explode(m);
+                m->state = CS_DEAD;
+                m->explode = 0;
+                cl.ws.explode(true, (fpsent *)m, m->o, m, guns[GUN_BARREL].damage, GUN_BARREL);
                 adddecal(DECAL_SCORCH, m->o, vec(0, 0, 1), RL_DAMRAD/2);
             }
             else if(m->moving || (m->onplayer && (m->onplayer->state!=CS_ALIVE || m->lastmoveattempt <= m->onplayer->lastmove))) moveplayer(m, 1, true);
         }
     }
 
-    void rendermovables()
+    void render()
     {
         loopv(movables)
         {
             movable &m = *movables[i];
             if(m.state!=CS_ALIVE) continue;
-            vec o = m.feetpos();
+            vec o(m.o);
+            o.z -= m.eyeheight;
             const char *mdlname = mapmodelname(m.mapmodel);
             if(!mdlname) continue;
 			rendermodel(NULL, mdlname, ANIM_MAPMODEL|ANIM_LOOP, o, m.yaw, 0, MDL_LIGHT | MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED, &m);
         }
     }
-    
-    void suicidemovable(movable *m)
-    {
-        m->suicide();
-    }
-
-    void hitmovable(int damage, movable *m, fpsent *at, const vec &vel, int gun)
-    {
-        m->hitpush(damage, vel, at, gun);
-        m->damaged(damage, at, gun);
-    }
-}
-
+};
diff --git a/fpsgame/pch.cpp b/fpsgame/pch.cpp
deleted file mode 100644
index 5d72794..0000000
--- a/fpsgame/pch.cpp
+++ /dev/null
@@ -1,2 +0,0 @@
-#include "game.h"
-
diff --git a/fpsgame/render.cpp b/fpsgame/render.cpp
deleted file mode 100644
index be8af52..0000000
--- a/fpsgame/render.cpp
+++ /dev/null
@@ -1,380 +0,0 @@
-#include "game.h"
-
-namespace game
-{      
-    vector<fpsent *> bestplayers;
-    vector<const char *> bestteams;
-
-    VARP(ragdoll, 0, 1, 1);
-    VARP(ragdollmillis, 0, 10000, 300000);
-    VARP(ragdollfade, 0, 1000, 300000);
-    VARP(playermodel, 0, 0, 2);
-    VARP(forceplayermodels, 0, 0, 1);
-
-    vector<fpsent *> ragdolls;
-
-    void saveragdoll(fpsent *d)
-    {
-        if(!d->ragdoll || !ragdollmillis || (!ragdollfade && lastmillis > d->lastpain + ragdollmillis)) return;
-        fpsent *r = new fpsent(*d);
-        r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain;
-        r->edit = NULL;
-        r->ai = NULL;
-        r->attackchan = r->idlechan = -1;
-        if(d==player1) r->playermodel = playermodel;
-        ragdolls.add(r);
-        d->ragdoll = NULL;   
-    }
-
-    void clearragdolls()
-    {
-        ragdolls.deletecontentsp();
-    }
-
-    void moveragdolls()
-    {
-        loopv(ragdolls)
-        {
-            fpsent *d = ragdolls[i];
-            if(lastmillis > d->lastupdate + ragdollmillis)
-            {
-                delete ragdolls.remove(i--);
-                continue;
-            }
-            moveragdoll(d);
-        }
-    }
-
-    static const playermodelinfo playermodels[3] =
-    {
-        { "mrfixit", "mrfixit/blue", "mrfixit/red", "mrfixit/hudguns", NULL, "mrfixit/horns", { "mrfixit/armor/blue", "mrfixit/armor/green", "mrfixit/armor/yellow" }, "mrfixit", "mrfixit_blue", "mrfixit_red", true, true},
-        { "snoutx10k", "snoutx10k/blue", "snoutx10k/red", "snoutx10k/hudguns", NULL, "snoutx10k/wings", { "snoutx10k/armor/blue", "snoutx10k/armor/green", "snoutx10k/armor/yellow" }, "snoutx10k", "snoutx10k_blue", "snoutx10k_red", true, true },
-        { "ogro/green", "ogro/blue", "ogro/red", "mrfixit/hudguns", "ogro/vwep", NULL, { NULL, NULL, NULL }, "ogro", "ogro_blue", "ogro_red", false, false }
-    };
-
-    int chooserandomplayermodel(int seed)
-    {
-        static int choices[sizeof(playermodels)/sizeof(playermodels[0])];
-        int numchoices = 0;
-        loopi(sizeof(playermodels)/sizeof(playermodels[0])) if(i == playermodel || playermodels[i].selectable) choices[numchoices++] = i;
-        if(numchoices <= 0) return -1;
-        return choices[(seed&0xFFFF)%numchoices];
-    }
-
-    const playermodelinfo *getplayermodelinfo(int n)
-    {
-        if(size_t(n) >= sizeof(playermodels)/sizeof(playermodels[0])) return NULL;
-        return &playermodels[n];
-    }
-
-    const playermodelinfo &getplayermodelinfo(fpsent *d)
-    {
-        const playermodelinfo *mdl = getplayermodelinfo(d==player1 || forceplayermodels ? playermodel : d->playermodel);
-        if(!mdl || !mdl->selectable) mdl = getplayermodelinfo(playermodel);
-        return *mdl;
-    }
-
-    void preloadplayermodel()
-    {
-        loopi(3)
-        {
-            const playermodelinfo *mdl = getplayermodelinfo(i);
-            if(!mdl) break;
-            if(i != playermodel && (!multiplayer(false) || forceplayermodels || !mdl->selectable)) continue;
-            if(m_teammode)
-            {
-                preloadmodel(mdl->blueteam);
-                preloadmodel(mdl->redteam);
-            }
-            else preloadmodel(mdl->ffa);
-            if(mdl->vwep) preloadmodel(mdl->vwep);
-            if(mdl->quad) preloadmodel(mdl->quad);
-            loopj(3) if(mdl->armour[j]) preloadmodel(mdl->armour[j]);
-        }
-    }
-    
-    VAR(testquad, 0, 0, 1);
-    VAR(testarmour, 0, 0, 1);
-    VAR(testteam, 0, 0, 3);
-
-    void renderplayer(fpsent *d, const playermodelinfo &mdl, int team, float fade, bool mainpass)
-    {
-        int lastaction = d->lastaction, hold = mdl.vwep || d->gunselect==GUN_PISTOL ? 0 : (ANIM_HOLD1+d->gunselect)|ANIM_LOOP, attack = ANIM_ATTACK1+d->gunselect, delay = mdl.vwep ? 300 : guns[d->gunselect].attackdelay+50;
-        if(intermission && d->state!=CS_DEAD)
-        {
-            lastaction = 0;
-            hold = attack = ANIM_LOSE|ANIM_LOOP;
-            delay = 0;
-            if(m_teammode) loopv(bestteams) { if(!strcmp(bestteams[i], d->team)) { hold = attack = ANIM_WIN|ANIM_LOOP; break; } }
-            else if(bestplayers.find(d)>=0) hold = attack = ANIM_WIN|ANIM_LOOP;
-        }
-        else if(d->state==CS_ALIVE && d->lasttaunt && lastmillis-d->lasttaunt<1000 && lastmillis-d->lastaction>delay)
-        {
-            lastaction = d->lasttaunt;
-            hold = attack = ANIM_TAUNT;
-            delay = 1000;
-        }
-        modelattach a[5];
-        static const char *vweps[] = {"vwep/fist", "vwep/shotg", "vwep/chaing", "vwep/rocket", "vwep/rifle", "vwep/gl", "vwep/pistol"};
-        int ai = 0;
-        if((!mdl.vwep || d->gunselect!=GUN_FIST) && d->gunselect<=GUN_PISTOL)
-        {
-            int vanim = ANIM_VWEP_IDLE|ANIM_LOOP, vtime = 0;
-            if(lastaction && d->lastattackgun==d->gunselect && lastmillis < lastaction + delay)
-            {
-                vanim = ANIM_VWEP_SHOOT;
-                vtime = lastaction;
-            }
-            a[ai++] = modelattach("tag_weapon", mdl.vwep ? mdl.vwep : vweps[d->gunselect], vanim, vtime);
-        }
-        if(d->state==CS_ALIVE)
-        {
-            if((testquad || d->quadmillis) && mdl.quad)
-                a[ai++] = modelattach("tag_powerup", mdl.quad, ANIM_POWERUP|ANIM_LOOP, 0);
-            if(testarmour || d->armour)
-            {
-                int type = clamp(d->armourtype, (int)A_BLUE, (int)A_YELLOW);
-                if(mdl.armour[type])
-                    a[ai++] = modelattach("tag_shield", mdl.armour[type], ANIM_SHIELD|ANIM_LOOP, 0);
-            }
-        }
-        if(mainpass)
-        {
-            d->muzzle = vec(-1, -1, -1);
-            a[ai++] = modelattach("tag_muzzle", &d->muzzle);
-        }
-        const char *mdlname = mdl.ffa;
-        switch(testteam ? testteam-1 : team)
-        {
-            case 1: mdlname = mdl.blueteam; break;
-            case 2: mdlname = mdl.redteam; break;
-        }
-        renderclient(d, mdlname, a[0].tag ? a : NULL, hold, attack, delay, lastaction, intermission && d->state!=CS_DEAD ? 0 : d->lastpain, fade, ragdoll && mdl.ragdoll);
-#if 0
-        if(d->state!=CS_DEAD && d->quadmillis) 
-        {
-            entitylight light;
-            rendermodel(&light, "quadrings", ANIM_MAPMODEL|ANIM_LOOP, vec(d->o).sub(vec(0, 0, d->eyeheight/2)), 360*lastmillis/1000.0f, 0, MDL_DYNSHADOW | MDL_CULL_VFC | MDL_CULL_DIST);
-        }
-#endif
-    }
-
-    VARP(teamskins, 0, 0, 1);
-
-    void rendergame(bool mainpass)
-    {
-        if(mainpass) ai::render();
-
-        if(intermission)
-        {
-            bestteams.setsize(0);
-            bestplayers.setsize(0);
-            if(m_teammode) getbestteams(bestteams);
-            else getbestplayers(bestplayers);
-        }
-
-        startmodelbatches();
-
-        fpsent *exclude = isthirdperson() ? NULL : followingplayer();
-        loopv(players)
-        {
-            fpsent *d = players[i];
-            if(d == player1 || d->state==CS_SPECTATOR || d->state==CS_SPAWNING || d->lifesequence < 0 || d == exclude) continue;
-            int team = 0;
-            if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2;
-            renderplayer(d, getplayermodelinfo(d), team, 1, mainpass);
-            copystring(d->info, colorname(d, NULL, "@"));
-            if(d->maxhealth>100) { defformatstring(sn)(" +%d", d->maxhealth-100); concatstring(d->info, sn); }
-            if(d->state!=CS_DEAD) particle_text(d->abovehead(), d->info, PART_TEXT, 1, team ? (team==1 ? 0x6496FF : 0xFF4B19) : 0x1EC850, 2.0f);
-        }
-        loopv(ragdolls)
-        {
-            fpsent *d = ragdolls[i];
-            int team = 0;
-            if(teamskins || m_teammode) team = isteam(player1->team, d->team) ? 1 : 2;
-            float fade = 1.0f;
-            if(ragdollmillis && ragdollfade) 
-                fade -= clamp(float(lastmillis - (d->lastupdate + max(ragdollmillis - ragdollfade, 0)))/min(ragdollmillis, ragdollfade), 0.0f, 1.0f);
-            renderplayer(d, getplayermodelinfo(d), team, fade, mainpass);
-        } 
-        if(isthirdperson() && !followingplayer()) renderplayer(player1, getplayermodelinfo(player1), teamskins || m_teammode ? 1 : 0, 1, mainpass);
-        rendermonsters();
-        rendermovables();
-        entities::renderentities();
-        renderbouncers();
-        renderprojectiles();
-        if(cmode) cmode->rendergame();
-
-        endmodelbatches();
-    }
-
-    VARP(hudgun, 0, 1, 1);
-    VARP(hudgunsway, 0, 1, 1);
-    VARP(teamhudguns, 0, 1, 1);
-    VARP(chainsawhudgun, 0, 1, 1);
-    VAR(testhudgun, 0, 0, 1);
-
-    FVAR(swaystep, 1, 35.0f, 100);
-    FVAR(swayside, 0, 0.04f, 1);
-    FVAR(swayup, 0, 0.05f, 1);
-
-    float swayfade = 0, swayspeed = 0, swaydist = 0;
-    vec swaydir(0, 0, 0);
-
-    void swayhudgun(int curtime)
-    {
-        fpsent *d = hudplayer();
-        if(d->state != CS_SPECTATOR)
-        {
-            if(d->physstate >= PHYS_SLOPE)
-            {
-                swayspeed = min(sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y), d->maxspeed);
-                swaydist += swayspeed*curtime/1000.0f;
-                swaydist = fmod(swaydist, 2*swaystep);
-                swayfade = 1;
-            }
-            else if(swayfade > 0)
-            {
-                swaydist += swayspeed*swayfade*curtime/1000.0f;
-                swaydist = fmod(swaydist, 2*swaystep);
-                swayfade -= 0.5f*(curtime*d->maxspeed)/(swaystep*1000.0f);
-            }
-
-            float k = pow(0.7f, curtime/10.0f);
-            swaydir.mul(k);
-            vec vel(d->vel);
-            vel.add(d->falling);
-            swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed))));
-        }
-    }
-
-    dynent guninterp;
-
-    SVARP(hudgunsdir, "");
-
-    void drawhudmodel(fpsent *d, int anim, float speed = 0, int base = 0)
-    {
-        if(d->gunselect>GUN_PISTOL) return;
-
-        vec sway;
-        vecfromyawpitch(d->yaw, 0, 0, 1, sway);
-        float steps = swaydist/swaystep*M_PI;
-        sway.mul(swayside*cosf(steps));
-        sway.z = swayup*(fabs(sinf(steps)) - 1);
-        sway.add(swaydir).add(d->o);
-        if(!hudgunsway) sway = d->o;
-
-#if 0
-        if(player1->state!=CS_DEAD && player1->quadmillis)
-        {
-            float t = 0.5f + 0.5f*sinf(2*M_PI*lastmillis/1000.0f);
-            color.y = color.y*(1-t) + t;
-        }
-#endif
-        const playermodelinfo &mdl = getplayermodelinfo(d);
-        defformatstring(gunname)("%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, guns[d->gunselect].file);
-        if((m_teammode || teamskins) && teamhudguns)
-            concatstring(gunname, d==player1 || isteam(d->team, player1->team) ? "/blue" : "/red");
-        else if(testteam > 1)
-            concatstring(gunname, testteam==2 ? "/blue" : "/red");
-        modelattach a[2];
-        d->muzzle = vec(-1, -1, -1);
-        a[0] = modelattach("tag_muzzle", &d->muzzle);
-        dynent *interp = NULL;
-        if(d->gunselect==GUN_FIST && chainsawhudgun)
-        {
-            anim |= ANIM_LOOP;
-            base = 0;
-            interp = &guninterp;
-        }
-        rendermodel(NULL, gunname, anim, sway, testhudgun ? 0 : d->yaw+90, testhudgun ? 0 : d->pitch, MDL_LIGHT, interp, a, base, (int)ceil(speed));
-        if(d->muzzle.x >= 0) d->muzzle = calcavatarpos(d->muzzle, 12);
-    }
-
-    void drawhudgun()
-    {
-        fpsent *d = hudplayer();
-        if(d->state==CS_SPECTATOR || d->state==CS_EDITING || !hudgun || editmode) 
-        { 
-            d->muzzle = player1->muzzle = vec(-1, -1, -1);
-            return;
-        }
-
-        int rtime = guns[d->gunselect].attackdelay;
-        if(d->lastaction && d->lastattackgun==d->gunselect && lastmillis-d->lastaction<rtime)
-        {
-            drawhudmodel(d, ANIM_GUN_SHOOT|ANIM_SETSPEED, rtime/17.0f, d->lastaction);
-        }
-        else
-        {
-            drawhudmodel(d, ANIM_GUN_IDLE|ANIM_LOOP);
-        }
-    }
-
-    void renderavatar()
-    {
-        drawhudgun();
-    }
-
-    vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d)
-    {
-        if(d->muzzle.x >= 0) return d->muzzle;
-        vec offset(from);
-        if(d!=hudplayer() || isthirdperson())
-        {
-            vec front, right;
-            vecfromyawpitch(d->yaw, d->pitch, 1, 0, front);
-            offset.add(front.mul(d->radius));
-            if(d->type!=ENT_AI)
-            {
-                offset.z += (d->aboveeye + d->eyeheight)*0.75f - d->eyeheight;
-                vecfromyawpitch(d->yaw, 0, 0, -1, right);
-                offset.add(right.mul(0.5f*d->radius));
-                offset.add(front);
-            }
-            return offset;
-        }
-        offset.add(vec(to).sub(from).normalize().mul(2));
-        if(hudgun)
-        {
-            offset.sub(vec(camup).mul(1.0f));
-            offset.add(vec(camright).mul(0.8f));
-        }
-        else offset.sub(vec(camup).mul(0.8f));
-        return offset;
-    }
-
-    void preloadweapons()
-    {
-        const playermodelinfo &mdl = getplayermodelinfo(player1);
-        loopi(NUMGUNS)
-        {
-            const char *file = guns[i].file;
-            if(!file) continue;
-            string fname;
-            if((m_teammode || teamskins) && teamhudguns)
-            {
-                formatstring(fname)("%s/%s/blue", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file);
-                preloadmodel(fname);
-            }
-            else
-            {
-                formatstring(fname)("%s/%s", hudgunsdir[0] ? hudgunsdir : mdl.hudguns, file);
-                preloadmodel(fname);
-            }
-            formatstring(fname)("vwep/%s", file);
-            preloadmodel(fname);
-        }
-    }
-
-    void preload()
-    {
-        if(hudgun) preloadweapons();
-        preloadbouncers();
-        preloadplayermodel();
-        entities::preloadentities();
-        if(m_sp) preloadmonsters();
-    }
-
-}
-
diff --git a/fpsgame/scoreboard.cpp b/fpsgame/scoreboard.h
similarity index 54%
rename from fpsgame/scoreboard.cpp
rename to fpsgame/scoreboard.h
index 3580b20..15e03be 100644
--- a/fpsgame/scoreboard.cpp
+++ b/fpsgame/scoreboard.h
@@ -1,16 +1,52 @@
 // creation of scoreboard
-#include "game.h"
 
-namespace game
+struct scoreboard : g3d_callback
 {
-    VARP(scoreboard2d, 0, 1, 1);
-    VARP(showclientnum, 0, 0, 1);
-    VARP(showpj, 0, 0, 1);
-    VARP(showping, 0, 1, 1);
-    VARP(showspectators, 0, 1, 1);
-    VARP(highlightscore, 0, 1, 1);
-    VARP(showconnecting, 0, 0, 1);
+    bool scoreson;
+    vec menupos;
+    int menustart;
+    fpsclient &cl;
+
+    IVARP(scoreboard2d, 0, 1, 1);
+    IVARP(showclientnum, 0, 0, 1);
+    IVARP(showpj, 0, 1, 1);
+    IVARP(showping, 0, 1, 1);
+    IVARP(showspectators, 0, 1, 1);
+    IVARP(highlightscore, 0, 1, 1);
+    IVARP(showconnecting, 0, 0, 1);
+
+    scoreboard(fpsclient &_cl) : scoreson(false), cl(_cl)
+    {
+        CCOMMAND(showscores, "D", (scoreboard *self, int *down), self->showscores(*down!=0));
+    }
+
+    void showscores(bool on)
+    {
+        if(!scoreson && on)
+        {
+            menupos = menuinfrontofplayer();
+            menustart = starttime();
+        }
+        scoreson = on;
+    }
+
+    struct sline { string s; };
 
+    struct teamscore
+    {
+        const char *team;
+        int score;
+        teamscore() {}
+        teamscore(const char *s, int n) : team(s), score(n) {}
+    };
+
+    static int teamscorecmp(const teamscore *x, const teamscore *y)
+    {
+        if(x->score > y->score) return -1;
+        if(x->score < y->score) return 1;
+        return strcmp(x->team, y->team);
+    }
+    
     static int playersort(const fpsent **a, const fpsent **b)
     {
         if((*a)->state==CS_SPECTATOR)
@@ -24,33 +60,52 @@ namespace game
         return strcmp((*a)->name, (*b)->name);
     }
 
-    void getbestplayers(vector<fpsent *> &best)
+    void bestplayers(vector<fpsent *> &best)
     {
-        loopv(players)
+        loopi(cl.numdynents())
         {
-            fpsent *o = players[i];
-            if(o->state!=CS_SPECTATOR) best.add(o);
+            fpsent *o = (fpsent *)cl.iterdynents(i);
+            if(o && o->type==ENT_PLAYER && o->state!=CS_SPECTATOR) best.add(o);
         }
-        best.sort(playersort);
+        best.sort(playersort);   
         while(best.length()>1 && best.last()->frags < best[0]->frags) best.drop();
     }
 
     void sortteams(vector<teamscore> &teamscores)
     {
-        if(cmode && cmode->hidefrags()) cmode->getteamscores(teamscores);
+        int gamemode = cl.gamemode;
+        if(m_capture)
+        {
+            loopv(cl.cpc.scores) teamscores.add(teamscore(cl.cpc.scores[i].team, cl.cpc.scores[i].total));
+        }
+        else if(m_ctf) 
+        {
+            loopv(cl.ctf.flags) if(cl.ctf.flags[i].score)
+            {
+                const char *team = ctfflagteam(cl.ctf.flags[i].team);
+                if(!team) continue;
+                teamscore *ts = NULL;
+                loopv(teamscores) if(!strcmp(teamscores[i].team, team)) { ts = &teamscores[i]; break; } 
+                if(!ts) teamscores.add(teamscore(team, cl.ctf.flags[i].score));
+                else ts->score += cl.ctf.flags[i].score;
+            }
+        }
 
-        loopv(players)
+        loopi(cl.numdynents())
         {
-            fpsent *o = players[i];
-            teamscore *ts = NULL;
-            loopv(teamscores) if(!strcmp(teamscores[i].team, o->team)) { ts = &teamscores[i]; break; }
-            if(!ts) teamscores.add(teamscore(o->team, cmode && cmode->hidefrags() ? 0 : o->frags));
-            else if(!cmode || !cmode->hidefrags()) ts->score += o->frags;
+            fpsent *o = (fpsent *)cl.iterdynents(i);
+            if(o && o->type==ENT_PLAYER)
+            {
+                teamscore *ts = NULL;
+                loopv(teamscores) if(!strcmp(teamscores[i].team, o->team)) { ts = &teamscores[i]; break; }
+                if(!ts) teamscores.add(teamscore(o->team, m_capture || m_ctf ? 0 : o->frags));
+                else if(!m_capture && !m_ctf) ts->score += o->frags;
+            }
         }
-        teamscores.sort(teamscore::compare);
+        teamscores.sort(teamscorecmp);
     }
 
-    void getbestteams(vector<const char *> &best)
+    void bestteams(vector<const char *> &best)
     {
         vector<teamscore> teamscores;
         sortteams(teamscores);
@@ -62,8 +117,8 @@ namespace game
     {
         vector<fpsent *> players;
     };
-    static vector<scoregroup *> groups;
-    static vector<fpsent *> spectators;
+    vector<scoregroup *> groups;
+    vector<fpsent *> spectators;
 
     static int scoregroupcmp(const scoregroup **x, const scoregroup **y)
     {
@@ -79,14 +134,14 @@ namespace game
         return (*x)->team && (*y)->team ? strcmp((*x)->team, (*y)->team) : 0;
     }
 
-    static int groupplayers()
+    int groupplayers()
     {
-        int numgroups = 0;
+        int gamemode = cl.gamemode, numgroups = 0;
         spectators.setsize(0);
-        loopv(players)
+        loopi(cl.numdynents())
         {
-            fpsent *o = players[i];
-            if(!showconnecting && !o->name[0]) continue;
+            fpsent *o = (fpsent *)cl.iterdynents(i);
+            if(!o || o->type!=ENT_PLAYER || (!showconnecting() && !o->name[0])) continue;
             if(o->state==CS_SPECTATOR) { spectators.add(o); continue; }
             const char *team = m_teammode && o->team[0] ? o->team : NULL;
             bool found = false;
@@ -94,7 +149,7 @@ namespace game
             {
                 scoregroup &g = *groups[j];
                 if(team!=g.team && (!team || !g.team || strcmp(team, g.team))) continue;
-                if(team && (!cmode || !cmode->hidefrags())) g.score += o->frags;
+                if(team && !m_capture && !m_ctf) g.score += o->frags;
                 g.players.add(o);
                 found = true;
             }
@@ -103,7 +158,12 @@ namespace game
             scoregroup &g = *groups[numgroups++];
             g.team = team;
             if(!team) g.score = 0;
-            else if(cmode && cmode->hidefrags()) g.score = cmode->getteamscore(o->team);
+            else if(m_capture) g.score = cl.cpc.findscore(o->team).total;
+            else if(m_ctf) 
+            {
+                g.score = 0;
+                loopv(cl.ctf.flags) if(cl.ctf.flags[i].team==ctfteamflag(o->team)) g.score += cl.ctf.flags[i].score;
+            }
             else g.score = o->frags;
             g.players.setsize(0);
             g.players.add(o);
@@ -114,30 +174,32 @@ namespace game
         return numgroups;
     }
 
-    void renderscoreboard(g3d_gui &g, bool firstpass)
+    void gui(g3d_gui &g, bool firstpass)
     {
-        const char *mname = getclientmap();
-        defformatstring(modemapstr)("%s: %s", server::modename(gamemode), mname[0] ? mname : "[new map]");
-        if(m_timed && mname[0] && minremain >= 0)
+        g.start(menustart, 0.03f, NULL, false);
+   
+        int gamemode = cl.gamemode;
+        s_sprintfd(modemapstr)("%s: %s", fpsserver::modestr(gamemode), cl.getclientmap()[0] ? cl.getclientmap() : "[new map]");
+        if((gamemode>1 || (gamemode==0 && (multiplayer(false) || cl.cc.demoplayback))) && cl.minremain >= 0)
         {
-            if(!minremain) concatstring(modemapstr, ", intermission");
+            if(!cl.minremain) s_strcat(modemapstr, ", intermission");
             else
             {
-                defformatstring(timestr)(", %d %s remaining", minremain, minremain==1 ? "minute" : "minutes");
-                concatstring(modemapstr, timestr);
+                s_sprintfd(timestr)(", %d %s remaining", cl.minremain, cl.minremain==1 ? "minute" : "minutes");
+                s_strcat(modemapstr, timestr);
             }
         }
-        if(paused || ispaused()) concatstring(modemapstr, ", paused");
-
         g.text(modemapstr, 0xFFFF80, "server");
     
+        const playermodelinfo &mdl = cl.fr.getplayermodelinfo();
         int numgroups = groupplayers();
         loopk(numgroups)
         {
             if((k%2)==0) g.pushlist(); // horizontal
             
             scoregroup &sg = *groups[k];
-            int bgcolor = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? 0x3030C0 : 0xC03030) : 0,
+            const char *icon = sg.team && m_teammode ? (isteam(cl.player1->team, sg.team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon;
+            int bgcolor = sg.team && m_teammode ? (isteam(cl.player1->team, sg.team) ? 0x3030C0 : 0xC03030) : 0,
                 fgcolor = 0xFFFF80;
 
             g.pushlist(); // vertical
@@ -161,15 +223,20 @@ namespace game
             g.text("", 0, "server");
             loopscoregroup(o,
             {
-                if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1))
+                if(o==cl.player1 && highlightscore() && (multiplayer(false) || cl.cc.demoplayback))
                 {
                     g.pushlist();
                     g.background(0x808080, numgroups>1 ? 3 : 5);
                 }
-                const playermodelinfo &mdl = getplayermodelinfo(o);
-                const char *icon = sg.team && m_teammode ? (isteam(player1->team, sg.team) ? mdl.blueicon : mdl.redicon) : mdl.ffaicon;
-                g.text("", 0, icon);
-                if(o==player1 && highlightscore && (multiplayer(false) || demoplayback || players.length() > 1)) g.poplist();
+                const char *oicon = icon;
+                if(m_assassin)
+                {
+                    if(cl.asc.targets.find(o)>=0) oicon = mdl.redicon;
+                    else if(cl.asc.hunters.find(o)>=0) oicon = mdl.ffaicon;
+                    else oicon = mdl.blueicon;
+                }
+                g.text("", 0, oicon);
+                if(o==cl.player1 && highlightscore() && (multiplayer(false) || cl.cc.demoplayback)) g.poplist();
             });
             g.poplist();
 
@@ -177,13 +244,13 @@ namespace game
             {
                 g.pushlist(); // vertical
 
-                if(sg.score>=10000) g.textf("%s: WIN", fgcolor, NULL, sg.team);
+                if(m_capture && sg.score>=10000) g.textf("%s: WIN", fgcolor, NULL, sg.team);
                 else g.textf("%s: %d", fgcolor, NULL, sg.team, sg.score);
 
                 g.pushlist(); // horizontal
             }
 
-            if(!cmode || !cmode->hidefrags())
+            if(!m_capture && !m_ctf)
             { 
                 g.pushlist();
                 g.strut(7);
@@ -192,9 +259,9 @@ namespace game
                 g.poplist();
             }
 
-            if(multiplayer(false) || demoplayback)
+            if(multiplayer(false) || cl.cc.demoplayback)
             {
-                if(showpj)
+                if(showpj())
                 {
                     g.pushlist();
                     g.strut(6);
@@ -207,38 +274,28 @@ namespace game
                     g.poplist();
                 }
         
-                if(showping)
+                if(showping())
                 {
                     g.pushlist();
                     g.text("ping", fgcolor);
                     g.strut(6);
-                    loopscoregroup(o, 
-                    {
-                        fpsent *p = getclient(o->ownernum);
-                        if(!p) p = o;
-                        if(!showpj && p->state==CS_LAGGED) g.text("LAG", 0xFFFFDD);
-                        else g.textf("%d", 0xFFFFDD, NULL, p->ping);
-                    });
+                    loopscoregroup(o, g.textf("%d", 0xFFFFDD, NULL, o->ping));
                     g.poplist();
                 }
             }
 
             g.pushlist();
             g.text("name", fgcolor);
-            g.strut(10);
             loopscoregroup(o, 
             {
-                int status = o->state!=CS_DEAD ? 0xFFFFDD : 0x606060;
-                if(o->privilege)
-                {
-                    status = o->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
-                    if(o->state==CS_DEAD) status = (status>>1)&0x7F7F7F;
-                }
-                g.text(colorname(o), status);
+                int status = 0xFFFFDD;
+                if(o->privilege) status = o->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
+                else if(o->state==CS_DEAD) status = 0x606060;
+                g.text(cl.colorname(o), status);
             });
             g.poplist();
 
-            if(showclientnum || player1->privilege>=PRIV_MASTER)
+            if(showclientnum() || cl.player1->privilege>=PRIV_MASTER)
             {
                 g.space(1);
                 g.pushlist();
@@ -260,9 +317,9 @@ namespace game
             else g.poplist(); // horizontal
         }
         
-        if(showspectators && spectators.length())
+        if(showspectators() && spectators.length())
         {
-            if(showclientnum || player1->privilege>=PRIV_MASTER)
+            if(showclientnum() || cl.player1->privilege>=PRIV_MASTER)
             {
                 g.pushlist();
                 
@@ -271,15 +328,13 @@ namespace game
                 loopv(spectators) 
                 {
                     fpsent *o = spectators[i];
-                    int status = 0xFFFFDD;
-                    if(o->privilege) status = o->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
-                    if(o==player1 && highlightscore)
+                    if(o==cl.player1 && highlightscore())
                     {
                         g.pushlist();
                         g.background(0x808080, 3);
                     }
-                    g.text(colorname(o), status, "spectator");
-                    if(o==player1 && highlightscore) g.poplist();
+                    g.text(cl.colorname(o), 0xFFFFDD, mdl.ffaicon);
+                    if(o==cl.player1 && highlightscore()) g.poplist();
                 }
                 g.poplist();
 
@@ -299,69 +354,32 @@ namespace game
                     if((i%3)==0) 
                     {
                         g.pushlist();
-                        g.text("", 0xFFFFDD, "spectator");
+                        g.text("", 0xFFFFDD, mdl.ffaicon);
                     }
                     fpsent *o = spectators[i];
                     int status = 0xFFFFDD;
                     if(o->privilege) status = o->privilege>=PRIV_ADMIN ? 0xFF8000 : 0x40FF80;
-                    if(o==player1 && highlightscore)
+                    if(o==cl.player1 && highlightscore())
                     {
                         g.pushlist();
                         g.background(0x808080);
                     }
-                    g.text(colorname(o), status);
-                    if(o==player1 && highlightscore) g.poplist();
+                    g.text(cl.colorname(o), status);
+                    if(o==cl.player1 && highlightscore()) g.poplist();
                     if(i+1<spectators.length() && (i+1)%3) g.space(1);
                     else g.poplist();
                 }
             }
         }
+             
+        g.end();
     }
-
-    struct scoreboardgui : g3d_callback
+    
+    void show()
     {
-        bool showing;
-        vec menupos;
-        int menustart;
-
-        scoreboardgui() : showing(false) {}
-
-        void show(bool on)
+        if(scoreson) 
         {
-            if(!showing && on)
-            {
-                menupos = menuinfrontofplayer();
-                menustart = starttime();
-            }
-            showing = on;
+            g3d_addgui(this, menupos, scoreboard2d() ? GUI_FORCE_2D : GUI_2D | GUI_FOLLOW);
         }
-
-        void gui(g3d_gui &g, bool firstpass)
-        {
-            g.start(menustart, 0.03f, NULL, false);
-            renderscoreboard(g, firstpass);
-            g.end();
-        }
-
-        void render()
-        {
-            if(showing) g3d_addgui(this, menupos, scoreboard2d ? GUI_FORCE_2D : GUI_2D | GUI_FOLLOW);
-        }
-
-    } scoreboard;
-
-    void g3d_gamemenus()
-    {
-        scoreboard.render();
     }
-
-    VARFN(scoreboard, showscoreboard, 0, 0, 1, scoreboard.show(showscoreboard!=0));
-
-    void showscores(bool on)
-    {
-        showscoreboard = on ? 1 : 0;
-        scoreboard.show(on);
-    }
-    ICOMMAND(showscores, "D", (int *down), showscores(*down!=0));
-}
-
+};
diff --git a/fpsgame/server.cpp b/fpsgame/server.cpp
deleted file mode 100644
index 399caaf..0000000
--- a/fpsgame/server.cpp
+++ /dev/null
@@ -1,2556 +0,0 @@
-#include "game.h"
-
-namespace game
-{
-    void parseoptions(vector<const char *> &args)
-    {
-        loopv(args)
-#ifndef STANDALONE
-            if(!game::clientoption(args[i]))
-#endif
-            if(!server::serveroption(args[i]))
-                conoutf(CON_ERROR, "unknown command-line option: %s", args[i]);
-    }
-}
-
-extern ENetAddress masteraddress;
-
-namespace server
-{
-    struct server_entity            // server side version of "entity" type
-    {
-        int type;
-        int spawntime;
-        char spawned;
-    };
-
-    static const int DEATHMILLIS = 300;
-
-    struct clientinfo;
-
-    struct gameevent
-    {
-        virtual ~gameevent() {}
-
-        virtual bool flush(clientinfo *ci, int fmillis);
-        virtual void process(clientinfo *ci) {}
-
-        virtual bool keepable() const { return false; }
-    };
-
-    struct timedevent : gameevent
-    {
-        int millis;
-
-        bool flush(clientinfo *ci, int fmillis);
-    };
-
-    struct hitinfo
-    {
-        int target;
-        int lifesequence;
-        union
-        {
-            int rays;
-            float dist;
-        };
-        vec dir;
-    };
-
-    struct shotevent : timedevent
-    {
-        int id, gun;
-        vec from, to;
-        vector<hitinfo> hits;
-
-        void process(clientinfo *ci);
-    };
-
-    struct explodeevent : timedevent
-    {
-        int id, gun;
-        vector<hitinfo> hits;
-
-        bool keepable() const { return true; }
-
-        void process(clientinfo *ci);
-    };
-
-    struct suicideevent : gameevent
-    {
-        void process(clientinfo *ci);
-    };
-
-    struct pickupevent : gameevent
-    {
-        int ent;
-
-        void process(clientinfo *ci);
-    };
-
-    template <int N>
-    struct projectilestate
-    {
-        int projs[N];
-        int numprojs;
-
-        projectilestate() : numprojs(0) {}
-
-        void reset() { numprojs = 0; }
-
-        void add(int val)
-        {
-            if(numprojs>=N) numprojs = 0;
-            projs[numprojs++] = val;
-        }
-
-        bool remove(int val)
-        {
-            loopi(numprojs) if(projs[i]==val)
-            {
-                projs[i] = projs[--numprojs];
-                return true;
-            }
-            return false;
-        }
-    };
-
-    struct gamestate : fpsstate
-    {
-        vec o;
-        int state, editstate;
-        int lastdeath, lastspawn, lifesequence;
-        int lastshot;
-        projectilestate<8> rockets, grenades;
-        int frags, flags, deaths, teamkills, shotdamage, damage;
-        int lasttimeplayed, timeplayed;
-        float effectiveness;
-
-        gamestate() : state(CS_DEAD), editstate(CS_DEAD) {}
-
-        bool isalive(int gamemillis)
-        {
-            return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS);
-        }
-
-        bool waitexpired(int gamemillis)
-        {
-            return gamemillis - lastshot >= gunwait;
-        }
-
-        void reset()
-        {
-            if(state!=CS_SPECTATOR) state = editstate = CS_DEAD;
-            maxhealth = 100;
-            rockets.reset();
-            grenades.reset();
-
-            timeplayed = 0;
-            effectiveness = 0;
-            frags = flags = deaths = teamkills = shotdamage = damage = 0;
-
-            respawn();
-        }
-
-        void respawn()
-        {
-            fpsstate::respawn();
-            o = vec(-1e10f, -1e10f, -1e10f);
-            lastdeath = 0;
-            lastspawn = -1;
-            lastshot = 0;
-        }
-
-        void reassign()
-        {
-            respawn();
-            rockets.reset();
-            grenades.reset();
-        }
-    };
-
-    struct savedscore
-    {
-        uint ip;
-        string name;
-        int maxhealth, frags, flags, deaths, teamkills, shotdamage, damage;
-        int timeplayed;
-        float effectiveness;
-
-        void save(gamestate &gs)
-        {
-            maxhealth = gs.maxhealth;
-            frags = gs.frags;
-            flags = gs.flags;
-            deaths = gs.deaths;
-            teamkills = gs.teamkills;
-            shotdamage = gs.shotdamage;
-            damage = gs.damage;
-            timeplayed = gs.timeplayed;
-            effectiveness = gs.effectiveness;
-        }
-
-        void restore(gamestate &gs)
-        {
-            if(gs.health==gs.maxhealth) gs.health = maxhealth;
-            gs.maxhealth = maxhealth;
-            gs.frags = frags;
-            gs.flags = flags;
-            gs.deaths = deaths;
-            gs.teamkills = teamkills;
-            gs.shotdamage = shotdamage;
-            gs.damage = damage;
-            gs.timeplayed = timeplayed;
-            gs.effectiveness = effectiveness;
-        }
-    };
-
-    struct clientinfo
-    {
-        int clientnum, ownernum, connectmillis, sessionid;
-        string name, team, mapvote;
-        int playermodel;
-        int modevote;
-        int privilege;
-        bool connected, local, timesync;
-        int gameoffset, lastevent;
-        gamestate state;
-        vector<gameevent *> events;
-        vector<uchar> position, messages;
-        int posoff, poslen, msgoff, msglen;
-        vector<clientinfo *> bots;
-        uint authreq;
-        string authname;
-        int ping, aireinit;
-        string clientmap;
-        int mapcrc;
-        bool warned, gameclip;
-
-        clientinfo() { reset(); }
-        ~clientinfo() { events.deletecontentsp(); }
-
-        void addevent(gameevent *e)
-        {
-            if(state.state==CS_SPECTATOR || events.length()>100) delete e;
-            else events.add(e);
-        }
-
-        void mapchange()
-        {
-            mapvote[0] = 0;
-            state.reset();
-            events.deletecontentsp();
-            timesync = false;
-            lastevent = 0;
-            clientmap[0] = '\0';
-            mapcrc = 0;
-            warned = false;
-            gameclip = false;
-        }
-
-        void reassign()
-        {
-            state.reassign();
-            events.deletecontentsp();
-            timesync = false;
-            lastevent = 0;
-        }
-
-        void reset()
-        {
-            name[0] = team[0] = 0;
-            playermodel = -1;
-            privilege = PRIV_NONE;
-            connected = local = false;
-            authreq = 0;
-            position.setsizenodelete(0);
-            messages.setsizenodelete(0);
-            ping = 0;
-            aireinit = 0;
-            mapchange();
-        }
-
-        int geteventmillis(int servmillis, int clientmillis)
-        {
-            if(!timesync || (events.empty() && state.waitexpired(servmillis)))
-            {
-                timesync = true;
-                gameoffset = servmillis - clientmillis;
-                return servmillis;
-            }
-            else return gameoffset + clientmillis;
-        }
-    };
-
-    struct worldstate
-    {
-        int uses;
-        vector<uchar> positions, messages;
-    };
-
-    struct ban
-    {
-        int time;
-        uint ip;
-    };
-
-    namespace aiman
-    {
-        extern void removeai(clientinfo *ci);
-        extern void clearai();
-        extern void checkai();
-        extern void reqadd(clientinfo *ci, int skill);
-        extern void reqdel(clientinfo *ci);
-        extern void setbotlimit(clientinfo *ci, int limit);
-        extern void setbotbalance(clientinfo *ci, bool balance);
-        extern void changemap();
-        extern void addclient(clientinfo *ci);
-        extern void changeteam(clientinfo *ci);
-    }
-
-    #define MM_MODE 0xF
-    #define MM_AUTOAPPROVE 0x1000
-    #define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE)
-    #define MM_PUBSERV ((1<<MM_OPEN) | (1<<MM_VETO))
-
-    bool notgotitems = true;        // true when map has changed and waiting for clients to send item
-    int gamemode = 0;
-    int gamemillis = 0, gamelimit = 0;
-    bool gamepaused = false;
-
-    string smapname = "";
-    int interm = 0, minremain = 0;
-    bool mapreload = false;
-    enet_uint32 lastsend = 0;
-    int mastermode = MM_OPEN, mastermask = MM_PRIVSERV;
-    int currentmaster = -1;
-    bool masterupdate = false;
-    stream *mapdata = NULL;
-
-    vector<uint> allowedips;
-    vector<ban> bannedips;
-    vector<clientinfo *> connects, clients, bots;
-    vector<worldstate *> worldstates;
-    bool reliablemessages = false;
-
-    struct demofile
-    {
-        string info;
-        uchar *data;
-        int len;
-    };
-
-    #define MAXDEMOS 5
-    vector<demofile> demos;
-
-    bool demonextmatch = false;
-    stream *demotmp = NULL, *demorecord = NULL, *demoplayback = NULL;
-    int nextplayback = 0, demomillis = 0;
-
-    struct servmode
-    {
-        virtual ~servmode() {}
-
-        virtual void entergame(clientinfo *ci) {}
-        virtual void leavegame(clientinfo *ci, bool disconnecting = false) {}
-
-        virtual void moved(clientinfo *ci, const vec &oldpos, bool oldclip, const vec &newpos, bool newclip) {}
-        virtual bool canspawn(clientinfo *ci, bool connecting = false) { return true; }
-        virtual void spawned(clientinfo *ci) {}
-        virtual int fragvalue(clientinfo *victim, clientinfo *actor)
-        {
-            if(victim==actor || isteam(victim->team, actor->team)) return -1;
-            return 1;
-        }
-        virtual void died(clientinfo *victim, clientinfo *actor) {}
-        virtual bool canchangeteam(clientinfo *ci, const char *oldteam, const char *newteam) { return true; }
-        virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {}
-        virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {}
-        virtual void update() {}
-        virtual void reset(bool empty) {}
-        virtual void intermission() {}
-        virtual bool hidefrags() { return false; }
-        virtual int getteamscore(const char *team) { return 0; }
-        virtual void getteamscores(vector<teamscore> &scores) {}
-        virtual bool extinfoteam(const char *team, ucharbuf &p) { return false; }
-    };
-
-    #define SERVMODE 1
-    #include "capture.h"
-    #include "ctf.h"
-
-    captureservmode capturemode;
-    ctfservmode ctfmode;
-    servmode *smode = NULL;
-
-    SVAR(serverdesc, "");
-    SVAR(serverpass, "");
-    SVAR(adminpass, "");
-    VARF(publicserver, 0, 0, 1, { mastermask = publicserver ? MM_PUBSERV : MM_PRIVSERV; });
-    SVAR(servermotd, "");
-
-    void *newclientinfo() { return new clientinfo; }
-    void deleteclientinfo(void *ci) { delete (clientinfo *)ci; }
-
-    clientinfo *getinfo(int n)
-    {
-        if(n < MAXCLIENTS) return (clientinfo *)getclientinfo(n);
-        n -= MAXCLIENTS;
-        return bots.inrange(n) ? bots[n] : NULL;
-    }
-
-    vector<server_entity> sents;
-    vector<savedscore> scores;
-
-    int msgsizelookup(int msg)
-    {
-        static int sizetable[NUMSV] = { -1 };
-        if(sizetable[0] < 0)
-        {
-            memset(sizetable, -1, sizeof(sizetable));
-            for(const int *p = msgsizes; *p >= 0; p += 2) sizetable[p[0]] = p[1];
-        }
-        return msg >= 0 && msg < NUMSV ? sizetable[msg] : -1;
-    }
-
-    const char *modename(int n, const char *unknown)
-    {
-        if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name;
-        return unknown;
-    }
-
-    const char *mastermodename(int n, const char *unknown)
-    {
-        return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodenames)/sizeof(mastermodenames[0])) ? mastermodenames[n-MM_START] : unknown;
-    }
-
-    const char *privname(int type)
-    {
-        switch(type)
-        {
-            case PRIV_ADMIN: return "admin";
-            case PRIV_MASTER: return "master";
-            default: return "unknown";
-        }
-    }
-
-    void sendservmsg(const char *s) { sendf(-1, 1, "ris", SV_SERVMSG, s); }
-
-    void resetitems()
-    {
-        sents.setsize(0);
-        //cps.reset();
-    }
-
-    bool serveroption(const char *arg)
-    {
-        if(arg[0]=='-') switch(arg[1])
-        {
-            case 'n': setsvar("serverdesc", &arg[2]); return true;
-            case 'y': setsvar("serverpass", &arg[2]); return true;
-            case 'p': setsvar("adminpass", &arg[2]); return true;
-            case 'o': setvar("publicserver", atoi(&arg[2])); return true;
-            case 'g': setvar("serverbotlimit", atoi(&arg[2])); return true;
-        }
-        return false;
-    }
-
-    void serverinit()
-    {
-        smapname[0] = '\0';
-        resetitems();
-    }
-
-    int numclients(int exclude = -1, bool nospec = true, bool noai = true)
-    {
-        int n = 0;
-        loopv(clients) if(i!=exclude && (!nospec || clients[i]->state.state!=CS_SPECTATOR) && (!noai || clients[i]->state.aitype == AI_NONE)) n++;
-        return n;
-    }
-
-    bool duplicatename(clientinfo *ci, char *name)
-    {
-        if(!name) name = ci->name;
-        loopv(clients) if(clients[i]!=ci && !strcmp(name, clients[i]->name)) return true;
-        return false;
-    }
-
-    const char *colorname(clientinfo *ci, char *name = NULL)
-    {
-        if(!name) name = ci->name;
-        if(name[0] && !duplicatename(ci, name) && ci->state.aitype == AI_NONE) return name;
-        static string cname[3];
-        static int cidx = 0;
-        cidx = (cidx+1)%3;
-        formatstring(cname[cidx])(ci->state.aitype == AI_NONE ? "%s \fs\f5(%d)\fr" : "%s \fs\f5[%d]\fr", name, ci->clientnum);
-        return cname[cidx];
-    }
-
-    bool canspawnitem(int type) { return !m_noitems && (type>=I_SHELLS && type<=I_QUAD && (!m_noammo || type<I_SHELLS || type>I_CARTRIDGES)); }
-
-    int spawntime(int type)
-    {
-        if(m_classicsp) return INT_MAX;
-        int np = numclients(-1, true, false);
-        np = np<3 ? 4 : (np>4 ? 2 : 3);         // spawn times are dependent on number of players
-        int sec = 0;
-        switch(type)
-        {
-            case I_SHELLS:
-            case I_BULLETS:
-            case I_ROCKETS:
-            case I_ROUNDS:
-            case I_GRENADES:
-            case I_CARTRIDGES: sec = np*4; break;
-            case I_HEALTH: sec = np*5; break;
-            case I_GREENARMOUR:
-            case I_YELLOWARMOUR: sec = 20; break;
-            case I_BOOST:
-            case I_QUAD: sec = 40+rnd(40); break;
-        }
-        return sec*1000;
-    }
-
-    bool pickup(int i, int sender)         // server side item pickup, acknowledge first client that gets it
-    {
-        if(minremain<=0 || !sents.inrange(i) || !sents[i].spawned) return false;
-        clientinfo *ci = getinfo(sender);
-        if(!ci || (!ci->local && !ci->state.canpickup(sents[i].type))) return false;
-        sents[i].spawned = false;
-        sents[i].spawntime = spawntime(sents[i].type);
-        sendf(-1, 1, "ri3", SV_ITEMACC, i, sender);
-        ci->state.pickup(sents[i].type);
-        return true;
-    }
-
-    clientinfo *choosebestclient(float &bestrank)
-    {
-        clientinfo *best = NULL;
-        bestrank = -1;
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(ci->state.timeplayed<0) continue;
-            float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1;
-            if(!best || rank > bestrank) { best = ci; bestrank = rank; }
-        }
-        return best;
-    }
-
-    void autoteam()
-    {
-        static const char *teamnames[2] = {"good", "evil"};
-        vector<clientinfo *> team[2];
-        float teamrank[2] = {0, 0};
-        for(int round = 0, remaining = clients.length(); remaining>=0; round++)
-        {
-            int first = round&1, second = (round+1)&1, selected = 0;
-            while(teamrank[first] <= teamrank[second])
-            {
-                float rank;
-                clientinfo *ci = choosebestclient(rank);
-                if(!ci) break;
-                if(smode && smode->hidefrags()) rank = 1;
-                else if(selected && rank<=0) break;
-                ci->state.timeplayed = -1;
-                team[first].add(ci);
-                if(rank>0) teamrank[first] += rank;
-                selected++;
-                if(rank<=0) break;
-            }
-            if(!selected) break;
-            remaining -= selected;
-        }
-        loopi(sizeof(team)/sizeof(team[0]))
-        {
-            loopvj(team[i])
-            {
-                clientinfo *ci = team[i][j];
-                if(!strcmp(ci->team, teamnames[i])) continue;
-                copystring(ci->team, teamnames[i], MAXTEAMLEN+1);
-                sendf(-1, 1, "riis", SV_SETTEAM, ci->clientnum, teamnames[i]);
-            }
-        }
-    }
-
-    struct teamrank
-    {
-        const char *name;
-        float rank;
-        int clients;
-
-        teamrank(const char *name) : name(name), rank(0), clients(0) {}
-    };
-
-    const char *chooseworstteam(const char *suggest = NULL, clientinfo *exclude = NULL)
-    {
-        teamrank teamranks[2] = { teamrank("good"), teamrank("evil") };
-        const int numteams = sizeof(teamranks)/sizeof(teamranks[0]);
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(ci==exclude || ci->state.aitype!=AI_NONE || ci->state.state==CS_SPECTATOR || !ci->team[0]) continue;
-            ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
-            ci->state.lasttimeplayed = lastmillis;
-
-            loopj(numteams) if(!strcmp(ci->team, teamranks[j].name))
-            {
-                teamrank &ts = teamranks[j];
-                ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
-                ts.clients++;
-                break;
-            }
-        }
-        teamrank *worst = &teamranks[numteams-1];
-        loopi(numteams-1)
-        {
-            teamrank &ts = teamranks[i];
-            if(smode && smode->hidefrags())
-            {
-                if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
-            }
-            else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
-        }
-        return worst->name;
-    }
-
-    void writedemo(int chan, void *data, int len)
-    {
-        if(!demorecord) return;
-        int stamp[3] = { gamemillis, chan, len };
-        lilswap(stamp, 3);
-        demorecord->write(stamp, sizeof(stamp));
-        demorecord->write(data, len);
-    }
-
-    void recordpacket(int chan, void *data, int len)
-    {
-        writedemo(chan, data, len);
-    }
-
-    void enddemorecord()
-    {
-        if(!demorecord) return;
-
-        DELETEP(demorecord);
-
-        if(!demotmp) return;
-
-        int len = demotmp->size();
-        if(demos.length()>=MAXDEMOS)
-        {
-            delete[] demos[0].data;
-            demos.remove(0);
-        }
-        demofile &d = demos.add();
-        time_t t = time(NULL);
-        char *timestr = ctime(&t), *trim = timestr + strlen(timestr);
-        while(trim>timestr && isspace(*--trim)) *trim = '\0';
-        formatstring(d.info)("%s: %s, %s, %.2f%s", timestr, modename(gamemode), smapname, len > 1024*1024 ? len/(1024*1024.f) : len/1024.0f, len > 1024*1024 ? "MB" : "kB");
-        defformatstring(msg)("demo \"%s\" recorded", d.info);
-        sendservmsg(msg);
-        d.data = new uchar[len];
-        d.len = len;
-        demotmp->seek(0, SEEK_SET);
-        demotmp->read(d.data, len);
-        DELETEP(demotmp);
-    }
-
-    int welcomepacket(packetbuf &p, clientinfo *ci);
-    void sendwelcome(clientinfo *ci);
-
-    void setupdemorecord()
-    {
-        if(!m_mp(gamemode) || m_edit) return;
-
-        demotmp = opentempfile("demorecord", "w+b");
-        if(!demotmp) return;
-
-        stream *f = opengzfile(NULL, "wb", demotmp);
-        if(!f) { DELETEP(demotmp); return; }
-
-        sendservmsg("recording demo");
-
-        demorecord = f;
-
-        demoheader hdr;
-        memcpy(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic));
-        hdr.version = DEMO_VERSION;
-        hdr.protocol = PROTOCOL_VERSION;
-        lilswap(&hdr.version, 2);
-        demorecord->write(&hdr, sizeof(demoheader));
-
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
-        welcomepacket(p, NULL);
-        writedemo(1, p.buf, p.len);
-    }
-
-    void listdemos(int cn)
-    {
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
-        putint(p, SV_SENDDEMOLIST);
-        putint(p, demos.length());
-        loopv(demos) sendstring(demos[i].info, p);
-        sendpacket(cn, 1, p.finalize());
-    }
-
-    void cleardemos(int n)
-    {
-        if(!n)
-        {
-            loopv(demos) delete[] demos[i].data;
-            demos.setsize(0);
-            sendservmsg("cleared all demos");
-        }
-        else if(demos.inrange(n-1))
-        {
-            delete[] demos[n-1].data;
-            demos.remove(n-1);
-            defformatstring(msg)("cleared demo %d", n);
-            sendservmsg(msg);
-        }
-    }
-
-    void senddemo(int cn, int num)
-    {
-        if(!num) num = demos.length();
-        if(!demos.inrange(num-1)) return;
-        demofile &d = demos[num-1];
-        sendf(cn, 2, "rim", SV_SENDDEMO, d.len, d.data);
-    }
-
-    void enddemoplayback()
-    {
-        if(!demoplayback) return;
-        DELETEP(demoplayback);
-
-        loopv(clients) sendf(clients[i]->clientnum, 1, "ri3", SV_DEMOPLAYBACK, 0, clients[i]->clientnum);
-
-        sendservmsg("demo playback finished");
-
-        loopv(clients) sendwelcome(clients[i]);
-    }
-
-    void setupdemoplayback()
-    {
-        if(demoplayback) return;
-        demoheader hdr;
-        string msg;
-        msg[0] = '\0';
-        defformatstring(file)("%s.dmo", smapname);
-        demoplayback = opengzfile(file, "rb");
-        if(!demoplayback) formatstring(msg)("could not read demo \"%s\"", file);
-        else if(demoplayback->read(&hdr, sizeof(demoheader))!=sizeof(demoheader) || memcmp(hdr.magic, DEMO_MAGIC, sizeof(hdr.magic)))
-            formatstring(msg)("\"%s\" is not a demo file", file);
-        else
-        {
-            lilswap(&hdr.version, 2);
-            if(hdr.version!=DEMO_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.version<DEMO_VERSION ? "older" : "newer");
-            else if(hdr.protocol!=PROTOCOL_VERSION) formatstring(msg)("demo \"%s\" requires an %s version of Cube 2: Sauerbraten", file, hdr.protocol<PROTOCOL_VERSION ? "older" : "newer");
-        }
-        if(msg[0])
-        {
-            DELETEP(demoplayback);
-            sendservmsg(msg);
-            return;
-        }
-
-        formatstring(msg)("playing demo \"%s\"", file);
-        sendservmsg(msg);
-
-        demomillis = 0;
-        sendf(-1, 1, "ri3", SV_DEMOPLAYBACK, 1, -1);
-
-        if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
-        {
-            enddemoplayback();
-            return;
-        }
-        lilswap(&nextplayback, 1);
-    }
-
-    void readdemo()
-    {
-        if(!demoplayback || gamepaused) return;
-        demomillis += curtime;
-        while(demomillis>=nextplayback)
-        {
-            int chan, len;
-            if(demoplayback->read(&chan, sizeof(chan))!=sizeof(chan) ||
-               demoplayback->read(&len, sizeof(len))!=sizeof(len))
-            {
-                enddemoplayback();
-                return;
-            }
-            lilswap(&chan, 1);
-            lilswap(&len, 1);
-            ENetPacket *packet = enet_packet_create(NULL, len, 0);
-            if(!packet || demoplayback->read(packet->data, len)!=len)
-            {
-                if(packet) enet_packet_destroy(packet);
-                enddemoplayback();
-                return;
-            }
-            sendpacket(-1, chan, packet);
-            if(!packet->referenceCount) enet_packet_destroy(packet);
-            if(demoplayback->read(&nextplayback, sizeof(nextplayback))!=sizeof(nextplayback))
-            {
-                enddemoplayback();
-                return;
-            }
-            lilswap(&nextplayback, 1);
-        }
-    }
-
-    void stopdemo()
-    {
-        if(m_demo) enddemoplayback();
-        else enddemorecord();
-    }
-
-    void pausegame(bool val)
-    {
-        if(gamepaused==val) return;
-        gamepaused = val;
-        sendf(-1, 1, "rii", SV_PAUSEGAME, gamepaused ? 1 : 0);
-    }
-
-    void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen)
-    {
-        char buf[2*sizeof(string)];
-        formatstring(buf)("%d %d ", cn, sessionid);
-        copystring(&buf[strlen(buf)], pwd);
-        if(!hashstring(buf, result, maxlen)) *result = '\0';
-    }
-
-    bool checkpassword(clientinfo *ci, const char *wanted, const char *given)
-    {
-        string hash;
-        hashpassword(ci->clientnum, ci->sessionid, wanted, hash, sizeof(hash));
-        return !strcmp(hash, given);
-    }
-
-    void revokemaster(clientinfo *ci)
-    {
-        ci->privilege = PRIV_NONE;
-        if(ci->state.state==CS_SPECTATOR && !ci->local) aiman::removeai(ci);
-    }
-
-    void setmaster(clientinfo *ci, bool val, const char *pass = "", const char *authname = NULL)
-    {
-        if(authname && !val) return;
-        const char *name = "";
-        if(val)
-        {
-            bool haspass = adminpass[0] && checkpassword(ci, adminpass, pass);
-            if(ci->privilege)
-            {
-                if(!adminpass[0] || haspass==(ci->privilege==PRIV_ADMIN)) return;
-            }
-            else if(ci->state.state==CS_SPECTATOR && !haspass && !authname && !ci->local) return;
-            loopv(clients) if(ci!=clients[i] && clients[i]->privilege)
-            {
-                if(haspass) clients[i]->privilege = PRIV_NONE;
-                else if((authname || ci->local) && clients[i]->privilege<=PRIV_MASTER) continue;
-                else return;
-            }
-            if(haspass) ci->privilege = PRIV_ADMIN;
-            else if(!authname && !(mastermask&MM_AUTOAPPROVE) && !ci->privilege && !ci->local)
-            {
-                sendf(ci->clientnum, 1, "ris", SV_SERVMSG, "This server requires you to use the \"/auth\" command to gain master.");
-                return;
-            }
-            else
-            {
-                if(authname)
-                {
-                    loopv(clients) if(ci!=clients[i] && clients[i]->privilege<=PRIV_MASTER) revokemaster(clients[i]);
-                }
-                ci->privilege = PRIV_MASTER;
-            }
-            name = privname(ci->privilege);
-        }
-        else
-        {
-            if(!ci->privilege) return;
-            name = privname(ci->privilege);
-            revokemaster(ci);
-        }
-        mastermode = MM_OPEN;
-        allowedips.setsize(0);
-        string msg;
-        if(val && authname) formatstring(msg)("%s claimed %s as '\fs\f5%s\fr'", colorname(ci), name, authname);
-        else formatstring(msg)("%s %s %s", colorname(ci), val ? "claimed" : "relinquished", name);
-        sendservmsg(msg);
-        currentmaster = val ? ci->clientnum : -1;
-        masterupdate = true;
-        if(gamepaused)
-        {
-            int admins = 0;
-            loopv(clients) if(clients[i]->privilege >= PRIV_ADMIN || clients[i]->local) admins++;
-            if(!admins) pausegame(false);
-        }
-    }
-
-    savedscore &findscore(clientinfo *ci, bool insert)
-    {
-        uint ip = getclientip(ci->clientnum);
-        if(!ip && !ci->local) return *(savedscore *)0;
-        if(!insert)
-        {
-            loopv(clients)
-            {
-                clientinfo *oi = clients[i];
-                if(oi->clientnum != ci->clientnum && getclientip(oi->clientnum) == ip && !strcmp(oi->name, ci->name))
-                {
-                    oi->state.timeplayed += lastmillis - oi->state.lasttimeplayed;
-                    oi->state.lasttimeplayed = lastmillis;
-                    static savedscore curscore;
-                    curscore.save(oi->state);
-                    return curscore;
-                }
-            }
-        }
-        loopv(scores)
-        {
-            savedscore &sc = scores[i];
-            if(sc.ip == ip && !strcmp(sc.name, ci->name)) return sc;
-        }
-        if(!insert) return *(savedscore *)0;
-        savedscore &sc = scores.add();
-        sc.ip = ip;
-        copystring(sc.name, ci->name);
-        return sc;
-    }
-
-    void savescore(clientinfo *ci)
-    {
-        savedscore &sc = findscore(ci, true);
-        if(&sc) sc.save(ci->state);
-    }
-
-    int checktype(int type, clientinfo *ci)
-    {
-        if(ci && ci->local) return type;
-        // only allow edit messages in coop-edit mode
-        if(type>=SV_EDITENT && type<=SV_EDITVAR && !m_edit) return -1;
-        // server only messages
-        static int servtypes[] = { SV_SERVINFO, SV_INITCLIENT, SV_WELCOME, SV_MAPRELOAD, SV_SERVMSG, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_CURRENTMASTER, SV_PONG, SV_RESUME, SV_BASESCORE, SV_BASEINFO, SV_BASEREGEN, SV_ANNOUNCE, SV_SENDDEMOLIST, SV_SENDDEMO, SV_DEMOPLAYBACK, SV_SENDMAP, SV_DROPFLAG, SV_SCOREFLAG, SV_RETURNFLAG, SV_RESETFLAG, SV_INVISFLAG, SV_CLIENT, SV_AUTHCHAL, SV_INITAI };
-        if(ci) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1;
-        return type;
-    }
-
-    void cleanworldstate(ENetPacket *packet)
-    {
-        loopv(worldstates)
-        {
-            worldstate *ws = worldstates[i];
-            if(ws->positions.inbuf(packet->data) || ws->messages.inbuf(packet->data)) ws->uses--;
-            else continue;
-            if(!ws->uses)
-            {
-                delete ws;
-                worldstates.remove(i);
-            }
-            break;
-        }
-    }
-
-    void addclientstate(worldstate &ws, clientinfo &ci)
-    {
-        if(ci.position.empty()) ci.posoff = -1;
-        else
-        {
-            ci.posoff = ws.positions.length();
-            loopvj(ci.position) ws.positions.add(ci.position[j]);
-            ci.poslen = ws.positions.length() - ci.posoff;
-            ci.position.setsizenodelete(0);
-        }
-        if(ci.messages.empty()) ci.msgoff = -1;
-        else
-        {
-            ci.msgoff = ws.messages.length();
-            ucharbuf p = ws.messages.reserve(16);
-            putint(p, SV_CLIENT);
-            putint(p, ci.clientnum);
-            putuint(p, ci.messages.length());
-            ws.messages.addbuf(p);
-            loopvj(ci.messages) ws.messages.add(ci.messages[j]);
-            ci.msglen = ws.messages.length() - ci.msgoff;
-            ci.messages.setsizenodelete(0);
-        }
-    }
-
-    bool buildworldstate()
-    {
-        worldstate &ws = *new worldstate;
-        loopv(clients)
-        {
-            clientinfo &ci = *clients[i];
-            if(ci.state.aitype != AI_NONE) continue;
-            addclientstate(ws, ci);
-            loopv(ci.bots)
-            {
-                clientinfo &bi = *ci.bots[i];
-                addclientstate(ws, bi);
-                if(bi.posoff >= 0)
-                {
-                    if(ci.posoff < 0) { ci.posoff = bi.posoff; ci.poslen = bi.poslen; }
-                    else ci.poslen += bi.poslen;
-                }
-                if(bi.msgoff >= 0)
-                {
-                    if(ci.msgoff < 0) { ci.msgoff = bi.msgoff; ci.msglen = bi.msglen; }
-                    else ci.msglen += bi.msglen;
-                }
-            }
-        }
-        int psize = ws.positions.length(), msize = ws.messages.length();
-        if(psize) recordpacket(0, ws.positions.getbuf(), psize);
-        if(msize) recordpacket(1, ws.messages.getbuf(), msize);
-        loopi(psize) { uchar c = ws.positions[i]; ws.positions.add(c); }
-        loopi(msize) { uchar c = ws.messages[i]; ws.messages.add(c); }
-        ws.uses = 0;
-        if(psize || msize) loopv(clients)
-        {
-            clientinfo &ci = *clients[i];
-            if(ci.state.aitype != AI_NONE) continue;
-            ENetPacket *packet;
-            if(psize && (ci.posoff<0 || psize-ci.poslen>0))
-            {
-                packet = enet_packet_create(&ws.positions[ci.posoff<0 ? 0 : ci.posoff+ci.poslen],
-                                            ci.posoff<0 ? psize : psize-ci.poslen,
-                                            ENET_PACKET_FLAG_NO_ALLOCATE);
-                sendpacket(ci.clientnum, 0, packet);
-                if(!packet->referenceCount) enet_packet_destroy(packet);
-                else { ++ws.uses; packet->freeCallback = cleanworldstate; }
-            }
-
-            if(msize && (ci.msgoff<0 || msize-ci.msglen>0))
-            {
-                packet = enet_packet_create(&ws.messages[ci.msgoff<0 ? 0 : ci.msgoff+ci.msglen],
-                                            ci.msgoff<0 ? msize : msize-ci.msglen,
-                                            (reliablemessages ? ENET_PACKET_FLAG_RELIABLE : 0) | ENET_PACKET_FLAG_NO_ALLOCATE);
-                sendpacket(ci.clientnum, 1, packet);
-                if(!packet->referenceCount) enet_packet_destroy(packet);
-                else { ++ws.uses; packet->freeCallback = cleanworldstate; }
-            }
-        }
-        reliablemessages = false;
-        if(!ws.uses)
-        {
-            delete &ws;
-            return false;
-        }
-        else
-        {
-            worldstates.add(&ws);
-            return true;
-        }
-    }
-
-    bool sendpackets()
-    {
-        if(clients.empty() || (!hasnonlocalclients() && !demorecord)) return false;
-        enet_uint32 curtime = enet_time_get()-lastsend;
-        if(curtime<33) return false;
-        bool flush = buildworldstate();
-        lastsend += curtime - (curtime%33);
-        return flush;
-    }
-
-    template<class T>
-    void sendstate(gamestate &gs, T &p)
-    {
-        putint(p, gs.lifesequence);
-        putint(p, gs.health);
-        putint(p, gs.maxhealth);
-        putint(p, gs.armour);
-        putint(p, gs.armourtype);
-        putint(p, gs.gunselect);
-        loopi(GUN_PISTOL-GUN_SG+1) putint(p, gs.ammo[GUN_SG+i]);
-    }
-
-    void spawnstate(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        gs.spawnstate(gamemode);
-        gs.lifesequence = (gs.lifesequence + 1)&0x7F;
-    }
-
-    void sendspawn(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        spawnstate(ci);
-        sendf(ci->ownernum, 1, "rii7v", SV_SPAWNSTATE, ci->clientnum, gs.lifesequence,
-            gs.health, gs.maxhealth,
-            gs.armour, gs.armourtype,
-            gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG]);
-        gs.lastspawn = gamemillis;
-    }
-
-    void sendwelcome(clientinfo *ci)
-    {
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
-        int chan = welcomepacket(p, ci);
-        sendpacket(ci->clientnum, chan, p.finalize());
-    }
-
-    void putinitclient(clientinfo *ci, packetbuf &p)
-    {
-        if(ci->state.aitype != AI_NONE)
-        {
-            putint(p, SV_INITAI);
-            putint(p, ci->clientnum);
-            putint(p, ci->ownernum);
-            putint(p, ci->state.aitype);
-            putint(p, ci->state.skill);
-            putint(p, ci->playermodel);
-            sendstring(ci->name, p);
-            sendstring(ci->team, p);
-        }
-        else
-        {
-            putint(p, SV_INITCLIENT);
-            putint(p, ci->clientnum);
-            sendstring(ci->name, p);
-            sendstring(ci->team, p);
-            putint(p, ci->playermodel);
-        }
-    }
-
-    void welcomeinitclient(packetbuf &p, int exclude = -1)
-    {
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(!ci->connected || ci->clientnum == exclude) continue;
-
-            putinitclient(ci, p);
-        }
-    }
-
-    int welcomepacket(packetbuf &p, clientinfo *ci)
-    {
-        int hasmap = (m_edit && (clients.length()>1 || (ci && ci->local))) || (smapname[0] && (minremain>0 || (ci && ci->state.state==CS_SPECTATOR) || numclients(ci && ci->local ? ci->clientnum : -1)));
-        putint(p, SV_WELCOME);
-        putint(p, hasmap);
-        if(hasmap)
-        {
-            putint(p, SV_MAPCHANGE);
-            sendstring(smapname, p);
-            putint(p, gamemode);
-            putint(p, notgotitems ? 1 : 0);
-            if(!ci || (m_timed && smapname[0]))
-            {
-                putint(p, SV_TIMEUP);
-                putint(p, minremain);
-            }
-            if(!notgotitems)
-            {
-                putint(p, SV_ITEMLIST);
-                loopv(sents) if(sents[i].spawned)
-                {
-                    putint(p, i);
-                    putint(p, sents[i].type);
-                }
-                putint(p, -1);
-            }
-        }
-        if(gamepaused)
-        {
-            putint(p, SV_PAUSEGAME);
-            putint(p, 1);
-        }
-        if(ci)
-        {
-            putint(p, SV_SETTEAM);
-            putint(p, ci->clientnum);
-            sendstring(ci->team, p);
-        }
-        if(ci && (m_demo || m_mp(gamemode)) && ci->state.state!=CS_SPECTATOR)
-        {
-            if(smode && !smode->canspawn(ci, true))
-            {
-                ci->state.state = CS_DEAD;
-                putint(p, SV_FORCEDEATH);
-                putint(p, ci->clientnum);
-                sendf(-1, 1, "ri2x", SV_FORCEDEATH, ci->clientnum, ci->clientnum);
-            }
-            else
-            {
-                gamestate &gs = ci->state;
-                spawnstate(ci);
-                putint(p, SV_SPAWNSTATE);
-                putint(p, ci->clientnum);
-                sendstate(gs, p);
-                gs.lastspawn = gamemillis;
-            }
-        }
-        if(ci && ci->state.state==CS_SPECTATOR)
-        {
-            putint(p, SV_SPECTATOR);
-            putint(p, ci->clientnum);
-            putint(p, 1);
-            sendf(-1, 1, "ri3x", SV_SPECTATOR, ci->clientnum, 1, ci->clientnum);
-        }
-        if(!ci || clients.length()>1)
-        {
-            putint(p, SV_RESUME);
-            loopv(clients)
-            {
-                clientinfo *oi = clients[i];
-                if(ci && oi->clientnum==ci->clientnum) continue;
-                putint(p, oi->clientnum);
-                putint(p, oi->state.state);
-                putint(p, oi->state.frags);
-                putint(p, oi->state.quadmillis);
-                sendstate(oi->state, p);
-            }
-            putint(p, -1);
-            welcomeinitclient(p, ci ? ci->clientnum : -1);
-        }
-        if(smode) smode->initclient(ci, p, true);
-        return 1;
-    }
-
-    bool restorescore(clientinfo *ci)
-    {
-        //if(ci->local) return false;
-        savedscore &sc = findscore(ci, false);
-        if(&sc)
-        {
-            sc.restore(ci->state);
-            return true;
-        }
-        return false;
-    }
-
-    void sendresume(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        sendf(-1, 1, "ri2i9vi", SV_RESUME, ci->clientnum,
-            gs.state, gs.frags, gs.quadmillis,
-            gs.lifesequence,
-            gs.health, gs.maxhealth,
-            gs.armour, gs.armourtype,
-            gs.gunselect, GUN_PISTOL-GUN_SG+1, &gs.ammo[GUN_SG], -1);
-    }
-
-    void sendinitclient(clientinfo *ci)
-    {
-        packetbuf p(MAXTRANS, ENET_PACKET_FLAG_RELIABLE);
-        putinitclient(ci, p);
-        sendpacket(-1, 1, p.finalize(), ci->clientnum);
-    }
-
-    void changemap(const char *s, int mode)
-    {
-        stopdemo();
-        pausegame(false);
-        if(smode) smode->reset(false);
-        aiman::clearai();
-
-        mapreload = false;
-        gamemode = mode;
-        gamemillis = 0;
-        minremain = m_overtime ? 15 : 10;
-        gamelimit = minremain*60000;
-        interm = 0;
-        copystring(smapname, s);
-        resetitems();
-        notgotitems = true;
-        scores.setsize(0);
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
-        }
-
-        if(!m_mp(gamemode)) kicknonlocalclients(DISC_PRIVATE);
-
-        if(m_teammode) autoteam();
-
-        if(m_capture) smode = &capturemode;
-        else if(m_ctf) smode = &ctfmode;
-        else smode = NULL;
-        if(smode) smode->reset(false);
-
-        if(m_timed && smapname[0]) sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            ci->mapchange();
-            ci->state.lasttimeplayed = lastmillis;
-            if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
-        }
-
-        aiman::changemap();
-
-        if(m_demo)
-        {
-            if(clients.length()) setupdemoplayback();
-        }
-        else if(demonextmatch)
-        {
-            demonextmatch = false;
-            setupdemorecord();
-        }
-    }
-
-    struct votecount
-    {
-        char *map;
-        int mode, count;
-        votecount() {}
-        votecount(char *s, int n) : map(s), mode(n), count(0) {}
-    };
-
-    void checkvotes(bool force = false)
-    {
-        vector<votecount> votes;
-        int maxvotes = 0;
-        loopv(clients)
-        {
-            clientinfo *oi = clients[i];
-            if(oi->state.state==CS_SPECTATOR && !oi->privilege && !oi->local) continue;
-            if(oi->state.aitype!=AI_NONE) continue;
-            maxvotes++;
-            if(!oi->mapvote[0]) continue;
-            votecount *vc = NULL;
-            loopvj(votes) if(!strcmp(oi->mapvote, votes[j].map) && oi->modevote==votes[j].mode)
-            {
-                vc = &votes[j];
-                break;
-            }
-            if(!vc) vc = &votes.add(votecount(oi->mapvote, oi->modevote));
-            vc->count++;
-        }
-        votecount *best = NULL;
-        loopv(votes) if(!best || votes[i].count > best->count || (votes[i].count == best->count && rnd(2))) best = &votes[i];
-        if(force || (best && best->count > maxvotes/2))
-        {
-            if(demorecord) enddemorecord();
-            if(best && (best->count > (force ? 1 : maxvotes/2)))
-            {
-                sendservmsg(force ? "vote passed by default" : "vote passed by majority");
-                sendf(-1, 1, "risii", SV_MAPCHANGE, best->map, best->mode, 1);
-                changemap(best->map, best->mode);
-            }
-            else
-            {
-                mapreload = true;
-                if(clients.length()) sendf(-1, 1, "ri", SV_MAPRELOAD);
-            }
-        }
-    }
-
-    void forcemap(const char *map, int mode)
-    {
-        stopdemo();
-        if(hasnonlocalclients() && !mapreload)
-        {
-            defformatstring(msg)("local player forced %s on map %s", modename(mode), map);
-            sendservmsg(msg);
-        }
-        sendf(-1, 1, "risii", SV_MAPCHANGE, map, mode, 1);
-        changemap(map, mode);
-    }
-
-    void vote(char *map, int reqmode, int sender)
-    {
-        clientinfo *ci = getinfo(sender);
-        if(!ci || (ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || (!ci->local && !m_mp(reqmode))) return;
-        copystring(ci->mapvote, map);
-        ci->modevote = reqmode;
-        if(!ci->mapvote[0]) return;
-        if(ci->local || mapreload || (ci->privilege && mastermode>=MM_VETO))
-        {
-            if(demorecord) enddemorecord();
-            if((!ci->local || hasnonlocalclients()) && !mapreload)
-            {
-                defformatstring(msg)("%s forced %s on map %s", ci->privilege && mastermode>=MM_VETO ? privname(ci->privilege) : "local player", modename(ci->modevote), ci->mapvote);
-                sendservmsg(msg);
-            }
-            sendf(-1, 1, "risii", SV_MAPCHANGE, ci->mapvote, ci->modevote, 1);
-            changemap(ci->mapvote, ci->modevote);
-        }
-        else
-        {
-            defformatstring(msg)("%s suggests %s on map %s (select map to vote)", colorname(ci), modename(reqmode), map);
-            sendservmsg(msg);
-            checkvotes();
-        }
-    }
-
-    void checkintermission()
-    {
-        if(minremain>0)
-        {
-            minremain = gamemillis>=gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000;
-            sendf(-1, 1, "ri2", SV_TIMEUP, minremain);
-            if(!minremain && smode) smode->intermission();
-        }
-        if(!interm && minremain<=0) interm = gamemillis+10000;
-    }
-
-    void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); }
-
-    void dodamage(clientinfo *target, clientinfo *actor, int damage, int gun, const vec &hitpush = vec(0, 0, 0))
-    {
-        gamestate &ts = target->state;
-        ts.dodamage(damage);
-        actor->state.damage += damage;
-        sendf(-1, 1, "ri6", SV_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health);
-        if(target!=actor && !hitpush.iszero())
-        {
-            ivec v = vec(hitpush).rescale(DNF);
-            sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", SV_HITPUSH, target->clientnum, gun, damage, v.x, v.y, v.z);
-        }
-        if(ts.health<=0)
-        {
-            target->state.deaths++;
-            if(actor!=target && isteam(actor->team, target->team)) actor->state.teamkills++;
-            int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
-            actor->state.frags += fragvalue;
-            if(fragvalue>0)
-            {
-                int friends = 0, enemies = 0; // note: friends also includes the fragger
-                if(m_teammode) loopv(clients) if(strcmp(clients[i]->team, actor->team)) enemies++; else friends++;
-                else { friends = 1; enemies = clients.length()-1; }
-                actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
-            }
-            sendf(-1, 1, "ri4", SV_DIED, target->clientnum, actor->clientnum, actor->state.frags);
-            target->position.setsizenodelete(0);
-            if(smode) smode->died(target, actor);
-            ts.state = CS_DEAD;
-            ts.lastdeath = gamemillis;
-            // don't issue respawn yet until DEATHMILLIS has elapsed
-            // ts.respawn();
-        }
-    }
-
-    void suicide(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        if(gs.state!=CS_ALIVE) return;
-        ci->state.frags += smode ? smode->fragvalue(ci, ci) : -1;
-        ci->state.deaths++;
-        sendf(-1, 1, "ri4", SV_DIED, ci->clientnum, ci->clientnum, gs.frags);
-        ci->position.setsizenodelete(0);
-        if(smode) smode->died(ci, NULL);
-        gs.state = CS_DEAD;
-        gs.respawn();
-    }
-
-    void suicideevent::process(clientinfo *ci)
-    {
-        suicide(ci);
-    }
-
-    void explodeevent::process(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        switch(gun)
-        {
-            case GUN_RL:
-                if(!gs.rockets.remove(id)) return;
-                break;
-
-            case GUN_GL:
-                if(!gs.grenades.remove(id)) return;
-                break;
-
-            default:
-                return;
-        }
-        loopv(hits)
-        {
-            hitinfo &h = hits[i];
-            clientinfo *target = getinfo(h.target);
-            if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>RL_DAMRAD) continue;
-
-            bool dup = false;
-            loopj(i) if(hits[i].target==h.target) { dup = true; break; }
-            if(dup) continue;
-
-            int damage = guns[gun].damage;
-            if(gs.quadmillis) damage *= 4;
-            damage = int(damage*(1-h.dist/RL_DISTSCALE/RL_DAMRAD));
-            if(gun==GUN_RL && target==ci) damage /= RL_SELFDAMDIV;
-            dodamage(target, ci, damage, gun, h.dir);
-        }
-    }
-
-    void shotevent::process(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        int wait = millis - gs.lastshot;
-        if(!gs.isalive(gamemillis) ||
-           wait<gs.gunwait ||
-           gun<GUN_FIST || gun>GUN_PISTOL ||
-           gs.ammo[gun]<=0)
-            return;
-        if(gun!=GUN_FIST) gs.ammo[gun]--;
-        gs.lastshot = millis;
-        gs.gunwait = guns[gun].attackdelay;
-        sendf(-1, 1, "ri9x", SV_SHOTFX, ci->clientnum, gun,
-                int(from.x*DMF), int(from.y*DMF), int(from.z*DMF),
-                int(to.x*DMF), int(to.y*DMF), int(to.z*DMF),
-                ci->ownernum);
-        gs.shotdamage += guns[gun].damage*(gs.quadmillis ? 4 : 1)*(gun==GUN_SG ? SGRAYS : 1);
-        switch(gun)
-        {
-            case GUN_RL: gs.rockets.add(id); break;
-            case GUN_GL: gs.grenades.add(id); break;
-            default:
-            {
-                int totalrays = 0, maxrays = gun==GUN_SG ? SGRAYS : 1;
-                loopv(hits)
-                {
-                    hitinfo &h = hits[i];
-                    clientinfo *target = getinfo(h.target);
-                    if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1) continue;
-
-                    totalrays += h.rays;
-                    if(totalrays>maxrays) continue;
-                    int damage = h.rays*guns[gun].damage;
-                    if(gs.quadmillis) damage *= 4;
-                    dodamage(target, ci, damage, gun, h.dir);
-                }
-                break;
-            }
-        }
-    }
-
-    void pickupevent::process(clientinfo *ci)
-    {
-        gamestate &gs = ci->state;
-        if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
-        pickup(ent, ci->clientnum);
-    }
-
-    bool gameevent::flush(clientinfo *ci, int fmillis)
-    {
-        process(ci);
-        return true;
-    }
-
-    bool timedevent::flush(clientinfo *ci, int fmillis)
-    {
-        if(millis > fmillis) return false;
-        else if(millis >= ci->lastevent)
-        {
-            ci->lastevent = millis;
-            process(ci);
-        }
-        return true;
-    }
-
-    void clearevent(clientinfo *ci)
-    {
-        delete ci->events.remove(0);
-    }
-
-    void flushevents(clientinfo *ci, int millis)
-    {
-        while(ci->events.length())
-        {
-            gameevent *ev = ci->events[0];
-            if(ev->flush(ci, millis)) clearevent(ci);
-            else break;
-        }
-    }
-
-    void processevents()
-    {
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(curtime>0 && ci->state.quadmillis) ci->state.quadmillis = max(ci->state.quadmillis-curtime, 0);
-            flushevents(ci, gamemillis);
-        }
-    }
-
-    void cleartimedevents(clientinfo *ci)
-    {
-        int keep = 0;
-        loopv(ci->events)
-        {
-            if(ci->events[i]->keepable())
-            {
-                if(keep < i)
-                {
-                    for(int j = keep; j < i; j++) delete ci->events[j];
-                    ci->events.remove(keep, i - keep);
-                    i = keep;
-                }
-                keep = i+1;
-                continue;
-            }
-        }
-        while(ci->events.length() > keep) delete ci->events.pop();
-        ci->timesync = false;
-    }
-
-    void serverupdate()
-    {
-        if(!gamepaused) gamemillis += curtime;
-
-        if(m_demo) readdemo();
-        else if(!gamepaused && minremain>0)
-        {
-            processevents();
-            if(curtime)
-            {
-                loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
-                {
-                    int oldtime = sents[i].spawntime;
-                    sents[i].spawntime -= curtime;
-                    if(sents[i].spawntime<=0)
-                    {
-                        sents[i].spawntime = 0;
-                        sents[i].spawned = true;
-                        sendf(-1, 1, "ri2", SV_ITEMSPAWN, i);
-                    }
-                    else if(sents[i].spawntime<=10000 && oldtime>10000 && (sents[i].type==I_QUAD || sents[i].type==I_BOOST))
-                    {
-                        sendf(-1, 1, "ri2", SV_ANNOUNCE, sents[i].type);
-                    }
-                }
-            }
-            aiman::checkai();
-            if(smode) smode->update();
-        }
-
-        while(bannedips.length() && bannedips[0].time-totalmillis>4*60*60000) bannedips.remove(0);
-        loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT);
-
-        if(masterupdate)
-        {
-            clientinfo *m = currentmaster>=0 ? getinfo(currentmaster) : NULL;
-            sendf(-1, 1, "ri3", SV_CURRENTMASTER, currentmaster, m ? m->privilege : 0);
-            masterupdate = false;
-        }
-
-        if(!gamepaused && m_timed && smapname[0] && gamemillis-curtime>0 && gamemillis/60000!=(gamemillis-curtime)/60000) checkintermission();
-        if(interm && gamemillis>interm)
-        {
-            if(demorecord) enddemorecord();
-            interm = 0;
-            checkvotes(true);
-        }
-    }
-
-    struct crcinfo
-    {
-        int crc, matches;
-
-        crcinfo(int crc, int matches) : crc(crc), matches(matches) {}
-
-        static int compare(const crcinfo *x, const crcinfo *y)
-        {
-            if(x->matches > y->matches) return -1;
-            if(x->matches < y->matches) return 1;
-            return 0;
-        }
-    };
-
-    void checkmaps(int req = -1)
-    {
-        if(m_edit || !smapname[0]) return;
-        vector<crcinfo> crcs;
-        int total = 0, unsent = 0, invalid = 0;
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE) continue;
-            total++;
-            if(!ci->clientmap[0])
-            {
-                if(ci->mapcrc < 0) invalid++;
-                else if(!ci->mapcrc) unsent++;
-            }
-            else
-            {
-                crcinfo *match = NULL;
-                loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; }
-                if(!match) crcs.add(crcinfo(ci->mapcrc, 1));
-                else match->matches++;
-            }
-        }
-        if(total - unsent < min(total, 4)) return;
-        crcs.sort(crcinfo::compare);
-        string msg;
-        loopv(clients)
-        {
-            clientinfo *ci = clients[i];
-            if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue;
-            formatstring(msg)("%s has modified map \"%s\"", colorname(ci), smapname);
-            sendf(req, 1, "ris", SV_SERVMSG, msg);
-            if(req < 0) ci->warned = true;
-        }
-        if(crcs.empty() || crcs.length() < 2) return;
-        loopv(crcs)
-        {
-            crcinfo &info = crcs[i];
-            if(i || info.matches <= crcs[i+1].matches) loopvj(clients)
-            {
-                clientinfo *ci = clients[j];
-                if(ci->state.state==CS_SPECTATOR || ci->state.aitype != AI_NONE || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue;
-                formatstring(msg)("%s has modified map \"%s\"", colorname(ci), smapname);
-                sendf(req, 1, "ris", SV_SERVMSG, msg);
-                if(req < 0) ci->warned = true;
-            }
-        }
-    }
-
-    void sendservinfo(clientinfo *ci)
-    {
-        sendf(ci->clientnum, 1, "ri5", SV_SERVINFO, ci->clientnum, PROTOCOL_VERSION, ci->sessionid, serverpass[0] ? 1 : 0);
-    }
-
-    void noclients()
-    {
-        bannedips.setsize(0);
-        aiman::clearai();
-    }
-
-    void localconnect(int n)
-    {
-        clientinfo *ci = getinfo(n);
-        ci->clientnum = ci->ownernum = n;
-        ci->connectmillis = totalmillis;
-        ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
-        ci->local = true;
-
-        connects.add(ci);
-        sendservinfo(ci);
-    }
-
-    void localdisconnect(int n)
-    {
-        if(m_demo) enddemoplayback();
-        clientdisconnect(n);
-    }
-
-    int clientconnect(int n, uint ip)
-    {
-        clientinfo *ci = getinfo(n);
-        ci->clientnum = ci->ownernum = n;
-        ci->connectmillis = totalmillis;
-        ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
-
-        connects.add(ci);
-        if(!m_mp(gamemode)) return DISC_PRIVATE;
-        sendservinfo(ci);
-        return DISC_NONE;
-    }
-
-    void clientdisconnect(int n)
-    {
-        clientinfo *ci = getinfo(n);
-        if(ci->connected)
-        {
-            if(ci->privilege) setmaster(ci, false);
-            if(smode) smode->leavegame(ci, true);
-            ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
-            savescore(ci);
-            sendf(-1, 1, "ri2", SV_CDIS, n);
-            clients.removeobj(ci);
-            aiman::removeai(ci);
-            if(!numclients(-1, false, true)) noclients(); // bans clear when server empties
-        }
-        else connects.removeobj(ci);
-    }
-
-    int reserveclients() { return 3; }
-
-    int allowconnect(clientinfo *ci, const char *pwd)
-    {
-        if(ci->local) return DISC_NONE;
-        if(!m_mp(gamemode)) return DISC_PRIVATE;
-        if(serverpass[0])
-        {
-            if(!checkpassword(ci, serverpass, pwd)) return DISC_PRIVATE;
-            return DISC_NONE;
-        }
-        if(adminpass[0] && checkpassword(ci, adminpass, pwd)) return DISC_NONE;
-        if(numclients(-1, false, true)>=maxclients) return DISC_MAXCLIENTS;
-        uint ip = getclientip(ci->clientnum);
-        loopv(bannedips) if(bannedips[i].ip==ip) return DISC_IPBAN;
-        if(mastermode>=MM_PRIVATE && allowedips.find(ip)<0) return DISC_PRIVATE;
-        return DISC_NONE;
-    }
-
-    bool allowbroadcast(int n)
-    {
-        clientinfo *ci = getinfo(n);
-        return ci && ci->connected;
-    }
-
-    clientinfo *findauth(uint id)
-    {
-        loopv(clients) if(clients[i]->authreq == id) return clients[i];
-        return NULL;
-    }
-
-    void authfailed(uint id)
-    {
-        clientinfo *ci = findauth(id);
-        if(!ci) return;
-        ci->authreq = 0;
-    }
-
-    void authsucceeded(uint id)
-    {
-        clientinfo *ci = findauth(id);
-        if(!ci) return;
-        ci->authreq = 0;
-        setmaster(ci, true, "", ci->authname);
-    }
-
-    void authchallenged(uint id, const char *val)
-    {
-        clientinfo *ci = findauth(id);
-        if(!ci) return;
-        sendf(ci->clientnum, 1, "risis", SV_AUTHCHAL, "", id, val);
-    }
-
-    uint nextauthreq = 0;
-
-    void tryauth(clientinfo *ci, const char *user)
-    {
-        if(!nextauthreq) nextauthreq = 1;
-        ci->authreq = nextauthreq++;
-        filtertext(ci->authname, user, false, 100);
-        if(!requestmasterf("reqauth %u %s\n", ci->authreq, ci->authname))
-        {
-            ci->authreq = 0;
-            sendf(ci->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
-        }
-    }
-
-    void answerchallenge(clientinfo *ci, uint id, char *val)
-    {
-        if(ci->authreq != id) return;
-        for(char *s = val; *s; s++)
-        {
-            if(!isxdigit(*s)) { *s = '\0'; break; }
-        }
-        if(!requestmasterf("confauth %u %s\n", id, val))
-        {
-            ci->authreq = 0;
-            sendf(ci->clientnum, 1, "ris", SV_SERVMSG, "not connected to authentication server");
-        }
-    }
-
-    void processmasterinput(const char *cmd, int cmdlen, const char *args)
-    {
-        uint id;
-        string val;
-        if(sscanf(cmd, "failauth %u", &id) == 1)
-            authfailed(id);
-        else if(sscanf(cmd, "succauth %u", &id) == 1)
-            authsucceeded(id);
-        else if(sscanf(cmd, "chalauth %u %s", &id, val) == 2)
-            authchallenged(id, val);
-    }
-
-    void receivefile(int sender, uchar *data, int len)
-    {
-        if(!m_edit || len > 1024*1024) return;
-        clientinfo *ci = getinfo(sender);
-        if(ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) return;
-        if(mapdata) DELETEP(mapdata);
-        if(!len) return;
-        mapdata = opentempfile("mapdata", "w+b");
-        if(!mapdata) { sendf(sender, 1, "ris", SV_SERVMSG, "failed to open temporary file for map"); return; }
-        mapdata->write(data, len);
-        defformatstring(msg)("[%s uploaded map to server, \"/getmap\" to receive it]", colorname(ci));
-        sendservmsg(msg);
-    }
-
-    void parsepacket(int sender, int chan, packetbuf &p)     // has to parse exactly each byte of the packet
-    {
-        if(sender<0) return;
-        char text[MAXTRANS];
-        int type;
-        clientinfo *ci = sender>=0 ? getinfo(sender) : NULL, *cq = ci, *cm = ci;
-        if(ci && !ci->connected)
-        {
-            if(chan==0) return;
-            else if(chan!=1 || getint(p)!=SV_CONNECT) { disconnect_client(sender, DISC_TAGT); return; }
-            else
-            {
-                getstring(text, p);
-                filtertext(text, text, false, MAXNAMELEN);
-                if(!text[0]) copystring(text, "unnamed");
-                copystring(ci->name, text, MAXNAMELEN+1);
-
-                getstring(text, p);
-                int disc = allowconnect(ci, text);
-                if(disc)
-                {
-                    disconnect_client(sender, disc);
-                    return;
-                }
-
-                ci->playermodel = getint(p);
-
-                if(m_demo) enddemoplayback();
-
-                connects.removeobj(ci);
-                clients.add(ci);
-
-                ci->connected = true;
-                if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
-                if(currentmaster>=0) masterupdate = true;
-                ci->state.lasttimeplayed = lastmillis;
-
-                const char *worst = m_teammode ? chooseworstteam(text, ci) : NULL;
-                copystring(ci->team, worst ? worst : "good", MAXTEAMLEN+1);
-
-                sendwelcome(ci);
-                if(restorescore(ci)) sendresume(ci);
-                sendinitclient(ci);
-
-                aiman::addclient(ci);
-
-                if(m_demo) setupdemoplayback();
-
-                if(servermotd[0]) sendf(sender, 1, "ris", SV_SERVMSG, servermotd);
-            }
-        }
-        else if(chan==2)
-        {
-            receivefile(sender, p.buf, p.maxlen);
-            return;
-        }
-
-        if(p.packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true;
-        #define QUEUE_AI clientinfo *cm = cq;
-        #define QUEUE_MSG { if(cm && (!cm->local || demorecord || hasnonlocalclients())) while(curmsg<p.length()) cm->messages.add(p.buf[curmsg++]); }
-        #define QUEUE_BUF(size, body) { \
-            if(cm && (!cm->local || demorecord || hasnonlocalclients())) \
-            { \
-                curmsg = p.length(); \
-                ucharbuf buf = cm->messages.reserve(size); \
-                { body; } \
-                cm->messages.addbuf(buf); \
-            } \
-        }
-        #define QUEUE_INT(n) QUEUE_BUF(5, putint(buf, n))
-        #define QUEUE_UINT(n) QUEUE_BUF(4, putuint(buf, n))
-        #define QUEUE_STR(text) QUEUE_BUF(2*strlen(text)+1, sendstring(text, buf))
-        int curmsg;
-        while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), ci))
-        {
-            case SV_POS:
-            {
-                int pcn = getint(p);
-                clientinfo *cp = getinfo(pcn);
-                if(cp && pcn != sender && cp->ownernum != sender) cp = NULL;
-                vec pos;
-                loopi(3) pos[i] = getuint(p)/DMF;
-                getuint(p);
-                loopi(5) getint(p);
-                int physstate = getuint(p);
-                if(physstate&0x20) loopi(2) getint(p);
-                if(physstate&0x10) getint(p);
-                getuint(p);
-                if(cp)
-                {
-                    if((!ci->local || demorecord || hasnonlocalclients()) && (cp->state.state==CS_ALIVE || cp->state.state==CS_EDITING))
-                    {
-                        cp->position.setsizenodelete(0);
-                        while(curmsg<p.length()) cp->position.add(p.buf[curmsg++]);
-                    }
-                    if(smode && cp->state.state==CS_ALIVE) smode->moved(cp, cp->state.o, cp->gameclip, pos, (physstate&0x80)!=0);
-                    cp->state.o = pos;
-                    cp->gameclip = (physstate&0x80)!=0;
-                }
-                break;
-            }
-
-            case SV_FROMAI:
-            {
-                int qcn = getint(p);
-                if(qcn < 0) cq = ci;
-                else
-                {
-                    cq = getinfo(qcn);
-                    if(cq && qcn != sender && cq->ownernum != sender) cq = NULL;
-                }
-                break;
-            }
-
-            case SV_EDITMODE:
-            {
-                int val = getint(p);
-                if(!ci->local && !m_edit) break;
-                if(val ? ci->state.state!=CS_ALIVE && ci->state.state!=CS_DEAD : ci->state.state!=CS_EDITING) break;
-                if(smode)
-                {
-                    if(val) smode->leavegame(ci);
-                    else smode->entergame(ci);
-                }
-                if(val)
-                {
-                    ci->state.editstate = ci->state.state;
-                    ci->state.state = CS_EDITING;
-                    ci->events.setsizenodelete(0);
-                    ci->state.rockets.reset();
-                    ci->state.grenades.reset();
-                }
-                else ci->state.state = ci->state.editstate;
-                QUEUE_MSG;
-                break;
-            }
-
-            case SV_MAPCRC:
-            {
-                getstring(text, p);
-                int crc = getint(p);
-                if(!ci) break;
-                if(strcmp(text, smapname))
-                {
-                    if(ci->clientmap[0])
-                    {
-                        ci->clientmap[0] = '\0';
-                        ci->mapcrc = 0;
-                    }
-                    else if(ci->mapcrc > 0) ci->mapcrc = 0;
-                    break;
-                }
-                copystring(ci->clientmap, text);
-                ci->mapcrc = text[0] ? crc : 1;
-                checkmaps();
-                break;
-            }
-
-            case SV_CHECKMAPS:
-                checkmaps(sender);
-                break;
-
-            case SV_TRYSPAWN:
-                if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0 || (smode && !smode->canspawn(cq))) break;
-                if(!ci->clientmap[0] && !ci->mapcrc)
-                {
-                    ci->mapcrc = -1;
-                    checkmaps();
-                }
-                if(cq->state.lastdeath)
-                {
-                    flushevents(cq, cq->state.lastdeath + DEATHMILLIS);
-                    cq->state.respawn();
-                }
-                cleartimedevents(cq);
-                sendspawn(cq);
-                break;
-
-            case SV_GUNSELECT:
-            {
-                int gunselect = getint(p);
-                if(!cq || cq->state.state!=CS_ALIVE) break;
-                cq->state.gunselect = gunselect;
-                QUEUE_AI;
-                QUEUE_MSG;
-                break;
-            }
-
-            case SV_SPAWN:
-            {
-                int ls = getint(p), gunselect = getint(p);
-                if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break;
-                cq->state.lastspawn = -1;
-                cq->state.state = CS_ALIVE;
-                cq->state.gunselect = gunselect;
-                if(smode) smode->spawned(cq);
-                QUEUE_AI;
-                QUEUE_BUF(100,
-                {
-                    putint(buf, SV_SPAWN);
-                    sendstate(cq->state, buf);
-                });
-                break;
-            }
-
-            case SV_SUICIDE:
-            {
-                if(cq) cq->addevent(new suicideevent);
-                break;
-            }
-
-            case SV_SHOOT:
-            {
-                shotevent *shot = new shotevent;
-                shot->id = getint(p);
-                shot->millis = cq ? cq->geteventmillis(gamemillis, shot->id) : 0;
-                shot->gun = getint(p);
-                loopk(3) shot->from[k] = getint(p)/DMF;
-                loopk(3) shot->to[k] = getint(p)/DMF;
-                int hits = getint(p);
-                loopk(hits)
-                {
-                    if(p.overread()) break;
-                    hitinfo &hit = shot->hits.add();
-                    hit.target = getint(p);
-                    hit.lifesequence = getint(p);
-                    hit.rays = getint(p);
-                    loopk(3) hit.dir[k] = getint(p)/DNF;
-                }
-                if(cq) cq->addevent(shot);
-                else delete shot;
-                break;
-            }
-
-            case SV_EXPLODE:
-            {
-                explodeevent *exp = new explodeevent;
-                int cmillis = getint(p);
-                exp->millis = cq ? cq->geteventmillis(gamemillis, cmillis) : 0;
-                exp->gun = getint(p);
-                exp->id = getint(p);
-                int hits = getint(p);
-                loopk(hits)
-                {
-                    if(p.overread()) break;
-                    hitinfo &hit = exp->hits.add();
-                    hit.target = getint(p);
-                    hit.lifesequence = getint(p);
-                    hit.dist = getint(p)/DMF;
-                    loopk(3) hit.dir[k] = getint(p)/DNF;
-                }
-                if(cq) cq->addevent(exp);
-                else delete exp;
-                break;
-            }
-
-            case SV_ITEMPICKUP:
-            {
-                int n = getint(p);
-                if(!cq) break;
-                pickupevent *pickup = new pickupevent;
-                pickup->ent = n;
-                cq->addevent(pickup);
-                break;
-            }
-
-            case SV_TEXT:
-            {
-                QUEUE_AI;
-                QUEUE_MSG;
-                getstring(text, p);
-                filtertext(text, text);
-                QUEUE_STR(text);
-                break;
-            }
-
-            case SV_SAYTEAM:
-            {
-                getstring(text, p);
-                if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local && !ci->privilege) || !m_teammode || !cq->team[0]) break;
-                loopv(clients)
-                {
-                    clientinfo *t = clients[i];
-                    if(t==cq || t->state.state==CS_SPECTATOR || t->state.aitype != AI_NONE || strcmp(cq->team, t->team)) continue;
-                    sendf(t->clientnum, 1, "riis", SV_SAYTEAM, cq->clientnum, text);
-                }
-                break;
-            }
-
-            case SV_SWITCHNAME:
-            {
-                QUEUE_MSG;
-                getstring(text, p);
-                filtertext(ci->name, text, false, MAXNAMELEN);
-                if(!ci->name[0]) copystring(ci->name, "unnamed");
-                QUEUE_STR(ci->name);
-                break;
-            }
-
-            case SV_SWITCHMODEL:
-            {
-                ci->playermodel = getint(p);
-                QUEUE_MSG;
-                break;
-            }
-
-            case SV_SWITCHTEAM:
-            {
-                getstring(text, p);
-                filtertext(text, text, false, MAXTEAMLEN);
-                if(strcmp(ci->team, text))
-                {
-                    if(m_teammode && smode && !smode->canchangeteam(ci, ci->team, text))
-                        sendf(sender, 1, "riis", SV_SETTEAM, sender, ci->team);
-                    else
-                    {
-                        if(smode && ci->state.state==CS_ALIVE) smode->changeteam(ci, ci->team, text);
-                        copystring(ci->team, text);
-                        aiman::changeteam(ci);
-                        sendf(-1, 1, "riis", SV_SETTEAM, sender, ci->team);
-                    }
-                }
-                break;
-            }
-
-            case SV_MAPVOTE:
-            case SV_MAPCHANGE:
-            {
-                getstring(text, p);
-                filtertext(text, text);
-                int reqmode = getint(p);
-                if(type!=SV_MAPVOTE && !mapreload) break;
-                vote(text, reqmode, sender);
-                break;
-            }
-
-            case SV_ITEMLIST:
-            {
-                if((ci->state.state==CS_SPECTATOR && !ci->privilege && !ci->local) || !notgotitems || strcmp(ci->clientmap, smapname)) { while(getint(p)>=0 && !p.overread()) getint(p); break; }
-                int n;
-                while((n = getint(p))>=0 && n<MAXENTS && !p.overread())
-                {
-                    server_entity se = { NOTUSED, 0, false };
-                    while(sents.length()<=n) sents.add(se);
-                    sents[n].type = getint(p);
-                    if(canspawnitem(sents[n].type))
-                    {
-                        if(m_mp(gamemode) && (sents[n].type==I_QUAD || sents[n].type==I_BOOST)) sents[n].spawntime = spawntime(sents[n].type);
-                        else sents[n].spawned = true;
-                    }
-                }
-                notgotitems = false;
-                break;
-            }
-
-            case SV_EDITENT:
-            {
-                int i = getint(p);
-                loopk(3) getint(p);
-                int type = getint(p);
-                loopk(5) getint(p);
-                if(!ci || ci->state.state==CS_SPECTATOR) break;
-                QUEUE_MSG;
-                bool canspawn = canspawnitem(type);
-                if(i<MAXENTS && (sents.inrange(i) || canspawnitem(type)))
-                {
-                    server_entity se = { NOTUSED, 0, false };
-                    while(sents.length()<=i) sents.add(se);
-                    sents[i].type = type;
-                    if(canspawn ? !sents[i].spawned : sents[i].spawned)
-                    {
-                        sents[i].spawntime = canspawn ? 1 : 0;
-                        sents[i].spawned = false;
-                    }
-                }
-                break;
-            }
-
-            case SV_EDITVAR:
-            {
-                int type = getint(p);
-                getstring(text, p);
-                switch(type)
-                {
-                    case ID_VAR: getint(p); break;
-                    case ID_FVAR: getfloat(p); break;
-                    case ID_SVAR: getstring(text, p);
-                }
-                if(ci && ci->state.state!=CS_SPECTATOR) QUEUE_MSG;
-                break;
-            }
-
-            case SV_PING:
-                sendf(sender, 1, "i2", SV_PONG, getint(p));
-                break;
-
-            case SV_CLIENTPING:
-            {
-                int ping = getint(p);
-                if(ci)
-                {
-                    ci->ping = ping;
-                    loopv(ci->bots) ci->bots[i]->ping = ping;
-                }
-                QUEUE_MSG;
-                break;
-            }
-
-            case SV_MASTERMODE:
-            {
-                int mm = getint(p);
-                if((ci->privilege || ci->local) && mm>=MM_OPEN && mm<=MM_PRIVATE)
-                {
-                    if((ci->privilege>=PRIV_ADMIN || ci->local) || (mastermask&(1<<mm)))
-                    {
-                        mastermode = mm;
-                        allowedips.setsize(0);
-                        if(mm>=MM_PRIVATE)
-                        {
-                            loopv(clients) allowedips.add(getclientip(clients[i]->clientnum));
-                        }
-                        defformatstring(s)("mastermode is now %s (%d)", mastermodename(mastermode), mastermode);
-                        sendservmsg(s);
-                    }
-                    else
-                    {
-                        defformatstring(s)("mastermode %d is disabled on this server", mm);
-                        sendf(sender, 1, "ris", SV_SERVMSG, s);
-                    }
-                }
-                break;
-            }
-
-            case SV_CLEARBANS:
-            {
-                if(ci->privilege || ci->local)
-                {
-                    bannedips.setsize(0);
-                    sendservmsg("cleared all bans");
-                }
-                break;
-            }
-
-            case SV_KICK:
-            {
-                int victim = getint(p);
-                if((ci->privilege || ci->local) && ci->clientnum!=victim && getclientinfo(victim)) // no bots
-                {
-                    ban &b = bannedips.add();
-                    b.time = totalmillis;
-                    b.ip = getclientip(victim);
-                    allowedips.removeobj(b.ip);
-                    disconnect_client(victim, DISC_KICK);
-                }
-                break;
-            }
-
-            case SV_SPECTATOR:
-            {
-                int spectator = getint(p), val = getint(p);
-                if(!ci->privilege && !ci->local && (spectator!=sender || (ci->state.state==CS_SPECTATOR && mastermode>=MM_LOCKED))) break;
-                clientinfo *spinfo = (clientinfo *)getclientinfo(spectator); // no bots
-                if(!spinfo || (spinfo->state.state==CS_SPECTATOR ? val : !val)) break;
-
-                if(spinfo->state.state!=CS_SPECTATOR && val)
-                {
-                    if(spinfo->state.state==CS_ALIVE) suicide(spinfo);
-                    if(smode) smode->leavegame(spinfo);
-                    spinfo->state.state = CS_SPECTATOR;
-                    spinfo->state.timeplayed += lastmillis - spinfo->state.lasttimeplayed;
-                    if(!spinfo->local && !spinfo->privilege) aiman::removeai(spinfo);
-                }
-                else if(spinfo->state.state==CS_SPECTATOR && !val)
-                {
-                    spinfo->state.state = CS_DEAD;
-                    spinfo->state.respawn();
-                    spinfo->state.lasttimeplayed = lastmillis;
-                    aiman::addclient(spinfo);
-                    if(spinfo->clientmap[0] || spinfo->mapcrc) checkmaps();
-                }
-                sendf(-1, 1, "ri3", SV_SPECTATOR, spectator, val);
-                break;
-            }
-
-            case SV_SETTEAM:
-            {
-                int who = getint(p);
-                getstring(text, p);
-                filtertext(text, text, false, MAXTEAMLEN);
-                if(!ci->privilege && !ci->local) break;
-                clientinfo *wi = getinfo(who);
-                if(!wi || !strcmp(wi->team, text)) break;
-                if(!smode || smode->canchangeteam(wi, wi->team, text))
-                {
-                    if(smode && wi->state.state==CS_ALIVE)
-                        smode->changeteam(wi, wi->team, text);
-                    copystring(wi->team, text, MAXTEAMLEN+1);
-                }
-                aiman::changeteam(wi);
-                sendf(-1, 1, "riis", SV_SETTEAM, who, wi->team);
-                break;
-            }
-
-            case SV_FORCEINTERMISSION:
-                if(ci->local && !hasnonlocalclients()) startintermission();
-                break;
-
-            case SV_RECORDDEMO:
-            {
-                int val = getint(p);
-                if(ci->privilege<PRIV_ADMIN && !ci->local) break;
-                demonextmatch = val!=0;
-                defformatstring(msg)("demo recording is %s for next match", demonextmatch ? "enabled" : "disabled");
-                sendservmsg(msg);
-                break;
-            }
-
-            case SV_STOPDEMO:
-            {
-                if(ci->privilege<PRIV_ADMIN && !ci->local) break;
-                stopdemo();
-                break;
-            }
-
-            case SV_CLEARDEMOS:
-            {
-                int demo = getint(p);
-                if(ci->privilege<PRIV_ADMIN && !ci->local) break;
-                cleardemos(demo);
-                break;
-            }
-
-            case SV_LISTDEMOS:
-                if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break;
-                listdemos(sender);
-                break;
-
-            case SV_GETDEMO:
-            {
-                int n = getint(p);
-                if(!ci->privilege  && !ci->local && ci->state.state==CS_SPECTATOR) break;
-                senddemo(sender, n);
-                break;
-            }
-
-            case SV_GETMAP:
-                if(mapdata)
-                {
-                    sendf(sender, 1, "ris", SV_SERVMSG, "server sending map...");
-                    sendfile(sender, 2, mapdata, "ri", SV_SENDMAP);
-                }
-                else sendf(sender, 1, "ris", SV_SERVMSG, "no map to send");
-                break;
-
-            case SV_NEWMAP:
-            {
-                int size = getint(p);
-                if(!ci->privilege && !ci->local && ci->state.state==CS_SPECTATOR) break;
-                if(size>=0)
-                {
-                    smapname[0] = '\0';
-                    resetitems();
-                    notgotitems = false;
-                    if(smode) smode->reset(true);
-                }
-                QUEUE_MSG;
-                break;
-            }
-
-            case SV_SETMASTER:
-            {
-                int val = getint(p);
-                getstring(text, p);
-                setmaster(ci, val!=0, text);
-                // don't broadcast the master password
-                break;
-            }
-
-            case SV_ADDBOT:
-            {
-                aiman::reqadd(ci, getint(p));
-                break;
-            }
-
-            case SV_DELBOT:
-            {
-                aiman::reqdel(ci);
-                break;
-            }
-
-            case SV_BOTLIMIT:
-            {
-                int limit = getint(p);
-                if(ci) aiman::setbotlimit(ci, limit);
-                break;
-            }
-
-            case SV_BOTBALANCE:
-            {
-                int balance = getint(p);
-                if(ci) aiman::setbotbalance(ci, balance!=0);
-                break;
-            }
-
-            case SV_AUTHTRY:
-            {
-                string desc, name;
-                getstring(desc, p, sizeof(desc)); // unused for now
-                getstring(name, p, sizeof(name));
-                if(!desc[0]) tryauth(ci, name);
-                break;
-            }
-
-            case SV_AUTHANS:
-            {
-                string desc, ans;
-                getstring(desc, p, sizeof(desc)); // unused for now
-                uint id = (uint)getint(p);
-                getstring(ans, p, sizeof(ans));
-                if(!desc[0]) answerchallenge(ci, id, ans);
-                break;
-            }
-
-            case SV_PAUSEGAME:
-            {
-                int val = getint(p);
-                if(ci->privilege<PRIV_ADMIN && !ci->local) break;
-                pausegame(val > 0);
-                break;
-            }
-
-            #define PARSEMESSAGES 1
-            #include "capture.h"
-            #include "ctf.h"
-            #undef PARSEMESSAGES
-
-            default:
-            {
-                int size = server::msgsizelookup(type);
-                if(size==-1) { disconnect_client(sender, DISC_TAGT); return; }
-                if(size>0) loopi(size-1) getint(p);
-                if(ci && cq && (ci != cq || ci->state.state!=CS_SPECTATOR)) { QUEUE_AI; QUEUE_MSG; }
-                break;
-            }
-        }
-    }
-
-    int laninfoport() { return SAUERBRATEN_LANINFO_PORT; }
-    int serverinfoport(int servport) { return servport < 0 ? SAUERBRATEN_SERVINFO_PORT : servport+1; }
-    int serverport(int infoport) { return infoport < 0 ? SAUERBRATEN_SERVER_PORT : infoport-1; }
-    const char *defaultmaster() { return "sauerbraten.org"; }
-    int masterport() { return SAUERBRATEN_MASTER_PORT; }
-
-    #include "extinfo.h"
-
-    void serverinforeply(ucharbuf &req, ucharbuf &p)
-    {
-        if(!getint(req))
-        {
-            extserverinforeply(req, p);
-            return;
-        }
-
-        putint(p, numclients(-1, false, true));
-        putint(p, 5);                   // number of attrs following
-        putint(p, PROTOCOL_VERSION);    // a // generic attributes, passed back below
-        putint(p, gamemode);            // b
-        putint(p, minremain);           // c
-        putint(p, maxclients);
-        putint(p, serverpass[0] ? MM_PASSWORD : (!m_mp(gamemode) ? MM_PRIVATE : (mastermode || mastermask&MM_AUTOAPPROVE ? mastermode : MM_AUTH)));
-        sendstring(smapname, p);
-        sendstring(serverdesc, p);
-        sendserverinforeply(p);
-    }
-
-    bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np)
-    {
-        return attr.length() && attr[0]==PROTOCOL_VERSION;
-    }
-
-    #include "aiman.h"
-}
-
diff --git a/fpsgame/waypoint.cpp b/fpsgame/waypoint.cpp
deleted file mode 100644
index 9635cec..0000000
--- a/fpsgame/waypoint.cpp
+++ /dev/null
@@ -1,729 +0,0 @@
-#include "game.h"
-
-extern selinfo sel;
-
-namespace ai
-{
-    using namespace game;
-
-    vector<waypoint> waypoints;
-
-    struct wpcachenode
-    {
-        float split[2];
-        uint child[2];
-
-        int axis() const { return child[0]>>30; }
-        int childindex(int which) const { return child[which]&0x3FFFFFFF; }
-        bool isleaf(int which) const { return (child[1]&(1<<(30+which)))!=0; }
-    };
-
-    vector<wpcachenode> wpcache;
-    int wpcachedepth = -1;
-    vec wpcachemin(1e16f, 1e16f, 1e16f), wpcachemax(-1e16f, -1e16f, -1e16f);
-
-    static void buildwpcache(int *indices, int numindices, int depth = 1)
-    {
-        vec vmin(1e16f, 1e16f, 1e16f), vmax(-1e16f, -1e16f, -1e16f);
-        loopi(numindices)
-        {
-            waypoint &w = waypoints[indices[i]];
-            float radius = WAYPOINTRADIUS;
-            loopk(3)
-            {
-                vmin[k] = min(vmin[k], w.o[k]-radius);
-                vmax[k] = max(vmax[k], w.o[k]+radius);
-            }
-        }
-        if(depth==1)
-        {
-            wpcachemin = vmin;
-            wpcachemax = vmax;
-        }
-
-        int axis = 2;
-        loopk(2) if(vmax[k] - vmin[k] > vmax[axis] - vmin[axis]) axis = k;
-
-        float split = 0.5f*(vmax[axis] + vmin[axis]), splitleft = -1e16f, splitright = 1e16f;
-        int left, right;
-        for(left = 0, right = numindices; left < right;)
-        {
-            waypoint &w = waypoints[indices[left]];
-            float radius = WAYPOINTRADIUS;
-            if(max(split - (w.o[axis]-radius), 0.0f) > max((w.o[axis]+radius) - split, 0.0f))
-            {
-                ++left;
-                splitleft = max(splitleft, w.o[axis]+radius);
-            }
-            else
-            {
-                --right;
-                swap(indices[left], indices[right]);
-                splitright = min(splitright, w.o[axis]-radius);
-            }
-        }
-
-        if(!left || right==numindices)
-        {
-            left = right = numindices/2;
-            splitleft = -1e16f;
-            splitright = 1e16f;
-            loopi(numindices)
-            {
-                waypoint &w = waypoints[indices[i]];
-                float radius = WAYPOINTRADIUS;
-                if(i < left) splitleft = max(splitleft, w.o[axis]+radius);
-                else splitright = min(splitright, w.o[axis]-radius);
-            }
-        }
-
-        int node = wpcache.length();
-        wpcache.add();
-        wpcache[node].split[0] = splitleft;
-        wpcache[node].split[1] = splitright;
-
-        if(left==1) wpcache[node].child[0] = (axis<<30) | indices[0];
-        else
-        {
-            wpcache[node].child[0] = (axis<<30) | wpcache.length();
-            if(left) buildwpcache(indices, left, depth+1);
-        }
-
-        if(numindices-right==1) wpcache[node].child[1] = (1<<31) | (left==1 ? 1<<30 : 0) | indices[right];
-        else
-        {
-            wpcache[node].child[1] = (left==1 ? 1<<30 : 0) | wpcache.length();
-            if(numindices-right) buildwpcache(&indices[right], numindices-right, depth+1);
-        }
-
-        wpcachedepth = max(wpcachedepth, depth);
-    }
-
-    void clearwpcache()
-    {
-        wpcache.setsizenodelete(0);
-        wpcachedepth = -1;
-        wpcachemin = vec(1e16f, 1e16f, 1e16f);
-        wpcachemax = vec(-1e16f, -1e16f, -1e16f);
-    }
-    COMMAND(clearwpcache, "");
-
-    void buildwpcache()
-    {
-        wpcache.setsizenodelete(0);
-        vector<int> indices;
-        loopv(waypoints) indices.add(i);
-        buildwpcache(indices.getbuf(), indices.length());
-    }
-
-    struct wpcachestack
-    {
-        wpcachenode *node;
-        float tmin, tmax;
-    };
-
-    vector<wpcachenode *> wpcachestack;
-
-    int closestwaypoint(const vec &pos, float mindist, bool links)
-    {
-        if(wpcachedepth<0) buildwpcache();
-
-        wpcachestack.setsizenodelete(0);
-
-        int closest = -1;
-        wpcachenode *curnode = &wpcache[0];
-        #define CHECKCLOSEST(branch) do { \
-            int n = curnode->childindex(branch); \
-            const waypoint &w = waypoints[n]; \
-            if(!links || w.links[0]) \
-            { \
-                float dist = w.o.squaredist(pos); \
-                if(dist < mindist*mindist) { closest = n; mindist = sqrtf(dist); } \
-            } \
-        } while(0)
-        for(;;)
-        {
-            int axis = curnode->axis();
-            float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
-            if(dist1 >= mindist)
-            {
-                if(dist2 < mindist)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKCLOSEST(1);
-                }
-            }
-            else if(curnode->isleaf(0))
-            {
-                CHECKCLOSEST(0);
-                if(dist2 < mindist)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKCLOSEST(1);
-                }
-            }
-            else
-            {
-                if(dist2 < mindist)
-                {
-                    if(!curnode->isleaf(1)) wpcachestack.add(&wpcache[curnode->childindex(1)]);
-                    else CHECKCLOSEST(1);
-                }
-                curnode = &wpcache[curnode->childindex(0)];
-                continue;
-            }
-            if(wpcachestack.empty()) return closest;
-            curnode = wpcachestack.pop();
-        }
-    }
-
-    void findwaypointswithin(const vec &pos, float mindist, float maxdist, vector<int> &results)
-    {
-        float mindist2 = mindist*mindist, maxdist2 = maxdist*maxdist;
-
-        if(wpcachedepth<0) buildwpcache();
-
-        wpcachestack.setsizenodelete(0);
-
-        wpcachenode *curnode = &wpcache[0];
-        #define CHECKWITHIN(branch) do { \
-            int n = curnode->childindex(branch); \
-            const waypoint &w = waypoints[n]; \
-            float dist = w.o.squaredist(pos); \
-            if(dist > mindist2 && dist < maxdist2) results.add(n); \
-        } while(0)
-        for(;;)
-        {
-            int axis = curnode->axis();
-            float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
-            if(dist1 >= maxdist)
-            {
-                if(dist2 < maxdist)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKWITHIN(1);
-                }
-            }
-            else if(curnode->isleaf(0))
-            {
-                CHECKWITHIN(0);
-                if(dist2 < maxdist)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKWITHIN(1);
-                }
-            }
-            else
-            {
-                if(dist2 < maxdist)
-                {
-                    if(!curnode->isleaf(1)) wpcachestack.add(&wpcache[curnode->childindex(1)]);
-                    else CHECKWITHIN(1);
-                }
-                curnode = &wpcache[curnode->childindex(0)];
-                continue;
-            }
-            if(wpcachestack.empty()) return;
-            curnode = wpcachestack.pop();
-        }
-    }
-
-    void avoidset::avoidnear(void *owner, float above, const vec &pos, float limit)
-    {
-        float limit2 = limit*limit;
-
-        if(wpcachedepth<0) buildwpcache();
-
-        wpcachestack.setsizenodelete(0);
-
-        wpcachenode *curnode = &wpcache[0];
-        #define CHECKNEAR(branch) do { \
-            int n = curnode->childindex(branch); \
-            const waypoint &w = ai::waypoints[n]; \
-            if(w.o.squaredist(pos) < limit2) add(owner, above, n); \
-        } while(0)
-        for(;;)
-        {
-            int axis = curnode->axis();
-            float dist1 = pos[axis] - curnode->split[0], dist2 = curnode->split[1] - pos[axis];
-            if(dist1 >= limit)
-            {
-                if(dist2 < limit)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKNEAR(1);
-                }
-            }
-            else if(curnode->isleaf(0))
-            {
-                CHECKNEAR(0);
-                if(dist2 < limit)
-                {
-                    if(!curnode->isleaf(1)) { curnode = &wpcache[curnode->childindex(1)]; continue; }
-                    CHECKNEAR(1);
-                }
-            }
-            else
-            {
-                if(dist2 < limit)
-                {
-                    if(!curnode->isleaf(1)) wpcachestack.add(&wpcache[curnode->childindex(1)]);
-                    else CHECKNEAR(1);
-                }
-                curnode = &wpcache[curnode->childindex(0)];
-                continue;
-            }
-            if(wpcachestack.empty()) return;
-            curnode = wpcachestack.pop();
-        }
-    }
-
-    int avoidset::remap(fpsent *d, int n, vec &pos)
-    {
-        if(!obstacles.empty())
-        {
-            int cur = 0;
-            loopv(obstacles)
-            {
-                obstacle &ob = obstacles[i];
-                int next = cur + ob.numwaypoints;
-                if(ob.owner != d)
-                {
-                    for(; cur < next; cur++) if(waypoints[cur] == n)
-                    {
-                        if(ob.above < 0) return -1;
-                        vec above(pos.x, pos.y, ob.above);
-                        if(above.z-d->o.z >= ai::JUMPMAX)
-                            return -1; // too much scotty
-                        int node = closestwaypoint(above, ai::NEARDIST, true);
-                        if(ai::waypoints.inrange(node) && node != n)
-                        { // try to reroute above their head?
-                            if(!find(node, d))
-                            {
-                                pos = ai::waypoints[node].o;
-                                return node;
-                            }
-                            else return -1;
-                        }
-                        else
-                        {
-                            vec old = d->o;
-                            d->o = vec(above).add(vec(0, 0, d->eyeheight));
-                            bool col = collide(d, vec(0, 0, 1));
-                            d->o = old;
-                            if(col)
-                            {
-                                pos = above;
-                                return n;
-                            }
-                            else return -1;
-                        }
-                    }
-                }
-                cur = next;
-            }
-        }
-        return n;
-    }
-
-    bool route(fpsent *d, int node, int goal, vector<int> &route, const avoidset &obstacles, bool check)
-    {
-        if(!waypoints.inrange(node) || !waypoints.inrange(goal) || goal == node || !waypoints[node].links[0])
-            return false;
-
-        static ushort routeid = 1;
-        static vector<waypoint *> queue;
-
-        if(!routeid)
-        {
-            loopv(waypoints) waypoints[i].route = 0;
-            routeid = 1;
-        }
-
-		if(check)
-		{
-            vec pos = d->o;
-            pos.z -= d->eyeheight;
-			loopavoid(obstacles, d,
-			{
-				if(waypoints.inrange(wp) && wp != node && wp != goal && waypoints[node].find(wp) < 0 && waypoints[goal].find(wp) < 0)
-				{
-					waypoints[wp].route = routeid;
-					waypoints[wp].curscore = -1.f;
-					waypoints[wp].estscore = 0.f;
-				}
-			});
-		}
-
-        waypoints[node].route = routeid;
-        waypoints[node].curscore = waypoints[node].estscore = 0;
-        waypoints[node].prev = 0;
-        queue.setsizenodelete(0);
-        queue.add(&waypoints[node]);
-        route.setsizenodelete(0);
-
-        int lowest = -1;
-        while(!queue.empty())
-        {
-            int q = queue.length()-1;
-            loopi(queue.length()-1) if(queue[i]->score() < queue[q]->score()) q = i;
-            waypoint &m = *queue.removeunordered(q);
-            int prevscore = m.curscore;
-            m.curscore = -1;
-            loopi(MAXWAYPOINTLINKS)
-            {
-                int link = m.links[i];
-                if(!link) break;
-                if(waypoints.inrange(link) && (link == node || link == goal || waypoints[link].links[0]))
-                {
-                    waypoint &n = waypoints[link];
-                    float curscore = prevscore + n.o.dist(m.o);
-                    if(n.route == routeid && curscore >= n.curscore) continue;
-                    n.curscore = short(curscore);
-                    n.prev = ushort(&m - &waypoints[0]);
-                    if(n.route != routeid)
-                    {
-                        n.estscore = short(n.o.dist(waypoints[goal].o));
-                        if(n.estscore <= WAYPOINTRADIUS*4 && (lowest < 0 || n.estscore <= waypoints[lowest].estscore))
-                            lowest = link;
-                        n.route = routeid;
-                        if(link == goal) goto foundgoal;
-                        queue.add(&n);
-                    }
-                }
-            }
-        }
-        foundgoal:
-
-        routeid++;
-
-        if(lowest >= 0) // otherwise nothing got there
-        {
-            for(waypoint *m = &waypoints[lowest]; m > &waypoints[0]; m = &waypoints[m->prev])
-                route.add(m - &waypoints[0]); // just keep it stored backward
-        }
-
-        return !route.empty();
-    }
-
-    VAR(dropwaypoints, 0, 0, 1);
-
-    int addwaypoint(const vec &o)
-    {
-        if(waypoints.length() > MAXWAYPOINTS) return -1;
-        int n = waypoints.length();
-        waypoints.add(o);
-        clearwpcache();
-        return n;
-    }
-
-    void linkwaypoint(waypoint &a, int n)
-    {
-        loopi(MAXWAYPOINTLINKS)
-        {
-            if(a.links[i] == n) return;
-            if(!a.links[i]) { a.links[i] = n; return; }
-        }
-        a.links[rnd(MAXWAYPOINTLINKS)] = n;
-    }
-
-    bool checkdropping()
-    {
-        if(waypoints.empty() && !dropwaypoints)
-        {
-			loopvrev(players) if(players[i]->aitype != AI_NONE) return true;
-			return false;
-        }
-        return true;
-    }
-
-    void inferwaypoints(fpsent *d, const vec &o, const vec &v, float mindist)
-    {
-    	if((m_timed || dropwaypoints) && !d->ai && checkdropping())
-    	{
-			if(waypoints.empty()) seedwaypoints();
-			int from = closestwaypoint(o, mindist, false), to = closestwaypoint(v, mindist, false);
-			if(!waypoints.inrange(from)) from = addwaypoint(o);
-			if(!waypoints.inrange(to)) to = addwaypoint(v);
-			if(d->lastnode != from && waypoints.inrange(d->lastnode) && waypoints.inrange(from))
-				linkwaypoint(waypoints[d->lastnode], from);
-			if(waypoints.inrange(to))
-			{
-				if(from != to && waypoints.inrange(from) && waypoints.inrange(to))
-					linkwaypoint(waypoints[from], to);
-				d->lastnode = to;
-			}
-		}
-		else d->lastnode = closestwaypoint(v, WAYPOINTRADIUS*2, false);
-    }
-
-    void trydropwaypoint(fpsent *d)
-    {
-        vec v(d->feetpos());
-        if(d->state != CS_ALIVE) { d->lastnode = -1; return; }
-        bool shoulddrop = (m_botmode || dropwaypoints) && !d->ai;
-        float dist = shoulddrop ? WAYPOINTRADIUS : NEARDIST;
-        int curnode = closestwaypoint(v, dist, false);
-        if(!waypoints.inrange(curnode) && shoulddrop)
-        {
-			if(waypoints.empty()) seedwaypoints();
-        	curnode = addwaypoint(v);
-        }
-        if(waypoints.inrange(curnode))
-        {
-            if(shoulddrop && d->lastnode != curnode && waypoints.inrange(d->lastnode))
-            {
-                linkwaypoint(waypoints[d->lastnode], curnode);
-                if(!d->timeinair) linkwaypoint(waypoints[curnode], d->lastnode);
-            }
-            d->lastnode = curnode;
-        }
-        else d->lastnode = closestwaypoint(v, FARDIST, false);
-    }
-
-    void trydropwaypoints()
-    {
-    	if(checkdropping())
-    	{
-			loopv(players) ai::trydropwaypoint(players[i]);
-    	}
-    }
-
-    string loadedwaypoints = "";
-
-    void clearwaypoints(bool full)
-    {
-        waypoints.setsizenodelete(0);
-        clearwpcache();
-        if(full)
-        {
-            loadedwaypoints[0] = '\0';
-            dropwaypoints = 0;
-        }
-    }
-    ICOMMAND(clearwaypoints, "", (), clearwaypoints());
-
-    void seedwaypoints()
-    {
-        if(waypoints.empty()) addwaypoint(vec(0, 0, 0));
-        loopv(entities::ents)
-        {
-            extentity &e = *entities::ents[i];
-            switch(e.type)
-            {
-                case PLAYERSTART: case TELEPORT: case JUMPPAD: case FLAG: case BASE:
-                    addwaypoint(e.o);
-                    break;
-                default:
-                    if(e.type >= I_SHELLS && e.type <= I_QUAD) addwaypoint(e.o);
-                    break;
-            }
-        }
-    }
-
-    bool unlinkwaypoint(waypoint &w, int link)
-    {
-        int found = -1, highest = MAXWAYPOINTLINKS-1;
-        loopi(MAXWAYPOINTLINKS)
-        {
-            if(w.links[i] == link) { found = -1; }
-            if(!w.links[i]) { highest = i-1; break; }
-        }
-        if(found < 0) return false;
-        w.links[found] = w.links[highest];
-        w.links[highest] = 0;
-        return true;
-    }
-
-    bool relinkwaypoint(waypoint &w, int olink, int nlink)
-    {
-        loopi(MAXWAYPOINTLINKS)
-        {
-            if(!w.links[i]) break;
-            if(w.links[i] == olink) { w.links[i] = nlink; return true; }
-        }
-        return false;
-    }
-
-    FVAR(waypointmergescale, 1e-3f, 0.75f, 1000);
-    VAR(waypointmergepasses, 0, 4, 10);
-
-    void remapwaypoints()
-    {
-        vector<ushort> remap;
-        int total = 0;
-        loopv(waypoints) remap.add(waypoints[i].links[1] == 0xFFFF ? 0 : total++);
-        total = 0;
-        loopvj(waypoints)
-        {
-            if(waypoints[j].links[1] == 0xFFFF) continue;
-            waypoint &w = waypoints[total];
-            if(j != total) w = waypoints[j];
-            loopi(MAXWAYPOINTLINKS)
-            {
-                int link = w.links[i];
-                if(!link) break;
-                w.links[i] = remap[link];
-            }
-            total++;
-        }
-        waypoints.setsizenodelete(total);
-    }
-
-    void mergewaypoints()
-    {
-        if(!waypointmergepasses) return;
-        float mindist = WAYPOINTRADIUS*waypointmergescale;
-        mindist *= mindist;
-        int totalmerges = 0, totalpasses = 0, merges = 0;
-        do
-        {
-            merges = 0;
-            for(int j = 1; j < waypoints.length(); j++)
-            {
-                waypoint &w = waypoints[j];
-                if(w.links[1] == 0xFFFF) continue;
-                vec o = w.o;
-                int curmerges = 0;
-                for(int k = 1; k < waypoints.length(); k++) if(k != j)
-                {
-                    waypoint &v = waypoints[k];
-                    if(v.links[1] != 0xFFFF && w.o.squaredist(v.o) <= mindist)
-                    {
-                        loopi(MAXWAYPOINTLINKS)
-                        {
-                            int link = v.links[i];
-                            if(!link) break;
-                            if(link != j) linkwaypoint(w, link);
-                        }
-                        for(int i = 1; i < waypoints.length(); i++) if(i != k)
-                        {
-                            waypoint &u = waypoints[i];
-                            if(u.links[1] != 0xFFFF) relinkwaypoint(u, k, j);
-                        }
-                        v.links[0] = 0;
-                        v.links[1] = 0xFFFF;
-                        o.add(v.o);
-                        curmerges++;
-                    }
-                }
-                if(curmerges)
-                {
-                    w.o = o.div(curmerges + 1);
-                    merges += curmerges;
-                }
-            }
-            totalpasses++;
-            totalmerges += merges;
-        } while(merges && totalpasses < waypointmergepasses);
-        if(totalmerges)
-        {
-            remapwaypoints();
-            clearwpcache();
-            conoutf("merged %d waypoints in %d passes", totalmerges, totalpasses);
-        }
-
-    }
-
-    bool getwaypointfile(const char *mname, char *wptname)
-    {
-        if(!mname || !*mname) mname = getclientmap();
-        if(!*mname) return false;
-
-        string pakname, mapname, cfgname;
-        getmapfilenames(mname, NULL, pakname, mapname, cfgname);
-        formatstring(wptname)("packages/%s.wpt", mapname);
-        path(wptname);
-        return true;
-    }
-
-    void loadwaypoints(bool force, const char *mname)
-    {
-        string wptname;
-        if(!getwaypointfile(mname, wptname)) return;
-
-        if(!force && !strcmp(loadedwaypoints, wptname)) return;
-        copystring(loadedwaypoints, wptname);
-
-        stream *f = opengzfile(wptname, "rb");
-        if(!f) return;
-        char magic[4];
-        if(f->read(magic, 4) < 4 || memcmp(magic, "OWPT", 4)) { delete f; return; }
-
-        waypoints.setsizenodelete(0);
-        waypoints.add(vec(0, 0, 0));
-        ushort numwp = f->getlil<ushort>();
-        loopi(numwp)
-        {
-            if(f->end()) break;
-            vec o;
-            o.x = f->getlil<float>();
-            o.y = f->getlil<float>();
-            o.z = f->getlil<float>();
-            waypoint &w = waypoints.add(o);
-            int numlinks = clamp(f->getchar(), 0, MAXWAYPOINTLINKS);
-            loopi(numlinks) w.links[i] = f->getlil<ushort>();
-        }
-
-        delete f;
-        conoutf("loaded %d waypoints from %s", numwp, wptname);
-
-        clearwpcache();
-    }
-    ICOMMAND(loadwaypoints, "s", (char *mname), loadwaypoints(true, mname));
-
-    void savewaypoints(bool force, const char *mname)
-    {
-        if((!dropwaypoints && !force) || waypoints.empty()) return;
-
-        string wptname;
-        if(!getwaypointfile(mname, wptname)) return;
-
-        mergewaypoints();
-
-        stream *f = opengzfile(wptname, "wb");
-        if(!f) return;
-        f->write("OWPT", 4);
-        f->putlil<ushort>(waypoints.length()-1);
-        for(int i = 1; i < waypoints.length(); i++)
-        {
-            waypoint &w = waypoints[i];
-            f->putlil<float>(w.o.x);
-            f->putlil<float>(w.o.y);
-            f->putlil<float>(w.o.z);
-            int numlinks = 0;
-            loopj(MAXWAYPOINTLINKS) { if(!w.links[j]) break; numlinks++; }
-            f->putchar(numlinks);
-            loopj(numlinks) f->putlil<ushort>(w.links[j]);
-        }
-
-        delete f;
-        conoutf("saved %d waypoints to %s", waypoints.length()-1, wptname);
-    }
-
-    ICOMMAND(savewaypoints, "s", (char *mname), savewaypoints(true, mname));
-
-    void delselwaypoints()
-    {
-        if(noedit(true)) return;
-        vec o = sel.o.tovec().sub(0.1f), s = sel.s.tovec().mul(sel.grid).add(o).add(0.1f);
-        int cleared = 0;
-        loopv(waypoints)
-        {
-            waypoint &w = waypoints[i];
-            if(w.o.x >= o.x && w.o.x <= s.x && w.o.y >= o.y && w.o.y <= s.y && w.o.z >= o.z && w.o.z <= s.z)
-            {
-                w.links[0] = 0;
-                w.links[1] = 0xFFFF;
-                cleared++;
-            }
-        }
-        if(cleared)
-        {
-            remapwaypoints();
-            clearwpcache();
-        }
-    }
-    COMMAND(delselwaypoints, "");
-}
-
diff --git a/fpsgame/weapon.cpp b/fpsgame/weapon.h
similarity index 52%
rename from fpsgame/weapon.cpp
rename to fpsgame/weapon.h
index 94f1dd2..00d2a40 100644
--- a/fpsgame/weapon.cpp
+++ b/fpsgame/weapon.h
@@ -1,32 +1,51 @@
-// weapon.cpp: all shooting and effects code, projectile management
-#include "game.h"
+// weapon.h: all shooting and effects code, projectile management
 
-namespace game
+struct weaponstate
 {
+    fpsclient &cl;
+    fpsent *player1;
+
     static const int MONSTERDAMAGEFACTOR = 4;
     static const int OFFSETMILLIS = 500;
     vec sg[SGRAYS];
 
-    struct hitmsg
+    IVARP(maxdebris, 10, 25, 1000);
+    IVARP(maxbarreldebris, 5, 10, 1000);
+
+    weaponstate(fpsclient &_cl) : cl(_cl), player1(_cl.player1)
     {
-        int target, lifesequence, info;
-        ivec dir;
-    };
-    vector<hitmsg> hits;
+        CCOMMAND(getweapon, "", (weaponstate *self), intret(self->player1->gunselect));
+        CCOMMAND(nextweapon, "ii", (weaponstate *self, int *dir, int *force), self->nextweapon(*dir, *force!=0));
+        CCOMMAND(setweapon, "ii", (weaponstate *self, int *gun, int *force), self->setweapon(*gun, *force!=0));
+        CCOMMAND(cycleweapon, "sssss", (weaponstate *self, char *w1, char *w2, char *w3, char *w4, char *w5),
+        {
+            int numguns = 0;
+            int guns[5];
+            if(w1[0]) guns[numguns++] = atoi(w1);
+            if(w2[0]) guns[numguns++] = atoi(w2);
+            if(w3[0]) guns[numguns++] = atoi(w3);
+            if(w4[0]) guns[numguns++] = atoi(w4);
+            if(w5[0]) guns[numguns++] = atoi(w5);
 
-    VARP(maxdebris, 10, 25, 1000);
-    VARP(maxbarreldebris, 5, 10, 1000);
+            self->cycleweapon(numguns, guns);
+        });
+        CCOMMAND(weapon, "sss", (weaponstate *self, char *w1, char *w2, char *w3),
+        {
+            self->weaponswitch(w1[0] ? atoi(w1) : -1,
+                               w2[0] ? atoi(w2) : -1,
+                               w3[0] ? atoi(w3) : -1);
 
-    ICOMMAND(getweapon, "", (), intret(player1->gunselect));
+        });
+    }
 
-    void gunselect(int gun, fpsent *d)
+    void gunselect(int gun)
     {
-        if(gun!=d->gunselect)
+        if(gun!=player1->gunselect)
         {
-            addmsg(SV_GUNSELECT, "rci", d, gun);
-            playsound(S_WEAPLOAD, &d->o);
+            cl.cc.addmsg(SV_GUNSELECT, "ri", gun);
+            playsound(S_WEAPLOAD, &player1->o);
         }
-        d->gunselect = gun;
+        player1->gunselect = gun;
     }
 
     void nextweapon(int dir, bool force = false)
@@ -39,27 +58,16 @@ namespace game
             gun = (gun + dir)%NUMGUNS;
             if(force || player1->ammo[gun]) break;
         }
-        if(gun != player1->gunselect) gunselect(gun, player1);
+        if(gun != player1->gunselect) gunselect(gun);
         else playsound(S_NOAMMO);
     }
-    ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0));
 
-    int getweapon(const char *name)
+    void setweapon(int gun, bool force = false)
     {
-        const char *abbrevs[] = { "FI", "SG", "CG", "RL", "RI", "GL", "PI" };
-        if(isdigit(name[0])) return atoi(name);
-        else loopi(sizeof(abbrevs)/sizeof(abbrevs[0])) if(!strcasecmp(abbrevs[i], name)) return i;
-        return -1;
-    }
-
-    void setweapon(const char *name, bool force = false)
-    {
-        int gun = getweapon(name);
         if(player1->state!=CS_ALIVE || gun<GUN_FIST || gun>GUN_PISTOL) return;
-        if(force || player1->ammo[gun]) gunselect(gun, player1);
+        if(force || player1->ammo[gun]) gunselect(gun);
         else playsound(S_NOAMMO);
     }
-    ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0));
 
     void cycleweapon(int numguns, int *guns, bool force = false)
     {
@@ -71,63 +79,36 @@ namespace game
             int gun = guns[(i+offset)%numguns];
             if(gun>=0 && gun<NUMGUNS && (force || player1->ammo[gun]))
             {
-                gunselect(gun, player1);
+                gunselect(gun);
                 return;
             }
         }
         playsound(S_NOAMMO);
     }
-    ICOMMAND(cycleweapon, "sssssss", (char *w1, char *w2, char *w3, char *w4, char *w5, char *w6, char *w7),
-    {
-         int numguns = 0;
-         int guns[7];
-         if(w1[0]) guns[numguns++] = getweapon(w1);
-         if(w2[0]) guns[numguns++] = getweapon(w2);
-         if(w3[0]) guns[numguns++] = getweapon(w3);
-         if(w4[0]) guns[numguns++] = getweapon(w4);
-         if(w5[0]) guns[numguns++] = getweapon(w5);
-         if(w6[0]) guns[numguns++] = getweapon(w6);
-         if(w7[0]) guns[numguns++] = getweapon(w7);
-         cycleweapon(numguns, guns);
-    });
-
-    void weaponswitch(fpsent *d)
-    {
-        if(d->state!=CS_ALIVE) return;
-        int s = d->gunselect;
-        if     (s!=GUN_CG     && d->ammo[GUN_CG])     s = GUN_CG;
-        else if(s!=GUN_RL     && d->ammo[GUN_RL])     s = GUN_RL;
-        else if(s!=GUN_SG     && d->ammo[GUN_SG])     s = GUN_SG;
-        else if(s!=GUN_RIFLE  && d->ammo[GUN_RIFLE])  s = GUN_RIFLE;
-        else if(s!=GUN_GL     && d->ammo[GUN_GL])     s = GUN_GL;
-        else if(s!=GUN_PISTOL && d->ammo[GUN_PISTOL]) s = GUN_PISTOL;
-        else                                          s = GUN_FIST;
-
-        gunselect(s, d);
-    }
 
-    #define TRYWEAPON(w) do { \
-        if(w[0]) \
-        { \
-            int gun = getweapon(w); \
-            if(gun >= GUN_FIST && gun <= GUN_PISTOL && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; } \
-        } \
-        else { weaponswitch(player1); return; } \
-    } while(0)
-    ICOMMAND(weapon, "sssssss", (char *w1, char *w2, char *w3, char *w4, char *w5, char *w6, char *w7),
+    void weaponswitch(int a = -1, int b = -1, int c = -1)
     {
-        if(player1->state!=CS_ALIVE) return;
-        TRYWEAPON(w1);
-        TRYWEAPON(w2);
-        TRYWEAPON(w3);
-        TRYWEAPON(w4);
-        TRYWEAPON(w5);
-        TRYWEAPON(w6);
-        TRYWEAPON(w7);
-        playsound(S_NOAMMO);
-    });
+        if(player1->state!=CS_ALIVE || a<-1 || b<-1 || c<-1 || a>=NUMGUNS || b>=NUMGUNS || c>=NUMGUNS) return;
+        int *ammo = player1->ammo;
+        int s = player1->gunselect;
 
-    void offsetray(const vec &from, const vec &to, int spread, vec &dest)
+        if     (a>=0 && s!=a  && ammo[a])          s = a;
+        else if(b>=0 && s!=b  && ammo[b])          s = b;
+        else if(c>=0 && s!=c  && ammo[c])          s = c;
+        else if(s!=GUN_CG     && ammo[GUN_CG])     s = GUN_CG;
+        else if(s!=GUN_RL     && ammo[GUN_RL])     s = GUN_RL;
+        else if(s!=GUN_SG     && ammo[GUN_SG])     s = GUN_SG;
+        else if(s!=GUN_RIFLE  && ammo[GUN_RIFLE])  s = GUN_RIFLE;
+        else if(s!=GUN_GL     && ammo[GUN_GL])     s = GUN_GL;
+        else if(s!=GUN_PISTOL && ammo[GUN_PISTOL]) s = GUN_PISTOL;
+        else                                       s = GUN_FIST;
+
+        gunselect(s);
+    }
+    
+    int reloadtime(int gun) { return guns[gun].attackdelay; }
+    
+    void offsetray(vec &from, vec &to, int spread, vec &dest)
     {
         float f = to.dist(from)*spread/1000;
         for(;;)
@@ -147,7 +128,7 @@ namespace game
         }
     }
 
-    void createrays(const vec &from, const vec &to)             // create random spread of rays for the shotgun
+    void createrays(vec &from, vec &to)             // create random spread of rays for the shotgun
     {
         loopi(SGRAYS) offsetray(from, to, SGSPREAD, sg[i]);
     }
@@ -169,15 +150,13 @@ namespace game
 
     vector<bouncent *> bouncers;
 
-    vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d);
-
     void newbouncer(const vec &from, const vec &to, bool local, fpsent *owner, int type, int lifetime, int speed, entitylight *light = NULL)
     {
         bouncent &bnc = *(bouncers.add(new bouncent));
         bnc.reset();
         bnc.type = ENT_BOUNCE;
         bnc.o = from;
-        bnc.radius = bnc.xradius = bnc.yradius = type==BNC_DEBRIS ? 0.5f : 1.5f;
+        bnc.radius = type==BNC_DEBRIS ? 0.5f : 1.5f;
         bnc.eyeheight = bnc.radius;
         bnc.aboveeye = bnc.radius;
         bnc.lifetime = lifetime;
@@ -185,7 +164,7 @@ namespace game
         bnc.local = local;
         bnc.owner = owner;
         bnc.bouncetype = type;
-        bnc.id = lastmillis;
+        bnc.id = cl.lastmillis;
         if(light) bnc.light = *light;
 
         vec dir(to);
@@ -195,28 +174,23 @@ namespace game
 
         avoidcollision(&bnc, dir, owner, 0.1f);
 
-        if(type==BNC_GRENADE)
-        {
-            bnc.offset = hudgunorigin(GUN_GL, from, to, owner);
-            if(owner==hudplayer() && !isthirdperson()) bnc.offset.sub(owner->o).rescale(16).add(owner->o);
-        }
-        else bnc.offset = from; 
+        bnc.offset = hudgunorigin(type==BNC_GRENADE ? GUN_GL : -1, from, to, owner);
         bnc.offset.sub(bnc.o);
         bnc.offsetmillis = OFFSETMILLIS;
 
         bnc.resetinterp();
     }
 
-    void updatebouncers(int time)
+    void bounceupdate(int time)
     {
         loopv(bouncers)
         {
-            bouncent &bnc = *bouncers[i];
+            bouncent &bnc = *(bouncers[i]);
             if(bnc.bouncetype==BNC_GRENADE && bnc.vel.magnitude() > 50.0f) 
             {
                 vec pos(bnc.o);
                 pos.add(vec(bnc.offset).mul(bnc.offsetmillis/float(OFFSETMILLIS)));
-                regular_particle_splash(PART_SMOKE, 1, 150, pos, 0x404040, 2.4f, 50, -20);
+                regular_particle_splash(5, 1, 150, pos);
             }
             vec old(bnc.o);
             bool stopped = false;
@@ -236,11 +210,12 @@ namespace game
                 if(bnc.bouncetype==BNC_GRENADE)
                 {
                     int qdam = guns[GUN_GL].damage*(bnc.owner->quadmillis ? 4 : 1);
+                    if(bnc.owner->type==ENT_AI) qdam /= MONSTERDAMAGEFACTOR;
                     hits.setsizenodelete(0);
                     explode(bnc.local, bnc.owner, bnc.o, NULL, qdam, GUN_GL);                    
                     adddecal(DECAL_SCORCH, bnc.o, vec(0, 0, 1), RL_DAMRAD/2);
                     if(bnc.local)
-                        addmsg(SV_EXPLODE, "rci3iv", bnc.owner, lastmillis-maptime, GUN_GL, bnc.id-maptime,
+                        cl.cc.addmsg(SV_EXPLODE, "ri3iv", cl.lastmillis-cl.maptime, GUN_GL, bnc.id-cl.maptime,
                                 hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
                 }
                 delete &bnc;
@@ -259,8 +234,6 @@ namespace game
         loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); }
     }
 
-    void clearbouncers() { bouncers.deletecontentsp(); }
-
     struct projectile
     {
         vec dir, o, to, offset;
@@ -274,7 +247,7 @@ namespace game
     };
     vector<projectile> projs;
 
-    void clearprojectiles() { projs.setsize(0); }
+    void projreset() { projs.setsize(0); bouncers.deletecontentsp(); bouncers.setsize(0); }
 
     void newprojectile(const vec &from, const vec &to, float speed, bool local, fpsent *owner, int gun)
     {
@@ -289,31 +262,29 @@ namespace game
         p.owner = owner;
         p.gun = gun;
         p.offsetmillis = OFFSETMILLIS;
-        p.id = lastmillis;
+        p.id = cl.lastmillis;
     }
-
+   
     void removeprojectiles(fpsent *owner) 
     { 
-        // can't use loopv here due to strange GCC optimizer bug
-        int len = projs.length();
-        loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; }
+        loopv(projs) if(projs[i].owner==owner) projs.remove(i--);
     }
 
-    VARP(blood, 0, 1, 1);
+    IVARP(blood, 0, 1, 1);
 
-    void damageeffect(int damage, fpsent *d, bool thirdperson)
+    void damageeffect(int damage, fpsent *d, bool thirdperson = true)
     {
         vec p = d->o;
         p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight;
-        if(blood) particle_splash(PART_BLOOD, damage/10, 1000, p, 0x60FFFF, 2.96f);
+        if(blood()) particle_splash(3, damage/10, 1000, p);
         if(thirdperson)
         {
-            defformatstring(ds)("@%d", damage);
-            particle_text(d->abovehead(), ds, PART_TEXT, 2000, 0xFF4B19, 4.0f, -8);
+            s_sprintfd(ds)("@%d", damage);
+            particle_text(d->abovehead(), ds, 8);
         }
     }
     
-    void spawnbouncer(const vec &p, const vec &vel, fpsent *d, int type, entitylight *light = NULL)
+    void spawnbouncer(vec &p, vec &vel, fpsent *d, int type, entitylight *light = NULL)
     {
         vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50);
         to.normalize();
@@ -321,7 +292,7 @@ namespace game
         newbouncer(p, to, true, d, type, rnd(1000)+1000, rnd(100)+20, light);
     }    
 
-    void superdamageeffect(const vec &vel, fpsent *d)
+    void superdamageeffect(vec &vel, fpsent *d)
     {
         if(!d->superdamage) return;
         vec from = d->abovehead();
@@ -329,48 +300,54 @@ namespace game
         loopi(min(d->superdamage/25, 40)+1) spawnbouncer(from, vel, d, BNC_GIBS);
     }
 
+    struct hitmsg
+    {
+        int target, lifesequence, info;
+        ivec dir;
+    };
+    vector<hitmsg> hits;
+
     void hit(int damage, dynent *d, fpsent *at, const vec &vel, int gun, int info = 1)
     {
-        if(at==player1 && d!=at)
-        {
-            extern int hitsound;
-            if(hitsound && lasthit != lastmillis) playsound(S_HIT);
-            lasthit = lastmillis;
-        } 
+        if(at==player1 && d!=player1) cl.lasthit = cl.lastmillis;
 
         if(d->type==ENT_INANIMATE) 
         {
-            hitmovable(damage, (movable *)d, at, vel, gun);
+            movableset::movable *m = (movableset::movable *)d;
+            m->hitpush(damage, vel, at, gun);
+            m->damaged(damage, at, gun);
             return;
         }
 
         fpsent *f = (fpsent *)d;
 
-        f->lastpain = lastmillis;
+        f->lastpain = cl.lastmillis;
         if(at->type==ENT_PLAYER) at->totaldamage += damage;
         f->superdamage = 0;
 
-        if(f->type==ENT_AI || !m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, gun);
+        if(f->type==ENT_AI || !m_mp(cl.gamemode) || f==player1) f->hitpush(damage, vel, at, gun);
 
-        if(f->type==ENT_AI) hitmonster(damage, (monster *)f, at, vel, gun);
-        else if(!m_mp(gamemode)) damaged(damage, f, at);
+        if(f->type==ENT_AI) ((monsterset::monster *)f)->monsterpain(damage, at); 
+        else if(!m_mp(cl.gamemode)) cl.damaged(damage, f, at);
         else 
         { 
             hitmsg &h = hits.add();
             h.target = f->clientnum;
             h.lifesequence = f->lifesequence;
             h.info = info;
-            h.dir = f==at ? ivec(0, 0, 0) : ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
-            if(at==player1)
+            damageeffect(damage, f);
+            if(f==player1)
             {
-                damageeffect(damage, f);
-                if(f==player1)
-                {
-                    damageblend(damage);
-                    damagecompass(damage, at ? at->o : f->o);
-                    playsound(S_PAIN6);
-                }
-                else playsound(S_PAIN1+rnd(5), &f->o); 
+                h.dir = ivec(0, 0, 0);
+                f->damageroll(damage);
+                damageblend(damage);
+                damagecompass(damage, at ? at->o : f->o);
+                playsound(S_PAIN6);
+            }
+            else 
+            {
+                h.dir = ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF));
+                playsound(S_PAIN1+rnd(5), &f->o); 
             }
         }
     }
@@ -383,21 +360,11 @@ namespace game
         hit(damage, d, at, v, gun, rays);
     }
 
-    float projdist(dynent *o, vec &dir, const vec &v)
-    {
-        vec middle = o->o;
-        middle.z += (o->aboveeye-o->eyeheight)/2;
-        float dist = middle.dist(v, dir);
-        dir.div(dist);
-        if(dist<0) dist = 0;
-        return dist;
-    }
-
-    void radialeffect(dynent *o, const vec &v, int qdam, fpsent *at, int gun)
+    void radialeffect(dynent *o, vec &v, int qdam, fpsent *at, int gun)
     {
         if(o->state!=CS_ALIVE) return;
         vec dir;
-        float dist = projdist(o, dir, v);
+        float dist = rocketdist(o, dir, v);
         if(dist<RL_DAMRAD) 
         {
             int damage = (int)(qdam*(1-dist/RL_DISTSCALE/RL_DAMRAD));
@@ -406,15 +373,25 @@ namespace game
         }
     }
 
-    void explode(bool local, fpsent *owner, const vec &v, dynent *safe, int damage, int gun)
+    float rocketdist(dynent *o, vec &dir, const vec &v)
     {
-        particle_splash(PART_SPARK, 200, 300, v, 0xB49B4B, 0.24f);
+        vec middle = o->o;
+        middle.z += (o->aboveeye-o->eyeheight)/2;
+        float dist = middle.dist(v, dir);
+        dir.div(dist);
+        if(dist<0) dist = 0;
+        return dist;
+    }
+
+    void explode(bool local, fpsent *owner, vec &v, dynent *notthis, int qdam, int gun)
+    {
+        particle_splash(0, 200, 300, v);
         playsound(S_RLHIT, &v);
-        particle_fireball(v, RL_DAMRAD, PART_EXPLOSION, -1, gun==GUN_RL ? 0xFF8080 : 0xA0C080, 4.0f);
+        particle_fireball(v, RL_DAMRAD, gun==GUN_RL || gun==GUN_BARREL ? 22 : 23);
         if(gun==GUN_RL) adddynlight(v, 1.15f*RL_DAMRAD, vec(2, 1.5f, 1), 900, 100, 0, RL_DAMRAD/2, vec(1, 0.75f, 0.5f));
         else if(gun==GUN_GL) adddynlight(v, 1.15f*RL_DAMRAD, vec(2, 1.5f, 1), 900, 100, 0, 8, vec(0.25f, 1, 1));
         else adddynlight(v, 1.15f*RL_DAMRAD, vec(2, 1.5f, 1), 900, 100);
-        int numdebris = gun==GUN_BARREL ? rnd(max(maxbarreldebris-5, 1))+5 : rnd(maxdebris-5)+5;
+        int numdebris = gun==GUN_BARREL ? rnd(max(maxbarreldebris()-5, 1))+5 : rnd(maxdebris()-5)+5;
         vec debrisvel = owner->o==v ? vec(0, 0, 0) : vec(owner->o).sub(v).normalize(), debrisorigin(v);
         if(gun==GUN_RL) debrisorigin.add(vec(debrisvel).mul(8));
         if(numdebris)
@@ -425,25 +402,25 @@ namespace game
                 spawnbouncer(debrisorigin, debrisvel, owner, gun==GUN_BARREL ? BNC_BARRELDEBRIS : BNC_DEBRIS, &light);
         }
         if(!local) return;
-        loopi(numdynents())
+        loopi(cl.numdynents())
         {
-            dynent *o = iterdynents(i);
-            if(o==safe) continue;
-            radialeffect(o, v, damage, owner, gun);
+            dynent *o = cl.iterdynents(i);
+            if(!o || o==notthis) continue;
+            radialeffect(o, v, qdam, owner, gun);
         }
     }
 
-    void projsplash(projectile &p, vec &v, dynent *safe, int damage)
+    void splash(projectile &p, vec &v, dynent *notthis, int qdam)
     {
         if(guns[p.gun].part)
         {
-            particle_splash(PART_SPARK, 100, 200, v, 0xB49B4B, 0.24f);
+            particle_splash(0, 100, 200, v);
             playsound(S_FEXPLODE, &v);
             // no push?
         }
         else
         {
-            explode(p.local, p.owner, v, safe, damage, GUN_RL);
+            explode(p.local, p.owner, v, notthis, qdam, GUN_RL);
             adddecal(DECAL_SCORCH, v, vec(p.dir).neg(), RL_DAMRAD/2);
         }
     }
@@ -452,14 +429,15 @@ namespace game
     {
         if(o->state!=CS_ALIVE) return false;
         if(!intersect(o, p.o, v)) return false;
-        projsplash(p, v, o, qdam);
+        splash(p, v, o, qdam);
         vec dir;
-        projdist(o, dir, v);
+        if(guns[p.gun].part) { dir = v; dir.normalize(); }
+        else rocketdist(o, dir, v);
         hit(qdam, o, p.owner, dir, p.gun, 0);
         return true;
     }
 
-    void updateprojectiles(int time)
+    void moveprojectiles(int time)
     {
         loopv(projs)
         {
@@ -477,10 +455,10 @@ namespace game
             hits.setsizenodelete(0);
             if(p.local)
             {
-                loopj(numdynents())
+                loopj(cl.numdynents())
                 {
-                    dynent *o = iterdynents(j);
-                    if(p.owner==o || o->o.reject(v, 10.0f)) continue;
+                    dynent *o = cl.iterdynents(j);
+                    if(!o || p.owner==o || o->o.reject(v, 10.0f)) continue;
                     if(projdamage(o, p, v, qdam)) { exploded = true; break; }
                 }
             }
@@ -492,7 +470,7 @@ namespace game
                     {
                         if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue;
                     }
-                    projsplash(p, v, NULL, qdam);
+                    splash(p, v, NULL, qdam);
                     exploded = true;
                 }
                 else 
@@ -501,21 +479,19 @@ namespace game
                     pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
                     if(guns[p.gun].part)
                     {
-                         regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 0.6f, 150, -20);
-                         int color = 0xFFFFFF;
-                         switch(guns[p.gun].part)
-                         {
-                            case PART_FIREBALL1: color = 0xFFC8C8; break;
-                         }
-                         particle_splash(guns[p.gun].part, 1, 1, pos, color, 4.8f, 150, 20);
+                         regular_particle_splash(1, 2, 300, pos);
+                         particle_splash(guns[p.gun].part, 1, 1, pos);
+                    }
+                    else
+                    {
+                        regular_particle_splash(5, 2, 300, pos);
                     }
-                    else regular_particle_splash(PART_SMOKE, 2, 300, pos, 0x404040, 2.4f, 50, -20);
                 }   
             }
             if(exploded) 
             {
                 if(p.local)
-                    addmsg(SV_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.gun, p.id-maptime,
+                    cl.cc.addmsg(SV_EXPLODE, "ri3iv", cl.lastmillis-cl.maptime, p.gun, p.id-cl.maptime,
                             hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
                 projs.remove(i--);
             }
@@ -523,50 +499,64 @@ namespace game
         }
     }
 
-    extern int chainsawhudgun;
-
-    VARP(muzzleflash, 0, 1, 1);
-    VARP(muzzlelight, 0, 1, 1);
+    vec hudgunorigin(int gun, const vec &from, const vec &to, fpsent *d)
+    {
+        vec offset(from);
+        if(d!=cl.hudplayer() || isthirdperson()) 
+        {
+            vec front, right;
+            vecfromyawpitch(d->yaw, d->pitch, 1, 0, front);
+            offset.add(front.mul(d->radius));
+            if(d->type!=ENT_AI)
+            {
+                offset.z += (d->aboveeye + d->eyeheight)*0.75f - d->eyeheight;
+                vecfromyawpitch(d->yaw, 0, 0, -1, right);
+                offset.add(right.mul(0.5f*d->radius));
+                offset.add(front);
+            }
+            return offset;
+        }
+        offset.add(vec(to).sub(from).normalize().mul(2));
+        if(cl.hudgun())
+        {
+            offset.sub(vec(camup).mul(1.0f));
+            offset.add(vec(camright).mul(0.8f));
+        }
+        return offset;
+    }
 
-    void shoteffects(int gun, const vec &from, const vec &to, fpsent *d, bool local, int prevaction)     // create visual effect from a shot
+    void shootv(int gun, vec &from, vec &to, fpsent *d, bool local)     // create visual effect from a shot
     {
-        int sound = guns[gun].sound, pspeed = 25;
+        playsound(guns[gun].sound, d==player1 ? NULL : &d->o);
+        int pspeed = 25;
         switch(gun)
         {
             case GUN_FIST:
-                if(d->type==ENT_PLAYER && chainsawhudgun) sound = S_CHAINSAW_ATTACK;
                 break;
 
             case GUN_SG:
             {
-                if(!local) createrays(from, to);
-                if(muzzleflash && d->muzzle.x >= 0)
-                    particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH3, 0xFFFFFF, 2.75f, d);
                 loopi(SGRAYS)
                 {
-                    particle_splash(PART_SPARK, 20, 250, sg[i], 0xB49B4B, 0.24f);
-                    particle_flare(hudgunorigin(gun, from, sg[i], d), sg[i], 300, PART_STREAK, 0xFFC864, 0.28f);
+                    particle_splash(0, 20, 250, sg[i]);
+                    particle_flare(hudgunorigin(gun, from, sg[i], d), sg[i], 300, 10);
                     if(!local) adddecal(DECAL_BULLET, sg[i], vec(from).sub(sg[i]).normalize(), 2.0f);
                 }
-                if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
                 break;
             }
 
             case GUN_CG:
             case GUN_PISTOL:
             {
-                particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
-                particle_flare(hudgunorigin(gun, from, to, d), to, 600, PART_STREAK, 0xFFC864, 0.28f);
-                if(muzzleflash && d->muzzle.x >= 0)
-                    particle_flare(d->muzzle, d->muzzle, gun==GUN_CG ? 100 : 200, PART_MUZZLE_FLASH1, 0xFFFFFF, gun==GUN_CG ? 2.25f : 1.25f, d);
+                particle_splash(0, 200, 250, to);
+                //particle_trail(1, 10, from, to);
+                particle_flare(hudgunorigin(gun, from, to, d), to, 600, 10);
                 if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).normalize(), 2.0f);
-                if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), gun==GUN_CG ? 30 : 15, vec(0.5f, 0.375f, 0.25f), gun==GUN_CG ? 50 : 100, gun==GUN_CG ? 50 : 100, DL_FLASH, 0, vec(0, 0, 0), d); 
+                //if(gun==GUN_CG) adddynlight(hudgunorigin(gun, d->o, to, d), 30, vec(1, 0.75f, 0.5f), 50, 0, DL_FLASH); 
                 break;
             }
 
             case GUN_RL:
-                if(muzzleflash && d->muzzle.x >= 0)
-                    particle_flare(d->muzzle, d->muzzle, 250, PART_MUZZLE_FLASH2, 0xFFFFFF, 3.0f, d);
             case GUN_FIREBALL:
             case GUN_ICEBALL:
             case GUN_SLIMEBALL:
@@ -580,81 +570,26 @@ namespace game
                 float dist = from.dist(to);
                 vec up = to;
                 up.z += dist/8;
-                if(muzzleflash && d->muzzle.x >= 0)
-                    particle_flare(d->muzzle, d->muzzle, 200, PART_MUZZLE_FLASH2, 0xFFFFFF, 1.5f, d);
-                if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 20, vec(0.5f, 0.375f, 0.25f), 100, 100, DL_FLASH, 0, vec(0, 0, 0), d);
                 newbouncer(from, up, local, d, BNC_GRENADE, 2000, 200);
                 break;
             }
 
             case GUN_RIFLE: 
-                particle_splash(PART_SPARK, 200, 250, to, 0xB49B4B, 0.24f);
-                particle_trail(PART_SMOKE, 500, hudgunorigin(gun, from, to, d), to, 0x404040, 0.6f, 20);
-                if(muzzleflash && d->muzzle.x >= 0)
-                    particle_flare(d->muzzle, d->muzzle, 150, PART_MUZZLE_FLASH3, 0xFFFFFF, 1.25f, d);
+                particle_splash(0, 200, 250, to);
+                particle_trail(21, 500, hudgunorigin(gun, from, to, d), to);
                 if(!local) adddecal(DECAL_BULLET, to, vec(from).sub(to).normalize(), 3.0f);
-                if(muzzlelight) adddynlight(hudgunorigin(gun, d->o, to, d), 25, vec(0.5f, 0.375f, 0.25f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d);
-                break;
-        }
-
-        bool looped = false;
-        if(d->attacksound >= 0 && d->attacksound != sound) d->stopattacksound();
-        if(d->idlesound >= 0) d->stopidlesound();
-        switch(sound)
-        {
-            case S_CHAINSAW_ATTACK:
-                if(d->attacksound >= 0) looped = true;
-                d->attacksound = sound;
-                d->attackchan = playsound(sound, d==hudplayer() ? NULL : &d->o, NULL, -1, 100, d->attackchan);
-                break;
-            default:
-                playsound(sound, d==hudplayer() ? NULL : &d->o);
                 break;
-        } 
-        if(d->quadmillis && lastmillis-prevaction>200 && !looped) playsound(S_ITEMPUP, d==hudplayer() ? NULL : &d->o);
-    }
-
-    void particletrack(physent *owner, vec &o, vec &d)
-    {
-        if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
-        fpsent *pl = (fpsent *)owner;
-        if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
-        float dist = o.dist(d);
-        o = pl->muzzle;
-        if(dist <= 0) d = o;
-        else
-        {
-            vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
-            float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
-            d.mul(min(newdist, dist)).add(owner->o);
         }
     }
 
-    void dynlighttrack(physent *owner, vec &o)
-    {
-        if(owner->type!=ENT_PLAYER && owner->type!=ENT_AI) return;
-        fpsent *pl = (fpsent *)owner;
-        if(pl->muzzle.x < 0 || pl->lastattackgun != pl->gunselect) return;
-        o = pl->muzzle;
-    }
-
-    bool intersect(dynent *d, const vec &from, const vec &to)   // if lineseg hits entity bounding box
-    {
-        float dist;
-        vec bottom(d->o), top(d->o);
-        bottom.z -= d->eyeheight;
-        top.z += d->aboveeye;
-        return linecylinderintersect(from, to, bottom, top, d->radius, dist);
-    }
-
-    dynent *intersectclosest(const vec &from, const vec &to, fpsent *at)
+    dynent *intersectclosest(vec &from, vec &to, fpsent *at)
     {
         dynent *best = NULL;
         float bestdist = 1e16f;
-        loopi(numdynents())
+        loopi(cl.numdynents())
         {
-            dynent *o = iterdynents(i);
-            if(o==at || o->state!=CS_ALIVE) continue;
+            dynent *o = cl.iterdynents(i);
+            if(!o || o==at || o->state!=CS_ALIVE) continue;
             if(!intersect(o, from, to)) continue;
             float dist = at->o.dist(o->o);
             if(dist<bestdist)
@@ -688,7 +623,7 @@ namespace game
                 o = NULL;
                 loop(r, SGRAYS) if(!done[r] && (cl = intersectclosest(from, sg[r], d)))
                 {
-                    if(!o || o==cl)
+                    if((!o || o==cl) /*&& (damage<cl->health+cl->armour || cl->type!=ENT_AI)*/)
                     {
                         hitrays++;
                         o = cl;
@@ -710,22 +645,22 @@ namespace game
         else if(d->gunselect!=GUN_FIST && d->gunselect!=GUN_BITE) adddecal(DECAL_BULLET, to, vec(from).sub(to).normalize(), d->gunselect==GUN_RIFLE ? 3.0f : 2.0f); 
     }
 
-    void shoot(fpsent *d, const vec &targ)
+    void shoot(fpsent *d, vec &targ)
     {
-        int prevaction = d->lastaction, attacktime = lastmillis-prevaction;
+        int attacktime = cl.lastmillis-d->lastaction;
         if(attacktime<d->gunwait) return;
         d->gunwait = 0;
-        if((d==player1 || d->ai) && !d->attacking) return;
-        d->lastaction = lastmillis;
+        if(d==player1 && !d->attacking) return;
+        d->lastaction = cl.lastmillis;
         d->lastattackgun = d->gunselect;
         if(!d->ammo[d->gunselect]) 
         { 
             if(d==player1)
             {
-                msgsound(S_NOAMMO, d); 
+                cl.playsoundc(S_NOAMMO, d); 
                 d->gunwait = 600; 
                 d->lastattackgun = -1; 
-                weaponswitch(d); 
+                weaponswitch(); 
             }
             return; 
         }
@@ -739,11 +674,12 @@ namespace game
         vec kickback(unitv);
         kickback.mul(guns[d->gunselect].kickamount*-2.5f);
         d->vel.add(kickback);
+        if(d->pitch<80.0f) d->pitch += guns[d->gunselect].kickamount*0.05f;
         float shorten = 0;
         if(guns[d->gunselect].range && dist > guns[d->gunselect].range)
             shorten = guns[d->gunselect].range;
         float barrier = raycube(d->o, unitv, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
-        if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten))
+        if(barrier < dist && (!shorten || barrier < shorten))
             shorten = barrier;
         if(shorten)
         {
@@ -755,18 +691,20 @@ namespace game
         if(d->gunselect==GUN_SG) createrays(from, to);
         else if(d->gunselect==GUN_CG) offsetray(from, to, 1, to);
             
+        if(d->quadmillis && attacktime>200) cl.playsoundc(S_ITEMPUP, d);
+
         hits.setsizenodelete(0);
 
         if(!guns[d->gunselect].projspeed) raydamage(from, to, d);
 
-        shoteffects(d->gunselect, from, to, d, true, prevaction);
+        shootv(d->gunselect, from, to, d, true);
 
-        if(d==player1 || d->ai)
+        if(d==player1)
         {
-            addmsg(SV_SHOOT, "rci2i6iv", d, lastmillis-maptime, 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),
-                   hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
+            cl.cc.addmsg(SV_SHOOT, "ri2i6iv", cl.lastmillis-cl.maptime, 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),
+                            hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
         }
 
         d->gunwait = guns[d->gunselect].attackdelay;
@@ -794,22 +732,7 @@ namespace game
         }
     }
 
-    void preloadbouncers()
-    {
-        const char *mdls[] =
-        {
-            "gibc", "gibh",
-            "projectiles/grenade", "projectiles/rocket",
-            "debris/debris01", "debris/debris02", "debris/debris03", "debris/debris04",
-            "barreldebris/debris01", "barreldebris/debris02", "barreldebris/debris03", "barreldebris/debris04"
-        };
-        loopi(sizeof(mdls)/sizeof(mdls[0]))
-        {
-            preloadmodel(mdls[i]);
-        }
-    }
-
-    void renderbouncers()
+    void renderprojectiles()
     {
         float yaw, pitch;
         loopv(bouncers)
@@ -829,19 +752,12 @@ namespace game
             const char *mdl = "projectiles/grenade";
             string debrisname;
             int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED;
-            float fade = 1;
-            if(bnc.bouncetype!=BNC_GRENADE && bnc.lifetime < 250) fade = bnc.lifetime/250.0f; 
             if(bnc.bouncetype==BNC_GIBS) { mdl = ((int)(size_t)&bnc)&0x40 ? "gibc" : "gibh"; cull |= MDL_LIGHT|MDL_DYNSHADOW; }
-            else if(bnc.bouncetype==BNC_DEBRIS) { formatstring(debrisname)("debris/debris0%d", ((((int)(size_t)&bnc)&0xC0)>>6)+1); mdl = debrisname; }
-            else if(bnc.bouncetype==BNC_BARRELDEBRIS) { formatstring(debrisname)("barreldebris/debris0%d", ((((int)(size_t)&bnc)&0xC0)>>6)+1); mdl = debrisname; }
+            else if(bnc.bouncetype==BNC_DEBRIS) { s_sprintf(debrisname)("debris/debris0%d", ((((int)(size_t)&bnc)&0xC0)>>6)+1); mdl = debrisname; }
+            else if(bnc.bouncetype==BNC_BARRELDEBRIS) { s_sprintf(debrisname)("barreldebris/debris0%d", ((((int)(size_t)&bnc)&0xC0)>>6)+1); mdl = debrisname; }
             else { cull |= MDL_LIGHT|MDL_DYNSHADOW; cull &= ~MDL_CULL_DIST; }
-            rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull, NULL, NULL, 0, 0, fade);
+            rendermodel(&bnc.light, mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, cull);
         }
-    }
-    
-    void renderprojectiles()
-    {
-        float yaw, pitch;
         loopv(projs)
         {
             projectile &p = projs[i];
@@ -860,90 +776,4 @@ namespace game
             rendermodel(&p.light, "projectiles/rocket", ANIM_MAPMODEL|ANIM_LOOP, v, yaw, pitch, MDL_CULL_VFC|MDL_CULL_OCCLUDED|MDL_LIGHT);
         }
     }  
-
-    void checkattacksound(fpsent *d, bool local)
-    {
-        int gun = -1;
-        switch(d->attacksound)
-        {
-            case S_CHAINSAW_ATTACK:
-                if(chainsawhudgun) gun = GUN_FIST;
-                break;
-            default:
-                return;
-        }
-        if(gun >= 0 && gun < NUMGUNS &&
-           d->clientnum >= 0 && d->state == CS_ALIVE &&
-           d->lastattackgun == gun && lastmillis - d->lastaction < guns[gun].attackdelay + 50)
-        {
-            d->attackchan = playsound(d->attacksound, local ? NULL : &d->o, NULL, -1, -1, d->attackchan);
-            if(d->attackchan < 0) d->attacksound = -1;
-        }
-        else d->stopattacksound();
-    }
-
-    void checkidlesound(fpsent *d, bool local)
-    {
-        int sound = -1, radius = 0;
-        if(d->clientnum >= 0 && d->state == CS_ALIVE) switch(d->gunselect)
-        {
-            case GUN_FIST:
-                if(chainsawhudgun && d->attacksound < 0) 
-                {
-                    sound = S_CHAINSAW_IDLE;
-                    radius = 50;
-                }
-                break;
-        }
-        if(d->idlesound != sound) 
-        {
-            if(d->idlesound >= 0) d->stopidlesound();
-            if(sound >= 0)
-            {
-                d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, -1, 100, d->idlechan, radius);
-                if(d->idlechan >= 0) d->idlesound = sound;
-            }
-        }
-        else if(sound >= 0)
-        {
-            d->idlechan = playsound(sound, local ? NULL : &d->o, NULL, -1, -1, d->idlechan, radius);
-            if(d->idlechan < 0) d->idlesound = -1;
-        }
-    }
-
-    void removeweapons(fpsent *d)
-    {
-        removebouncers(d);
-        removeprojectiles(d);
-    }
-
-    void updateweapons(int curtime)
-    {
-        updateprojectiles(curtime);
-        if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server
-        updatebouncers(curtime); // need to do this after the player shoots so grenades don't end up inside player's BB next frame
-        fpsent *following = followingplayer();
-        if(!following) following = player1;
-        loopv(players) 
-        {
-            fpsent *d = players[i];
-            checkattacksound(d, d==following);
-            checkidlesound(d, d==following);
-        }
-    }
-
-    void avoidweapons(ai::avoidset &obstacles, float radius)
-    {
-        loopv(projs)
-        {
-            projectile &p = projs[i];
-            obstacles.avoidnear(NULL, p.o.z + RL_DAMRAD + 1, p.o, radius + RL_DAMRAD);
-        }
-        loopv(bouncers)
-        {
-            bouncent &bnc = *bouncers[i];
-            obstacles.avoidnear(NULL, bnc.o.z + RL_DAMRAD + 1, bnc.o, radius + RL_DAMRAD);
-        }
-    }
 };
-
diff --git a/include/SDL_image.h b/include/SDL_image.h
index 7a47487..7bc332c 100644
--- a/include/SDL_image.h
+++ b/include/SDL_image.h
@@ -38,7 +38,7 @@ extern "C" {
 */
 #define SDL_IMAGE_MAJOR_VERSION	1
 #define SDL_IMAGE_MINOR_VERSION	2
-#define SDL_IMAGE_PATCHLEVEL	7
+#define SDL_IMAGE_PATCHLEVEL	6
 
 /* This macro can be used to fill a version structure with the compile-time
  * version of the SDL_image library.
diff --git a/include/SDL_mouse.h b/include/SDL_mouse.h
index 019497f..c2364d8 100644
--- a/include/SDL_mouse.h
+++ b/include/SDL_mouse.h
@@ -122,13 +122,9 @@ extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle);
 #define SDL_BUTTON_RIGHT	3
 #define SDL_BUTTON_WHEELUP	4
 #define SDL_BUTTON_WHEELDOWN	5
-#define SDL_BUTTON_X1		6
-#define SDL_BUTTON_X2		7
 #define SDL_BUTTON_LMASK	SDL_BUTTON(SDL_BUTTON_LEFT)
 #define SDL_BUTTON_MMASK	SDL_BUTTON(SDL_BUTTON_MIDDLE)
 #define SDL_BUTTON_RMASK	SDL_BUTTON(SDL_BUTTON_RIGHT)
-#define SDL_BUTTON_X1MASK	SDL_BUTTON(SDL_BUTTON_X1)
-#define SDL_BUTTON_X2MASK	SDL_BUTTON(SDL_BUTTON_X2)
 
 
 /* Ends C function definitions when using C++ */
diff --git a/include/SDL_opengl.h b/include/SDL_opengl.h
index 36c0a30..1d1bf5f 100644
--- a/include/SDL_opengl.h
+++ b/include/SDL_opengl.h
@@ -6542,7 +6542,6 @@ GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei, const GLvoid *);
 typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const GLvoid *string);
 #endif
 
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/SDL_stdinc.h b/include/SDL_stdinc.h
index e47c21d..891a9ac 100644
--- a/include/SDL_stdinc.h
+++ b/include/SDL_stdinc.h
@@ -174,8 +174,6 @@ extern DECLSPEC void SDLCALL SDL_free(void *mem);
 #  define alloca _alloca
 # elif defined(__WATCOMC__)
 #  include <malloc.h>
-# elif defined(__BORLANDC__)
-#  include <malloc.h>
 # elif defined(__DMC__)
 #  include <stdlib.h>
 # elif defined(__AIX__)
diff --git a/include/SDL_version.h b/include/SDL_version.h
index 9ff0fa8..610ffe6 100644
--- a/include/SDL_version.h
+++ b/include/SDL_version.h
@@ -37,7 +37,7 @@ extern "C" {
 */
 #define SDL_MAJOR_VERSION	1
 #define SDL_MINOR_VERSION	2
-#define SDL_PATCHLEVEL		13
+#define SDL_PATCHLEVEL		12
 
 typedef struct SDL_version {
 	Uint8 major;
diff --git a/include/begin_code.h b/include/begin_code.h
index d1ddaa6..b03787f 100644
--- a/include/begin_code.h
+++ b/include/begin_code.h
@@ -90,8 +90,8 @@
 #elif !defined(__WINS__)
 #undef DECLSPEC
 #define DECLSPEC __declspec(dllexport)
-#endif /* !EKA2 */
-#endif /* __SYMBIAN32__ */
+#endif //EKA2
+#endif //__SYMBIAN32__
 
 /* Force structure packing at 4 byte alignment.
    This is necessary if the header is included in code which has structure
diff --git a/lib/SDL.lib b/lib/SDL.lib
new file mode 100644
index 0000000..993fd2b
Binary files /dev/null and b/lib/SDL.lib differ
diff --git a/lib/SDL_image.lib b/lib/SDL_image.lib
new file mode 100644
index 0000000..50b4676
Binary files /dev/null and b/lib/SDL_image.lib differ
diff --git a/lib/SDL_mixer.lib b/lib/SDL_mixer.lib
new file mode 100644
index 0000000..0a39732
Binary files /dev/null and b/lib/SDL_mixer.lib differ
diff --git a/lib/SDLmain.lib b/lib/SDLmain.lib
new file mode 100644
index 0000000..1a88abe
Binary files /dev/null and b/lib/SDLmain.lib differ
diff --git a/lib/zdll.lib b/lib/zdll.lib
new file mode 100644
index 0000000..01f4e10
Binary files /dev/null and b/lib/zdll.lib differ
diff --git a/mingw/Makefile.mingw b/mingw/Makefile.mingw
new file mode 100644
index 0000000..2512eb2
--- /dev/null
+++ b/mingw/Makefile.mingw
@@ -0,0 +1,63 @@
+CXX=g++
+CXXOPTFLAGS=-O3 -fomit-frame-pointer
+CXXFLAGS= $(CXXOPTFLAGS) -Wall -fsigned-char -Ienet/include -Iinclude -Ishared -Iengine -Ifpsgame
+
+CLIENT_LIBS=-Lenet -Lmingw/lib -lstdc++ -lenet -lmingw32 -lSDLmain -lSDL -lSDL_image -lSDL_mixer -mwindows -lz -lopengl32 -lglu32 -lws2_32 -lwinmm
+CLIENT_OBJS= \
+	shared/tools.o \
+	shared/geom.o \
+	engine/3dgui.o \
+	engine/bih.o \
+	engine/client.o \
+	engine/command.o \
+	engine/console.o \
+	engine/cubeloader.o \
+	engine/decal.o \
+	engine/dynlight.o \
+	engine/glare.o \
+	engine/grass.o \
+	engine/lightmap.o \
+	engine/main.o \
+	engine/material.o \
+	engine/menus.o \
+	engine/normal.o \
+	engine/octa.o \
+	engine/octaedit.o \
+	engine/octarender.o \
+	engine/physics.o \
+	engine/pvs.o \
+	engine/rendergl.o \
+	engine/rendermodel.o \
+	engine/renderparticles.o \
+	engine/rendersky.o \
+	engine/rendertext.o \
+	engine/renderva.o \
+	engine/server.o \
+	engine/serverbrowser.o \
+	engine/shader.o \
+	engine/shadowmap.o \
+	engine/sound.o \
+	engine/texture.o \
+	engine/water.o \
+	engine/world.o \
+	engine/worldio.o \
+	fpsgame/fps.o \
+	rpggame/rpg.o
+
+default: all
+
+all: client
+
+enet/Makefile:
+	cd enet; ./configure
+
+libenet: enet/Makefile
+	$(MAKE) -C enet/ all
+
+clean:
+	-$(RM) $(CLIENT_OBJS)
+	$(MAKE) -C enet/ clean
+
+client: $(CLIENT_OBJS)
+	$(CXX) $(CXXFLAGS) -o ../bin/sauerbraten.exe $(CLIENT_OBJS) $(CLIENT_LIBS)
+	strip ../bin/sauerbraten.exe
diff --git a/mingw/lib/libSDL.dll.a b/mingw/lib/libSDL.dll.a
new file mode 100644
index 0000000..ba5102c
Binary files /dev/null and b/mingw/lib/libSDL.dll.a differ
diff --git a/mingw/lib/libSDL_image.dll.a b/mingw/lib/libSDL_image.dll.a
new file mode 100644
index 0000000..af27936
Binary files /dev/null and b/mingw/lib/libSDL_image.dll.a differ
diff --git a/mingw/lib/libSDL_mixer.dll.a b/mingw/lib/libSDL_mixer.dll.a
new file mode 100644
index 0000000..7d3e4b7
Binary files /dev/null and b/mingw/lib/libSDL_mixer.dll.a differ
diff --git a/mingw/lib/libSDLmain.a b/mingw/lib/libSDLmain.a
new file mode 100644
index 0000000..79abf8f
Binary files /dev/null and b/mingw/lib/libSDLmain.a differ
diff --git a/mingw/lib/libenet.a b/mingw/lib/libenet.a
new file mode 100644
index 0000000..d5cbf3a
Binary files /dev/null and b/mingw/lib/libenet.a differ
diff --git a/mingw/lib/libz.a b/mingw/lib/libz.a
new file mode 100644
index 0000000..b452fc9
Binary files /dev/null and b/mingw/lib/libz.a differ
diff --git a/mingw/make.mingw.bat b/mingw/make.mingw.bat
new file mode 100644
index 0000000..86d0c49
--- /dev/null
+++ b/mingw/make.mingw.bat
@@ -0,0 +1,3 @@
+cd src
+mingw32-make all -fMakefile.mingw
+PAUSE
diff --git a/mingw/sauerbraten.cbp b/mingw/sauerbraten.cbp
new file mode 100644
index 0000000..f511fde
--- /dev/null
+++ b/mingw/sauerbraten.cbp
@@ -0,0 +1,825 @@
+<?xml version="1.0"?>
+<!DOCTYPE CodeBlocks_project_file>
+<CodeBlocks_project_file>
+	<FileVersion major="1" minor="1"/>
+	<Project>
+		<Option title="sauerbraten"/>
+		<Option makefile="Makefile"/>
+		<Option makefile_is_custom="0"/>
+		<Option compiler="0"/>
+		<Build>
+			<Target title="default">
+				<Option output="..\..\bin\sauerbraten.exe"/>
+				<Option working_dir="..\..\"/>
+				<Option object_output=".objs"/>
+				<Option deps_output=".deps"/>
+				<Option type="1"/>
+				<Option compiler="0"/>
+				<Option parameters="-f0 -w640 -h480 -grpg"/>
+				<Option projectResourceIncludeDirsRelation="1"/>
+			</Target>
+		</Build>
+		<Compiler>
+			<Add option="-fexpensive-optimizations"/>
+			<Add option="-Os"/>
+			<Add option="-O3"/>
+			<Add option="-fsigned-char"/>
+			<Add option="-finline-functions"/>
+			<Add option="-fomit-frame-pointer"/>
+			<Add option="-fno-strict-aliasing"/>
+			<Add option="-DDEVCPP"/>
+			<Add option="-D__GNUWIN32__"/>
+			<Add option="-DNDEBUG"/>
+			<Add option="-D_CONSOLE"/>
+			<Add option="-D_MBCS"/>
+			<Add directory="..\enet\include"/>
+			<Add directory="..\include"/>
+			<Add directory="..\engine"/>
+			<Add directory="..\shared"/>
+		</Compiler>
+		<Linker>
+			<Add option="-s"/>
+			<Add library="mingw32"/>
+			<Add library="SDLmain"/>
+			<Add library="SDL"/>
+			<Add library="SDL_image"/>
+			<Add library="SDL_mixer"/>
+			<Add library="z"/>
+			<Add library="opengl32"/>
+			<Add library="glu32"/>
+			<Add library="ws2_32"/>
+			<Add library="winmm"/>
+			<Add directory="lib"/>
+		</Linker>
+		<Unit filename="..\enet\callbacks.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\host.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\callbacks.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\enet.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\list.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\protocol.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\time.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\types.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\unix.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\utility.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\include\enet\win32.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\list.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\packet.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\peer.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\protocol.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\unix.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\enet\win32.c">
+			<Option compilerVar="CC"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\3dgui.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\animmodel.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\bih.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\bih.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\client.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\command.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\console.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\cubeloader.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\decal.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\dynlight.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\engine.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\explosion.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\glare.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\grass.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\lensflare.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\lightmap.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\lightmap.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\lightning.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\main.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\material.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\md2.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\md3.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\md5.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\menus.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\model.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\normal.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\obj.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\octa.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\octa.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\octaedit.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\octarender.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\physics.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\pvs.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\rendergl.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\rendermodel.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\renderparticles.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\rendersky.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\rendertarget.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\rendertext.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\renderva.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\server.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\serverbrowser.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\shader.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\shadowmap.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\skelmodel.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\sound.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\texture.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\texture.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\tristrip.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\vertmodel.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\water.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\world.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\world.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\engine\worldio.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\assassin.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\capture.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\client.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\ctf.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\entities.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\fps.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\fpsrender.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\fpsserver.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\game.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\monster.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\movable.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\scoreboard.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\fpsgame\weapon.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\GL\glext.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_active.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_audio.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_byteorder.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_cdrom.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_config.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_config_macosx.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_config_win32.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_copying.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_cpuinfo.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_endian.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_error.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_events.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_getenv.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_image.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_joystick.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_keyboard.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_keysym.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_loadso.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_main.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_mixer.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_mouse.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_mutex.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_name.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_opengl.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_platform.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_quit.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_rwops.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_stdinc.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_syswm.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_thread.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_timer.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_types.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_version.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\SDL_video.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\begin_code.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\close_code.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\wincompat.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\zconf.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\include\zlib.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\entities.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\rpg.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\rpgent.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\rpgobj.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\rpgobjset.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\stats.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\rpggame\stubs.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\command.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\cube.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\ents.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\geom.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\geom.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\iengine.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\igame.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\pch.cpp">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\pch.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\sbtrace.h">
+			<Option compilerVar=""/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\tools.cpp">
+			<Option compilerVar="CPP"/>
+			<Option target="default"/>
+		</Unit>
+		<Unit filename="..\shared\tools.h">
+			<Option compilerVar="CPP"/>
+			<Option compile="0"/>
+			<Option link="0"/>
+			<Option target="default"/>
+		</Unit>
+	</Project>
+</CodeBlocks_project_file>
diff --git a/readme_source.txt b/readme_source.txt
index 6d9cadd..fd2e912 100644
--- a/readme_source.txt
+++ b/readme_source.txt
@@ -1,6 +1,6 @@
 Sauerbraten source code license, usage, and documentation.
 
-You may use the Sauerbraten source code if you abide by the ZLIB license
+You may use the sauerbraten source code if you abide by the ZLIB license
 http://www.opensource.org/licenses/zlib-license.php
 (very similar to the BSD license):
 
@@ -10,7 +10,7 @@ LICENSE
 
 Sauerbraten game engine source code, any release.
 
-Copyright (C) 2001-2009 Wouter van Oortmerssen, Lee Salzman, Mike Dysart, Robert Pointon, and Quinton Reeves
+Copyright (C) 2001-2006 Wouter van Oortmerssen.
 
 This software is provided 'as-is', without any express or implied
 warranty.  In no event will the authors be held liable for any damages
@@ -32,9 +32,8 @@ freely, subject to the following restrictions:
 LICENSE NOTES
 =============
 The license covers the source code found in the "src" directory of this
-archive as well as the .cfg files under the "data" directory. The included 
-ENet network library which Sauerbraten uses is covered by an MIT-style 
-license, which is however compatible with the above license for all 
+archive, the included enet network library which sauerbraten uses is covered by
+an MIT-style license, which is however compatible with the above license for all 
 practical purposes.
 
 Game media included in the game (maps, textures, sounds, models etc.)
@@ -47,46 +46,38 @@ USAGE
 Compiling the sources should be straight forward.
 
 Unix users need to make sure to have the development version of all libs
-installed (OpenGL, SDL, SDL_mixer, SDL_image, zlib). The included
-Makefile can be used to build.
-
-Windows users can use the included Visual Studio project files in the vcpp 
-directory,  which references the lib/include directories for the external 
-libraries and should thus be self contained. Release mode builds will place 
-executables in the bin dir ready for testing and distribution.
-
-An alternative to Visual Studio for Windows is MinGW/MSYS, which can be compiled
-using the provided Makefile. Another alternative for Windows is to compile under
-Code::Blocks with the provided vcpp/sauerbraten.cbp project file.
-
-The Sauerbraten sources are very small, compact, and non-redundant, so anyone
+installed (OpenGL, SDL, SDL_Mixer, SDL_Image, zlib, libpng). The included
+makefiles can be used to build.
+
+Windows users can use the included visual studio .net project files, which
+references the lib/include directories for the external libraries and should
+thus be self contained. Release mode builds will place executables in the bin dir
+ready for testing and distribution. Do not come ask me for help in compiling
+or modifying the sources, if you can't figure out how to do this yourself
+you probably shouldn't be touching the files anyway.
+
+An alternative to visual studio for Windows is MinGW. In order to compile 
+using MinGW, copy Makefile.mingw from the mingw directory to the src directory.
+You can also optionally copy make.mingw.bat to the sauerbraten directory for an
+easy way to compile.
+
+The sauerbraten sources are very small, compact, and non redundant, so anyone
 wishing to modify the source code should be able to gain an overview of
-Sauerbraten's inner workings by simply reading through the source code in its
+sauerbraten's inner workings by simply reading through the source code in its
 entirety. Small amounts of comments should guide you through the more
 tricky sections.
 
-When reading the source code and trying to understand Sauerbaten's internal design,
-keep in mind the goal of Cube: minimalism. I wanted to create a very complete
+When reading the source code and trying to understand sauerbaten's internal design,
+keep in mind the goal of cube: minimalism. I wanted to create a very complete
 game / game engine with absolutely minimal means, and made a sport out of it
-keeping the implementation small and simple. Sauerbraten is not a commercial 
-product, it is merely the author's idea of a fun little programming project.
+keeping the implementation small and simple. Sauerbraten is not a commercial product,
+it is merely the author's idea of a fun little programming project.
 
 
-AUTHORS
+AUTHOR
 ======
-Wouter "Aardappel" van Oortmerssen
+Wouter van Oortmerssen aka Aardappel
+wvo at gmx dot net
 http://strlen.com
 
-Lee "eihrul" Salzman 
-http://lee.fov120.com
-
-Mike "Gilt" Dysart
-
-Robert "baby-rabbit" Pointon
-http://www.fernlightning.com
-
-Quinton "Quin" Reeves
-http://bloodfrontier.com
-
-For additional authors/contributors, see the Sauerbraten binary distribution readme.
-
+For additional authors/contributors, see the sauerbraten binary distribution readme.
diff --git a/rpggame/entities.h b/rpggame/entities.h
new file mode 100644
index 0000000..1b5c80a
--- /dev/null
+++ b/rpggame/entities.h
@@ -0,0 +1,68 @@
+
+enum { ETR_SPAWN = ET_GAMESPECIFIC, };
+
+static const int SPAWNNAMELEN = 64;
+
+struct rpgentity : extentity
+{
+    char name[SPAWNNAMELEN];
+    
+    rpgentity() { memset(name, 0, SPAWNNAMELEN); }
+};
+
+struct rpgentities : icliententities
+{
+    rpgclient &cl;
+    vector<rpgentity *> ents;
+    rpgentity *lastcreated;
+
+    ~rpgentities() {}
+    rpgentities(rpgclient &_cl) : cl(_cl), lastcreated(NULL)
+    {
+        CCOMMAND(spawnname, "s", (rpgentities *self, char *s), { if(self->lastcreated) { s_strncpy(self->lastcreated->name, s, SPAWNNAMELEN); self->spawnfroment(*self->lastcreated); } });    
+    }
+
+    vector<extentity *> &getents() { return (vector<extentity *> &)ents; }
+
+    void editent(int i) {}
+
+    const char *entnameinfo(entity &e) { return ((rpgentity &)e).name; }
+    const char *entname(int i)
+    {
+        static const char *entnames[] = { "none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight", "spawn" };
+        return i>=0 && size_t(i)<sizeof(entnames)/sizeof(entnames[0]) ? entnames[i] : "";
+    }
+
+    int extraentinfosize() { return SPAWNNAMELEN; }
+    void writeent(entity &e, char *buf) { memcpy(buf, ((rpgentity &)e).name, SPAWNNAMELEN); }
+    void readent (entity &e, char *buf) { memcpy(((rpgentity &)e).name, buf, SPAWNNAMELEN); }
+
+    float dropheight(entity &e) { return e.type==ET_MAPMODEL ? 0 : 4; }
+
+    void rumble(const extentity &e) { playsoundname("free/rumble", &e.o); }
+    void trigger(extentity &e) {}
+
+    extentity *newentity() { return new rpgentity; }
+
+    void fixentity(extentity &e)
+    {
+        lastcreated = (rpgentity *)&e;
+        switch(e.type)
+        {
+            case ETR_SPAWN:
+                e.attr1 = (int)cl.player1.yaw;
+        }
+    }
+    
+    void spawnfroment(rpgentity &e)
+    {
+        cl.os.spawn(e.name);
+        cl.os.placeinworld(e.o, e.attr1);      
+    }
+    
+    void startmap()
+    {
+        lastcreated = NULL;
+        loopv(ents) if(ents[i]->type==ETR_SPAWN) spawnfroment(*ents[i]);
+    }
+};
diff --git a/rpggame/rpg.cpp b/rpggame/rpg.cpp
new file mode 100644
index 0000000..8d7f5c7
--- /dev/null
+++ b/rpggame/rpg.cpp
@@ -0,0 +1,194 @@
+
+#include "pch.h"
+#include "cube.h"
+#include "iengine.h"
+#include "igame.h"
+#include "stubs.h"
+
+
+struct rpgclient : igameclient, g3d_callback
+{
+    struct rpgent;
+
+    #include "entities.h"
+    #include "stats.h"
+    #include "rpgobj.h"
+    #include "rpgobjset.h"
+    #include "rpgent.h"
+
+    rpgentities et;
+    rpgdummycom cc;
+    rpgobjset os;
+    
+    rpgent player1;
+
+    int lastmillis, maptime;
+    string mapname;
+      
+    int menutime, menutab, menuwhich;
+    vec menupos;
+
+    rpgclient() : et(*this), os(*this), player1(os.playerobj, *this, vec(0, 0, 0), 0, 100, ENT_PLAYER), lastmillis(0), maptime(0), menutime(0), menutab(1), menuwhich(0)
+    {
+        CCOMMAND(map, "s", (rpgclient *self, char *s), load_world(s));    
+        CCOMMAND(showplayergui, "i", (rpgclient *self, int *which), self->showplayergui(*which));    
+    }
+    ~rpgclient() {}
+
+    icliententities *getents() { return &et; }
+    iclientcom *getcom() { return &cc; }
+
+    void updateworld(vec &pos, int curtime, int lm)
+    {
+        if(!maptime) { maptime = lm + curtime; return; }
+        lastmillis = lm;
+        if(!curtime) return;
+        physicsframe();
+        os.update(curtime);
+        player1.updateplayer(curtime, pos);
+        checktriggers();
+    }
+    
+    void showplayergui(int which)
+    {
+        if((menutime && which==menuwhich) || !which)
+        {
+            menutime = 0;
+        }
+        else
+        {
+            menutime = starttime();
+            menupos  = menuinfrontofplayer();        
+            menuwhich = which;
+        }
+    }
+
+    void gui(g3d_gui &g, bool firstpass)
+    {
+        g.start(menutime, 0.03f, &menutab);
+        switch(menuwhich)
+        {
+            default:
+            case 1:
+                g.tab("inventory", 0xFFFFF);
+                os.playerobj->invgui(g);
+                break;
+            
+            case 2:
+                g.tab("stats", 0xFFFFF);
+                os.playerobj->st_show(g);
+                break;
+            
+            case 3:
+                g.tab("active quests", 0xFFFFF);
+                os.listquests(false, g);
+                g.tab("completed quests", 0xFFFFF);
+                os.listquests(true, g);
+        }
+        g.end();
+    }
+    
+    void initclient() {}
+        
+    void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material)
+    {
+        if     (waterlevel>0) playsoundname("free/splash1", d==&player1 ? NULL : &d->o);
+        else if(waterlevel<0) playsoundname("free/splash2", d==&player1 ? NULL : &d->o);
+        if     (floorlevel>0) { if(local) playsoundname("aard/jump"); else if(d->type==ENT_AI) playsoundname("aard/jump", &d->o); }
+        else if(floorlevel<0) { if(local) playsoundname("aard/land"); else if(d->type==ENT_AI) playsoundname("aard/land", &d->o); }    
+    }
+    
+    void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0) {}
+    char *getclientmap() { return mapname; }
+    void resetgamestate() {}
+    void suicide(physent *d) {}
+    void newmap(int size) {}
+
+    void startmap(const char *name)
+    {
+        os.clearworld();
+        s_strcpy(mapname, name);
+        maptime = 0;
+        findplayerspawn(&player1);
+        if(*name) os.playerobj->st_init();
+        et.startmap();
+    }
+    
+    void quad(int x, int y, int xs, int ys)
+    {
+        glBegin(GL_QUADS);
+        glTexCoord2f(0, 0); glVertex2i(x,    y);
+        glTexCoord2f(1, 0); glVertex2i(x+xs, y);
+        glTexCoord2f(1, 1); glVertex2i(x+xs, y+ys);
+        glTexCoord2f(0, 1); glVertex2i(x,    y+ys);
+        glEnd();
+    }
+    
+    void gameplayhud(int w, int h)
+    {
+        glLoadIdentity();
+        glOrtho(0, w*2, h*2, 0, -1, 1);
+        draw_textf("using: %s", 636*2, h*2-256+149, os.selected ? os.selected->name : "(none)");       // temp     
+                                            
+        glLoadIdentity();
+        glOrtho(0, w, h, 0, -1, 1);
+        settexture("packages/hud/hud_rpg.png", true);
+        
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        quad(0, h-128, 768, 128);        
+        settexture("packages/hud/hbar.png", true);
+        glColor4f(1, 0, 0, 0.5f);
+        quad(130, h-128+57, 193*os.playerobj->s_hp/os.playerobj->eff_maxhp(), 17);        
+        glColor4f(0, 0, 1, 0.5f);
+        quad(130, h-128+87, 193*os.playerobj->s_mana/os.playerobj->eff_maxmana(), 17);        
+    }
+    
+    void drawhudmodel(int anim, float speed = 0, int base = 0)
+    {
+        rendermodel(NULL, "hudguns/fist", anim, player1.o, player1.yaw+90, player1.pitch, MDL_LIGHT, NULL, NULL, base, speed);
+    }
+
+    void drawhudgun()
+    {
+        if(editmode) return;
+
+        int rtime = 250;
+        if(lastmillis-player1.lastaction<rtime)
+        {
+            drawhudmodel(ANIM_GUNSHOOT, rtime/17.0f, player1.lastaction);
+        }
+        else
+        {
+            drawhudmodel(ANIM_GUNIDLE|ANIM_LOOP);
+        }
+    }
+
+    bool canjump() { return true; }
+    void doattack(bool on) { player1.attacking = on; }
+    dynent *iterdynents(int i) { return i ? os.set[i-1]->ent : &player1; }
+    int numdynents() { return os.set.length()+1; }
+
+    void rendergame()
+    {
+        if(isthirdperson()) renderclient(&player1, "monster/ogro", NULL, ANIM_PUNCH, 300, player1.lastaction, player1.lastpain);
+        os.render();
+    }
+    
+    void g3d_gamemenus() { os.g3d_npcmenus(); if(menutime) g3d_addgui(this, menupos, GUI_2D); }
+
+    void writegamedata(vector<char> &extras) {}
+    void readgamedata (vector<char> &extras) {}
+
+    const char *gameident()     { return "rpg"; }
+    const char *defaultmap()    { return "rpg_01"; }
+    const char *savedconfig()   { return "rpg_config.cfg"; }
+    const char *defaultconfig() { return "data/defaults.cfg"; }
+    const char *autoexec()      { return "rpg_autoexec.cfg"; }
+};
+
+#define N(n) int rpgclient::stats::pointscale_##n, rpgclient::stats::percentscale_##n; 
+RPGSTATNAMES 
+#undef N
+
+REGISTERGAME(rpggame, "rpg", new rpgclient(), new rpgdummyserver());
+
diff --git a/rpggame/rpg_plan.txt b/rpggame/rpg_plan.txt
new file mode 100644
index 0000000..8abfb64
--- /dev/null
+++ b/rpggame/rpg_plan.txt
@@ -0,0 +1,39 @@
+implementation plan (roughly in order of importance to make the game playable):
+
+- wolves sometimes don't do damage when attacking?
+- pain & attack sounds
+- add icons for most items - show current item & quick bar on hud
+- make mousewheel select between weapons in inventory
+- implement npcs getting hostile if you pick up anything in the world within N range, or M sightrange
+  the more worth the item, the longer they will be hostile. if they knock you out, they'll take the item back.
+- move more stuff from c++ to script
+- hud weapons?
+- make some game messages into centerprints?
+- implement attackspeed/movespeed/jumpheight and other stats
+- enemies can sometimes just run in circles around you
+- game loading/saving
+- work on animations (attack etc)
+- make generic box that can take icons to replace generic bag
+- add many more standard items, maybe some temp more monsters from the fps
+- show body when dead
+- tweak all stats
+- improved grid layout inventory screens
+- horse riding (maybe at first as teleport, later as fast transport)
+- FIXME's in code
+- utilize all hud items (radar etc)
+- do world pickups differently (not with gui window), show worth stat while hovering over
+  maybe generally do system with different crosshairs depending on what you point at (take, attack)
+- need a confirmation of wether you want to take on a quest or not?
+- r_take should take a list of items
+
+art list:
+- hud with health mana stat bars
+  * health and mana
+  * current weapon icon
+  * 12 slots for quick use items
+  * small radar
+  * info line for mouse over?
+- icons:
+  * buy
+  * sell
+- hud weapons: fist / sword (maybe axe and others) / bow or crossbow / shotgun? / spell casting hands
diff --git a/rpggame/rpgent.h b/rpggame/rpgent.h
new file mode 100644
index 0000000..7f24ba8
--- /dev/null
+++ b/rpggame/rpgent.h
@@ -0,0 +1,274 @@
+struct rpgent : dynent
+{
+    rpgobj *ro;
+    rpgclient &cl;
+
+    int lastaction, lastpain;
+    bool attacking;
+    
+    bool magicprojectile;
+    vec mppos, mpdir;
+    rpgobj *mpweapon;
+    float mpdist;
+    
+    rpgent *enemy;
+    
+    enum { R_STARE, R_ROAM, R_SEEK, R_ATTACK, R_BLOCKED, R_BACKHOME };
+    int npcstate;
+    
+    int trigger;
+
+    float sink;
+
+    vec home;
+        
+    enum { ROTSPEED = 200 };
+
+    rpgent(rpgobj *_ro, rpgclient &_cl, const vec &_pos, float _yaw, int _maxspeed = 40, int _type = ENT_AI) : ro(_ro), cl(_cl), lastaction(0), lastpain(0), attacking(false), magicprojectile(false), enemy(NULL), npcstate(R_STARE), trigger(0), sink(0)
+    {
+        o = _pos;
+        home = _pos;
+        yaw = _yaw;
+        maxspeed = _maxspeed;
+        type = _type;
+        enemy = &cl.player1;
+    }
+
+    float vecyaw(vec &t) { return -(float)atan2(t.x-o.x, t.y-o.y)/RAD+180; }
+
+    static const int ATTACKSAMPLES = 32;
+
+    void tryattackobj(rpgobj &eo, rpgobj &weapon)
+    {
+        if(!eo.s_ai || (eo.s_ai==ro->s_ai && eo.ent!=enemy)) return;    
+        
+        rpgent &e = *eo.ent;
+        if(e.state!=CS_ALIVE) return;
+
+        vec d = e.o;
+        d.sub(o);
+        d.z = 0;
+        if(d.magnitude()>e.radius+weapon.s_maxrange) return; 
+
+        if(o.z+aboveeye<=e.o.z-e.eyeheight || o.z-eyeheight>=e.o.z+e.aboveeye) return;
+
+        vec p(0, 0, 0), closep;
+        float closedist = 1e10f; 
+        loopj(ATTACKSAMPLES)
+        {
+            p.x = e.xradius * cosf(2*M_PI*j/ATTACKSAMPLES);
+            p.y = e.yradius * sinf(2*M_PI*j/ATTACKSAMPLES);
+            p.rotate_around_z((e.yaw+90)*RAD);
+
+            p.x += e.o.x;
+            p.y += e.o.y;
+            float tyaw = vecyaw(p);
+            normalize_yaw(tyaw);
+            if(fabs(tyaw-yaw)>weapon.s_maxangle) continue;
+
+            float dx = p.x-o.x, dy = p.y-o.y, dist = dx*dx + dy*dy;
+            if(dist<closedist) { closedist = dist; closep = p; }
+        }
+
+        if(closedist>weapon.s_maxrange*weapon.s_maxrange) return;
+
+        weapon.useaction(eo, *this, true);   
+    }
+    
+    #define loopallrpgobjsexcept(ro) loop(i, cl.os.set.length()+1) for(rpgobj *eo = i ? cl.os.set[i-1] : cl.os.playerobj; eo; eo = NULL) if((ro)!=eo) 
+
+    void tryattack(vec &lookatpos, rpgobj &weapon)
+    {                
+        if(cl.lastmillis-lastaction<weapon.s_attackrate) return;
+        
+        lastaction = cl.lastmillis;
+
+        switch(weapon.s_usetype)
+        {
+            case 1:
+                if(!weapon.s_damage) return;
+                weapon.usesound(this);
+                loopallrpgobjsexcept(ro) tryattackobj(*eo, weapon);
+                break;
+            
+            case 2:
+            {
+                if(!weapon.s_damage) return;
+                weapon.usesound(this);
+                particle_splash(0, 200, 250, lookatpos);
+                vec flarestart = o;
+                flarestart.z -= 2;
+                particle_flare(flarestart, lookatpos, 600, 10);  // FIXME hudgunorigin(), and shorten to maxrange
+                float bestdist = 1e16f;
+                rpgobj *best = NULL;
+                loopallrpgobjsexcept(ro)
+                {
+                    if(eo->ent->state!=CS_ALIVE) continue;
+                    if(!intersect(eo->ent, o, lookatpos)) continue;
+                    float dist = o.dist(eo->ent->o);
+                    if(dist<weapon.s_maxrange && dist<bestdist)
+                    {
+                        best = eo;
+                        bestdist = dist;
+                    }
+                }
+                if(best) weapon.useaction(*best, *this, true);  
+                break;
+            }   
+            case 3:                
+                if(weapon.s_maxrange)   // projectile, cast on target
+                {
+                    if(magicprojectile) return;     // only one in the air at once
+                    if(!ro->usemana(weapon)) return;
+                                       
+                    magicprojectile = true;
+                    mpweapon = &weapon;
+                    mppos = o;
+                    //mpdir = vec(yaw*RAD, pitch*RAD);
+                    float worlddist = lookatpos.dist(o, mpdir);
+                    mpdir.normalize();
+                    mpdist = min(float(weapon.s_maxrange), worlddist);
+                }
+                else
+                {
+                    weapon.useaction(*ro, *this, true);   // cast on self
+                }                
+                break;
+        }
+        
+    }
+
+    void updateprojectile(int curtime)
+    {
+        if(!magicprojectile) return;
+        
+        regular_particle_splash(1, 2, 300, mppos);
+        particle_splash(mpweapon->s_effect, 1, 1, mppos);    
+
+        float dist = curtime/5.0f;
+        if((mpdist -= dist)<0) { magicprojectile = false; return; };
+        
+        vec mpto = vec(mpdir).mul(dist).add(mppos);
+
+        loopallrpgobjsexcept(ro)     // FIXME: make fast "give me all rpgobs in range R that are not X" function
+        {
+            if(eo->ent->o.dist(mppos)<32 && intersect(eo->ent, mppos, mpto))  // quick reject, for now
+            {
+                magicprojectile = false;
+                mpweapon->useaction(*eo, *this, false);    // cast on target
+            }
+        }
+        
+        mppos = mpto;  
+    }
+
+    void transition(int _state, int _moving, int n) 
+    {
+        npcstate = _state;
+        move = _moving;
+        trigger = cl.lastmillis+n;
+    }
+    
+    void gotoyaw(float yaw, int s, int m, int t)
+    {
+        targetyaw = yaw;            
+        rotspeed = ROTSPEED;
+        transition(s, m, t);            
+    }
+    
+    void gotopos(vec &pos, int s, int m, int t) { gotoyaw(vecyaw(pos), s, m, t); }
+    
+    void goroam()
+    {
+        if(home.dist(o)>128 && npcstate!=R_BACKHOME)  gotopos(home, R_ROAM, 1, 1000);
+        else                                          gotoyaw(targetyaw+90+rnd(180), R_ROAM, 1, 1000);                                       
+    }
+    
+    void stareorroam()
+    {
+        if(rnd(10)) transition(R_STARE, 0, 500);
+        else goroam();    
+    }
+    
+    void update(int curtime, float playerdist)
+    {
+        updateprojectile(curtime);
+
+        if(state==CS_DEAD) { stopmoving(); return; };
+
+        if(blocked && npcstate!=R_BLOCKED && npcstate!=R_SEEK)                                                             
+        {
+            blocked = false;
+            gotoyaw(targetyaw+90+rnd(180), R_BLOCKED, 1, 1000);                           
+        }
+                
+        if(ro->s_hp<ro->eff_maxhp() && npcstate!=R_SEEK) gotopos(enemy->o, R_SEEK,  1, 200);   
+        
+        #define ifnextstate   if(trigger<cl.lastmillis)
+        #define ifplayerclose if(playerdist<64)
+
+        switch(npcstate)
+        {
+            case R_BLOCKED:
+            case R_BACKHOME:
+                ifnextstate goroam();
+                break;
+        
+            case R_STARE:
+                ifplayerclose
+                {
+                    if(ro->s_ai==2) { gotopos(cl.player1.o, R_SEEK,  1, 200); enemy = &cl.player1; }
+                    else            { gotopos(cl.player1.o, R_STARE, 0, 500); }
+                }
+                else ifnextstate stareorroam();
+                break;
+            
+            case R_ROAM:
+                ifplayerclose    transition(R_STARE, 0, 500);
+                else ifnextstate stareorroam();
+                break;
+                
+            case R_SEEK:
+                ifnextstate
+                {
+                    vec target;
+                    if(raycubelos(o, enemy->o, target))
+                    {
+                        rpgobj &weapon = ro->selectedweapon();
+                        if(target.dist(o)<weapon.s_maxrange) tryattack(target, weapon);
+                    }
+                    if(enemy->o.dist(o)>256) goroam();
+                    else gotopos(enemy->o, R_SEEK, 1, 100);
+                }
+        }
+        
+        #undef ifnextstate
+        #undef ifplayerclose
+    }
+
+    void updateplayer(int curtime, vec &lookatpos)     // alternative version of update() if this ent is the main player
+    {
+        updateprojectile(curtime);
+
+        if(state==CS_DEAD)
+        {
+            //lastaction = lastmillis;
+            if(cl.lastmillis-lastaction>5000)
+            {
+                conoutf("\f2you were found unconscious, and have been carried back home");
+
+                findplayerspawn(this);
+                state = CS_ALIVE;
+                ro->st_respawn();
+                attacking = false;
+                particle_splash(2, 1000, 500, o);
+            }
+        }
+        else
+        {
+            moveplayer(this, 20, true);
+            ro->st_update(cl.lastmillis);
+            if(attacking) tryattack(lookatpos, ro->selectedweapon());
+        }            
+    }
+};
diff --git a/rpggame/rpgobj.h b/rpggame/rpgobj.h
new file mode 100644
index 0000000..ff9b1b8
--- /dev/null
+++ b/rpggame/rpgobj.h
@@ -0,0 +1,394 @@
+struct rpgobjset;
+
+struct rpgquest
+{
+    rpgquest *next;
+    const char *npc;
+    const char *questline;
+    bool completed;
+    
+    rpgquest(rpgquest *_n, const char *_npc, const char *_ql) : next(_n), npc(_npc), questline(_ql), completed(false) {}
+};
+
+struct rpgaction
+{
+    rpgaction *next;
+    char *initiate, *script;
+    rpgquest *q;
+    bool used;
+
+    rpgaction(char *_i = NULL, char *_s = NULL, rpgaction *_n = NULL) : next(_n), initiate(_i), script(_s), q(NULL), used(false) {}
+
+    ~rpgaction() { DELETEP(next); }
+
+    void exec(rpgobj *obj, rpgobj *target, rpgobj *user, rpgobjset &os)
+    {
+        if(!*script) return;
+        os.pushobj(user);
+        os.pushobj(target);
+        os.pushobj(obj);
+        execute(script);
+        used = true;
+    }
+};
+
+struct rpgobj : g3d_callback, stats
+{
+    rpgobj *parent;     // container object, if not top level
+    rpgobj *inventory;  // contained objects, if any
+    rpgobj *sibling;    // used for linking, if contained
+
+    rpgent *ent;        // representation in the world, if top level
+
+    const char *name;   // name it was spawned as
+    const char *model;  // what to display it as
+
+    enum
+    {
+        IF_INVENTORY = 1,   // parent owns this object, will contribute to parent stats
+        IF_LOOT      = 2,   // if parent dies, this object should drop to the ground
+        IF_TRADE     = 4,   // parent has this item available for trade, for player, all currently unused weapons etc are of this type
+    };
+
+    enum
+    {
+        MENU_DEFAULT,
+        MENU_BUY,
+        MENU_SELL
+    };
+
+    int itemflags;
+    
+    rpgaction *actions, action_use;
+    char *abovetext;    
+
+    int menutime, menutab, menuwhich;
+
+    rpgobjset &os;
+    
+    #define loopinventory() for(rpgobj *o = inventory; o; o = o->sibling)
+    #define loopinventorytype(T) loopinventory() if(o->itemflags&(T))
+
+    rpgobj(const char *_name, rpgobjset &_os) : parent(NULL), inventory(NULL), sibling(NULL), ent(NULL), name(_name), model(NULL), itemflags(IF_INVENTORY),
+        actions(NULL), abovetext(NULL), menutime(0), menutab(1), menuwhich(MENU_DEFAULT), os(_os) {}
+
+    ~rpgobj()
+    {
+        DELETEP(inventory);
+        DELETEP(sibling);
+        DELETEP(ent);
+        DELETEP(actions);
+    }
+
+    void scriptinit()
+    {
+        DELETEP(inventory);
+        s_sprintfd(aliasname)("spawn_%s", name);
+        execute(aliasname);
+    }
+
+    void decontain() 
+    {
+        if(parent) parent->remove(this);
+    }
+
+    void add(rpgobj *o, int itemflags)
+    {
+        o->sibling = inventory;
+        o->parent = this;
+        inventory = o;
+        o->itemflags = itemflags;
+        
+        if(itemflags&IF_INVENTORY) recalcstats();
+    }
+
+    void remove(rpgobj *o)
+    {
+        for(rpgobj **l = &inventory; *l; )
+            if(*l==o) 
+            {
+                *l = o->sibling;
+                o->sibling = o->parent = NULL;
+            }
+            else l = &(*l)->sibling;
+            
+        if(o->itemflags&IF_INVENTORY) recalcstats();
+    }
+
+    void recalcstats()
+    {
+        st_reset();
+        loopinventorytype(IF_INVENTORY) st_accumulate(*o);
+    }
+    
+    rpgobj &selectedweapon()
+    {
+        if(this==os.playerobj) return os.selected ? *os.selected : *this;
+        else { loopinventorytype(IF_INVENTORY) if(o->s_usetype) return *o; };
+        return *this;
+    }
+    
+    void placeinworld(rpgent *_ent)
+    {
+        if(!model) model = "tentus/moneybag";
+        ent = _ent;
+        setbbfrommodel(ent, model);
+        entinmap(ent);
+        //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096));
+        st_init();
+    }
+
+    void render()
+    {
+        if(s_ai) 
+        {
+            float sink = 0;
+            if(ent->physstate>=PHYS_SLIDE)
+                sink = raycube(ent->o, vec(0, 0, -1), 2*ent->eyeheight)-ent->eyeheight;
+            ent->sink = ent->sink*0.8 + sink*0.2;
+            //if(ent->blocked) particle_splash(0, 100, 100, ent->o);
+            renderclient(ent, model, NULL, ANIM_PUNCH, 300, ent->lastaction, 0, ent->sink);
+            if(s_hp<eff_maxhp() && ent->state==CS_ALIVE) particle_meter(ent->abovehead(), s_hp/(float)eff_maxhp(), 17);
+
+        }
+        else
+        {
+            rendermodel(NULL, model, ANIM_MAPMODEL|ANIM_LOOP, vec(ent->o).sub(vec(0, 0, ent->eyeheight)), ent->yaw+90, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED | MDL_LIGHT, ent);
+        }
+    }
+
+    void update(int curtime)
+    {
+        float dist = ent->o.dist(os.cl.player1.o);
+        if(s_ai) { ent->update(curtime, dist); st_update(ent->cl.lastmillis); };
+        moveplayer(ent, 10, false, curtime);    // 10 or above gets blocked less, because physics accuracy doesn't need extra tests
+        //ASSERT(!(ent->o.x<0 || ent->o.y<0 || ent->o.z<0 || ent->o.x>4096 || ent->o.y>4096 || ent->o.z>4096));
+        if(!menutime && dist<(s_ai ? 40 : 24) && ent->state==CS_ALIVE && s_ai<2) { menutime = starttime(); menuwhich = MENU_DEFAULT; }
+        else if(dist>(s_ai ? 96 : 48)) menutime = 0;
+    }
+
+    void addaction(char *initiate, char *script, bool startquest)
+    {
+        for(rpgaction *a = actions; a; a = a->next) if(strcmp(a->initiate, initiate)==0) return;
+        actions = new rpgaction(initiate, script, actions);
+        if(startquest) os.addquest(actions, abovetext, name);
+    }
+
+    void droploot()
+    {
+        loopinventorytype(IF_LOOT)
+        {
+            o->decontain();
+            os.pushobj(o);
+            os.placeinworld(ent->o, rnd(360));
+            droploot();
+            return;
+        }
+    }
+
+    rpgobj *take(char *name)
+    {
+        loopinventory() if(strcmp(o->name, name)==0)
+        {
+            o->decontain();
+            return o;
+        }
+        return NULL;
+    }
+    
+    void takedamage(int damage, rpgobj &attacker)
+    {
+        ent->enemy = attacker.ent;
+        
+        particle_splash(3, damage*5, 1000, ent->o);
+        s_sprintfd(ds)("@%d", damage);
+        particle_text(ent->o, ds, 8);
+        
+        if((s_hp -= damage)<=0)
+        {
+            s_hp = 0;
+            ent->state = CS_DEAD;
+            ent->attacking = false;
+            ent->lastaction = os.cl.lastmillis;
+            menutime = 0;
+            conoutf("%s killed: %s", attacker.name, name);
+            droploot();
+        }    
+    }
+    
+    void usesound(rpgent *user)
+    {
+        if(s_usesound) playsound(s_usesound, &user->o);    
+    }
+    
+    bool usemana(rpgobj &o)
+    {
+        if(o.s_manacost>s_mana) { if(this==os.playerobj) conoutf("\f2not enough mana"); return false; };
+        s_mana -= o.s_manacost;
+        o.usesound(ent);
+        return true;
+    }
+        
+    void useaction(rpgobj &target, rpgent &initiator, bool chargemana)
+    {
+        if(action_use.script && (!chargemana || initiator.ro->usemana(*this)))
+        {
+            action_use.exec(this, &target, initiator.ro, os);
+            if(s_useamount && !--s_useamount) os.removefromsystem(this);
+        } 
+    }
+    
+    void selectuse()
+    {
+        if(s_usetype)
+        {
+            conoutf("\f2using: %s", name);
+            os.selected = this;                    
+        }
+        else
+        {
+            useaction(*os.playerobj, *os.playerobj->ent, true);
+        }
+    }
+    
+    void guiaction(g3d_gui &g, rpgaction *a)
+    {
+        if(!a) return;
+        guiaction(g, a->next);
+        if(g.button(a->initiate, a->used ? 0xAAAAAA : 0xFFFFFF, "chat")&G3D_UP)
+        {
+            os.currentquest = a->q;
+            a->exec(this, os.playerobj, os.playerobj, os);
+            os.currentquest = NULL;
+        } 
+    }
+    
+    void gui(g3d_gui &g, bool firstpass)
+    {
+        g.start(menutime, 0.015f, &menutab);
+        switch(menuwhich)
+        {
+            case MENU_DEFAULT:
+            {
+                g.tab(name, 0xFFFFFF);
+                if(abovetext) g.text(abovetext, 0xDDFFDD);
+
+                guiaction(g, actions);
+
+                if(s_ai)
+                {
+                    bool trader = false;
+                    loopinventorytype(IF_TRADE) trader = true;
+                    if(trader)
+                    {
+                        if(g.button("buy",  0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_BUY;
+                        if(g.button("sell", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_SELL;                    
+                    }
+                }
+                else
+                {
+                    s_sprintfd(wtext)("worth %d", s_worth);
+                    g.text(wtext, 0xAAAAAA, "coins");
+                    if(g.button("take", 0xFFFFFF, "hand")&G3D_UP)
+                    {
+                        conoutf("\f2you take a %s (worth %d gold)", name, s_worth);
+                        os.take(this, os.playerobj);
+                    }
+                    if(!s_usetype && g.button("use", 0xFFFFFF, "hand")&G3D_UP)
+                    {
+                        selectuse();
+                    }
+                }
+                break;
+            }
+               
+            case MENU_BUY:
+            {
+                s_sprintfd(info)("buying from: %s", name);
+                g.tab(info, 0xFFFFFF);
+                loopinventorytype(IF_TRADE)
+                {
+                    int price = o->s_worth;
+                    s_sprintfd(info)("%s (%d)", o->name, price);
+                    int ret = g.button(info, 0xFFFFFF, "coins");
+                    if(ret&G3D_UP)
+                    {
+                        if(os.playerobj->s_gold>=price)
+                        {
+                            conoutf("\f2you bought %s for %d gold", o->name, price);
+                            os.playerobj->s_gold -= price;
+                            s_gold += price;
+                            o->decontain();
+                            os.playerobj->add(o, IF_INVENTORY);
+                        }
+                        else
+                        {
+                            conoutf("\f2you cannot afford this item!");
+                        }
+                    }
+                }
+                s_sprintf(info)("you have %d gold", os.playerobj->s_gold);
+                g.text(info, 0xAAAAAA, "info");   
+                if(g.button("done buying", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_DEFAULT;
+                break;
+            }
+               
+            case MENU_SELL:
+            {
+                s_sprintfd(info)("selling to: %s", name);
+                g.tab(info, 0xFFFFFF);
+                os.playerobj->invgui(g, this);
+                if(g.button("done selling", 0xFFFFFF, "coins")&G3D_UP) menuwhich = MENU_DEFAULT;
+                break;
+            }
+        }
+        g.end();
+    }
+    
+    void invgui(g3d_gui &g, rpgobj *buyer = NULL)
+    {
+        loopinventory()
+        {
+            int price = o->s_worth/2;
+            s_sprintfd(info)("%s (%d)", o->name, price);
+            int ret = g.button(info, 0xFFFFFF, "coins");
+            if(ret&G3D_UP)
+            {
+                if(buyer)
+                {
+                    if(price>buyer->s_gold)
+                    {
+                        conoutf("\f2%s cannot afford to buy %s from you!", buyer->name, o->name);                    
+                    }
+                    else
+                    {
+                        if(price)
+                        {
+                            conoutf("\f2you sold %s for %d gold", o->name, price);
+                            s_gold += price;
+                            buyer->s_gold -= price;
+                            o->decontain();
+                            buyer->add(o, IF_TRADE);                                            
+                        }
+                        else
+                        {
+                            conoutf("\f2you cannot sell %s", o->name);
+                        }
+                    }
+                }
+                else    // player wants to use this item
+                {
+                    o->selectuse();
+                }
+            }
+        }
+        s_sprintfd(info)("you have %d gold", os.playerobj->s_gold);
+        g.text(info, 0xAAAAAA, "info");   
+    }
+
+    void g3d_menu()
+    {
+        if(!menutime) return;
+        g3d_addgui(this, vec(ent->o).add(vec(0, 0, 2)));
+    }
+};
diff --git a/rpggame/rpgobjset.h b/rpggame/rpgobjset.h
new file mode 100644
index 0000000..63e4975
--- /dev/null
+++ b/rpggame/rpgobjset.h
@@ -0,0 +1,169 @@
+struct rpgobjset
+{
+    rpgclient &cl;
+
+    vector<rpgobj *> set, stack;
+    hashtable<char *, char *> names;
+    
+    rpgobj *pointingat;
+    rpgobj *playerobj;
+    rpgobj *selected;
+    
+    rpgquest *quests;
+    rpgquest *currentquest;
+    
+    rpgobjset(rpgclient &_cl) : cl(_cl), pointingat(NULL), playerobj(NULL), selected(NULL), quests(NULL), currentquest(NULL)
+    {
+        #define N(n) CCOMMAND(r_##n,     "i", (rpgobjset *self, int *val), { self->stack[0]->s_##n = *val; }); \
+                     CCOMMAND(r_get_##n, "",  (rpgobjset *self), { intret(self->stack[0]->s_##n); }); 
+                     
+        RPGNAMES 
+        #undef N
+        #define N(n) CCOMMAND(r_def_##n, "ii", (rpgobjset *self, int *i1, int *i2), { stats::def_##n(*i1, *i2); }); \
+                     CCOMMAND(r_eff_##n, "",   (rpgobjset *self), { intret(self->stack[0]->eff_##n()); }); 
+        RPGSTATNAMES 
+        #undef N
+        
+        CCOMMAND(r_model,       "s",   (rpgobjset *self, char *s), { self->stack[0]->model = self->stringpool(s); });    
+        CCOMMAND(r_spawn,       "s",   (rpgobjset *self, char *s), { self->spawn(self->stringpool(s)); });    
+        CCOMMAND(r_contain,     "s",   (rpgobjset *self, char *s), { self->stack[0]->decontain(); self->stack[1]->add(self->stack[0], atoi(s)); });    
+        CCOMMAND(r_pop,         "",    (rpgobjset *self), { self->popobj(); });    
+        CCOMMAND(r_swap,        "",    (rpgobjset *self), { swap(self->stack[0], self->stack[1]); });    
+        CCOMMAND(r_say,         "s",   (rpgobjset *self, char *s), { self->stack[0]->abovetext = self->stringpool(s); });    
+        CCOMMAND(r_quest,       "ss",  (rpgobjset *self, char *s, char *a), { self->stack[0]->addaction(self->stringpool(s), self->stringpool(a), true); });    
+        CCOMMAND(r_action,      "ss",  (rpgobjset *self, char *s, char *a), { self->stack[0]->addaction(self->stringpool(s), self->stringpool(a), false); });    
+        CCOMMAND(r_action_use,  "s",   (rpgobjset *self, char *s), { self->stack[0]->action_use.script = self->stringpool(s); });    
+        CCOMMAND(r_take,        "sss", (rpgobjset *self, char *name, char *ok, char *notok), { self->takefromplayer(name, ok, notok); });    
+        CCOMMAND(r_give,        "s",   (rpgobjset *self, char *s), { self->givetoplayer(s); });    
+        CCOMMAND(r_use,         "",    (rpgobjset *self), { self->stack[0]->selectuse(); });    
+        CCOMMAND(r_applydamage, "i",   (rpgobjset *self, int *d), { self->stack[0]->takedamage(*d, *self->stack[1]); });    
+        clearworld();
+    }
+    
+    void resetstack()
+    {
+        stack.setsize(0);
+        loopi(10) stack.add(playerobj);     // determines the stack depth    
+    }
+    
+    void clearworld()
+    {
+        if(playerobj) { playerobj->ent = NULL; delete playerobj; }
+        playerobj = new rpgobj("player", *this);
+        playerobj->ent = &cl.player1;
+        cl.player1.ro = playerobj;
+
+        pointingat = NULL;
+        set.deletecontentsp();
+        resetstack();
+        
+        playerobj->scriptinit();            // will fail when this is called from emptymap(), which is ok
+    }
+    
+    void removefromsystem(rpgobj *o)
+    {
+        removefromworld(o);
+        o->decontain();
+        if(pointingat==o) pointingat = NULL;
+        if(selected==o) selected = NULL;
+        resetstack();
+        DELETEP(o);
+    }
+    
+    void update(int curtime)
+    {
+        extern vec worldpos;
+        pointingat = NULL;
+        loopv(set)
+        {
+            set[i]->update(curtime);
+
+            float dist = cl.player1.o.dist(set[i]->ent->o);
+            if(dist<50 && intersect(set[i]->ent, cl.player1.o, worldpos) && (!pointingat || cl.player1.o.dist(pointingat->ent->o)>dist))    
+            {    
+                pointingat = set[i]; 
+            }
+        }
+    }
+    
+    void spawn(char *name)
+    {
+        rpgobj *o = new rpgobj(name, *this);
+        pushobj(o);
+        o->scriptinit();
+    }
+    
+    void placeinworld(vec &pos, float yaw)
+    {
+        stack[0]->placeinworld(new rpgent(stack[0], cl, pos, yaw));
+        set.add(stack[0]);
+    }
+    
+    void pushobj(rpgobj *o) { stack.pop(); stack.insert(0, o); }       // never overflows, just removes bottom
+    void popobj()           { stack.add(stack.remove(0)); }            // never underflows, just puts it at the bottom
+    
+    void removefromworld(rpgobj *worldobj)
+    {
+        set.removeobj(worldobj);
+        DELETEP(worldobj->ent);    
+    }
+    
+    void take(rpgobj *worldobj, rpgobj *newowner)
+    {
+        removefromworld(worldobj);
+        newowner->add(worldobj, false);
+    }
+    
+    void takefromplayer(char *name, char *ok, char *notok)
+    {
+        rpgobj *o = playerobj->take(name);
+        if(o)
+        {
+            stack[0]->add(o, false);
+            conoutf("\f2you hand over a %s", o->name);
+            if(currentquest)
+            {
+                conoutf("\f2you finish a quest for %s", currentquest->npc); 
+                currentquest->completed = true;           
+            }
+        }
+        execute(o ? ok : notok);
+    }
+    
+    void givetoplayer(char *name)
+    {
+        rpgobj *o = stack[0]->take(name);
+        if(o)
+        {
+            conoutf("\f2you receive a %s", o->name);
+            playerobj->add(o, false);
+        }
+    }
+    
+    void addquest(rpgaction *a, const char *questline, const char *npc)
+    {
+        a->q = quests = new rpgquest(quests, npc, questline);
+        conoutf("\f2you have accepted a quest for %s", npc);
+    }
+    
+    void listquests(bool completed, g3d_gui &g)
+    {
+        for(rpgquest *q = quests; q; q = q->next) if(q->completed==completed)
+        {
+            s_sprintfd(info)("%s: %s", q->npc, q->questline);
+            g.text(info, 0xAAAAAA, "info");
+        }
+    }
+    
+    char *stringpool(char *name)
+    {
+        char **n = names.access(name);
+        if(n) return *n;
+        name = newstring(name);
+        names[name] = name;
+        return name;
+    }
+    
+    void render()       { loopv(set) set[i]->render();   }
+    void g3d_npcmenus() { loopv(set) set[i]->g3d_menu(); } 
+};
diff --git a/rpggame/stats.h b/rpggame/stats.h
new file mode 100644
index 0000000..53646df
--- /dev/null
+++ b/rpggame/stats.h
@@ -0,0 +1,131 @@
+
+// all of these stats are the total "points" an object has
+// points are converted into efficiency = log(points/pointscale+1)*percentscale+100
+// efficiency is by default 100% and rises logarithmically from that, according to the two scale vars, which are set in script
+// efficiency is used with a base value, i.e. a sword that does 10 damage used by a player with 150% melee efficiency does 15 damage
+
+// with this define, we can uses these names to define vars, strings, functions etc
+
+// see rpg.html for detailed explanation as to their meaning
+
+#define RPGSTATNAMES \
+    N(melee) \
+    N(ranged) \
+    N(magic) \
+    \
+    N(hpregen) \
+    N(manaregen) \
+    \
+    N(maxhp) \
+    N(maxmana) \
+    \
+    N(attackspeed) \
+    N(movespeed) \
+    N(jumpheight) \
+    N(tradeskill) \
+    N(feared) \
+    N(stealth) \
+    N(hostility) \
+    \
+    N(stata) \
+    N(statb) \
+    N(statc) \
+
+
+#define RPGATTRNAMES \
+    N(ai) \
+    N(hp) \
+    N(mana) \
+    N(gold) \
+    N(worth) \
+    N(useamount) \
+    N(usetype) \
+    N(damage) \
+    N(maxrange) \
+    N(maxangle) \
+    N(attackrate) \
+    N(manacost) \
+    N(effect) \
+    \
+    N(attra) \
+    N(attrb) \
+    N(attrc) \
+    \
+    N(usesound)
+   
+   
+#define RPGNAMES RPGSTATNAMES RPGATTRNAMES
+
+struct rpgobj;
+
+struct stats
+{
+    #define N(n) static int pointscale_##n, percentscale_##n; \
+                 static void def_##n(int a, int b) { pointscale_##n = a; percentscale_##n = b; } \
+                 int eff_##n() { return int(logf(s_##n/pointscale_##n+1)*percentscale_##n)+100; }
+    RPGSTATNAMES 
+    #undef N
+    #define N(n) int s_##n;
+    RPGNAMES
+    #undef N
+    
+    int statupdatetime;
+        
+    stats() : statupdatetime(0)
+    {
+        st_reset();
+        #define N(n) s_##n = 0;
+        RPGATTRNAMES 
+        #undef N
+    }
+    
+    void st_reset()
+    {
+        #define N(n) s_##n = 0;
+        RPGSTATNAMES 
+        #undef N
+    }
+    
+    void st_accumulate(rpgobj &o)
+    {
+        #define N(n) s_##n += o.s_##n;
+        RPGSTATNAMES 
+        #undef N
+    }
+    
+    void st_show(g3d_gui &g)
+    {
+        #define N(n) if(s_##n) { s_sprintfd(s)(#n ": %d => %d%%", s_##n, eff_##n()); g.text(s, 0xFFFFFF, "info"); }
+        RPGSTATNAMES 
+        #undef N
+        #define N(n) if(s_##n) { s_sprintfd(s)(#n ": %d", s_##n); g.text(s, 0xFFAAAA, "info"); }
+        RPGATTRNAMES 
+        #undef N
+    }
+    
+    void st_init()
+    {
+        s_hp   = eff_maxhp();
+        s_mana = eff_maxmana();
+    }
+    
+    void st_respawn()   // player only
+    {
+        s_hp = 10;
+    }
+    
+    void st_update(int lastmillis)
+    {
+        if(lastmillis-statupdatetime>1000)
+        {
+            statupdatetime += 1000;
+            const int base_hp_regen_rate = 2, base_mana_regen_rate = 3;     // in script?
+            s_hp   += eff_hpregen()  *base_hp_regen_rate  /100;
+            s_mana += eff_manaregen()*base_mana_regen_rate/100;
+            if(s_hp  >eff_maxhp())   s_hp   = eff_maxhp();
+            if(s_mana>eff_maxmana()) s_mana = eff_maxmana();
+        }
+    }
+};
+
+
diff --git a/rpggame/stubs.h b/rpggame/stubs.h
new file mode 100644
index 0000000..a926896
--- /dev/null
+++ b/rpggame/stubs.h
@@ -0,0 +1,38 @@
+struct rpgdummycom : iclientcom
+{
+    ~rpgdummycom() {}
+
+    void gamedisconnect() {}
+    void parsepacketclient(int chan, ucharbuf &p) {}
+    int sendpacketclient(ucharbuf &p, bool &reliable, dynent *d) { return -1; }
+    void gameconnect(bool _remote) {}
+    bool allowedittoggle() { return true; }
+    void writeclientinfo(FILE *f) {}
+    void toserver(char *text) {}
+    void changemap(const char *name) { load_world(name); }
+};
+
+struct rpgdummyserver : igameserver
+{
+    ~rpgdummyserver() {}
+
+    void *newinfo() { return NULL; }
+    void deleteinfo(void *ci) {}
+    void serverinit() {}
+    void clientdisconnect(int n) {}
+    int clientconnect(int n, uint ip) { return DISC_NONE; }
+    void localdisconnect(int n) {}
+    void localconnect(int n) {}
+    const char *servername() { return "foo"; }
+    void parsepacket(int sender, int chan, bool reliable, ucharbuf &p) {}
+    bool sendpackets() { return false; }
+    int welcomepacket(ucharbuf &p, int n, ENetPacket *packet) { return -1; }
+    void serverinforeply(ucharbuf &q, ucharbuf &p) {}
+    void serverupdate(int lastmillis, int totalmillis) {}
+    bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np) { return false; }
+    void serverinfostr(char *buf, const char *name, const char *desc, const char *map, int ping, const vector<int> &attr, int np) {}
+    int serverinfoport() { return 0; }
+    int serverport() { return 0; }
+    const char *getdefaultmaster() { return "localhost"; }
+    void sendservmsg(const char *s) {}
+};
diff --git a/shared/command.h b/shared/command.h
index 0c620ab..6baeacd 100644
--- a/shared/command.h
+++ b/shared/command.h
@@ -5,7 +5,7 @@ enum { ID_VAR, ID_FVAR, ID_SVAR, ID_COMMAND, ID_CCOMMAND, ID_ALIAS };
 
 enum { NO_OVERRIDE = INT_MAX, OVERRIDDEN = 0 };
 
-enum { IDF_PERSIST = 1<<0, IDF_OVERRIDE = 1<<1, IDF_HEX = 1<<2, IDF_READONLY = 1<<3 };
+enum { IDF_PERSIST = 1<<0, IDF_OVERRIDE = 1<<1 };
 
 struct identstack
 {
@@ -31,16 +31,7 @@ struct ident
 {
     int type;           // one of ID_* above
     const char *name;
-    union
-    {
-        int minval;    // ID_VAR
-        float minvalf; // ID_FVAR
-    };
-    union
-    {
-        int maxval;    // ID_VAR
-        float maxvalf; // ID_FVAR
-    };
+    int minval, maxval; // ID_VAR
     int override;       // either NO_OVERRIDE, OVERRIDDEN, or value
     union
     {
@@ -65,11 +56,11 @@ struct ident
     ident() {}
     // ID_VAR
     ident(int t, const char *n, int m, int c, int x, int *s, void *f = NULL, int flags = 0)
-        : type(t), name(n), minval(m), maxval(x), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags | (m > x ? IDF_READONLY : 0))
+        : type(t), name(n), minval(m), maxval(x), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags)
     { val.i = c; storage.i = s; }
     // ID_FVAR
-    ident(int t, const char *n, float m, float c, float x, float *s, void *f = NULL, int flags = 0)
-        : type(t), name(n), minvalf(m), maxvalf(x), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags | (m > x ? IDF_READONLY : 0))
+    ident(int t, const char *n, float c, float *s, void *f = NULL, int flags = 0)
+        : type(t), name(n), override(NO_OVERRIDE), fun((void (__cdecl *)())f), flags(flags)
     { val.f = c; storage.f = s; }
     // ID_SVAR
     ident(int t, const char *n, char *c, char **s, void *f = NULL, int flags = 0)
@@ -91,8 +82,6 @@ struct ident
 
 extern void addident(const char *name, ident *id);
 extern void intret(int v);
-extern const char *floatstr(float v);
-extern void floatret(float v);
 extern void result(const char *s);
 
 // nasty macros for registering script functions, abuses globals to avoid excessive infrastructure
@@ -112,31 +101,18 @@ extern void result(const char *s);
 #define VARFP(name, min, cur, max, body) _VARF(name, name, min, cur, max, body, IDF_PERSIST)
 #define VARFR(name, min, cur, max, body) _VARF(name, name, min, cur, max, body, IDF_OVERRIDE)
 
-#define _HVAR(name, global, min, cur, max, persist)  int global = variable(#name, min, cur, max, &global, NULL, persist | IDF_HEX)
-#define HVARN(name, global, min, cur, max) _HVAR(name, global, min, cur, max, 0)
-#define HVARNP(name, global, min, cur, max) _HVAR(name, global, min, cur, max, IDF_PERSIST)
-#define HVARNR(name, global, min, cur, max) _HVAR(name, global, min, cur, max, IDF_OVERRIDE)
-#define HVAR(name, min, cur, max) _HVAR(name, name, min, cur, max, 0)
-#define HVARP(name, min, cur, max) _HVAR(name, name, min, cur, max, IDF_PERSIST)
-#define HVARR(name, min, cur, max) _HVAR(name, name, min, cur, max, IDF_OVERRIDE)
-#define _HVARF(name, global, min, cur, max, body, persist)  void var_##name(); int global = variable(#name, min, cur, max, &global, var_##name, persist | IDF_HEX); void var_##name() { body; }
-#define HVARFN(name, global, min, cur, max, body) _HVARF(name, global, min, cur, max, body, 0)
-#define HVARF(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, 0)
-#define HVARFP(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, IDF_PERSIST)
-#define HVARFR(name, min, cur, max, body) _HVARF(name, name, min, cur, max, body, IDF_OVERRIDE)
-
-#define _FVAR(name, global, min, cur, max, persist) float global = fvariable(#name, min, cur, max, &global, NULL, persist)
-#define FVARN(name, global, min, cur, max) _FVAR(name, global, min, cur, max, 0)
-#define FVARNP(name, global, min, cur, max) _FVAR(name, global, min, cur, max, IDF_PERSIST)
-#define FVARNR(name, global, min, cur, max) _FVAR(name, global, min, cur, max, IDF_OVERRIDE)
-#define FVAR(name, min, cur, max) _FVAR(name, name, min, cur, max, 0)
-#define FVARP(name, min, cur, max) _FVAR(name, name, min, cur, max, IDF_PERSIST)
-#define FVARR(name, min, cur, max) _FVAR(name, name, min, cur, max, IDF_OVERRIDE)
-#define _FVARF(name, global, min, cur, max, body, persist) void var_##name(); float global = fvariable(#name, min, cur, max, &global, var_##name, persist); void var_##name() { body; }
-#define FVARFN(name, global, min, cur, max, body) _FVARF(name, global, min, cur, max, body, 0)
-#define FVARF(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, 0)
-#define FVARFP(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, IDF_PERSIST)
-#define FVARFR(name, min, cur, max, body) _FVARF(name, name, min, cur, max, body, IDF_OVERRIDE)
+#define _FVAR(name, global, cur, persist) float global = fvariable(#name, cur, &global, NULL, persist)
+#define FVARN(name, global, cur) _FVAR(name, global, cur, 0)
+#define FVARNP(name, global, cur) _FVAR(name, global, cur, IDF_PERSIST)
+#define FVARNR(name, global, cur) _FVAR(name, global, cur, IDF_OVERRIDE)
+#define FVAR(name, cur) _FVAR(name, name, cur, 0)
+#define FVARP(name, cur) _FVAR(name, name, cur, IDF_PERSIST)
+#define FVARR(name, cur) _FVAR(name, name, cur, IDF_OVERRIDE)
+#define _FVARF(name, global, cur, body, persist) void var_##name(); float global = fvariable(#name, cur, &global, var_##name, persist); void var_##name() { body; }
+#define FVARFN(name, global, cur, body) _FVARF(name, global, cur, body, 0)
+#define FVARF(name, cur, body) _FVARF(name, name, cur, body, 0)
+#define FVARFP(name, cur, body) _FVARF(name, name, cur, body, IDF_PERSIST)
+#define FVARFR(name, cur, body) _FVARF(name, name, cur, body, IDF_OVERRIDE)
 
 #define _SVAR(name, global, cur, persist) char *global = svariable(#name, cur, &global, NULL, persist)
 #define SVARN(name, global, cur) _SVAR(name, global, cur, 0)
@@ -152,22 +128,17 @@ extern void result(const char *s);
 #define SVARFR(name, cur, body) _SVARF(name, name, cur, body, IDF_OVERRIDE)
 
 // new style macros, have the body inline, and allow binds to happen anywhere, even inside class constructors, and access the surrounding class
-#define _CCOMMAND(idtype, tv, n, g, proto, b) \
-    struct _ccmd_##n : ident \
+#define _COMMAND(idtype, tv, n, g, proto, b) \
+    struct cmd_##n : ident \
     { \
-        _ccmd_##n(void *self = NULL) : ident(idtype, #n, g, (void *)run, self) \
+        cmd_##n(void *self = NULL) : ident(idtype, #n, g, (void *)run, self) \
         { \
             addident(name, this); \
         } \
         static void run proto { b; } \
-    } __ccmd_##n tv
-#define CCOMMAND(n, g, proto, b) _CCOMMAND(ID_CCOMMAND, (this), n, g, proto, b)
-
-// anonymous inline commands, uses nasty template trick with line numbers to keep names unique
-#define _ICOMMAND(cmdname, name, nargs, proto, b) template<int N> struct cmdname; template<> struct cmdname<__LINE__> { static bool init; static void run proto; }; bool cmdname<__LINE__>::init = addcommand(#name, (void (*)())cmdname<__LINE__>::run, nargs); void cmdname<__LINE__>::run proto \
-    { b; }
-#define ICOMMANDNAME(name) _icmd_##name
-#define ICOMMAND(name, nargs, proto, b) _ICOMMAND(ICOMMANDNAME(name), name, nargs, proto, b)
+    } icom_##n tv
+#define ICOMMAND(n, g, proto, b) _COMMAND(ID_COMMAND, , n, g, proto, b)
+#define CCOMMAND(n, g, proto, b) _COMMAND(ID_CCOMMAND, (this), n, g, proto, b)
  
 #define _IVAR(n, m, c, x, b, p) \
     struct var_##n : ident \
@@ -185,4 +156,5 @@ extern void result(const char *s);
 #define IVARR(n, m, c, x)  _IVAR(n, m, c, x, , IDF_OVERRIDE)
 #define IVARFP(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_PERSIST)
 #define IVARFR(n, m, c, x, b) _IVAR(n, m, c, x, void changed() { b; }, IDF_OVERRIDE)
-
+//#define ICALL(n, a) { char *args[] = a; icom_##n.run(args); }
+//
diff --git a/shared/crypto.cpp b/shared/crypto.cpp
deleted file mode 100644
index 4f967e0..0000000
--- a/shared/crypto.cpp
+++ /dev/null
@@ -1,820 +0,0 @@
-#include "cube.h"
-
-///////////////////////// cryptography /////////////////////////////////
-
-/* Based off the reference implementation of Tiger, a cryptographically
- * secure 192 bit hash function by Ross Anderson and Eli Biham. More info at:
- * http://www.cs.technion.ac.il/~biham/Reports/Tiger/
- */
-
-#define TIGER_PASSES 3
-
-namespace tiger
-{
-    typedef unsigned long long int chunk;
-
-    union hashval
-    {
-        uchar bytes[3*8];
-        chunk chunks[3];
-    };
-
-    chunk sboxes[4*256];
-
-    void compress(const chunk *str, chunk state[3])
-    {
-        chunk a, b, c;
-        chunk aa, bb, cc;
-        chunk x0, x1, x2, x3, x4, x5, x6, x7;
-
-        a = state[0];
-        b = state[1];
-        c = state[2];
-
-        x0=str[0]; x1=str[1]; x2=str[2]; x3=str[3];
-        x4=str[4]; x5=str[5]; x6=str[6]; x7=str[7];
-
-        aa = a;
-        bb = b;
-        cc = c;
-
-        loop(pass_no, TIGER_PASSES)
-        {
-            if(pass_no)
-            {
-                x0 -= x7 ^ 0xA5A5A5A5A5A5A5A5ULL; x1 ^= x0; x2 += x1; x3 -= x2 ^ ((~x1)<<19);
-                x4 ^= x3; x5 += x4; x6 -= x5 ^ ((~x4)>>23); x7 ^= x6;
-                x0 += x7; x1 -= x0 ^ ((~x7)<<19); x2 ^= x1; x3 += x2;
-                x4 -= x3 ^ ((~x2)>>23); x5 ^= x4; x6 += x5; x7 -= x6 ^ 0x0123456789ABCDEFULL;
-            }
-
-#define sb1 (sboxes)
-#define sb2 (sboxes+256)
-#define sb3 (sboxes+256*2)
-#define sb4 (sboxes+256*3)
-
-#define round(a, b, c, x) \
-      c ^= x; \
-      a -= sb1[((c)>>(0*8))&0xFF] ^ sb2[((c)>>(2*8))&0xFF] ^ \
-       sb3[((c)>>(4*8))&0xFF] ^ sb4[((c)>>(6*8))&0xFF] ; \
-      b += sb4[((c)>>(1*8))&0xFF] ^ sb3[((c)>>(3*8))&0xFF] ^ \
-       sb2[((c)>>(5*8))&0xFF] ^ sb1[((c)>>(7*8))&0xFF] ; \
-      b *= mul;
-
-            uint mul = !pass_no ? 5 : (pass_no==1 ? 7 : 9);
-            round(a, b, c, x0) round(b, c, a, x1) round(c, a, b, x2) round(a, b, c, x3)
-            round(b, c, a, x4) round(c, a, b, x5) round(a, b, c, x6) round(b, c, a, x7)
-
-            chunk tmp = a; a = c; c = b; b = tmp;
-        }
-
-        a ^= aa;
-        b -= bb;
-        c += cc;
-
-        state[0] = a;
-        state[1] = b;
-        state[2] = c;
-    }
-
-    void gensboxes()
-    {
-        const char *str = "Tiger - A Fast New Hash Function, by Ross Anderson and Eli Biham";
-        chunk state[3] = { 0x0123456789ABCDEFULL, 0xFEDCBA9876543210ULL, 0xF096A5B4C3B2E187ULL };
-        uchar temp[64];
-
-        if(!*(const uchar *)&islittleendian) loopj(64) temp[j^7] = str[j];
-        else loopj(64) temp[j] = str[j];
-        loopi(1024) loop(col, 8) ((uchar *)&sboxes[i])[col] = i&0xFF;
-
-        int abc = 2;
-        loop(pass, 5) loopi(256) for(int sb = 0; sb < 1024; sb += 256)
-        {
-            abc++;
-            if(abc >= 3) { abc = 0; compress((chunk *)temp, state); }
-            loop(col, 8)
-            {
-                uchar val = ((uchar *)&sboxes[sb+i])[col];
-                ((uchar *)&sboxes[sb+i])[col] = ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col];
-                ((uchar *)&sboxes[sb + ((uchar *)&state[abc])[col]])[col] = val;
-            }
-        }
-    }
-
-    void hash(const uchar *str, int length, hashval &val)
-    {
-        static bool init = false;
-        if(!init) { gensboxes(); init = true; }
-
-        uchar temp[64];
-
-        val.chunks[0] = 0x0123456789ABCDEFULL;
-        val.chunks[1] = 0xFEDCBA9876543210ULL;
-        val.chunks[2] = 0xF096A5B4C3B2E187ULL;
-
-        int i = length;
-        for(; i >= 64; i -= 64, str += 64)
-        {
-            if(!*(const uchar *)&islittleendian)
-            {
-                loopj(64) temp[j^7] = str[j];
-                compress((chunk *)temp, val.chunks);
-            }
-            else compress((chunk *)str, val.chunks);
-        }
-
-        int j;
-        if(!*(const uchar *)&islittleendian)
-        {
-            for(j = 0; j < i; j++) temp[j^7] = str[j];
-            temp[j^7] = 0x01;
-            while(++j&7) temp[j^7] = 0;
-        }
-        else
-        {
-            for(j = 0; j < i; j++) temp[j] = str[j];
-            temp[j] = 0x01;
-            while(++j&7) temp[j] = 0;
-        }
-
-        if(j > 56)
-        {
-            while(j < 64) temp[j++] = 0;
-            compress((chunk *)temp, val.chunks);
-            j = 0;
-        }
-        while(j < 56) temp[j++] = 0;
-        *(chunk *)(temp+56) = (chunk)length<<3;
-        compress((chunk *)temp, val.chunks);
-    }
-}
-
-/* Elliptic curve cryptography based on NIST DSS prime curves. */
-
-#define BI_DIGIT_BITS 16
-#define BI_DIGIT_MASK ((1<<BI_DIGIT_BITS)-1)
-
-template<int BI_DIGITS> struct bigint
-{
-    typedef ushort digit;
-    typedef uint dbldigit;
-
-    int len;
-    digit digits[BI_DIGITS];
-
-    bigint() {}
-    bigint(digit n) { if(n) { len = 1; digits[0] = n; } else len = 0; }
-    bigint(const char *s) { parse(s); }
-    template<int Y_DIGITS> bigint(const bigint<Y_DIGITS> &y) { *this = y; }
-
-    static int parsedigits(ushort *digits, int maxlen, const char *s)
-    {
-        int slen = 0;
-        while(isxdigit(s[slen])) slen++;
-        int len = (slen+2*sizeof(ushort)-1)/(2*sizeof(ushort));
-        if(len>maxlen) return 0;
-        memset(digits, 0, len*sizeof(ushort));
-        loopi(slen)
-        {
-            int c = s[slen-i-1];
-            if(isalpha(c)) c = toupper(c) - 'A' + 10;
-            else if(isdigit(c)) c -= '0';
-            else return 0;
-            digits[i/(2*sizeof(ushort))] |= c<<(4*(i%(2*sizeof(ushort))));
-        }
-        return len;
-    }
-
-    void parse(const char *s)
-    {
-        len = parsedigits(digits, BI_DIGITS, s);
-        shrink();
-    }
-
-    void zero() { len = 0; }
-
-    void print(stream *out) const
-    {
-        vector<char> buf;
-        printdigits(buf);
-        out->write(buf.getbuf(), buf.length());
-    }
-
-    void printdigits(vector<char> &buf) const
-    {
-        loopi(len)
-        {
-            digit d = digits[len-i-1];
-            loopj(BI_DIGIT_BITS/4)
-            {
-                uint shift = BI_DIGIT_BITS - (j+1)*4;
-                int val = (d >> shift) & 0xF;
-                if(val < 10) buf.add('0' + val);
-                else buf.add('a' + val - 10);
-            }
-        }
-    }
-
-    template<int Y_DIGITS> bigint &operator=(const bigint<Y_DIGITS> &y)
-    {
-        len = y.len;
-        memcpy(digits, y.digits, len*sizeof(digit));
-        return *this;
-    }
-
-    bool iszero() const { return !len; }
-    bool isone() const { return len==1 && digits[0]==1; }
-
-    int numbits() const
-    {
-        if(!len) return 0;
-        int bits = len*BI_DIGIT_BITS;
-        digit last = digits[len-1], mask = 1<<(BI_DIGIT_BITS-1);
-        while(mask)
-        {
-            if(last&mask) return bits;
-            bits--;
-            mask >>= 1;
-        }
-        return 0;
-    }
-
-    bool hasbit(int n) const { return n/BI_DIGIT_BITS < len && ((digits[n/BI_DIGIT_BITS]>>(n%BI_DIGIT_BITS))&1); }
-
-    template<int X_DIGITS, int Y_DIGITS> bigint &add(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        dbldigit carry = 0;
-        int maxlen = max(x.len, y.len), i;
-        for(i = 0; i < y.len || carry; i++)
-        {
-             carry += (i < x.len ? (dbldigit)x.digits[i] : 0) + (i < y.len ? (dbldigit)y.digits[i] : 0);
-             digits[i] = (digit)carry;
-             carry >>= BI_DIGIT_BITS;
-        }
-        if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit));
-        len = max(i, maxlen);
-        return *this;
-    }
-    template<int Y_DIGITS> bigint &add(const bigint<Y_DIGITS> &y) { return add(*this, y); }
-
-    template<int X_DIGITS, int Y_DIGITS> bigint &sub(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        ASSERT(x >= y);
-        dbldigit borrow = 0;
-        int i;
-        for(i = 0; i < y.len || borrow; i++)
-        {
-             borrow = (1<<BI_DIGIT_BITS) + (dbldigit)x.digits[i] - (i<y.len ? (dbldigit)y.digits[i] : 0) - borrow;
-             digits[i] = (digit)borrow;
-             borrow = (borrow>>BI_DIGIT_BITS)^1;
-        }
-        if(i < x.len && this != &x) memcpy(&digits[i], &x.digits[i], (x.len - i)*sizeof(digit));
-        len = x.len;
-        shrink();
-        return *this;
-    }
-    template<int Y_DIGITS> bigint &sub(const bigint<Y_DIGITS> &y) { return sub(*this, y); }
-
-    void shrink() { while(len && !digits[len-1]) len--; }
-
-    template<int X_DIGITS, int Y_DIGITS> bigint &mul(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        if(!x.len || !y.len) { len = 0; return *this; }
-        memset(digits, 0, y.len*sizeof(digit));
-        loopi(x.len)
-        {
-            dbldigit carry = 0;
-            loopj(y.len)
-            {
-                carry += (dbldigit)x.digits[i] * (dbldigit)y.digits[j] + (dbldigit)digits[i+j];
-                digits[i+j] = (digit)carry;
-                carry >>= BI_DIGIT_BITS;
-            }
-            digits[i+y.len] = carry;
-        }
-        len = x.len + y.len;
-        shrink();
-        return *this;
-    }
-
-    template<int X_DIGITS> bigint &rshift(const bigint<X_DIGITS> &x, int n)
-    {
-        if(!len || !n) return *this;
-        int dig = (n-1)/BI_DIGIT_BITS;
-        n = ((n-1) % BI_DIGIT_BITS)+1;
-        digit carry = digit(x.digits[dig]>>n);
-        loopi(len-dig-1)
-        {
-            digit tmp = x.digits[i+dig+1];
-            digits[i] = digit((tmp<<(BI_DIGIT_BITS-n)) | carry);
-            carry = digit(tmp>>n);
-        }
-        digits[len-dig-1] = carry;
-        len -= dig + (n>>BI_DIGIT_BITS);
-        shrink();
-        return *this;
-    }
-    bigint &rshift(int n) { return rshift(*this, n); }
-
-    template<int X_DIGITS> bigint &lshift(const bigint<X_DIGITS> &x, int n)
-    {
-        if(!len || !n) return *this;
-        int dig = n/BI_DIGIT_BITS;
-        n %= BI_DIGIT_BITS;
-        digit carry = 0;
-        for(int i = len-1; i>=0; i--)
-        {
-            digit tmp = x.digits[i];
-            digits[i+dig] = digit((tmp<<n) | carry);
-            carry = digit(tmp>>(BI_DIGIT_BITS-n));
-        }
-        len += dig;
-        if(carry) digits[len++] = carry;
-        if(dig) memset(digits, 0, dig*sizeof(digit));
-        return *this;
-    }
-    bigint &lshift(int n) { return lshift(*this, n); }
-
-    template<int Y_DIGITS> bool operator==(const bigint<Y_DIGITS> &y) const
-    {
-        if(len!=y.len) return false;
-        for(int i = len-1; i>=0; i--) if(digits[i]!=y.digits[i]) return false;
-        return true;
-    }
-    template<int Y_DIGITS> bool operator!=(const bigint<Y_DIGITS> &y) const { return !(*this==y); }
-    template<int Y_DIGITS> bool operator<(const bigint<Y_DIGITS> &y) const
-    {
-        if(len<y.len) return true;
-        if(len>y.len) return false;
-        for(int i = len-1; i>=0; i--)
-        {
-            if(digits[i]<y.digits[i]) return true;
-            if(digits[i]>y.digits[i]) return false;
-        }
-        return false;
-    }
-    template<int Y_DIGITS> bool operator>(const bigint<Y_DIGITS> &y) const { return y<*this; }
-    template<int Y_DIGITS> bool operator<=(const bigint<Y_DIGITS> &y) const { return !(y<*this); }
-    template<int Y_DIGITS> bool operator>=(const bigint<Y_DIGITS> &y) const { return !(*this<y); }
-};
-
-#define GF_BITS         192
-#define GF_DIGITS       ((GF_BITS+BI_DIGIT_BITS-1)/BI_DIGIT_BITS)
-
-typedef bigint<GF_DIGITS+1> gfint;
-
-/* NIST prime Galois fields.
- * Currently only supports NIST P-192, where P=2^192-2^64-1.
- */
-struct gfield : gfint
-{
-    static const gfield P;
-
-    gfield() {}
-    gfield(digit n) : gfint(n) {}
-    gfield(const char *s) : gfint(s) {}
-
-    template<int Y_DIGITS> gfield(const bigint<Y_DIGITS> &y) : gfint(y) {}
-
-    template<int Y_DIGITS> gfield &operator=(const bigint<Y_DIGITS> &y)
-    {
-        gfint::operator=(y);
-        return *this;
-    }
-
-    template<int X_DIGITS, int Y_DIGITS> gfield &add(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        gfint::add(x, y);
-        if(*this >= P) gfint::sub(*this, P);
-        return *this;
-    }
-    template<int Y_DIGITS> gfield &add(const bigint<Y_DIGITS> &y) { return add(*this, y); }
-
-    template<int X_DIGITS> gfield &mul2(const bigint<X_DIGITS> &x) { return add(x, x); }
-    gfield &mul2() { return mul2(*this); }
-
-    template<int X_DIGITS> gfield &div2(const bigint<X_DIGITS> &x)
-    {
-        if(hasbit(0)) { gfint::add(x, P); rshift(1); }
-        else rshift(x, 1);
-        return *this;
-    }
-    gfield &div2() { return div2(*this); }
-
-    template<int X_DIGITS, int Y_DIGITS> gfield &sub(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        if(x < y)
-        {
-            gfint tmp; /* necessary if this==&y, using this instead would clobber y */
-            tmp.add(x, P);
-            gfint::sub(tmp, y);
-        }
-        else gfint::sub(x, y);
-        return *this;
-    }
-    template<int Y_DIGITS> gfield &sub(const bigint<Y_DIGITS> &y) { return sub(*this, y); }
-
-    template<int X_DIGITS> gfield &neg(const bigint<X_DIGITS> &x)
-    {
-        gfint::sub(P, x);
-        return *this;
-    }
-    gfield &neg() { return neg(*this); }
-
-    template<int X_DIGITS> gfield &square(const bigint<X_DIGITS> &x) { return mul(x, x); }
-    gfield &square() { return square(*this); }
-
-    template<int X_DIGITS, int Y_DIGITS> gfield &mul(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        bigint<X_DIGITS+Y_DIGITS> result;
-        result.mul(x, y);
-        reduce(result);
-        return *this;
-    }
-    template<int Y_DIGITS> gfield &mul(const bigint<Y_DIGITS> &y) { return mul(*this, y); }
-
-    template<int RESULT_DIGITS> void reduce(const bigint<RESULT_DIGITS> &result)
-    {
-#if GF_BITS==192
-        len = min(result.len, GF_DIGITS);
-        memcpy(digits, result.digits, len*sizeof(digit));
-        shrink();
-
-        if(result.len > 192/BI_DIGIT_BITS)
-        {
-            gfield s;
-            memcpy(s.digits, &result.digits[192/BI_DIGIT_BITS], min(result.len-192/BI_DIGIT_BITS, 64/BI_DIGIT_BITS)*sizeof(digit));
-            if(result.len < 256/BI_DIGIT_BITS) memset(&s.digits[result.len-192/BI_DIGIT_BITS], 0, (256/BI_DIGIT_BITS-result.len)*sizeof(digit));
-            memcpy(&s.digits[64/BI_DIGIT_BITS], s.digits, 64/BI_DIGIT_BITS*sizeof(digit));
-            s.len = 128/BI_DIGIT_BITS;
-            s.shrink();
-            add(s);
-
-            if(result.len > 256/BI_DIGIT_BITS)
-            {
-                memset(s.digits, 0, 64/BI_DIGIT_BITS*sizeof(digit));
-                memcpy(&s.digits[64/BI_DIGIT_BITS], &result.digits[256/BI_DIGIT_BITS], min(result.len-256/BI_DIGIT_BITS, 64/BI_DIGIT_BITS)*sizeof(digit));
-                if(result.len < 320/BI_DIGIT_BITS) memset(&s.digits[result.len+(64-256)/BI_DIGIT_BITS], 0, (320/BI_DIGIT_BITS-result.len)*sizeof(digit));
-                memcpy(&s.digits[128/BI_DIGIT_BITS], &s.digits[64/BI_DIGIT_BITS], 64/BI_DIGIT_BITS*sizeof(digit));
-                s.len = GF_DIGITS;
-                s.shrink();
-                add(s);
-
-                if(result.len > 320/BI_DIGIT_BITS)
-                {
-                    memcpy(s.digits, &result.digits[320/BI_DIGIT_BITS], min(result.len-320/BI_DIGIT_BITS, 64/BI_DIGIT_BITS)*sizeof(digit));
-                    if(result.len < 384/BI_DIGIT_BITS) memset(&s.digits[result.len-320/BI_DIGIT_BITS], 0, (384/BI_DIGIT_BITS-result.len)*sizeof(digit));
-                    memcpy(&s.digits[64/BI_DIGIT_BITS], s.digits, 64/BI_DIGIT_BITS*sizeof(digit));
-                    memcpy(&s.digits[128/BI_DIGIT_BITS], s.digits, 64/BI_DIGIT_BITS*sizeof(digit));
-                    s.len = GF_DIGITS;
-                    s.shrink();
-                    add(s);
-                }
-            }
-        }
-        else if(*this >= P) gfint::sub(*this, P);
-#else
-#error Unsupported GF
-#endif
-    }
-
-    template<int X_DIGITS, int Y_DIGITS> gfield &pow(const bigint<X_DIGITS> &x, const bigint<Y_DIGITS> &y)
-    {
-        gfield a(x);
-        if(y.hasbit(0)) *this = a;
-        else
-        {
-            len = 1;
-            digits[0] = 1;
-            if(!y.len) return *this;
-        }
-        for(int i = 1, j = y.numbits(); i < j; i++)
-        {
-            a.square();
-            if(y.hasbit(i)) mul(a);
-        }
-        return *this;
-    }
-    template<int Y_DIGITS> gfield &pow(const bigint<Y_DIGITS> &y) { return pow(*this, y); }
-
-    bool invert(const gfield &x)
-    {
-        if(!x.len) return false;
-        gfint u(x), v(P), A((gfint::digit)1), C((gfint::digit)0);
-        while(!u.iszero())
-        {
-            int ushift = 0, ashift = 0;
-            while(!u.hasbit(ushift))
-            {
-                ushift++;
-                if(A.hasbit(ashift))
-                {
-                    if(ashift) { A.rshift(ashift); ashift = 0; }
-                    A.add(P);
-                }
-                ashift++;
-            }
-            if(ushift) u.rshift(ushift);
-            if(ashift) A.rshift(ashift);
-            int vshift = 0, cshift = 0;
-            while(!v.hasbit(vshift))
-            {
-                vshift++;
-                if(C.hasbit(cshift))
-                {
-                    if(cshift) { C.rshift(cshift); cshift = 0; }
-                    C.add(P);
-                }
-                cshift++;
-            }
-            if(vshift) v.rshift(vshift);
-            if(cshift) C.rshift(cshift);
-            if(u >= v)
-            {
-                u.sub(v);
-                if(A < C) A.add(P);
-                A.sub(C);
-            }
-            else
-            {
-                v.sub(v, u);
-                if(C < A) C.add(P);
-                C.sub(A);
-            }
-        }
-        if(C >= P) gfint::sub(C, P);
-        else { len = C.len; memcpy(digits, C.digits, len*sizeof(digit)); }
-        ASSERT(*this < P);
-        return true;
-    }
-    void invert() { invert(*this); }
-
-    template<int X_DIGITS> static int legendre(const bigint<X_DIGITS> &x)
-    {
-        static const gfint Psub1div2(gfint(P).sub(bigint<1>(1)).rshift(1));
-        gfield L;
-        L.pow(x, Psub1div2);
-        if(!L.len) return 0;
-        if(L.len==1) return 1;
-        return -1;
-    }
-    int legendre() const { return legendre(*this); }
-
-    bool sqrt(const gfield &x)
-    {
-        if(!x.len) { len = 0; return true; }
-#if GF_BITS==224
-#error Unsupported GF
-#else
-        ASSERT((P.digits[0]%4)==3);
-        static const gfint Padd1div4(gfint(P).add(bigint<1>(1)).rshift(2));
-        switch(legendre(x))
-        {
-            case 0: len = 0; return true;
-            case -1: return false;
-            default: pow(x, Padd1div4); return true;
-        }
-#endif
-    }
-    bool sqrt() { return sqrt(*this); }
-};
-
-struct ecjacobian
-{
-    static const gfield B;
-    static const ecjacobian base;
-    static const ecjacobian origin;
-
-    gfield x, y, z;
-
-    ecjacobian() {}
-    ecjacobian(const gfield &x, const gfield &y) : x(x), y(y), z(bigint<1>(1)) {}
-    ecjacobian(const gfield &x, const gfield &y, const gfield &z) : x(x), y(y), z(z) {}
-
-    void mul2()
-    {
-        if(z.iszero()) return;
-        else if(y.iszero()) { *this = origin; return; }
-        gfield a, b, c, d;
-        d.sub(x, c.square(z));
-        d.mul(c.add(x));
-        c.mul2(d).add(d);
-        z.mul(y).add(z);
-        a.square(y);
-        b.mul2(a);
-        d.mul2(x).mul(b);
-        x.square(c).sub(d).sub(d);
-        a.square(b).add(a);
-        y.sub(d, x).mul(c).sub(a);
-    }
-
-    void add(const ecjacobian &q)
-    {
-        if(q.z.iszero()) return;
-        else if(z.iszero()) { *this = q; return; }
-        gfield a, b, c, d, e, f;
-        a.square(z);
-        b.mul(q.y, a).mul(z);
-        a.mul(q.x);
-        if(q.z.isone())
-        {
-            c.add(x, a);
-            d.add(y, b);
-            a.sub(x, a);
-            b.sub(y, b);
-        }
-        else
-        {
-            f.mul(y, e.square(q.z)).mul(q.z);
-            e.mul(x);
-            c.add(e, a);
-            d.add(f, b);
-            a.sub(e, a);
-            b.sub(f, b);
-        }
-        if(a.iszero()) { if(b.iszero()) mul2(); else *this = origin; return; }
-        if(!q.z.isone()) z.mul(q.z);
-        z.mul(a);
-        x.square(b).sub(f.mul(c, e.square(a)));
-        y.sub(f, x).sub(x).mul(b).sub(e.mul(a).mul(d)).div2();
-    }
-
-    template<int Q_DIGITS> void mul(const ecjacobian &p, const bigint<Q_DIGITS> q)
-    {
-        *this = origin;
-        for(int i = q.numbits()-1; i >= 0; i--)
-        {
-            mul2();
-            if(q.hasbit(i)) add(p);
-        }
-    }
-    template<int Q_DIGITS> void mul(const bigint<Q_DIGITS> q) { ecjacobian tmp(*this); mul(tmp, q); }
-
-    void normalize()
-    {
-        if(z.iszero() || z.isone()) return;
-        gfield tmp;
-        z.invert();
-        tmp.square(z);
-        x.mul(tmp);
-        y.mul(tmp).mul(z);
-        z = bigint<1>(1);
-    }
-
-    bool calcy(bool ybit)
-    {
-        gfield y2, tmp;
-        y2.square(x).mul(x).sub(tmp.add(x, x).add(x)).add(B);
-        if(!y.sqrt(y2)) { y.zero(); return false; }
-        if(y.hasbit(0) != ybit) y.neg();
-        return true;
-    }
-
-    void print(vector<char> &buf)
-    {
-        normalize();
-        buf.add(y.hasbit(0) ? '-' : '+');
-        x.printdigits(buf);
-    }
-
-    void parse(const char *s)
-    {
-        bool ybit = *s++ == '-';
-        x.parse(s);
-        calcy(ybit);
-        z = bigint<1>(1);
-    }
-};
-
-const ecjacobian ecjacobian::origin(gfield((gfield::digit)1), gfield((gfield::digit)1), gfield((gfield::digit)0));
-
-#if GF_BITS==192
-const gfield gfield::P("fffffffffffffffffffffffffffffffeffffffffffffffff");
-const gfield ecjacobian::B("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1");
-const ecjacobian ecjacobian::base(
-    gfield("188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012"),
-    gfield("07192b95ffc8da78631011ed6b24cdd573f977a11e794811")
-);
-#elif GF_BITS==224
-const gfield gfield::P("ffffffffffffffffffffffffffffffff000000000000000000000001");
-const gfield ecjacobian::B("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4");
-const ecjacobian ecjacobian::base(
-    gfield("b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21"),
-    gfield("bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34"),
-);
-#elif GF_BITS==256
-const gfield gfield::P("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff");
-const gfield ecjacobian::B("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b");
-const ecjacobian ecjacobian::base(
-    gfield("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296"),
-    gfield("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5"),
-);
-#elif GF_BITS==384
-const gfield gfield::P("fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff");
-const gfield ecjacobian::B("b3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef");
-const ecjacobian ecjacobian::base(
-    gfield("aa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7"),
-    gfield("3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f"),
-);
-#elif GF_BITS==521
-const gfield gfield::P("1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
-const gfield ecjacobian::B("051953eb968e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00");
-const ecjacobian ecjacobian::base(
-    gfield("c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66"),
-    gfield("11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650")
-);
-#else
-#error Unsupported GF
-#endif
-
-void genprivkey(const char *seed, vector<char> &privstr, vector<char> &pubstr)
-{
-    tiger::hashval hash;
-    tiger::hash((const uchar *)seed, (int)strlen(seed), hash);
-    bigint<8*sizeof(hash.bytes)/BI_DIGIT_BITS> privkey;
-    memcpy(privkey.digits, hash.bytes, sizeof(privkey.digits));
-    privkey.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS;
-    privkey.shrink();
-    privkey.printdigits(privstr);
-    privstr.add('\0');
-
-    ecjacobian c(ecjacobian::base);
-    c.mul(privkey);
-    c.normalize();
-    c.print(pubstr);
-    pubstr.add('\0');
-}
-
-bool hashstring(const char *str, char *result, int maxlen)
-{
-    tiger::hashval hv;
-    if(maxlen < 2*(int)sizeof(hv.bytes) + 1) return false;
-    tiger::hash((uchar *)str, strlen(str), hv);
-    loopi(sizeof(hv.bytes))
-    {
-        uchar c = hv.bytes[i];
-        *result++ = "0123456789abcdef"[c&0xF];
-        *result++ = "0123456789abcdef"[c>>4];
-    }
-    *result = '\0';
-    return true;
-}
-
-void answerchallenge(const char *privstr, const char *challenge, vector<char> &answerstr)
-{
-    gfint privkey;
-    privkey.parse(privstr);
-    ecjacobian answer;
-    answer.parse(challenge);
-    answer.mul(privkey);
-    answer.normalize();
-    answer.x.printdigits(answerstr);
-    answerstr.add('\0');
-}
-
-void *parsepubkey(const char *pubstr)
-{
-    ecjacobian *pubkey = new ecjacobian;
-    pubkey->parse(pubstr);
-    return pubkey;
-}
-
-void freepubkey(void *pubkey)
-{
-    delete (ecjacobian *)pubkey;
-}
-
-void *genchallenge(void *pubkey, const void *seed, int seedlen, vector<char> &challengestr)
-{
-    tiger::hashval hash;
-    tiger::hash((const uchar *)seed, sizeof(seed), hash);
-    gfint challenge;
-    memcpy(challenge.digits, hash.bytes, sizeof(challenge.digits));
-    challenge.len = 8*sizeof(hash.bytes)/BI_DIGIT_BITS;
-    challenge.shrink();
-
-    ecjacobian answer(*(ecjacobian *)pubkey);
-    answer.mul(challenge);
-    answer.normalize();
-
-    ecjacobian secret(ecjacobian::base);
-    secret.mul(challenge);
-    secret.normalize();
-
-    secret.print(challengestr);
-    challengestr.add('\0');
-   
-    return new gfield(answer.x);
-}
-
-void freechallenge(void *answer)
-{
-    delete (gfint *)answer;
-}
-
-bool checkchallenge(const char *answerstr, void *correct)
-{
-    gfint answer(answerstr);
-    return answer == *(gfint *)correct;
-}
-
diff --git a/shared/cube.h b/shared/cube.h
index 21eec4f..35e5471 100644
--- a/shared/cube.h
+++ b/shared/cube.h
@@ -1,71 +1,5 @@
-#ifndef __CUBE_H__
-#define __CUBE_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 <ctype.h>
-#include <stdarg.h>
-#include <limits.h>
-#include <assert.h>
-#ifdef __GNUC__
-#include <new>
-#else
-#include <new.h>
-#endif
-#include <time.h>
-
-#ifdef WIN32
-#define WIN32_LEAN_AND_MEAN
-#include "windows.h"
-#endif
-
-#ifndef STANDALONE
-#include <SDL.h>
-#include <SDL_image.h>
-
-#define GL_GLEXT_LEGACY
-#define __glext_h__
-#define NO_SDL_GLEXT
-#include <SDL_opengl.h>
-#undef __glext_h__
-#include "GL/glext.h"
-#endif
-
-#include <enet/enet.h>
-
-#ifdef WIN32
-  #define _WINDOWS
-  #ifndef __GNUC__
-    #define ZLIB_DLL
-    #include <eh.h>
-    #include <dbghelp.h>
-  #endif
-#endif
-#include <zlib.h>
-
-#ifdef __sun__
-#undef sun
-#undef MAXNAMELEN
-#endif
-
 #include "tools.h"
 #include "geom.h"
 #include "ents.h"
 #include "command.h"
 
-#include "iengine.h"
-#include "igame.h"
-
-#endif
-
diff --git a/shared/ents.h b/shared/ents.h
index 638f0f7..a640103 100644
--- a/shared/ents.h
+++ b/shared/ents.h
@@ -40,8 +40,6 @@ struct extentity : entity                       // part of the entity that doesn
     extentity() : visible(false), triggerstate(TRIGGER_RESET), lasttrigger(0), attached(NULL) {}
 };
 
-#define MAXENTS 10000
-
 //extern vector<extentity *> ents;                // map entities
 
 enum { CS_ALIVE = 0, CS_DEAD, CS_SPAWNING, CS_LAGGED, CS_EDITING, CS_SPECTATOR };
@@ -64,7 +62,11 @@ struct physent                                  // base entity type, can be affe
     vec floor;                                  // the normal of floor the dynent is on
 
     int inwater;
-    bool jumping;
+    bool jumpnext;
+    bool blocked, moving;                       // used by physics to signal ai
+    physent *onplayer;
+    int lastmove, lastmoveattempt, collisions, stacks;
+
     char move, strafe;
 
     uchar physstate;                            // one of PHYS_* above
@@ -72,16 +74,12 @@ struct physent                                  // base entity type, can be affe
     uchar type;                                 // one of ENT_* above
     uchar collidetype;                          // one of COLLIDE_* above           
 
-    bool blocked, moving;                       // used by physics to signal ai
-    physent *onplayer;
-    int lastmove, lastmoveattempt, collisions, stacks;
-
     physent() : o(0, 0, 0), deltapos(0, 0, 0), newpos(0, 0, 0), yaw(270), pitch(0), roll(0), maxspeed(100), 
                radius(4.1f), eyeheight(14), aboveeye(1), xradius(4.1f), yradius(4.1f), zmargin(0),
+               blocked(false), moving(true), 
+               onplayer(NULL), lastmove(0), lastmoveattempt(0), collisions(0), stacks(0),
                state(CS_ALIVE), editstate(CS_ALIVE), type(ENT_PLAYER),
-               collidetype(COLLIDE_ELLIPSE),
-               blocked(false), moving(true),
-               onplayer(NULL), lastmove(0), lastmoveattempt(0), collisions(0), stacks(0)
+               collidetype(COLLIDE_ELLIPSE)
                { reset(); }
               
     void resetinterp()
@@ -99,42 +97,37 @@ struct physent                                  // base entity type, can be affe
         vel = falling = vec(0, 0, 0);
         floor = vec(0, 0, 1);
     }
-
-    vec feetpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset - eyeheight)); }
-    vec headpos(float offset = 0) const { return vec(o).add(vec(0, 0, offset)); }
 };
 
 enum
 {
     ANIM_DEAD = 0, ANIM_DYING, ANIM_IDLE,
     ANIM_FORWARD, ANIM_BACKWARD, ANIM_LEFT, ANIM_RIGHT,
-    ANIM_HOLD1, ANIM_HOLD2, ANIM_HOLD3, ANIM_HOLD4, ANIM_HOLD5, ANIM_HOLD6, ANIM_HOLD7,
-    ANIM_ATTACK1, ANIM_ATTACK2, ANIM_ATTACK3, ANIM_ATTACK4, ANIM_ATTACK5, ANIM_ATTACK6, ANIM_ATTACK7,
-    ANIM_PAIN,
+    ANIM_PUNCH, ANIM_SHOOT, ANIM_PAIN,
     ANIM_JUMP, ANIM_SINK, ANIM_SWIM,
     ANIM_EDIT, ANIM_LAG, ANIM_TAUNT, ANIM_WIN, ANIM_LOSE,
-    ANIM_GUN_IDLE, ANIM_GUN_SHOOT,
-    ANIM_VWEP_IDLE, ANIM_VWEP_SHOOT, ANIM_SHIELD, ANIM_POWERUP,
+    ANIM_GUNSHOOT, ANIM_GUNIDLE,
+    ANIM_VWEP, ANIM_SHIELD, ANIM_POWERUP,
     ANIM_MAPMODEL, ANIM_TRIGGER,
     NUMANIMS
 };
 
-#define ANIM_ALL         0x7F
-#define ANIM_INDEX       0x7F
-#define ANIM_LOOP        (1<<7)
-#define ANIM_START       (1<<8)
-#define ANIM_END         (1<<9)
-#define ANIM_REVERSE     (1<<10)
-#define ANIM_DIR         0x780
-#define ANIM_SECONDARY   11
-#define ANIM_NOSKIN      (1<<22)
-#define ANIM_SETTIME     (1<<23)
-#define ANIM_FULLBRIGHT  (1<<24)
-#define ANIM_REUSE       (1<<25)
-#define ANIM_NORENDER    (1<<26)
-#define ANIM_RAGDOLL     (1<<27)
-#define ANIM_SETSPEED    (1<<28)
-#define ANIM_FLAGS       (0x1FF<<22)
+#define ANIM_ALL         0xFF
+#define ANIM_INDEX       0xFF
+#define ANIM_LOOP        (1<<8)
+#define ANIM_START       (1<<9)
+#define ANIM_END         (1<<10)
+#define ANIM_REVERSE     (1<<11)
+#define ANIM_DIR         0xF00
+#define ANIM_SECONDARY   12
+#define ANIM_NOSKIN      (1<<24)
+#define ANIM_ENVMAP      (1<<25)
+#define ANIM_TRANSLUCENT (1<<26)
+#define ANIM_SHADOW      (1<<27)
+#define ANIM_SETTIME     (1<<28)
+#define ANIM_FULLBRIGHT  (1<<29)
+#define ANIM_REUSE       (1<<30)
+#define ANIM_FLAGS       (0x7F<<24)
 
 struct animinfo // description of a character's animation
 {
@@ -160,7 +153,6 @@ struct animinterpinfo // used for animation blending of animated characters
 #define MAXANIMPARTS 2
 
 struct occludequery;
-struct ragdolldata;
 
 struct dynent : physent                         // animated characters, or characters that can receive input
 {
@@ -169,26 +161,17 @@ struct dynent : physent                         // animated characters, or chara
 
     entitylight light;
     animinterpinfo animinterp[MAXANIMPARTS];
-    ragdolldata *ragdoll;
     occludequery *query;
     int occluded, lastrendered;
 
-    dynent() : ragdoll(NULL), query(NULL), occluded(0), lastrendered(0)
+    dynent() : query(NULL), occluded(0), lastrendered(0)
     { 
         reset(); 
     }
-
-    ~dynent()
-    {
-#ifndef STANDALONE
-        extern void cleanragdoll(dynent *d);
-        if(ragdoll) cleanragdoll(this);
-#endif
-    }
                
     void stopmoving()
     {
-        k_left = k_right = k_up = k_down = jumping = false;
+        k_left = k_right = k_up = k_down = jumpnext = false;
         move = strafe = 0;
         targetyaw = rotspeed = 0;
     }
diff --git a/shared/geom.cpp b/shared/geom.cpp
index cf14051..c6638c2 100644
--- a/shared/geom.cpp
+++ b/shared/geom.cpp
@@ -1,140 +1,51 @@
 
-#include "cube.h"
+#include "pch.h"
+#include "tools.h"
+#include "geom.h"
 
-static inline float det2x2(float a, float b, float c, float d) { return a*d - b*c; }
-static inline float det3x3(float a1, float a2, float a3,
-                           float b1, float b2, float b3,
-                           float c1, float c2, float c3)
+bool raysphereintersect(vec c, float radius, const vec &o, const vec &ray, float &dist)
 {
-    return a1 * det2x2(b2, b3, c2, c3)
-         - b1 * det2x2(a2, a3, c2, c3)
-         + c1 * det2x2(a2, a3, b2, b3);
-}
-
-float glmatrixf::determinant() const
-{
-    float a1 = v[0], a2 = v[1], a3 = v[2], a4 = v[3],
-          b1 = v[4], b2 = v[5], b3 = v[6], b4 = v[7],
-          c1 = v[8], c2 = v[9], c3 = v[10], c4 = v[11],
-          d1 = v[12], d2 = v[13], d3 = v[14], d4 = v[15];
-
-    return a1 * det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4)
-         - b1 * det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4)
-         + c1 * det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4)
-         - d1 * det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
-}
-
-void glmatrixf::adjoint(const glmatrixf &m)
-{
-    float a1 = m.v[0], a2 = m.v[1], a3 = m.v[2], a4 = m.v[3],
-          b1 = m.v[4], b2 = m.v[5], b3 = m.v[6], b4 = m.v[7],
-          c1 = m.v[8], c2 = m.v[9], c3 = m.v[10], c4 = m.v[11],
-          d1 = m.v[12], d2 = m.v[13], d3 = m.v[14], d4 = m.v[15];
-
-    v[0]  =  det3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4);
-    v[1]  = -det3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4);
-    v[2]  =  det3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4);
-    v[3]  = -det3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4);
-
-    v[4]  = -det3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4);
-    v[5]  =  det3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4);
-    v[6]  = -det3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4);
-    v[7]  =  det3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4);
-
-    v[8]  =  det3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4);
-    v[9]  = -det3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4);
-    v[10] =  det3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4);
-    v[11] = -det3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4);
-
-    v[12] = -det3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3);
-    v[13] =  det3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3);
-    v[14] = -det3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3);
-    v[15] =  det3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3);
-}
-
-bool glmatrixf::invert(const glmatrixf &m, float mindet)
-{
-    float det = m.determinant();
-    if(fabs(det) < mindet) return false;
-    adjoint(m);
-    float invdet = 1/det;
-    loopi(16) v[i] *= invdet;
-    return true;
-}
-
-bool raysphereintersect(const vec &center, float radius, const vec &o, const vec &ray, float &dist)
-{
-    vec c(center);
     c.sub(o);
     float v = c.dot(ray),
-          inside = radius*radius - c.squaredlen();
-    if(inside<0 && v<0) return false;
-    float d = inside + v*v;
-    if(d<0) return false;
-    dist = v - sqrt(d);
+          inside = radius*radius - c.squaredlen(),
+          d = inside + v*v;
+    if(inside<0 && d<0) return false;
+    dist += v - sqrt(d);
     return true;
 }
 
 bool rayrectintersect(const vec &b, const vec &s, const vec &o, const vec &ray, float &dist, int &orient)
 {
-    loop(d, 3) if(ray[d])
+    loopi(6)
     {
-        int dc = ray[d]<0 ? 1 : 0;
-        float pdist = (b[d]+s[d]*dc - o[d]) / ray[d];
-        vec v(ray);
-        v.mul(pdist).add(o);
-        if(v[R[d]] >= b[R[d]] && v[R[d]] <= b[R[d]]+s[R[d]]
-        && v[C[d]] >= b[C[d]] && v[C[d]] <= b[C[d]]+s[C[d]])
+        int d = i>>1, dc = i&1; orient = i;
+        if(dc == (ray[d]>0)) continue;
+        float t = 0;
+        plane pl(d, b[D[d]]+s[D[d]]*dc);
+        if(pl.rayintersect(o, ray, t))
         {
-            dist = pdist;
-            orient = 2*d+dc;
-            return true;
+            vec v(ray);
+            v.mul(t);
+            v.add(o);
+            if(v[R[d]] >= b[R[d]] && v[R[d]] <= b[R[d]]+s[R[d]]
+            && v[C[d]] >= b[C[d]] && v[C[d]] <= b[C[d]]+s[C[d]])
+            {
+                dist += t;
+                return true;
+            }
         }
     }
     return false;
 }
 
-bool linecylinderintersect(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist)
+int intersect_plane_line(vec &linestart, vec &linestop, vec &planeorig, vec &planenormal, vec &intersectionpoint)
 {
-    vec d(end), m(from), n(to);
-    d.sub(start);
-    m.sub(start);
-    n.sub(from);
-    float md = m.dot(d),
-          nd = n.dot(d),
-          dd = d.squaredlen();
-    if(md < 0 && md + nd < 0) return false;
-    if(md > dd && md + nd > dd) return false;
-    float nn = n.squaredlen(),
-          mn = m.dot(n),
-          a = dd*nn - nd*nd,
-          k = m.squaredlen() - radius*radius,
-          c = dd*k - md*md;
-    if(fabs(a) < 0.005f)
-    {
-        if(c > 0) return false;
-        if(md < 0) dist = -mn / nn;
-        else if(md > dd) dist = (nd - mn) / nn;
-        else dist = 0;
-        return true;
-    }
-    float b = dd*mn - nd*md,
-          discrim = b*b - a*c;
-    if(discrim < 0) return false;
-    dist = (-b - sqrtf(discrim)) / a;
-    float offset = md + dist*nd;
-    if(offset < 0)
-    {
-        if(nd < 0) return false;
-        dist = -md / nd;
-        if(k + dist*(2*mn + dist*nn) > 0) return false;
-    }
-    else if(offset > dd)
-    {
-        if(nd >= 0) return false;
-        dist = (dd - md) / nd;
-        if(k + dd - 2*md + dist*(2*(mn-nd) + dist*nn) > 0) return false;
-    }
-    return dist >= 0 && dist <= 1;
+    vec u = linestop;  u.sub(linestart);
+    vec w = linestart; w.sub(planeorig);
+    float d = planenormal.dot(u);
+    float n = -planenormal.dot(w);
+    if(fabs(d)<0.000001) return n==0 ? INTERSECT_OVERLAP : INTERSECT_NONE;                 
+    float si = n/d;
+    intersectionpoint = u.mul(si).add(linestart);              
+    return si<0 ? INTERSECT_BEFORESTART : (si>1 ? INTERSECT_AFTEREND : INTERSECT_MIDDLE);
 }
-
diff --git a/shared/geom.h b/shared/geom.h
index 10048aa..b7f337a 100644
--- a/shared/geom.h
+++ b/shared/geom.h
@@ -29,9 +29,7 @@ struct vec
     bool iszero() const { return x==0 && y==0 && z==0; }
     float squaredlen() const { return x*x + y*y + z*z; }
     float dot(const vec &o) const { return x*o.x + y*o.y + z*o.z; }
-    vec &mul(const vec &o)   { x *= o.x; y *= o.y; z *= o.z; return *this; }
     vec &mul(float f)        { x *= f; y *= f; z *= f; return *this; }
-    vec &div(const vec &o)   { x /= o.x; y /= o.y; z /= o.z; return *this; }
     vec &div(float f)        { x /= f; y /= f; z /= f; return *this; }
     vec &add(const vec &o)   { x += o.x; y += o.y; z += o.z; return *this; }
     vec &add(float f)        { x += f; y += f; z += f; return *this; }
@@ -41,7 +39,6 @@ struct vec
     float magnitude() const  { return sqrtf(squaredlen()); }
     vec &normalize()         { div(magnitude()); return *this; }
     bool isnormalized() const { float m = squaredlen(); return (m>0.99f && m<1.01f); }
-    float squaredist(const vec &e) const { return vec(*this).sub(e).squaredlen(); }
     float dist(const vec &e) const { vec t; return dist(e, t); }
     float dist(const vec &e, vec &t) const { t = *this; t.sub(e); return t.magnitude(); }
     bool reject(const vec &o, float max) { return x>o.x+max || x<o.x-max || y>o.y+max || y<o.y-max; }
@@ -63,31 +60,29 @@ struct vec
         rescale(sqrtf(max(m - k*k, 0.0f)));
         return *this;
     }
-    void lerp(const vec &a, const vec &b, float t) { x = a.x + (b.x-a.x)*t; y = a.y + (b.y-a.y)*t; z = a.z + (b.z-a.z)*t; }
+    void lerp(const vec &a, const vec &b, float t) { x = a.x*(1-t)+b.x*t; y = a.y*(1-t)+b.y*t; z = a.z*(1-t)+b.z*t; }
 
-    vec &rescale(float k)
+    void rescale(float k)
     {
         float mag = magnitude();
         if(mag > 1e-6f) mul(k / mag);
-        return *this;
     }
 
-    vec &rotate_around_z(float angle) { *this = vec(cosf(angle)*x-sinf(angle)*y, cosf(angle)*y+sinf(angle)*x, z); return *this; }
-    vec &rotate_around_x(float angle) { *this = vec(x, cosf(angle)*y-sinf(angle)*z, cosf(angle)*z+sinf(angle)*y); return *this; }
-    vec &rotate_around_y(float angle) { *this = vec(cosf(angle)*x-sinf(angle)*z, y, cosf(angle)*z+sinf(angle)*x); return *this; }
+    void rotate_around_z(float angle) { *this = vec(cosf(angle)*x-sinf(angle)*y, cosf(angle)*y+sinf(angle)*x, z); }
+    void rotate_around_x(float angle) { *this = vec(x, cosf(angle)*y-sinf(angle)*z, cosf(angle)*z+sinf(angle)*y); }
+    void rotate_around_y(float angle) { *this = vec(cosf(angle)*x-sinf(angle)*z, y, cosf(angle)*z+sinf(angle)*x); }
 
-    vec &rotate(float angle, const vec &d)
+    void rotate(float angle, const vec &d)
     {
         float c = cosf(angle), s = sinf(angle);
-        return rotate(c, s, d);
+        rotate(c, s, d);
     }
 
-    vec &rotate(float c, float s, const vec &d)
+    void rotate(float c, float s, const vec &d)
     {
         *this = vec(x*(d.x*d.x*(1-c)+c) + y*(d.x*d.y*(1-c)-d.z*s) + z*(d.x*d.z*(1-c)+d.y*s),
                     x*(d.y*d.x*(1-c)+d.z*s) + y*(d.y*d.y*(1-c)+c) + z*(d.y*d.z*(1-c)-d.x*s),
                     x*(d.x*d.z*(1-c)-d.y*s) + y*(d.y*d.z*(1-c)+d.x*s) + z*(d.z*d.z*(1-c)+c));
-        return *this;
     }
 
     void orthogonal(const vec &d)
@@ -122,18 +117,6 @@ struct vec
     }
 };
 
-static inline bool htcmp(const vec &x, const vec &y)
-{
-    return x == y;
-}
-
-static inline uint hthash(const vec &k)
-{
-    union { int i; float f; } x, y, z;
-    x.f = k.x; y.f = k.y; z.f = k.z;
-    return x.i^y.i^z.i;
-}
-
 struct vec4
 {
     union
@@ -160,29 +143,21 @@ struct vec4
 
     void lerp(const vec4 &a, const vec4 &b, float t) 
     { 
-        x = a.x+(b.x-a.x)*t; 
-        y = a.y+(b.y-a.y)*t; 
-        z = a.z+(b.z-a.z)*t;
-        w = a.w+(b.w-b.w)*t;
+        x = a.x*(1-t)+b.x*t; 
+        y = a.y*(1-t)+b.y*t; 
+        z = a.z*(1-t)+b.z*t;
+        w = a.w*(1-t)+b.w*t;
     }
 
-    vec4 &mul3(float f)      { x *= f; y *= f; z *= f; return *this; }
+    vec4 &mul3(float f)       { x *= f; y *= f; z *= f; return *this; }
     vec4 &mul(float f)       { mul3(f); w *= f; return *this; }
     vec4 &add(const vec4 &o) { x += o.x; y += o.y; z += o.z; w += o.w; return *this; }
-    vec4 &addw(float f)      { w += f; return *this; }
-    vec4 &sub(const vec4 &o) { x -= o.x; y -= o.y; z -= o.z; w -= o.w; return *this; }
-    vec4 &subw(float f)      { w -= f; return *this; }
     vec4 &neg3()             { x = -x; y = -y; z = -z; return *this; }
     vec4 &neg()              { neg3(); w = -w; return *this; }
-
-    void setxyz(const vec &v) { x = v.x; y = v.y; z = v.z; }
 };
 
 inline vec::vec(const vec4 &v) : x(v.x), y(v.y), z(v.z) {}
 
-struct matrix3x3;
-struct matrix3x4;
-
 struct quat : vec4
 {
     quat() {}
@@ -195,13 +170,10 @@ struct quat : vec4
         y = s*axis.y;
         z = s*axis.z;
     }
-    explicit quat(const matrix3x3 &m) { convertmatrix(m); }
-    explicit quat(const matrix3x4 &m) { convertmatrix(m); }
-
+ 
     void restorew() { w = 1.0f-x*x-y*y-z*z; w = w<0 ? 0 : -sqrtf(w); }
-    
+
     void add(const vec4 &o) { vec4::add(o); }
-    void sub(const vec4 &o) { vec4::sub(o); }
     void mul(float k) { vec4::mul(k); }
 
     void mul(const quat &p, const quat &o)
@@ -224,21 +196,10 @@ struct quat : vec4
 
     quat &invert() { neg3(); return *this; }
 
-    void calcangleaxis(float &angle, vec &axis)
-    {
-        float rr = dot3(*this);
-        if(rr>0)
-        {
-            angle = 2*acosf(w);
-            axis = vec(x, y, z).mul(1/rr); 
-        }
-        else { angle = 0; axis = vec(0, 0, 1); }
-    }
-
     void slerp(const quat &from, const quat &to, float t)
     {
         float cosomega = from.dot(to), fromk, tok = 1;
-        if(cosomega < 0) { cosomega = -cosomega; tok = -1; }
+        if(cosomega<0) { cosomega = -cosomega; tok = -1; }
 
         if(cosomega > 1 - 1e-6) { fromk = 1-t; tok *= t; }
         else
@@ -268,44 +229,6 @@ struct quat : vec4
         return vec(tmp.x, tmp.y, tmp.z);
 #endif
     }
-
-    template<class M>
-    void convertmatrix(const M &m)
-    {
-        float trace = m.a.x + m.b.y + m.c.z;
-        if(trace>0)
-        {
-            float r = sqrtf(1 + trace), inv = 0.5f/r;
-            w = 0.5f*r;
-            x = (m.c.y - m.b.z)*inv;
-            y = (m.a.z - m.c.x)*inv;
-            z = (m.b.x - m.a.y)*inv;
-        }
-        else if(m.a.x > m.b.y && m.a.x > m.c.z)
-        {
-            float r = sqrtf(1 + m.a.x - m.b.y - m.c.z), inv = 0.5f/r;
-            x = 0.5f*r;
-            y = (m.b.x + m.a.y)*inv;
-            z = (m.a.z + m.c.x)*inv;
-            w = (m.c.y - m.b.z)*inv;
-        }
-        else if(m.b.y > m.c.z)
-        {
-            float r = sqrtf(1 + m.b.y - m.a.x - m.c.z), inv = 0.5f/r;
-            x = (m.b.x + m.a.y)*inv;
-            y = 0.5f*r;
-            z = (m.c.y + m.b.z)*inv;
-            w = (m.a.z - m.c.x)*inv;
-        }
-        else
-        {
-            float r = sqrtf(1 + m.c.z - m.a.x - m.b.y), inv = 0.5f/r;
-            x = (m.a.z + m.c.x)*inv;
-            y = (m.c.y + m.b.z)*inv;
-            z = 0.5f*r;
-            w = (m.b.x - m.a.y)*inv;
-        }
-    }
 };
 
 struct dualquat
@@ -313,17 +236,15 @@ struct dualquat
     quat real, dual;
 
     dualquat() {}
-    dualquat(const quat &q, const vec &p) 
-        : real(q),
-          dual(0.5f*( p.x*q.w + p.y*q.z - p.z*q.y),
-               0.5f*(-p.x*q.z + p.y*q.w + p.z*q.x),
-               0.5f*( p.x*q.y - p.y*q.x + p.z*q.w),
-              -0.5f*( p.x*q.x + p.y*q.y + p.z*q.z))
+    dualquat(const quat &q, const vec &p) : real(q)
     {
+        dual.x =  0.5f*( p.x*q.w + p.y*q.z - p.z*q.y);
+        dual.y =  0.5f*(-p.x*q.z + p.y*q.w + p.z*q.x);
+        dual.z =  0.5f*( p.x*q.y - p.y*q.x + p.z*q.w);
+        dual.w = -0.5f*( p.x*q.x + p.y*q.y + p.z*q.z);
     }
     explicit dualquat(const quat &q) : real(q), dual(0, 0, 0, 0) {}
-    explicit dualquat(const matrix3x4 &m);
-
+    
     dualquat &mul(float k) { real.mul(k); dual.mul(k); return *this; }
     dualquat &add(const dualquat &d) { real.add(d.real); dual.add(d.dual); return *this; }
 
@@ -368,13 +289,7 @@ struct dualquat
         dual.add(tmp);
     }       
     void mul(const dualquat &o) { mul(dualquat(*this), o); }    
-  
-    void mulorient(const quat &q)
-    {
-        real.mul(q, quat(real));
-        dual.mul(quat(q).invert(), quat(dual));
-    }
-
+    
     void normalize()
     {
         float invlen = 1/real.magnitude();
@@ -406,16 +321,8 @@ struct dualquat
 
     void accumulate(const dualquat &d, float k)
     {
-        if(real.dot(d.real) < 0)
-        {
-            real.sub(vec4(d.real).mul(k));
-            dual.sub(vec4(d.dual).mul(k));
-        }
-        else
-        {
-            real.add(vec4(d.real).mul(k));
-            dual.add(vec4(d.dual).mul(k));
-        }
+        real.add(vec4(d.real).mul(k));
+        dual.add(vec4(d.dual).mul(k));
     }
 
     vec transform(const vec &v) const
@@ -436,294 +343,116 @@ struct dualquat
     }
 };
 
-struct matrix3x3
-{
-    vec a, b, c;
-
-    matrix3x3() {}
-    matrix3x3(const vec &a, const vec &b, const vec &c) : a(a), b(b), c(c) {}
-    explicit matrix3x3(const quat &q)
-    {
-        float x = q.x, y = q.y, z = q.z, w = q.w,
-              tx = 2*x, ty = 2*y, tz = 2*z,
-              txx = tx*x, tyy = ty*y, tzz = tz*z,
-              txy = tx*y, txz = tx*z, tyz = ty*z,
-              twx = w*tx, twy = w*ty, twz = w*tz;
-        a = vec(1 - (tyy + tzz), txy - twz, txz + twy);
-        b = vec(txy + twz, 1 - (txx + tzz), tyz - twx);
-        c = vec(txz - twy, tyz + twx, 1 - (txx + tyy));
-    }
-
-    void mul(const matrix3x3 &m, const matrix3x3 &n)
-    {
-        a = vec(n.a).mul(m.a.x).add(vec(n.b).mul(m.a.y)).add(vec(n.c).mul(m.a.z));
-        b = vec(n.a).mul(m.b.x).add(vec(n.b).mul(m.b.y)).add(vec(n.c).mul(m.b.z));
-        c = vec(n.a).mul(m.c.x).add(vec(n.b).mul(m.c.y)).add(vec(n.c).mul(m.c.z));
-    }
-    void mul(const matrix3x3 &n) { mul(*this, n); }
-
-    void multranspose(const matrix3x3 &m, const matrix3x3 &n)
-    {
-        a = vec(m.a.dot(n.a), m.a.dot(n.b), m.a.dot(n.c));
-        b = vec(m.b.dot(n.a), m.b.dot(n.b), m.b.dot(n.c));
-        c = vec(m.c.dot(n.a), m.c.dot(n.b), m.c.dot(n.c));
-    }
-
-    void transposemul(const matrix3x3 &m, const matrix3x3 &n)
-    {
-        a = vec(n.a).mul(m.a.x).add(vec(n.b).mul(m.b.x)).add(vec(n.c).mul(m.c.x));
-        b = vec(n.a).mul(m.a.y).add(vec(n.b).mul(m.b.y)).add(vec(n.c).mul(m.c.y));
-        c = vec(n.a).mul(m.a.z).add(vec(n.b).mul(m.b.z)).add(vec(n.c).mul(m.c.z));
-    }
-
-    void transpose(const matrix3x3 &o)
-    {
-        a = vec(o.a.x, o.b.x, o.c.x);
-        b = vec(o.a.y, o.b.y, o.c.y);
-        c = vec(o.a.z, o.b.z, o.c.z);
-    }
-
-    void rotate(float angle, const vec &axis)
-    {
-        rotate(cosf(angle), sinf(angle), axis);
-    }
-
-    void rotate(float ck, float sk, const vec &axis)
-    {
-        a = vec(axis.x*axis.x*(1-ck)+ck, axis.x*axis.y*(1-ck)-axis.z*sk, axis.x*axis.z*(1-ck)+axis.y*sk);
-        b = vec(axis.y*axis.x*(1-ck)+axis.z*sk, axis.y*axis.y*(1-ck)+ck, axis.y*axis.z*(1-ck)-axis.x*sk);
-        c = vec(axis.x*axis.z*(1-ck)-axis.y*sk, axis.y*axis.z*(1-ck)+axis.x*sk, axis.z*axis.z*(1-ck)+ck);
-    }
-
-    void calcangleaxis(float &angle, vec &axis)
-    {
-        angle = acosf(clamp(0.5f*(a.x + b.y + c.z - 1), -1.0f, 1.0f));
-
-		if(angle <= 0) axis = vec(0, 0, 1);
-        else if(angle < M_PI) axis = vec(c.y - b.z, a.z - c.x, b.x - a.y).normalize();
-        else if(a.x >= b.y && a.x >= c.z)
-        {
-            float r = sqrtf(1 + a.x - b.y - c.z), inv = 1/r;
-            axis.x = 0.5f*r;
-            axis.y = a.y*inv;
-            axis.z = a.z*inv;
-        }
-        else if(b.y >= c.z)
-        {
-            float r = sqrtf(1 + b.y - a.x - c.z), inv = 1/r;
-            axis.y = 0.5f*r;
-            axis.x = a.y*inv;
-            axis.z = b.z*inv;
-        }
-        else
-        {
-            float r = sqrtf(1 + b.y - a.x - c.z), inv = 1/r;
-            axis.z = 0.5f*r;
-            axis.x = a.z*inv;
-            axis.y = b.z*inv;
-        }
-    }
-
-    vec transform(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); }
-    vec transposedtransform(const vec &o) const
-    {
-        return vec(a.x*o.x + b.x*o.y + c.x*o.z,
-                   a.y*o.x + b.y*o.y + c.y*o.z,
-                   a.z*o.x + b.z*o.y + c.z*o.z);
-    }
-};
-
 struct matrix3x4
 {
-    vec4 a, b, c;
+    vec4 X, Y, Z;
     
     matrix3x4() {}
-    matrix3x4(const vec4 &x, const vec4 &y, const vec4 &z) : a(x), b(y), c(z) {}
-    matrix3x4(const matrix3x3 &rot, const vec &trans)
-     : a(rot.a, trans.x), b(rot.b, trans.y), c(rot.c, trans.z)
-    {}
+    matrix3x4(const vec4 &x, const vec4 &y, const vec4 &z) : X(x), Y(y), Z(z) {}
     matrix3x4(const dualquat &d)
     {
         float x = d.real.x, y = d.real.y, z = d.real.z, w = d.real.w, 
               ww = w*w, xx = x*x, yy = y*y, zz = z*z,
               xy = x*y, xz = x*z, yz = y*z,
               wx = w*x, wy = w*y, wz = w*z;
-        a = vec4(ww + xx - yy - zz, 2*(xy - wz), 2*(xz + wy),
+        X = vec4(ww + xx - yy - zz, 2*(xy - wz), 2*(xz + wy),
             -2*(d.dual.w*x - d.dual.x*w + d.dual.y*z - d.dual.z*y));
-        b = vec4(2*(xy + wz), ww + yy - xx - zz, 2*(yz - wx),
+        Y = vec4(2*(xy + wz), ww + yy - xx - zz, 2*(yz - wx),
             -2*(d.dual.w*y - d.dual.x*z - d.dual.y*w + d.dual.z*x));
-        c = vec4(2*(xz - wy), 2*(yz + wx), ww + zz - xx - yy,
+        Z = vec4(2*(xz - wy), 2*(yz + wx), ww + zz - xx - yy,
             -2*(d.dual.w*z + d.dual.x*y - d.dual.y*x - d.dual.z*w));
 
         float invrr = 1/d.real.dot(d.real);
-        a.mul(invrr);
-        b.mul(invrr);
-        c.mul(invrr);
+        X.mul(invrr);
+        Y.mul(invrr);
+        Z.mul(invrr);
     }
 
     void scale(float k)
     {
-        a.mul(k);
-        b.mul(k);
-        c.mul(k);
+        X.mul(k);
+        Y.mul(k);
+        Z.mul(k);
     }
 
     void translate(const vec &p)
     {
-        a.w += p.x;
-        b.w += p.y;
-        c.w += p.z;
+        X.w += p.x;
+        Y.w += p.y;
+        Z.w += p.z;
     }
 
     void accumulate(const matrix3x4 &m, float k)
     {
-        a.add(vec4(m.a).mul(k));
-        b.add(vec4(m.b).mul(k));
-        c.add(vec4(m.c).mul(k));
+        X.add(vec4(m.X).mul(k));
+        Y.add(vec4(m.Y).mul(k));
+        Z.add(vec4(m.Z).mul(k));
     }
 
     void normalize()
     {
-        a.mul3(1/a.magnitude3());
-        b.mul3(1/b.magnitude3());
-        c.mul3(1/c.magnitude3());
+        X.mul3(1/X.magnitude3());
+        Y.mul3(1/Y.magnitude3());
+        Z.mul3(1/Z.magnitude3());
     }
 
     void lerp(const matrix3x4 &from, const matrix3x4 &to, float t)
     {
         loopi(4)
         {
-            a[i] += from.a[i] + (to.a[i]-from.a[i])*t;
-            b[i] += from.b[i] + (to.b[i]-from.b[i])*t;
-            c[i] += from.c[i] + (to.c[i]-from.c[i])*t;
+            X[i] += to.X[i]*t + from.X[i]*(1-t);
+            Y[i] += to.Y[i]*t + from.Y[i]*(1-t);
+            Z[i] += to.Z[i]*t + from.Z[i]*(1-t);
         }
     }
 
     void identity()
     {
-        a = vec4(1, 0, 0, 0);
-        b = vec4(0, 1, 0, 0);
-        c = vec4(0, 0, 1, 0);
+        X = vec4(1, 0, 0, 0);
+        Y = vec4(0, 1, 0, 0);
+        Z = vec4(0, 0, 1, 0);
     }
 
     void mul(const matrix3x4 &m, const matrix3x4 &n)
     {
-        a = vec4(n.a).mul(m.a.x).add(vec4(n.b).mul(m.a.y)).add(vec4(n.c).mul(m.a.z)).addw(m.a.w);
-        b = vec4(n.a).mul(m.b.x).add(vec4(n.b).mul(m.b.y)).add(vec4(n.c).mul(m.b.z)).addw(m.b.w);
-        c = vec4(n.a).mul(m.c.x).add(vec4(n.b).mul(m.c.y)).add(vec4(n.c).mul(m.c.z)).addw(m.c.w);
-    }
-    void mul(const matrix3x4 &n) { mul(*this, n); }
-
-    void mulorient(const matrix3x3 &m)
-    {
-        vec ra = vec(a).mul(m.a.x).add(vec(b).mul(m.a.y)).add(vec(c).mul(m.a.z)),
-            rb = vec(a).mul(m.b.x).add(vec(b).mul(m.b.y)).add(vec(c).mul(m.b.z)),
-            rc = vec(a).mul(m.c.x).add(vec(b).mul(m.c.y)).add(vec(c).mul(m.c.z));
-        a.setxyz(ra);
-        b.setxyz(rb);
-        c.setxyz(rc);
-    }
-
-    void transpose(const matrix3x4 &o)
-    {
-        a = vec4(o.a.x, o.b.x, o.c.x, -(o.a.x*o.a.w + o.b.x*o.b.w + o.c.x*o.c.w));
-        b = vec4(o.a.y, o.b.y, o.c.y, -(o.a.y*o.a.w + o.b.y*o.b.w + o.c.y*o.c.w));
-        c = vec4(o.a.z, o.b.z, o.c.z, -(o.a.z*o.a.w + o.b.z*o.b.w + o.c.z*o.c.w));
-    }
-
-    void transposemul(const matrix3x3 &rot, const vec &trans, const matrix3x4 &o)
-    {
-        a = vec4(o.a).mul(rot.a.x).add(vec4(o.b).mul(rot.b.x)).add(vec4(o.c).mul(rot.c.x)).addw(trans.x);
-        b = vec4(o.a).mul(rot.a.y).add(vec4(o.b).mul(rot.b.y)).add(vec4(o.c).mul(rot.c.y)).addw(trans.y);
-        c = vec4(o.a).mul(rot.a.z).add(vec4(o.b).mul(rot.b.z)).add(vec4(o.c).mul(rot.c.z)).addw(trans.z);
-    } 
-
-    void transposemul(const matrix3x4 &m, const matrix3x4 &n)
-    {
-        float tx = m.a.x*m.a.w + m.b.x*m.b.w + m.c.x*m.c.w,
-              ty = m.a.y*m.a.w + m.b.y*m.b.w + m.c.y*m.c.w,
-              tz = m.a.z*m.a.w + m.b.z*m.b.w + m.c.z*m.c.w;
-        a = vec4(n.a).mul(m.a.x).add(vec4(n.b).mul(m.b.x)).add(vec4(n.c).mul(m.c.x)).subw(tx);
-        b = vec4(n.a).mul(m.a.y).add(vec4(n.b).mul(m.b.y)).add(vec4(n.c).mul(m.c.y)).subw(ty);
-        c = vec4(n.a).mul(m.a.z).add(vec4(n.b).mul(m.b.z)).add(vec4(n.c).mul(m.c.z)).subw(tz);
-    }
+        X = vec4(m.X.dot3(vec(n.X.x, n.Y.x, n.Z.x)),
+                 m.X.dot3(vec(n.X.y, n.Y.y, n.Z.y)),
+                 m.X.dot3(vec(n.X.z, n.Y.z, n.Z.z)),
+                 m.X.dot(vec(n.X.w, n.Y.w, n.Z.w)));
+        Y = vec4(m.Y.dot3(vec(n.X.x, n.Y.x, n.Z.x)),
+                 m.Y.dot3(vec(n.X.y, n.Y.y, n.Z.y)),
+                 m.Y.dot3(vec(n.X.z, n.Y.z, n.Z.z)),
+                 m.Y.dot(vec(n.X.w, n.Y.w, n.Z.w)));
+        Z = vec4(m.Z.dot3(vec(n.X.x, n.Y.x, n.Z.x)),
+                 m.Z.dot3(vec(n.X.y, n.Y.y, n.Z.y)),
+                 m.Z.dot3(vec(n.X.z, n.Y.z, n.Z.z)),
+                 m.Z.dot(vec(n.X.w, n.Y.w, n.Z.w)));
+    }
+    void mul(const matrix3x4 &n) { mul(matrix3x4(*this), n); }
 
     void rotate(float angle, const vec &d)
     {
-        rotate(cosf(angle), sinf(angle), d);
-    }
-
-    void rotate(float ck, float sk, const vec &d)
-    {
-        a = vec4(d.x*d.x*(1-ck)+ck, d.x*d.y*(1-ck)-d.z*sk, d.x*d.z*(1-ck)+d.y*sk, 0);
-        b = vec4(d.y*d.x*(1-ck)+d.z*sk, d.y*d.y*(1-ck)+ck, d.y*d.z*(1-ck)-d.x*sk, 0);
-        c = vec4(d.x*d.z*(1-ck)-d.y*sk, d.y*d.z*(1-ck)+d.x*sk, d.z*d.z*(1-ck)+ck, 0);
-    }
-
-    #define ROTVEC(V, m, n) \
-    { \
-        float m = V.m, n = V.n; \
-        V.m = m*ck + n*sk; \
-        V.n = n*ck - m*sk; \
-    }
-
-    void rotate_around_x(float angle)
-    {
-        float ck = cosf(angle), sk = sinf(angle);
-        ROTVEC(a, y, z);
-        ROTVEC(b, y, z);
-        ROTVEC(c, y, z);
-    }
-
-    void rotate_around_y(float angle)
-    {
-        float ck = cosf(angle), sk = sinf(angle);
-        ROTVEC(a, z, x);
-        ROTVEC(b, z, x);
-        ROTVEC(c, z, x);
+        float c = cosf(angle), s = sinf(angle);
+        rotate(c, s, d);
     }
 
-    void rotate_around_z(float angle)
+    void rotate(float c, float s, const vec &d)
     {
-        float ck = cosf(angle), sk = sinf(angle);
-        ROTVEC(a, x, y);
-        ROTVEC(b, x, y);
-        ROTVEC(c, x, y);
+        X = vec4(d.x*d.x*(1-c)+c, d.x*d.y*(1-c)-d.z*s, d.x*d.z*(1-c)+d.y*s, 0);
+        Y = vec4(d.y*d.x*(1-c)+d.z*s, d.y*d.y*(1-c)+c, d.y*d.z*(1-c)-d.x*s, 0);
+        Z = vec4(d.x*d.z*(1-c)-d.y*s, d.y*d.z*(1-c)+d.x*s, d.z*d.z*(1-c)+c, 0);
     }
 
-    #undef ROTVEC
-
-    vec transform(const vec &o) const { return vec(a.dot(o), b.dot(o), c.dot(o)); }
-    vec transposedtransform(const vec &o) const
-    {
-        vec p = o;
-        p.x -= a.w;
-        p.y -= b.w;
-        p.z -= c.w;
-        return vec(a.x*p.x + b.x*p.y + c.x*p.z,
-                   a.y*p.x + b.y*p.y + c.y*p.z,
-                   a.z*p.x + b.z*p.y + c.z*p.z);
-    }
-    vec transformnormal(const vec &o) const { return vec(a.dot3(o), b.dot3(o), c.dot3(o)); }
+    vec transform(const vec &o) const { return vec(X.dot(o), Y.dot(o), Z.dot(o)); }
+    vec transformnormal(const vec &o) const { return vec(X.dot3(o), Y.dot3(o), Z.dot3(o)); }
     vec transposedtransformnormal(const vec &o) const
     {
-        return vec(a.x*o.x + b.x*o.y + c.x*o.z,
-                   a.y*o.x + b.y*o.y + c.y*o.z,
-                   a.z*o.x + b.z*o.y + c.z*o.z);
+        return vec(X.x*o.x + Y.x*o.y + Z.x*o.z,
+                   X.y*o.x + Y.y*o.y + Z.y*o.z,
+                   X.z*o.x + Y.z*o.y + Z.z*o.z);
     }
-
-    float getscale() const { return a.magnitude3(); }
-    vec gettranslation() const { return vec(a.w, b.w, c.w); }
 };
 
-inline dualquat::dualquat(const matrix3x4 &m) : real(m)
-{
-    dual.x =  0.5f*( m.a.w*real.w + m.b.w*real.z - m.c.w*real.y);
-    dual.y =  0.5f*(-m.a.w*real.z + m.b.w*real.w + m.c.w*real.x);
-    dual.z =  0.5f*( m.a.w*real.y - m.b.w*real.x + m.c.w*real.w);
-    dual.w = -0.5f*( m.a.w*real.x + m.b.w*real.y + m.c.w*real.z);
-}
-
 struct plane : vec
 {
     float offset;
@@ -733,7 +462,7 @@ struct plane : vec
     bool operator!=(const plane &p) const { return x!=p.x || y!=p.y || z!=p.z || offset!=p.offset; }
 
     plane() {}
-    plane(const vec &c, float off) : vec(c), offset(off) {} 
+    plane(vec &c, float off) : vec(c), offset(off) {} 
     plane(int d, float off)
     {
         x = y = z = 0.0f;
@@ -767,18 +496,6 @@ struct plane : vec
         return true;
     }
 
-    plane &scale(float k)
-    {
-        mul(k);
-        return *this;
-    }
-
-    plane &translate(const vec &p)
-    {
-        offset += dot(p);
-        return *this;
-    }
-
     float zintersect(const vec &p) const { return -(x*p.x+y*p.y+offset)/z; }
     float zdist(const vec &p) const { return p.z-zintersect(p); }
 };
@@ -934,262 +651,9 @@ struct bvec
     vec tovec() const { return vec(x*(2.0f/255.0f)-1.0f, y*(2.0f/255.0f)-1.0f, z*(2.0f/255.0f)-1.0f); }
 };
 
-struct glmatrixf
-{
-    float v[16];
-
-    glmatrixf() {}
-    glmatrixf(const matrix3x4 &m)
-    {
-        v[0] = m.a.x; v[1] = m.b.x; v[2] = m.c.x;
-        v[4] = m.a.y; v[5] = m.b.y; v[6] = m.c.y;
-        v[8] = m.a.z; v[9] = m.b.z; v[10] = m.c.z;
-        v[12] = m.a.w; v[13] = m.b.w; v[14] = m.c.w;
-        v[3] = v[7] = v[11] = 0.0f; v[15] = 1.0f;
-    }
-
-    float operator[](int i) const { return v[i]; }
-    float &operator[](int i) { return v[i]; }
-
-    #define ROTVEC(A, B) \
-    { \
-        float a = A, b = B; \
-        A = a*c + b*s; \
-        B = b*c - a*s; \
-    }
-
-    void rotate_around_x(float angle)
-    {
-        float c = cosf(angle), s = sinf(angle);
-        ROTVEC(v[4], v[8]);
-        ROTVEC(v[5], v[9]);
-        ROTVEC(v[6], v[10]);
-    }
-
-    void rotate_around_y(float angle)
-    {
-        float c = cosf(angle), s = sinf(angle);
-        ROTVEC(v[8], v[0]);
-        ROTVEC(v[9], v[1]);
-        ROTVEC(v[10], v[2]);
-    }
-
-    void rotate_around_z(float angle)
-    {
-        float c = cosf(angle), s = sinf(angle);
-        ROTVEC(v[0], v[4]);
-        ROTVEC(v[1], v[5]);
-        ROTVEC(v[2], v[6]);
-    }
-
-    #undef ROTVEC
-
-    void rotate(float c, float s, const vec &d)
-    {
-        vec c1(d.x*d.x*(1-c)+c, d.y*d.x*(1-c)+d.z*s, d.x*d.z*(1-c)-d.y*s),
-            c2(d.x*d.y*(1-c)-d.z*s, d.y*d.y*(1-c)+c, d.y*d.z*(1-c)+d.x*s),
-            c3(d.x*d.z*(1-c)+d.y*s, d.y*d.z*(1-c)-d.x*s, d.z*d.z*(1-c)+c);
-
-        vec r1(v[0], v[4], v[8]);
-        v[0] = r1.dot(c1);
-        v[4] = r1.dot(c2);
-        v[8] = r1.dot(c3);
-
-        vec r2(v[1], v[5], v[9]);
-        v[1] = r2.dot(c1);
-        v[5] = r2.dot(c2);
-        v[9] = r2.dot(c3);
-
-        vec r3(v[2], v[6], v[10]);
-        v[2] = r3.dot(c1);
-        v[6] = r3.dot(c2);
-        v[10] = r3.dot(c3);
-    }
-
-    void rotate(float angle, const vec &d)
-    {
-        float c = cosf(angle), s = sinf(angle);
-        rotate(c, s, d);
-    }
-
-    #define MULMAT(row, col) \
-       v[col + row] = x[row]*y[col] + x[row + 4]*y[col + 1] + x[row + 8]*y[col + 2] + x[row + 12]*y[col + 3];
-
-    template<class XT, class YT>
-    void mul(const XT x[16], const YT y[16])
-    {
-        MULMAT(0, 0); MULMAT(1, 0); MULMAT(2, 0); MULMAT(3, 0);
-        MULMAT(0, 4); MULMAT(1, 4); MULMAT(2, 4); MULMAT(3, 4);
-        MULMAT(0, 8); MULMAT(1, 8); MULMAT(2, 8); MULMAT(3, 8);
-        MULMAT(0, 12); MULMAT(1, 12); MULMAT(2, 12); MULMAT(3, 12);
-    }
-
-    #undef MULMAT
-
-    void mul(const glmatrixf &x, const glmatrixf &y)
-    {
-        mul(x.v, y.v);
-    }
-
-    void identity()
-    {
-        static const float m[16] =
-        {
-            1, 0, 0, 0,
-            0, 1, 0, 0,
-            0, 0, 1, 0,
-            0, 0, 0, 1
-        };
-        memcpy(v, m, sizeof(v));
-    }
-
-    void translate(float x, float y, float z)
-    {
-        v[12] += x;
-        v[13] += y;
-        v[14] += z;
-    }
-
-    void translate(const vec &o)
-    {
-        translate(o.x, o.y, o.z);
-    }
-
-    void scale(float x, float y, float z)
-    {
-        v[0] *= x; v[1] *= x; v[2] *= x; v[3] *= x;
-        v[4] *= y; v[5] *= y; v[6] *= y; v[7] *= y;
-        v[8] *= z; v[9] *= z; v[10] *= z; v[11] *= z;
-    }
-
-    void reflectz(float z)
-    {
-        v[8] = -v[8]; v[9] = -v[9]; v[10] = -v[10]; v[11] = -v[11];
-        v[14] += 2*z;
-    }
-
-    void projective(float zscale = 0.5f, float zoffset = 0.5f)
-    {
-        loopi(2) loopj(4) v[i + j*4] = 0.5f*(v[i + j*4] + v[3 + j*4]); 
-        loopj(4) v[2 + j*4] = zscale*v[2 + j*4] + zoffset*v[3 + j*4];
-    }
-
-    void transpose()
-    {
-        swap(v[1], v[4]); swap(v[2], v[8]); swap(v[3], v[12]);
-        swap(v[6], v[9]); swap(v[7], v[13]);
-        swap(v[11], v[14]);
-    }
-
-    void frustum(float left, float right, float bottom, float top, float znear, float zfar)
-    {
-        float width = right - left, height = top - bottom, zrange = znear - zfar;
-        v[0] = 2*znear/width; v[4] = 0;              v[8] = (right + left)/width;   v[12] = 0;
-        v[1] = 0;             v[5] = 2*znear/height; v[9] = (top + bottom)/height;  v[13] = 0;
-        v[2] = 0;             v[6] = 0;              v[10] = (zfar + znear)/zrange; v[14] = 2*znear*zfar/zrange;
-        v[3] = 0;             v[7] = 0;              v[11] = -1;                    v[15] = 0;
-    }
-
-    void perspective(float fovy, float aspect, float znear, float zfar)
-    {
-        float ydist = znear * tan(fovy/2*RAD), xdist = ydist * aspect;
-        frustum(-xdist, xdist, -ydist, ydist, znear, zfar);
-    }
-
-    void clip(const plane &p, const glmatrixf &m)
-    {
-        float x = ((p.x<0 ? -1 : (p.x>0 ? 1 : 0)) + m.v[8]) / m.v[0],
-              y = ((p.y<0 ? -1 : (p.y>0 ? 1 : 0)) + m.v[9]) / m.v[5],
-              w = (1 + m.v[10]) / m.v[14],
-            scale = 2 / (x*p.x + y*p.y - p.z + w*p.offset);
-        memcpy(v, m.v, sizeof(v));
-        v[2] = p.x*scale;
-        v[6] = p.y*scale;
-        v[10] = p.z*scale + 1.0f;
-        v[14] = p.offset*scale;
-    }
-            
-    void invertnormal(vec &dir) const
-    {
-        vec n(dir);
-        dir.x = n.x*v[0] + n.y*v[1] + n.z*v[2];
-        dir.y = n.x*v[4] + n.y*v[5] + n.z*v[6];
-        dir.z = n.x*v[8] + n.y*v[9] + n.z*v[10];
-    }
-
-    void invertvertex(vec &pos) const
-    {
-        pos.x -= v[12];
-        pos.y -= v[13];
-        pos.z -= v[14];
-        invertnormal(pos);
-    }
-
-    void invertplane(plane &p)
-    {
-        p.offset += p.x*v[12] + p.y*v[13] + p.z*v[14];
-        invertnormal(p);
-    }
-
-    template<class T> float transformx(const T &p) const
-    {
-        return p.x*v[0] + p.y*v[4] + p.z*v[8] + v[12];
-    }
-
-    template<class T> float transformy(const T &p) const
-    {
-        return p.x*v[1] + p.y*v[5] + p.z*v[9] + v[13];
-    }
-
-    template<class T> float transformz(const T &p) const
-    {
-        return p.x*v[2] + p.y*v[6] + p.z*v[10] + v[14];
-    }
-
-    template<class T> float transformw(const T &p) const
-    {
-        return p.x*v[3] + p.y*v[7] + p.z*v[11] + v[15];
-    }
-
-    template<class T> void transform(const T &in, vec &out) const
-    {
-        out.x = transformx(in);
-        out.y = transformy(in);
-        out.z = transformz(in);
-    }
-
-    template<class T> void transform(const T &in, vec4 &out) const
-    {
-        out.x = transformx(in);
-        out.y = transformy(in);
-        out.z = transformz(in);
-        out.w = transformw(in);
-    }
-
-    void transposetransform(const plane &in, plane &out) const
-    {
-        out.x      = in.x*v[0] + in.y*v[1] + in.z*v[2] + in.offset*v[3];
-        out.y      = in.x*v[4] + in.y*v[5] + in.z*v[6] + in.offset*v[7];
-        out.z      = in.x*v[8] + in.y*v[9] + in.z*v[10] + in.offset*v[11];
-        out.offset = in.x*v[12] + in.y*v[13] + in.z*v[14] + in.offset*v[15];
-    }
-
-    float getscale() const
-    {
-        return sqrtf(v[0]*v[0] + v[4]*v[4] + v[8]*v[8]);
-    }
-
-    vec gettranslation() const
-    {
-        return vec(v[12], v[13], v[14]);
-    }
-
-    float determinant() const;
-    void adjoint(const glmatrixf &m);
-    bool invert(const glmatrixf &m, float mindet = 1.0e-10f);
-};
-
-extern bool raysphereintersect(const vec &center, float radius, const vec &o, const vec &ray, float &dist);
+extern bool raysphereintersect(vec c, float radius, const vec &o, const vec &ray, float &dist);
 extern bool rayrectintersect(const vec &b, const vec &s, const vec &o, const vec &ray, float &dist, int &orient);
-extern bool linecylinderintersect(const vec &from, const vec &to, const vec &start, const vec &end, float radius, float &dist);
+
+enum { INTERSECT_NONE, INTERSECT_OVERLAP, INTERSECT_BEFORESTART, INTERSECT_MIDDLE, INTERSECT_AFTEREND };
+extern int intersect_plane_line(vec &linestart, vec &linestop, vec &planeorig, vec &planenormal, vec &intersectionpoint);
 
diff --git a/shared/iengine.h b/shared/iengine.h
index 49dba21..938824c 100644
--- a/shared/iengine.h
+++ b/shared/iengine.h
@@ -1,10 +1,5 @@
 // the interface the game uses to access the engine
 
-extern int curtime;                     // current frame time
-extern int lastmillis;                  // last time
-extern int totalmillis;                 // total elapsed time
-extern int gamespeed, paused;
-
 enum
 {
     MATF_VOLUME_SHIFT = 0,
@@ -18,17 +13,17 @@ enum
 
 enum // cube empty-space materials
 {
-    MAT_AIR      = 0,                      // the default, fill the empty space with air
-    MAT_WATER    = 1 << MATF_VOLUME_SHIFT, // fill with water, showing waves at the surface
-    MAT_LAVA     = 2 << MATF_VOLUME_SHIFT, // fill with lava
-    MAT_GLASS    = 3 << MATF_VOLUME_SHIFT, // behaves like clip but is blended blueish
+    MAT_AIR   = 0,                      // the default, fill the empty space with air
+    MAT_WATER = 1 << MATF_VOLUME_SHIFT, // fill with water, showing waves at the surface
+    MAT_LAVA  = 2 << MATF_VOLUME_SHIFT, // fill with lava
+    MAT_GLASS = 3 << MATF_VOLUME_SHIFT, // behaves like clip but is blended blueish
 
-    MAT_NOCLIP   = 1 << MATF_CLIP_SHIFT,  // collisions always treat cube as empty
-    MAT_CLIP     = 2 << MATF_CLIP_SHIFT,  // collisions always treat cube as solid
-    MAT_GAMECLIP = 3 << MATF_CLIP_SHIFT,  // game specific clip material
+    MAT_NOCLIP = 1 << MATF_CLIP_SHIFT,  // collisions always treat cube as empty
+    MAT_CLIP   = 2 << MATF_CLIP_SHIFT,  // collisions always treat cube as solid
+    MAT_AICLIP = 3 << MATF_CLIP_SHIFT,  // clip monsters only
 
-    MAT_DEATH    = 1 << MATF_FLAG_SHIFT,  // force player suicide
-    MAT_EDIT     = 4 << MATF_FLAG_SHIFT   // edit-only surfaces
+    MAT_DEATH  = 1 << MATF_FLAG_SHIFT,  // force player suicide
+    MAT_EDIT   = 4 << MATF_FLAG_SHIFT   // edit-only surfaces
 };
 
 extern void lightent(extentity &e, float height = 8.0f);
@@ -42,10 +37,9 @@ extern float raycubepos(const vec &o, const vec &ray, vec &hit, float radius = 0
 extern float rayfloor  (const vec &o, vec &floor, int mode = 0, float radius = 0);
 extern bool  raycubelos(const vec &o, const vec &dest, vec &hitpos);
 
-extern int thirdperson;
 extern bool isthirdperson();
 
-extern void settexture(const char *name, int clamp = 0);
+extern void settexture(const char *name, bool clamp = false);
 
 // octaedit
 
@@ -63,17 +57,17 @@ struct selinfo
 };
 
 struct editinfo;
-extern editinfo *localedit;
 
 extern bool editmode;
 
 extern void freeeditinfo(editinfo *&e);
+extern void cursorupdate();
 extern void pruneundos(int maxremain = 0);
-extern bool noedit(bool view = false, bool msg = true);
+extern bool noedit(bool view = false);
 extern void toggleedit(bool force = true);
 extern void mpeditface(int dir, int mode, selinfo &sel, bool local);
 extern void mpedittex(int tex, int allfaces, selinfo &sel, bool local);
-extern void mpeditmat(int matid, int filter, selinfo &sel, bool local);
+extern void mpeditmat(int matid, selinfo &sel, bool local);
 extern void mpflip(selinfo &sel, bool local);
 extern void mpcopy(editinfo *&e, selinfo &sel, bool local);
 extern void mppaste(editinfo *&e, selinfo &sel, bool local);
@@ -84,11 +78,11 @@ extern void mpremip(bool local);
 
 // command
 extern int variable(const char *name, int min, int cur, int max, int *storage, void (*fun)(), int flags);
-extern float fvariable(const char *name, float min, float cur, float max, float *storage, void (*fun)(), int flags);
+extern float fvariable(const char *name, float cur, float *storage, void (*fun)(), int flags);
 extern char *svariable(const char *name, const char *cur, char **storage, void (*fun)(), int flags);
-extern void setvar(const char *name, int i, bool dofunc = true, bool doclamp = true);
-extern void setfvar(const char *name, float f, bool dofunc = true, bool doclamp = true);
-extern void setsvar(const char *name, const char *str, bool dofunc = true);
+extern void setvar(const char *name, int i, bool dofunc = false);
+extern void setfvar(const char *name, float f, bool dofunc = false);
+extern void setsvar(const char *name, const char *str, bool dofunc = false);
 extern void touchvar(const char *name);
 extern int getvar(const char *name);
 extern int getvarmin(const char *name);
@@ -98,7 +92,8 @@ extern ident *getident(const char *name);
 extern bool addcommand(const char *name, void (*fun)(), const char *narg);
 extern int execute(const char *p);
 extern char *executeret(const char *p);
-extern bool execfile(const char *cfgfile, bool msg = true);
+extern void exec(const char *cfgfile);
+extern bool execfile(const char *cfgfile);
 extern void alias(const char *name, const char *action);
 extern const char *getalias(const char *name);
 
@@ -114,38 +109,37 @@ enum
     CON_ECHO  = 1<<5
 };
 
+extern void keypress(int code, bool isdown, int cooked);
+extern int rendercommand(int x, int y, int w);
+extern int renderconsole(int w, int h);
 extern void conoutf(const char *s, ...);
 extern void conoutf(int type, const char *s, ...);
+extern void resetcomplete();
+extern void complete(char *s);
 
 // menus
 extern vec menuinfrontofplayer();
 extern void newgui(char *name, char *contents, char *header = NULL);
 extern void showgui(const char *name);
-extern int cleargui(int n = 0);
-
-// octa
-extern int lookupmaterial(const vec &o);
 
 // world
-extern bool emptymap(int factor, bool force, const char *mname = "", bool usecfg = true);
+extern bool emptymap(int factor, bool force, const char *mname = "");
 extern bool enlargemap(bool force);
 extern int findentity(int type, int index = 0, int attr1 = -1, int attr2 = -1);
-extern void findents(int low, int high, bool notspawned, const vec &pos, const vec &radius, vector<int> &found);
-extern void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, int attr5, bool local);
-extern vec getselpos();
+extern void mpeditent(int i, const vec &o, int type, int attr1, int attr2, int attr3, int attr4, bool local);
 extern int getworldsize();
 extern int getmapversion();
-extern void renderentcone(const extentity &e, const vec &dir, float radius, float angle);
-extern void renderentarrow(const extentity &e, const vec &dir, float radius);
-extern void renderentattachment(const extentity &e);
-extern void renderentsphere(const extentity &e, float radius);
-extern void renderentring(const extentity &e, float radius, int axis = 0);
 extern void resettriggers();
 extern void checktriggers();
 
 // main
+struct igame;
+
 extern void fatal(const char *s, ...);
 extern void keyrepeat(bool on);
+extern void registergame(const char *name, igame *ig);
+
+#define REGISTERGAME(t, n, c, s) struct t : igame { t() { registergame(n, this); } igameclient *newclient() { return c; } igameserver *newserver() { return s; } } reg_##t
 
 // rendertext
 extern bool setfont(const char *name);
@@ -165,47 +159,23 @@ enum
     DL_FLASH  = 1<<2
 };
 
-extern void adddynlight(const vec &o, float radius, const vec &color, int fade = 0, int peak = 0, int flags = 0, float initradius = 0, const vec &initcolor = vec(0, 0, 0), physent *owner = NULL);
+extern void adddynlight(const vec &o, float radius, const vec &color, int fade = 0, int peak = 0, int flags = 0, float initradius = 0, const vec &initcolor = vec(0, 0, 0));
 extern void dynlightreaching(const vec &target, vec &color, vec &dir);
-extern void removetrackeddynlights(physent *owner = NULL);
 
 // rendergl
-extern physent *camera1;
 extern vec worldpos, camdir, camright, camup;
-
-extern void disablezoom();
-
-extern vec calcavatarpos(const vec &pos, float dist);
-
 extern void damageblend(int n);
 extern void damagecompass(int n, const vec &loc);
 
 // renderparticles
-enum
-{
-    PART_BLOOD = 0,
-    PART_WATER,
-    PART_SMOKE,
-    PART_STEAM,
-    PART_FLAME,
-    PART_FIREBALL1, PART_FIREBALL2, PART_FIREBALL3,
-    PART_STREAK, PART_LIGHTNING,
-    PART_EXPLOSION, PART_EXPLOSION_NO_GLARE,
-    PART_SPARK, PART_EDIT,
-    PART_MUZZLE_FLASH1, PART_MUZZLE_FLASH2, PART_MUZZLE_FLASH3,
-    PART_TEXT,
-    PART_METER, PART_METER_VS,
-    PART_LENS_FLARE
-};
-
-extern bool canaddparticles();
-extern void regular_particle_splash(int type, int num, int fade, const vec &p, int color = 0xFFFFFF, float size = 1.0f, int radius = 150, int gravity = 2, int delay = 0);
-extern void particle_splash(int type, int num, int fade, const vec &p, int color = 0xFFFFFF, float size = 1.0f, int radius = 150, int gravity = 2);
-extern void particle_trail(int type, int fade, const vec &from, const vec &to, int color = 0xFFFFFF, float size = 1.0f, int gravity = 20);
-extern void particle_text(const vec &s, const char *t, int type, int fade = 2000, int color = 0xFFFFFF, float size = 2.0f, int gravity = 0);
-extern void particle_meter(const vec &s, float val, int type, int fade = 1, int color = 0xFFFFFF, int color2 = 0xFFFFF, float size = 2.0f);
-extern void particle_flare(const vec &p, const vec &dest, int fade, int type, int color = 0xFFFFFF, float size = 0.28f, physent *owner = NULL);
-extern void particle_fireball(const vec &dest, float max, int type, int fade = -1, int color = 0xFFFFFF, float size = 4.0f);
+extern void render_particles(int time);
+extern void regular_particle_splash(int type, int num, int fade, const vec &p, int delay = 0);
+extern void particle_splash(int type, int num, int fade, const vec &p);
+extern void particle_trail(int type, int fade, const vec &from, const vec &to);
+extern void particle_text(const vec &s, const char *t, int type, int fade = 2000);
+extern void particle_meter(const vec &s, float val, int type, int fade = 1);
+extern void particle_flare(const vec &p, const vec &dest, int fade, int type = 10, physent *owner = NULL);
+extern void particle_fireball(const vec &dest, float max, int type, int fade = -1);
 extern void removetrackedparticles(physent *owner = NULL);
 
 // decal
@@ -221,8 +191,6 @@ extern void adddecal(int type, const vec &center, const vec &surface, float radi
 // worldio
 extern bool load_world(const char *mname, const char *cname = NULL);
 extern bool save_world(const char *mname, bool nolms = false);
-extern void getmapfilenames(const char *fname, const char *cname, char *pakname, char *mapname, char *cfgname);
-extern uint getmapcrc();
 
 // physics
 extern void moveplayer(physent *pl, int moveres, bool local);
@@ -231,13 +199,13 @@ extern bool collide(physent *d, const vec &dir = vec(0, 0, 0), float cutoff = 0.
 extern bool bounce(physent *d, float secs, float elasticity, float waterfric);
 extern bool bounce(physent *d, float elasticity, float waterfric);
 extern void avoidcollision(physent *d, const vec &dir, physent *obstacle, float space);
-extern bool movecamera(physent *pl, const vec &dir, float dist, float stepdist);
 extern void physicsframe();
 extern void dropenttofloor(entity *e);
 extern bool droptofloor(vec &o, float radius, float height);
 
 extern void vecfromyawpitch(float yaw, float pitch, int move, int strafe, vec &m);
 extern void vectoyawpitch(const vec &v, float &yaw, float &pitch);
+extern bool intersect(physent *d, vec &from, vec &to);
 extern bool moveplatform(physent *p, const vec &dir);
 extern void updatephysstate(physent *d);
 extern void cleardynentcache();
@@ -246,101 +214,68 @@ extern bool entinmap(dynent *d, bool avoidplayers = false);
 extern void findplayerspawn(dynent *d, int forceent = -1, int tag = 0);
 
 // sound
-extern int playsound(int n, const vec *loc = NULL, extentity *ent = NULL, int loops = 0, int fade = 0, int chanid = -1, int radius = 0);
-extern int playsoundname(const char *s, const vec *loc = NULL, int vol = 0, int loops = 0, int fade = 0, int chanid = -1, int radius = 0);
-extern bool stopsound(int n, int chanid, int fade = 0);
-extern void stopsounds();
+extern void playsound    (int n,   const vec *loc = NULL, extentity *ent = NULL);
+extern void playsoundname(const char *s, const vec *loc = NULL, int vol = 0);
 extern void initsound();
 
+
 // rendermodel
-enum { MDL_CULL_VFC = 1<<0, MDL_CULL_DIST = 1<<1, MDL_CULL_OCCLUDED = 1<<2, MDL_CULL_QUERY = 1<<3, MDL_SHADOW = 1<<4, MDL_DYNSHADOW = 1<<5, MDL_LIGHT = 1<<6, MDL_DYNLIGHT = 1<<7, MDL_FULLBRIGHT = 1<<8, MDL_NORENDER = 1<<9 };
+enum { MDL_CULL_VFC = 1<<0, MDL_CULL_DIST = 1<<1, MDL_CULL_OCCLUDED = 1<<2, MDL_CULL_QUERY = 1<<3, MDL_SHADOW = 1<<4, MDL_DYNSHADOW = 1<<5, MDL_LIGHT = 1<<6, MDL_DYNLIGHT = 1<<7, MDL_TRANSLUCENT = 1<<8, MDL_FULLBRIGHT = 1<<9 };
 
 struct model;
 struct modelattach
 {
-    const char *tag, *name;
+    const char *name, *tag;
     int anim, basetime;
-    vec *pos;
     model *m;
-
-    modelattach() : tag(NULL), name(NULL), anim(-1), basetime(0), pos(NULL), m(NULL) {}
-    modelattach(const char *tag, const char *name, int anim = -1, int basetime = 0) : tag(tag), name(name), anim(anim), basetime(basetime), pos(NULL), m(NULL) {}
-    modelattach(const char *tag, vec *pos) : tag(tag), name(NULL), anim(-1), basetime(0), pos(pos), m(NULL) {}
 };
 
 extern void startmodelbatches();
 extern void endmodelbatches();
-extern void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw = 0, float pitch = 0, int cull = MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED | MDL_LIGHT, dynent *d = NULL, modelattach *a = NULL, int basetime = 0, int basetime2 = 0, float trans = 1);
+extern void rendermodel(entitylight *light, const char *mdl, int anim, const vec &o, float yaw = 0, float pitch = 0, int cull = MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED | MDL_LIGHT, dynent *d = NULL, modelattach *a = NULL, int basetime = 0, float speed = 0);
 extern void abovemodel(vec &o, const char *mdl);
 extern void rendershadow(dynent *d);
-extern void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int hold, int attack, int attackdelay, int lastaction, int lastpain, float fade = 1, bool ragdoll = false);
+extern void renderclient(dynent *d, const char *mdlname, modelattach *attachments, int attack, int attackdelay, int lastaction, int lastpain, float sink = 0);
 extern void interpolateorientation(dynent *d, float &interpyaw, float &interppitch);
 extern void setbbfrommodel(dynent *d, const char *mdl);
 extern const char *mapmodelname(int i);
 extern model *loadmodel(const char *name, int i = -1, bool msg = false);
-extern void preloadmodel(const char *name);
-extern void flushpreloadedmodels();
-
-// ragdoll
-
-extern void moveragdoll(dynent *d);
-extern void cleanragdoll(dynent *d);
 
 // server
-#define MAXCLIENTS 128                 // DO NOT set this any higher
+#define MAXCLIENTS 256                  // in a multiplayer game, can be arbitrarily changed
 #define MAXTRANS 5000                  // max amount of data to swallow in 1 go
 
 extern int maxclients;
 
-enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_KICK, DISC_TAGT, DISC_IPBAN, DISC_PRIVATE, DISC_MAXCLIENTS, DISC_TIMEOUT, DISC_NUM };
+enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_KICK, DISC_TAGT, DISC_IPBAN, DISC_PRIVATE, DISC_MAXCLIENTS, DISC_NUM };
 
-extern void *getclientinfo(int i);
+extern void *getinfo(int i);
 extern void sendf(int cn, int chan, const char *format, ...);
-extern void sendfile(int cn, int chan, stream *file, const char *format = "", ...);
+extern void sendfile(int cn, int chan, FILE *file, const char *format = "", ...);
 extern void sendpacket(int cn, int chan, ENetPacket *packet, int exclude = -1);
 extern int getnumclients();
 extern uint getclientip(int n);
 extern void putint(ucharbuf &p, int n);
-extern void putint(packetbuf &p, int n);
 extern int getint(ucharbuf &p);
 extern void putuint(ucharbuf &p, int n);
-extern void putuint(packetbuf &p, int n);
 extern int getuint(ucharbuf &p);
-extern void putfloat(ucharbuf &p, float f);
-extern void putfloat(packetbuf &p, float f);
-extern float getfloat(ucharbuf &p);
 extern void sendstring(const char *t, ucharbuf &p);
-extern void sendstring(const char *t, packetbuf &p);
 extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS);
 extern void filtertext(char *dst, const char *src, bool whitespace = true, int len = sizeof(string)-1);
-extern void localconnect();
 extern void disconnect_client(int n, int reason);
-extern void kicknonlocalclients(int reason = DISC_NONE);
 extern bool hasnonlocalclients();
 extern bool haslocalclients();
 extern void sendserverinforeply(ucharbuf &p);
-extern bool requestmaster(const char *req);
-extern bool requestmasterf(const char *fmt, ...);
 
 // client
-extern void sendclientpacket(ENetPacket *packet, int chan);
-extern void flushclient();
-extern void disconnect(bool async = false, bool cleanup = true);
-extern bool isconnected(bool attempt = false);
+extern void c2sinfo(dynent *d, int rate = 33);
+extern void sendpackettoserv(ENetPacket *packet, int chan);
+extern void disconnect(int onlyclean = 0, int async = 0);
+extern bool isconnected();
 extern bool multiplayer(bool msg = true);
-extern void neterr(const char *s, bool disc = true);
+extern void neterr(const char *s);
 extern void gets2c();
 
-// crypto
-extern void genprivkey(const char *seed, vector<char> &privstr, vector<char> &pubstr);
-extern bool hashstring(const char *str, char *result, int maxlen);
-extern void answerchallenge(const char *privstr, const char *challenge, vector<char> &answerstr);
-extern void *parsepubkey(const char *pubstr);
-extern void freepubkey(void *pubkey);
-extern void *genchallenge(void *pubkey, const void *seed, int seedlen, vector<char> &challengestr);
-extern void freechallenge(void *answer);
-extern bool checkchallenge(const char *answerstr, void *correct);
-
 // 3dgui
 struct Texture;
 
@@ -358,13 +293,13 @@ struct g3d_gui
     virtual int text(const char *text, int color, const char *icon = NULL) = 0;
     int textf(const char *fmt, int color, const char *icon = NULL, ...)
     {
-        defvformatstring(str, icon, fmt);
+        s_sprintfdlv(str, icon, fmt);
         return text(str, color, icon);
     }
     virtual int button(const char *text, int color, const char *icon = NULL) = 0;
     int buttonf(const char *fmt, int color, const char *icon = NULL, ...)
     {
-        defvformatstring(str, icon, fmt);
+        s_sprintfdlv(str, icon, fmt);
         return button(str, color, icon);
     }
     virtual void background(int color, int parentw = 0, int parenth = 0) = 0;
@@ -377,15 +312,13 @@ struct g3d_gui
 	virtual void tab(const char *name = NULL, int color = 0) = 0;
     virtual int title(const char *text, int color, const char *icon = NULL) = 0;
     virtual int image(Texture *t, float scale, bool overlaid = false) = 0;
-    virtual int texture(Texture *t, float scale, int rotate = 0, int xoff = 0, int yoff = 0, Texture *glowtex = NULL, const vec &glowcolor = vec(1, 1, 1), Texture *layertex = NULL) = 0;
+    virtual int texture(Texture *t, float scale, int rotate = 0, int xoff = 0, int yoff = 0, Texture *glowtex = NULL, const vec &glowcolor = vec(1, 1, 1)) = 0;
     virtual void slider(int &val, int vmin, int vmax, int color, char *label = NULL) = 0;
     virtual void separator() = 0;
 	virtual void progress(float percent) = 0;
 	virtual void strut(int size) = 0;
     virtual void space(int size) = 0;
-    virtual char *keyfield(const char *name, int color, int length, int height = 0, const char *initval = NULL, int initmode = EDITORFOCUSED) = 0;
     virtual char *field(const char *name, int color, int length, int height = 0, const char *initval = NULL, int initmode = EDITORFOCUSED) = 0;
-    virtual void textbox(const char *text, int width, int height, int color = 0xFFFFFF) = 0;
     virtual void mergehits(bool on) = 0;
 };
 
@@ -393,7 +326,7 @@ struct g3d_callback
 {
     virtual ~g3d_callback() {}
 
-    int starttime() { return totalmillis; }
+    int starttime() { extern int totalmillis; return totalmillis; }
 
     virtual void gui(g3d_gui &g, bool firstpass) = 0;
 };
diff --git a/shared/igame.h b/shared/igame.h
index 724e41f..0ce936d 100644
--- a/shared/igame.h
+++ b/shared/igame.h
@@ -1,115 +1,121 @@
 // the interface the engine uses to run the gameplay module
 
-namespace entities
+struct icliententities
 {
-    extern void editent(int i);
-    extern const char *entnameinfo(entity &e);
-    extern const char *entname(int i);
-    extern int extraentinfosize();
-    extern void writeent(entity &e, char *buf);
-    extern void readent(entity &e, char *buf);
-    extern float dropheight(entity &e);
-    extern void rumble(const extentity &e);
-    extern void trigger(extentity &e);
-    extern void fixentity(extentity &e);
-    extern void entradius(extentity &e, bool color);
-    extern bool mayattach(extentity &e);
-    extern bool attachent(extentity &e, extentity &a);
-    extern bool printent(extentity &e, char *buf);
-    extern extentity *newentity();
-    extern void deleteentity(extentity *e);
-    extern void clearents();
-    extern vector<extentity *> &getents();
-    extern const char *entmodel(const entity &e);
-}
+    virtual ~icliententities() {}
 
-namespace game
+    virtual void editent(int i) = 0;
+    virtual const char *entnameinfo(entity &e) = 0;
+    virtual const char *entname(int i) = 0;
+    virtual int extraentinfosize() = 0;
+    virtual void writeent(entity &e, char *buf) = 0;
+    virtual void readent(entity &e, char *buf) = 0;
+    virtual float dropheight(entity &e) = 0;
+    virtual void rumble(const extentity &e) = 0;
+    virtual void trigger(extentity &e) = 0;
+    virtual void fixentity(extentity &e) = 0;
+    virtual void entradius(extentity &e, float &radius, float &angle, vec &dir) {}
+    virtual bool mayattach(extentity &e) { return false; }
+    virtual bool attachent(extentity &e, extentity &a) { return false; }
+    virtual extentity *newentity() = 0;
+    virtual vector<extentity *> &getents() = 0;
+};
+
+struct iclientcom
+{
+    virtual ~iclientcom() {}
+
+    virtual void gamedisconnect() = 0;
+    virtual void parsepacketclient(int chan, ucharbuf &p) = 0;
+    virtual int sendpacketclient(ucharbuf &p, bool &reliable, dynent *d) = 0;
+    virtual void gameconnect(bool _remote) = 0;
+    virtual bool allowedittoggle() = 0;
+    virtual void edittoggled(bool on) {}
+    virtual void writeclientinfo(FILE *f) = 0;
+    virtual void toserver(char *text) = 0;
+    virtual void changemap(const char *name) = 0;
+    virtual int numchannels() { return 1; }
+};
+
+struct igameclient
 {
-    extern void parseoptions(vector<const char *> &args);
+    virtual ~igameclient() {}
 
-    extern void gamedisconnect(bool cleanup);
-    extern void parsepacketclient(int chan, packetbuf &p);
-    extern void connectattempt(const char *name, const char *password, const ENetAddress &address);
-    extern void connectfail();
-    extern void gameconnect(bool _remote);
-    extern bool allowedittoggle();
-    extern void edittoggled(bool on);
-    extern void writeclientinfo(stream *f);
-    extern void toserver(char *text);
-    extern void changemap(const char *name);
-    extern void forceedit(const char *name);
-    extern int numchannels();
-    extern bool ispaused();
+    virtual const char *gameident() = 0;
+    virtual const char *defaultmap() = 0;
+    virtual const char *savedconfig() = 0;
+    virtual const char *defaultconfig() = 0;
+    virtual const char *autoexec() = 0;
+    virtual const char *savedservers() { return NULL; }
 
-    extern const char *gameident();
-    extern const char *savedconfig();
-    extern const char *defaultconfig();
-    extern const char *autoexec();
-    extern const char *savedservers();
-    extern void loadconfigs();
+    virtual icliententities *getents() = 0;
+    virtual iclientcom *getcom() = 0;
 
-    extern void updateworld();
-    extern void initclient();
-    extern void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material = 0);
-    extern void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0);
-    extern void vartrigger(ident *id);
-    extern const char *getclientmap();
-    extern const char *getmapinfo();
-    extern void resetgamestate();
-    extern void suicide(physent *d);
-    extern void newmap(int size);
-    extern void startmap(const char *name);
-    extern void preload();
-    extern float abovegameplayhud();
-    extern void gameplayhud(int w, int h);
-    extern bool canjump();
-    extern bool allowmove(physent *d);
-    extern void doattack(bool on);
-    extern dynent *iterdynents(int i);
-    extern int numdynents();
-    extern void rendergame(bool mainpass);
-    extern void renderavatar();
-    extern void writegamedata(vector<char> &extras);
-    extern void readgamedata(vector<char> &extras);
-    extern int clipconsole(int w, int h);
-    extern void g3d_gamemenus();
-    extern const char *defaultcrosshair(int index);
-    extern int selectcrosshair(float &r, float &g, float &b);
-    extern void lighteffects(dynent *d, vec &color, vec &dir);
-    extern void setupcamera();
-    extern bool detachcamera();
-    extern bool collidecamera();
-    extern void adddynlights();
-    extern void particletrack(physent *owner, vec &o, vec &d);
-    extern void dynlighttrack(physent *owner, vec &o);
-    extern bool serverinfostartcolumn(g3d_gui *g, int i);
-    extern void serverinfoendcolumn(g3d_gui *g, int i);
-    extern bool serverinfoentry(g3d_gui *g, int i, const char *name, int port, const char *desc, const char *map, int ping, const vector<int> &attr, int np);
-} 
+    virtual bool clientoption(char *arg) { return false; }
+    virtual void updateworld(vec &pos, int curtime, int lm) = 0;
+    virtual void initclient() = 0;
+    virtual void physicstrigger(physent *d, bool local, int floorlevel, int waterlevel, int material = 0) = 0;
+    virtual void edittrigger(const selinfo &sel, int op, int arg1 = 0, int arg2 = 0, int arg3 = 0) = 0;
+    virtual char *getclientmap() = 0;
+    virtual void resetgamestate() = 0;
+    virtual void suicide(physent *d) = 0;
+    virtual void newmap(int size) = 0;
+    virtual void startmap(const char *name) = 0;
+    virtual void preload() {}
+    virtual float abovegameplayhud() { return 1.0f; }
+    virtual void gameplayhud(int w, int h) = 0;
+    virtual void drawhudgun() = 0;
+    virtual bool canjump() = 0;
+    virtual bool allowmove(physent *d) { return true; }
+    virtual void doattack(bool on) = 0;
+    virtual dynent *iterdynents(int i) = 0;
+    virtual int numdynents() = 0;
+    virtual void rendergame() = 0;
+    virtual void writegamedata(vector<char> &extras) = 0;
+    virtual void readgamedata(vector<char> &extras) = 0;
+    virtual void g3d_gamemenus() = 0;
+    virtual const char *defaultcrosshair(int index) { return NULL; }
+    virtual int selectcrosshair(float &r, float &g, float &b) { return 0; }
+    virtual void lighteffects(dynent *d, vec &color, vec &dir) {}
+    virtual void setupcamera() {}
+    virtual bool detachcamera() { return false; }
+    virtual void adddynlights() {}
+    virtual void particletrack(physent *owner, vec &o, vec &d) {}
+    virtual bool serverinfostartcolumn(g3d_gui *g, int i) { return false; }
+    virtual void serverinfoendcolumn(g3d_gui *g, int i) {}
+    virtual bool serverinfoentry(g3d_gui *g, int i, const char *name, const char *desc, const char *map, int ping, const vector<int> &attr, int np) { return false; };
+}; 
  
-namespace server
+struct igameserver
+{
+    virtual ~igameserver() {}
+
+    virtual bool serveroption(char *arg) { return false; }
+    virtual void *newinfo() = 0;
+    virtual void deleteinfo(void *ci) = 0;
+    virtual void serverinit() = 0;
+    virtual void clientdisconnect(int n) = 0;
+    virtual int clientconnect(int n, uint ip) = 0;
+    virtual void localdisconnect(int n) = 0;
+    virtual void localconnect(int n) = 0;
+    virtual const char *servername() = 0;
+    virtual void recordpacket(int chan, void *data, int len) {}
+    virtual void parsepacket(int sender, int chan, bool reliable, ucharbuf &p) = 0;
+    virtual bool sendpackets() = 0;
+    virtual int welcomepacket(ucharbuf &p, int n, ENetPacket *packet) = 0;
+    virtual void serverinforeply(ucharbuf &req, ucharbuf &p) = 0;
+    virtual void serverupdate(int lastmillis, int totalmillis) = 0;
+    virtual bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np) = 0;
+    virtual int serverinfoport() = 0;
+    virtual int serverport() = 0;
+    virtual const char *getdefaultmaster() = 0;
+    virtual void sendservmsg(const char *s) = 0;
+};
+
+struct igame
 {
-    extern void *newclientinfo();
-    extern void deleteclientinfo(void *ci);
-    extern void serverinit();
-    extern int reserveclients();
-    extern void clientdisconnect(int n);
-    extern int clientconnect(int n, uint ip);
-    extern void localdisconnect(int n);
-    extern void localconnect(int n);
-    extern bool allowbroadcast(int n);
-    extern void recordpacket(int chan, void *data, int len);
-    extern void parsepacket(int sender, int chan, packetbuf &p);
-    extern void sendservmsg(const char *s);
-    extern bool sendpackets();
-    extern void serverinforeply(ucharbuf &req, ucharbuf &p);
-    extern void serverupdate();
-    extern bool servercompatible(char *name, char *sdec, char *map, int ping, const vector<int> &attr, int np);
-    extern int laninfoport();
-    extern int serverinfoport(int servport = -1);
-    extern int serverport(int infoport = -1);
-    extern const char *defaultmaster();
-    extern int masterport();
-    extern void processmasterinput(const char *cmd, int cmdlen, const char *args);
-}
+    virtual ~igame() {}
 
+    virtual igameclient *newclient() = 0;
+    virtual igameserver *newserver() = 0;
+};
diff --git a/shared/pch.cpp b/shared/pch.cpp
index 5b241e7..1d9f38c 100644
--- a/shared/pch.cpp
+++ b/shared/pch.cpp
@@ -1 +1 @@
-#include "cube.h"
+#include "pch.h"
diff --git a/shared/cube.h b/shared/pch.h
similarity index 83%
copy from shared/cube.h
copy to shared/pch.h
index 21eec4f..7330680 100644
--- a/shared/cube.h
+++ b/shared/pch.h
@@ -1,6 +1,3 @@
-#ifndef __CUBE_H__
-#define __CUBE_H__
-
 #ifdef __GNUC__
 #define gamma __gamma
 #endif
@@ -59,13 +56,3 @@
 #undef MAXNAMELEN
 #endif
 
-#include "tools.h"
-#include "geom.h"
-#include "ents.h"
-#include "command.h"
-
-#include "iengine.h"
-#include "igame.h"
-
-#endif
-
diff --git a/shared/sbtrace.d b/shared/sbtrace.d
new file mode 100644
index 0000000..228219a
--- /dev/null
+++ b/shared/sbtrace.d
@@ -0,0 +1,21 @@
+
+/*
+ * DTrace Sauerbraten provider
+ */
+/*
+#pragma D attributes Evolving/Evolving/Common provider myserv provider
+#pragma D attributes Evolving/Evolving/Common provider myserv module
+#pragma D attributes Evolving/Evolving/Common provider myserv function
+#pragma D attributes Evolving/Evolving/Common provider myserv name
+#pragma D attributes Evolving/Evolving/Common provider myserv args
+*/
+
+provider sauerbraten {
+	probe command__entry(char *, char *, char *, char *);
+	probe command__return(char *);
+	probe var__entry(char *, char *);
+	probe var__return(char *, int);
+	probe alias__entry(char *, char *, char *, char *);
+	probe alias__return(char *);
+};
+
diff --git a/shared/sbtrace.h b/shared/sbtrace.h
new file mode 100644
index 0000000..44e2e24
--- /dev/null
+++ b/shared/sbtrace.h
@@ -0,0 +1,79 @@
+/*
+ * Generated by dtrace(1M).
+ */
+
+#ifndef	_SBTRACE_H
+#define	_SBTRACE_H
+
+#ifndef WIN32
+#include <unistd.h>
+#endif
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#if _DTRACE_VERSION
+
+#define	SAUERBRATEN_ALIAS_ENTRY(arg0, arg1, arg2, arg3) \
+	__dtrace_sauerbraten___alias__entry(arg0, arg1, arg2, arg3)
+#define	SAUERBRATEN_ALIAS_ENTRY_ENABLED() \
+	__dtraceenabled_sauerbraten___alias__entry()
+#define	SAUERBRATEN_ALIAS_RETURN(arg0) \
+	__dtrace_sauerbraten___alias__return(arg0)
+#define	SAUERBRATEN_ALIAS_RETURN_ENABLED() \
+	__dtraceenabled_sauerbraten___alias__return()
+#define	SAUERBRATEN_COMMAND_ENTRY(arg0, arg1, arg2, arg3) \
+	__dtrace_sauerbraten___command__entry(arg0, arg1, arg2, arg3)
+#define	SAUERBRATEN_COMMAND_ENTRY_ENABLED() \
+	__dtraceenabled_sauerbraten___command__entry()
+#define	SAUERBRATEN_COMMAND_RETURN(arg0) \
+	__dtrace_sauerbraten___command__return(arg0)
+#define	SAUERBRATEN_COMMAND_RETURN_ENABLED() \
+	__dtraceenabled_sauerbraten___command__return()
+#define	SAUERBRATEN_VAR_ENTRY(arg0, arg1) \
+	__dtrace_sauerbraten___var__entry(arg0, arg1)
+#define	SAUERBRATEN_VAR_ENTRY_ENABLED() \
+	__dtraceenabled_sauerbraten___var__entry()
+#define	SAUERBRATEN_VAR_RETURN(arg0, arg1) \
+	__dtrace_sauerbraten___var__return(arg0, arg1)
+#define	SAUERBRATEN_VAR_RETURN_ENABLED() \
+	__dtraceenabled_sauerbraten___var__return()
+
+
+extern void __dtrace_sauerbraten___alias__entry(char *, char *, char *, char *);
+extern int __dtraceenabled_sauerbraten___alias__entry(void);
+extern void __dtrace_sauerbraten___alias__return(char *);
+extern int __dtraceenabled_sauerbraten___alias__return(void);
+extern void __dtrace_sauerbraten___command__entry(char *, char *, char *, char *);
+extern int __dtraceenabled_sauerbraten___command__entry(void);
+extern void __dtrace_sauerbraten___command__return(char *);
+extern int __dtraceenabled_sauerbraten___command__return(void);
+extern void __dtrace_sauerbraten___var__entry(char *, char *);
+extern int __dtraceenabled_sauerbraten___var__entry(void);
+extern void __dtrace_sauerbraten___var__return(char *, int);
+extern int __dtraceenabled_sauerbraten___var__return(void);
+
+#else
+
+#define	SAUERBRATEN_ALIAS_ENTRY(arg0, arg1, arg2, arg3)
+#define	SAUERBRATEN_ALIAS_ENTRY_ENABLED() (0)
+#define	SAUERBRATEN_ALIAS_RETURN(arg0)
+#define	SAUERBRATEN_ALIAS_RETURN_ENABLED() (0)
+#define	SAUERBRATEN_COMMAND_ENTRY(arg0, arg1, arg2, arg3)
+#define	SAUERBRATEN_COMMAND_ENTRY_ENABLED() (0)
+#define	SAUERBRATEN_COMMAND_RETURN(arg0)
+#define	SAUERBRATEN_COMMAND_RETURN_ENABLED() (0)
+#define	SAUERBRATEN_VAR_ENTRY(arg0, arg1)
+#define	SAUERBRATEN_VAR_ENTRY_ENABLED() (0)
+#define	SAUERBRATEN_VAR_RETURN(arg0, arg1)
+#define	SAUERBRATEN_VAR_RETURN_ENABLED() (0)
+
+#endif
+
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* _SBTRACE_H */
diff --git a/shared/stream.cpp b/shared/stream.cpp
deleted file mode 100644
index cb2e41d..0000000
--- a/shared/stream.cpp
+++ /dev/null
@@ -1,675 +0,0 @@
-#include "cube.h"
-
-///////////////////////// file system ///////////////////////
-
-#ifndef WIN32
-#include <unistd.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <dirent.h>
-#endif
-
-string homedir = "";
-vector<char *> packagedirs;
-
-char *makerelpath(const char *dir, const char *file, const char *prefix, const char *cmd)
-{
-    static string tmp;
-    if(prefix) copystring(tmp, prefix);
-    else tmp[0] = '\0';
-    if(file[0]=='<')
-    {
-        const char *end = strrchr(file, '>');
-        if(end)
-        {
-            size_t len = strlen(tmp);
-            copystring(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
-            file = end+1;
-        }
-    }
-    if(cmd) concatstring(tmp, cmd);
-    defformatstring(pname)("%s/%s", dir, file);
-    concatstring(tmp, pname);
-    return tmp;
-}
-
-
-char *path(char *s)
-{
-    for(char *curpart = s;;)
-    {
-        char *endpart = strchr(curpart, '&');
-        if(endpart) *endpart = '\0';
-        if(curpart[0]=='<')
-        {
-            char *file = strrchr(curpart, '>');
-            if(!file) return s;
-            curpart = file+1;
-        }
-        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
-        for(char *prevdir = NULL, *curdir = s;;)
-        {
-            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
-            curdir = strchr(prevdir, PATHDIV);
-            if(!curdir) break;
-            if(prevdir+1==curdir && prevdir[0]=='.')
-            {
-                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
-                curdir = prevdir;
-            }
-            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV)
-            {
-                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
-                memmove(prevdir, curdir+4, strlen(curdir+4)+1);
-                curdir = prevdir;
-            }
-        }
-        if(endpart)
-        {
-            *endpart = '&';
-            curpart = endpart+1;
-        }
-        else break;
-    }
-    return s;
-}
-
-char *path(const char *s, bool copy)
-{
-    static string tmp;
-    copystring(tmp, s);
-    path(tmp);
-    return tmp;
-}
-
-const char *parentdir(const char *directory)
-{
-    const char *p = directory + strlen(directory);
-    while(p > directory && *p != '/' && *p != '\\') p--;
-    static string parent;
-    size_t len = p-directory+1;
-    copystring(parent, directory, len);
-    return parent;
-}
-
-bool fileexists(const char *path, const char *mode)
-{
-    bool exists = true;
-    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
-#ifdef WIN32
-    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
-#else
-    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
-#endif
-    return exists;
-}
-
-bool createdir(const char *path)
-{
-    size_t len = strlen(path);
-    if(path[len-1]==PATHDIV)
-    {
-        static string strip;
-        path = copystring(strip, path, len);
-    }
-#ifdef WIN32
-    return CreateDirectory(path, NULL)!=0;
-#else
-    return mkdir(path, 0777)==0;
-#endif
-}
-
-size_t fixpackagedir(char *dir)
-{
-    path(dir);
-    size_t len = strlen(dir);
-    if(len > 0 && dir[len-1] != PATHDIV)
-    {
-        dir[len] = PATHDIV;
-        dir[len+1] = '\0';
-    }
-    return len;
-}
-
-void sethomedir(const char *dir)
-{
-    string pdir;
-    copystring(pdir, dir);
-    if(fixpackagedir(pdir) > 0) copystring(homedir, pdir);
-}
-
-void addpackagedir(const char *dir)
-{
-    string pdir;
-    copystring(pdir, dir);
-    if(fixpackagedir(pdir) > 0) packagedirs.add(newstring(pdir));
-}
-
-const char *findfile(const char *filename, const char *mode)
-{
-    static string s;
-    if(homedir[0])
-    {
-        formatstring(s)("%s%s", homedir, filename);
-        if(fileexists(s, mode)) return s;
-        if(mode[0]=='w' || mode[0]=='a')
-        {
-            string dirs;
-            copystring(dirs, s);
-            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
-            while(dir)
-            {
-                *dir = '\0';
-                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
-                *dir = PATHDIV;
-                dir = strchr(dir+1, PATHDIV);
-            }
-            return s;
-        }
-    }
-    if(mode[0]=='w' || mode[0]=='a') return filename;
-    loopv(packagedirs)
-    {
-        formatstring(s)("%s%s", packagedirs[i], filename);
-        if(fileexists(s, mode)) return s;
-    }
-    return filename;
-}
-
-bool listdir(const char *dir, const char *ext, vector<char *> &files)
-{
-    int extsize = ext ? (int)strlen(ext)+1 : 0;
-    #if defined(WIN32)
-    defformatstring(pathname)("%s\\*.%s", dir, ext ? ext : "*");
-    WIN32_FIND_DATA FindFileData;
-    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
-    if(Find != INVALID_HANDLE_VALUE)
-    {
-        do {
-            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
-        } while(FindNextFile(Find, &FindFileData));
-        return true;
-    }
-    #else
-    string pathname;
-    copystring(pathname, dir);
-    DIR *d = opendir(path(pathname));
-    if(d)
-    {
-        struct dirent *de;
-        while((de = readdir(d)) != NULL)
-        {
-            if(!ext) files.add(newstring(de->d_name));
-            else
-            {
-                int namelength = (int)strlen(de->d_name) - extsize;
-                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
-                    files.add(newstring(de->d_name, namelength));
-            }
-        }
-        closedir(d);
-        return true;
-    }
-    #endif
-    else return false;
-}
-
-int listfiles(const char *dir, const char *ext, vector<char *> &files)
-{
-    int dirs = 0;
-    if(listdir(dir, ext, files)) dirs++;
-    string s;
-    if(homedir[0])
-    {
-        formatstring(s)("%s%s", homedir, dir);
-        if(listdir(s, ext, files)) dirs++;
-    }
-    loopv(packagedirs)
-    {
-        formatstring(s)("%s%s", packagedirs[i], dir);
-        if(listdir(s, ext, files)) dirs++;
-    }
-#ifndef STANDALONE
-    dirs += listzipfiles(dir, ext, files);
-#endif
-    return dirs;
-}
-
-#ifndef STANDALONE
-static int rwopsseek(SDL_RWops *rw, int offset, int whence)
-{
-    stream *f = (stream *)rw->hidden.unknown.data1;
-    if((!offset && whence==SEEK_CUR) || f->seek(offset, whence)) return f->tell();
-    return -1;
-}
-
-static int rwopsread(SDL_RWops *rw, void *buf, int size, int nmemb)
-{
-    stream *f = (stream *)rw->hidden.unknown.data1;
-    return f->read(buf, size*nmemb)/size;
-}
-
-static int rwopswrite(SDL_RWops *rw, const void *buf, int size, int nmemb)
-{
-    stream *f = (stream *)rw->hidden.unknown.data1;
-    return f->write(buf, size*nmemb)/size;
-}
-
-static int rwopsclose(SDL_RWops *rw)
-{
-    return 0;
-}
-
-SDL_RWops *stream::rwops()
-{
-    SDL_RWops *rw = SDL_AllocRW();
-    if(!rw) return NULL;
-    rw->hidden.unknown.data1 = this;
-    rw->seek = rwopsseek;
-    rw->read = rwopsread;
-    rw->write = rwopswrite;
-    rw->close = rwopsclose;
-    return rw;
-}
-#endif
-
-long stream::size()
-{
-    long pos = tell(), endpos;
-    if(pos < 0 || !seek(0, SEEK_END)) return -1;
-    endpos = tell();
-    return pos == endpos || seek(pos, SEEK_SET) ? endpos : -1;
-}
-
-bool stream::getline(char *str, int len)
-{
-    loopi(len-1)
-    {
-        if(read(&str[i], 1) != 1) { str[i] = '\0'; return i > 0; }
-        else if(str[i] == '\n') { str[i+1] = '\0'; return true; }
-    }
-    if(len > 0) str[len-1] = '\0';
-    return true;
-}
-
-struct filestream : stream
-{
-    FILE *file;
-
-    filestream() : file(NULL) {}
-    ~filestream() { close(); }
-
-    bool open(const char *name, const char *mode)
-    {
-        if(file) return false;
-        file = fopen(name, mode);
-        return file!=NULL;
-    }
-
-    bool opentemp(const char *name, const char *mode)
-    {
-        if(file) return false;
-#ifdef WIN32
-        file = fopen(name, mode);
-#else
-        file = tmpfile();
-#endif
-        return file!=NULL;
-    }
-
-    void close()
-    {
-        if(file) { fclose(file); file = NULL; }
-    }
-
-    bool end() { return feof(file)!=0; }
-    long tell() { return ftell(file); }
-    bool seek(long offset, int whence) { return fseek(file, offset, whence) >= 0; }
-    int read(void *buf, int len) { return fread(buf, 1, len, file); }
-    int write(const void *buf, int len) { return fwrite(buf, 1, len, file); }
-    int getchar() { return fgetc(file); }
-    bool putchar(int c) { return fputc(c, file)!=EOF; }
-    bool getline(char *str, int len) { return fgets(str, len, file)!=NULL; }
-    bool putstring(const char *str) { return fputs(str, file)!=EOF; }
-
-    int printf(const char *fmt, ...)
-    {
-        va_list v;
-        va_start(v, fmt);
-        int result = vfprintf(file, fmt, v);
-        va_end(v);
-        return result;
-    }
-};
-
-#ifndef STANDALONE
-VAR(dbggz, 0, 0, 1);
-#endif
-
-struct gzstream : stream
-{
-    enum
-    {
-        MAGIC1   = 0x1F,
-        MAGIC2   = 0x8B,
-        BUFSIZE  = 16384,
-        OS_UNIX  = 0x03
-    };
-
-    enum
-    {
-        F_ASCII    = 0x01,
-        F_CRC      = 0x02,
-        F_EXTRA    = 0x04,
-        F_NAME     = 0x08,
-        F_COMMENT  = 0x10,
-        F_RESERVED = 0xE0
-    };
-
-    stream *file;
-    z_stream zfile;
-    uchar *buf;
-    bool reading, writing, autoclose;
-    uint crc;
-    int headersize;
-
-    gzstream() : file(NULL), buf(NULL), reading(false), writing(false), autoclose(false), crc(0), headersize(0)
-    {
-        zfile.zalloc = NULL;
-        zfile.zfree = NULL;
-        zfile.opaque = NULL;
-        zfile.next_in = zfile.next_out = NULL;
-        zfile.avail_in = zfile.avail_out = 0;
-    }
-
-    ~gzstream()
-    {
-        close();
-    }
-
-    void writeheader()
-    {
-        uchar header[] = { MAGIC1, MAGIC2, Z_DEFLATED, 0, 0, 0, 0, 0, 0, OS_UNIX };
-        file->write(header, sizeof(header));
-    }
-
-    void readbuf(int size = BUFSIZE)
-    {
-        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
-        size = min(size, int(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
-        int n = file->read(zfile.next_in + zfile.avail_in, size);
-        if(n > 0) zfile.avail_in += n;
-    }
-
-    int readbyte(int size = BUFSIZE)
-    {
-        if(!zfile.avail_in) readbuf(size);
-        if(!zfile.avail_in) return 0;
-        zfile.avail_in--;
-        return *(uchar *)zfile.next_in++;
-    }
-
-    void skipbytes(int n)
-    {
-        while(n > 0 && zfile.avail_in > 0)
-        {
-            int skipped = min(n, (int)zfile.avail_in);
-            zfile.avail_in -= skipped;
-            zfile.next_in += skipped;
-            n -= skipped;
-        }
-        if(n <= 0) return;
-        file->seek(n, SEEK_CUR);
-    }
-
-    bool checkheader()
-    {
-        readbuf(10);
-        if(readbyte() != MAGIC1 || readbyte() != MAGIC2 || readbyte() != Z_DEFLATED) return false;
-        int flags = readbyte();
-        if(flags & F_RESERVED) return false;
-        skipbytes(6);
-        if(flags & F_EXTRA)
-        {
-            int len = readbyte(512);
-            len |= readbyte(512)<<8;
-            skipbytes(len);
-        }
-        if(flags & F_NAME) while(readbyte(512));
-        if(flags & F_COMMENT) while(readbyte(512));
-        if(flags & F_CRC) skipbytes(2);
-        headersize = file->tell() - zfile.avail_in;
-        return zfile.avail_in > 0 || !file->end();
-    }
-
-    bool open(stream *f, const char *mode, bool needclose, int level)
-    {
-        if(file) return false;
-        for(; *mode; *mode++)
-        {
-            if(*mode=='r') { reading = true; break; }
-            else if(*mode=='w') { writing = true; break; }
-        }
-        if(reading)
-        {
-            if(inflateInit2(&zfile, -MAX_WBITS) != Z_OK) reading = false;
-        }
-        else if(writing && deflateInit2(&zfile, level, Z_DEFLATED, -MAX_WBITS, min(MAX_MEM_LEVEL, 8), Z_DEFAULT_STRATEGY) != Z_OK) writing = false;
-        if(!reading && !writing) return false;
-
-        autoclose = needclose;
-        file = f;
-        crc = crc32(0, NULL, 0);
-        buf = new uchar[BUFSIZE];
-
-        if(reading)
-        {
-            if(!checkheader()) { stopreading(); return false; }
-        }
-        else if(writing) writeheader();
-        return true;
-    }
-
-    uint getcrc() { return crc; }
-
-    void finishreading()
-    {
-        if(!reading) return;
-#ifndef STANDALONE
-        if(dbggz)
-        {
-            uint checkcrc = 0, checksize = 0;
-            loopi(4) checkcrc |= uint(readbyte()) << (i*8);
-            loopi(4) checksize |= uint(readbyte()) << (i*8);
-            if(checkcrc != crc)
-                conoutf(CON_DEBUG, "gzip crc check failed: read %X, calculated %X", checkcrc, crc);
-            if(checksize != zfile.total_out)
-                conoutf(CON_DEBUG, "gzip size check failed: read %d, calculated %d", checksize, zfile.total_out);
-        }
-#endif
-    }
-
-    void stopreading()
-    {
-        if(!reading) return;
-        inflateEnd(&zfile);
-        reading = false;
-    }
-
-    void finishwriting()
-    {
-        if(!writing) return;
-        for(;;)
-        {
-            int err = zfile.avail_out > 0 ? deflate(&zfile, Z_FINISH) : Z_OK;
-            if(err != Z_OK && err != Z_STREAM_END) break;
-            flush();
-            if(err == Z_STREAM_END) break;
-        }
-        uchar trailer[8] =
-        {
-            crc&0xFF, (crc>>8)&0xFF, (crc>>16)&0xFF, (crc>>24)&0xFF,
-            zfile.total_in&0xFF, (zfile.total_in>>8)&0xFF, (zfile.total_in>>16)&0xFF, (zfile.total_in>>24)&0xFF
-        };
-        file->write(trailer, sizeof(trailer));
-    }
-
-    void stopwriting()
-    {
-        if(!writing) return;
-        deflateEnd(&zfile);
-        writing = false;
-    }
-
-    void close()
-    {
-        if(reading) finishreading();
-        stopreading();
-        if(writing) finishwriting();
-        stopwriting();
-        DELETEA(buf);
-        if(autoclose) DELETEP(file);
-    }
-
-    bool end() { return !reading && !writing; }
-    long tell() { return reading ? zfile.total_out : (writing ? zfile.total_in : -1); }
-
-    bool seek(long offset, int whence)
-    {
-        if(writing || !reading || whence == SEEK_END) return false;
-
-        if(whence == SEEK_CUR) offset += zfile.total_out;
-
-        if(offset >= (int)zfile.total_out) offset -= zfile.total_out;
-        else if(offset < 0 || !file->seek(headersize, SEEK_SET)) return false;
-        else
-        {
-            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
-            {
-                zfile.avail_in += zfile.total_in;
-                zfile.next_in -= zfile.total_in;
-            }
-            else
-            {
-                zfile.avail_in = 0;
-                zfile.next_in = NULL;
-            }
-            inflateReset(&zfile);
-            crc = crc32(0, NULL, 0);
-        }
-
-        uchar skip[512];
-        while(offset > 0)
-        {
-            int skipped = min(offset, (long)sizeof(skip));
-            if(read(skip, skipped) != skipped) { stopreading(); return false; }
-            offset -= skipped;
-        }
-
-        return true;
-    }
-
-    int read(void *buf, int len)
-    {
-        if(!reading || !buf || !len) return 0;
-        zfile.next_out = (Bytef *)buf;
-        zfile.avail_out = len;
-        while(zfile.avail_out > 0)
-        {
-            if(!zfile.avail_in)
-            {
-                readbuf(BUFSIZE);
-                if(!zfile.avail_in) { stopreading(); break; }
-            }
-            int err = inflate(&zfile, Z_NO_FLUSH);
-            if(err == Z_STREAM_END) { crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out); finishreading(); stopreading(); return len - zfile.avail_out; }
-            else if(err != Z_OK) { stopreading(); break; }
-        }
-        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_out);
-        return len - zfile.avail_out;
-    }
-
-    bool flush()
-    {
-        if(zfile.next_out && zfile.avail_out < BUFSIZE)
-        {
-            if(file->write(buf, BUFSIZE - zfile.avail_out) != int(BUFSIZE - zfile.avail_out))
-                return false;
-        }
-        zfile.next_out = buf;
-        zfile.avail_out = BUFSIZE;
-        return true;
-    }
-
-    int write(const void *buf, int len)
-    {
-        if(!writing || !buf || !len) return 0;
-        zfile.next_in = (Bytef *)buf;
-        zfile.avail_in = len;
-        while(zfile.avail_in > 0)
-        {
-            if(!zfile.avail_out && !flush()) { stopwriting(); break; }
-            int err = deflate(&zfile, Z_NO_FLUSH);
-            if(err != Z_OK) { stopwriting(); break; }
-        }
-        crc = crc32(crc, (Bytef *)buf, len - zfile.avail_in);
-        return len - zfile.avail_in;
-    }
-};
-
-
-stream *openrawfile(const char *filename, const char *mode)
-{
-    const char *found = findfile(filename, mode);
-    if(!found) return NULL;
-    filestream *file = new filestream;
-    if(!file->open(found, mode)) { delete file; return NULL; }
-    return file;
-}
-
-stream *openfile(const char *filename, const char *mode)
-{
-#ifndef STANDALONE
-    stream *s = openzipfile(filename, mode);
-    if(s) return s;
-#endif
-    return openrawfile(filename, mode);
-}
-
-stream *opentempfile(const char *name, const char *mode)
-{
-    const char *found = findfile(name, mode);
-    filestream *file = new filestream;
-    if(!file->opentemp(found ? found : name, mode)) { delete file; return NULL; }
-    return file;
-}
-
-stream *opengzfile(const char *filename, const char *mode, stream *file, int level)
-{
-    stream *source = file ? file : openfile(filename, mode);
-    if(!source) return NULL;
-    gzstream *gz = new gzstream;
-    if(!gz->open(source, mode, !file, level)) { if(!file) delete source; return NULL; }
-    return gz;
-}
-
-char *loadfile(const char *fn, int *size)
-{
-    stream *f = openfile(fn, "rb");
-    if(!f) return NULL;
-    int len = f->size();
-    if(len<=0) { delete f; return NULL; }
-    char *buf = new char[len+1];
-    if(!buf) { delete f; return NULL; }
-    buf[len] = 0;
-    int rlen = f->read(buf, len);
-    delete f;
-    if(len!=rlen)
-    {
-        delete[] buf;
-        return NULL;
-    }
-    if(size!=NULL) *size = len;
-    return buf;
-}
-
diff --git a/shared/tools.cpp b/shared/tools.cpp
index 76305c4..f451b21 100644
--- a/shared/tools.cpp
+++ b/shared/tools.cpp
@@ -1,6 +1,284 @@
 // implementation of generic tools
 
-#include "cube.h"
+#include "pch.h"
+#include "tools.h"
+
+///////////////////////// file system ///////////////////////
+
+#ifndef WIN32
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+#endif
+
+string homedir = "";
+vector<char *> packagedirs;
+
+char *makerelpath(const char *dir, const char *file, const char *prefix)
+{
+    static string tmp;
+    if(prefix) s_strcpy(tmp, prefix);
+    else tmp[0] = '\0';
+    if(file[0]=='<')
+    {
+        const char *end = strrchr(file, '>');
+        if(end)
+        {
+            size_t len = strlen(tmp);
+            s_strncpy(&tmp[len], file, min(sizeof(tmp)-len, size_t(end+2-file)));
+            file = end+1;
+        }
+    }
+    s_sprintfd(pname)("%s/%s", dir, file);
+    s_strcat(tmp, pname);
+    return tmp;
+}
+
+char *path(char *s)
+{
+    for(char *curpart = s;;)
+    {
+        char *endpart = strchr(curpart, '&');
+        if(endpart) *endpart = '\0';
+        if(curpart[0]=='<')
+        {
+            char *file = strrchr(curpart, '>');
+            if(!file) return s;
+            curpart = file+1;
+        }
+        for(char *t = curpart; (t = strpbrk(t, "/\\")); *t++ = PATHDIV);
+        for(char *prevdir = NULL, *curdir = s;;)
+        {
+            prevdir = curdir[0]==PATHDIV ? curdir+1 : curdir;
+            curdir = strchr(prevdir, PATHDIV);
+            if(!curdir) break;
+            if(prevdir+1==curdir && prevdir[0]=='.')
+            {
+                memmove(prevdir, curdir+1, strlen(curdir+1)+1);
+                curdir = prevdir;
+            }
+            else if(curdir[1]=='.' && curdir[2]=='.' && curdir[3]==PATHDIV) 
+            {
+                if(prevdir+2==curdir && prevdir[0]=='.' && prevdir[1]=='.') continue;
+                memmove(prevdir, curdir+4, strlen(curdir+4)+1); 
+                curdir = prevdir;
+            }
+        }
+        if(endpart)
+        {
+            *endpart = '&';
+            curpart = endpart+1;
+        }
+        else break;
+    }
+    return s;
+}
+
+char *path(const char *s, bool copy)
+{
+    static string tmp;
+    s_strcpy(tmp, s);
+    path(tmp);
+    return tmp;
+}
+
+const char *parentdir(const char *directory)
+{
+    const char *p = strrchr(directory, '/');
+    if(!p) p = strrchr(directory, '\\');
+    if(!p) p = directory;
+    static string parent;
+    size_t len = p-directory+1;
+    s_strncpy(parent, directory, len);
+    return parent;
+}
+
+bool fileexists(const char *path, const char *mode)
+{
+    bool exists = true;
+    if(mode[0]=='w' || mode[0]=='a') path = parentdir(path);
+#ifdef WIN32
+    if(GetFileAttributes(path) == INVALID_FILE_ATTRIBUTES) exists = false;
+#else
+    if(access(path, R_OK | (mode[0]=='w' || mode[0]=='a' ? W_OK : 0)) == -1) exists = false;
+#endif
+    return exists;
+}
+
+bool createdir(const char *path)
+{
+    size_t len = strlen(path);
+    if(path[len-1]==PATHDIV)
+    {
+        static string strip;
+        path = s_strncpy(strip, path, len);
+    }
+#ifdef WIN32
+    return CreateDirectory(path, NULL)!=0;
+#else
+    return mkdir(path, 0777)==0;
+#endif
+}
+
+static void fixdir(char *dir)
+{
+    path(dir);
+    size_t len = strlen(dir);
+    if(dir[len-1]!=PATHDIV)
+    {
+        dir[len] = PATHDIV;
+        dir[len+1] = '\0';
+    }
+}
+
+void sethomedir(const char *dir)
+{
+    fixdir(s_strcpy(homedir, dir));
+}
+
+void addpackagedir(const char *dir)
+{
+    fixdir(packagedirs.add(newstringbuf(dir)));
+}
+ 
+const char *findfile(const char *filename, const char *mode)
+{
+    static string s;
+    if(homedir[0])
+    {
+        s_sprintf(s)("%s%s", homedir, filename);
+        if(fileexists(s, mode)) return s;
+        if(mode[0]=='w' || mode[0]=='a')
+        {
+            string dirs;
+            s_strcpy(dirs, s);
+            char *dir = strchr(dirs[0]==PATHDIV ? dirs+1 : dirs, PATHDIV);
+            while(dir)
+            {
+                *dir = '\0';
+                if(!fileexists(dirs, "r") && !createdir(dirs)) return s;
+                *dir = PATHDIV;
+                dir = strchr(dir+1, PATHDIV);
+            }
+            return s;
+        } 
+    }
+    if(mode[0]=='w' || mode[0]=='a') return filename;
+    loopv(packagedirs)
+    {
+        s_sprintf(s)("%s%s", packagedirs[i], filename);
+        if(fileexists(s, mode)) return s;
+    }
+    return filename;
+}
+
+FILE *openfile(const char *filename, const char *mode)
+{
+    const char *found = findfile(filename, mode);
+    if(!found) return NULL;
+    return fopen(found, mode);
+}
+
+gzFile opengzfile(const char *filename, const char *mode)
+{
+    const char *found = findfile(filename, mode);
+    if(!found) return NULL;
+    return gzopen(found, mode);
+}
+
+char *loadfile(const char *fn, int *size)
+{
+    FILE *f = openfile(fn, "rb");
+    if(!f) return NULL;
+    fseek(f, 0, SEEK_END);
+    int len = ftell(f);
+    if(len<=0) { fclose(f); return NULL; }
+    fseek(f, 0, SEEK_SET);
+    char *buf = new char[len+1];
+    if(!buf) { fclose(f); return NULL; }
+    buf[len] = 0;
+    size_t rlen = fread(buf, 1, len, f);
+    fclose(f);
+    if(size_t(len)!=rlen)
+    {
+        delete[] buf;
+        return NULL;
+    }
+    if(size!=NULL) *size = len;
+    return buf;
+}
+
+bool listdir(const char *dir, const char *ext, vector<char *> &files)
+{
+    int extsize = ext ? (int)strlen(ext)+1 : 0;
+    #if defined(WIN32)
+    s_sprintfd(pathname)("%s\\*.%s", dir, ext ? ext : "*");
+    WIN32_FIND_DATA FindFileData;
+    HANDLE Find = FindFirstFile(path(pathname), &FindFileData);
+    if(Find != INVALID_HANDLE_VALUE)
+    {
+        do {
+            files.add(newstring(FindFileData.cFileName, (int)strlen(FindFileData.cFileName) - extsize));
+        } while(FindNextFile(Find, &FindFileData));
+        return true;
+    }
+    #else
+    string pathname;
+    s_strcpy(pathname, dir);
+    DIR *d = opendir(path(pathname));
+    if(d)
+    {
+        struct dirent *de;
+        while((de = readdir(d)) != NULL)
+        {
+            if(!ext) files.add(newstring(de->d_name));
+            else
+            {
+                int namelength = (int)strlen(de->d_name) - extsize;
+                if(namelength > 0 && de->d_name[namelength] == '.' && strncmp(de->d_name+namelength+1, ext, extsize-1)==0)
+                    files.add(newstring(de->d_name, namelength));
+            }
+        }
+        closedir(d);
+        return true;
+    }
+    #endif
+    else return false;
+}
+
+int listfiles(const char *dir, const char *ext, vector<char *> &files)
+{
+    int dirs = 0;
+    if(listdir(dir, ext, files)) dirs++;
+    string s;
+    if(homedir[0])
+    {
+        s_sprintf(s)("%s%s", homedir, dir);
+        if(listdir(s, ext, files)) dirs++;
+    }
+    loopv(packagedirs)
+    {
+        s_sprintf(s)("%s%s", packagedirs[i], dir);
+        if(listdir(s, ext, files)) dirs++;
+    }
+    return dirs;
+}
+
+///////////////////////// misc tools ///////////////////////
+
+void endianswap(void *memory, int stride, int length)   // little endian as storage format
+{
+    static const int littleendian = 1;
+    if(!*(const char *)&littleendian) 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;
+    }
+}
+
 
 ////////////////////////// rnd numbers ////////////////////////////////////////
 
@@ -48,4 +326,3 @@ uint randomMT(void)
     y ^= (y << 15) & 0xEFC60000U;
     return(y ^ (y >> 18));
 }
-
diff --git a/shared/tools.h b/shared/tools.h
index 44eda90..05f14db 100644
--- a/shared/tools.h
+++ b/shared/tools.h
@@ -51,7 +51,6 @@ static inline T min(T a, T b)
 
 #define clamp(a,b,c) (max(b, min(a, c)))
 #define rnd(x) ((int)(randomMT()&0xFFFFFF)%(x))
-#define rndscale(x) (float((randomMT()&0xFFFFFF)*double(x)/double(0xFFFFFF)))
 #define detrnd(s, x) ((int)(((((uint)(s))*1103515245+12345)>>16)%(x)))
 
 #define loop(v,m) for(int v = 0; v<int(m); v++)
@@ -97,31 +96,34 @@ static inline T min(T a, T b)
 #define MAXSTRLEN 260
 typedef char string[MAXSTRLEN];
 
-inline void vformatstring(char *d, const char *fmt, va_list v, int len = MAXSTRLEN) { _vsnprintf(d, len, fmt, v); d[len-1] = 0; }
-inline char *copystring(char *d, const char *s, size_t len = MAXSTRLEN) { strncpy(d, s, len); d[len-1] = 0; return d; }
-inline char *concatstring(char *d, const char *s) { size_t len = strlen(d); return copystring(d+len, s, MAXSTRLEN-len); }
+inline void formatstring(char *d, const char *fmt, va_list v) { _vsnprintf(d, MAXSTRLEN, fmt, v); d[MAXSTRLEN-1] = 0; }
+inline char *s_strncpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[m-1] = 0; return d; }
+inline char *s_strcpy(char *d, const char *s) { return s_strncpy(d,s,MAXSTRLEN); }
+inline char *s_strcat(char *d, const char *s) { size_t n = strlen(d); return s_strncpy(d+n,s,MAXSTRLEN-n); }
 
-struct stringformatter
+
+struct s_sprintf_f
 {
-    char *buf;
-    stringformatter(char *buf): buf((char *)buf) {}
-    void operator()(const char *fmt, ...)
+    char *d;
+    s_sprintf_f(char *str): d(str) {}
+    void operator()(const char* fmt, ...)
     {
         va_list v;
         va_start(v, fmt);
-        vformatstring(buf, fmt, v);
+        formatstring(d, fmt, v);
         va_end(v);
     }
 };
 
-#define formatstring(d) stringformatter((char *)d)
-#define defformatstring(d) string d; formatstring(d)
-#define defvformatstring(d,last,fmt) string d; { va_list ap; va_start(ap, last); vformatstring(d, fmt, ap); va_end(ap); }
+#define s_sprintf(d) s_sprintf_f((char *)d)
+#define s_sprintfd(d) string d; s_sprintf(d)
+#define s_sprintfdlv(d,last,fmt) string d; { va_list ap; va_start(ap, last); formatstring(d, fmt, ap); va_end(ap); }
+#define s_sprintfdv(d,fmt) s_sprintfdlv(d,fmt,fmt)
 
-#define loopv(v)    for(int i = 0; i<(v).length(); i++)
-#define loopvj(v)   for(int j = 0; j<(v).length(); j++)
-#define loopvk(v)   for(int k = 0; k<(v).length(); k++)
-#define loopvrev(v) for(int i = (v).length()-1; i>=0; i--)
+#define loopv(v)    if(false) {} else for(int i = 0; i<(v).length(); i++)
+#define loopvj(v)   if(false) {} else for(int j = 0; j<(v).length(); j++)
+#define loopvk(v)   if(false) {} else for(int k = 0; k<(v).length(); k++)
+#define loopvrev(v) if(false) {} else for(int i = (v).length()-1; i>=0; i--)
 
 template <class T>
 struct databuf
@@ -136,8 +138,6 @@ struct databuf
     int len, maxlen;
     uchar flags;
 
-    databuf() : buf(NULL), len(0), maxlen(0), flags(0) {}
-
     template<class U> 
     databuf(T *buf, U maxlen) : buf(buf), len(0), maxlen((int)maxlen), flags(0) {}
 
@@ -193,64 +193,6 @@ struct databuf
 typedef databuf<char> charbuf;
 typedef databuf<uchar> ucharbuf;
 
-struct packetbuf : ucharbuf
-{
-    ENetPacket *packet;
-    int growth;
-
-    packetbuf(ENetPacket *packet) : ucharbuf(packet->data, packet->dataLength), packet(packet), growth(0) {}
-    packetbuf(int growth, int pflags = 0) : growth(growth)
-    {
-        packet = enet_packet_create(NULL, growth, pflags);
-        buf = (uchar *)packet->data;
-        maxlen = packet->dataLength;
-    }
-    ~packetbuf() { cleanup(); }
-
-    void reliable() { packet->flags |= ENET_PACKET_FLAG_RELIABLE; }
-
-    void resize(int n)
-    {
-        enet_packet_resize(packet, n);
-        buf = (uchar *)packet->data;
-        maxlen = packet->dataLength;
-    }
-
-    void checkspace(int n)
-    {
-        if(len + n > maxlen && packet && growth > 0) resize(max(len + n, maxlen + growth));    
-    }
-
-    ucharbuf subbuf(int sz)
-    {
-        checkspace(sz);
-        return ucharbuf::subbuf(sz);
-    }
-
-    void put(const uchar &val)
-    {
-        checkspace(1);
-        ucharbuf::put(val);
-    }
-
-    void put(const uchar *vals, int numvals)
-    {
-        checkspace(numvals);
-        ucharbuf::put(vals, numvals);
-    }
-    
-    ENetPacket *finalize()
-    {
-        resize(len);
-        return packet;
-    }
-
-    void cleanup()
-    {
-        if(growth > 0 && packet && !packet->referenceCount) { enet_packet_destroy(packet); packet = NULL; buf = NULL; len = maxlen = 0; }
-    }
-};
-
 template <class T> struct vector
 {
     static const int MINSIZE = 8;
@@ -323,7 +265,6 @@ template <class T> struct vector
     void drop() { buf[--ulen].~T(); }
     bool empty() const { return ulen==0; }
 
-    int capacity() const { return alen; }
     int length() const { return ulen; }
     T &operator[](int i) { ASSERT(i>=0 && i<ulen); return buf[i]; }
     const T &operator[](int i) const { ASSERT(i >= 0 && i<ulen); return buf[i]; }
@@ -336,7 +277,6 @@ template <class T> struct vector
     
     T *getbuf() { return buf; }
     const T *getbuf() const { return buf; }
-    bool inbuf(const T *e) const { return e >= buf && e < &buf[ulen]; }
 
     template<class ST>
     void sort(int (__cdecl *cf)(ST *, ST *), int i = 0, int n = -1) 
@@ -396,14 +336,6 @@ template <class T> struct vector
         return e;
     }
 
-    T removeunordered(int i)
-    {
-        T e = buf[i];
-        ulen--;
-        if(ulen>0) buf[i] = buf[ulen];
-        return e;
-    }
-
     template<class U>
     int find(const U &o)
     {
@@ -508,7 +440,6 @@ template <class K, class T> struct hashtable
     ~hashtable()
     {
         DELETEA(table);
-        deletechunks();
     }
 
     chain *insert(const K &key, uint h)
@@ -531,30 +462,29 @@ template <class K, class T> struct hashtable
         return c;
     }
 
-    #define HTFIND(success, fail) \
-        uint h = hthash(key)&(size-1); \
-        for(chain *c = table[h]; c; c = c->next) \
-        { \
-            if(htcmp(key, c->key)) return (success); \
-        } \
-        return (fail);
-
-    T *access(const K &key)
+    chain *find(const K &key, bool doinsert)
     {
-        HTFIND(&c->data, NULL);
+        uint h = hthash(key)&(size-1);
+        for(chain *c = table[h]; c; c = c->next)
+        {
+            if(htcmp(key, c->key)) return c;
+        }
+        if(doinsert) return insert(key, h);
+        return NULL;
     }
 
-    T &access(const K &key, const T &data)
+    T *access(const K &key, const T *data = NULL)
     {
-        HTFIND(c->data, insert(key, h)->data = data);
+        chain *c = find(key, data != NULL);
+        if(data) c->data = *data;
+        if(c) return &c->data;
+        return NULL;
     }
 
     T &operator[](const K &key)
     {
-        HTFIND(c->data, insert(key, h)->data);
+        return find(key, true)->data;
     }
-
-    #undef HTFIND
    
     bool remove(const K &key)
     {
@@ -569,7 +499,7 @@ template <class K, class T> struct hashtable
                 new (&c->data) T;
                 new (&c->key) K;
                 c->next = unused;
-                unused = c;
+                unused = c->next;
                 numelems--;
                 return true;
             }
@@ -577,27 +507,22 @@ template <class K, class T> struct hashtable
         return false;
     }
 
-    void deletechunks()
-    {
-        for(chainchunk *nextchunk; chunks; chunks = nextchunk)
-        {
-            nextchunk = chunks->next;
-            delete chunks;
-        }
-    }
-
     void clear()
     {
         if(!numelems) return;
         loopi(size) table[i] = NULL;
         numelems = 0;
         unused = NULL;
-        deletechunks();
+        for(chainchunk *nextchunk; chunks; chunks = nextchunk)
+        {
+            nextchunk = chunks->next;
+            delete chunks;
+        }
     }
 };
 
-#define enumeratekt(ht,k,e,t,f,b) loopi((ht).size)  for(hashtable<k,t>::chain *enumc = (ht).table[i]; enumc;) { hashtable<k,t>::const_key &e = enumc->key; t &f = enumc->data; enumc = enumc->next; b; }
-#define enumerate(ht,t,e,b)       loopi((ht).size) for((ht).enumc = (ht).table[i]; (ht).enumc;) { t &e = (ht).enumc->data;  (ht).enumc = (ht).enumc->next; b; }
+#define enumeratekt(ht,k,e,t,f,b) loopi((ht).size)  for(hashtable<k,t>::chain *enumc = (ht).table[i]; enumc; enumc = enumc->next) { hashtable<k,t>::const_key &e = enumc->key; t &f = enumc->data; b; }
+#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; }
 
 struct unionfind
 {
@@ -657,10 +582,11 @@ template <class T, int SIZE> struct ringbuf
 
     T &add(const T &e)
     {
-        T &t = (data[index] = e);
+        T &t = data[index];
+        t = e;
         index++;
-        if(index >= SIZE) index -= SIZE;
-        if(len < SIZE) len++;
+        if(index>=SIZE) index = 0;
+        if(len<SIZE) len++;
         return t;
     }
 
@@ -668,57 +594,25 @@ template <class T, int SIZE> struct ringbuf
 
     T &operator[](int i)
     {
-        i += index - len;
-        return data[i < 0 ? i + SIZE : i%SIZE];
+        int start = index - len;
+        if(start < 0) start += SIZE;
+        i += start;
+        if(i >= SIZE) i -= SIZE;
+        return data[i];
     }
 
     const T &operator[](int i) const
     {
-        i += index - len;
-        return data[i < 0 ? i + SIZE : i%SIZE];
-    }
-};
-
-template <class T, int SIZE> struct queue
-{
-    int head, tail, len;
-    T data[SIZE];
-    
-    queue() { clear(); }
-    
-    void clear() { head = tail = len = 0; }
-
-    int length() const { return len; }
-    bool empty() const { return !len; }
-    bool full() const { return len == SIZE; }
-
-    T &added() { return data[tail > 0 ? tail-1 : SIZE-1]; }
-    T &added(int offset) { return data[tail-offset > 0 ? tail-offset-1 : tail-offset-1 + SIZE]; }
-    T &adding() { return data[tail]; }
-    T &adding(int offset) { return data[tail+offset >= SIZE ? tail+offset - SIZE : tail+offset]; }
-    T &add()
-    {
-        ASSERT(len < SIZE);    
-        T &t = data[tail];
-        tail = (tail + 1)%SIZE;
-        len++;
-        return t;
-    }
-
-    T &removing() { return data[head]; }
-    T &removing(int offset) { return data[head+offset >= SIZE ? head+offset - SIZE : head+offset]; }
-    T &remove()
-    {
-        ASSERT(len > 0);
-        T &t = data[head];
-        head = (head + 1)%SIZE;
-        len--;
-        return t;
+        int start = index - len;
+        if(start < 0) start += SIZE;
+        i += start;
+        if(i >= SIZE) i -= SIZE;
+        return data[i];
     }
 };
 
 inline char *newstring(size_t l)                { return new char[l+1]; }
-inline char *newstring(const char *s, size_t l) { return copystring(newstring(l), s, l+1); }
+inline char *newstring(const char *s, size_t l) { return s_strncpy(newstring(l), s, l+1); }
 inline char *newstring(const char *s)           { return newstring(s, strlen(s));          }
 inline char *newstringbuf(const char *s)        { return newstring(s, MAXSTRLEN-1);       }
 
@@ -732,88 +626,21 @@ inline void __cdecl operator delete(void *p, const char *fn, int l) { ::operator
 #endif 
 #endif
 
-const int islittleendian = 1;
-#ifdef SDL_BYTEORDER
-#define endianswap16 SDL_Swap16
-#define endianswap32 SDL_Swap32
-#else
-inline ushort endianswap16(ushort n) { return (n<<8) | (n>>8); }
-inline uint endianswap32(uint n) { return (n<<24) | (n>>24) | ((n>>8)&0xFF00) | ((n<<8)&0xFF0000); }
-#endif
-template<class T> inline T endianswap(T n) { union { T t; uint i; } conv; conv.t = n; conv.i = endianswap32(conv.i); return conv.t; }
-template<> inline ushort endianswap<ushort>(ushort n) { return endianswap16(n); }
-template<> inline short endianswap<short>(short n) { return endianswap16(n); }
-template<> inline uint endianswap<uint>(uint n) { return endianswap32(n); }
-template<> inline int endianswap<int>(int n) { return endianswap32(n); }
-template<class T> inline void endianswap(T *buf, int len) { for(T *end = &buf[len]; buf < end; buf++) *buf = endianswap(*buf); }
-template<> inline void endianswap(float *buf, int len) { uint *src = (uint *)buf; for(uint *end = &src[len]; src < end; src++) *src = endianswap(*src); }
-template<class T> inline T endiansame(T n) { return n; }
-template<class T> inline void endiansame(T *buf, int len) {}
-#ifdef SDL_BYTEORDER
-#if SDL_BYTEORDER == SDL_LIL_ENDIAN
-#define lilswap endiansame
-#define bigswap endianswap
-#else
-#define lilswap endianswap
-#define bigswap endiansame
-#endif
-#else
-template<class T> inline T lilswap(T n) { return *(const uchar *)&islittleendian ? n : endianswap(n); }
-template<class T> inline void lilswap(T *buf, int len) { if(!*(const uchar *)&islittleendian) endianswap(buf, len); }
-template<class T> inline T bigswap(T n) { return *(const uchar *)&islittleendian ? endianswap(n) : n; }
-template<class T> inline void bigswap(T *buf, int len) { if(*(const uchar *)&islittleendian) endianswap(buf, len); }
-#endif
-
-struct stream
-{
-    virtual ~stream() {}
-    virtual void close() = 0;
-    virtual bool end() = 0;
-    virtual long tell() { return -1; }
-    virtual bool seek(long offset, int whence = SEEK_SET) { return false; }
-    virtual long size();
-    virtual int read(void *buf, int len) { return 0; }
-    virtual int write(const void *buf, int len) { return 0; }
-    virtual int getchar() { uchar c; return read(&c, 1) == 1 ? c : -1; }
-    virtual bool putchar(int n) { uchar c = n; return write(&c, 1) == 1; }
-    virtual bool getline(char *str, int len);
-    virtual bool putstring(const char *str) { int len = strlen(str); return write(str, len) == len; }
-    virtual bool putline(const char *str) { return putstring(str) && putchar('\n'); }
-    virtual int printf(const char *fmt, ...) { return -1; }
-    virtual uint getcrc() { return 0; }
-
-    template<class T> bool put(T n) { return write(&n, sizeof(n)) == sizeof(n); }
-    template<class T> bool putlil(T n) { return put<T>(lilswap(n)); }
-    template<class T> bool putbig(T n) { return put<T>(bigswap(n)); }
-
-    template<class T> T get() { T n; return read(&n, sizeof(n)) == sizeof(n) ? n : 0; }
-    template<class T> T getlil() { return lilswap(get<T>()); }
-    template<class T> T getbig() { return bigswap(get<T>()); }
-
-#ifndef STANDALONE
-    SDL_RWops *rwops();
-#endif
-};
-
-extern char *makerelpath(const char *dir, const char *file, const char *prefix = NULL, const char *cmd = NULL);
+extern char *makerelpath(const char *dir, const char *file, const char *prefix = NULL);
 extern char *path(char *s);
 extern char *path(const char *s, bool copy);
 extern const char *parentdir(const char *directory);
 extern bool fileexists(const char *path, const char *mode);
 extern bool createdir(const char *path);
-extern size_t fixpackagedir(char *dir);
 extern void sethomedir(const char *dir);
 extern void addpackagedir(const char *dir);
 extern const char *findfile(const char *filename, const char *mode);
-extern stream *openrawfile(const char *filename, const char *mode);
-extern stream *openzipfile(const char *filename, const char *mode);
-extern stream *openfile(const char *filename, const char *mode);
-extern stream *opentempfile(const char *filename, const char *mode);
-extern stream *opengzfile(const char *filename, const char *mode, stream *file = NULL, int level = Z_BEST_COMPRESSION);
+extern FILE *openfile(const char *filename, const char *mode);
+extern gzFile opengzfile(const char *filename, const char *mode);
 extern char *loadfile(const char *fn, int *size);
 extern bool listdir(const char *dir, const char *ext, vector<char *> &files);
 extern int listfiles(const char *dir, const char *ext, vector<char *> &files);
-extern int listzipfiles(const char *dir, const char *ext, vector<char *> &files);
+extern void endianswap(void *, int, int);
 extern void seedMT(uint seed);
 extern uint randomMT(void);
 
diff --git a/shared/zip.cpp b/shared/zip.cpp
deleted file mode 100644
index 7c3b517..0000000
--- a/shared/zip.cpp
+++ /dev/null
@@ -1,557 +0,0 @@
-#include "cube.h"
-
-enum
-{
-    ZIP_LOCAL_FILE_SIGNATURE = 0x04034B50,
-    ZIP_LOCAL_FILE_SIZE      = 30,
-    ZIP_FILE_SIGNATURE       = 0x02014B50,
-    ZIP_FILE_SIZE            = 46,
-    ZIP_DIRECTORY_SIGNATURE  = 0x06054B50,
-    ZIP_DIRECTORY_SIZE       = 22
-};
-
-struct ziplocalfileheader
-{
-    uint signature;
-    ushort version, flags, compression, modtime, moddate;
-    uint crc32, compressedsize, uncompressedsize;
-    ushort namelength, extralength;
-};
-
-struct zipfileheader
-{
-    uint signature;
-    ushort version, needversion, flags, compression, modtime, moddate;
-    uint crc32, compressedsize, uncompressedsize; 
-    ushort namelength, extralength, commentlength, disknumber, internalattribs;
-    uint externalattribs, offset;
-};
-
-struct zipdirectoryheader
-{
-    uint signature;
-    ushort disknumber, directorydisk, diskentries, entries;
-    uint size, offset;
-    ushort commentlength;
-};
-
-struct zipfile
-{
-    char *name;
-    uint header, offset, size, compressedsize;
-
-    zipfile() : name(NULL), header(0), offset(~0U), size(0), compressedsize(0)
-    {
-    }
-    ~zipfile() 
-    { 
-        DELETEA(name); 
-    }
-};
-
-struct zipstream;
-
-struct ziparchive
-{
-    char *name;
-    FILE *data;
-    hashtable<const char *, zipfile> files;
-    int openfiles;
-    zipstream *owner;
-
-    ziparchive() : name(NULL), data(NULL), files(512), openfiles(0), owner(NULL)
-    {
-    }
-    ~ziparchive()
-    {
-        DELETEA(name);
-        if(data) { fclose(data); data = NULL; }
-    }
-};
-
-static bool findzipdirectory(FILE *f, zipdirectoryheader &hdr)
-{
-    if(fseek(f, 0, SEEK_END) < 0) return false;
-
-    uchar buf[1024], *src = NULL;
-    int len = 0, offset = ftell(f), end = max(offset - 0xFFFF - ZIP_DIRECTORY_SIZE, 0);
-    const uint signature = lilswap<uint>(ZIP_DIRECTORY_SIGNATURE);
-
-    while(offset > end)
-    {
-        int carry = min(len, ZIP_DIRECTORY_SIZE-1), next = min((int)sizeof(buf) - carry, offset - end);
-        offset -= next;
-        memmove(&buf[next], buf, carry);
-        if(next + carry < ZIP_DIRECTORY_SIZE || fseek(f, offset, SEEK_SET) < 0 || (int)fread(buf, 1, next, f) != next) return false;
-        len = next + carry;
-        uchar *search = &buf[next-1];
-        for(; search >= buf; search--) if(*(uint *)search == signature) break; 
-        if(search >= buf) { src = search; break; }
-    }        
-
-    if(&buf[len] - src < ZIP_DIRECTORY_SIZE) return false;
-
-    hdr.signature = lilswap(*(uint *)src); src += 4;
-    hdr.disknumber = lilswap(*(ushort *)src); src += 2;
-    hdr.directorydisk = lilswap(*(ushort *)src); src += 2;
-    hdr.diskentries = lilswap(*(ushort *)src); src += 2;
-    hdr.entries = lilswap(*(ushort *)src); src += 2;
-    hdr.size = lilswap(*(uint *)src); src += 4;
-    hdr.offset = lilswap(*(uint *)src); src += 4;
-    hdr.commentlength = lilswap(*(ushort *)src); src += 2;
-
-    if(hdr.signature != ZIP_DIRECTORY_SIGNATURE || hdr.disknumber != hdr.directorydisk || hdr.diskentries != hdr.entries) return false;
-
-    return true;
-}
-
-#ifndef STANDALONE
-VAR(dbgzip, 0, 0, 1);
-#endif
-
-static bool readzipdirectory(const char *archname, FILE *f, int entries, int offset, int size, vector<zipfile> &files)
-{
-    uchar *buf = new uchar[size], *src = buf;
-    if(fseek(f, offset, SEEK_SET) < 0 || (int)fread(buf, 1, size, f) != size) { delete[] buf; return false; }
-    loopi(entries)
-    {
-        if(src + ZIP_FILE_SIZE > &buf[size]) break;
-
-        zipfileheader hdr;
-        hdr.signature = lilswap(*(uint *)src); src += 4;
-        hdr.version = lilswap(*(ushort *)src); src += 2;
-        hdr.needversion = lilswap(*(ushort *)src); src += 2;
-        hdr.flags = lilswap(*(ushort *)src); src += 2;
-        hdr.compression = lilswap(*(ushort *)src); src += 2;
-        hdr.modtime = lilswap(*(ushort *)src); src += 2;
-        hdr.moddate = lilswap(*(ushort *)src); src += 2;
-        hdr.crc32 = lilswap(*(uint *)src); src += 4;
-        hdr.compressedsize = lilswap(*(uint *)src); src += 4;
-        hdr.uncompressedsize = lilswap(*(uint *)src); src += 4;
-        hdr.namelength = lilswap(*(ushort *)src); src += 2;
-        hdr.extralength = lilswap(*(ushort *)src); src += 2;
-        hdr.commentlength = lilswap(*(ushort *)src); src += 2;
-        hdr.disknumber = lilswap(*(ushort *)src); src += 2;
-        hdr.internalattribs = lilswap(*(ushort *)src); src += 2;
-        hdr.externalattribs = lilswap(*(uint *)src); src += 4;
-        hdr.offset = lilswap(*(uint *)src); src += 4;
-        if(hdr.signature != ZIP_FILE_SIGNATURE) break;
-        if(!hdr.namelength || !hdr.uncompressedsize || (hdr.compression && (hdr.compression != Z_DEFLATED || !hdr.compressedsize)))
-        {
-            src += hdr.namelength + hdr.extralength + hdr.commentlength;
-            continue;
-        }
-        if(src + hdr.namelength > &buf[size]) break;
-
-        string pname;
-        int namelen = min((int)hdr.namelength, (int)sizeof(pname)-1);
-        memcpy(pname, src, namelen);
-        pname[namelen] = '\0';
-        path(pname);
-        char *name = newstring(pname);
-    
-        zipfile &f = files.add();
-        f.name = name;
-        f.header = hdr.offset;
-        f.size = hdr.uncompressedsize;
-        f.compressedsize = hdr.compression ? hdr.compressedsize : 0;
-#ifndef STANDALONE
-        if(dbgzip) conoutf(CON_DEBUG, "file %s, size %d, compress %d, flags %x", archname, name, hdr.uncompressedsize, hdr.compression, hdr.flags);
-#endif
-
-        src += hdr.namelength + hdr.extralength + hdr.commentlength;
-    }
-    delete[] buf;
-
-    return files.length() > 0;
-}
-
-static bool readlocalfileheader(FILE *f, ziplocalfileheader &h, uint offset)
-{
-    fseek(f, offset, SEEK_SET);
-    uchar buf[ZIP_LOCAL_FILE_SIZE];
-    if(fread(buf, 1, ZIP_LOCAL_FILE_SIZE, f) != ZIP_LOCAL_FILE_SIZE)
-        return false;
-    uchar *src = buf;
-    h.signature = lilswap(*(uint *)src); src += 4;
-    h.version = lilswap(*(ushort *)src); src += 2;
-    h.flags = lilswap(*(ushort *)src); src += 2;
-    h.compression = lilswap(*(ushort *)src); src += 2;
-    h.modtime = lilswap(*(ushort *)src); src += 2;
-    h.moddate = lilswap(*(ushort *)src); src += 2;
-    h.crc32 = lilswap(*(uint *)src); src += 4;
-    h.compressedsize = lilswap(*(uint *)src); src += 4;
-    h.uncompressedsize = lilswap(*(uint *)src); src += 4;
-    h.namelength = lilswap(*(ushort *)src); src += 2;
-    h.extralength = lilswap(*(ushort *)src); src += 2;
-    if(h.signature != ZIP_LOCAL_FILE_SIGNATURE) return false;
-    // h.uncompressedsize or h.compressedsize may be zero - so don't validate
-    return true;
-}
-
-static vector<ziparchive *> archives;
-
-ziparchive *findzip(const char *name)
-{
-    loopv(archives) if(!strcmp(name, archives[i]->name)) return archives[i];
-    return NULL;
-}
-
-static bool checkprefix(vector<zipfile> &files, const char *prefix, int prefixlen)
-{
-    loopv(files)
-    {
-        if(!strncmp(files[i].name, prefix, prefixlen)) return false;
-    }
-    return true;
-}
-
-static void mountzip(ziparchive &arch, vector<zipfile> &files, const char *mountdir, const char *stripdir)
-{
-    string packagesdir = "packages/";
-    path(packagesdir);
-    int striplen = stripdir ? strlen(stripdir) : 0;
-    if(!mountdir && !stripdir) loopv(files)
-    {
-        zipfile &f = files[i];
-        const char *foundpackages = strstr(f.name, packagesdir);
-        if(foundpackages)
-        {
-            if(foundpackages > f.name) 
-            {
-                stripdir = f.name;
-                striplen = foundpackages - f.name;
-            }
-            break;
-        }
-        const char *foundogz = strstr(f.name, ".ogz");
-        if(foundogz)
-        {
-            const char *ogzdir = foundogz;
-            while(--ogzdir >= f.name && *ogzdir != PATHDIV);
-            if(ogzdir < f.name || checkprefix(files, f.name, ogzdir + 1 - f.name))
-            {
-                if(ogzdir >= f.name)
-                {
-                    stripdir = f.name;
-                    striplen = ogzdir + 1 - f.name;
-                }
-                if(!mountdir) mountdir = "packages/base/";
-                break;
-            }
-        }    
-    }
-    string mdir = "", fname;
-    if(mountdir)
-    {
-        copystring(mdir, mountdir);
-        if(fixpackagedir(mdir) <= 1) mdir[0] = '\0';
-    }
-    loopv(files)
-    {
-        zipfile &f = files[i];
-        formatstring(fname)("%s%s", mdir, striplen && !strncmp(f.name, stripdir, striplen) ? &f.name[striplen] : f.name);
-        if(arch.files.access(fname)) continue;
-        char *mname = newstring(fname);
-        zipfile &mf = arch.files[mname];
-        mf = f;
-        mf.name = mname;
-    }
-}
-
-bool addzip(const char *name, const char *mount = NULL, const char *strip = NULL)
-{
-    string pname;
-    copystring(pname, name);
-    path(pname);
-    int plen = strlen(pname);
-    if(plen < 4 || !strchr(&pname[plen-4], '.')) concatstring(pname, ".zip");
-
-    ziparchive *exists = findzip(pname);
-    if(exists) 
-    {
-        conoutf(CON_ERROR, "already added zip %s", pname);
-        return true;
-    }
- 
-    FILE *f = fopen(findfile(pname, "rb"), "rb");
-    if(!f) 
-    {
-        conoutf(CON_ERROR, "could not open file %s", pname);
-        return false;
-    }
-    zipdirectoryheader h;
-    vector<zipfile> files;
-    if(!findzipdirectory(f, h) || !readzipdirectory(pname, f, h.entries, h.offset, h.size, files))
-    {
-        conoutf(CON_ERROR, "could not read directory in zip %s", pname);
-        fclose(f);
-        return false;
-    }
-    
-    ziparchive *arch = new ziparchive;
-    arch->name = newstring(pname);
-    arch->data = f;
-    mountzip(*arch, files, mount, strip);
-    archives.add(arch);
-
-    conoutf("added zip %s", pname);
-    return true;
-} 
-     
-bool removezip(const char *name)
-{
-    const char *pname = path(name, true);
-    ziparchive *exists = findzip(pname);
-    if(!exists)
-    {
-        conoutf(CON_ERROR, "zip %s is not loaded", pname);
-        return false;
-    }
-    if(exists->openfiles)
-    {
-        conoutf(CON_ERROR, "zip %s has open files", pname);
-        return false;
-    }
-    conoutf("removed zip %s", exists->name);
-    archives.removeobj(exists); 
-    delete exists;
-    return true;
-}
-
-struct zipstream : stream
-{
-    enum
-    {
-        BUFSIZE  = 16384
-    };
-
-    ziparchive *arch;
-    zipfile *info;
-    z_stream zfile;
-    uchar *buf;
-    int reading;
-
-    zipstream() : arch(NULL), info(NULL), buf(NULL), reading(-1)
-    {
-        zfile.zalloc = NULL;
-        zfile.zfree = NULL;
-        zfile.opaque = NULL;
-        zfile.next_in = zfile.next_out = NULL;
-        zfile.avail_in = zfile.avail_out = 0;
-    }
-
-    ~zipstream()
-    {
-        close();
-    }
-
-    void readbuf(uint size = BUFSIZE)
-    {
-        if(!zfile.avail_in) zfile.next_in = (Bytef *)buf;
-        size = min(size, uint(&buf[BUFSIZE] - &zfile.next_in[zfile.avail_in]));
-        if(arch->owner != this)
-        {
-            arch->owner = NULL;
-            if(fseek(arch->data, reading, SEEK_SET) >= 0) arch->owner = this;
-            else return;
-        }
-        else if(ftell(arch->data) != reading) *(int *)0 = 0;
-        uint remaining = info->offset + info->compressedsize - reading;
-        int n = arch->owner == this ? fread(zfile.next_in + zfile.avail_in, 1, min(size, remaining), arch->data) : 0;
-        zfile.avail_in += n;
-        reading += n;
-    }
-
-    bool open(ziparchive *a, zipfile *f)
-    {
-        if(f->offset == ~0U)
-        {
-            ziplocalfileheader h;
-            a->owner = NULL;
-            if(!readlocalfileheader(a->data, h, f->header)) return false;
-            f->offset = f->header + ZIP_LOCAL_FILE_SIZE + h.namelength + h.extralength;
-        }
-
-        if(f->compressedsize && inflateInit2(&zfile, -MAX_WBITS) != Z_OK) return false;
-
-        a->openfiles++;
-        arch = a;
-        info = f;
-        reading = f->offset;
-        if(f->compressedsize) buf = new uchar[BUFSIZE];
-        return true;
-    }
-
-    void stopreading()
-    {
-        if(reading < 0) return;
-#ifndef STANDALONE
-        if(dbgzip) conoutf(CON_DEBUG, info->compressedsize ? "%s: zfile.total_out %d, info->size %d" : "%s: reading %d, info->size %d", info->name, info->compressedsize ? zfile.total_out : reading - info->offset, info->size);
-#endif
-        if(info->compressedsize) inflateEnd(&zfile);
-        reading = -1;
-    }
-
-    void close()
-    {
-        stopreading();
-        DELETEA(buf);
-        if(arch) { arch->owner = NULL; arch->openfiles--; arch = NULL; }
-    }
-
-    long size() { return info->size; }
-    bool end() { return reading < 0; }
-    long tell() { return reading >= 0 ? (info->compressedsize ? zfile.total_out : reading - info->offset) : -1; }
-
-    bool seek(long pos, int whence)
-    {
-        if(reading < 0) return false;
-        if(!info->compressedsize)
-        {
-            switch(whence)
-            {
-                case SEEK_END: pos += info->offset + info->size; break; 
-                case SEEK_CUR: pos += reading; break;
-                case SEEK_SET: pos += info->offset; break;
-                default: return false;
-            } 
-            pos = clamp(pos, long(info->offset), long(info->offset + info->size));
-            arch->owner = NULL;
-            if(fseek(arch->data, pos, SEEK_SET) < 0) return false;
-            arch->owner = this;
-            reading = pos;
-            return true;
-        }
- 
-        switch(whence)
-        {
-            case SEEK_END: pos += info->size; break; 
-            case SEEK_CUR: pos += zfile.total_out; break;
-            case SEEK_SET: break;
-            default: return false;
-        }
-
-        if(pos >= (long)info->size)
-        {
-            reading = info->offset + info->compressedsize;
-            zfile.next_in += zfile.avail_in;
-            zfile.avail_in = 0;
-            zfile.total_in = info->compressedsize; 
-            arch->owner = NULL;
-            return true;
-        }
-
-        if(pos < 0) return false;
-        if(pos >= (long)zfile.total_out) pos -= zfile.total_out;
-        else 
-        {
-            if(zfile.next_in && zfile.total_in <= uint(zfile.next_in - buf))
-            {
-                zfile.avail_in += zfile.total_in;
-                zfile.next_in -= zfile.total_in;
-            }
-            else
-            {
-                arch->owner = NULL;
-                zfile.avail_in = 0;
-                zfile.next_in = NULL;
-                reading = info->offset;
-            }
-            inflateReset(&zfile);
-        }
-
-        uchar skip[512];
-        while(pos > 0)
-        {
-            int skipped = min(pos, (long)sizeof(skip));
-            if(read(skip, skipped) != skipped) { stopreading(); return false; }
-            pos -= skipped;
-        }
-
-        return true;
-    }
-
-    int read(void *buf, int len)
-    {
-        if(reading < 0 || !buf || !len) return 0;
-        if(!info->compressedsize)
-        {
-            if(arch->owner != this)
-            {
-                arch->owner = NULL;
-                if(fseek(arch->data, reading, SEEK_SET) < 0) { stopreading(); return 0; }
-                arch->owner = this;
-            }
-              
-            int n = fread(buf, 1, min(len, int(info->size + info->offset - reading)), arch->data);
-            reading += n;
-            if(n < len) stopreading();
-            return n;
-        }
-
-        zfile.next_out = (Bytef *)buf;
-        zfile.avail_out = len;
-        while(zfile.avail_out > 0)
-        {
-            if(!zfile.avail_in) readbuf(BUFSIZE);
-            int err = inflate(&zfile, Z_NO_FLUSH);
-            if(err != Z_OK) 
-            {
-#ifndef STANDALONE
-                if(err != Z_STREAM_END && dbgzip) conoutf(CON_DEBUG, "inflate error: %s", err);
-#endif
-                stopreading(); 
-                break; 
-            }
-        }
-        return len - zfile.avail_out;
-    }
-};
-
-stream *openzipfile(const char *name, const char *mode)
-{
-    for(; *mode; mode++) if(*mode=='w' || *mode=='a') return NULL;
-    loopvrev(archives)
-    {
-        ziparchive *arch = archives[i];
-        zipfile *f = arch->files.access(name);
-        if(!f) continue;
-        zipstream *s = new zipstream;
-        if(s->open(arch, f)) return s;
-        delete s;
-    }
-    return NULL;
-}
-
-int listzipfiles(const char *dir, const char *ext, vector<char *> &files)
-{
-    int extsize = ext ? (int)strlen(ext)+1 : 0, dirsize = strlen(dir), dirs = 0;
-    loopvrev(archives)
-    {
-        ziparchive *arch = archives[i];
-        int oldsize = files.length();
-        enumerate(arch->files, zipfile, f,
-        {
-            if(strncmp(f.name, dir, dirsize)) continue;
-            const char *name = f.name + dirsize;
-            if(name[0] == PATHDIV) name++;
-            if(strchr(name, PATHDIV)) continue;
-            if(!ext) files.add(newstring(name));
-            else
-            {
-                int namelength = (int)strlen(name) - extsize;
-                if(namelength > 0 && name[namelength] == '.' && strncmp(name+namelength+1, ext, extsize-1)==0)
-                    files.add(newstring(name, namelength));
-            }
-        });
-        if(files.length() > oldsize) dirs++;
-    }
-    return dirs;
-}
-
-#ifndef STANDALONE
-ICOMMAND(addzip, "sss", (const char *name, const char *mount, const char *strip), addzip(name, mount[0] ? mount : NULL, strip[0] ? strip : NULL));
-ICOMMAND(removezip, "s", (const char *name), removezip(name));
-#endif
-
diff --git a/vcpp/cube.vcproj b/vcpp/cube.vcproj
new file mode 100644
index 0000000..76fe9d4
--- /dev/null
+++ b/vcpp/cube.vcproj
@@ -0,0 +1,1730 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioProject
+	ProjectType="Visual C++"
+	Version="8.00"
+	Name="sauerbraten"
+	ProjectGUID="{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}"
+	RootNamespace="sauerbraten"
+	>
+	<Platforms>
+		<Platform
+			Name="Win32"
+		/>
+	</Platforms>
+	<ToolFiles>
+	</ToolFiles>
+	<Configurations>
+		<Configuration
+			Name="Release|Win32"
+			OutputDirectory=".\Release"
+			IntermediateDirectory=".\Release"
+			ConfigurationType="1"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TypeLibraryName=".\Release/cube.tlb"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="1"
+				InlineFunctionExpansion="2"
+				EnableIntrinsicFunctions="false"
+				FavorSizeOrSpeed="2"
+				OmitFramePointers="true"
+				AdditionalIncludeDirectories="..\enet\include;..\include;..\engine;..\fpsgame;..\shared"
+				PreprocessorDefinitions="WIN32,NDEBUG,_CONSOLE"
+				StringPooling="true"
+				MinimalRebuild="false"
+				ExceptionHandling="2"
+				RuntimeLibrary="2"
+				BufferSecurityCheck="false"
+				EnableFunctionLevelLinking="false"
+				FloatingPointModel="2"
+				FloatingPointExceptions="false"
+				DisableLanguageExtensions="false"
+				ForceConformanceInForLoopScope="true"
+				UsePrecompiledHeader="0"
+				PrecompiledHeaderThrough="cube.h"
+				PrecompiledHeaderFile=".\Release/cube.pch"
+				AssemblerListingLocation=".\Release/"
+				ObjectFile=".\Release/"
+				ProgramDataBaseFileName=".\Release/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				Detect64BitPortabilityProblems="true"
+				DebugInformationFormat="1"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="2057"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalOptions="/MACHINE:I386"
+				AdditionalDependencies="msvcrt.lib zdll.lib opengl32.lib glu32.lib SDLmain.lib SDL.lib SDL_image.lib ws2_32.lib SDL_mixer.lib winmm.lib dbghelp.lib kernel32.lib user32.lib"
+				OutputFile="..\..\bin\sauerbraten.exe"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="..\lib"
+				GenerateManifest="true"
+				IgnoreAllDefaultLibraries="true"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Release/sauerbraten.pdb"
+				GenerateMapFile="true"
+				MapFileName=".\Release/cube.map"
+				MapExports="true"
+				SubSystem="1"
+				OptimizeReferences="2"
+				EnableCOMDATFolding="2"
+				OptimizeForWindows98="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+				CommandLine="copy release\sauerbraten.pdb ..\.."
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|Win32"
+			OutputDirectory=".\Debug"
+			IntermediateDirectory=".\Debug"
+			ConfigurationType="1"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			WholeProgramOptimization="1"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TypeLibraryName=".\Debug/cube.tlb"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="0"
+				AdditionalIncludeDirectories="..\enet\include;..\include;..\engine;..\fpsgame;..\shared"
+				PreprocessorDefinitions="WIN32,_DEBUG,_CONSOLE"
+				ExceptionHandling="2"
+				BasicRuntimeChecks="1"
+				RuntimeLibrary="3"
+				FloatingPointModel="2"
+				UsePrecompiledHeader="2"
+				PrecompiledHeaderThrough="cube.h"
+				PrecompiledHeaderFile=".\Debug/cube.pch"
+				AssemblerListingLocation=".\Debug/"
+				ObjectFile=".\Debug/"
+				ProgramDataBaseFileName=".\Debug/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="3"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="_DEBUG"
+				Culture="2057"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalOptions="/MACHINE:I386"
+				AdditionalDependencies="zdll.lib opengl32.lib glu32.lib SDLmain.lib SDL.lib SDL_image.lib SDL_mixer.lib ws2_32.lib winmm.lib"
+				OutputFile="..\..\bin\sauerbraten_debug.exe"
+				LinkIncremental="2"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="..\lib"
+				IgnoreAllDefaultLibraries="false"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Debug/cube_debug.pdb"
+				SubSystem="1"
+				HeapReserveSize="0"
+				HeapCommitSize="0"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+		<Configuration
+			Name="Profile|Win32"
+			OutputDirectory="Profile"
+			IntermediateDirectory="Profile"
+			ConfigurationType="1"
+			InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
+			UseOfMFC="0"
+			ATLMinimizesCRunTimeLibraryUsage="false"
+			CharacterSet="2"
+			>
+			<Tool
+				Name="VCPreBuildEventTool"
+			/>
+			<Tool
+				Name="VCCustomBuildTool"
+			/>
+			<Tool
+				Name="VCXMLDataGeneratorTool"
+			/>
+			<Tool
+				Name="VCWebServiceProxyGeneratorTool"
+			/>
+			<Tool
+				Name="VCMIDLTool"
+				TypeLibraryName=".\Release/cube.tlb"
+			/>
+			<Tool
+				Name="VCCLCompilerTool"
+				Optimization="2"
+				InlineFunctionExpansion="0"
+				EnableIntrinsicFunctions="false"
+				FavorSizeOrSpeed="2"
+				OmitFramePointers="true"
+				AdditionalIncludeDirectories="..\enet\include;..\include;..\engine;..\fpsgame;..\shared"
+				PreprocessorDefinitions="WIN32,NDEBUG,_CONSOLE,USE_ENET"
+				StringPooling="true"
+				MinimalRebuild="false"
+				ExceptionHandling="0"
+				RuntimeLibrary="2"
+				BufferSecurityCheck="false"
+				EnableFunctionLevelLinking="false"
+				ForceConformanceInForLoopScope="true"
+				UsePrecompiledHeader="0"
+				PrecompiledHeaderFile=".\Release/cube.pch"
+				AssemblerListingLocation=".\Release/"
+				ObjectFile=".\Release/"
+				ProgramDataBaseFileName=".\Release/"
+				WarningLevel="3"
+				SuppressStartupBanner="true"
+				DebugInformationFormat="3"
+				CompileAs="0"
+			/>
+			<Tool
+				Name="VCManagedResourceCompilerTool"
+			/>
+			<Tool
+				Name="VCResourceCompilerTool"
+				PreprocessorDefinitions="NDEBUG"
+				Culture="2057"
+			/>
+			<Tool
+				Name="VCPreLinkEventTool"
+			/>
+			<Tool
+				Name="VCLinkerTool"
+				AdditionalOptions="/MACHINE:I386"
+				AdditionalDependencies="msvcrt.lib zlib.lib opengl32.lib glu32.lib SDLmain.lib SDL.lib SDL_image.lib ws2_32.lib SDL_mixer.lib"
+				OutputFile="c:\w\sauerbraten\sauerbraten\bin\sauerbraten.exe"
+				LinkIncremental="1"
+				SuppressStartupBanner="true"
+				AdditionalLibraryDirectories="C:\w\cube\lib"
+				IgnoreAllDefaultLibraries="true"
+				GenerateDebugInformation="true"
+				ProgramDatabaseFile=".\Release/cube.pdb"
+				GenerateMapFile="true"
+				MapFileName=".\Release/cube.map"
+				MapExports="true"
+				SubSystem="1"
+				OptimizeForWindows98="1"
+			/>
+			<Tool
+				Name="VCALinkTool"
+			/>
+			<Tool
+				Name="VCManifestTool"
+			/>
+			<Tool
+				Name="VCXDCMakeTool"
+			/>
+			<Tool
+				Name="VCBscMakeTool"
+			/>
+			<Tool
+				Name="VCFxCopTool"
+			/>
+			<Tool
+				Name="VCAppVerifierTool"
+			/>
+			<Tool
+				Name="VCWebDeploymentTool"
+			/>
+			<Tool
+				Name="VCPostBuildEventTool"
+			/>
+		</Configuration>
+	</Configurations>
+	<References>
+	</References>
+	<Files>
+		<Filter
+			Name="Engine"
+			Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+			>
+			<File
+				RelativePath="..\engine\engine.h"
+				>
+			</File>
+			<File
+				RelativePath=".\sauerbraten.rc"
+				>
+			</File>
+			<Filter
+				Name="world"
+				>
+				<File
+					RelativePath="..\engine\cubeloader.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\lightmap.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\lightmap.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\normal.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\octa.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\octa.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\octaedit.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\octarender.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\physics.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\pvs.cpp"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\world.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\world.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\worldio.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+			</Filter>
+			<Filter
+				Name="cs"
+				>
+				<File
+					RelativePath="..\engine\client.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\server.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\serverbrowser.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<Filter
+					Name="enet"
+					>
+					<File
+						RelativePath="..\enet\callbacks.c"
+						>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\host.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\list.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\packet.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\peer.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\protocol.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\unix.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+					<File
+						RelativePath="..\enet\win32.c"
+						>
+						<FileConfiguration
+							Name="Release|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+						<FileConfiguration
+							Name="Debug|Win32"
+							>
+							<Tool
+								Name="VCCLCompilerTool"
+								UsePrecompiledHeader="0"
+							/>
+						</FileConfiguration>
+					</File>
+				</Filter>
+			</Filter>
+			<Filter
+				Name="render"
+				>
+				<File
+					RelativePath="..\engine\3dgui.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\animmodel.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\bih.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\bih.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\decal.cpp"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\dynlight.cpp"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\explosion.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\glare.cpp"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\grass.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\lensflare.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\lightning.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\material.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\md2.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\md3.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\md5.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\model.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\obj.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\rendergl.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\rendermodel.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\renderparticles.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\rendersky.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\rendertarget.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\rendertext.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\renderva.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\shader.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\shadowmap.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\skelmodel.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\textedit.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\texture.cpp"
+					>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\texture.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\vertmodel.h"
+					>
+				</File>
+				<File
+					RelativePath="..\engine\water.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+			</Filter>
+			<Filter
+				Name="system"
+				>
+				<File
+					RelativePath="..\engine\command.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\console.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\main.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							ExceptionHandling="2"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\menus.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+				<File
+					RelativePath="..\engine\sound.cpp"
+					>
+					<FileConfiguration
+						Name="Release|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Debug|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+					<FileConfiguration
+						Name="Profile|Win32"
+						>
+						<Tool
+							Name="VCCLCompilerTool"
+							UsePrecompiledHeader="2"
+							PrecompiledHeaderThrough="pch.h"
+						/>
+					</FileConfiguration>
+				</File>
+			</Filter>
+		</Filter>
+		<Filter
+			Name="Common"
+			Filter="h;hpp;hxx;hm;inl"
+			>
+			<File
+				RelativePath="..\shared\command.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\cube.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\ents.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\geom.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\shared\geom.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\iengine.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\igame.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\pch.cpp"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Profile|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						UsePrecompiledHeader="1"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\shared\pch.h"
+				>
+			</File>
+			<File
+				RelativePath=".\resource.h"
+				>
+			</File>
+			<File
+				RelativePath="..\shared\tools.cpp"
+				>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\shared\tools.h"
+				>
+			</File>
+			<Filter
+				Name="text"
+				Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+				>
+				<File
+					RelativePath="..\OCTA_TODO.txt"
+					>
+				</File>
+				<Filter
+					Name="docs"
+					>
+					<File
+						RelativePath="..\..\docs\config.html"
+						>
+					</File>
+					<File
+						RelativePath="..\..\docs\editing.html"
+						>
+					</File>
+					<File
+						RelativePath="..\..\docs\editref.html"
+						>
+					</File>
+					<File
+						RelativePath="..\..\docs\game.html"
+						>
+					</File>
+					<File
+						RelativePath="..\..\docs\history.html"
+						>
+					</File>
+					<File
+						RelativePath="..\..\README.html"
+						>
+					</File>
+				</Filter>
+				<Filter
+					Name="cfg"
+					>
+					<File
+						RelativePath="..\..\data\brush.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\default_map_models.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\default_map_settings.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\defaults.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\game_fps.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\keymap.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\menus.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\sounds.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\stdlib.cfg"
+						>
+					</File>
+					<File
+						RelativePath="..\..\data\stdshader.cfg"
+						>
+					</File>
+				</Filter>
+			</Filter>
+			<Filter
+				Name="eneth"
+				>
+				<File
+					RelativePath="..\enet\include\enet\enet.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\list.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\protocol.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\time.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\types.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\unix.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\utility.h"
+					>
+				</File>
+				<File
+					RelativePath="..\enet\include\enet\win32.h"
+					>
+				</File>
+			</Filter>
+		</Filter>
+		<Filter
+			Name="FPS"
+			>
+			<File
+				RelativePath="..\fpsgame\assassin.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\capture.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\client.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\ctf.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\entities.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\fps.cpp"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Profile|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\fpsgame\fpsrender.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\fpsserver.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\game.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\monster.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\scoreboard.h"
+				>
+			</File>
+			<File
+				RelativePath="..\fpsgame\weapon.h"
+				>
+			</File>
+		</Filter>
+		<Filter
+			Name="RPG"
+			>
+			<File
+				RelativePath="..\rpggame\entities.h"
+				>
+			</File>
+			<File
+				RelativePath="..\rpggame\rpg.cpp"
+				>
+				<FileConfiguration
+					Name="Release|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Debug|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+				<FileConfiguration
+					Name="Profile|Win32"
+					>
+					<Tool
+						Name="VCCLCompilerTool"
+						PrecompiledHeaderThrough="pch.h"
+					/>
+				</FileConfiguration>
+			</File>
+			<File
+				RelativePath="..\rpggame\rpgent.h"
+				>
+			</File>
+			<File
+				RelativePath="..\rpggame\rpgobj.h"
+				>
+			</File>
+			<File
+				RelativePath="..\rpggame\rpgobjset.h"
+				>
+			</File>
+			<File
+				RelativePath="..\rpggame\stats.h"
+				>
+			</File>
+			<File
+				RelativePath="..\rpggame\stubs.h"
+				>
+			</File>
+			<Filter
+				Name="text"
+				>
+				<File
+					RelativePath="..\..\data\game_rpg.cfg"
+					>
+				</File>
+				<File
+					RelativePath="..\..\docs\rpg.html"
+					>
+				</File>
+				<File
+					RelativePath="..\rpggame\rpg_plan.txt"
+					>
+				</File>
+			</Filter>
+		</Filter>
+	</Files>
+	<Globals>
+		<Global
+			Name="DevPartner_IsInstrumented"
+			Value="0"
+		/>
+	</Globals>
+</VisualStudioProject>
diff --git a/vcpp/cube.vcproj.SMU.21490304.user b/vcpp/cube.vcproj.SMU.21490304.user
new file mode 100644
index 0000000..c06fdb1
--- /dev/null
+++ b/vcpp/cube.vcproj.SMU.21490304.user
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="Windows-1252"?>
+<VisualStudioUserFile
+	ProjectType="Visual C++"
+	Version="8.00"
+	ShowAllFiles="false"
+	>
+	<Configurations>
+		<Configuration
+			Name="Release|Win32"
+			>
+			<DebugSettings
+				Command="$(TargetPath)"
+				WorkingDirectory="C:\w\sauerbraten"
+				CommandArguments="-t -w1280 -h1024 -gfps -lhjhdas"
+				Attach="false"
+				DebuggerType="3"
+				Remote="1"
+				RemoteMachine="XPS2-210"
+				RemoteCommand=""
+				HttpUrl=""
+				PDBPath=""
+				SQLDebugging=""
+				Environment=""
+				EnvironmentMerge="true"
+				DebuggerFlavor="0"
+				MPIRunCommand=""
+				MPIRunArguments=""
+				MPIRunWorkingDirectory=""
+				ApplicationCommand=""
+				ApplicationArguments=""
+				ShimCommand=""
+				MPIAcceptMode=""
+				MPIAcceptFilter=""
+			/>
+		</Configuration>
+		<Configuration
+			Name="Debug|Win32"
+			>
+			<DebugSettings
+				Command="$(TargetPath)"
+				WorkingDirectory="C:\w\sauerbraten"
+				CommandArguments="-t -w1024 -h768 -a4 -grpg"
+				Attach="false"
+				DebuggerType="3"
+				Remote="1"
+				RemoteMachine="XPS2-210"
+				RemoteCommand=""
+				HttpUrl=""
+				PDBPath=""
+				SQLDebugging=""
+				Environment=""
+				EnvironmentMerge="true"
+				DebuggerFlavor="0"
+				MPIRunCommand=""
+				MPIRunArguments=""
+				MPIRunWorkingDirectory=""
+				ApplicationCommand=""
+				ApplicationArguments=""
+				ShimCommand=""
+				MPIAcceptMode=""
+				MPIAcceptFilter=""
+			/>
+		</Configuration>
+		<Configuration
+			Name="Profile|Win32"
+			>
+			<DebugSettings
+				Command="$(TargetPath)"
+				WorkingDirectory=""
+				CommandArguments=""
+				Attach="false"
+				DebuggerType="3"
+				Remote="1"
+				RemoteMachine="XPS2-210"
+				RemoteCommand=""
+				HttpUrl=""
+				PDBPath=""
+				SQLDebugging=""
+				Environment=""
+				EnvironmentMerge="true"
+				DebuggerFlavor=""
+				MPIRunCommand=""
+				MPIRunArguments=""
+				MPIRunWorkingDirectory=""
+				ApplicationCommand=""
+				ApplicationArguments=""
+				ShimCommand=""
+				MPIAcceptMode=""
+				MPIAcceptFilter=""
+			/>
+		</Configuration>
+	</Configurations>
+</VisualStudioUserFile>
diff --git a/vcpp/icon1.ico b/vcpp/icon1.ico
new file mode 100644
index 0000000..722278b
Binary files /dev/null and b/vcpp/icon1.ico differ
diff --git a/vcpp/sauerbraten.nsi b/vcpp/sauerbraten.nsi
new file mode 100644
index 0000000..2b3566a
--- /dev/null
+++ b/vcpp/sauerbraten.nsi
@@ -0,0 +1,64 @@
+Name "Sauerbraten"
+
+OutFile "sauerbraten_2006_xx_xx_setup.exe"
+
+InstallDir $PROGRAMFILES\Sauerbraten
+
+InstallDirRegKey HKLM "Software\Sauerbraten" "Install_Dir"
+
+SetCompressor /SOLID lzma
+XPStyle on
+
+Page components
+Page directory
+Page instfiles
+
+UninstPage uninstConfirm
+UninstPage instfiles
+
+Section "Sauerbraten (required)"
+
+  SectionIn RO
+  
+  SetOutPath $INSTDIR
+  
+  File /r "..\..\*.*"
+  
+  WriteRegStr HKLM SOFTWARE\Sauerbraten "Install_Dir" "$INSTDIR"
+  
+  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Sauerbraten" "DisplayName" "Sauerbraten"
+  WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Sauerbraten" "UninstallString" '"$INSTDIR\uninstall.exe"'
+  WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Sauerbraten" "NoModify" 1
+  WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Sauerbraten" "NoRepair" 1
+  WriteUninstaller "uninstall.exe"
+  
+SectionEnd
+
+Section "Visual C++ redistributable runtime"
+
+  ExecWait '"$INSTDIR\bin\vcredist_x86.exe"'
+  
+SectionEnd
+
+Section "Start Menu Shortcuts"
+
+  CreateDirectory "$SMPROGRAMS\Sauerbraten"
+  
+  SetOutPath "$INSTDIR"
+  
+  CreateShortCut "$INSTDIR\Sauerbraten.lnk"                "$INSTDIR\sauerbraten.bat" "" "$INSTDIR\sauerbraten.bat" 0
+  CreateShortCut "$SMPROGRAMS\Sauerbraten\Sauerbraten.lnk" "$INSTDIR\sauerbraten.bat" "" "$INSTDIR\sauerbraten.bat" 0
+  CreateShortCut "$SMPROGRAMS\Sauerbraten\Uninstall.lnk"   "$INSTDIR\uninstall.exe"   "" "$INSTDIR\uninstall.exe" 0
+  CreateShortCut "$SMPROGRAMS\Sauerbraten\README.lnk"      "$INSTDIR\README.html"     "" "$INSTDIR\README.html" 0
+  
+SectionEnd
+
+Section "Uninstall"
+  
+  DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Sauerbraten"
+  DeleteRegKey HKLM SOFTWARE\Sauerbraten
+
+  RMDir /r "$SMPROGRAMS\Sauerbraten"
+  RMDir /r "$INSTDIR"
+
+SectionEnd
diff --git a/vcpp/sauerbraten.rc b/vcpp/sauerbraten.rc
new file mode 100644
index 0000000..36bd0d5
--- /dev/null
+++ b/vcpp/sauerbraten.rc
@@ -0,0 +1,4 @@
+#include "winres.h"
+
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+IDI_ICON1 ICON "icon1.ico"
diff --git a/vcpp/sauerbraten.sln b/vcpp/sauerbraten.sln
new file mode 100644
index 0000000..974bb1f
--- /dev/null
+++ b/vcpp/sauerbraten.sln
@@ -0,0 +1,23 @@
+
+Microsoft Visual Studio Solution File, Format Version 9.00
+# Visual Studio 2005
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sauerbraten", "cube.vcproj", "{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Profile|Win32 = Profile|Win32
+		Release|Win32 = Release|Win32
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Debug|Win32.ActiveCfg = Debug|Win32
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Debug|Win32.Build.0 = Debug|Win32
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Profile|Win32.ActiveCfg = Profile|Win32
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Profile|Win32.Build.0 = Profile|Win32
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Release|Win32.ActiveCfg = Release|Win32
+		{06594C6D-6DA9-49DC-9A91-8F47221DDCFD}.Release|Win32.Build.0 = Release|Win32
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/vcpp/sauerbraten.suo b/vcpp/sauerbraten.suo
new file mode 100644
index 0000000..b8d669d
Binary files /dev/null and b/vcpp/sauerbraten.suo differ
diff --git a/xcode/ConsoleView.h b/xcode/ConsoleView.h
new file mode 100755
index 0000000..89df0b7
--- /dev/null
+++ b/xcode/ConsoleView.h
@@ -0,0 +1,25 @@
+#import <Cocoa/Cocoa.h>
+
+/*
+ * A basic console view
+ */
+
+ at interface ConsoleView : NSView {
+ at private
+	NSMutableArray *array;
+	NSDictionary *attr;
+	BOOL endScroll;
+}
+- (void)scrollToEnd:(BOOL)enable;
+
+/*
+ * Append the given line
+ */
+- (void)appendLine:(NSString*)line;
+
+/*
+ * Append multiple lines, skip blank ones
+ */
+- (void)appendText:(NSString*)text;
+
+ at end
diff --git a/xcode/ConsoleView.m b/xcode/ConsoleView.m
new file mode 100755
index 0000000..a8c65c3
--- /dev/null
+++ b/xcode/ConsoleView.m
@@ -0,0 +1,120 @@
+#import "ConsoleView.h"
+
+#define MAX_LINES 200
+#define LINE_WIDTH 500
+#define LINE_HEIGHT 12
+
+/*
+ * This VScroller sends a scrollToEnd message to the console based on whether you hit page/line down or not
+ */
+ at interface VScroller : NSScroller {
+}
+ at end
+ at implementation VScroller
+- (NSScrollerPart)hitPart {
+	ConsoleView *view =  (ConsoleView *)[(NSScrollView*)[self superview] documentView];
+	NSScrollerPart part = [super hitPart];
+	[view scrollToEnd:((part == NSScrollerIncrementPage) || (part == NSScrollerIncrementLine))];
+	return part;
+}
+ at end
+
+
+ at implementation ConsoleView
+
+- (id)initWithFrame:(NSRect)frame {
+    self = [super initWithFrame:frame];
+    if (self) {
+		array = [[[NSMutableArray alloc] init] retain];
+		
+		attr = [[NSDictionary dictionaryWithObjectsAndKeys:
+			[NSFont userFontOfSize:(LINE_HEIGHT-2)], NSFontAttributeName, 
+			[NSColor colorWithCalibratedRed:0.2 green:0.8 blue:0.2 alpha:1.0], NSForegroundColorAttributeName, 
+			nil] retain];
+    }
+    return self;
+}
+
+- (void)awakeFromNib {
+	[self scrollToEnd:YES];
+	
+	NSScroller *vscroll = [[VScroller alloc] init];
+	[vscroll setControlSize:[[[self enclosingScrollView] verticalScroller] controlSize]];
+	[[self enclosingScrollView] setVerticalScroller:vscroll];
+}
+
+- (void)dealloc {
+	[attr release];
+	[array release];
+	[super dealloc];
+}
+
+- (BOOL)isFlipped { 
+	return YES; 
+}
+
+- (void)drawRect:(NSRect)rect {	
+	//draw the visible lines only
+	int startLine = rect.origin.y/LINE_HEIGHT;
+	int endLine = 1 + (rect.origin.y+rect.size.height)/LINE_HEIGHT;
+	if(startLine < 0) startLine = 0;
+	if(endLine > [array count]) endLine = [array count];
+	int i;	
+	for(i = startLine; i < endLine; i++) {
+		NSString *str = [array objectAtIndex:i];
+		[str drawAtPoint:NSMakePoint(2, i * LINE_HEIGHT) withAttributes:attr];
+	}
+}
+
+- (void)scrollToEnd:(BOOL)enable {
+	endScroll = enable;
+}
+
+- (void)appendLine:(NSString*)line {
+	BOOL chop = [array count] > MAX_LINES;
+	if(chop) {
+		[array removeObjectAtIndex:0]; // limit the number of lines
+	}
+	[array addObject:line];	
+	int i = [array count];
+	[self setFrame:NSMakeRect(0, 0, LINE_WIDTH, i*LINE_HEIGHT)]; // increase the frame size	
+		
+	NSRect rect = NSMakeRect(0, (i-1)*LINE_HEIGHT, LINE_WIDTH, LINE_HEIGHT);
+	if(endScroll) {
+		// Scroll to the line just added
+		if([self scrollRectToVisible:rect]) return;
+	} else {
+		// Lock the scrolling on the first visible line
+		i = [self visibleRect].origin.y/LINE_HEIGHT;
+		if(!chop) i++;
+		if(i < 0) i = 0; 
+		if(i > [array count]) i = [array count];
+		NSRect vrect = NSMakeRect(0, (i-1)*LINE_HEIGHT, LINE_WIDTH, LINE_HEIGHT);
+		if([self scrollRectToVisible:vrect]) return;
+	}
+	if(chop)
+		[self setNeedsDisplay:YES];
+	else
+		[self setNeedsDisplayInRect:rect];
+}
+
+- (void)appendText:(NSString*)text {
+	NSArray *lines = [text componentsSeparatedByString:@"\n"]; //@TODO assumes we get given lines rather than fragments...
+	int i;
+	for(i = 0; i < [lines count]; i++) {
+		NSString *line = [lines objectAtIndex:i];
+		if([line length] == 0) continue; //skip empty
+		[self appendLine:line];
+	}
+}
+
+- (BOOL)acceptsFirstResponder { 
+	return YES; 
+}
+
+- (IBAction)delete:(id)sender {
+	[array removeAllObjects];
+	[self setFrame:NSMakeRect(0,0,0,0)];
+}
+
+ at end
diff --git a/xcode/English.lproj/InfoPlist.strings b/xcode/English.lproj/InfoPlist.strings
new file mode 100644
index 0000000..187e55c
Binary files /dev/null and b/xcode/English.lproj/InfoPlist.strings differ
diff --git a/xcode/English.lproj/Localizable.strings b/xcode/English.lproj/Localizable.strings
new file mode 100644
index 0000000..4055ba4
--- /dev/null
+++ b/xcode/English.lproj/Localizable.strings
@@ -0,0 +1,27 @@
+/* note ':s' becomes 'sauerbraten' */
+
+/* tabs */
+"Main" = "Main";
+"Maps" = "Maps";
+"Keys" = "Keys";
+"Server" = "Server";
+"EisenStern" = "EisenStern";
+"Help" = "Help";
+
+"Ok" = "Ok";
+"Cancel" = "Cancel";
+"Start" = "Start";
+"Stop" = "Stop";
+"ServerAlertMesg" = "Error - Can't launch server";
+
+"ClientAlertTitle" = "Can't start :s";
+"ClientAlertMesg" = "Try moving the game folder contain :s so that the path doesn't contain weird characters, or start :s manually.";
+
+"InitAlertTitle" = "Invalid installation";
+"InitAlertMesg" = "The :s folder must exist alongside this launcher";
+
+"FileAlertTitle" = "Invalid file location";
+"FileAlertMesg" = "Can only load files that are within the :s/packages/ folder. Do you want to show this folder?";
+
+/* for MOD convenience */
+"forumURL" = "http://www.cubeengine.com/forum.php4";
diff --git a/xcode/English.lproj/MainMenu.nib/classes.nib b/xcode/English.lproj/MainMenu.nib/classes.nib
new file mode 100644
index 0000000..3bc2dad
--- /dev/null
+++ b/xcode/English.lproj/MainMenu.nib/classes.nib
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBClasses</key>
+	<array>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key>helpAction</key>
+				<string>id</string>
+				<key>multiplayerAction</key>
+				<string>id</string>
+				<key>openUserdir</key>
+				<string>id</string>
+				<key>playAction</key>
+				<string>id</string>
+				<key>playMap</key>
+				<string>id</string>
+				<key>playRpg</key>
+				<string>id</string>
+				<key>showForum</key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>Launcher</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>OUTLETS</key>
+			<dict>
+				<key>console</key>
+				<string>ConsoleView</string>
+				<key>keys</key>
+				<string>NSArrayController</string>
+				<key>maps</key>
+				<string>NSArrayController</string>
+				<key>multiplayer</key>
+				<string>NSButton</string>
+				<key>prog</key>
+				<string>NSProgressIndicator</string>
+				<key>resolutions</key>
+				<string>NSPopUpButton</string>
+				<key>tabs</key>
+				<string>NSTabView</string>
+				<key>view1</key>
+				<string>NSView</string>
+				<key>view2</key>
+				<string>NSView</string>
+				<key>view3</key>
+				<string>NSView</string>
+				<key>view4</key>
+				<string>NSView</string>
+				<key>view5</key>
+				<string>NSView</string>
+				<key>window</key>
+				<string>NSWindow</string>
+			</dict>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>ACTIONS</key>
+			<dict>
+				<key></key>
+				<string>id</string>
+			</dict>
+			<key>CLASS</key>
+			<string>FirstResponder</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSObject</string>
+		</dict>
+		<dict>
+			<key>CLASS</key>
+			<string>ConsoleView</string>
+			<key>LANGUAGE</key>
+			<string>ObjC</string>
+			<key>SUPERCLASS</key>
+			<string>NSView</string>
+		</dict>
+	</array>
+	<key>IBVersion</key>
+	<string>1</string>
+</dict>
+</plist>
diff --git a/xcode/English.lproj/MainMenu.nib/info.nib b/xcode/English.lproj/MainMenu.nib/info.nib
new file mode 100644
index 0000000..84ac30e
--- /dev/null
+++ b/xcode/English.lproj/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IBFramework Version</key>
+	<string>629</string>
+	<key>IBLastKnownRelativeProjectPath</key>
+	<string>../../sauerbraten.xcodeproj</string>
+	<key>IBOldestOS</key>
+	<integer>5</integer>
+	<key>IBOpenObjects</key>
+	<array>
+		<integer>264</integer>
+		<integer>213</integer>
+	</array>
+	<key>IBSystem Version</key>
+	<string>9C31</string>
+	<key>targetFramework</key>
+	<string>IBCocoaFramework</string>
+</dict>
+</plist>
diff --git a/xcode/English.lproj/MainMenu.nib/keyedobjects.nib b/xcode/English.lproj/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000..54f100c
Binary files /dev/null and b/xcode/English.lproj/MainMenu.nib/keyedobjects.nib differ
diff --git a/xcode/English.lproj/MainMenu.nib/objects.nib b/xcode/English.lproj/MainMenu.nib/objects.nib
new file mode 100644
index 0000000..42707bd
Binary files /dev/null and b/xcode/English.lproj/MainMenu.nib/objects.nib differ
diff --git a/xcode/Help.tiff b/xcode/Help.tiff
new file mode 100644
index 0000000..0584831
Binary files /dev/null and b/xcode/Help.tiff differ
diff --git a/xcode/Keys.gif b/xcode/Keys.gif
new file mode 100644
index 0000000..3ca1565
Binary files /dev/null and b/xcode/Keys.gif differ
diff --git a/xcode/Launcher.h b/xcode/Launcher.h
new file mode 100644
index 0000000..607ae6c
--- /dev/null
+++ b/xcode/Launcher.h
@@ -0,0 +1,42 @@
+#import <Cocoa/Cocoa.h>
+
+ at class ConsoleView;
+
+ at interface Launcher : NSObject {
+    IBOutlet NSWindow *window;
+	
+    //able to leave these disconnected
+    IBOutlet NSView *view1; //Main
+    IBOutlet NSView *view2; //Maps
+    IBOutlet NSView *view3; //Keys
+    IBOutlet NSView *view4; //Server
+    IBOutlet NSView *view5; //EisenStern
+    
+	
+    IBOutlet NSProgressIndicator *prog; //while scanning maps - it's there if you want to wire it up
+    IBOutlet NSArrayController *maps;
+    IBOutlet NSArrayController *keys;
+    IBOutlet NSPopUpButton *resolutions;
+    IBOutlet NSButton *multiplayer;
+    IBOutlet ConsoleView *console;
+ at private	
+    NSMutableDictionary *toolBarItems;
+    pid_t server;
+    NSMutableDictionary *fileRoles;
+}
+
+- (IBAction)playAction:(id)sender;
+
+- (IBAction)multiplayerAction:(id)sender;
+
+- (IBAction)helpAction:(id)sender;
+
+- (IBAction)showForum:(id)sender;
+
+- (IBAction)playRpg:(id)sender;
+
+- (IBAction)playMap:(id)sender;
+
+- (IBAction)openUserdir:(id)sender;
+
+ at end
diff --git a/xcode/Launcher.m b/xcode/Launcher.m
new file mode 100644
index 0000000..cabe0ce
--- /dev/null
+++ b/xcode/Launcher.m
@@ -0,0 +1,742 @@
+#import "Launcher.h"
+#import "ConsoleView.h"
+#include <stdlib.h>
+#include <unistd.h> /* _exit() */
+#include <util.h> /* forkpty() */
+
+// User default keys
+#define dkVERSION @"version"
+#define dkUSERDIR @"userdir"
+#define dkNAME @"name"
+#define dkFULLSCREEN @"fullscreen"
+#define dkFSAA @"fsaa"
+#define dkSHADER @"shader"
+#define dkRESOLUTION @"resolution"
+#define dkADVANCEDOPTS @"advancedOptions"
+#define dkSERVEROPTS @"server_options"
+#define dkDESCRIPTION @"server_description"
+#define dkPASSWORD @"server_password"
+#define dkMAXCLIENTS @"server_maxclients"
+
+#define kMaxDisplays	16
+
+//If you make a MOD then please change this, the bundle indentifier, the file extensions (.ogz, .dmo), and the url registration.
+#define kSAUERBRATEN @"sauerbraten"
+
+//tab names, i.e. image names (text is localised)
+#define tkMAIN @"Main"
+#define tkMAPS @"Maps"
+#define tkKEYS @"Keys"
+#define tkSERVER @"Server"
+#define tkEISENSTERN @"EisenStern"
+
+
+ at interface NSString(Extras)
+ at end
+ at implementation NSString(Extras)
+- (NSString*)expand {
+    NSMutableString *str = [NSMutableString string];
+    [str setString:self];
+    [str replaceOccurrencesOfString:@":s" withString:kSAUERBRATEN options:0 range:NSMakeRange(0, [str length])]; 
+    return str;
+}
+ at end
+
+
+ at interface NSUserDefaults(Extras) // unless you want strings with "(null)" in them :-/
+- (NSString*)nonNullStringForKey:(NSString*)key;
+ at end
+ at implementation NSUserDefaults(Extras)
+- (NSString*)nonNullStringForKey:(NSString*)key {
+    NSString *result = [self stringForKey:key];
+    return (result ? result : @"");
+}
+ at end
+
+
+ at interface Map : NSObject {
+    NSString *path;
+    BOOL demo, user;
+}
+ at end
+ at implementation Map
+- (id)initWithPath:(NSString*)aPath user:(BOOL)aUser demo:(BOOL)aDemo
+{
+    if((self = [super init])) 
+    {
+        path = [[aPath stringByDeletingPathExtension] retain];
+        user = aUser;
+        demo = aDemo;
+    }
+    return self;
+}
+- (void)dealloc 
+{
+    [path release];
+    [super dealloc];
+}
+- (NSString*)path { return (demo ? [NSString stringWithFormat:@"-xdemo \"%@\"", path] : path); } // minor hack
+- (NSString*)name { return [path lastPathComponent]; }
+- (NSImage*)image 
+{ 
+    NSImage *image = [[NSImage alloc] initWithContentsOfFile:[path stringByAppendingString:@".jpg"]]; 
+    if(!image && demo) image = [NSImage imageNamed:tkMAIN];
+    if(!image) image = [NSImage imageNamed:@"Nomap"];
+    return image;
+}
+- (NSString*)text 
+{
+    NSString *text = [NSString alloc];
+    NSError *error;
+    if([text respondsToSelector:@selector(initWithContentsOfFile:encoding:error:)])
+        text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"] encoding:NSASCIIStringEncoding error:&error];
+    else
+        text = [text initWithContentsOfFile:[path stringByAppendingString:@".txt"]]; //deprecated in 10.4
+    if(!text)
+    {
+        text = user ? @"user " : @"";
+        if(demo) text = [text stringByAppendingString:@"demo"];
+    }
+    return text;
+}
+- (void)setText:(NSString*)text { } // wtf? - damn textfield believes it's editable
+- (NSString*)tickIfExists:(NSString*)ext 
+{
+    unichar tickCh = 0x2713; 
+    return ([[NSFileManager defaultManager] fileExistsAtPath:[path stringByAppendingString:ext]] ? [NSString stringWithCharacters:&tickCh length:1] : @"");
+}
+- (NSString*)hasImage { return [self tickIfExists:@".jpg"]; }
+- (NSString*)hasText { return [self tickIfExists:@".txt"]; }
+- (NSString*)hasCfg { return [self tickIfExists:@".cfg"]; }
+ at end
+
+
+static int numberForKey(CFDictionaryRef desc, CFStringRef key) 
+{
+    CFNumberRef value;
+    int num = 0;
+    if ((value = CFDictionaryGetValue(desc, key)) == NULL)
+        return 0;
+    CFNumberGetValue(value, kCFNumberIntType, &num);
+    return num;
+}
+
+
+ at interface Launcher(ToolBar)
+ at end
+ at implementation Launcher(ToolBar)
+
+- (void)switchViews:(NSToolbarItem *)item 
+{
+    NSView *views[] = {view1, view2, view3, view4, view5};
+    NSView *prefsView = views[[item tag]-1];
+    
+    //to stop flicker, we make a temp blank view.
+    NSView *tempView = [[NSView alloc] initWithFrame:[[window contentView] frame]];
+    [window setContentView:tempView];
+    [tempView release];
+    
+    //mojo to get the right frame for the new window.
+    NSRect newFrame = [window frame];
+    newFrame.size.height = [prefsView frame].size.height + ([window frame].size.height - [[window contentView] frame].size.height);
+    newFrame.size.width = [prefsView frame].size.width;
+    newFrame.origin.y += ([[window contentView] frame].size.height - [prefsView frame].size.height);
+    
+    //set the frame to newFrame and animate it. 
+    [window setFrame:newFrame display:YES animate:YES];
+    //set the main content view to the new view we have picked through.
+    [window setContentView:prefsView];
+    [window setContentMinSize:[prefsView bounds].size];
+}
+
+- (void)initToolBar 
+{
+    toolBarItems = [[NSMutableDictionary alloc] init];
+    NSEnumerator *e = [[self toolbarDefaultItemIdentifiers:nil] objectEnumerator];
+    NSString *identifier;
+    while(identifier = [e nextObject])
+    {
+        NSToolbarItem *item = [[NSToolbarItem alloc] initWithItemIdentifier:identifier];
+        int tag = [identifier intValue];
+        NSString *name = identifier;
+        SEL action = @selector(helpAction:);
+        if(tag) {
+            NSString *names[] = {tkMAIN, tkMAPS, tkKEYS, tkSERVER, tkEISENSTERN};
+            name = names[tag-1];
+            action = @selector(switchViews:);
+        }
+        [item setTag:tag];
+        [item setTarget:self];
+        [item setAction:action];
+        [item setLabel:NSLocalizedString(name, @"")];
+        [item setImage:[NSImage imageNamed:name]];
+        [toolBarItems setObject:item forKey:identifier];
+        [item release];
+    }
+    NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@""];
+    [toolbar setDelegate:self]; 
+    [toolbar setAllowsUserCustomization:NO]; 
+    [toolbar setAutosavesConfiguration:NO];  
+    [window setToolbar:toolbar]; 
+    [toolbar release];
+    if([window respondsToSelector:@selector(setShowsToolbarButton:)]) [window setShowsToolbarButton:NO]; //10.4+
+    
+    //select the first by default
+    NSToolbarItem *first = [toolBarItems objectForKey:[[self toolbarDefaultItemIdentifiers:nil] objectAtIndex:0]];
+    [toolbar setSelectedItemIdentifier:[first itemIdentifier]];
+    [self switchViews:first]; 
+}
+
+#pragma mark toolbar delegate methods
+
+- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag 
+{
+    return [toolBarItems objectForKey:itemIdentifier];
+}
+
+- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar*)theToolbar 
+{
+    return [self toolbarDefaultItemIdentifiers:theToolbar];
+}
+
+- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar*)toolbar 
+{
+    NSMutableArray *array = (NSMutableArray *)[self toolbarSelectableItemIdentifiers:toolbar];
+    [array addObject:NSToolbarFlexibleSpaceItemIdentifier];
+    [array addObject:@"Help"];
+    return array;
+}
+
+- (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar 
+{
+    NSMutableArray *array = [NSMutableArray array];
+    NSView *views[] = {view1, view2, view3, view4, view5};
+    int i;
+    for(i = 0; i < sizeof(views)/sizeof(NSView*); i++) if(views[i]) [array addObject:[NSString stringWithFormat:@"%d", i+1]];
+    return array;
+}
+ at end
+
+
+ at implementation Launcher
+
+/* directory where the executable lives */
++ (NSString *)cwd
+{
+    return [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:kSAUERBRATEN];
+}
+
++ (BOOL)hasUserdir { return [[NSUserDefaults standardUserDefaults] boolForKey:dkUSERDIR]; }
+
+/* directory where user files are kept - typically /Users/<name>/Application Support/sauerbraten */
++ (NSString*)userdir 
+{
+    FSRef folder;
+    NSString *path = nil;
+    if(FSFindFolder(kUserDomain, kApplicationSupportFolderType, NO, &folder) == noErr) {
+        CFURLRef url = CFURLCreateFromFSRef(kCFAllocatorDefault, &folder);
+        path = [(NSURL *)url path];
+        CFRelease(url);
+        path = [path stringByAppendingPathComponent:kSAUERBRATEN];
+    }
+    return path;
+}
+
+- (void)addResolutionsForDisplay:(CGDirectDisplayID)dspy 
+{
+    CFIndex i, cnt;
+    CFArrayRef modeList = CGDisplayAvailableModes(dspy);
+    if(modeList == NULL) return;
+    cnt = CFArrayGetCount(modeList);
+    for(i = 0; i < cnt; i++) {
+        CFDictionaryRef mode = CFArrayGetValueAtIndex(modeList, i);
+        NSString *title = [NSString stringWithFormat:@"%i x %i", numberForKey(mode, kCGDisplayWidth), numberForKey(mode, kCGDisplayHeight)];
+        if(![resolutions itemWithTitle:title]) [resolutions addItemWithTitle:title];
+    }	
+}
+
+- (void)initResolutions 
+{
+    CGDirectDisplayID display[kMaxDisplays];
+    CGDisplayCount numDisplays;
+    [resolutions removeAllItems];
+    if(CGGetActiveDisplayList(kMaxDisplays, display, &numDisplays) == CGDisplayNoErr) 
+    {
+        CGDisplayCount i;
+        for (i = 0; i < numDisplays; i++)
+            [self addResolutionsForDisplay:display[i]];
+    }
+    [resolutions selectItemAtIndex: [[NSUserDefaults standardUserDefaults] integerForKey:dkRESOLUTION]];	
+}
+
+/* build key array from config data */
+-(NSArray *)getKeys:(NSDictionary *)dict 
+{	
+    NSMutableArray *arr = [NSMutableArray array];
+    NSEnumerator *e = [dict keyEnumerator];
+    NSString *key;
+    while ((key = [e nextObject])) 
+    {
+        int pos = [key rangeOfString:@"bind."].location;
+        if(pos == NSNotFound || pos > 5) continue;
+        [arr addObject:[NSDictionary dictionaryWithObjectsAndKeys: //keys used in nib
+            [key substringFromIndex:pos+5], @"key",
+            [key substringToIndex:pos], @"mode",
+            [dict objectForKey:key], @"action",
+            nil]];
+    }
+    return arr;
+}
+
+/*
+ * extract a dictionary from the config files containing:
+ * - name, team, gamma strings
+ * - bind/editbind '.' key strings
+ */
+-(NSDictionary *)readConfigFiles 
+{
+    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+    [dict setObject:@"" forKey:@"name"]; //ensure these entries are never nil
+    [dict setObject:@"" forKey:@"team"]; 
+    
+    NSString *files[] = {@"config.cfg", @"autoexec.cfg"};
+    int i;
+    for(i = 0; i < sizeof(files)/sizeof(NSString*); i++) 
+    {
+        NSString *file = [Launcher hasUserdir] ? [Launcher userdir] : [Launcher cwd];
+        file = [file stringByAppendingPathComponent:files[i]];
+        
+        NSArray *lines = [[NSString stringWithContentsOfFile:file] componentsSeparatedByString:@"\n"];
+        
+        if(i==0 && !lines)  // ugh - special case when first run...
+        { 
+            file = [[Launcher cwd] stringByAppendingPathComponent:@"data/defaults.cfg"];
+            lines = [[NSString stringWithContentsOfFile:file] componentsSeparatedByString:@"\n"];
+        }
+		
+        NSString *line; 
+        NSEnumerator *e = [lines objectEnumerator];
+        while(line = [e nextObject]) 
+        {
+            NSRange r; // more flexible to do this manually rather than via NSScanner...
+            int j = 0;
+            while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
+            r.location = j;
+            while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
+            r.length = j - r.location;
+            NSString *type = [line substringWithRange:r];
+			
+            while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
+            if(j < [line length] && [line characterAtIndex:j] == '"') 
+            {
+                r.location = ++j;
+                while(j < [line length] && [line characterAtIndex:j] != '"') j++; //until close quote
+                r.length = (j++) - r.location;
+            } else {
+                r.location = j;
+                while(j < [line length] && [line characterAtIndex:j] > ' ') j++; //until white
+                r.length = j - r.location;
+            }
+            NSString *value = [line substringWithRange:r];
+            
+            while(j < [line length] && [line characterAtIndex:j] <= ' ') j++; //skip white
+            NSString *remainder = [line substringFromIndex:j];
+			
+            if([type isEqual:@"name"] || [type isEqual:@"team"] || [type isEqual:@"gamma"]) 
+                [dict setObject:value forKey:type];
+            else if([type isEqual:@"bind"] || [type isEqual:@"editbind"] || [type isEqual:@"specbind"]) 
+                [dict setObject:remainder forKey:[NSString stringWithFormat:@"%@.%@", type,value]];
+        }
+    }
+    return dict;
+}
+
+- (void)killServer {
+    if(server > 0) kill(server, SIGKILL); //@WARNING - you do NOT want a 0 or -1 to be accidentally sent a  kill!
+    server = -1;
+    [multiplayer setTitle:NSLocalizedString(@"Start", @"")];
+    [console appendText:@"\n \n"];
+}
+
+- (void)serverDataAvailable:(NSNotification *)note
+{
+    NSFileHandle *taskOutput = [note object];
+    NSData *data = [[note userInfo] objectForKey:NSFileHandleNotificationDataItem];
+	
+    if (data && [data length])
+    {
+        NSString *text = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];		
+        [console appendText:text];
+        [text release];					
+        [taskOutput readInBackgroundAndNotify]; //wait for more data
+    }
+    else
+    {
+        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+        [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:taskOutput];
+        close([taskOutput fileDescriptor]);
+        [self killServer];
+    }
+}
+
+- (BOOL)launchGame:(NSArray *)args {
+    NSString *cwd = [Launcher cwd];
+    //NSString *exe = [cwd stringByAppendingPathComponent:[@":s.app/Contents/MacOS/:s" expand]];
+    NSString *exe = [[NSBundle bundleWithPath:[cwd stringByAppendingPathComponent:[@":s.app" expand]]] executablePath];
+    
+    BOOL okay = YES;
+    
+    if([args containsObject:@"-d"])
+    {
+        if(server != -1) return NO; // server is already running
+        
+        const char **argv = (const char**)malloc(sizeof(char*)*([args count] + 2)); //{path, <args>, NULL};
+        argv[0] = [exe fileSystemRepresentation];        
+        argv[[args count]+1] = NULL;
+        int i;
+        for(i = 0; i < [args count]; i++) argv[i+1] = [[args objectAtIndex:i] UTF8String];  
+        
+        int fdm;
+        NSString *fail = [NSLocalizedString(@"ServerAlertMesg", nil) expand];
+        switch ( (server = forkpty(&fdm, NULL, NULL, NULL)) ) // forkpty so we can reliably grab SDL console
+        { 
+            case -1:
+                [console appendLine:fail];
+                [self killServer];
+                okay = NO;
+                break;
+            case 0: // child
+                chdir([cwd fileSystemRepresentation]);
+                if(execv([exe fileSystemRepresentation], (char*const*)argv) == -1) fprintf(stderr, "%s\n", [fail UTF8String]);
+                _exit(0);
+            default: // parent
+                [multiplayer setTitle:NSLocalizedString(@"Stop", @"")];
+                NSFileHandle *taskOutput = [[NSFileHandle alloc] initWithFileDescriptor:fdm];
+                NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+                [nc addObserver:self selector:@selector(serverDataAvailable:) name:NSFileHandleReadCompletionNotification object:taskOutput];
+                [taskOutput readInBackgroundAndNotify];
+                break;
+        }
+        free(argv);
+    } 
+    else 
+    {
+        NS_DURING
+            NSTask *task = [[NSTask alloc] init];
+            [task setCurrentDirectoryPath:cwd];
+            [task setLaunchPath:exe];
+            [task setArguments:args];
+            [task setEnvironment:[NSDictionary dictionaryWithObjectsAndKeys: 
+                @"1", @"SDL_SINGLEDISPLAY",
+                @"1", @"SDL_ENABLEAPPEVENTS", nil
+            ]]; // makes Command-H, Command-M and Command-Q work at least when not in fullscreen
+
+            [task launch];
+            if(server == -1) [NSApp terminate:self]; //if there is a server then don't exit!
+        NS_HANDLER
+            //NSLog(@"%@", localException);
+            NSBeginCriticalAlertSheet(
+                [NSLocalizedString(@"ClientAlertTitle", @"") expand] , nil, nil, nil,
+                window, nil, nil, nil, nil,
+                [NSLocalizedString(@"ClientAlertMesg", @"") expand]);
+            okay = NO;
+        NS_ENDHANDLER
+    }
+
+    return okay;
+}
+
+/*
+ * nil will just launch the fps game
+ * "-rpg" will launch the rpg demo
+ * "-x.." will launch and run commands
+ * otherwise we are specifying a map to play
+ */
+- (BOOL)playFile:(id)filename 
+{	
+    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
+    
+    NSArray *res = [[resolutions titleOfSelectedItem] componentsSeparatedByString:@" x "];	
+    NSMutableArray *args = [NSMutableArray array];
+	
+    [args addObject:[NSString stringWithFormat:@"-w%@", [res objectAtIndex:0]]];
+    [args addObject:[NSString stringWithFormat:@"-h%@", [res objectAtIndex:1]]];
+    [args addObject:@"-z32"]; //otherwise seems to have a fondness to use -z16 which looks crap
+	
+    if([defs integerForKey:dkFULLSCREEN] == 0) [args addObject:@"-t"];
+    [args addObject:[NSString stringWithFormat:@"-a%d", [defs integerForKey:dkFSAA]]];
+    [args addObject:[NSString stringWithFormat:@"-f%d", [defs integerForKey:dkSHADER]]];
+    
+    if([Launcher hasUserdir]) [args addObject:[NSString stringWithFormat:@"-q%@", [Launcher userdir]]];
+
+    NSMutableArray *cmds = [NSMutableArray array];
+    NSString *name = [defs nonNullStringForKey:dkNAME];
+    if(name) [cmds addObject:[NSString stringWithFormat:@"name \"%@\"", name]];
+    
+    if(filename) 
+    {
+        if([filename isEqual:@"-rpg"]) {
+            [cmds removeAllObjects]; // rpg current doesn't require name/team
+            [args addObject:@"-grpg"]; //demo the rpg game
+        } else if([filename hasPrefix:@"-x"])
+            [cmds addObject:[filename substringFromIndex:2]];
+        else 
+            [args addObject:[NSString stringWithFormat:@"-l%@", filename]];
+    }
+    
+    if([cmds count] > 0) 
+    {
+        NSString *script = [cmds objectAtIndex:0];
+        int i;
+        for(i = 1; i < [cmds count]; i++) script = [NSString stringWithFormat:@"%@;%@", script, [cmds objectAtIndex:i]];
+        [args addObject:[NSString stringWithFormat:@"-x%@", script]];
+    }
+    
+    NSEnumerator *e = [[[defs nonNullStringForKey:dkADVANCEDOPTS] componentsSeparatedByString:@" "] objectEnumerator];
+    NSString *opt;
+    while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
+
+    return [self launchGame:args];
+}
+
+- (void)scanMaps:(id)obj //@note threaded!
+{
+    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    int len = [Launcher hasUserdir] ? 2 : 1;
+    int i;
+    for(i = 0; i < len; i++) 
+    {
+        NSString *dir = (i==0) ? [Launcher cwd] : [Launcher userdir];
+        NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager] enumeratorAtPath:dir];
+        NSString *file;
+        while(file = [enumerator nextObject]) 
+        {
+            NSString *role = [fileRoles objectForKey:[file pathExtension]];
+            if(role) 
+            {   
+                Map *map = [[Map alloc] initWithPath:[dir stringByAppendingPathComponent:file] user:(i==1) demo:[role isEqual:@"Viewer"]];
+                [maps performSelectorOnMainThread:@selector(addObject:) withObject:map waitUntilDone:NO];
+            }
+        }
+    }
+    [prog performSelectorOnMainThread:@selector(stopAnimation:) withObject:nil waitUntilDone:NO];
+    [pool release];
+}
+
+- (void)initMaps 
+{
+    [prog startAnimation:nil];
+    [maps removeObjects:[maps arrangedObjects]];
+    [NSThread detachNewThreadSelector: @selector(scanMaps:) toTarget:self withObject:nil];
+}
+
+- (void)awakeFromNib 
+{
+    //generate some pretty icons if they are missing
+    NSRect region = NSMakeRect(0, 0, 64, 64);
+    NSImage *image = [NSImage imageNamed:tkMAIN];
+    if(!image) {
+        image = [[NSImage imageNamed:@"NSApplicationIcon"] copy];
+        [image setSize:region.size];
+        [image setName:tkMAIN]; //one less image to include
+    }
+    NSImage *en = [NSImage imageNamed:tkEISENSTERN];
+    if(!en) {
+        en = [image copy]; 
+        [en lockFocus];
+        [[NSColor cyanColor] set]; //greenish icon instead - as CGBlendMode is 10.4+, bitmap filters are too much code  
+        NSRectFillUsingOperation(region, NSCompositeSourceAtop);
+        [image drawInRect:region fromRect:region operation:NSCompositePlusDarker fraction:1.0];
+        [en unlockFocus];
+        [en setName:tkEISENSTERN]; //one less image to include
+    }
+    
+    [self initToolBar];
+    [window setBackgroundColor:[NSColor colorWithDeviceRed:0.90 green:0.90 blue:0.90 alpha:1.0]]; //Apples 'mercury' crayon color
+
+    //from the plist we determine that dmo->Viewer, and ogz->Editor 
+    fileRoles = [[NSMutableDictionary dictionary] retain];
+    NSEnumerator *types = [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDocumentTypes"] objectEnumerator];
+    NSDictionary *type;
+    while((type = [types nextObject])) {
+        NSString *role = [type objectForKey:@"CFBundleTypeRole"];
+        NSEnumerator *exts = [[type objectForKey:@"CFBundleTypeExtensions"] objectEnumerator];
+        NSString *ext;
+        while((ext = [exts nextObject])) [fileRoles setObject:role forKey:ext];
+    }
+	
+    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
+    NSFileManager *fm = [NSFileManager defaultManager];
+    
+    NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
+    NSString *version = [defs stringForKey:dkVERSION];
+    if(!version || ![version isEqual:appVersion]) 
+    {
+        NSLog(@"Upgraded Version...");
+        //need to flush lurking config files - they're automatically generated, so no big deal...
+        NSString *dir = [Launcher userdir];
+        [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"init.cfg"] handler:nil];
+        [fm removeFileAtPath:[dir stringByAppendingPathComponent:@"config.cfg"] handler:nil];
+    }
+    [defs setObject:appVersion forKey:dkVERSION];
+    
+    NSDictionary *dict = [self readConfigFiles];
+    [keys addObjects:[self getKeys:dict]];
+    
+    if([[defs nonNullStringForKey:dkNAME] isEqual:@""]) 
+    {
+        NSString *name = [dict objectForKey:@"name"];
+        if([name isEqual:@""] || [name isEqual:@"unnamed"]) name = NSUserName();
+        [defs setValue:name forKey:dkNAME];
+    }
+    
+    NSString *dir = [Launcher cwd];
+    if(![fm isWritableFileAtPath:dir]) [defs setBool:YES forKey:dkUSERDIR];  // auto-enable userdir
+	
+    [self initMaps];
+    [self initResolutions];
+    server = -1;
+    [NSApp setDelegate:self]; //so can catch the double-click, dropped files, termination
+    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
+    
+    //Listen for changes in the preferences that require the gui to refresh	
+    [defs addObserver:self forKeyPath:dkUSERDIR options:NSKeyValueObservingOptionNew context:nil];
+
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+    if(![keyPath isEqual:dkUSERDIR]) return;
+    [self initMaps];
+}
+
+#pragma mark -
+#pragma mark application delegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)note {
+    NSFileManager *fm = [NSFileManager defaultManager];
+    NSString *dir = [Launcher cwd];
+    if(![fm fileExistsAtPath:dir])
+        NSBeginCriticalAlertSheet(
+            [NSLocalizedString(@"InitAlertTitle", @"") expand], nil, nil, nil,
+            window, self, nil, nil, nil,
+            [NSLocalizedString(@"InitAlertMesg", @"") expand]);
+}
+
+-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication {
+    return YES;
+}
+
+- (void)applicationWillTerminate: (NSNotification *)note {
+    [self killServer];
+}
+
+//we register 'ogz' and 'dmo' as doc types
+- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename 
+{
+    NSString *role = [fileRoles objectForKey:[filename pathExtension]];
+    if(!role) return NO;
+    BOOL demo = [role isEqual:@"Viewer"];
+    filename = [filename stringByDeletingPathExtension]; //chop off extension
+    int len = [Launcher hasUserdir] ? 2 : 1;
+    int i;
+    for(i = 0; i < len; i++) {
+        NSString *pkg = (i == 0) ? [Launcher cwd] : [Launcher userdir];
+        if(!demo) pkg = [pkg stringByAppendingPathComponent:@"packages"];
+        if([filename hasPrefix:pkg])
+            return [self playFile:(demo ? [NSString stringWithFormat:@"-xdemo \"%@\"", filename] : filename)];
+    }
+    NSBeginCriticalAlertSheet(
+        [NSLocalizedString(@"FileAlertMesg", @"") expand], NSLocalizedString(@"Ok", @""), NSLocalizedString(@"Cancel", @""), nil,
+        window, self, @selector(openPackageFolder:returnCode:contextInfo:), nil, nil,
+        [NSLocalizedString(@"FileAlertMesg", @"") expand]);
+    return NO;
+}
+
+- (void)openPackageFolder:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 
+{
+    if(returnCode == 0) return;
+    if([Launcher hasUserdir]) //close enough... otherwise need to ensure that sauerbraten/packages folder exists too
+        [self openUserdir:nil]; 
+    else
+        [[NSWorkspace sharedWorkspace] openFile:[Launcher cwd]];
+}
+
+//we register 'sauerbraten' as a url scheme
+- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent
+{
+    NSURL *url = [NSURL URLWithString:[[event paramDescriptorForKeyword:keyDirectObject] stringValue]];
+    if(!url) return;
+    [self playFile:[NSString stringWithFormat:@"-xconnect %@", [url host]]]; 
+}
+
+#pragma mark interface actions
+
+- (IBAction)multiplayerAction:(id)sender 
+{ 
+    [window makeFirstResponder:window]; //ensure fields are exited and committed
+    if(server != -1) 
+    {
+        [self killServer]; 
+    }
+    else 
+    {
+        NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
+    
+        NSMutableArray *args = [NSMutableArray arrayWithObject:@"-d"];
+
+        NSEnumerator *e = [[[defs nonNullStringForKey:dkSERVEROPTS] componentsSeparatedByString:@" "] objectEnumerator];
+        NSString *opt;
+        while(opt = [e nextObject]) if([opt length] != 0) [args addObject:opt]; //skip empty ones
+        
+        NSString *desc = [defs nonNullStringForKey:dkDESCRIPTION];
+        if (![desc isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-n%@", desc]];
+        
+        NSString *pass = [defs nonNullStringForKey:dkPASSWORD];
+        if (![pass isEqual:@""]) [args addObject:[NSString stringWithFormat:@"-p%@", pass]];
+		
+        int clients = [defs integerForKey:dkMAXCLIENTS];
+        if (clients > 0) [args addObject:[NSString stringWithFormat:@"-c%d", clients]];
+        
+        if([Launcher hasUserdir]) [args addObject:[NSString stringWithFormat:@"-q%@", [Launcher userdir]]];
+        
+        [self launchGame:args];
+    } 
+}
+
+- (IBAction)playAction:(id)sender 
+{ 
+    [window makeFirstResponder:window]; //ensure fields are exited and committed
+    [self playFile:nil]; 
+}
+
+- (IBAction)playRpg:(id)sender 
+{ 
+    [self playFile:@"-rpg"]; 
+}
+
+- (IBAction)playMap:(id)sender
+{
+    NSArray *sel = [maps selectedObjects];
+    if(sel && [sel count] > 0) [self playFile:[[sel objectAtIndex:0] path]];
+}
+
+- (IBAction)helpAction:(id)sender 
+{
+    NSString *file = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"README.html"];
+    [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:file]];
+}
+
+- (IBAction)showForum:(id)sender
+{
+     [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:NSLocalizedString(@"forumURL", @"")]];
+}
+
+- (IBAction)openUserdir:(id)sender 
+{
+    NSString *dir = [Launcher userdir];
+    NSFileManager *fm = [NSFileManager defaultManager];
+    if(![fm fileExistsAtPath:dir]) [fm createDirectoryAtPath:dir attributes:nil]; //ensure there is a folder to open
+    [[NSWorkspace sharedWorkspace] openFile:dir];
+}
+
+ at end
diff --git a/xcode/Maps.gif b/xcode/Maps.gif
new file mode 100644
index 0000000..00ff6cc
Binary files /dev/null and b/xcode/Maps.gif differ
diff --git a/xcode/Nomap.png b/xcode/Nomap.png
new file mode 100644
index 0000000..7d20e31
Binary files /dev/null and b/xcode/Nomap.png differ
diff --git a/xcode/SDLMain.h b/xcode/SDLMain.h
new file mode 100644
index 0000000..4683df5
--- /dev/null
+++ b/xcode/SDLMain.h
@@ -0,0 +1,11 @@
+/*   SDLMain.m - main entry point for our Cocoa-ized SDL app
+       Initial Version: Darrell Walisser <dwaliss1 at purdue.edu>
+       Non-NIB-Code & other changes: Max Horn <max at quendi.de>
+
+    Feel free to customize this file to suit your needs
+*/
+
+#import <Cocoa/Cocoa.h>
+
+ at interface SDLMain : NSObject
+ at end
diff --git a/xcode/SDLMain.m b/xcode/SDLMain.m
new file mode 100644
index 0000000..1022a3e
--- /dev/null
+++ b/xcode/SDLMain.m
@@ -0,0 +1,401 @@
+/*   SDLMain.m - main entry point for our Cocoa-ized SDL app
+       Initial Version: Darrell Walisser <dwaliss1 at purdue.edu>
+       Non-NIB-Code & other changes: Max Horn <max at quendi.de>
+
+    Feel free to customize this file to suit your needs
+*/
+
+#import "SDL.h"
+#import "SDLMain.h"
+#import <sys/param.h> /* for MAXPATHLEN */
+#import <unistd.h>
+
+/* For some reaon, Apple removed setAppleMenu from the headers in 10.4,
+ but the method still is there and works. To avoid warnings, we declare
+ it ourselves here. */
+ at interface NSApplication(SDL_Missing_Methods)
+- (void)setAppleMenu:(NSMenu *)menu;
+ at end
+
+/* Use this flag to determine whether we use SDLMain.nib or not */
+#define		SDL_USE_NIB_FILE	0
+
+/* Use this flag to determine whether we use CPS (docking) or not */
+#define		SDL_USE_CPS		1
+#ifdef SDL_USE_CPS
+/* Portions of CPS.h */
+typedef struct CPSProcessSerNum
+{
+	UInt32		lo;
+	UInt32		hi;
+} CPSProcessSerNum;
+
+extern OSErr	CPSGetCurrentProcess( CPSProcessSerNum *psn);
+extern OSErr 	CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
+extern OSErr	CPSSetFrontProcess( CPSProcessSerNum *psn);
+
+#endif /* SDL_USE_CPS */
+
+static int    gArgc;
+static char  **gArgv;
+static BOOL   gFinderLaunch;
+static BOOL   gCalledAppMainline = FALSE;
+
+static NSString *getApplicationName(void)
+{
+    NSDictionary *dict;
+    NSString *appName = 0;
+
+    /* Determine the application name */
+    dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle());
+    if (dict)
+        appName = [dict objectForKey: @"CFBundleName"];
+    
+    if (![appName length])
+        appName = [[NSProcessInfo processInfo] processName];
+
+    return appName;
+}
+
+#if SDL_USE_NIB_FILE
+/* A helper category for NSString */
+ at interface NSString (ReplaceSubString)
+- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString;
+ at end
+#endif
+
+ at interface SDLApplication : NSApplication
+ at end
+
+ at implementation SDLApplication
+/* Invoked from the Quit menu item */
+- (void)terminate:(id)sender
+{
+    /* Post a SDL_QUIT event */
+    SDL_Event event;
+    event.type = SDL_QUIT;
+    SDL_PushEvent(&event);
+}
+
+// ADDED SO IT DOESN'T BEEP BECAUSE WE ENABLE SDL_ENABLEAPPEVENTS
+- (void)sendEvent:(NSEvent *)anEvent
+{
+	if( NSKeyDown == [anEvent type] || NSKeyUp == [anEvent type] ) {
+		if( [anEvent modifierFlags] & NSCommandKeyMask ) 
+			[super sendEvent: anEvent];
+	} else 
+		[super sendEvent: anEvent];
+}
+ at end
+
+/* The main class of the application, the application's delegate */
+ at implementation SDLMain
+
+/* Set the working directory to the .app's parent directory */
+- (void) setupWorkingDirectory:(BOOL)shouldChdir
+{
+    if (shouldChdir)
+    {
+        char parentdir[MAXPATHLEN];
+		CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle());
+		CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url);
+		if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) {
+	        assert ( chdir (parentdir) == 0 );   /* chdir to the binary app's parent */
+		}
+		CFRelease(url);
+		CFRelease(url2);
+	}
+
+}
+
+#if SDL_USE_NIB_FILE
+
+/* Fix menu to contain the real app name instead of "SDL App" */
+- (void)fixMenu:(NSMenu *)aMenu withAppName:(NSString *)appName
+{
+    NSRange aRange;
+    NSEnumerator *enumerator;
+    NSMenuItem *menuItem;
+
+    aRange = [[aMenu title] rangeOfString:@"SDL App"];
+    if (aRange.length != 0)
+        [aMenu setTitle: [[aMenu title] stringByReplacingRange:aRange with:appName]];
+
+    enumerator = [[aMenu itemArray] objectEnumerator];
+    while ((menuItem = [enumerator nextObject]))
+    {
+        aRange = [[menuItem title] rangeOfString:@"SDL App"];
+        if (aRange.length != 0)
+            [menuItem setTitle: [[menuItem title] stringByReplacingRange:aRange with:appName]];
+        if ([menuItem hasSubmenu])
+            [self fixMenu:[menuItem submenu] withAppName:appName];
+    }
+    [ aMenu sizeToFit ];
+}
+
+#else
+
+static void setApplicationMenu(void)
+{
+    /* warning: this code is very odd */
+    NSMenu *appleMenu;
+    NSMenuItem *menuItem;
+    NSString *title;
+    NSString *appName;
+    
+    appName = getApplicationName();
+    appleMenu = [[NSMenu alloc] initWithTitle:@""];
+    
+    /* Add menu items */
+    title = [@"About " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""];
+
+    [appleMenu addItem:[NSMenuItem separatorItem]];
+
+    title = [@"Hide " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"];
+
+    menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"];
+    [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
+
+    [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
+
+    [appleMenu addItem:[NSMenuItem separatorItem]];
+
+    title = [@"Quit " stringByAppendingString:appName];
+    [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
+
+    
+    /* Put menu into the menubar */
+    menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+    [menuItem setSubmenu:appleMenu];
+    [[NSApp mainMenu] addItem:menuItem];
+
+    /* Tell the application object that this is now the application menu */
+    [NSApp setAppleMenu:appleMenu];
+
+    /* Finally give up our references to the objects */
+    [appleMenu release];
+    [menuItem release];
+}
+
+/* Create a window menu */
+static void setupWindowMenu(void)
+{
+    NSMenu      *windowMenu;
+    NSMenuItem  *windowMenuItem;
+    NSMenuItem  *menuItem;
+
+    windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
+    
+    /* "Minimize" item */
+    menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"];
+    [windowMenu addItem:menuItem];
+    [menuItem release];
+    
+    /* Put menu into the menubar */
+    windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""];
+    [windowMenuItem setSubmenu:windowMenu];
+    [[NSApp mainMenu] addItem:windowMenuItem];
+    
+    /* Tell the application object that this is now the window menu */
+    [NSApp setWindowsMenu:windowMenu];
+
+    /* Finally give up our references to the objects */
+    [windowMenu release];
+    [windowMenuItem release];
+}
+
+/* Replacement for NSApplicationMain */
+static void CustomApplicationMain (int argc, char **argv)
+{
+    NSAutoreleasePool	*pool = [[NSAutoreleasePool alloc] init];
+    SDLMain				*sdlMain;
+
+    /* Ensure the application object is initialised */
+    [SDLApplication sharedApplication];
+    
+#ifdef SDL_USE_CPS
+    /* @TODO - use this to get rid of the warnings?
+    {
+        ProcessSerialNumber psn = { 0, kCurrentProcess };
+        if(TransformProcessType(&psn,kProcessTransformToForegroundApplication)==0) //10.3
+            if(SetFrontProcess(&psn) == 0)
+               [SDLApplication sharedApplication];
+    }
+    */
+    {
+        CPSProcessSerNum PSN;
+        /* Tell the dock about us */
+        if (!CPSGetCurrentProcess(&PSN))
+            if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
+                if (!CPSSetFrontProcess(&PSN))
+                    [SDLApplication sharedApplication];
+    }
+#endif /* SDL_USE_CPS */
+
+    /* Set up the menubar */
+    [NSApp setMainMenu:[[NSMenu alloc] init]];
+    setApplicationMenu();
+    setupWindowMenu();
+
+    /* Create SDLMain and make it the app delegate */
+    sdlMain = [[SDLMain alloc] init];
+    [NSApp setDelegate:sdlMain];
+    
+    /* Start the main event loop */
+    [NSApp run];
+    
+    [sdlMain release];
+    [pool release];
+}
+
+#endif
+
+
+/*
+ * Catch document open requests...this lets us notice files when the app
+ *  was launched by double-clicking a document, or when a document was
+ *  dragged/dropped on the app's icon. You need to have a
+ *  CFBundleDocumentsType section in your Info.plist to get this message,
+ *  apparently.
+ *
+ * Files are added to gArgv, so to the app, they'll look like command line
+ *  arguments. Previously, apps launched from the finder had nothing but
+ *  an argv[0].
+ *
+ * This message may be received multiple times to open several docs on launch.
+ *
+ * This message is ignored once the app's mainline has been called.
+ */
+- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename
+{
+    const char *temparg;
+    size_t arglen;
+    char *arg;
+    char **newargv;
+
+    if (!gFinderLaunch)  /* MacOS is passing command line args. */
+        return FALSE;
+
+    if (gCalledAppMainline)  /* app has started, ignore this document. */
+        return FALSE;
+
+    temparg = [filename UTF8String];
+    arglen = SDL_strlen(temparg) + 1;
+    arg = (char *) SDL_malloc(arglen);
+    if (arg == NULL)
+        return FALSE;
+
+    newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2));
+    if (newargv == NULL)
+    {
+        SDL_free(arg);
+        return FALSE;
+    }
+    gArgv = newargv;
+
+    SDL_strlcpy(arg, temparg, arglen);
+    gArgv[gArgc++] = arg;
+    gArgv[gArgc] = NULL;
+    return TRUE;
+}
+
+
+/* Called when the internal event loop has just started running */
+- (void) applicationDidFinishLaunching: (NSNotification *) note
+{
+    int status;
+
+    /* Set the working directory to the .app's parent directory */
+    [self setupWorkingDirectory:gFinderLaunch];
+
+#if SDL_USE_NIB_FILE
+    /* Set the main menu to contain the real app name instead of "SDL App" */
+    [self fixMenu:[NSApp mainMenu] withAppName:getApplicationName()];
+#endif
+
+    /* Hand off to main application code */
+    gCalledAppMainline = TRUE;
+    status = SDL_main (gArgc, gArgv);
+
+    /* We're done, thank you for playing */
+    exit(status);
+}
+ at end
+
+
+ at implementation NSString (ReplaceSubString)
+
+- (NSString *)stringByReplacingRange:(NSRange)aRange with:(NSString *)aString
+{
+    unsigned int bufferSize;
+    unsigned int selfLen = [self length];
+    unsigned int aStringLen = [aString length];
+    unichar *buffer;
+    NSRange localRange;
+    NSString *result;
+
+    bufferSize = selfLen + aStringLen - aRange.length;
+    buffer = NSAllocateMemoryPages(bufferSize*sizeof(unichar));
+    
+    /* Get first part into buffer */
+    localRange.location = 0;
+    localRange.length = aRange.location;
+    [self getCharacters:buffer range:localRange];
+    
+    /* Get middle part into buffer */
+    localRange.location = 0;
+    localRange.length = aStringLen;
+    [aString getCharacters:(buffer+aRange.location) range:localRange];
+     
+    /* Get last part into buffer */
+    localRange.location = aRange.location + aRange.length;
+    localRange.length = selfLen - localRange.location;
+    [self getCharacters:(buffer+aRange.location+aStringLen) range:localRange];
+    
+    /* Build output string */
+    result = [NSString stringWithCharacters:buffer length:bufferSize];
+    
+    NSDeallocateMemoryPages(buffer, bufferSize);
+    
+    return result;
+}
+
+ at end
+
+
+
+#ifdef main
+#  undef main
+#endif
+
+
+/* Main entry point to executable - should *not* be SDL_main! */
+int main (int argc, char **argv)
+{
+    /* Copy the arguments into a global variable */
+    /* This is passed if we are launched by double-clicking */
+    if ( argc >= 2 && strncmp (argv[1], "-psn", 4) == 0 ) {
+        gArgv = (char **) SDL_malloc(sizeof (char *) * 2);
+        gArgv[0] = argv[0];
+        gArgv[1] = NULL;
+        gArgc = 1;
+        gFinderLaunch = YES;
+    } else {
+        int i;
+        gArgc = argc;
+        gArgv = (char **) SDL_malloc(sizeof (char *) * (argc+1));
+        for (i = 0; i <= argc; i++)
+            gArgv[i] = argv[i];
+        gFinderLaunch = NO;
+    }
+
+#if SDL_USE_NIB_FILE
+    [SDLApplication poseAsClass:[NSApplication class]];
+    NSApplicationMain (argc, argv);
+#else
+    CustomApplicationMain (argc, argv);
+#endif
+    return 0;
+}
\ No newline at end of file
diff --git a/xcode/Server.gif b/xcode/Server.gif
new file mode 100644
index 0000000..60a7b3b
Binary files /dev/null and b/xcode/Server.gif differ
diff --git a/xcode/launcher-Info.plist b/xcode/launcher-Info.plist
new file mode 100644
index 0000000..141bb28
--- /dev/null
+++ b/xcode/launcher-Info.plist
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleDocumentTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>dmo</string>
+			</array>
+			<key>CFBundleTypeIconFile</key>
+			<string>map.icns</string>
+			<key>CFBundleTypeName</key>
+			<string>Sauerbraten demo</string>
+			<key>CFBundleTypeRole</key>
+			<string>Viewer</string>
+			<key>LSTypeIsPackage</key>
+			<false/>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>Binary</string>
+		</dict>
+		<dict>
+			<key>CFBundleTypeExtensions</key>
+			<array>
+				<string>ogz</string>
+			</array>
+			<key>CFBundleTypeIconFile</key>
+			<string>map.icns</string>
+			<key>CFBundleTypeName</key>
+			<string>Sauerbraten map</string>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>LSTypeIsPackage</key>
+			<false/>
+			<key>NSPersistentStoreTypeKey</key>
+			<string>Binary</string>
+		</dict>
+	</array>
+	<key>CFBundleExecutable</key>
+	<string>launcher</string>
+	<key>CFBundleIconFile</key>
+	<string>sauerbraten</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>CFBundleURLName</key>
+			<string>SauerbratenServer</string>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>sauerbraten</string>
+			</array>
+		</dict>
+	</array>
+	<key>CFBundleVersion</key>
+	<string>1.2</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/xcode/macutils.mm b/xcode/macutils.mm
new file mode 100644
index 0000000..d8b81a8
--- /dev/null
+++ b/xcode/macutils.mm
@@ -0,0 +1,28 @@
+#import <Cocoa/Cocoa.h>
+
+#define MAXSTRLEN 260
+inline char *s_strncpy(char *d, const char *s, size_t m) { strncpy(d,s,m); d[m-1] = 0; return d; };
+inline char *s_strcat(char *d, const char *s) { size_t n = strlen(d); return s_strncpy(d+n,s,MAXSTRLEN-n); };
+
+void mac_pasteconsole(char *commandbuf)
+{	
+    NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+    NSString *type = [pasteboard availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]];
+    if (type != nil) {
+        NSString *contents = [pasteboard stringForType:type];
+        if (contents != nil)
+			s_strcat(commandbuf, [contents lossyCString]);
+    }
+}
+
+/*
+ * 0x1030 = 10.3
+ * 0x1040 = 10.4
+ * 0x1050 = 10.5
+ */
+int mac_osversion() 
+{
+    SInt32 MacVersion;
+    Gestalt(gestaltSystemVersion, &MacVersion);
+    return MacVersion;
+}
diff --git a/xcode/main.m b/xcode/main.m
new file mode 100644
index 0000000..05e5b6c
--- /dev/null
+++ b/xcode/main.m
@@ -0,0 +1,6 @@
+#import <Cocoa/Cocoa.h>
+
+int main(int argc, char *argv[])
+{
+    return NSApplicationMain(argc,  (const char **) argv);
+}
\ No newline at end of file
diff --git a/xcode/map.icns b/xcode/map.icns
new file mode 100644
index 0000000..fcf4bfd
Binary files /dev/null and b/xcode/map.icns differ
diff --git a/xcode/sauerbraten-Info.plist b/xcode/sauerbraten-Info.plist
new file mode 100644
index 0000000..f4febb0
--- /dev/null
+++ b/xcode/sauerbraten-Info.plist
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>sauerbraten</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1.2</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/xcode/sauerbraten.icns b/xcode/sauerbraten.icns
new file mode 100644
index 0000000..9bf56a1
Binary files /dev/null and b/xcode/sauerbraten.icns differ
diff --git a/xcode/sauerbraten.xcodeproj/project.pbxproj b/xcode/sauerbraten.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..95f62fc
--- /dev/null
+++ b/xcode/sauerbraten.xcodeproj/project.pbxproj
@@ -0,0 +1,995 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 42;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		666E40F00B67D87F005B491F /* macutils.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666E40EF0B67D87F005B491F /* macutils.mm */; };
+		B90357B509D09B9D002C9DC7 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
+		B90ADD4509B344D800A5B00B /* callbacks.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD0009B344D800A5B00B /* callbacks.c */; };
+		B90ADD5709B344D800A5B00B /* host.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD1309B344D800A5B00B /* host.c */; };
+		B90ADD6A09B344D800A5B00B /* list.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD2809B344D800A5B00B /* list.c */; };
+		B90ADD7109B344D800A5B00B /* packet.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD2F09B344D800A5B00B /* packet.c */; };
+		B90ADD7209B344D800A5B00B /* peer.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD3009B344D800A5B00B /* peer.c */; };
+		B90ADD7309B344D800A5B00B /* protocol.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD3109B344D800A5B00B /* protocol.c */; };
+		B90ADD7609B344D800A5B00B /* unix.c in Sources */ = {isa = PBXBuildFile; fileRef = B90ADD3409B344D800A5B00B /* unix.c */; };
+		B91D401F0D525FD3004EF78A /* animmodel.h in Headers */ = {isa = PBXBuildFile; fileRef = B91D401E0D525FD3004EF78A /* animmodel.h */; };
+		B91D40210D525FE0004EF78A /* decal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B91D40200D525FE0004EF78A /* decal.cpp */; };
+		B91D40230D525FE9004EF78A /* md5.h in Headers */ = {isa = PBXBuildFile; fileRef = B91D40220D525FE9004EF78A /* md5.h */; };
+		B91D40250D52600A004EF78A /* skelmodel.h in Headers */ = {isa = PBXBuildFile; fileRef = B91D40240D52600A004EF78A /* skelmodel.h */; };
+		B92DC0FC09D08CF9008219BD /* SDL_image.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B92DC0F909D08CF9008219BD /* SDL_image.framework */; };
+		B92DC0FD09D08CF9008219BD /* SDL_mixer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B92DC0FA09D08CF9008219BD /* SDL_mixer.framework */; };
+		B92DC0FE09D08CF9008219BD /* SDL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B92DC0FB09D08CF9008219BD /* SDL.framework */; };
+		B930D0920A3D9BC700BDFB85 /* ConsoleView.m in Sources */ = {isa = PBXBuildFile; fileRef = B930D0910A3D9BC700BDFB85 /* ConsoleView.m */; };
+		B930D0D20A3DA92300BDFB85 /* map.icns in Resources */ = {isa = PBXBuildFile; fileRef = B930D0D10A3DA92300BDFB85 /* map.icns */; };
+		B941988C09207E810029DAD1 /* SDLMain.h in Headers */ = {isa = PBXBuildFile; fileRef = F5A47A9D01A0482F01D3D55B /* SDLMain.h */; };
+		B94198A509207E810029DAD1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
+		B94198A809207E810029DAD1 /* SDLMain.m in Sources */ = {isa = PBXBuildFile; fileRef = F5A47A9E01A0483001D3D55B /* SDLMain.m */; };
+		B94198C309207E810029DAD1 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
+		B94198C409207E810029DAD1 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2F67ED704C74A3F00A80002 /* OpenGL.framework */; };
+		B96D0D590920970C00B6C936 /* Launcher.m in Sources */ = {isa = PBXBuildFile; fileRef = B96D0D580920970C00B6C936 /* Launcher.m */; };
+		B96D0D630920971300B6C936 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = B96D0D5A0920971300B6C936 /* MainMenu.nib */; };
+		B96D0D6D0920976E00B6C936 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = B96D0D6C0920976E00B6C936 /* main.m */; };
+		B9AC7ACF0D06DB44005506F8 /* 3dgui.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A840D06DB44005506F8 /* 3dgui.cpp */; };
+		B9AC7AD00D06DB44005506F8 /* bih.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A850D06DB44005506F8 /* bih.cpp */; };
+		B9AC7AD10D06DB44005506F8 /* bih.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A860D06DB44005506F8 /* bih.h */; };
+		B9AC7AD20D06DB44005506F8 /* client.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A870D06DB44005506F8 /* client.cpp */; };
+		B9AC7AD30D06DB44005506F8 /* command.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A880D06DB44005506F8 /* command.cpp */; };
+		B9AC7AD40D06DB44005506F8 /* console.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A890D06DB44005506F8 /* console.cpp */; };
+		B9AC7AD50D06DB44005506F8 /* cubeloader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A8A0D06DB44005506F8 /* cubeloader.cpp */; };
+		B9AC7AD60D06DB44005506F8 /* engine.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A8B0D06DB44005506F8 /* engine.h */; };
+		B9AC7AD70D06DB44005506F8 /* grass.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A8C0D06DB44005506F8 /* grass.cpp */; };
+		B9AC7AD80D06DB44005506F8 /* lightmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A8D0D06DB44005506F8 /* lightmap.cpp */; };
+		B9AC7AD90D06DB44005506F8 /* lightmap.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A8E0D06DB44005506F8 /* lightmap.h */; };
+		B9AC7ADA0D06DB44005506F8 /* main.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A8F0D06DB44005506F8 /* main.cpp */; };
+		B9AC7ADB0D06DB44005506F8 /* material.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A900D06DB44005506F8 /* material.cpp */; };
+		B9AC7ADC0D06DB44005506F8 /* md2.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A910D06DB44005506F8 /* md2.h */; };
+		B9AC7ADD0D06DB44005506F8 /* md3.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A920D06DB44005506F8 /* md3.h */; };
+		B9AC7ADE0D06DB44005506F8 /* menus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A930D06DB44005506F8 /* menus.cpp */; };
+		B9AC7ADF0D06DB44005506F8 /* model.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A940D06DB44005506F8 /* model.h */; };
+		B9AC7AE00D06DB44005506F8 /* normal.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A950D06DB44005506F8 /* normal.cpp */; };
+		B9AC7AE10D06DB44005506F8 /* octa.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A960D06DB44005506F8 /* octa.cpp */; };
+		B9AC7AE20D06DB44005506F8 /* octa.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7A970D06DB44005506F8 /* octa.h */; };
+		B9AC7AE30D06DB44005506F8 /* octaedit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A980D06DB44005506F8 /* octaedit.cpp */; };
+		B9AC7AE40D06DB44005506F8 /* octarender.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A990D06DB44005506F8 /* octarender.cpp */; };
+		B9AC7AE50D06DB44005506F8 /* physics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9A0D06DB44005506F8 /* physics.cpp */; };
+		B9AC7AE60D06DB44005506F8 /* rendergl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9B0D06DB44005506F8 /* rendergl.cpp */; };
+		B9AC7AE70D06DB44005506F8 /* rendermodel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9C0D06DB44005506F8 /* rendermodel.cpp */; };
+		B9AC7AE80D06DB44005506F8 /* renderparticles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9D0D06DB44005506F8 /* renderparticles.cpp */; };
+		B9AC7AE90D06DB44005506F8 /* rendersky.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9E0D06DB44005506F8 /* rendersky.cpp */; };
+		B9AC7AEA0D06DB44005506F8 /* rendertext.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7A9F0D06DB44005506F8 /* rendertext.cpp */; };
+		B9AC7AEB0D06DB44005506F8 /* renderva.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA00D06DB44005506F8 /* renderva.cpp */; };
+		B9AC7AEC0D06DB44005506F8 /* server.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA10D06DB44005506F8 /* server.cpp */; };
+		B9AC7AED0D06DB44005506F8 /* serverbrowser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA20D06DB44005506F8 /* serverbrowser.cpp */; };
+		B9AC7AEE0D06DB44005506F8 /* shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA30D06DB44005506F8 /* shader.cpp */; };
+		B9AC7AEF0D06DB44005506F8 /* shadowmap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA40D06DB44005506F8 /* shadowmap.cpp */; };
+		B9AC7AF00D06DB44005506F8 /* sound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA50D06DB44005506F8 /* sound.cpp */; };
+		B9AC7AF10D06DB44005506F8 /* texture.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA60D06DB44005506F8 /* texture.cpp */; };
+		B9AC7AF20D06DB44005506F8 /* texture.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AA70D06DB44005506F8 /* texture.h */; };
+		B9AC7AF30D06DB44005506F8 /* vertmodel.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AA80D06DB44005506F8 /* vertmodel.h */; };
+		B9AC7AF40D06DB44005506F8 /* water.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AA90D06DB44005506F8 /* water.cpp */; };
+		B9AC7AF50D06DB44005506F8 /* world.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AAA0D06DB44005506F8 /* world.cpp */; };
+		B9AC7AF60D06DB44005506F8 /* world.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AAB0D06DB44005506F8 /* world.h */; };
+		B9AC7AF70D06DB44005506F8 /* worldio.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AAC0D06DB44005506F8 /* worldio.cpp */; };
+		B9AC7AF80D06DB44005506F8 /* capture.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AAE0D06DB44005506F8 /* capture.h */; };
+		B9AC7AF90D06DB44005506F8 /* client.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AAF0D06DB44005506F8 /* client.h */; };
+		B9AC7AFA0D06DB44005506F8 /* entities.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB00D06DB44005506F8 /* entities.h */; };
+		B9AC7AFB0D06DB44005506F8 /* fps.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AB10D06DB44005506F8 /* fps.cpp */; };
+		B9AC7AFC0D06DB44005506F8 /* fpsrender.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB20D06DB44005506F8 /* fpsrender.h */; };
+		B9AC7AFD0D06DB44005506F8 /* fpsserver.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB30D06DB44005506F8 /* fpsserver.h */; };
+		B9AC7AFE0D06DB44005506F8 /* game.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB40D06DB44005506F8 /* game.h */; };
+		B9AC7AFF0D06DB44005506F8 /* monster.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB50D06DB44005506F8 /* monster.h */; };
+		B9AC7B000D06DB44005506F8 /* movable.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB60D06DB44005506F8 /* movable.h */; };
+		B9AC7B010D06DB44005506F8 /* scoreboard.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB70D06DB44005506F8 /* scoreboard.h */; };
+		B9AC7B020D06DB44005506F8 /* weapon.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AB80D06DB44005506F8 /* weapon.h */; };
+		B9AC7B030D06DB44005506F8 /* entities.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ABA0D06DB44005506F8 /* entities.h */; };
+		B9AC7B040D06DB44005506F8 /* rpg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7ABB0D06DB44005506F8 /* rpg.cpp */; };
+		B9AC7B050D06DB44005506F8 /* rpgent.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ABC0D06DB44005506F8 /* rpgent.h */; };
+		B9AC7B060D06DB44005506F8 /* rpgobj.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ABD0D06DB44005506F8 /* rpgobj.h */; };
+		B9AC7B070D06DB44005506F8 /* rpgobjset.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ABE0D06DB44005506F8 /* rpgobjset.h */; };
+		B9AC7B080D06DB44005506F8 /* stats.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ABF0D06DB44005506F8 /* stats.h */; };
+		B9AC7B090D06DB44005506F8 /* stubs.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC00D06DB44005506F8 /* stubs.h */; };
+		B9AC7B0A0D06DB44005506F8 /* command.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC20D06DB44005506F8 /* command.h */; };
+		B9AC7B0B0D06DB44005506F8 /* cube.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC30D06DB44005506F8 /* cube.h */; };
+		B9AC7B0C0D06DB44005506F8 /* ents.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC40D06DB44005506F8 /* ents.h */; };
+		B9AC7B0D0D06DB44005506F8 /* geom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AC50D06DB44005506F8 /* geom.cpp */; };
+		B9AC7B0E0D06DB44005506F8 /* geom.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC60D06DB44005506F8 /* geom.h */; };
+		B9AC7B0F0D06DB44005506F8 /* iengine.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC70D06DB44005506F8 /* iengine.h */; };
+		B9AC7B100D06DB44005506F8 /* igame.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7AC80D06DB44005506F8 /* igame.h */; };
+		B9AC7B110D06DB44005506F8 /* pch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7AC90D06DB44005506F8 /* pch.cpp */; };
+		B9AC7B120D06DB44005506F8 /* pch.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ACA0D06DB44005506F8 /* pch.h */; };
+		B9AC7B130D06DB44005506F8 /* sbtrace.d in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7ACB0D06DB44005506F8 /* sbtrace.d */; };
+		B9AC7B140D06DB44005506F8 /* sbtrace.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ACC0D06DB44005506F8 /* sbtrace.h */; };
+		B9AC7B150D06DB44005506F8 /* tools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B9AC7ACD0D06DB44005506F8 /* tools.cpp */; };
+		B9AC7B160D06DB44005506F8 /* tools.h in Headers */ = {isa = PBXBuildFile; fileRef = B9AC7ACE0D06DB44005506F8 /* tools.h */; };
+		B9CACA13092099DF00A13F04 /* launcher-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B9CACA12092099DF00A13F04 /* launcher-Info.plist */; };
+		B9CACA15092099E700A13F04 /* sauerbraten-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B9CACA14092099E700A13F04 /* sauerbraten-Info.plist */; };
+		B9CACA2509209A5800A13F04 /* sauerbraten.icns in Resources */ = {isa = PBXBuildFile; fileRef = B97E99CE085F4B3E002F9BC6 /* sauerbraten.icns */; };
+		D116C0AB0D9F7DB500E8B945 /* rendertarget.h in Headers */ = {isa = PBXBuildFile; fileRef = D116C0AA0D9F7DB500E8B945 /* rendertarget.h */; };
+		D118DEB60D979281000E8C4C /* glare.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D118DEB50D979281000E8C4C /* glare.cpp */; };
+		D123F95B0DA8B312001DB09F /* ctf.h in Headers */ = {isa = PBXBuildFile; fileRef = D123F95A0DA8B312001DB09F /* ctf.h */; };
+		D139B59E0D59A8E600AA994D /* assassin.h in Headers */ = {isa = PBXBuildFile; fileRef = D139B59D0D59A8E600AA994D /* assassin.h */; };
+		D167A1970C493AED007F7D96 /* Nomap.png in Resources */ = {isa = PBXBuildFile; fileRef = D167A1960C493AED007F7D96 /* Nomap.png */; };
+		D16BD00D0D7000EA0053CECE /* dynlight.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D16BD00C0D7000EA0053CECE /* dynlight.cpp */; };
+		D16C16920E04812500A522EB /* obj.h in Headers */ = {isa = PBXBuildFile; fileRef = D16C16900E04812500A522EB /* obj.h */; };
+		D16C16930E04812500A522EB /* textedit.h in Headers */ = {isa = PBXBuildFile; fileRef = D16C16910E04812500A522EB /* textedit.h */; };
+		D17E0F2C0BAF1FA9008BB696 /* Keys.gif in Resources */ = {isa = PBXBuildFile; fileRef = D17E0F280BAF1FA9008BB696 /* Keys.gif */; };
+		D17E0F2E0BAF1FA9008BB696 /* Server.gif in Resources */ = {isa = PBXBuildFile; fileRef = D17E0F2A0BAF1FA9008BB696 /* Server.gif */; };
+		D17E0F310BAF2031008BB696 /* Help.tiff in Resources */ = {isa = PBXBuildFile; fileRef = D17E0F300BAF2031008BB696 /* Help.tiff */; };
+		D18B8FC10DB0AF8200171439 /* explosion.h in Headers */ = {isa = PBXBuildFile; fileRef = D18B8FBE0DB0AF8200171439 /* explosion.h */; };
+		D18B8FC20DB0AF8200171439 /* lensflare.h in Headers */ = {isa = PBXBuildFile; fileRef = D18B8FBF0DB0AF8200171439 /* lensflare.h */; };
+		D18B8FC30DB0AF8200171439 /* lightning.h in Headers */ = {isa = PBXBuildFile; fileRef = D18B8FC00DB0AF8200171439 /* lightning.h */; };
+		D1C660CA0D5467C8002E52C1 /* pvs.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D1C660C90D5467C8002E52C1 /* pvs.cpp */; };
+		D1DB53E80D70F1CD003AE25C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D1DB53E70D70F1CD003AE25C /* Localizable.strings */; };
+		D1F0AC200BB0688A00CE4E84 /* Maps.gif in Resources */ = {isa = PBXBuildFile; fileRef = D1F0AC1F0BB0688A00CE4E84 /* Maps.gif */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		B96D0D780920980E00B6C936 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = B96D0D4B092096F200B6C936;
+			remoteInfo = launcher;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
+		29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
+		29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
+		666E40EF0B67D87F005B491F /* macutils.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = macutils.mm; sourceTree = "<group>"; };
+		B2F67ED704C74A3F00A80002 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = /System/Library/Frameworks/OpenGL.framework; sourceTree = "<absolute>"; };
+		B90ADD0009B344D800A5B00B /* callbacks.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = callbacks.c; sourceTree = "<group>"; };
+		B90ADD1309B344D800A5B00B /* host.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = host.c; sourceTree = "<group>"; };
+		B90ADD2809B344D800A5B00B /* list.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = list.c; sourceTree = "<group>"; };
+		B90ADD2F09B344D800A5B00B /* packet.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = packet.c; sourceTree = "<group>"; };
+		B90ADD3009B344D800A5B00B /* peer.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = peer.c; sourceTree = "<group>"; };
+		B90ADD3109B344D800A5B00B /* protocol.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = protocol.c; sourceTree = "<group>"; };
+		B90ADD3409B344D800A5B00B /* unix.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; path = unix.c; sourceTree = "<group>"; };
+		B91D401E0D525FD3004EF78A /* animmodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = animmodel.h; sourceTree = "<group>"; };
+		B91D40200D525FE0004EF78A /* decal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = decal.cpp; sourceTree = "<group>"; };
+		B91D40220D525FE9004EF78A /* md5.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md5.h; sourceTree = "<group>"; };
+		B91D40240D52600A004EF78A /* skelmodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = skelmodel.h; sourceTree = "<group>"; };
+		B92DC0F909D08CF9008219BD /* SDL_image.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL_image.framework; path = /Library/Frameworks/SDL_image.framework; sourceTree = "<absolute>"; };
+		B92DC0FA09D08CF9008219BD /* SDL_mixer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL_mixer.framework; path = /Library/Frameworks/SDL_mixer.framework; sourceTree = "<absolute>"; };
+		B92DC0FB09D08CF9008219BD /* SDL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL.framework; path = /Library/Frameworks/SDL.framework; sourceTree = "<absolute>"; };
+		B930D0900A3D9BC700BDFB85 /* ConsoleView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ConsoleView.h; sourceTree = "<group>"; };
+		B930D0910A3D9BC700BDFB85 /* ConsoleView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ConsoleView.m; sourceTree = "<group>"; };
+		B930D0D10A3DA92300BDFB85 /* map.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = map.icns; sourceTree = "<group>"; };
+		B94198CB09207E810029DAD1 /* sauerbraten.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = sauerbraten.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		B96D0D4C092096F200B6C936 /* launcher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = launcher.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		B96D0D570920970C00B6C936 /* Launcher.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Launcher.h; sourceTree = "<group>"; };
+		B96D0D580920970C00B6C936 /* Launcher.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Launcher.m; sourceTree = "<group>"; };
+		B96D0D5B0920971300B6C936 /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
+		B96D0D6C0920976E00B6C936 /* main.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		B97E99CE085F4B3E002F9BC6 /* sauerbraten.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = sauerbraten.icns; sourceTree = "<group>"; };
+		B9AC7A840D06DB44005506F8 /* 3dgui.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 3dgui.cpp; sourceTree = "<group>"; };
+		B9AC7A850D06DB44005506F8 /* bih.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bih.cpp; sourceTree = "<group>"; };
+		B9AC7A860D06DB44005506F8 /* bih.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = bih.h; sourceTree = "<group>"; };
+		B9AC7A870D06DB44005506F8 /* client.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = client.cpp; sourceTree = "<group>"; };
+		B9AC7A880D06DB44005506F8 /* command.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = command.cpp; sourceTree = "<group>"; };
+		B9AC7A890D06DB44005506F8 /* console.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = console.cpp; sourceTree = "<group>"; };
+		B9AC7A8A0D06DB44005506F8 /* cubeloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = cubeloader.cpp; sourceTree = "<group>"; };
+		B9AC7A8B0D06DB44005506F8 /* engine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = engine.h; sourceTree = "<group>"; };
+		B9AC7A8C0D06DB44005506F8 /* grass.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = grass.cpp; sourceTree = "<group>"; };
+		B9AC7A8D0D06DB44005506F8 /* lightmap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = lightmap.cpp; sourceTree = "<group>"; };
+		B9AC7A8E0D06DB44005506F8 /* lightmap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lightmap.h; sourceTree = "<group>"; };
+		B9AC7A8F0D06DB44005506F8 /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
+		B9AC7A900D06DB44005506F8 /* material.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = material.cpp; sourceTree = "<group>"; };
+		B9AC7A910D06DB44005506F8 /* md2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md2.h; sourceTree = "<group>"; };
+		B9AC7A920D06DB44005506F8 /* md3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = md3.h; sourceTree = "<group>"; };
+		B9AC7A930D06DB44005506F8 /* menus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = menus.cpp; sourceTree = "<group>"; };
+		B9AC7A940D06DB44005506F8 /* model.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = model.h; sourceTree = "<group>"; };
+		B9AC7A950D06DB44005506F8 /* normal.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = normal.cpp; sourceTree = "<group>"; };
+		B9AC7A960D06DB44005506F8 /* octa.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = octa.cpp; sourceTree = "<group>"; };
+		B9AC7A970D06DB44005506F8 /* octa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = octa.h; sourceTree = "<group>"; };
+		B9AC7A980D06DB44005506F8 /* octaedit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = octaedit.cpp; sourceTree = "<group>"; };
+		B9AC7A990D06DB44005506F8 /* octarender.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = octarender.cpp; sourceTree = "<group>"; };
+		B9AC7A9A0D06DB44005506F8 /* physics.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = physics.cpp; sourceTree = "<group>"; };
+		B9AC7A9B0D06DB44005506F8 /* rendergl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rendergl.cpp; sourceTree = "<group>"; };
+		B9AC7A9C0D06DB44005506F8 /* rendermodel.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rendermodel.cpp; sourceTree = "<group>"; };
+		B9AC7A9D0D06DB44005506F8 /* renderparticles.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = renderparticles.cpp; sourceTree = "<group>"; };
+		B9AC7A9E0D06DB44005506F8 /* rendersky.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rendersky.cpp; sourceTree = "<group>"; };
+		B9AC7A9F0D06DB44005506F8 /* rendertext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rendertext.cpp; sourceTree = "<group>"; };
+		B9AC7AA00D06DB44005506F8 /* renderva.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = renderva.cpp; sourceTree = "<group>"; };
+		B9AC7AA10D06DB44005506F8 /* server.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = server.cpp; sourceTree = "<group>"; };
+		B9AC7AA20D06DB44005506F8 /* serverbrowser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serverbrowser.cpp; sourceTree = "<group>"; };
+		B9AC7AA30D06DB44005506F8 /* shader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shader.cpp; sourceTree = "<group>"; };
+		B9AC7AA40D06DB44005506F8 /* shadowmap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = shadowmap.cpp; sourceTree = "<group>"; };
+		B9AC7AA50D06DB44005506F8 /* sound.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sound.cpp; sourceTree = "<group>"; };
+		B9AC7AA60D06DB44005506F8 /* texture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = texture.cpp; sourceTree = "<group>"; };
+		B9AC7AA70D06DB44005506F8 /* texture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = texture.h; sourceTree = "<group>"; };
+		B9AC7AA80D06DB44005506F8 /* vertmodel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vertmodel.h; sourceTree = "<group>"; };
+		B9AC7AA90D06DB44005506F8 /* water.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = water.cpp; sourceTree = "<group>"; };
+		B9AC7AAA0D06DB44005506F8 /* world.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = world.cpp; sourceTree = "<group>"; };
+		B9AC7AAB0D06DB44005506F8 /* world.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = world.h; sourceTree = "<group>"; };
+		B9AC7AAC0D06DB44005506F8 /* worldio.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = worldio.cpp; sourceTree = "<group>"; };
+		B9AC7AAE0D06DB44005506F8 /* capture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = capture.h; sourceTree = "<group>"; };
+		B9AC7AAF0D06DB44005506F8 /* client.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = client.h; sourceTree = "<group>"; };
+		B9AC7AB00D06DB44005506F8 /* entities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = entities.h; sourceTree = "<group>"; };
+		B9AC7AB10D06DB44005506F8 /* fps.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = fps.cpp; sourceTree = "<group>"; };
+		B9AC7AB20D06DB44005506F8 /* fpsrender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fpsrender.h; sourceTree = "<group>"; };
+		B9AC7AB30D06DB44005506F8 /* fpsserver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fpsserver.h; sourceTree = "<group>"; };
+		B9AC7AB40D06DB44005506F8 /* game.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = game.h; sourceTree = "<group>"; };
+		B9AC7AB50D06DB44005506F8 /* monster.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = monster.h; sourceTree = "<group>"; };
+		B9AC7AB60D06DB44005506F8 /* movable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = movable.h; sourceTree = "<group>"; };
+		B9AC7AB70D06DB44005506F8 /* scoreboard.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = scoreboard.h; sourceTree = "<group>"; };
+		B9AC7AB80D06DB44005506F8 /* weapon.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = weapon.h; sourceTree = "<group>"; };
+		B9AC7ABA0D06DB44005506F8 /* entities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = entities.h; sourceTree = "<group>"; };
+		B9AC7ABB0D06DB44005506F8 /* rpg.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = rpg.cpp; sourceTree = "<group>"; };
+		B9AC7ABC0D06DB44005506F8 /* rpgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rpgent.h; sourceTree = "<group>"; };
+		B9AC7ABD0D06DB44005506F8 /* rpgobj.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rpgobj.h; sourceTree = "<group>"; };
+		B9AC7ABE0D06DB44005506F8 /* rpgobjset.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rpgobjset.h; sourceTree = "<group>"; };
+		B9AC7ABF0D06DB44005506F8 /* stats.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stats.h; sourceTree = "<group>"; };
+		B9AC7AC00D06DB44005506F8 /* stubs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stubs.h; sourceTree = "<group>"; };
+		B9AC7AC20D06DB44005506F8 /* command.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = command.h; sourceTree = "<group>"; };
+		B9AC7AC30D06DB44005506F8 /* cube.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = cube.h; sourceTree = "<group>"; };
+		B9AC7AC40D06DB44005506F8 /* ents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ents.h; sourceTree = "<group>"; };
+		B9AC7AC50D06DB44005506F8 /* geom.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = geom.cpp; sourceTree = "<group>"; };
+		B9AC7AC60D06DB44005506F8 /* geom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = geom.h; sourceTree = "<group>"; };
+		B9AC7AC70D06DB44005506F8 /* iengine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iengine.h; sourceTree = "<group>"; };
+		B9AC7AC80D06DB44005506F8 /* igame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = igame.h; sourceTree = "<group>"; };
+		B9AC7AC90D06DB44005506F8 /* pch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pch.cpp; sourceTree = "<group>"; };
+		B9AC7ACA0D06DB44005506F8 /* pch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pch.h; sourceTree = "<group>"; };
+		B9AC7ACB0D06DB44005506F8 /* sbtrace.d */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.dtrace; path = sbtrace.d; sourceTree = "<group>"; };
+		B9AC7ACC0D06DB44005506F8 /* sbtrace.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sbtrace.h; sourceTree = "<group>"; };
+		B9AC7ACD0D06DB44005506F8 /* tools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tools.cpp; sourceTree = "<group>"; };
+		B9AC7ACE0D06DB44005506F8 /* tools.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = tools.h; sourceTree = "<group>"; };
+		B9CACA12092099DF00A13F04 /* launcher-Info.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "launcher-Info.plist"; sourceTree = "<group>"; };
+		B9CACA14092099E700A13F04 /* sauerbraten-Info.plist */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xml; path = "sauerbraten-Info.plist"; sourceTree = "<group>"; };
+		D116C0AA0D9F7DB500E8B945 /* rendertarget.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = rendertarget.h; sourceTree = "<group>"; };
+		D118DEB50D979281000E8C4C /* glare.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = glare.cpp; sourceTree = "<group>"; };
+		D123F95A0DA8B312001DB09F /* ctf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ctf.h; sourceTree = "<group>"; };
+		D139B59D0D59A8E600AA994D /* assassin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = assassin.h; sourceTree = "<group>"; };
+		D167A1960C493AED007F7D96 /* Nomap.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Nomap.png; sourceTree = "<group>"; };
+		D16BD00C0D7000EA0053CECE /* dynlight.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = dynlight.cpp; sourceTree = "<group>"; };
+		D16C16900E04812500A522EB /* obj.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = obj.h; sourceTree = "<group>"; };
+		D16C16910E04812500A522EB /* textedit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = textedit.h; sourceTree = "<group>"; };
+		D17E0F280BAF1FA9008BB696 /* Keys.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = Keys.gif; sourceTree = "<group>"; };
+		D17E0F2A0BAF1FA9008BB696 /* Server.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = Server.gif; sourceTree = "<group>"; };
+		D17E0F300BAF2031008BB696 /* Help.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Help.tiff; sourceTree = "<group>"; };
+		D18B8FBE0DB0AF8200171439 /* explosion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = explosion.h; sourceTree = "<group>"; };
+		D18B8FBF0DB0AF8200171439 /* lensflare.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lensflare.h; sourceTree = "<group>"; };
+		D18B8FC00DB0AF8200171439 /* lightning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lightning.h; sourceTree = "<group>"; };
+		D1C660C90D5467C8002E52C1 /* pvs.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = pvs.cpp; sourceTree = "<group>"; };
+		D1DB53E50D70F1C3003AE25C /* English */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/Localizable.strings; sourceTree = "<group>"; };
+		D1F0AC1F0BB0688A00CE4E84 /* Maps.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = Maps.gif; sourceTree = "<group>"; };
+		F5A47A9D01A0482F01D3D55B /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = SOURCE_ROOT; };
+		F5A47A9E01A0483001D3D55B /* SDLMain.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = SDLMain.m; sourceTree = SOURCE_ROOT; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		B94198C209207E810029DAD1 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B94198C309207E810029DAD1 /* Cocoa.framework in Frameworks */,
+				B94198C409207E810029DAD1 /* OpenGL.framework in Frameworks */,
+				B92DC0FC09D08CF9008219BD /* SDL_image.framework in Frameworks */,
+				B92DC0FD09D08CF9008219BD /* SDL_mixer.framework in Frameworks */,
+				B92DC0FE09D08CF9008219BD /* SDL.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		B96D0D4A092096F200B6C936 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		080E96DDFE201D6D7F000001 /* Classes */ = {
+			isa = PBXGroup;
+			children = (
+				666E40EF0B67D87F005B491F /* macutils.mm */,
+				B96D0D6C0920976E00B6C936 /* main.m */,
+				B930D0900A3D9BC700BDFB85 /* ConsoleView.h */,
+				B930D0910A3D9BC700BDFB85 /* ConsoleView.m */,
+				B96D0D570920970C00B6C936 /* Launcher.h */,
+				B96D0D580920970C00B6C936 /* Launcher.m */,
+				F5A47A9D01A0482F01D3D55B /* SDLMain.h */,
+				F5A47A9E01A0483001D3D55B /* SDLMain.m */,
+			);
+			name = Classes;
+			sourceTree = "<group>";
+		};
+		1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				B92DC0F909D08CF9008219BD /* SDL_image.framework */,
+				B92DC0FA09D08CF9008219BD /* SDL_mixer.framework */,
+				B92DC0FB09D08CF9008219BD /* SDL.framework */,
+				1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
+				B2F67ED704C74A3F00A80002 /* OpenGL.framework */,
+			);
+			name = "Linked Frameworks";
+			sourceTree = "<group>";
+		};
+		1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				29B97325FDCFA39411CA2CEA /* Foundation.framework */,
+				29B97324FDCFA39411CA2CEA /* AppKit.framework */,
+			);
+			name = "Other Frameworks";
+			sourceTree = "<group>";
+		};
+		19C28FACFE9D520D11CA2CBB /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				B94198CB09207E810029DAD1 /* sauerbraten.app */,
+				B96D0D4C092096F200B6C936 /* launcher.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		29B97314FDCFA39411CA2CEA /* sauerbraten */ = {
+			isa = PBXGroup;
+			children = (
+				080E96DDFE201D6D7F000001 /* Classes */,
+				29B97315FDCFA39411CA2CEA /* Other Sources */,
+				29B97317FDCFA39411CA2CEA /* Resources */,
+				29B97323FDCFA39411CA2CEA /* Frameworks */,
+				19C28FACFE9D520D11CA2CBB /* Products */,
+			);
+			name = sauerbraten;
+			sourceTree = "<group>";
+		};
+		29B97315FDCFA39411CA2CEA /* Other Sources */ = {
+			isa = PBXGroup;
+			children = (
+				B90ADCEE09B344D800A5B00B /* enet */,
+				B9AC7A830D06DB44005506F8 /* engine */,
+				B9AC7AAD0D06DB44005506F8 /* fpsgame */,
+				B9AC7AB90D06DB44005506F8 /* rpggame */,
+				B9AC7AC10D06DB44005506F8 /* shared */,
+			);
+			name = "Other Sources";
+			sourceTree = "<group>";
+		};
+		29B97317FDCFA39411CA2CEA /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				D167A1960C493AED007F7D96 /* Nomap.png */,
+				D1F0AC1F0BB0688A00CE4E84 /* Maps.gif */,
+				D17E0F280BAF1FA9008BB696 /* Keys.gif */,
+				D17E0F2A0BAF1FA9008BB696 /* Server.gif */,
+				D17E0F300BAF2031008BB696 /* Help.tiff */,
+				B930D0D10A3DA92300BDFB85 /* map.icns */,
+				B9CACA14092099E700A13F04 /* sauerbraten-Info.plist */,
+				B9CACA12092099DF00A13F04 /* launcher-Info.plist */,
+				B97E99CE085F4B3E002F9BC6 /* sauerbraten.icns */,
+				B96D0D5A0920971300B6C936 /* MainMenu.nib */,
+				089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
+				D1DB53E70D70F1CD003AE25C /* Localizable.strings */,
+			);
+			name = Resources;
+			sourceTree = "<group>";
+		};
+		29B97323FDCFA39411CA2CEA /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
+				1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		B90ADCEE09B344D800A5B00B /* enet */ = {
+			isa = PBXGroup;
+			children = (
+				B90ADD0009B344D800A5B00B /* callbacks.c */,
+				B90ADD1309B344D800A5B00B /* host.c */,
+				B90ADD2809B344D800A5B00B /* list.c */,
+				B90ADD2F09B344D800A5B00B /* packet.c */,
+				B90ADD3009B344D800A5B00B /* peer.c */,
+				B90ADD3109B344D800A5B00B /* protocol.c */,
+				B90ADD3409B344D800A5B00B /* unix.c */,
+			);
+			name = enet;
+			path = ../enet;
+			sourceTree = SOURCE_ROOT;
+		};
+		B9AC7A830D06DB44005506F8 /* engine */ = {
+			isa = PBXGroup;
+			children = (
+				D16C16900E04812500A522EB /* obj.h */,
+				D16C16910E04812500A522EB /* textedit.h */,
+				D18B8FBE0DB0AF8200171439 /* explosion.h */,
+				D18B8FBF0DB0AF8200171439 /* lensflare.h */,
+				D18B8FC00DB0AF8200171439 /* lightning.h */,
+				D116C0AA0D9F7DB500E8B945 /* rendertarget.h */,
+				D118DEB50D979281000E8C4C /* glare.cpp */,
+				D1C660C90D5467C8002E52C1 /* pvs.cpp */,
+				D16BD00C0D7000EA0053CECE /* dynlight.cpp */,
+				B9AC7A840D06DB44005506F8 /* 3dgui.cpp */,
+				B91D401E0D525FD3004EF78A /* animmodel.h */,
+				B9AC7A850D06DB44005506F8 /* bih.cpp */,
+				B9AC7A860D06DB44005506F8 /* bih.h */,
+				B9AC7A870D06DB44005506F8 /* client.cpp */,
+				B9AC7A880D06DB44005506F8 /* command.cpp */,
+				B9AC7A890D06DB44005506F8 /* console.cpp */,
+				B9AC7A8A0D06DB44005506F8 /* cubeloader.cpp */,
+				B91D40200D525FE0004EF78A /* decal.cpp */,
+				B9AC7A8B0D06DB44005506F8 /* engine.h */,
+				B9AC7A8C0D06DB44005506F8 /* grass.cpp */,
+				B9AC7A8D0D06DB44005506F8 /* lightmap.cpp */,
+				B9AC7A8E0D06DB44005506F8 /* lightmap.h */,
+				B9AC7A8F0D06DB44005506F8 /* main.cpp */,
+				B9AC7A900D06DB44005506F8 /* material.cpp */,
+				B9AC7A910D06DB44005506F8 /* md2.h */,
+				B9AC7A920D06DB44005506F8 /* md3.h */,
+				B91D40220D525FE9004EF78A /* md5.h */,
+				B9AC7A930D06DB44005506F8 /* menus.cpp */,
+				B9AC7A940D06DB44005506F8 /* model.h */,
+				B9AC7A950D06DB44005506F8 /* normal.cpp */,
+				B9AC7A960D06DB44005506F8 /* octa.cpp */,
+				B9AC7A970D06DB44005506F8 /* octa.h */,
+				B9AC7A980D06DB44005506F8 /* octaedit.cpp */,
+				B9AC7A990D06DB44005506F8 /* octarender.cpp */,
+				B9AC7A9A0D06DB44005506F8 /* physics.cpp */,
+				B9AC7A9B0D06DB44005506F8 /* rendergl.cpp */,
+				B9AC7A9C0D06DB44005506F8 /* rendermodel.cpp */,
+				B9AC7A9D0D06DB44005506F8 /* renderparticles.cpp */,
+				B9AC7A9E0D06DB44005506F8 /* rendersky.cpp */,
+				B9AC7A9F0D06DB44005506F8 /* rendertext.cpp */,
+				B9AC7AA00D06DB44005506F8 /* renderva.cpp */,
+				B9AC7AA10D06DB44005506F8 /* server.cpp */,
+				B9AC7AA20D06DB44005506F8 /* serverbrowser.cpp */,
+				B9AC7AA30D06DB44005506F8 /* shader.cpp */,
+				B9AC7AA40D06DB44005506F8 /* shadowmap.cpp */,
+				B91D40240D52600A004EF78A /* skelmodel.h */,
+				B9AC7AA50D06DB44005506F8 /* sound.cpp */,
+				B9AC7AA60D06DB44005506F8 /* texture.cpp */,
+				B9AC7AA70D06DB44005506F8 /* texture.h */,
+				B9AC7AA80D06DB44005506F8 /* vertmodel.h */,
+				B9AC7AA90D06DB44005506F8 /* water.cpp */,
+				B9AC7AAA0D06DB44005506F8 /* world.cpp */,
+				B9AC7AAB0D06DB44005506F8 /* world.h */,
+				B9AC7AAC0D06DB44005506F8 /* worldio.cpp */,
+			);
+			name = engine;
+			path = ../engine;
+			sourceTree = SOURCE_ROOT;
+		};
+		B9AC7AAD0D06DB44005506F8 /* fpsgame */ = {
+			isa = PBXGroup;
+			children = (
+				D123F95A0DA8B312001DB09F /* ctf.h */,
+				D139B59D0D59A8E600AA994D /* assassin.h */,
+				B9AC7AAE0D06DB44005506F8 /* capture.h */,
+				B9AC7AAF0D06DB44005506F8 /* client.h */,
+				B9AC7AB00D06DB44005506F8 /* entities.h */,
+				B9AC7AB10D06DB44005506F8 /* fps.cpp */,
+				B9AC7AB20D06DB44005506F8 /* fpsrender.h */,
+				B9AC7AB30D06DB44005506F8 /* fpsserver.h */,
+				B9AC7AB40D06DB44005506F8 /* game.h */,
+				B9AC7AB50D06DB44005506F8 /* monster.h */,
+				B9AC7AB60D06DB44005506F8 /* movable.h */,
+				B9AC7AB70D06DB44005506F8 /* scoreboard.h */,
+				B9AC7AB80D06DB44005506F8 /* weapon.h */,
+			);
+			name = fpsgame;
+			path = ../fpsgame;
+			sourceTree = SOURCE_ROOT;
+		};
+		B9AC7AB90D06DB44005506F8 /* rpggame */ = {
+			isa = PBXGroup;
+			children = (
+				B9AC7ABA0D06DB44005506F8 /* entities.h */,
+				B9AC7ABB0D06DB44005506F8 /* rpg.cpp */,
+				B9AC7ABC0D06DB44005506F8 /* rpgent.h */,
+				B9AC7ABD0D06DB44005506F8 /* rpgobj.h */,
+				B9AC7ABE0D06DB44005506F8 /* rpgobjset.h */,
+				B9AC7ABF0D06DB44005506F8 /* stats.h */,
+				B9AC7AC00D06DB44005506F8 /* stubs.h */,
+			);
+			name = rpggame;
+			path = ../rpggame;
+			sourceTree = SOURCE_ROOT;
+		};
+		B9AC7AC10D06DB44005506F8 /* shared */ = {
+			isa = PBXGroup;
+			children = (
+				B9AC7AC20D06DB44005506F8 /* command.h */,
+				B9AC7AC30D06DB44005506F8 /* cube.h */,
+				B9AC7AC40D06DB44005506F8 /* ents.h */,
+				B9AC7AC50D06DB44005506F8 /* geom.cpp */,
+				B9AC7AC60D06DB44005506F8 /* geom.h */,
+				B9AC7AC70D06DB44005506F8 /* iengine.h */,
+				B9AC7AC80D06DB44005506F8 /* igame.h */,
+				B9AC7AC90D06DB44005506F8 /* pch.cpp */,
+				B9AC7ACA0D06DB44005506F8 /* pch.h */,
+				B9AC7ACB0D06DB44005506F8 /* sbtrace.d */,
+				B9AC7ACC0D06DB44005506F8 /* sbtrace.h */,
+				B9AC7ACD0D06DB44005506F8 /* tools.cpp */,
+				B9AC7ACE0D06DB44005506F8 /* tools.h */,
+			);
+			name = shared;
+			path = ../shared;
+			sourceTree = SOURCE_ROOT;
+		};
+/* End PBXGroup section */
+
+/* Begin PBXHeadersBuildPhase section */
+		B941988B09207E810029DAD1 /* Headers */ = {
+			isa = PBXHeadersBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B941988C09207E810029DAD1 /* SDLMain.h in Headers */,
+				B9AC7AD10D06DB44005506F8 /* bih.h in Headers */,
+				B9AC7AD60D06DB44005506F8 /* engine.h in Headers */,
+				B9AC7AD90D06DB44005506F8 /* lightmap.h in Headers */,
+				B9AC7ADC0D06DB44005506F8 /* md2.h in Headers */,
+				B9AC7ADD0D06DB44005506F8 /* md3.h in Headers */,
+				B9AC7ADF0D06DB44005506F8 /* model.h in Headers */,
+				B9AC7AE20D06DB44005506F8 /* octa.h in Headers */,
+				B9AC7AF20D06DB44005506F8 /* texture.h in Headers */,
+				B9AC7AF30D06DB44005506F8 /* vertmodel.h in Headers */,
+				B9AC7AF60D06DB44005506F8 /* world.h in Headers */,
+				B9AC7AF80D06DB44005506F8 /* capture.h in Headers */,
+				B9AC7AF90D06DB44005506F8 /* client.h in Headers */,
+				B9AC7AFA0D06DB44005506F8 /* entities.h in Headers */,
+				B9AC7AFC0D06DB44005506F8 /* fpsrender.h in Headers */,
+				B9AC7AFD0D06DB44005506F8 /* fpsserver.h in Headers */,
+				B9AC7AFE0D06DB44005506F8 /* game.h in Headers */,
+				B9AC7AFF0D06DB44005506F8 /* monster.h in Headers */,
+				B9AC7B000D06DB44005506F8 /* movable.h in Headers */,
+				B9AC7B010D06DB44005506F8 /* scoreboard.h in Headers */,
+				B9AC7B020D06DB44005506F8 /* weapon.h in Headers */,
+				B9AC7B030D06DB44005506F8 /* entities.h in Headers */,
+				B9AC7B050D06DB44005506F8 /* rpgent.h in Headers */,
+				B9AC7B060D06DB44005506F8 /* rpgobj.h in Headers */,
+				B9AC7B070D06DB44005506F8 /* rpgobjset.h in Headers */,
+				B9AC7B080D06DB44005506F8 /* stats.h in Headers */,
+				B9AC7B090D06DB44005506F8 /* stubs.h in Headers */,
+				B9AC7B0A0D06DB44005506F8 /* command.h in Headers */,
+				B9AC7B0B0D06DB44005506F8 /* cube.h in Headers */,
+				B9AC7B0C0D06DB44005506F8 /* ents.h in Headers */,
+				B9AC7B0E0D06DB44005506F8 /* geom.h in Headers */,
+				B9AC7B0F0D06DB44005506F8 /* iengine.h in Headers */,
+				B9AC7B100D06DB44005506F8 /* igame.h in Headers */,
+				B9AC7B120D06DB44005506F8 /* pch.h in Headers */,
+				B9AC7B140D06DB44005506F8 /* sbtrace.h in Headers */,
+				B9AC7B160D06DB44005506F8 /* tools.h in Headers */,
+				B91D401F0D525FD3004EF78A /* animmodel.h in Headers */,
+				B91D40230D525FE9004EF78A /* md5.h in Headers */,
+				B91D40250D52600A004EF78A /* skelmodel.h in Headers */,
+				D139B59E0D59A8E600AA994D /* assassin.h in Headers */,
+				D116C0AB0D9F7DB500E8B945 /* rendertarget.h in Headers */,
+				D123F95B0DA8B312001DB09F /* ctf.h in Headers */,
+				D18B8FC10DB0AF8200171439 /* explosion.h in Headers */,
+				D18B8FC20DB0AF8200171439 /* lensflare.h in Headers */,
+				D18B8FC30DB0AF8200171439 /* lightning.h in Headers */,
+				D16C16920E04812500A522EB /* obj.h in Headers */,
+				D16C16930E04812500A522EB /* textedit.h in Headers */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXHeadersBuildPhase section */
+
+/* Begin PBXNativeTarget section */
+		B941988A09207E810029DAD1 /* sauerbraten */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = B94198C609207E810029DAD1 /* Build configuration list for PBXNativeTarget "sauerbraten" */;
+			buildPhases = (
+				B941988B09207E810029DAD1 /* Headers */,
+				B94198A409207E810029DAD1 /* Resources */,
+				B94198A709207E810029DAD1 /* Sources */,
+				B94198C209207E810029DAD1 /* Frameworks */,
+				B94199400920820E0029DAD1 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				B96D0D790920980E00B6C936 /* PBXTargetDependency */,
+			);
+			name = sauerbraten;
+			productInstallPath = "$(HOME)/Applications";
+			productName = sauerbraten;
+			productReference = B94198CB09207E810029DAD1 /* sauerbraten.app */;
+			productType = "com.apple.product-type.application";
+		};
+		B96D0D4B092096F200B6C936 /* launcher */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = B96D0D4F092096F300B6C936 /* Build configuration list for PBXNativeTarget "launcher" */;
+			buildPhases = (
+				B96D0D48092096F200B6C936 /* Resources */,
+				B96D0D49092096F200B6C936 /* Sources */,
+				B96D0D4A092096F200B6C936 /* Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = launcher;
+			productName = launcher;
+			productReference = B96D0D4C092096F200B6C936 /* launcher.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		29B97313FDCFA39411CA2CEA /* Project object */ = {
+			isa = PBXProject;
+			buildConfigurationList = B960CF35085F3CC7004FB142 /* Build configuration list for PBXProject "sauerbraten" */;
+			compatibilityVersion = "Xcode 2.4";
+			hasScannedForEncodings = 1;
+			mainGroup = 29B97314FDCFA39411CA2CEA /* sauerbraten */;
+			projectDirPath = "";
+			projectRoot = ../..;
+			targets = (
+				B941988A09207E810029DAD1 /* sauerbraten */,
+				B96D0D4B092096F200B6C936 /* launcher */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		B94198A409207E810029DAD1 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B94198A509207E810029DAD1 /* InfoPlist.strings in Resources */,
+				B9CACA15092099E700A13F04 /* sauerbraten-Info.plist in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		B96D0D48092096F200B6C936 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B96D0D630920971300B6C936 /* MainMenu.nib in Resources */,
+				B9CACA13092099DF00A13F04 /* launcher-Info.plist in Resources */,
+				B9CACA2509209A5800A13F04 /* sauerbraten.icns in Resources */,
+				B90357B509D09B9D002C9DC7 /* InfoPlist.strings in Resources */,
+				B930D0D20A3DA92300BDFB85 /* map.icns in Resources */,
+				D17E0F2C0BAF1FA9008BB696 /* Keys.gif in Resources */,
+				D17E0F2E0BAF1FA9008BB696 /* Server.gif in Resources */,
+				D17E0F310BAF2031008BB696 /* Help.tiff in Resources */,
+				D1F0AC200BB0688A00CE4E84 /* Maps.gif in Resources */,
+				D167A1970C493AED007F7D96 /* Nomap.png in Resources */,
+				D1DB53E80D70F1CD003AE25C /* Localizable.strings in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		B94199400920820E0029DAD1 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 8;
+			files = (
+			);
+			inputPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 1;
+			shellPath = /bin/sh;
+			shellScript = "echo $TARGET_BUILD_DIR\ncd $TARGET_BUILD_DIR\n\n#make the directory which our disk image will be made of\n#use /tmp as a destination because copying ourself (xcode folder) whilst compling causes the dog to chase its tail\nSAUERPKG=`mktemp -d /tmp/sauerpkg.XXXXXX` || exit 1\nmkdir $SAUERPKG/sauerbraten\n\n#leave indicator of where temp directory is in case things break\nln -sf $SAUERPKG $TARGET_BUILD_DIR/sauerpkg\n\n#copy sauerbraten there\n/Developer/Tools/CpMac -r $TARGET_BUILD_DIR/sauerbraten.app $SAUERPKG/sauerbraten\n/Developer/Tools/CpMac -r $TARGET_BUILD_DIR/launcher.app $SAUERPKG/\nmv $SAUERPKG/launcher.app $SAUERPKG/Sauerbraten.app\n\nstrip -u -r $SAUERPKG/sauerbraten/sauerbraten.app/Contents/MacOS/sauerbraten\n\n#install all frameworks into app bundle\nmkdir $SAUERPKG/sauerbraten/sauerbraten.app/Contents/Frameworks\nfor framework in SDL.framework SDL_mixer.framework SDL_image.framework\ndo\n\tif [ -d $LOCAL_LIBRARY_DIR/Frameworks/$framework/ ] \n\tthen\t\n\t\t/Developer/Tools/CpMac -r $LOCAL_LIBRARY_DIR/Frameworks/$framework/ $SAUERPKG/sauerbraten/sauerbraten.app/Contents/Frameworks\n\t\trm -rf $SAUERPKG/sauerbraten/sauerbraten.app/Contents/Frameworks/$framework/Versions/A/Headers\n\telse\n\t\texit 1\n\tfi\ndone\n\n#copy readme and data and remove unneccesary stuff\n/Developer/Tools/CpMac -r $SRCROOT/../../docs $SAUERPKG/\n/Developer/Tools/CpMac -r $SRCROOT/../../data $SAUERPKG/sauerbraten/\n/Developer/Tools/CpMac -r $SRCROOT/../../packages $SAUERPKG/sauerbraten/\n/Developer/Tools/CpMac -r $SRCROOT/../../src $SAUERPKG/sauerbraten/\n/Developer/Tools/CpMac -r $SRCROOT/../../README.html $SAUERPKG/\nfind -d $SAUERPKG -name \"CVS\" -exec rm -rf {} \\;\nfind $SAUERPKG -name \".DS_Store\" -exec rm -f {} \\;\nrm -rf $SAUERPKG/sauerbraten/src/xcode/build\n\n#finally make a disk image out of the stuff\nhdiutil create -srcfolder $SAUERPKG -volname sauerbraten sauerbraten.dmg\nhdiutil internet-enable -yes sauerbraten.dmg\n\n#cleanup\nrm -rf $SAUERPKG\n";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		B94198A709207E810029DAD1 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B94198A809207E810029DAD1 /* SDLMain.m in Sources */,
+				B90ADD4509B344D800A5B00B /* callbacks.c in Sources */,
+				B90ADD5709B344D800A5B00B /* host.c in Sources */,
+				B90ADD6A09B344D800A5B00B /* list.c in Sources */,
+				B90ADD7109B344D800A5B00B /* packet.c in Sources */,
+				B90ADD7209B344D800A5B00B /* peer.c in Sources */,
+				B90ADD7309B344D800A5B00B /* protocol.c in Sources */,
+				B90ADD7609B344D800A5B00B /* unix.c in Sources */,
+				666E40F00B67D87F005B491F /* macutils.mm in Sources */,
+				B9AC7ACF0D06DB44005506F8 /* 3dgui.cpp in Sources */,
+				B9AC7AD00D06DB44005506F8 /* bih.cpp in Sources */,
+				B9AC7AD20D06DB44005506F8 /* client.cpp in Sources */,
+				B9AC7AD30D06DB44005506F8 /* command.cpp in Sources */,
+				B9AC7AD40D06DB44005506F8 /* console.cpp in Sources */,
+				B9AC7AD50D06DB44005506F8 /* cubeloader.cpp in Sources */,
+				B9AC7AD70D06DB44005506F8 /* grass.cpp in Sources */,
+				B9AC7AD80D06DB44005506F8 /* lightmap.cpp in Sources */,
+				B9AC7ADA0D06DB44005506F8 /* main.cpp in Sources */,
+				B9AC7ADB0D06DB44005506F8 /* material.cpp in Sources */,
+				B9AC7ADE0D06DB44005506F8 /* menus.cpp in Sources */,
+				B9AC7AE00D06DB44005506F8 /* normal.cpp in Sources */,
+				B9AC7AE10D06DB44005506F8 /* octa.cpp in Sources */,
+				B9AC7AE30D06DB44005506F8 /* octaedit.cpp in Sources */,
+				B9AC7AE40D06DB44005506F8 /* octarender.cpp in Sources */,
+				B9AC7AE50D06DB44005506F8 /* physics.cpp in Sources */,
+				B9AC7AE60D06DB44005506F8 /* rendergl.cpp in Sources */,
+				B9AC7AE70D06DB44005506F8 /* rendermodel.cpp in Sources */,
+				B9AC7AE80D06DB44005506F8 /* renderparticles.cpp in Sources */,
+				B9AC7AE90D06DB44005506F8 /* rendersky.cpp in Sources */,
+				B9AC7AEA0D06DB44005506F8 /* rendertext.cpp in Sources */,
+				B9AC7AEB0D06DB44005506F8 /* renderva.cpp in Sources */,
+				B9AC7AEC0D06DB44005506F8 /* server.cpp in Sources */,
+				B9AC7AED0D06DB44005506F8 /* serverbrowser.cpp in Sources */,
+				B9AC7AEE0D06DB44005506F8 /* shader.cpp in Sources */,
+				B9AC7AEF0D06DB44005506F8 /* shadowmap.cpp in Sources */,
+				B9AC7AF00D06DB44005506F8 /* sound.cpp in Sources */,
+				B9AC7AF10D06DB44005506F8 /* texture.cpp in Sources */,
+				B9AC7AF40D06DB44005506F8 /* water.cpp in Sources */,
+				B9AC7AF50D06DB44005506F8 /* world.cpp in Sources */,
+				B9AC7AF70D06DB44005506F8 /* worldio.cpp in Sources */,
+				B9AC7AFB0D06DB44005506F8 /* fps.cpp in Sources */,
+				B9AC7B040D06DB44005506F8 /* rpg.cpp in Sources */,
+				B9AC7B0D0D06DB44005506F8 /* geom.cpp in Sources */,
+				B9AC7B110D06DB44005506F8 /* pch.cpp in Sources */,
+				B9AC7B130D06DB44005506F8 /* sbtrace.d in Sources */,
+				B9AC7B150D06DB44005506F8 /* tools.cpp in Sources */,
+				B91D40210D525FE0004EF78A /* decal.cpp in Sources */,
+				D1C660CA0D5467C8002E52C1 /* pvs.cpp in Sources */,
+				D16BD00D0D7000EA0053CECE /* dynlight.cpp in Sources */,
+				D118DEB60D979281000E8C4C /* glare.cpp in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		B96D0D49092096F200B6C936 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				B96D0D590920970C00B6C936 /* Launcher.m in Sources */,
+				B96D0D6D0920976E00B6C936 /* main.m in Sources */,
+				B930D0920A3D9BC700BDFB85 /* ConsoleView.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		B96D0D790920980E00B6C936 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = B96D0D4B092096F200B6C936 /* launcher */;
+			targetProxy = B96D0D780920980E00B6C936 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				089C165DFE840E0CC02AAC07 /* English */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+		B96D0D5A0920971300B6C936 /* MainMenu.nib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				B96D0D5B0920971300B6C936 /* English */,
+			);
+			name = MainMenu.nib;
+			sourceTree = "<group>";
+		};
+		D1DB53E70D70F1CD003AE25C /* Localizable.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				D1DB53E50D70F1C3003AE25C /* English */,
+			);
+			name = Localizable.strings;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		B94198C709207E810029DAD1 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = NO;
+				GCC_PREFIX_HEADER = "";
+				GCC_PREPROCESSOR_DEFINITIONS = HAS_SOCKLEN_T;
+				HEADER_SEARCH_PATHS = (
+					../enet/include,
+					../include/,
+				);
+				INFOPLIST_FILE = "sauerbraten-Info.plist";
+				OTHER_LDFLAGS = "-lz";
+				PRODUCT_NAME = sauerbraten;
+			};
+			name = Debug;
+		};
+		B94198C809207E810029DAD1 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = NO;
+				GCC_PREFIX_HEADER = "";
+				GCC_PREPROCESSOR_DEFINITIONS = HAS_SOCKLEN_T;
+				HEADER_SEARCH_PATHS = (
+					../enet/include,
+					../include/,
+				);
+				INFOPLIST_FILE = "sauerbraten-Info.plist";
+				OTHER_CPLUSPLUSFLAGS = (
+					"$(OTHER_CFLAGS)",
+					"-fomit-frame-pointer",
+				);
+				OTHER_LDFLAGS = "-lz";
+				PRODUCT_NAME = sauerbraten;
+			};
+			name = Release;
+		};
+		B94198EB09207F510029DAD1 /* Release-Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = NO;
+				GCC_PREFIX_HEADER = "";
+				GCC_PREPROCESSOR_DEFINITIONS = HAS_SOCKLEN_T;
+				HEADER_SEARCH_PATHS = (
+					../enet/include,
+					../include/,
+				);
+				INFOPLIST_FILE = "sauerbraten-Info.plist";
+				OTHER_CPLUSPLUSFLAGS = (
+					"$(OTHER_CFLAGS)",
+					"-fomit-frame-pointer",
+				);
+				OTHER_LDFLAGS = "-lz";
+				PRODUCT_NAME = sauerbraten;
+			};
+			name = "Release-Deployment";
+		};
+		B94198EC09207F510029DAD1 /* Release-Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = (
+					ppc,
+					i386,
+				);
+				COPY_PHASE_STRIP = NO;
+				DEPLOYMENT_POSTPROCESSING = YES;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(USER_LIBRARY_DIR)/Frameworks",
+					"$(LOCAL_LIBRARY_DIR)/Frameworks",
+				);
+				GCC_OPTIMIZATION_LEVEL = 3;
+				MACOSX_DEPLOYMENT_TARGET_i386 = 10.4;
+				MACOSX_DEPLOYMENT_TARGET_ppc = 10.3;
+				PREBINDING = NO;
+				SDKROOT_i386 = /Developer/SDKs/MacOSX10.4u.sdk;
+				SDKROOT_ppc = /Developer/SDKs/MacOSX10.4u.sdk;
+				STRIP_INSTALLED_PRODUCT = NO;
+			};
+			name = "Release-Deployment";
+		};
+		B960CF36085F3CC7004FB142 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = "$(NATIVE_ARCH)";
+				COPY_PHASE_STRIP = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(USER_LIBRARY_DIR)/Frameworks",
+					"$(LOCAL_LIBRARY_DIR)/Frameworks",
+				);
+				GCC_OPTIMIZATION_LEVEL = 0;
+				MACOSX_DEPLOYMENT_TARGET_i386 = 10.4;
+				MACOSX_DEPLOYMENT_TARGET_ppc = 10.3;
+				PREBINDING = NO;
+				SDKROOT_i386 = /Developer/SDKs/MacOSX10.4u.sdk;
+				SDKROOT_ppc = /Developer/SDKs/MacOSX10.4u.sdk;
+				STRIP_INSTALLED_PRODUCT = NO;
+			};
+			name = Debug;
+		};
+		B960CF37085F3CC7004FB142 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ARCHS = (
+					ppc,
+					i386,
+				);
+				COPY_PHASE_STRIP = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(USER_LIBRARY_DIR)/Frameworks",
+					"$(LOCAL_LIBRARY_DIR)/Frameworks",
+				);
+				GCC_OPTIMIZATION_LEVEL = 3;
+				MACOSX_DEPLOYMENT_TARGET_i386 = 10.4;
+				MACOSX_DEPLOYMENT_TARGET_ppc = 10.3;
+				PREBINDING = NO;
+				SDKROOT_i386 = /Developer/SDKs/MacOSX10.4u.sdk;
+				SDKROOT_ppc = /Developer/SDKs/MacOSX10.4u.sdk;
+				STRIP_INSTALLED_PRODUCT = NO;
+			};
+			name = Release;
+		};
+		B96D0D50092096F300B6C936 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+				INFOPLIST_FILE = "launcher-Info.plist";
+				OTHER_LDFLAGS = (
+					"-framework",
+					Foundation,
+					"-framework",
+					AppKit,
+					"-bind_at_load",
+				);
+				PRODUCT_NAME = launcher;
+			};
+			name = Debug;
+		};
+		B96D0D51092096F300B6C936 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+				INFOPLIST_FILE = "launcher-Info.plist";
+				OTHER_LDFLAGS = (
+					"-framework",
+					Foundation,
+					"-framework",
+					AppKit,
+					"-bind_at_load",
+				);
+				PRODUCT_NAME = launcher;
+			};
+			name = Release;
+		};
+		B96D0D52092096F300B6C936 /* Release-Deployment */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "$(SYSTEM_LIBRARY_DIR)/Frameworks/AppKit.framework/Headers/AppKit.h";
+				INFOPLIST_FILE = "launcher-Info.plist";
+				OTHER_LDFLAGS = (
+					"-framework",
+					Foundation,
+					"-framework",
+					AppKit,
+					"-bind_at_load",
+				);
+				PRODUCT_NAME = launcher;
+			};
+			name = "Release-Deployment";
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		B94198C609207E810029DAD1 /* Build configuration list for PBXNativeTarget "sauerbraten" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				B94198C709207E810029DAD1 /* Debug */,
+				B94198C809207E810029DAD1 /* Release */,
+				B94198EB09207F510029DAD1 /* Release-Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Debug;
+		};
+		B960CF35085F3CC7004FB142 /* Build configuration list for PBXProject "sauerbraten" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				B960CF36085F3CC7004FB142 /* Debug */,
+				B960CF37085F3CC7004FB142 /* Release */,
+				B94198EC09207F510029DAD1 /* Release-Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Debug;
+		};
+		B96D0D4F092096F300B6C936 /* Build configuration list for PBXNativeTarget "launcher" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				B96D0D50092096F300B6C936 /* Debug */,
+				B96D0D51092096F300B6C936 /* Release */,
+				B96D0D52092096F300B6C936 /* Release-Deployment */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Debug;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
+}

-- 
Packaging for sauerbraten game engine



More information about the Pkg-games-commits mailing list