From 8d6e3b7f79a4cb8684085ce0d56d90b6eab68573 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Fri, 13 Apr 2018 19:23:45 +0300 Subject: [PATCH] Pure engine source code(LF line endings, UTF8 encoded) --- common/beamdef.h | 60 + common/bspfile.h | 321 ++ common/cl_entity.h | 105 + common/com_model.h | 497 +++ common/con_nprint.h | 25 + common/const.h | 779 +++++ common/cvardef.h | 45 + common/demo_api.h | 27 + common/dlight.h | 31 + common/entity_state.h | 188 ++ common/entity_types.h | 25 + common/event_api.h | 56 + common/event_args.h | 47 + common/event_flags.h | 45 + common/features.h | 30 + common/gameinfo.h | 49 + common/hltv.h | 59 + common/ivoicetweak.h | 38 + common/lightstyle.h | 29 + common/mathlib.h | 95 + common/net_api.h | 97 + common/netadr.h | 37 + common/particledef.h | 53 + common/pmtrace.h | 41 + common/qfont.h | 38 + common/r_efx.h | 195 ++ common/r_studioint.h | 154 + common/ref_params.h | 105 + common/render_api.h | 266 ++ common/screenfade.h | 29 + common/studio_event.h | 27 + common/triangleapi.h | 62 + common/usercmd.h | 39 + common/wadfile.h | 97 + common/weaponinfo.h | 50 + common/wrect.h | 24 + engine/alias.h | 138 + engine/anorms.h | 177 + engine/cdll_exp.h | 67 + engine/cdll_int.h | 259 ++ engine/client/cl_cmds.c | 507 +++ engine/client/cl_custom.c | 143 + engine/client/cl_demo.c | 1464 +++++++++ engine/client/cl_events.c | 505 +++ engine/client/cl_frame.c | 1326 ++++++++ engine/client/cl_game.c | 3900 ++++++++++++++++++++++ engine/client/cl_gameui.c | 1064 ++++++ engine/client/cl_main.c | 2834 ++++++++++++++++ engine/client/cl_netgraph.c | 683 ++++ engine/client/cl_parse.c | 2499 ++++++++++++++ engine/client/cl_pmove.c | 1410 ++++++++ engine/client/cl_remap.c | 433 +++ engine/client/cl_scrn.c | 757 +++++ engine/client/cl_tent.c | 3106 ++++++++++++++++++ engine/client/cl_tent.h | 120 + engine/client/cl_video.c | 306 ++ engine/client/cl_view.c | 396 +++ engine/client/client.h | 1050 ++++++ engine/client/gl_alias.c | 1497 +++++++++ engine/client/gl_backend.c | 721 ++++ engine/client/gl_beams.c | 2032 ++++++++++++ engine/client/gl_cull.c | 151 + engine/client/gl_decals.c | 1293 ++++++++ engine/client/gl_draw.c | 282 ++ engine/client/gl_export.h | 1302 ++++++++ engine/client/gl_frustum.c | 355 ++ engine/client/gl_frustum.h | 52 + engine/client/gl_image.c | 2775 ++++++++++++++++ engine/client/gl_local.h | 677 ++++ engine/client/gl_refrag.c | 205 ++ engine/client/gl_rlight.c | 430 +++ engine/client/gl_rmain.c | 1541 +++++++++ engine/client/gl_rmath.c | 320 ++ engine/client/gl_rmisc.c | 514 +++ engine/client/gl_rpart.c | 1667 ++++++++++ engine/client/gl_rsurf.c | 2190 +++++++++++++ engine/client/gl_sprite.c | 1091 +++++++ engine/client/gl_studio.c | 3939 ++++++++++++++++++++++ engine/client/gl_vidnt.c | 1766 ++++++++++ engine/client/gl_warp.c | 815 +++++ engine/client/s_backend.c | 483 +++ engine/client/s_dsp.c | 905 ++++++ engine/client/s_load.c | 395 +++ engine/client/s_main.c | 2244 +++++++++++++ engine/client/s_mix.c | 1062 ++++++ engine/client/s_mouth.c | 152 + engine/client/s_stream.c | 343 ++ engine/client/s_utils.c | 306 ++ engine/client/s_vox.c | 681 ++++ engine/client/sound.h | 359 ++ engine/client/vgui/vgui_clip.cpp | 110 + engine/client/vgui/vgui_draw.c | 218 ++ engine/client/vgui/vgui_draw.h | 77 + engine/client/vgui/vgui_input.cpp | 290 ++ engine/client/vgui/vgui_int.cpp | 129 + engine/client/vgui/vgui_main.h | 117 + engine/client/vgui/vgui_surf.cpp | 465 +++ engine/client/vox.h | 47 + engine/common/avikit.c | 712 ++++ engine/common/build.c | 53 + engine/common/cfgscript.c | 341 ++ engine/common/cmd.c | 1149 +++++++ engine/common/com_strings.h | 60 + engine/common/common.c | 1323 ++++++++ engine/common/common.h | 1013 ++++++ engine/common/con_utils.c | 1062 ++++++ engine/common/console.c | 2477 ++++++++++++++ engine/common/crclib.c | 604 ++++ engine/common/crtlib.c | 722 ++++ engine/common/crtlib.h | 130 + engine/common/custom.c | 150 + engine/common/cvar.c | 890 +++++ engine/common/cvar.h | 71 + engine/common/filesystem.c | 3203 ++++++++++++++++++ engine/common/filesystem.h | 132 + engine/common/gamma.c | 88 + engine/common/host.c | 909 ++++++ engine/common/host_state.c | 181 ++ engine/common/hpak.c | 1088 +++++++ engine/common/imagelib/imagelib.h | 330 ++ engine/common/imagelib/img_bmp.c | 484 +++ engine/common/imagelib/img_dds.c | 330 ++ engine/common/imagelib/img_main.c | 508 +++ engine/common/imagelib/img_quant.c | 472 +++ engine/common/imagelib/img_tga.c | 306 ++ engine/common/imagelib/img_utils.c | 1565 +++++++++ engine/common/imagelib/img_wad.c | 522 +++ engine/common/infostring.c | 500 +++ engine/common/input.c | 587 ++++ engine/common/input.h | 54 + engine/common/keys.c | 766 +++++ engine/common/library.c | 891 +++++ engine/common/library.h | 160 + engine/common/mathlib.c | 738 +++++ engine/common/mathlib.h | 197 ++ engine/common/matrixlib.c | 807 +++++ engine/common/mod_bmodel.c | 3030 +++++++++++++++++ engine/common/mod_local.h | 196 ++ engine/common/mod_studio.c | 954 ++++++ engine/common/model.c | 596 ++++ engine/common/net_buffer.c | 688 ++++ engine/common/net_buffer.h | 135 + engine/common/net_chan.c | 1748 ++++++++++ engine/common/net_encode.c | 1992 ++++++++++++ engine/common/net_encode.h | 121 + engine/common/net_ws.c | 1511 +++++++++ engine/common/net_ws.h | 65 + engine/common/netchan.h | 234 ++ engine/common/pm_debug.c | 88 + engine/common/pm_local.h | 49 + engine/common/pm_surface.c | 270 ++ engine/common/pm_trace.c | 653 ++++ engine/common/protocol.h | 185 ++ engine/common/soundlib/snd_main.c | 287 ++ engine/common/soundlib/snd_mp3.c | 301 ++ engine/common/soundlib/snd_utils.c | 241 ++ engine/common/soundlib/snd_wav.c | 465 +++ engine/common/soundlib/soundlib.h | 137 + engine/common/sys_con.c | 535 +++ engine/common/sys_win.c | 659 ++++ engine/common/system.h | 112 + engine/common/titles.c | 345 ++ engine/common/world.c | 256 ++ engine/common/world.h | 107 + engine/common/zone.c | 486 +++ engine/custom.h | 93 + engine/customentity.h | 39 + engine/edict.h | 49 + engine/eiface.h | 499 +++ engine/keydefs.h | 133 + engine/menu_int.h | 189 ++ engine/physint.h | 164 + engine/progdefs.h | 218 ++ engine/server/server.h | 684 ++++ engine/server/sv_client.c | 2414 ++++++++++++++ engine/server/sv_cmds.c | 898 +++++ engine/server/sv_custom.c | 578 ++++ engine/server/sv_frame.c | 966 ++++++ engine/server/sv_game.c | 4883 ++++++++++++++++++++++++++++ engine/server/sv_init.c | 947 ++++++ engine/server/sv_log.c | 193 ++ engine/server/sv_main.c | 970 ++++++ engine/server/sv_move.c | 573 ++++ engine/server/sv_phys.c | 2068 ++++++++++++ engine/server/sv_pmove.c | 1126 +++++++ engine/server/sv_save.c | 2324 +++++++++++++ engine/server/sv_world.c | 1742 ++++++++++ engine/shake.h | 50 + engine/sprite.h | 128 + engine/studio.h | 413 +++ engine/warpsin.h | 53 + pm_shared/pm_debug.h | 23 + pm_shared/pm_defs.h | 221 ++ pm_shared/pm_info.h | 20 + pm_shared/pm_materials.h | 32 + pm_shared/pm_movevars.h | 50 + pm_shared/pm_shared.h | 32 + 197 files changed, 125800 insertions(+) create mode 100644 common/beamdef.h create mode 100644 common/bspfile.h create mode 100644 common/cl_entity.h create mode 100644 common/com_model.h create mode 100644 common/con_nprint.h create mode 100644 common/const.h create mode 100644 common/cvardef.h create mode 100644 common/demo_api.h create mode 100644 common/dlight.h create mode 100644 common/entity_state.h create mode 100644 common/entity_types.h create mode 100644 common/event_api.h create mode 100644 common/event_args.h create mode 100644 common/event_flags.h create mode 100644 common/features.h create mode 100644 common/gameinfo.h create mode 100644 common/hltv.h create mode 100644 common/ivoicetweak.h create mode 100644 common/lightstyle.h create mode 100644 common/mathlib.h create mode 100644 common/net_api.h create mode 100644 common/netadr.h create mode 100644 common/particledef.h create mode 100644 common/pmtrace.h create mode 100644 common/qfont.h create mode 100644 common/r_efx.h create mode 100644 common/r_studioint.h create mode 100644 common/ref_params.h create mode 100644 common/render_api.h create mode 100644 common/screenfade.h create mode 100644 common/studio_event.h create mode 100644 common/triangleapi.h create mode 100644 common/usercmd.h create mode 100644 common/wadfile.h create mode 100644 common/weaponinfo.h create mode 100644 common/wrect.h create mode 100644 engine/alias.h create mode 100644 engine/anorms.h create mode 100644 engine/cdll_exp.h create mode 100644 engine/cdll_int.h create mode 100644 engine/client/cl_cmds.c create mode 100644 engine/client/cl_custom.c create mode 100644 engine/client/cl_demo.c create mode 100644 engine/client/cl_events.c create mode 100644 engine/client/cl_frame.c create mode 100644 engine/client/cl_game.c create mode 100644 engine/client/cl_gameui.c create mode 100644 engine/client/cl_main.c create mode 100644 engine/client/cl_netgraph.c create mode 100644 engine/client/cl_parse.c create mode 100644 engine/client/cl_pmove.c create mode 100644 engine/client/cl_remap.c create mode 100644 engine/client/cl_scrn.c create mode 100644 engine/client/cl_tent.c create mode 100644 engine/client/cl_tent.h create mode 100644 engine/client/cl_video.c create mode 100644 engine/client/cl_view.c create mode 100644 engine/client/client.h create mode 100644 engine/client/gl_alias.c create mode 100644 engine/client/gl_backend.c create mode 100644 engine/client/gl_beams.c create mode 100644 engine/client/gl_cull.c create mode 100644 engine/client/gl_decals.c create mode 100644 engine/client/gl_draw.c create mode 100644 engine/client/gl_export.h create mode 100644 engine/client/gl_frustum.c create mode 100644 engine/client/gl_frustum.h create mode 100644 engine/client/gl_image.c create mode 100644 engine/client/gl_local.h create mode 100644 engine/client/gl_refrag.c create mode 100644 engine/client/gl_rlight.c create mode 100644 engine/client/gl_rmain.c create mode 100644 engine/client/gl_rmath.c create mode 100644 engine/client/gl_rmisc.c create mode 100644 engine/client/gl_rpart.c create mode 100644 engine/client/gl_rsurf.c create mode 100644 engine/client/gl_sprite.c create mode 100644 engine/client/gl_studio.c create mode 100644 engine/client/gl_vidnt.c create mode 100644 engine/client/gl_warp.c create mode 100644 engine/client/s_backend.c create mode 100644 engine/client/s_dsp.c create mode 100644 engine/client/s_load.c create mode 100644 engine/client/s_main.c create mode 100644 engine/client/s_mix.c create mode 100644 engine/client/s_mouth.c create mode 100644 engine/client/s_stream.c create mode 100644 engine/client/s_utils.c create mode 100644 engine/client/s_vox.c create mode 100644 engine/client/sound.h create mode 100644 engine/client/vgui/vgui_clip.cpp create mode 100644 engine/client/vgui/vgui_draw.c create mode 100644 engine/client/vgui/vgui_draw.h create mode 100644 engine/client/vgui/vgui_input.cpp create mode 100644 engine/client/vgui/vgui_int.cpp create mode 100644 engine/client/vgui/vgui_main.h create mode 100644 engine/client/vgui/vgui_surf.cpp create mode 100644 engine/client/vox.h create mode 100644 engine/common/avikit.c create mode 100644 engine/common/build.c create mode 100644 engine/common/cfgscript.c create mode 100644 engine/common/cmd.c create mode 100644 engine/common/com_strings.h create mode 100644 engine/common/common.c create mode 100644 engine/common/common.h create mode 100644 engine/common/con_utils.c create mode 100644 engine/common/console.c create mode 100644 engine/common/crclib.c create mode 100644 engine/common/crtlib.c create mode 100644 engine/common/crtlib.h create mode 100644 engine/common/custom.c create mode 100644 engine/common/cvar.c create mode 100644 engine/common/cvar.h create mode 100644 engine/common/filesystem.c create mode 100644 engine/common/filesystem.h create mode 100644 engine/common/gamma.c create mode 100644 engine/common/host.c create mode 100644 engine/common/host_state.c create mode 100644 engine/common/hpak.c create mode 100644 engine/common/imagelib/imagelib.h create mode 100644 engine/common/imagelib/img_bmp.c create mode 100644 engine/common/imagelib/img_dds.c create mode 100644 engine/common/imagelib/img_main.c create mode 100644 engine/common/imagelib/img_quant.c create mode 100644 engine/common/imagelib/img_tga.c create mode 100644 engine/common/imagelib/img_utils.c create mode 100644 engine/common/imagelib/img_wad.c create mode 100644 engine/common/infostring.c create mode 100644 engine/common/input.c create mode 100644 engine/common/input.h create mode 100644 engine/common/keys.c create mode 100644 engine/common/library.c create mode 100644 engine/common/library.h create mode 100644 engine/common/mathlib.c create mode 100644 engine/common/mathlib.h create mode 100644 engine/common/matrixlib.c create mode 100644 engine/common/mod_bmodel.c create mode 100644 engine/common/mod_local.h create mode 100644 engine/common/mod_studio.c create mode 100644 engine/common/model.c create mode 100644 engine/common/net_buffer.c create mode 100644 engine/common/net_buffer.h create mode 100644 engine/common/net_chan.c create mode 100644 engine/common/net_encode.c create mode 100644 engine/common/net_encode.h create mode 100644 engine/common/net_ws.c create mode 100644 engine/common/net_ws.h create mode 100644 engine/common/netchan.h create mode 100644 engine/common/pm_debug.c create mode 100644 engine/common/pm_local.h create mode 100644 engine/common/pm_surface.c create mode 100644 engine/common/pm_trace.c create mode 100644 engine/common/protocol.h create mode 100644 engine/common/soundlib/snd_main.c create mode 100644 engine/common/soundlib/snd_mp3.c create mode 100644 engine/common/soundlib/snd_utils.c create mode 100644 engine/common/soundlib/snd_wav.c create mode 100644 engine/common/soundlib/soundlib.h create mode 100644 engine/common/sys_con.c create mode 100644 engine/common/sys_win.c create mode 100644 engine/common/system.h create mode 100644 engine/common/titles.c create mode 100644 engine/common/world.c create mode 100644 engine/common/world.h create mode 100644 engine/common/zone.c create mode 100644 engine/custom.h create mode 100644 engine/customentity.h create mode 100644 engine/edict.h create mode 100644 engine/eiface.h create mode 100644 engine/keydefs.h create mode 100644 engine/menu_int.h create mode 100644 engine/physint.h create mode 100644 engine/progdefs.h create mode 100644 engine/server/server.h create mode 100644 engine/server/sv_client.c create mode 100644 engine/server/sv_cmds.c create mode 100644 engine/server/sv_custom.c create mode 100644 engine/server/sv_frame.c create mode 100644 engine/server/sv_game.c create mode 100644 engine/server/sv_init.c create mode 100644 engine/server/sv_log.c create mode 100644 engine/server/sv_main.c create mode 100644 engine/server/sv_move.c create mode 100644 engine/server/sv_phys.c create mode 100644 engine/server/sv_pmove.c create mode 100644 engine/server/sv_save.c create mode 100644 engine/server/sv_world.c create mode 100644 engine/shake.h create mode 100644 engine/sprite.h create mode 100644 engine/studio.h create mode 100644 engine/warpsin.h create mode 100644 pm_shared/pm_debug.h create mode 100644 pm_shared/pm_defs.h create mode 100644 pm_shared/pm_info.h create mode 100644 pm_shared/pm_materials.h create mode 100644 pm_shared/pm_movevars.h create mode 100644 pm_shared/pm_shared.h diff --git a/common/beamdef.h b/common/beamdef.h new file mode 100644 index 00000000..3b8c553a --- /dev/null +++ b/common/beamdef.h @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef BEAMDEF_H +#define BEAMDEF_H + +#define FBEAM_STARTENTITY 0x00000001 +#define FBEAM_ENDENTITY 0x00000002 +#define FBEAM_FADEIN 0x00000004 +#define FBEAM_FADEOUT 0x00000008 +#define FBEAM_SINENOISE 0x00000010 +#define FBEAM_SOLID 0x00000020 +#define FBEAM_SHADEIN 0x00000040 +#define FBEAM_SHADEOUT 0x00000080 +#define FBEAM_STARTVISIBLE 0x10000000 // Has this client actually seen this beam's start entity yet? +#define FBEAM_ENDVISIBLE 0x20000000 // Has this client actually seen this beam's end entity yet? +#define FBEAM_ISACTIVE 0x40000000 +#define FBEAM_FOREVER 0x80000000 + +typedef struct beam_s BEAM; +struct beam_s +{ + BEAM *next; + int type; + int flags; + vec3_t source; + vec3_t target; + vec3_t delta; + float t; // 0 .. 1 over lifetime of beam + float freq; + float die; + float width; + float amplitude; + float r, g, b; + float brightness; + float speed; + float frameRate; + float frame; + int segments; + int startEntity; + int endEntity; + int modelIndex; + int frameCount; + struct model_s *pFollowModel; + struct particle_s *particles; +}; + +#endif//BEAMDEF_H \ No newline at end of file diff --git a/common/bspfile.h b/common/bspfile.h new file mode 100644 index 00000000..ec1575b5 --- /dev/null +++ b/common/bspfile.h @@ -0,0 +1,321 @@ +/* +bspfile.h - BSP format included q1, hl1 support +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef BSPFILE_H +#define BSPFILE_H + +//#define SUPPORT_BSP2_FORMAT // allow to loading Darkplaces BSP2 maps (with broke binary compatibility) + +/* +============================================================================== + +BRUSH MODELS + +.bsp contain level static geometry with including PVS and lightning info +============================================================================== +*/ + +// header +#define Q1BSP_VERSION 29 // quake1 regular version (beta is 28) +#define HLBSP_VERSION 30 // half-life regular version +#define QBSP2_VERSION (('B' << 0) | ('S' << 8) | ('P' << 16) | ('2'<<24)) + +#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" +#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior + // ver. 3 was occupied by experimental versions of P2:savior change fmt + +#define DELUXEMAP_VERSION 1 +#define IDDELUXEMAPHEADER (('T'<<24)+('I'<<16)+('L'<<8)+'Q') // little-endian "QLIT" + +// worldcraft predefined angles +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +// bmodel limits +#define MAX_MAP_HULLS 4 // MAX_HULLS + +#define SURF_PLANEBACK BIT( 1 ) // plane should be negated +#define SURF_DRAWSKY BIT( 2 ) // sky surface +#define SURF_DRAWTURB_QUADS BIT( 3 ) // all subidivided polygons are quads +#define SURF_DRAWTURB BIT( 4 ) // warp surface +#define SURF_DRAWTILED BIT( 5 ) // face without lighmap +#define SURF_CONVEYOR BIT( 6 ) // scrolled texture (was SURF_DRAWBACKGROUND) +#define SURF_UNDERWATER BIT( 7 ) // caustics +#define SURF_TRANSPARENT BIT( 8 ) // it's a transparent texture (was SURF_DONTWARP) + +// lightstyle management +#define LM_STYLES 4 // MAXLIGHTMAPS +#define LS_NORMAL 0x00 +#define LS_UNUSED 0xFE +#define LS_NONE 0xFF + +// these limis not using by modelloader but only for displaying 'mapstats' correctly +#ifdef SUPPORT_BSP2_FORMAT +#define MAX_MAP_MODELS 2048 // embedded models +#define MAX_MAP_ENTSTRING 0x200000 // 2 Mb should be enough +#define MAX_MAP_PLANES 131072 // can be increased without problems +#define MAX_MAP_NODES 262144 // can be increased without problems +#define MAX_MAP_CLIPNODES 524288 // can be increased without problems +#define MAX_MAP_LEAFS 131072 // CRITICAL STUFF to run ad_sepulcher!!! +#define MAX_MAP_VERTS 524288 // can be increased without problems +#define MAX_MAP_FACES 262144 // can be increased without problems +#define MAX_MAP_MARKSURFACES 524288 // can be increased without problems +#else +#define MAX_MAP_MODELS 768 // embedded models +#define MAX_MAP_ENTSTRING 0x80000 // 512 kB should be enough +#define MAX_MAP_PLANES 65536 // can be increased without problems +#define MAX_MAP_NODES 32767 // because negative shorts are leafs +#define MAX_MAP_CLIPNODES 32767 // because negative shorts are contents +#define MAX_MAP_LEAFS 32767 // signed short limit +#define MAX_MAP_VERTS 65535 // unsigned short limit +#define MAX_MAP_FACES 65535 // unsigned short limit +#define MAX_MAP_MARKSURFACES 65535 // unsigned short limit +#endif + +#define MAX_MAP_ENTITIES 8192 // network limit +#define MAX_MAP_TEXINFO MAX_MAP_FACES // in theory each face may have personal texinfo +#define MAX_MAP_EDGES 0x100000 // can be increased but not needs +#define MAX_MAP_SURFEDGES 0x200000 // can be increased but not needs +#define MAX_MAP_TEXTURES 2048 // can be increased but not needs +#define MAX_MAP_MIPTEX 0x2000000 // 32 Mb internal textures data +#define MAX_MAP_LIGHTING 0x2000000 // 32 Mb lightmap raw data (can contain deluxemaps) +#define MAX_MAP_VISIBILITY 0x1000000 // 16 Mb visdata +#define MAX_MAP_FACEINFO 8192 // can be increased but not needs + +// quake lump ordering +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 // internal textures +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 // internal submodels +#define HEADER_LUMPS 15 + +// extra lump ordering +#define LUMP_LIGHTVECS 0 // deluxemap data +#define LUMP_FACEINFO 1 // landscape and lightmap resolution info +#define LUMP_CUBEMAPS 2 // cubemap description +#define LUMP_VERTNORMALS 3 // phong shaded vertex normals +#define LUMP_VERTEX_LIGHT 4 // contain compressed light cubes per empty leafs +#define LUMP_WORLDLIGHTS 5 // list of all the virtual and real lights (used to relight models in-game) +#define LUMP_COLLISION 6 // physics engine collision hull dump +#define LUMP_AINODEGRAPH 7 // node graph that stored into the bsp +#define LUMP_SHADOWMAP 8 // contains shadow map for direct light +#define LUMP_UNUSED1 9 // one lump reserved for me +#define LUMP_UNUSED2 10 // one lump reserved for me +#define LUMP_UNUSED3 11 // one lump reserved for me +#define EXTRA_LUMPS 12 // count of the extra lumps + +// texture flags +#define TEX_SPECIAL BIT( 0 ) // sky or slime, no lightmap or 256 subdivision +#define TEX_WORLD_LUXELS BIT( 1 ) // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels) +#define TEX_AXIAL_LUXELS BIT( 2 ) // force world luxels to axial positive scales +#define TEX_EXTRA_LIGHTMAP BIT( 3 ) // bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel + +// ambient sound types +enum +{ + AMBIENT_WATER = 0, // waterfall + AMBIENT_SKY, // wind + AMBIENT_SLIME, // never used in quake + AMBIENT_LAVA, // never used in quake + NUM_AMBIENTS, // automatic ambient sounds +}; + +// +// BSP File Structures +// + +typedef struct +{ + int fileofs; + int filelen; +} dlump_t; + +typedef struct +{ + int version; + dlump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int id; // must be little endian XASH + int version; + dlump_t lumps[EXTRA_LUMPS]; +} dextrahdr_t; + +typedef struct +{ + vec3_t mins; + vec3_t maxs; + vec3_t origin; // for sounds or lights + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface; + int numfaces; +} dmodel_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +typedef struct +{ + vec3_t point; +} dvertex_t; + +typedef struct +{ + vec3_t normal; + float dist; + int type; // PLANE_X - PLANE_ANYZ ? +} dplane_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are -(leafs + 1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + word firstface; + word numfaces; // counting both sides +} dnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + float mins[3]; // for sphere culling + float maxs[3]; + int firstface; + int numfaces; // counting both sides +} dnode32_t; + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + word firstmarksurface; + word nummarksurfaces; + + // automatic ambient sounds + byte ambient_level[NUM_AMBIENTS]; // ambient sound level (0 - 255) +} dleaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + float mins[3]; // for frustum culling + float maxs[3]; + + int firstmarksurface; + int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dleaf32_t; + +typedef struct +{ + int planenum; + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are contents +} dclipnode32_t; + +typedef struct +{ + float vecs[2][4]; // texmatrix [s/t][xyz offset] + int miptex; + short flags; + short faceinfo; // -1 no face info otherwise dfaceinfo_t +} dtexinfo_t; + +typedef struct +{ + char landname[16]; // name of decsription in mapname_land.txt + unsigned short texture_step; // default is 16, pixels\luxels ratio + unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) + short groupid; // to determine equal landscapes from various groups, -1 - no group +} dfaceinfo_t; + +typedef word dmarkface_t; // leaf marksurfaces indexes +typedef int dmarkface32_t; // leaf marksurfaces indexes + +typedef int dsurfedge_t; // map surfedges + +// NOTE: that edge 0 is never used, because negative edge nums +// are used for counterclockwise use of the edge in a face +typedef struct +{ + word v[2]; // vertex numbers +} dedge_t; + +typedef struct +{ + int v[2]; // vertex numbers +} dedge32_t; + +typedef struct +{ + word planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + + // lighting info + byte styles[LM_STYLES]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int planenum; + int side; + + int firstedge; // we must support > 64k edges + int numedges; + int texinfo; + + // lighting info + byte styles[LM_STYLES]; + int lightofs; // start of [numstyles*surfsize] samples +} dface32_t; + +#endif//BSPFILE_H \ No newline at end of file diff --git a/common/cl_entity.h b/common/cl_entity.h new file mode 100644 index 00000000..02f84e35 --- /dev/null +++ b/common/cl_entity.h @@ -0,0 +1,105 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef CL_ENTITY_H +#define CL_ENTITY_H + +typedef struct efrag_s +{ + struct mleaf_s *leaf; + struct efrag_s *leafnext; + struct cl_entity_s *entity; + struct efrag_s *entnext; +} efrag_t; + +typedef struct +{ + byte mouthopen; // 0 = mouth closed, 255 = mouth agape + byte sndcount; // counter for running average + int sndavg; // running average +} mouth_t; + +typedef struct +{ + float prevanimtime; + float sequencetime; + byte prevseqblending[2]; + vec3_t prevorigin; + vec3_t prevangles; + + int prevsequence; + float prevframe; + + byte prevcontroller[4]; + byte prevblending[2]; +} latchedvars_t; + +typedef struct +{ + // Time stamp for this movement + float animtime; + + vec3_t origin; + vec3_t angles; +} position_history_t; + +typedef struct cl_entity_s cl_entity_t; + +#define HISTORY_MAX 64 // Must be power of 2 +#define HISTORY_MASK ( HISTORY_MAX - 1 ) + +#include "entity_state.h" +#include "event_args.h" + +struct cl_entity_s +{ + int index; // Index into cl_entities ( should match actual slot, but not necessarily ) + qboolean player; // True if this entity is a "player" + + entity_state_t baseline; // The original state from which to delta during an uncompressed message + entity_state_t prevstate; // The state information from the penultimate message received from the server + entity_state_t curstate; // The state information from the last message received from server + + int current_position; // Last received history update index + position_history_t ph[HISTORY_MAX]; // History of position and angle updates for this player + + mouth_t mouth; // For synchronizing mouth movements. + + latchedvars_t latched; // Variables used by studio model rendering routines + + // Information based on interplocation, extrapolation, prediction, or just copied from last msg received. + // + float lastmove; + + // Actual render position and angles + vec3_t origin; + vec3_t angles; + + // Attachment points + vec3_t attachment[4]; + + // Other entity local information + int trivial_accept; + + struct model_s *model; // cl.model_precache[ curstate.modelindes ]; all visible entities have a model + struct efrag_s *efrag; // linked list of efrags + struct mnode_s *topnode; // for bmodels, first world node that splits bmodel, or NULL if not split + + float syncbase; // for client-side animations -- used by obsolete alias animation system, remove? + int visframe; // last frame this entity was found in an active leaf + colorVec cvFloorColor; +}; + +#endif//CL_ENTITY_H \ No newline at end of file diff --git a/common/com_model.h b/common/com_model.h new file mode 100644 index 00000000..d71e98fa --- /dev/null +++ b/common/com_model.h @@ -0,0 +1,497 @@ +/* +com_model.h - cient model structures +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef COM_MODEL_H +#define COM_MODEL_H + +#include "bspfile.h" // we need some declarations from it + +typedef vec_t vec2_t[2]; +typedef vec_t vec4_t[4]; + +/* +============================================================================== + + ENGINE MODEL FORMAT +============================================================================== +*/ +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define ZISCALE ((float)0x8000) + +#define MIPLEVELS 4 +#define VERTEXSIZE 7 +#define MAXLIGHTMAPS 4 +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// model types +typedef enum +{ + mod_bad = -1, + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +typedef struct mplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} mplane_t; + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + int planenum; +#ifdef SUPPORT_BSP2_FORMAT + int children[2]; // negative numbers are contents +#else + short children[2]; // negative numbers are contents +#endif +} mclipnode_t; + +// size is matched but representation is not +typedef struct +{ +#ifdef SUPPORT_BSP2_FORMAT + unsigned int v[2]; +#else + unsigned short v[2]; + unsigned int cachededgeoffset; +#endif +} medge_t; + +typedef struct texture_s +{ + char name[16]; + unsigned int width, height; + int gl_texturenum; + struct msurface_s *texturechain; // for gl_texsort drawing + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned short fb_texturenum; // auto-luma texturenum + unsigned short dt_texturenum; // detail-texture binding + unsigned int unused[3]; // reserved +} texture_t; + +typedef struct +{ + char landname[16]; // name of decsription in mapname_land.txt + unsigned short texture_step; // default is 16, pixels\luxels ratio + unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) + short groupid; // to determine equal landscapes from various groups, -1 - no group + + vec3_t mins, maxs; // terrain bounds (fill by user) + + int reserved[32]; // just for future expansions or mod-makers +} mfaceinfo_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot( 3Dpoint, vecs[i] ) + vecs[i][3] + mfaceinfo_t *faceinfo; // pointer to landscape info and lightmap resolution (may be NULL) + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct glpoly_s +{ + struct glpoly_s *next; + struct glpoly_s *chain; + int numverts; + int flags; // for SURF_UNDERWATER + float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +} glpoly_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; +#ifdef SUPPORT_BSP2_FORMAT + int firstsurface; + int numsurfaces; +#else + unsigned short firstsurface; + unsigned short numsurfaces; +#endif +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + float dx; // local texture coordinates + float dy; // + float scale; // Pixel scale + short texture; // Decal texture + short flags; // Decal flags FDECAL_* + short entityIndex; // Entity this is attached to +// Xash3D specific + vec3_t position; // location of the decal center in world space. + glpoly_t *polys; // precomputed decal vertices +}; + +typedef struct mleaf_s +{ +// common with node + int contents; + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int cluster; // helper to acess to uncompressed visdata + byte ambient_sound_level[NUM_AMBIENTS]; + +} mleaf_t; + +// surface extradata +typedef struct mextrasurf_s +{ + vec3_t mins, maxs; + vec3_t origin; // surface origin + struct msurface_s *surf; // upcast to surface + + // extended light info + int dlight_s, dlight_t; // gl lightmap coordinates for dynamic lightmaps + + short lightmapmins[2]; // lightmatrix + short lightextents[2]; + float lmvecs[2][4]; + + color24 *deluxemap; // note: this is the actual deluxemap data for this surface + byte *shadowmap; // note: occlusion map for this surface +// begin userdata + struct msurface_s *lightmapchain; // lightmapped polys + struct mextrasurf_s *detailchain; // for detail textures drawing + struct mextrasurf_s *mirrorchain; // for gl_texsort drawing + struct mextrasurf_s *lumachain; // draw fullbrights + struct cl_entity_s *parent; // upcast to owner entity + + int mirrortexturenum; // gl texnum + float mirrormatrix[4][4]; + + struct grasshdr_s *grass; // grass that linked by this surface + unsigned short grasscount; // number of bushes per polygon (used to determine total VBO size) + unsigned short numverts; // world->vertexes[] + int firstvertex; // fisrt look up in tr.tbn_vectors[], then acess to world->vertexes[] + + int reserved[32]; // just for future expansions or mod-makers +} mextrasurf_t; + +typedef struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + + short texturemins[2]; + short extents[2]; + + int light_s, light_t; // gl lightmap coordinates + + glpoly_t *polys; // multiple if warped + struct msurface_s *texturechain; + + mtexinfo_t *texinfo; + + // lighting info + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + int lightmaptexturenum; + byte styles[MAXLIGHTMAPS]; + int cached_light[MAXLIGHTMAPS]; // values currently used in lightmap + mextrasurf_t *info; // pointer to surface extradata (was cached_dlight) + + color24 *samples; // note: this is the actual lightmap data for this surface + decal_t *pdecals; +} msurface_t; + +typedef struct hull_s +{ + mclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +#ifndef CACHE_USER +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; // extradata +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[64]; // model name + qboolean needload; // bmodels and sprites don't cache normally + + // shared modelinfo + modtype_t type; // model type + int numframes; // sprite's framecount + byte *mempool; // private mempool (was synctype) + int flags; // hl compatibility + +// +// volume occupied by the model +// + vec3_t mins, maxs; // bounding box at angles '0 0 0' + float radius; + + // brush model + int firstmodelsurface; + int nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; // or studio animations + + int numplanes; + mplane_t *planes; + + int numleafs; // number of visible leafs, not counting 0 + mleaf_t *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + int *surfedges; + + int numclipnodes; + mclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; + + byte *visdata; + + color24 *lightdata; + char *entities; +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata +} model_t; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#define MAX_SCOREBOARDNAME 32 +#define MAX_INFO_STRING 256 + +#include "custom.h" + +typedef struct player_info_s +{ + int userid; // User id on server + char userinfo[MAX_INFO_STRING]; // User info string + char name[MAX_SCOREBOARDNAME]; // Name (extracted from userinfo) + int spectator; // Spectator or not, unused + + int ping; + int packet_loss; + + // skin information + char model[64]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; + + // hashed cd key + char hashedcdkey[16]; +} player_info_t; + +// +// sprite representation in memory +// +typedef enum { SPR_SINGLE = 0, SPR_GROUP, SPR_ANGLED } spriteframetype_t; + +typedef struct mspriteframe_s +{ + int width; + int height; + float up, down, left, right; + int gl_texturenum; +} mspriteframe_t; + +typedef struct +{ + int numframes; + float *intervals; + mspriteframe_t *frames[1]; +} mspritegroup_t; + +typedef struct +{ + spriteframetype_t type; + mspriteframe_t *frameptr; +} mspriteframedesc_t; + +typedef struct +{ + short type; + short texFormat; + int maxwidth; + int maxheight; + int numframes; + int radius; + int facecull; + int synctype; + mspriteframedesc_t frames[1]; +} msprite_t; + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ +#define MAXALIASVERTS 2048 +#define MAXALIASFRAMES 256 +#define MAXALIASTRIS 4096 +#define MAX_SKINS 32 + +// This mirrors trivert_t in trilib.h, is present so Quake knows how to +// load this data +typedef struct +{ + byte v[3]; + byte lightnormalindex; +} trivertex_t; + +typedef struct +{ + int firstpose; + int numposes; + trivertex_t bboxmin; + trivertex_t bboxmax; + float interval; + char name[16]; +} maliasframedesc_t; + +typedef struct +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + int synctype; + int flags; + float size; + + int reserved[8]; // VBO offsets + + int numposes; + int poseverts; + trivertex_t *posedata; // numposes * poseverts trivert_t + int *commands; // gl command list with embedded s/t + unsigned short gl_texturenum[MAX_SKINS][4]; + unsigned short fb_texturenum[MAX_SKINS][4]; + unsigned short gl_reserved0[MAX_SKINS][4]; // detail tex + unsigned short gl_reserved1[MAX_SKINS][4]; // normalmap + unsigned short gl_reserved2[MAX_SKINS][4]; // glossmap + + maliasframedesc_t frames[1]; // variable sized +} aliashdr_t; + +#endif//COM_MODEL_H \ No newline at end of file diff --git a/common/con_nprint.h b/common/con_nprint.h new file mode 100644 index 00000000..5d87c760 --- /dev/null +++ b/common/con_nprint.h @@ -0,0 +1,25 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CON_NPRINT_H +#define CON_NPRINT_H + +typedef struct con_nprint_s +{ + int index; // Row # + float time_to_live; // # of seconds before it dissappears + float color[3]; // RGB colors ( 0.0 -> 1.0 scale ) +} con_nprint_t; + +#endif//CON_NPRINT_H \ No newline at end of file diff --git a/common/const.h b/common/const.h new file mode 100644 index 00000000..8e81df25 --- /dev/null +++ b/common/const.h @@ -0,0 +1,779 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1<<0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1<<2) +#define FL_CLIENT (1<<3) +#define FL_INWATER (1<<4) +#define FL_MONSTER (1<<5) +#define FL_GODMODE (1<<6) +#define FL_NOTARGET (1<<7) +#define FL_SKIPLOCALHOST (1<<8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1<<9) // At rest / on the ground +#define FL_PARTIALGROUND (1<<10) // not all corners are valid +#define FL_WATERJUMP (1<<11) // player jumping out of water +#define FL_FROZEN (1<<12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1<<13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1<<14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1<<15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1<<16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1<<17) +#define FL_IMMUNE_SLIME (1<<18) +#define FL_IMMUNE_LAVA (1<<19) + +#define FL_PROXY (1<<20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1<<23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1<<26) // This client is a spectator, don't run touch functions, etc. +#define FL_LASERDOT (1<<27) // Predicted laser spot from rocket launcher + +#define FL_CUSTOMENTITY (1<<29) // This is a custom entity +#define FL_KILLME (1<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1<<31) // Entity is dormant, no updates to client + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1<<0) // Traceline with a simple box +#define FTRACE_IGNORE_GLASS (1<<1) // traceline will be ignored entities with rendermode != kRenderNormal + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) +#define MOVETYPE_COMPOUND 14 // glue two entities together (simple movewith) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block +#define SOLID_CUSTOM 5 // call external callbacks for tracing +#define SOLID_PORTAL 6 // borrowed from FTE + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD 1 // swirling cloud of particles +#define EF_MUZZLEFLASH 2 // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT 4 // DLIGHT centered at entity origin +#define EF_DIMLIGHT 8 // player flashlight +#define EF_INVLIGHT 16 // get lighting from ceiling +#define EF_NOINTERP 32 // don't interpolate the next frame +#define EF_LIGHT 64 // rocket flare glow sprite +#define EF_NODRAW 128 // don't draw entity + +#define EF_WATERSIDES (1<<26) // Do not remove sides for func_water entity +#define EF_FULLBRIGHT (1<<27) // Just get fullbright +#define EF_NOSHADOW (1<<28) // ignore shadow for this entity +#define EF_MERGE_VISIBILITY (1<<29) // this entity allowed to merge vis (e.g. env_sky or portal camera) +#define EF_REQUEST_PHS (1<<30) // This entity requested phs bitvector instead of pvsbitvector in AddToFullPack calls +// g-cont. one reserved bit here for me + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles +#define TE_EXPLFLAG_DRAWALPHA 16 // sprite will be drawn alpha +#define TE_EXPLFLAG_ROTATE 32 // rotate the sprite randomly + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction vector) +// byte (color) +// short (count) +// short (base speed) +// short (random velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out +// 1 is flickery credits +// 2 is write out (training room) +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. +#define TEFIRE_FLAG_ADDITIVE 32 // if set, sprite is rendered as additive + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// contents of a spot in the world +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +// These additional contents constants are defined in bspfile.h +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_CLIP -8 // changed to contents_solid +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 +#define CONTENTS_TRANSLUCENT -15 + +#define CONTENTS_LADDER -16 + +#define CONTENT_FLYFIELD -17 +#define CONTENT_GRAVITY_FLYFIELD -18 +#define CONTENT_FOG -19 + +#define CONTENT_EMPTY -1 +#define CONTENT_SOLID -2 +#define CONTENT_WATER -3 +#define CONTENT_SLIME -4 +#define CONTENT_LAVA -5 +#define CONTENT_SKY -6 + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#define IN_ATTACK (1<<0) +#define IN_JUMP (1<<1) +#define IN_DUCK (1<<2) +#define IN_FORWARD (1<<3) +#define IN_BACK (1<<4) +#define IN_USE (1<<5) +#define IN_CANCEL (1<<6) +#define IN_LEFT (1<<7) +#define IN_RIGHT (1<<8) +#define IN_MOVELEFT (1<<9) +#define IN_MOVERIGHT (1<<10) +#define IN_ATTACK2 (1<<11) +#define IN_RUN (1<<12) +#define IN_RELOAD (1<<13) +#define IN_ALT1 (1<<14) +#define IN_SCORE (1<<15) // Used by client.dll for when scoreboard is held down + +// Break Model Defines +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) +}; + +typedef int func_t; +typedef int string_t; + +typedef unsigned char byte; +typedef unsigned short word; + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum { false, true } qboolean; +#else +typedef int qboolean; +#endif + +typedef struct +{ + byte r, g, b; +} color24; + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + int hitgroup; // 0 == generic, non zero is specific body part +} trace_t; + +#endif//CONST_H \ No newline at end of file diff --git a/common/cvardef.h b/common/cvardef.h new file mode 100644 index 00000000..d963619e --- /dev/null +++ b/common/cvardef.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1<<1) // changes the client's info string +#define FCVAR_SERVER (1<<2) // notifies players when changed +#define FCVAR_EXTDLL (1<<3) // defined by external DLL +#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll +#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log +#define FCVAR_NOEXTRAWHITEPACE (1<<9) // strip trailing/leading white space from this cvar + +#define FCVAR_MOVEVARS (1<<10) // this cvar is a part of movevars_t struct that shared between client and server +#define FCVAR_LATCH (1<<11) // notify client what this cvar will be applied only after server restart (but don't does more nothing) +#define FCVAR_GLCONFIG (1<<12) // write it into opengl.cfg +#define FCVAR_CHANGED (1<<13) // set each time the cvar is changed +#define FCVAR_GAMEUIDLL (1<<14) // defined by the menu DLL +#define FCVAR_CHEAT (1<<15) // can not be changed if cheats are disabled + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; + +#endif//CVARDEF_H \ No newline at end of file diff --git a/common/demo_api.h b/common/demo_api.h new file mode 100644 index 00000000..fa89bbef --- /dev/null +++ b/common/demo_api.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef DEMO_API_H +#define DEMO_API_H + +typedef struct demo_api_s +{ + int (*IsRecording)( void ); + int (*IsPlayingback)( void ); + int (*IsTimeDemo)( void ); + void (*WriteBuffer)( int size, unsigned char *buffer ); +} demo_api_t; + +#endif//DEMO_API_H \ No newline at end of file diff --git a/common/dlight.h b/common/dlight.h new file mode 100644 index 00000000..b139d582 --- /dev/null +++ b/common/dlight.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef DLIGHT_H +#define DLIGHT_H + +typedef struct dlight_s +{ + vec3_t origin; + float radius; + color24 color; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + qboolean dark; // subtracts light instead of adding +} dlight_t; + +#endif//DLIGHT_H \ No newline at end of file diff --git a/common/entity_state.h b/common/entity_state.h new file mode 100644 index 00000000..4d04e8f0 --- /dev/null +++ b/common/entity_state.h @@ -0,0 +1,188 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENTITY_STATE_H +#define ENTITY_STATE_H + +// For entityType below +#define ENTITY_NORMAL (1<<0) +#define ENTITY_BEAM (1<<1) + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct entity_state_s entity_state_t; + +struct entity_state_s +{ +// Fields which are filled in by routines outside of delta compression + int entityType; + // Index into cl_entities array for this entity. + int number; + float msg_time; + + // Message number last time the player/entity state was updated. + int messagenum; + +// Fields which can be transitted and reconstructed over the network stream + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + byte eflags; + + // Render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + // Send bbox down to client for use during prediction. + vec3_t mins; + vec3_t maxs; + + int aiment; + // If owned by a player, the index of that player ( for projectiles ). + int owner; + + // Friction, for prediction. + float friction; + // Gravity multiplier + float gravity; + +// PLAYER SPECIFIC + int team; + int playerclass; + int health; + qboolean spectator; + int weaponmodel; + int gaitsequence; + // If standing on conveyor, e.g. + vec3_t basevelocity; + // Use the crouched hull, or the regular player hull. + int usehull; + // Latched buttons last time state updated. + int oldbuttons; + // -1 = in air, else pmove entity number + int onground; + int iStepLeft; + // How fast we are falling + float flFallVelocity; + + float fov; + int weaponanim; + + // Parametric movement overrides + vec3_t startpos; + vec3_t endpos; + float impacttime; + float starttime; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +}; + +#include "pm_info.h" + +typedef struct clientdata_s +{ + vec3_t origin; + vec3_t velocity; + + int viewmodel; + vec3_t punchangle; + int flags; + int waterlevel; + int watertype; + vec3_t view_ofs; + float health; + + int bInDuck; + int weapons; // remove? + + int flTimeStepSound; + int flDuckTime; + int flSwimTime; + int waterjumptime; + + float maxspeed; + + float fov; + int weaponanim; + + int m_iId; + int ammo_shells; + int ammo_nails; + int ammo_cells; + int ammo_rockets; + float m_flNextAttack; + + int tfstate; + int pushmsec; + int deadflag; + char physinfo[MAX_PHYSINFO_STRING]; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + +} clientdata_t; + +#include "weaponinfo.h" + +#define MAX_LOCAL_WEAPONS 64 // max weapons that can be predicted on the client + +typedef struct local_state_s +{ + entity_state_t playerstate; + clientdata_t client; + weapon_data_t weapondata[MAX_LOCAL_WEAPONS]; +} local_state_t; + +#endif//ENTITY_STATE_H \ No newline at end of file diff --git a/common/entity_types.h b/common/entity_types.h new file mode 100644 index 00000000..25a8fce9 --- /dev/null +++ b/common/entity_types.h @@ -0,0 +1,25 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ENTITY_TYPES_H +#define ENTITY_TYPES_H + +#define ET_NORMAL 0 +#define ET_PLAYER 1 +#define ET_TEMPENTITY 2 +#define ET_BEAM 3 +#define ET_FRAGMENTED 4 // BMODEL or SPRITE that was split across BSP nodes + +#endif//ENTITY_TYPES_H \ No newline at end of file diff --git a/common/event_api.h b/common/event_api.h new file mode 100644 index 00000000..20e50705 --- /dev/null +++ b/common/event_api.h @@ -0,0 +1,56 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EVENT_API_H +#define EVENT_API_H + +#define EVENT_API_VERSION 1 + +typedef struct event_api_s +{ + int version; + void ( *EV_PlaySound )( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + void ( *EV_StopSound )( int ent, int channel, const char *sample ); + int ( *EV_FindModelIndex )( const char *pmodel ); + int ( *EV_IsLocal )( int playernum ); + int ( *EV_LocalPlayerDucking )( void ); + void ( *EV_LocalPlayerViewheight )( float * ); + void ( *EV_LocalPlayerBounds )( int hull, float *mins, float *maxs ); + int ( *EV_IndexFromTrace)( struct pmtrace_s *pTrace ); + struct physent_s *( *EV_GetPhysent )( int idx ); + void ( *EV_SetUpPlayerPrediction )( int dopred, int bIncludeLocalClient ); + void ( *EV_PushPMStates )( void ); + void ( *EV_PopPMStates )( void ); + void ( *EV_SetSolidPlayers )( int playernum ); + void ( *EV_SetTraceHull )( int hull ); + void ( *EV_PlayerTrace )( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr ); + void ( *EV_WeaponAnimation )( int sequence, int body ); + unsigned short ( *EV_PrecacheEvent )( int type, const char* psz ); + void ( *EV_PlaybackEvent )( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + const char *( *EV_TraceTexture )( int ground, float *vstart, float *vend ); + void ( *EV_StopAllSounds )( int entnum, int entchannel ); + void ( *EV_KillEvents )( int entnum, const char *eventname ); + + // Xash3D extension + void ( *EV_PlayerTraceExt )( float *start, float *end, int traceFlags, int (*pfnIgnore)( struct physent_s *pe ), struct pmtrace_s *tr ); + const char *(*EV_SoundForIndex)( int index ); + struct msurface_s *( *EV_TraceSurface )( int ground, float *vstart, float *vend ); + struct movevars_s *( *EV_GetMovevars )( void ); + struct pmtrace_s *( *EV_VisTraceLine )( float *start, float *end, int flags ); + struct physent_s *( *EV_GetVisent )( int idx ); + int ( *EV_TestLine)( const vec3_t start, const vec3_t end, int flags ); +} event_api_t; + +#endif//EVENT_API_H \ No newline at end of file diff --git a/common/event_args.h b/common/event_args.h new file mode 100644 index 00000000..d85906cc --- /dev/null +++ b/common/event_args.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EVENT_ARGS_H +#define EVENT_ARGS_H + +// Event was invoked with stated origin +#define FEVENT_ORIGIN ( 1<<0 ) + +// Event was invoked with stated angles +#define FEVENT_ANGLES ( 1<<1 ) + +typedef struct event_args_s +{ + int flags; + + // Transmitted + int entindex; + + float origin[3]; + float angles[3]; + float velocity[3]; + + int ducking; + + float fparam1; + float fparam2; + + int iparam1; + int iparam2; + + int bparam1; + int bparam2; +} event_args_t; + +#endif//EVENT_ARGS_H \ No newline at end of file diff --git a/common/event_flags.h b/common/event_flags.h new file mode 100644 index 00000000..3c1d8fb3 --- /dev/null +++ b/common/event_flags.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EVENT_FLAGS_H +#define EVENT_FLAGS_H + +// Skip local host for event send. +#define FEV_NOTHOST (1<<0) + +// Send the event reliably. You must specify the origin and angles and use +// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything +// that depends on the event origin/angles. I.e., the origin/angles are not +// taken from the invoking edict for reliable events. +#define FEV_RELIABLE (1<<1) + +// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC +// sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). +#define FEV_GLOBAL (1<<2) + +// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate +// +#define FEV_UPDATE (1<<3) + +// Only send to entity specified as the invoker +#define FEV_HOSTONLY (1<<4) + +// Only send if the event was created on the server. +#define FEV_SERVER (1<<5) + +// Only issue event client side ( from shared code ) +#define FEV_CLIENT (1<<6) + +#endif//EVENT_FLAGS_H \ No newline at end of file diff --git a/common/features.h b/common/features.h new file mode 100644 index 00000000..4efc5578 --- /dev/null +++ b/common/features.h @@ -0,0 +1,30 @@ +/* +features.h - engine features that can be enabled by mod-maker request +Copyright (C) 2012 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef FEATURES_H +#define FEATURES_H + +// list of engine features that can be enabled through callback SV_CheckFeatures +#define ENGINE_WRITE_LARGE_COORD (1<<0) // replace standard message WRITE_COORD with big message for support more than 8192 units in world +#define ENGINE_QUAKE_COMPATIBLE (1<<1) // make engine compatible with quake (flags and effects) +#define ENGINE_LOAD_DELUXEDATA (1<<2) // loading deluxemap for map (if present) +#define ENGINE_PHYSICS_PUSHER_EXT (1<<3) // enable sets of improvements for MOVETYPE_PUSH physics +#define ENGINE_LARGE_LIGHTMAPS (1<<4) // change lightmap sizes from 128x128 to 1024x1024 +#define ENGINE_COMPENSATE_QUAKE_BUG (1<<5) // compensate stupid quake bug (inverse pitch) for mods where this bug is fixed +// reserved +#define ENGINE_COMPUTE_STUDIO_LERP (1<<7) // enable MOVETYPE_STEP lerping back in engine +#define ENGINE_FIXED_FRAMERATE (1<<8) // keep constant rate for client and server (but don't clamp renderer calls) + +#endif//FEATURES_H \ No newline at end of file diff --git a/common/gameinfo.h b/common/gameinfo.h new file mode 100644 index 00000000..511b3718 --- /dev/null +++ b/common/gameinfo.h @@ -0,0 +1,49 @@ +/* +gameinfo.h - current game info +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef GAMEINFO_H +#define GAMEINFO_H + +#define GFL_NOMODELS (1<<0) + +/* +======================================================================== + +GAMEINFO stuff + +internal shared gameinfo structure (readonly for engine parts) +======================================================================== +*/ +typedef struct +{ + // filesystem info + char gamefolder[64]; // used for change game '-game x' + char startmap[64]; // map to start singleplayer game + char trainmap[64]; // map to start hazard course (if specified) + char title[64]; // Game Main Title + char version[14]; // game version (optional) + short flags; // game flags + + // about mod info + char game_url[256]; // link to a developer's site + char update_url[256]; // link to updates page + char type[64]; // single, toolkit, multiplayer etc + char date[64]; + char size[64]; // displayed mod size + + int gamemode; +} GAMEINFO; + +#endif//GAMEINFO_H \ No newline at end of file diff --git a/common/hltv.h b/common/hltv.h new file mode 100644 index 00000000..79251910 --- /dev/null +++ b/common/hltv.h @@ -0,0 +1,59 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef HLTV_H +#define HLTV_H + +#define TYPE_CLIENT 0 // client is a normal HL client (default) +#define TYPE_PROXY 1 // client is another proxy +#define TYPE_COMMENTATOR 3 // client is a commentator +#define TYPE_DEMO 4 // client is a demo file + +// sub commands of svc_hltv: +#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands +#define HLTV_STATUS 1 // send status infos about proxy +#define HLTV_LISTEN 2 // tell client to listen to a multicast stream + +// sub commands of svc_director: +#define DRC_CMD_NONE 0 // NULL director command +#define DRC_CMD_START 1 // start director mode +#define DRC_CMD_EVENT 2 // informs about director command +#define DRC_CMD_MODE 3 // switches camera modes +#define DRC_CMD_CAMERA 4 // sets camera registers +#define DRC_CMD_TIMESCALE 5 // sets time scale +#define DRC_CMD_MESSAGE 6 // send HUD centerprint +#define DRC_CMD_SOUND 7 // plays a particular sound +#define DRC_CMD_STATUS 8 // status info about broadcast +#define DRC_CMD_BANNER 9 // banner file name for HLTV gui +#define DRC_CMD_FADE 10 // send screen fade command +#define DRC_CMD_SHAKE 11 // send screen shake command +#define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command + +#define DRC_CMD_LAST 12 + +// HLTV_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) // +#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene +#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo +#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) +#define DRC_FLAG_INTRO (1<<8) // is a introduction scene +#define DRC_FLAG_FINAL (1<<9) // is a final scene +#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data + +#define MAX_DIRECTOR_CMD_PARAMETERS 4 +#define MAX_DIRECTOR_CMD_STRING 128 + +#endif//HLTV_H \ No newline at end of file diff --git a/common/ivoicetweak.h b/common/ivoicetweak.h new file mode 100644 index 00000000..2f95d515 --- /dev/null +++ b/common/ivoicetweak.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef IVOICETWEAK_H +#define IVOICETWEAK_H + +// These provide access to the voice controls. +typedef enum +{ + MicrophoneVolume = 0, // values 0-1. + OtherSpeakerScale // values 0-1. Scales how loud other players are. +} VoiceTweakControl; + +typedef struct IVoiceTweak_s +{ + // These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back + // without sending to the server. + int (*StartVoiceTweakMode)( void ); // Returns 0 on error. + void (*EndVoiceTweakMode)( void ); + + // Get/set control values. + void (*SetControlFloat)( VoiceTweakControl iControl, float value ); + float (*GetControlFloat)( VoiceTweakControl iControl ); +} IVoiceTweak; + +#endif//IVOICETWEAK_H \ No newline at end of file diff --git a/common/lightstyle.h b/common/lightstyle.h new file mode 100644 index 00000000..8b42edac --- /dev/null +++ b/common/lightstyle.h @@ -0,0 +1,29 @@ +/* +lightstyle.h - lighstyle description +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef LIGHTSTYLE_H +#define LIGHTSTYLE_H + +typedef struct +{ + char pattern[256]; + float map[256]; + int length; + float value; + qboolean interp; // allow to interpolate this lightstyle + float time; // local time is gurantee what new style begins from the start, not mid or end of the sequence +} lightstyle_t; + +#endif//LIGHTSTYLE_H \ No newline at end of file diff --git a/common/mathlib.h b/common/mathlib.h new file mode 100644 index 00000000..fadb97fb --- /dev/null +++ b/common/mathlib.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// mathlib.h + +#include + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; // x,y,z,w + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +struct mplane_s; + +extern vec3_t vec3_origin; +extern int nanmask; + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#ifndef VECTOR_H + #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#endif + +#define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];} +#define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];} +#define VectorCopy(a,b) {(b)[0]=(a)[0];(b)[1]=(a)[1];(b)[2]=(a)[2];} +#define VectorClear(a) {(a)[0]=0.0;(a)[1]=0.0;(a)[2]=0.0;} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc); + +vec_t _DotProduct (vec3_t v1, vec3_t v2); +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out); +void _VectorCopy (vec3_t in, vec3_t out); + +int VectorCompare (const vec3_t v1, const vec3_t v2); +float Length (const vec3_t v); +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross); +float VectorNormalize (vec3_t v); // returns vector length +void VectorInverse (vec3_t v); +void VectorScale (const vec3_t in, vec_t scale, vec3_t out); + +void R_ConcatRotations (float in1[3][3], float in2[3][3], float out[3][3]); +void R_ConcatTransforms (float in1[3][4], float in2[3][4], float out[3][4]); + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up); +#define AngleIVectors AngleVectorsTranspose + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ); +void AngleIMatrix (const vec3_t angles, float (*matrix)[4] ); +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out); + +void NormalizeAngles( vec3_t angles ); +void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac ); +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ); + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up); +void VectorAngles( const vec3_t forward, vec3_t angles ); + +int InvertMatrix( const float * m, float *out ); + +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); +float anglemod(float a); + +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) \ + (((p)->type < 3)? \ + ( \ + ((p)->dist <= (emins)[(p)->type])? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type])?\ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide( (emins), (emaxs), (p))) diff --git a/common/net_api.h b/common/net_api.h new file mode 100644 index 00000000..00831394 --- /dev/null +++ b/common/net_api.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef NET_API_H +#define NET_API_H + +#include "netadr.h" + +#define NETAPI_REQUEST_SERVERLIST ( 0 ) // Doesn't need a remote address +#define NETAPI_REQUEST_PING ( 1 ) +#define NETAPI_REQUEST_RULES ( 2 ) +#define NETAPI_REQUEST_PLAYERS ( 3 ) +#define NETAPI_REQUEST_DETAILS ( 4 ) + +// Set this flag for things like broadcast requests, etc. where the engine should not +// kill the request hook after receiving the first response +#define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) + +typedef void (*net_api_response_func_t) ( struct net_response_s *response ); + +#define NET_SUCCESS ( 0 ) +#define NET_ERROR_TIMEOUT ( 1<<0 ) +#define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) +#define NET_ERROR_UNDEFINED ( 1<<2 ) + +typedef struct net_adrlist_s +{ + struct net_adrlist_s *next; + netadr_t remote_address; +} net_adrlist_t; + +typedef struct net_response_s +{ + // NET_SUCCESS or an error code + int error; + // Context ID + int context; + // Type + int type; + // Server that is responding to the request + netadr_t remote_address; + // Response RTT ping time + double ping; + // Key/Value pair string ( separated by backlash \ characters ) + // WARNING: You must copy this buffer in the callback function, because it is freed + // by the engine right after the call!!!! + // ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's + void *response; +} net_response_t; + +typedef struct net_status_s +{ + // Connected to remote server? 1 == yes, 0 otherwise + int connected; + // Client's IP address + netadr_t local_address; + // Address of remote server + netadr_t remote_address; + // Packet Loss ( as a percentage ) + int packet_loss; + // Latency, in seconds ( multiply by 1000.0 to get milliseconds ) + double latency; + // Connection time, in seconds + double connection_time; + // Rate setting ( for incoming data ) + double rate; +} net_status_t; + +typedef struct net_api_s +{ + // APIs + void (*InitNetworking)( void ); + void (*Status )( struct net_status_s *status ); + void (*SendRequest)( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response ); + void (*CancelRequest)( int context ); + void (*CancelAllRequests)( void ); + char *(*AdrToString)( struct netadr_s *a ); + int ( *CompareAdr)( struct netadr_s *a, struct netadr_s *b ); + int ( *StringToAdr)( char *s, struct netadr_s *a ); + const char *(*ValueForKey)( const char *s, const char *key ); + void (*RemoveKey)( char *s, const char *key ); + void (*SetValueForKey)( char *s, const char *key, const char *value, int maxsize ); +} net_api_t; + +#endif//NET_APIH \ No newline at end of file diff --git a/common/netadr.h b/common/netadr.h new file mode 100644 index 00000000..dfeea8b0 --- /dev/null +++ b/common/netadr.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef NETADR_H +#define NETADR_H + +typedef enum +{ + NA_UNUSED, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX +} netadrtype_t; + +typedef struct netadr_s +{ + netadrtype_t type; + unsigned char ip[4]; + unsigned char ipx[10]; + unsigned short port; +} netadr_t; + +#endif//NETADR_H \ No newline at end of file diff --git a/common/particledef.h b/common/particledef.h new file mode 100644 index 00000000..959c960b --- /dev/null +++ b/common/particledef.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PARTICLEDEF_H +#define PARTICLEDEF_H + +typedef enum +{ + pt_static, + pt_grav, + pt_slowgrav, + pt_fire, + pt_explode, + pt_explode2, + pt_blob, + pt_blob2, + pt_vox_slowgrav, + pt_vox_grav, + pt_clientcustom // Must have callback function specified +} ptype_t; + +typedef struct particle_s +{ + vec3_t org; + short color; + short packedColor; + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; + void (*deathfunc)( struct particle_s *particle ); + + // for pt_clientcusttom, we'll call this function each frame + void (*callback)( struct particle_s *particle, float frametime ); + + // For deathfunc, etc. + unsigned char context; +} particle_t; + +#endif//PARTICLEDEF_H \ No newline at end of file diff --git a/common/pmtrace.h b/common/pmtrace.h new file mode 100644 index 00000000..6394de56 --- /dev/null +++ b/common/pmtrace.h @@ -0,0 +1,41 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PM_TRACE_H +#define PM_TRACE_H + +typedef struct +{ + vec3_t normal; + float dist; +} pmplane_t; + +typedef struct pmtrace_s pmtrace_t; + +struct pmtrace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; // End point is in empty space or in water + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + pmplane_t plane; // surface normal at impact + int ent; // entity at impact + vec3_t deltavelocity; // Change in player's velocity caused by impact. + // Only run on server. + int hitgroup; +}; + +#endif//PM_TRACE_H \ No newline at end of file diff --git a/common/qfont.h b/common/qfont.h new file mode 100644 index 00000000..06408572 --- /dev/null +++ b/common/qfont.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef QFONT_H +#define QFONT_H + +// Font stuff + +#define NUM_GLYPHS 256 + +typedef struct +{ + short startoffset; + short charwidth; +} charinfo; + +typedef struct qfont_s +{ + int width, height; + int rowcount; + int rowheight; + charinfo fontinfo[NUM_GLYPHS]; + byte data[4]; +} qfont_t; + +#endif//QFONT_H \ No newline at end of file diff --git a/common/r_efx.h b/common/r_efx.h new file mode 100644 index 00000000..fc27bef7 --- /dev/null +++ b/common/r_efx.h @@ -0,0 +1,195 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef R_EFX_H +#define R_EFX_H + +// particle_t +#if !defined( PARTICLEDEFH ) +#include "particledef.h" +#endif + +// BEAM +#if !defined( BEAMDEFH ) +#include "beamdef.h" +#endif + +// dlight_t +#if !defined ( DLIGHTH ) +#include "dlight.h" +#endif + +// cl_entity_t +#if !defined( CL_ENTITYH ) +#include "cl_entity.h" +#endif + +/* +// FOR REFERENCE, These are the built-in tracer colors. Note, color 4 is the one +// that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings +color24 gTracerColors[] = +{ + { 255, 255, 255 }, // White + { 255, 0, 0 }, // Red + { 0, 255, 0 }, // Green + { 0, 0, 255 }, // Blue + { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. + { 255, 167, 17 }, // Yellow-orange sparks + { 255, 130, 90 }, // Yellowish streaks (garg) + { 55, 60, 144 }, // Blue egon streak + { 255, 130, 90 }, // More Yellowish streaks (garg) + { 255, 140, 90 }, // More Yellowish streaks (garg) + { 200, 130, 90 }, // More red streaks (garg) + { 255, 120, 70 }, // Darker red streaks (garg) +}; +*/ + +// Temporary entity array +#define TENTPRIORITY_LOW 0 +#define TENTPRIORITY_HIGH 1 + +// TEMPENTITY flags +#define FTENT_NONE 0x00000000 +#define FTENT_SINEWAVE 0x00000001 +#define FTENT_GRAVITY 0x00000002 +#define FTENT_ROTATE 0x00000004 +#define FTENT_SLOWGRAVITY 0x00000008 +#define FTENT_SMOKETRAIL 0x00000010 +#define FTENT_COLLIDEWORLD 0x00000020 +#define FTENT_FLICKER 0x00000040 +#define FTENT_FADEOUT 0x00000080 +#define FTENT_SPRANIMATE 0x00000100 +#define FTENT_HITSOUND 0x00000200 +#define FTENT_SPIRAL 0x00000400 +#define FTENT_SPRCYCLE 0x00000800 +#define FTENT_COLLIDEALL 0x00001000 // will collide with world and slideboxes +#define FTENT_PERSIST 0x00002000 // tent is not removed when unable to draw +#define FTENT_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything +#define FTENT_PLYRATTACHMENT 0x00008000 // tent is attached to a player (owner) +#define FTENT_SPRANIMATELOOP 0x00010000 // animating sprite doesn't die when last frame is displayed +#define FTENT_SPARKSHOWER 0x00020000 +#define FTENT_NOMODEL 0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things ) +#define FTENT_CLIENTCUSTOM 0x00080000 // Must specify callback. Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things ) +#define FTENT_SCALE 0x00100000 // An experiment + +typedef struct tempent_s TEMPENTITY; +typedef struct tempent_s +{ + int flags; + float die; + float frameMax; + float x; + float y; + float z; + float fadeSpeed; + float bounceFactor; + int hitSound; + void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ); + void (*callback)( struct tempent_s *ent, float frametime, float currenttime ); + TEMPENTITY *next; + int priority; + short clientIndex; // if attached, this is the index of the client to stick to + // if COLLIDEALL, this is the index of the client to ignore + // TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex! + + vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. + cl_entity_t entity; + + // baseline.origin - velocity + // baseline.renderamt - starting fadeout intensity + // baseline.angles - angle velocity +} TEMPENTITY; + +typedef struct efx_api_s efx_api_t; + +struct efx_api_s +{ + particle_t *(*R_AllocParticle)( void (*callback)( struct particle_s *particle, float frametime )); + void (*R_BlobExplosion)( float *org ); + void (*R_Blood)( float *org, float *dir, int pcolor, int speed ); + void (*R_BloodSprite)( float *org, int colorindex, int modelIndex, int modelIndex2, float size ); + void (*R_BloodStream)( float *org, float *dir, int pcolor, int speed ); + void (*R_BreakModel)( float *pos, float *size, float *dir, float random, float life, int count, int modelIndex, char flags ); + void (*R_Bubbles)( float *mins, float *maxs, float height, int modelIndex, int count, float speed ); + void (*R_BubbleTrail)( float *start, float *end, float height, int modelIndex, int count, float speed ); + void (*R_BulletImpactParticles)( float *pos ); + void (*R_EntityParticles)( struct cl_entity_s *ent ); + void (*R_Explosion)( float *pos, int model, float scale, float framerate, int flags ); + void (*R_FizzEffect)( struct cl_entity_s *pent, int modelIndex, int density ); + void (*R_FireField)( float *org, int radius, int modelIndex, int count, int flags, float life ); + void (*R_FlickerParticles)( float *org ); + void (*R_FunnelSprite)( float *org, int modelIndex, int reverse ); + void (*R_Implosion)( float *end, float radius, int count, float life ); + void (*R_LargeFunnel)( float *org, int reverse ); + void (*R_LavaSplash)( float *org ); + void (*R_MultiGunshot)( float *org, float *dir, float *noise, int count, int decalCount, int *decalIndices ); + void (*R_MuzzleFlash)( float *pos1, int type ); + void (*R_ParticleBox)( float *mins, float *maxs, unsigned char r, unsigned char g, unsigned char b, float life ); + void (*R_ParticleBurst)( float *pos, int size, int color, float life ); + void (*R_ParticleExplosion)( float *org ); + void (*R_ParticleExplosion2)( float *org, int colorStart, int colorLength ); + void (*R_ParticleLine)( float *start, float *end, unsigned char r, unsigned char g, unsigned char b, float life ); + void (*R_PlayerSprites)( int client, int modelIndex, int count, int size ); + void (*R_Projectile)( float *origin, float *velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) ); + void (*R_RicochetSound)( float *pos ); + void (*R_RicochetSprite)( float *pos, struct model_s *pmodel, float duration, float scale ); + void (*R_RocketFlare)( float *pos ); + void (*R_RocketTrail)( float *start, float *end, int type ); + void (*R_RunParticleEffect)( float *org, float *dir, int color, int count ); + void (*R_ShowLine)( float *start, float *end ); + void (*R_SparkEffect)( float *pos, int count, int velocityMin, int velocityMax ); + void (*R_SparkShower)( float *pos ); + void (*R_SparkStreaks)( float *pos, int count, int velocityMin, int velocityMax ); + void (*R_Spray)( float *pos, float *dir, int modelIndex, int count, int speed, int spread, int rendermode ); + void (*R_Sprite_Explode)( TEMPENTITY *pTemp, float scale, int flags ); + void (*R_Sprite_Smoke)( TEMPENTITY *pTemp, float scale ); + void (*R_Sprite_Spray)( float *pos, float *dir, int modelIndex, int count, int speed, int iRand ); + void (*R_Sprite_Trail)( int type, float *start, float *end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed ); + void (*R_Sprite_WallPuff)( TEMPENTITY *pTemp, float scale ); + void (*R_StreakSplash)( float *pos, float *dir, int color, int count, float speed, int velocityMin, int velocityMax ); + void (*R_TracerEffect)( float *start, float *end ); + void (*R_UserTracerParticle)( float *org, float *vel, float life, int colorIndex, float length, unsigned char deathcontext, void (*deathfunc)( struct particle_s *particle )); + particle_t *(*R_TracerParticles)( float *org, float *vel, float life ); + void (*R_TeleportSplash)( float *org ); + void (*R_TempSphereModel)( float *pos, float speed, float life, int count, int modelIndex ); + TEMPENTITY *(*R_TempModel)( float *pos, float *dir, float *angles, float life, int modelIndex, int soundtype ); + TEMPENTITY *(*R_DefaultSprite)( float *pos, int spriteIndex, float framerate ); + TEMPENTITY *(*R_TempSprite)( float *pos, float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); + int (*Draw_DecalIndex)( int id ); + int (*Draw_DecalIndexFromName)( char *name ); + void (*R_DecalShoot)( int textureIndex, int entity, int modelIndex, float *position, int flags ); + void (*R_AttachTentToPlayer)( int client, int modelIndex, float zoffset, float life ); + void (*R_KillAttachedTents)( int client ); + BEAM *(*R_BeamCirclePoints)( int type, float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamEntPoint)( int startEnt, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamEnts)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamFollow)( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); + void (*R_BeamKill)( int deadEntity ); + BEAM *(*R_BeamLightning)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); + BEAM *(*R_BeamPoints)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamRing)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + dlight_t *(*CL_AllocDlight)( int key ); + dlight_t *(*CL_AllocElight)( int key ); + TEMPENTITY *(*CL_TempEntAlloc)( float *org, struct model_s *model ); + TEMPENTITY *(*CL_TempEntAllocNoModel)( float *org ); + TEMPENTITY *(*CL_TempEntAllocHigh)( float *org, struct model_s *model ); + TEMPENTITY *(*CL_TentEntAllocCustom)( float *origin, struct model_s *model, int high, void (*callback)( struct tempent_s *ent, float frametime, float currenttime )); + void (*R_GetPackedColor)( short *packed, short color ); + short (*R_LookupColor)( unsigned char r, unsigned char g, unsigned char b ); + void (*R_DecalRemoveAll)( int textureIndex ); // textureIndex points to the decal index in the array, not the actual texture index. + void (*R_FireCustomDecal)( int textureIndex, int entity, int modelIndex, float *position, int flags, float scale ); +}; + +#endif//R_EFX_H \ No newline at end of file diff --git a/common/r_studioint.h b/common/r_studioint.h new file mode 100644 index 00000000..00384a16 --- /dev/null +++ b/common/r_studioint.h @@ -0,0 +1,154 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#ifndef R_STUDIOINT_H +#define R_STUDIOINT_H + +#define STUDIO_INTERFACE_VERSION 1 + +typedef struct engine_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc )( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check )( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile )( char *path, struct cache_user_s *cu ); + // Retrieve model pointer for the named model + struct model_s *( *Mod_ForName )( const char *name, int crash_if_missing ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata )( struct model_s *mod ); + // Retrieve indexed model from client side model precache list + struct model_s *( *GetModelByIndex )( int index ); + // Get entity that is set for rendering + struct cl_entity_s * ( *GetCurrentEntity )( void ); + // Get referenced player_info_t + struct player_info_s *( *PlayerInfo )( int index ); + // Get most recently received player state data from network system + struct entity_state_s *( *GetPlayerState )( int index ); + // Get viewentity + struct cl_entity_s * ( *GetViewEntity )( void ); + // Get current frame count, and last two timestampes on client + void ( *GetTimes )( int *framecount, double *current, double *old ); + // Get a pointer to a cvar by name + struct cvar_s *( *GetCvar )( const char *name ); + // Get current render origin and view vectors ( up, right and vpn ) + void ( *GetViewInfo )( float *origin, float *upv, float *rightv, float *vpnv ); + // Get sprite model used for applying chrome effect + struct model_s *( *GetChromeSprite )( void ); + // Get model counters so we can incement instrumentation + void ( *GetModelCounters )( int **s, int **a ); + // Get software scaling coefficients + void ( *GetAliasScale )( float *x, float *y ); + + // Get bone, light, alias, and rotation matrices + float ****( *StudioGetBoneTransform )( void ); + float ****( *StudioGetLightTransform )( void ); + float ***( *StudioGetAliasTransform )( void ); + float ***( *StudioGetRotationMatrix )( void ); + + // Set up body part, and get submodel pointers + void ( *StudioSetupModel )( int bodypart, void **ppbodypart, void **ppsubmodel ); + // Check if entity's bbox is in the view frustum + int ( *StudioCheckBBox )( void ); + // Apply lighting effects to model + void ( *StudioDynamicLight )( struct cl_entity_s *ent, struct alight_s *plight ); + void ( *StudioEntityLight )( struct alight_s *plight ); + void ( *StudioSetupLighting )( struct alight_s *plighting ); + + // Draw mesh vertices + void ( *StudioDrawPoints )( void ); + + // Draw hulls around bones + void ( *StudioDrawHulls )( void ); + // Draw bbox around studio models + void ( *StudioDrawAbsBBox )( void ); + // Draws bones + void ( *StudioDrawBones )( void ); + // Loads in appropriate texture for model + void ( *StudioSetupSkin )( void *ptexturehdr, int index ); + // Sets up for remapped colors + void ( *StudioSetRemapColors )( int top, int bottom ); + // Set's player model and returns model pointer + struct model_s *( *SetupPlayerModel )( int index ); + // Fires any events embedded in animation + void ( *StudioClientEvents )( void ); + // Retrieve/set forced render effects flags + int ( *GetForceFaceFlags )( void ); + void ( *SetForceFaceFlags )( int flags ); + // Tell engine the value of the studio model header + void ( *StudioSetHeader )( void *header ); + // Tell engine which model_t * is being renderered + void ( *SetRenderModel )( struct model_s *model ); + + // Final state setup and restore for rendering + void ( *SetupRenderer )( int rendermode ); + void ( *RestoreRenderer )( void ); + + // Set render origin for applying chrome effect + void ( *SetChromeOrigin )( void ); + + // True if using D3D/OpenGL + int ( *IsHardware )( void ); + + // Only called by hardware interface + void ( *GL_StudioDrawShadow )( void ); + void ( *GL_SetRenderMode )( int mode ); + + void ( *StudioSetRenderamt )( int iRenderamt ); + void ( *StudioSetCullState )( int iCull ); + void ( *StudioRenderShadow )( int iSprite, float *p1, float *p2, float *p3, float *p4 ); +} engine_studio_api_t; + +typedef struct server_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc )( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check )( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile )( char *path, struct cache_user_s *cu ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata )( struct model_s *mod ); +} server_studio_api_t; + +// client blending +typedef struct r_studio_interface_s +{ + int version; + int ( *StudioDrawModel )( int flags ); + int ( *StudioDrawPlayer )( int flags, struct entity_state_s *pplayer ); +} r_studio_interface_t; + +// server blending +#define SV_BLENDING_INTERFACE_VERSION 1 + +typedef struct sv_blending_interface_s +{ + int version; + + void ( *SV_StudioSetupBones )( struct model_s *pModel, + float frame, + int sequence, + const vec3_t angles, + const vec3_t origin, + const byte *pcontroller, + const byte *pblending, + int iBone, + const edict_t *pEdict ); +} sv_blending_interface_t; + +#endif//R_STUDIOINT_H \ No newline at end of file diff --git a/common/ref_params.h b/common/ref_params.h new file mode 100644 index 00000000..3a0c1859 --- /dev/null +++ b/common/ref_params.h @@ -0,0 +1,105 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef REF_PARAMS_H +#define REF_PARAMS_H + +typedef struct ref_params_s +{ + // output + vec3_t vieworg; + vec3_t viewangles; + + vec3_t forward; + vec3_t right; + vec3_t up; + + // Client frametime; + float frametime; + // Client time + float time; + + // Misc + int intermission; + int paused; + int spectator; + int onground; + int waterlevel; + + vec3_t simvel; + vec3_t simorg; + + vec3_t viewheight; + float idealpitch; + + vec3_t cl_viewangles; + int health; + vec3_t crosshairangle; + float viewsize; + + vec3_t punchangle; + int maxclients; + int viewentity; + int playernum; + int max_entities; + int demoplayback; + int hardware; + int smoothing; + + // Last issued usercmd + struct usercmd_s *cmd; + + // Movevars + struct movevars_s *movevars; + + int viewport[4]; // the viewport coordinates x, y, width, height + int nextView; // the renderer calls ClientDLL_CalcRefdef() and Renderview + // so long in cycles until this value is 0 (multiple views) + int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_params_t; + +// same as ref_params but for overview mode +typedef struct ref_overview_s +{ + vec3_t origin; + qboolean rotated; + + float xLeft; + float xRight; + float yTop; + float yBottom; + float zFar; + float zNear; + float flZoom; +} ref_overview_t; + +// ref_viewpass_t->flags +#define RF_DRAW_WORLD (1<<0) // pass should draw the world (otherwise it's player menu model) +#define RF_DRAW_CUBEMAP (1<<1) // special 6x pass to render cubemap\skybox sides +#define RF_DRAW_OVERVIEW (1<<2) // overview mode is active +#define RF_ONLY_CLIENTDRAW (1<<3) // nothing is drawn by the engine except clientDraw functions + +// intermediate struct for viewpass (or just a single frame) +typedef struct ref_viewpass_s +{ + int viewport[4]; // size of new viewport + vec3_t vieworigin; // view origin + vec3_t viewangles; // view angles + int viewentity; // entitynum (P2: Savior uses this) + float fov_x, fov_y; // vertical & horizontal FOV + int flags; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_viewpass_t; + +#endif//REF_PARAMS_H \ No newline at end of file diff --git a/common/render_api.h b/common/render_api.h new file mode 100644 index 00000000..59f5e6f5 --- /dev/null +++ b/common/render_api.h @@ -0,0 +1,266 @@ +/* +render_api.h - Xash3D extension for client interface +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef RENDER_API_H +#define RENDER_API_H + +#include "lightstyle.h" +#include "dlight.h" + +#define CL_RENDER_INTERFACE_VERSION 37 // Xash3D 1.0 +#define MAX_STUDIO_DECALS 4096 // + unused space of BSP decals + +// render info parms +#define PARM_TEX_WIDTH 1 // all parms with prefix 'TEX_' receive arg as texnum +#define PARM_TEX_HEIGHT 2 // otherwise it's not used +#define PARM_TEX_SRC_WIDTH 3 +#define PARM_TEX_SRC_HEIGHT 4 +#define PARM_TEX_SKYBOX 5 // second arg as skybox ordering num +#define PARM_TEX_SKYTEXNUM 6 // skytexturenum for quake sky +#define PARM_TEX_LIGHTMAP 7 // second arg as number 0 - 128 +#define PARM_TEX_TARGET 8 +#define PARM_TEX_TEXNUM 9 +#define PARM_TEX_FLAGS 10 +#define PARM_TEX_DEPTH 11 // 3D texture depth or 2D array num layers +//reserved +#define PARM_TEX_GLFORMAT 13 // get a texture GL-format +#define PARM_TEX_ENCODE 14 // custom encoding for DXT image +#define PARM_TEX_MIPCOUNT 15 // count of mipmaps (0 - autogenerated, 1 - disabled of mipmapping) +#define PARM_BSP2_SUPPORTED 16 // tell custom renderer what engine is support BSP2 in this build +#define PARM_SKY_SPHERE 17 // sky is quake sphere ? +//reserved +#define PARM_MAP_HAS_DELUXE 19 // map has deluxedata +#define PARM_MAX_ENTITIES 20 +#define PARM_WIDESCREEN 21 +#define PARM_FULLSCREEN 22 +#define PARM_SCREEN_WIDTH 23 +#define PARM_SCREEN_HEIGHT 24 +#define PARM_CLIENT_INGAME 25 +#define PARM_FEATURES 26 // same as movevars->features +#define PARM_ACTIVE_TMU 27 // for debug +#define PARM_LIGHTSTYLEVALUE 28 // second arg is stylenum +#define PARM_MAX_IMAGE_UNITS 29 +#define PARM_CLIENT_ACTIVE 30 +#define PARM_REBUILD_GAMMA 31 // if true lightmaps rebuilding for gamma change +#define PARM_DEDICATED_SERVER 32 +#define PARM_SURF_SAMPLESIZE 33 // lightmap resolution per face (second arg interpret as facenumber) +#define PARM_GL_CONTEXT_TYPE 34 // opengl or opengles +#define PARM_GLES_WRAPPER 35 // +#define PARM_STENCIL_ACTIVE 36 +#define PARM_WATER_ALPHA 37 + +// skybox ordering +enum +{ + SKYBOX_RIGHT = 0, + SKYBOX_BACK, + SKYBOX_LEFT, + SKYBOX_FORWARD, + SKYBOX_UP, + SKYBOX_DOWN, +}; + +typedef enum +{ + TF_NEAREST = (1<<0), // disable texfilter + TF_KEEP_SOURCE = (1<<1), // some images keep source + TF_NOFLIP_TGA = (1<<2), // Steam background completely ignore tga attribute 0x20 + TF_EXPAND_SOURCE = (1<<3), // Don't keep source as 8-bit expand to RGBA + TF_TEXTURE_2D_ARRAY = (1<<4), // this is 2D texture array (multi-layers) + TF_TEXTURE_RECTANGLE= (1<<5), // this is GL_TEXTURE_RECTANGLE + TF_CUBEMAP = (1<<6), // it's cubemap texture + TF_DEPTHMAP = (1<<7), // custom texture filter used + TF_QUAKEPAL = (1<<8), // image has an quake1 palette + TF_LUMINANCE = (1<<9), // force image to grayscale + TF_SKYSIDE = (1<<10), // this is a part of skybox + TF_CLAMP = (1<<11), // clamp texcoords to [0..1] range + TF_NOMIPMAP = (1<<12), // don't build mips for this image + TF_HAS_LUMA = (1<<13), // sets by GL_UploadTexture + TF_MAKELUMA = (1<<14), // create luma from quake texture (only q1 textures contain luma-pixels) + TF_NORMALMAP = (1<<15), // is a normalmap + TF_HAS_ALPHA = (1<<16), // image has alpha (used only for GL_CreateTexture) + TF_FORCE_COLOR = (1<<17), // force upload monochrome textures as RGB (detail textures) + TF_TEXTURE_1D = (1<<18), // this is GL_TEXTURE_1D + TF_BORDER = (1<<19), // zero clamp for projected textures + TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D + TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page +// reserved +// reserved +// reserved + TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage + TF_ARB_FLOAT = (1<<26), // float textures + TF_NOCOMPARE = (1<<27), // disable comparing for depth textures + TF_ARB_16BIT = (1<<28), // keep image as 16-bit (not 24) +} texFlags_t; + +typedef enum +{ + CONTEXT_TYPE_GL = 0, + CONTEXT_TYPE_GLES_1_X, + CONTEXT_TYPE_GLES_2_X +} gl_context_type_t; + +typedef enum +{ + GLES_WRAPPER_NONE = 0, // native GLES + GLES_WRAPPER_NANOGL, // used on GLES platforms +} gles_wrapper_t; + +// 30 bytes here +typedef struct modelstate_s +{ + short sequence; + short frame; // 10 bits multiple by 4, should be enough + byte blending[2]; + byte controller[4]; + byte poseparam[16]; + byte body; + byte skin; + short scale; // model scale (multiplied by 16) +} modelstate_t; + +typedef struct decallist_s +{ + vec3_t position; + char name[64]; + short entityIndex; + byte depth; + byte flags; + float scale; + + // this is the surface plane that we hit so that + // we can move certain decals across + // transitions if they hit similar geometry + vec3_t impactPlaneNormal; + + modelstate_t studio_state; // studio decals only +} decallist_t; + +typedef struct render_api_s +{ + // Get renderer info (doesn't changes engine state at all) + int (*RenderGetParm)( int parm, int arg ); // generic + void (*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale ); + void (*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha ); + lightstyle_t* (*GetLightStyle)( int number ); + dlight_t* (*GetDynamicLight)( int number ); + dlight_t* (*GetEntityLight)( int number ); + byte (*LightToTexGamma)( byte color ); // software gamma support + float (*GetFrameTime)( void ); + + // Set renderer info (tell engine about changes) + void (*R_SetCurrentEntity)( struct cl_entity_s *ent ); // tell engine about both currententity and currentmodel + void (*R_SetCurrentModel)( struct model_s *mod ); // change currentmodel but leave currententity unchanged + int (*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); + void (*R_StoreEfrags)( struct efrag_s **ppefrag, int framecount );// store efrags for static entities + + // Texture tools + int (*GL_FindTexture)( const char *name ); + const char* (*GL_TextureName)( unsigned int texnum ); + const byte* (*GL_TextureData)( unsigned int texnum ); // may be NULL + int (*GL_LoadTexture)( const char *name, const byte *buf, size_t size, int flags ); + int (*GL_CreateTexture)( const char *name, int width, int height, const void *buffer, int flags ); + int (*GL_LoadTextureArray)( const char **names, int flags ); + int (*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, int flags ); + void (*GL_FreeTexture)( unsigned int texnum ); + + // Decals manipulating (draw & remove) + void (*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa ); + float *(*R_DecalSetupVerts)( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount ); + void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) + + // AVIkit support + void *(*AVI_LoadVideo)( const char *filename ); + int (*AVI_GetVideoInfo)( void *Avi, long *xres, long *yres, float *duration ); + long (*AVI_GetVideoFrameNumber)( void *Avi, float time ); + byte *(*AVI_GetVideoFrame)( void *Avi, long frame ); + void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); + void (*AVI_FreeVideo)( void *Avi ); + int (*AVI_IsActive)( void *Avi ); + void (*AVI_Reserved0)( void ); // for potential interface expansion without broken compatibility + void (*AVI_Reserved1)( void ); + void (*AVI_Reserved2)( void ); + + // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) + void (*GL_Bind)( int tmu, unsigned int texnum ); + void (*GL_SelectTexture)( int tmu ); + void (*GL_LoadTextureMatrix)( const float *glmatrix ); + void (*GL_TexMatrixIdentity)( void ); + void (*GL_CleanUpTextureUnits)( int last ); // pass 0 for clear all the texture units + void (*GL_TexGen)( unsigned int coord, unsigned int mode ); + void (*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture + void (*GL_TexCoordArrayMode)( unsigned int texmode ); + void* (*GL_GetProcAddress)( const char *name ); + void (*GL_Reserved0)( void ); // for potential interface expansion without broken compatibility + void (*GL_Reserved1)( void ); + void (*GL_Reserved2)( void ); + + // Misc renderer functions + void (*GL_DrawParticles)( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime ); + void (*EnvShot)( const float *vieworg, const char *name, qboolean skyshot, int shotsize ); // store skybox into gfx\env folder + int (*SPR_LoadExt)( const char *szPicName, unsigned int texFlags ); // extended version of SPR_Load + colorVec (*LightVec)( const float *start, const float *end, float *lightspot ); + struct mstudiotex_s *( *StudioGetTexture )( struct cl_entity_s *e ); + const struct ref_overview_s *( *GetOverviewParms )( void ); + const char *( *GetFileByIndex )( int fileindex ); + void (*R_Reserved1)( void ); // for potential interface expansion without broken compatibility + void (*R_Reserved2)( void ); + + // static allocations + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // engine utils (not related with render API but placed here) + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); + unsigned long (*pfnFileBufferCRC32)( const void *buffer, const int length ); + int (*COM_CompareFileTime)( const char *filename1, const char *filename2, int *iCompare ); + void (*Host_Error)( const char *error, ... ); // cause Host Error + void* ( *pfnGetModel )( int modelindex ); + float (*pfnTime)( void ); // Sys_DoubleTime + void (*Cvar_Set)( char *name, char *value ); + void (*S_FadeMusicVolume)( float fadePercent ); // fade background track (0-100 percents) + void (*SetRandomSeed)( long lSeed ); // set custom seed for RANDOM_FLOAT\RANDOM_LONG for predictable random + // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 37 +} render_api_t; + +// render callbacks +typedef struct render_interface_s +{ + int version; + // passed through R_RenderFrame (0 - use engine renderer, 1 - use custom client renderer) + int (*GL_RenderFrame)( const struct ref_viewpass_s *rvp ); + // build all the lightmaps on new level or when gamma is changed + void (*GL_BuildLightmaps)( void ); + // setup map bounds for ortho-projection when we in dev_overview mode + void (*GL_OrthoBounds)( const float *mins, const float *maxs ); + // prepare studio decals for save + int (*R_CreateStudioDecalList)( decallist_t *pList, int count ); + // clear decals by engine request (e.g. for demo recording or vid_restart) + void (*R_ClearStudioDecals)( void ); + // grab r_speeds message + qboolean (*R_SpeedsMessage)( char *out, size_t size ); + // alloc or destroy model custom data + void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer ); + // alloc or destroy entity custom data + void (*R_ProcessEntData)( qboolean allocate ); + // get visdata for current frame from custom renderer + byte* (*Mod_GetCurrentVis)( void ); + // tell the renderer what new map is started + void (*R_NewMap)( void ); + // clear the render entities before each frame + void (*R_ClearScene)( void ); +} render_interface_t; + +#endif//RENDER_API_H \ No newline at end of file diff --git a/common/screenfade.h b/common/screenfade.h new file mode 100644 index 00000000..730f729e --- /dev/null +++ b/common/screenfade.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef SCREENFADE_H +#define SCREENFADE_H + +typedef struct screenfade_s +{ + float fadeSpeed; // How fast to fade (tics / second) (+ fade in, - fade out) + float fadeEnd; // When the fading hits maximum + float fadeTotalEnd; // Total End Time of the fade (used for FFADE_OUT) + float fadeReset; // When to reset to not fading (for fadeout and hold) + byte fader, fadeg, fadeb, fadealpha; // Fade color + int fadeFlags; // Fading flags +} screenfade_t; + +#endif//SCREENFADE_H \ No newline at end of file diff --git a/common/studio_event.h b/common/studio_event.h new file mode 100644 index 00000000..29ea1f90 --- /dev/null +++ b/common/studio_event.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef STUDIO_EVENT_H +#define STUDIO_EVENT_H + +typedef struct mstudioevent_s +{ + int frame; + int event; + int type; + char options[64]; +} mstudioevent_t; + +#endif//STUDIO_EVENT_H \ No newline at end of file diff --git a/common/triangleapi.h b/common/triangleapi.h new file mode 100644 index 00000000..f83c7ff0 --- /dev/null +++ b/common/triangleapi.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef TRIANGLEAPI_H +#define TRIANGLEAPI_H + +typedef enum +{ + TRI_FRONT = 0, + TRI_NONE = 1, +} TRICULLSTYLE; + +#define TRI_API_VERSION 1 + +#define TRI_TRIANGLES 0 +#define TRI_TRIANGLE_FAN 1 +#define TRI_QUADS 2 +#define TRI_POLYGON 3 +#define TRI_LINES 4 +#define TRI_TRIANGLE_STRIP 5 +#define TRI_QUAD_STRIP 6 +#define TRI_POINTS 7 // Xash3D added + +typedef struct triangleapi_s +{ + int version; + + void (*RenderMode)( int mode ); + void (*Begin)( int primitiveCode ); + void (*End)( void ); + + void (*Color4f)( float r, float g, float b, float a ); + void (*Color4ub)( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); + void (*TexCoord2f)( float u, float v ); + void (*Vertex3fv)( float *worldPnt ); + void (*Vertex3f)( float x, float y, float z ); + void (*Brightness)( float brightness ); + void (*CullFace)( TRICULLSTYLE style ); + int (*SpriteTexture)( struct model_s *pSpriteModel, int frame ); + int (*WorldToScreen)( float *world, float *screen ); // Returns 1 if it's z clipped + void (*Fog)( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b. + void (*ScreenToWorld)( float *screen, float *world ); + void (*GetMatrix)( const int pname, float *matrix ); + int (*BoxInPVS)( float *mins, float *maxs ); + void (*LightAtPoint)( float *pos, float *value ); + void (*Color4fRendermode)( float r, float g, float b, float a, int rendermode ); + void (*FogParams)( float flDensity, int iFogSkybox ); +} triangleapi_t; + +#endif//TRIANGLEAPI_H \ No newline at end of file diff --git a/common/usercmd.h b/common/usercmd.h new file mode 100644 index 00000000..96e3c91b --- /dev/null +++ b/common/usercmd.h @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef USERCMD_H +#define USERCMD_H + +typedef struct usercmd_s +{ + short lerp_msec; // Interpolation time on client + byte msec; // Duration in ms of command + vec3_t viewangles; // Command view angles + + // intended velocities + float forwardmove; // Forward velocity + float sidemove; // Sideways velocity + float upmove; // Upward velocity + byte lightlevel; // Light level at spot where we are standing. + unsigned short buttons; // Attack and move buttons + byte impulse; // Impulse command issued + byte weaponselect; // Current weapon id + + // Experimental player impact stuff. + int impact_index; + vec3_t impact_position; +} usercmd_t; + +#endif//USERCMD_H \ No newline at end of file diff --git a/common/wadfile.h b/common/wadfile.h new file mode 100644 index 00000000..b96b3658 --- /dev/null +++ b/common/wadfile.h @@ -0,0 +1,97 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef WADFILE_H +#define WADFILE_H + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYP_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDWAD2HEADER (('2'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD2" quake wads +#define IDWAD3HEADER (('3'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD3" half-life wads + +// dlumpinfo_t->attribs +#define ATTR_NONE 0 // allow to read-write +#define ATTR_READONLY BIT( 0 ) // don't overwrite this lump in anyway +#define ATTR_COMPRESSED BIT( 1 ) // not used for now, just reserved +#define ATTR_HIDDEN BIT( 2 ) // not used for now, just reserved +#define ATTR_SYSTEM BIT( 3 ) // not used for now, just reserved + +// dlumpinfo_t->type +#define TYP_ANY -1 // any type can be accepted +#define TYP_NONE 0 // unknown lump type +#define TYP_LABEL 1 // legacy from Doom1. Empty lump - label (like P_START, P_END etc) +#define TYP_PALETTE 64 // quake or half-life palette (768 bytes) +#define TYP_DDSTEX 65 // contain DDS texture +#define TYP_GFXPIC 66 // menu or hud image (not contain mip-levels) +#define TYP_MIPTEX 67 // quake1 and half-life in-game textures with four miplevels +#define TYP_SCRIPT 68 // contain script files +#define TYP_COLORMAP2 69 // old stuff. build palette from LBM file (not used) +#define TYP_QFONT 70 // half-life font (qfont_t) + +// dlumpinfo_t->img_type +#define IMG_DIFFUSE 0 // same as default pad1 always equal 0 +#define IMG_ALPHAMASK 1 // alpha-channel that stored separate as luminance texture +#define IMG_NORMALMAP 2 // indexed normalmap +#define IMG_GLOSSMAP 3 // luminance or color specularity map +#define IMG_GLOSSPOWER 4 // gloss power map (each value is a specular pow) +#define IMG_HEIGHTMAP 5 // heightmap (for parallax occlusion mapping or source of normalmap) +#define IMG_LUMA 6 // luma or glow texture with self-illuminated parts +#define IMG_DECAL_ALPHA 7 // it's a decal texture (last color in palette is base color, and other colors his graduations) +#define IMG_DECAL_COLOR 8 // decal without alpha-channel uses base, like 127 127 127 as transparent color + +/* +======================================================================== + +.LMP image format (Half-Life gfx.wad lumps) + +======================================================================== +*/ +typedef struct lmp_s +{ + unsigned int width; + unsigned int height; +} lmp_t; + +/* +======================================================================== + +.MIP image format (half-Life textures) + +======================================================================== +*/ +typedef struct mip_s +{ + char name[16]; + unsigned int width; + unsigned int height; + unsigned int offsets[4]; // four mip maps stored +} mip_t; + +#endif//WADFILE_H \ No newline at end of file diff --git a/common/weaponinfo.h b/common/weaponinfo.h new file mode 100644 index 00000000..ae78c645 --- /dev/null +++ b/common/weaponinfo.h @@ -0,0 +1,50 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef WEAPONINFO_H +#define WEAPONINFO_H + +// Info about weapons player might have in his/her possession +typedef struct weapon_data_s +{ + int m_iId; + int m_iClip; + + float m_flNextPrimaryAttack; + float m_flNextSecondaryAttack; + float m_flTimeWeaponIdle; + + int m_fInReload; + int m_fInSpecialReload; + float m_flNextReload; + float m_flPumpTime; + float m_fReloadTime; + + float m_fAimedDamage; + float m_fNextAimBonus; + int m_fInZoom; + int m_iWeaponState; + + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; +} weapon_data_t; + +#endif//WEAPONINFO_H \ No newline at end of file diff --git a/common/wrect.h b/common/wrect.h new file mode 100644 index 00000000..8dd2ba2f --- /dev/null +++ b/common/wrect.h @@ -0,0 +1,24 @@ +/* +wrect.h - rectangle definition +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef WRECT_H +#define WRECT_H + +typedef struct wrect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif//WRECT_H \ No newline at end of file diff --git a/engine/alias.h b/engine/alias.h new file mode 100644 index 00000000..7c6910ac --- /dev/null +++ b/engine/alias.h @@ -0,0 +1,138 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ALIAS_H +#define ALIAS_H + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +#define IDALIASHEADER (('O'<<24)+('P'<<16)+('D'<<8)+'I') // little-endian "IDPO" + +#define ALIAS_VERSION 6 + +// client-side model flags +#define ALIAS_ROCKET 0x0001 // leave a trail +#define ALIAS_GRENADE 0x0002 // leave a trail +#define ALIAS_GIB 0x0004 // leave a trail +#define ALIAS_ROTATE 0x0008 // rotate (bonus items) +#define ALIAS_TRACER 0x0010 // green split trail +#define ALIAS_ZOMGIB 0x0020 // small blood trail +#define ALIAS_TRACER2 0x0040 // orange split trail + rotate +#define ALIAS_TRACER3 0x0080 // purple trail + +// must match definition in sprite.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum +{ + ST_SYNC = 0, + ST_RAND +} synctype_t; +#endif + +typedef enum +{ + ALIAS_SINGLE = 0, + ALIAS_GROUP +} aliasframetype_t; + +typedef enum +{ + ALIAS_SKIN_SINGLE = 0, + ALIAS_SKIN_GROUP +} aliasskintype_t; + +typedef struct +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} daliashdr_t; + +typedef struct +{ + int onseam; + int s; + int t; +} stvert_t; + +typedef struct dtriangle_s +{ + int facesfront; + int vertindex[3]; +} dtriangle_t; + +#define DT_FACES_FRONT 0x0010 +#define ALIAS_ONSEAM 0x0020 + +typedef struct +{ + trivertex_t bboxmin; // lightnormal isn't used + trivertex_t bboxmax; // lightnormal isn't used + char name[16]; // frame name from grabbing +} daliasframe_t; + +typedef struct +{ + int numframes; + trivertex_t bboxmin; // lightnormal isn't used + trivertex_t bboxmax; // lightnormal isn't used +} daliasgroup_t; + +typedef struct +{ + int numskins; +} daliasskingroup_t; + +typedef struct +{ + float interval; +} daliasinterval_t; + +typedef struct +{ + float interval; +} daliasskininterval_t; + +typedef struct +{ + aliasframetype_t type; +} daliasframetype_t; + +typedef struct +{ + aliasskintype_t type; +} daliasskintype_t; + +#endif//ALIAS_H \ No newline at end of file diff --git a/engine/anorms.h b/engine/anorms.h new file mode 100644 index 00000000..d147aea0 --- /dev/null +++ b/engine/anorms.h @@ -0,0 +1,177 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +{-0.525731, 0.000000, 0.850651}, +{-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, +{-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, +{0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, +{-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, +{0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, +{0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, +{0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, +{-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, +{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, +{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, +{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, +{-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, +{-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, +{-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, +{-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, +{-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, +{0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, +{0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, +{0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, +{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, +{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, +{0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, +{0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, +{0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, +{0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, +{0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, +{0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, +{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, +{0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, +{0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, +{0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, +{0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, +{0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, +{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, +{0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, +{0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, +{0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, +{0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, +{-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, +{0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, +{-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, +{-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, +{0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, +{-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, +{-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, +{-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, +{0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, +{0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, +{0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, +{0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, +{0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, +{0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, +{0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, +{0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, +{0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, +{0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, +{-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, +{-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, +{-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, +{-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, +{-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, +{-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, +{-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, +{-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, +{-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, +{-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, +{0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, +{0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, +{0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, +{0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, +{-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, +{-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, +{-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, +{-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, +{-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, +{-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, +{-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, +{-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, +{-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, +{-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, +{-0.688191, -0.587785, -0.425325}, diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h new file mode 100644 index 00000000..9ba867b0 --- /dev/null +++ b/engine/cdll_exp.h @@ -0,0 +1,67 @@ +/* +cdll_exp.h - exports for client +Copyright (C) 2013 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ +#ifndef CDLL_EXP_H +#define CDLL_EXP_H + +// NOTE: ordering is important! +typedef struct cldll_func_s +{ + int (*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion ); + void (*pfnInit)( void ); + int (*pfnVidInit)( void ); + int (*pfnRedraw)( float flTime, int intermission ); + int (*pfnUpdateClientData)( client_data_t *cdata, float flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s *ppmove, int server ); + void (*pfnPlayerMoveInit)( struct playermove_s *ppmove ); + char (*pfnPlayerMoveTexture)( char *name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active ); + int (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float *ofs ); // unused + void *(*KB_Find)( const char *name ); + void (*CAM_Think)( void ); // camera stuff + void (*pfnCalcRefdef)( ref_params_t *pparams ); + int (*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); + void (*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client ); + void (*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src ); + void (*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd ); + void (*pfnDemo_ReadBuffer)( int size, byte *buffer ); + int (*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size ); + int (*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs ); + void (*pfnFrame)( double time ); + int (*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding ); + void (*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + cl_entity_t *(*pfnGetUserEntity)( int index ); + void (*pfnVoiceStatus)( int entindex, qboolean bTalking ); + void (*pfnDirectorMessage)( int iSize, void *pbuf ); + int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); + void (*pfnChatInputPosition)( int *x, int *y ); + // Xash3D extension + int (*pfnGetRenderInterface)( int version, render_api_t *renderfuncs, render_interface_t *callback ); + void (*pfnClipMoveToEntity)( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); +} cldll_func_t; + +#endif//CDLL_EXP_H \ No newline at end of file diff --git a/engine/cdll_int.h b/engine/cdll_int.h new file mode 100644 index 00000000..6b70f717 --- /dev/null +++ b/engine/cdll_int.h @@ -0,0 +1,259 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.h +// +// 4-23-98 +// JOHN: client dll interface declarations +// + +#ifndef CDLL_INT_H +#define CDLL_INT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "const.h" + +// this file is included by both the engine and the client-dll, +// so make sure engine declarations aren't done twice + +typedef int HSPRITE; // handle to a graphic +typedef int (*pfnUserMsgHook)( const char *pszName, int iSize, void *pbuf ); + +#include "wrect.h" + +#define SCRINFO_SCREENFLASH 1 +#define SCRINFO_STRETCHED 2 + +typedef struct SCREENINFO_s +{ + int iSize; + int iWidth; + int iHeight; + int iFlags; + int iCharHeight; + short charWidths[256]; +} SCREENINFO; + +typedef struct client_data_s +{ + // fields that cannot be modified (ie. have no effect if changed) + vec3_t origin; + + // fields that can be changed by the cldll + vec3_t viewangles; + int iWeaponBits; + float fov; // field of view +} client_data_t; + +typedef struct client_sprite_s +{ + char szName[64]; + char szSprite[64]; + int hspr; + int iRes; + wrect_t rc; +} client_sprite_t; + +typedef struct client_textmessage_s +{ + int effect; + byte r1, g1, b1, a1; // 2 colors for effects + byte r2, g2, b2, a2; + float x; + float y; + float fadein; + float fadeout; + float holdtime; + float fxtime; + const char *pName; + const char *pMessage; +} client_textmessage_t; + +typedef struct hud_player_info_s +{ + char *name; + short ping; + byte thisplayer; // TRUE if this is the calling player + + // stuff that's unused at the moment, but should be done + byte spectator; + byte packetloss; + char *model; + short topcolor; + short bottomcolor; +} hud_player_info_t; + +typedef struct cl_enginefuncs_s +{ + // sprite handlers + HSPRITE (*pfnSPR_Load)( const char *szPicName ); + int (*pfnSPR_Frames)( HSPRITE hPic ); + int (*pfnSPR_Height)( HSPRITE hPic, int frame ); + int (*pfnSPR_Width)( HSPRITE hPic, int frame ); + void (*pfnSPR_Set)( HSPRITE hPic, int r, int g, int b ); + void (*pfnSPR_Draw)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_DrawHoles)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_DrawAdditive)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_EnableScissor)( int x, int y, int width, int height ); + void (*pfnSPR_DisableScissor)( void ); + client_sprite_t *(*pfnSPR_GetList)( char *psz, int *piCount ); + + // screen handlers + void (*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a ); + int (*pfnGetScreenInfo)( SCREENINFO *pscrinfo ); + void (*pfnSetCrosshair)( HSPRITE hspr, wrect_t rc, int r, int g, int b ); + + // cvar handlers + struct cvar_s *(*pfnRegisterVariable)( char *szName, char *szValue, int flags ); + float (*pfnGetCvarFloat)( char *szName ); + char* (*pfnGetCvarString)( char *szName ); + + // command handlers + int (*pfnAddCommand)( char *cmd_name, void (*function)(void) ); + int (*pfnHookUserMsg)( char *szMsgName, pfnUserMsgHook pfn ); + int (*pfnServerCmd)( char *szCmdString ); + int (*pfnClientCmd)( char *szCmdString ); + + void (*pfnGetPlayerInfo)( int ent_num, hud_player_info_t *pinfo ); + + // sound handlers + void (*pfnPlaySoundByName)( char *szSound, float volume ); + void (*pfnPlaySoundByIndex)( int iSound, float volume ); + + // vector helpers + void (*pfnAngleVectors)( const float *vecAngles, float *forward, float *right, float *up ); + + // text message system + client_textmessage_t *(*pfnTextMessageGet)( const char *pName ); + int (*pfnDrawCharacter)( int x, int y, int number, int r, int g, int b ); + int (*pfnDrawConsoleString)( int x, int y, char *string ); + void (*pfnDrawSetTextColor)( float r, float g, float b ); + void (*pfnDrawConsoleStringLen)( const char *string, int *length, int *height ); + + void (*pfnConsolePrint)( const char *string ); + void (*pfnCenterPrint)( const char *string ); + + // Added for user input processing + int (*GetWindowCenterX)( void ); + int (*GetWindowCenterY)( void ); + void (*GetViewAngles)( float * ); + void (*SetViewAngles)( float * ); + int (*GetMaxClients)( void ); + void (*Cvar_SetValue)( char *cvar, float value ); + + int (*Cmd_Argc)( void ); + char *(*Cmd_Argv)( int arg ); + void (*Con_Printf)( char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_NPrintf)( int pos, char *fmt, ... ); + void (*Con_NXPrintf)( struct con_nprint_s *info, char *fmt, ... ); + + const char* (*PhysInfo_ValueForKey)( const char *key ); + const char* (*ServerInfo_ValueForKey)( const char *key ); + float (*GetClientMaxspeed)( void ); + int (*CheckParm)( char *parm, char **ppnext ); + + void (*Key_Event)( int key, int down ); + void (*GetMousePosition)( int *mx, int *my ); + int (*IsNoClipping)( void ); + + struct cl_entity_s *(*GetLocalPlayer)( void ); + struct cl_entity_s *(*GetViewModel)( void ); + struct cl_entity_s *(*GetEntityByIndex)( int idx ); + + float (*GetClientTime)( void ); + void (*V_CalcShake)( void ); + void (*V_ApplyShake)( float *origin, float *angles, float factor ); + + int (*PM_PointContents)( float *point, int *truecontents ); + int (*PM_WaterEntity)( float *p ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe ); + + struct model_s *(*CL_LoadModel)( const char *modelname, int *index ); + int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); + + const struct model_s* (*GetSpritePointer)( HSPRITE hSprite ); + void (*pfnPlaySoundByNameAtLocation)( char *szSound, float volume, float *origin ); + + unsigned short (*pfnPrecacheEvent)( int type, const char* psz ); + void (*pfnPlaybackEvent)( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + void (*pfnWeaponAnim)( int iAnim, int body ); + float (*pfnRandomFloat)( float flLow, float flHigh ); + long (*pfnRandomLong)( long lLow, long lHigh ); + void (*pfnHookEvent)( char *name, void ( *pfnEvent )( struct event_args_s *args )); + int (*Con_IsVisible) (); + const char *(*pfnGetGameDirectory)( void ); + struct cvar_s *(*pfnGetCvarPointer)( const char *szName ); + const char *(*Key_LookupBinding)( const char *pBinding ); + const char *(*pfnGetLevelName)( void ); + void (*pfnGetScreenFade)( struct screenfade_s *fade ); + void (*pfnSetScreenFade)( struct screenfade_s *fade ); + void* (*VGui_GetPanel)( ); + void (*VGui_ViewportPaintBackground)( int extents[4] ); + + byte* (*COM_LoadFile)( char *path, int usehunk, int *pLength ); + char* (*COM_ParseFile)( char *data, char *token ); + void (*COM_FreeFile)( void *buffer ); + + struct triangleapi_s *pTriAPI; + struct efx_api_s *pEfxAPI; + struct event_api_s *pEventAPI; + struct demo_api_s *pDemoAPI; + struct net_api_s *pNetAPI; + struct IVoiceTweak_s *pVoiceTweak; + + // returns 1 if the client is a spectator only (connected to a proxy), 0 otherwise or 2 if in dev_overview mode + int (*IsSpectateOnly)( void ); + struct model_s *(*LoadMapSprite)( const char *filename ); + + // file search functions + void (*COM_AddAppDirectoryToSearchPath)( const char *pszBaseDir, const char *appName ); + int (*COM_ExpandFilename)( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); + + // User info + // playerNum is in the range (1, MaxClients) + // returns NULL if player doesn't exit + // returns "" if no value is set + const char *( *PlayerInfo_ValueForKey )( int playerNum, const char *key ); + void (*PlayerInfo_SetValueForKey )( const char *key, const char *value ); + + // Gets a unique ID for the specified player. This is the same even if you see the player on a different server. + // iPlayer is an entity index, so client 0 would use iPlayer=1. + // Returns false if there is no player on the server in the specified slot. + qboolean (*GetPlayerUniqueID)(int iPlayer, char playerID[16]); + + // TrackerID access + int (*GetTrackerIDForPlayer)(int playerSlot); + int (*GetPlayerForTrackerID)(int trackerID); + + // Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream + // (but it might not get there). + int ( *pfnServerCmdUnreliable )( char *szCmdString ); + + void (*pfnGetMousePos)( struct tagPOINT *ppt ); + void (*pfnSetMousePos)( int x, int y ); + void (*pfnSetMouseEnable)( qboolean fEnable ); +} cl_enginefunc_t; + +#define CLDLL_INTERFACE_VERSION 7 + +#ifdef __cplusplus +} +#endif + +#endif//CDLL_INT_H \ No newline at end of file diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c new file mode 100644 index 00000000..4e62420b --- /dev/null +++ b/engine/client/cl_cmds.c @@ -0,0 +1,507 @@ +/* +cl_cmds.c - client console commnds +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" + +/* +==================== +CL_PlayVideo_f + +movie +==================== +*/ +void CL_PlayVideo_f( void ) +{ + string path; + + if( Cmd_Argc() != 2 && Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "movie [full]\n" ); + return; + } + + if( cls.state == ca_active ) + { + Con_Printf( "Can't play movie while connected to a server.\nPlease disconnect first.\n" ); + return; + } + + switch( Cmd_Argc( )) + { + case 2: // simple user version + Q_snprintf( path, sizeof( path ), "media/%s.avi", Cmd_Argv( 1 )); + SCR_PlayCinematic( path ); + break; + case 3: // sequenced cinematics used this + SCR_PlayCinematic( Cmd_Argv( 1 )); + break; + } +} + +/* +=============== +CL_PlayCDTrack_f + +Emulate audio-cd system +=============== +*/ +void CL_PlayCDTrack_f( void ) +{ + const char *command; + const char *pszTrack; + static int track = 0; + static qboolean paused = false; + static qboolean looped = false; + static qboolean enabled = true; + + if( Cmd_Argc() < 2 ) return; + command = Cmd_Argv( 1 ); + pszTrack = Cmd_Argv( 2 ); + + if( !enabled && Q_stricmp( command, "on" )) + return; // CD-player is disabled + + if( !Q_stricmp( command, "play" )) + { + if( Q_isdigit( pszTrack )) + { + track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS ); + S_StartBackgroundTrack( clgame.cdtracks[track-1], NULL, 0, false ); + } + else S_StartBackgroundTrack( pszTrack, NULL, 0, true ); + paused = false; + looped = false; + } + else if( !Q_stricmp( command, "loop" )) + { + if( Q_isdigit( pszTrack )) + { + track = bound( 1, Q_atoi( Cmd_Argv( 2 )), MAX_CDTRACKS ); + S_StartBackgroundTrack( clgame.cdtracks[track-1], clgame.cdtracks[track-1], 0, false ); + } + else S_StartBackgroundTrack( pszTrack, pszTrack, 0, true ); + paused = false; + looped = true; + } + else if( !Q_stricmp( command, "pause" )) + { + S_StreamSetPause( true ); + paused = true; + } + else if( !Q_stricmp( command, "resume" )) + { + S_StreamSetPause( false ); + paused = false; + } + else if( !Q_stricmp( command, "stop" )) + { + S_StopBackgroundTrack(); + paused = false; + looped = false; + track = 0; + } + else if( !Q_stricmp( command, "on" )) + { + enabled = true; + } + else if( !Q_stricmp( command, "off" )) + { + enabled = false; + } + else if( !Q_stricmp( command, "info" )) + { + int i, maxTrack; + + for( maxTrack = i = 0; i < MAX_CDTRACKS; i++ ) + if( Q_strlen( clgame.cdtracks[i] )) maxTrack++; + + Con_Printf( "%u tracks\n", maxTrack ); + if( track ) + { + if( paused ) Con_Printf( "Paused %s track %u\n", looped ? "looping" : "playing", track ); + else Con_Printf( "Currently %s track %u\n", looped ? "looping" : "playing", track ); + } + Con_Printf( "Volume is %f\n", Cvar_VariableValue( "MP3Volume" )); + return; + } + else Con_Printf( "%s: unknown command %s\n", Cmd_Argv( 0 ), command ); +} + +/* +================== +CL_ScreenshotGetName +================== +*/ +qboolean CL_ScreenshotGetName( int lastnum, char *filename ) +{ + int a, b, c, d; + + if( lastnum < 0 || lastnum > 9999 ) + { + MsgDev( D_ERROR, "unable to write screenshot\n" ); + return false; + } + + a = lastnum / 1000; + lastnum -= a * 1000; + b = lastnum / 100; + lastnum -= b * 100; + c = lastnum / 10; + lastnum -= c * 10; + d = lastnum; + + Q_sprintf( filename, "scrshots/%s_shot%i%i%i%i.bmp", clgame.mapname, a, b, c, d ); + + return true; +} + +/* +================== +CL_SnapshotGetName +================== +*/ +qboolean CL_SnapshotGetName( int lastnum, char *filename ) +{ + int a, b, c, d; + + if( lastnum < 0 || lastnum > 9999 ) + { + MsgDev( D_ERROR, "unable to write snapshot\n" ); + FS_AllowDirectPaths( false ); + return false; + } + + a = lastnum / 1000; + lastnum -= a * 1000; + b = lastnum / 100; + lastnum -= b * 100; + c = lastnum / 10; + lastnum -= c * 10; + d = lastnum; + + Q_sprintf( filename, "../%s_%i%i%i%i.bmp", clgame.mapname, a, b, c, d ); + + return true; +} + +/* +============================================================================== + + SCREEN SHOTS + +============================================================================== +*/ +/* +================== +CL_ScreenShot_f + +normal screenshot +================== +*/ +void CL_ScreenShot_f( void ) +{ + int i; + string checkname; + + if( CL_IsDevOverviewMode() == 1 ) + { + // special case for write overview image and script file + Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); + cls.scrshot_action = scrshot_mapshot; // build new frame for mapshot + } + else + { + // scan for a free filename + for( i = 0; i < 9999; i++ ) + { + if( !CL_ScreenshotGetName( i, checkname )) + return; // no namespace + + if( !FS_FileExists( checkname, false )) + break; + } + + Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); + cls.scrshot_action = scrshot_normal; // build new frame for screenshot + } + + cls.envshot_vieworg = NULL; // no custom view + cls.envshot_viewsize = 0; +} + +/* +================== +CL_SnapShot_f + +save screenshots into root dir +================== +*/ +void CL_SnapShot_f( void ) +{ + int i; + string checkname; + + if( CL_IsDevOverviewMode() == 1 ) + { + // special case for write overview image and script file + Q_snprintf( cls.shotname, sizeof( cls.shotname ), "overviews/%s.bmp", clgame.mapname ); + cls.scrshot_action = scrshot_mapshot; // build new frame for mapshot + } + else + { + FS_AllowDirectPaths( true ); + + // scan for a free filename + for( i = 0; i < 9999; i++ ) + { + if( !CL_SnapshotGetName( i, checkname )) + return; // no namespace + + if( !FS_FileExists( checkname, false )) + break; + } + + FS_AllowDirectPaths( false ); + Q_strncpy( cls.shotname, checkname, sizeof( cls.shotname )); + cls.scrshot_action = scrshot_snapshot; // build new frame for screenshot + } + + cls.envshot_vieworg = NULL; // no custom view + cls.envshot_viewsize = 0; +} + +/* +================== +CL_EnvShot_f + +cubemap view +================== +*/ +void CL_EnvShot_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "envshot \n" ); + return; + } + + Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); + cls.scrshot_action = scrshot_envshot; // build new frame for envshot + cls.envshot_vieworg = NULL; // no custom view + cls.envshot_viewsize = 0; +} + +/* +================== +CL_SkyShot_f + +skybox view +================== +*/ +void CL_SkyShot_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "skyshot \n" ); + return; + } + + Q_sprintf( cls.shotname, "gfx/env/%s", Cmd_Argv( 1 )); + cls.scrshot_action = scrshot_skyshot; // build new frame for skyshot + cls.envshot_vieworg = NULL; // no custom view + cls.envshot_viewsize = 0; +} + +/* +================== +CL_LevelShot_f + +splash logo while map is loading +================== +*/ +void CL_LevelShot_f( void ) +{ + size_t ft1, ft2; + string filename; + + if( cls.scrshot_request != scrshot_plaque ) return; + cls.scrshot_request = scrshot_inactive; + + // check for exist + if( cls.demoplayback && ( cls.demonum != -1 )) + { + Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", cls.demoname, glState.wideScreen ? "16x9" : "4x3" ); + Q_snprintf( filename, sizeof( filename ), "demos/%s.dem", cls.demoname ); + + // make sure what levelshot is newer than demo + ft1 = FS_FileTime( filename, false ); + ft2 = FS_FileTime( cls.shotname, true ); + } + else + { + Q_sprintf( cls.shotname, "levelshots/%s_%s.bmp", clgame.mapname, glState.wideScreen ? "16x9" : "4x3" ); + + // make sure what levelshot is newer than bsp + ft1 = FS_FileTime( cl.worldmodel->name, false ); + ft2 = FS_FileTime( cls.shotname, true ); + } + + // missing levelshot or level never than levelshot + if( ft2 == -1 || ft1 > ft2 ) + cls.scrshot_action = scrshot_plaque; // build new frame for levelshot + else cls.scrshot_action = scrshot_inactive; // disable - not needs +} + +/* +================== +CL_SaveShot_f + +mini-pic in loadgame menu +================== +*/ +void CL_SaveShot_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "saveshot \n" ); + return; + } + + Q_sprintf( cls.shotname, "%s%s.bmp", DEFAULT_SAVE_DIRECTORY, Cmd_Argv( 1 )); + cls.scrshot_action = scrshot_savegame; // build new frame for saveshot +} + +/* +================== +CL_DemoShot_f + +mini-pic in playdemo menu +================== +*/ +void CL_DemoShot_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "demoshot \n" ); + return; + } + + Q_sprintf( cls.shotname, "demos/%s.bmp", Cmd_Argv( 1 )); + cls.scrshot_action = scrshot_demoshot; // build new frame for demoshot +} + +/* +============== +CL_DeleteDemo_f + +============== +*/ +void CL_DeleteDemo_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "killdemo \n" ); + return; + } + + if( cls.demorecording && !Q_stricmp( cls.demoname, Cmd_Argv( 1 ))) + { + Con_Printf( "Can't delete %s - recording\n", Cmd_Argv( 1 )); + return; + } + + // delete save and saveshot + FS_Delete( va( "demos/%s.dem", Cmd_Argv( 1 ))); + FS_Delete( va( "demos/%s.bmp", Cmd_Argv( 1 ))); +} + +/* +================= +CL_SetSky_f + +Set a specified skybox (only for local clients) +================= +*/ +void CL_SetSky_f( void ) +{ + if( Cmd_Argc() < 2 ) + { + Con_Printf( S_USAGE "skyname \n" ); + return; + } + + R_SetupSky( Cmd_Argv( 1 )); +} + +/* +================ +SCR_TimeRefresh_f + +timerefresh [noflip] +================ +*/ +void SCR_TimeRefresh_f( void ) +{ + int i; + double start, stop; + double time; + + if( cls.state != ca_active ) + return; + + start = Sys_DoubleTime(); + + // run without page flipping like GoldSrc + if( Cmd_Argc() == 1 ) + { + pglDrawBuffer( GL_FRONT ); + for( i = 0; i < 128; i++ ) + { + RI.viewangles[1] = i / 128.0 * 360.0f; + R_RenderScene(); + } + pglFinish(); + R_EndFrame(); + } + else + { + for( i = 0; i < 128; i++ ) + { + R_BeginFrame( true ); + RI.viewangles[1] = i / 128.0 * 360.0f; + R_RenderScene(); + R_EndFrame(); + } + } + + stop = Sys_DoubleTime (); + time = (stop - start); + Con_Printf( "%f seconds (%f fps)\n", time, 128 / time ); +} + +/* +============= +SCR_Viewpos_f + +viewpos (level-designer helper) +============= +*/ +void SCR_Viewpos_f( void ) +{ + Con_Printf( "org ( %g %g %g )\n", RI.vieworg[0], RI.vieworg[1], RI.vieworg[2] ); + Con_Printf( "ang ( %g %g %g )\n", RI.viewangles[0], RI.viewangles[1], RI.viewangles[2] ); +} \ No newline at end of file diff --git a/engine/client/cl_custom.c b/engine/client/cl_custom.c new file mode 100644 index 00000000..6ea718aa --- /dev/null +++ b/engine/client/cl_custom.c @@ -0,0 +1,143 @@ +/* +cl_custom.c - downloading custom resources +Copyright (C) 2018 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" + +qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ) +{ + char filepath[MAX_QPATH]; + + switch( pResource->type ) + { + case t_sound: + case t_model: + // built-in resources not needs to be downloaded + if( pResource->szFileName[0] == '*' ) + return true; + break; + } + + // resource was missed on server + if( pResource->nDownloadSize == -1 ) + { + ClearBits( pResource->ucFlags, RES_FATALIFMISSING ); + return true; + } + + if( pResource->type == t_sound ) + Q_strncpy( filepath, va( "%s%s", DEFAULT_SOUNDPATH, pResource->szFileName ), sizeof( filepath )); + else Q_strncpy( filepath, pResource->szFileName, sizeof( filepath )); + + if( !COM_IsSafeFileToDownload( filepath )) + { + MsgDev( D_REPORT, "refusing to download %s\n", filepath ); + return true; + } + + if( !cl_allow_download.value ) + { + MsgDev( D_REPORT, "Download refused, cl_allow_download is 0\n" ); + return true; + } + + if( cls.state == ca_active && !cl_download_ingame.value ) + { + MsgDev( D_REPORT, "In-game download refused...\n" ); + return true; + } + + // don't request downloads from local client it's silly + if( Host_IsLocalClient() || FS_FileExists( filepath, false )) + return true; + + if( cls.demoplayback ) + { + MsgDev( D_WARN, "file %s missing during demo playback.\n", filepath ); + return true; + } + + MSG_BeginClientCmd( msg, clc_stringcmd ); + MSG_WriteString( msg, va( "dlfile %s", filepath )); + host.downloadcount++; + + return false; +} + +void CL_AddToResourceList( resource_t *pResource, resource_t *pList ) +{ + if( pResource->pPrev != NULL || pResource->pNext != NULL ) + { + MsgDev( D_ERROR, "Resource already linked\n" ); + return; + } + + if( pList->pPrev == NULL || pList->pNext == NULL ) + Host_Error( "Resource list corrupted.\n" ); + + pResource->pPrev = pList->pPrev; + pResource->pNext = pList; + pList->pPrev->pNext = pResource; + pList->pPrev = pResource; +} + +void CL_RemoveFromResourceList( resource_t *pResource ) +{ + if( pResource->pPrev == NULL || pResource->pNext == NULL ) + Host_Error( "mislinked resource in CL_RemoveFromResourceList\n" ); + + if ( pResource->pNext == pResource || pResource->pPrev == pResource ) + Host_Error( "attempt to free last entry in list.\n" ); + + pResource->pPrev->pNext = pResource->pNext; + pResource->pNext->pPrev = pResource->pPrev; + pResource->pPrev = NULL; + pResource->pNext = NULL; +} + +void CL_MoveToOnHandList( resource_t *pResource ) +{ + if( !pResource ) + { + MsgDev( D_REPORT, "Null resource passed to CL_MoveToOnHandList\n" ); + return; + } + + CL_RemoveFromResourceList( pResource ); + CL_AddToResourceList( pResource, &cl.resourcesonhand ); +} + +void CL_ClearResourceList( resource_t *pList ) +{ + resource_t *p, *n; + + for( p = pList->pNext; p != pList && p; p = n ) + { + n = p->pNext; + + CL_RemoveFromResourceList( p ); + Mem_Free( p ); + } + + pList->pPrev = pList; + pList->pNext = pList; +} + +void CL_ClearResourceLists( void ) +{ + CL_ClearResourceList( &cl.resourcesneeded ); + CL_ClearResourceList( &cl.resourcesonhand ); +} \ No newline at end of file diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c new file mode 100644 index 00000000..04b7ec18 --- /dev/null +++ b/engine/client/cl_demo.c @@ -0,0 +1,1464 @@ +/* +cl_demo.c - demo record & playback +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "net_encode.h" + +#define dem_unknown 0 // unknown command +#define dem_norewind 1 // startup message +#define dem_read 2 // it's a normal network packet +#define dem_jumptime 3 // move the demostart time value forward by this amount +#define dem_userdata 4 // userdata from the client.dll +#define dem_usercmd 5 // read usercmd_t +#define dem_stop 6 // end of time +#define dem_lastcmd dem_stop + +#define DEMO_STARTUP 0 // this lump contains startup info needed to spawn into the server +#define DEMO_NORMAL 1 // this lump contains playback info of messages, etc., needed during playback. + +// Demo flags +#define FDEMO_TITLE 0x01 // Show title +#define FDEMO_PLAY 0x04 // Playing cd track +#define FDEMO_FADE_IN_SLOW 0x08 // Fade in (slow) +#define FDEMO_FADE_IN_FAST 0x10 // Fade in (fast) +#define FDEMO_FADE_OUT_SLOW 0x20 // Fade out (slow) +#define FDEMO_FADE_OUT_FAST 0x40 // Fade out (fast) + +#define IDEMOHEADER (('M'<<24)+('E'<<16)+('D'<<8)+'I') // little-endian "IDEM" +#define DEMO_PROTOCOL 3 + +const char *demo_cmd[dem_lastcmd+1] = +{ + "dem_unknown", + "dem_norewind", + "dem_read", + "dem_jumptime", + "dem_userdata", + "dem_usercmd", + "dem_stop", +}; + +typedef struct +{ + int id; // should be IDEM + int dem_protocol; // should be DEMO_PROTOCOL + int net_protocol; // should be PROTOCOL_VERSION + double host_fps; // fps for demo playing + char mapname[64]; // name of map + char comment[64]; // comment for demo + char gamedir[64]; // name of game directory (FS_Gamedir()) + int directory_offset; // offset of Entry Directory. +} demoheader_t; + +typedef struct +{ + int entrytype; // DEMO_STARTUP or DEMO_NORMAL + float playback_time; // time of track + int playback_frames; // # of frames in track + int offset; // file offset of track data + int length; // length of track + int flags; // FX-flags + char description[64]; // entry description +} demoentry_t; + +typedef struct +{ + demoentry_t *entries; // track entry info + int numentries; // number of tracks +} demodirectory_t; + +// add angles +typedef struct +{ + float starttime; + vec3_t viewangles; +} demoangle_t; + +// private demo states +struct +{ + demoheader_t header; + demoentry_t *entry; + demodirectory_t directory; + int framecount; + float starttime; + float realstarttime; + float timestamp; + float lasttime; + int entryIndex; + + // interpolation stuff + demoangle_t cmds[ANGLE_BACKUP]; + int angle_position; +} demo; + +/* +==================== +CL_StartupDemoHeader + +spooling demo header in case +we record a demo on this level +==================== +*/ +void CL_StartupDemoHeader( void ) +{ + if( cls.demoheader ) + { + FS_Close( cls.demoheader ); + } + + // Note: this is replacing tmpfile() + cls.demoheader = FS_Open( "demoheader.tmp", "w+b", true ); + + if( !cls.demoheader ) + { + MsgDev( D_ERROR, "couldn't open temporary header file.\n" ); + return; + } + + Con_Printf( "Spooling demo header.\n" ); +} + +/* +==================== +CL_CloseDemoHeader + +close demoheader file on engine shutdown +==================== +*/ +void CL_CloseDemoHeader( void ) +{ + if( !cls.demoheader ) + return; + + FS_Close( cls.demoheader ); +} + +/* +==================== +CL_GetDemoRecordClock + +write time while demo is recording +==================== +*/ +float CL_GetDemoRecordClock( void ) +{ + return cl.mtime[0]; +} + +/* +==================== +CL_GetDemoPlaybackClock + +overwrite host.realtime +==================== +*/ +float CL_GetDemoPlaybackClock( void ) +{ + return host.realtime + host.frametime; +} + +/* +==================== +CL_GetDemoFramerate + +overwrite host.frametime +==================== +*/ +double CL_GetDemoFramerate( void ) +{ + if( cls.timedemo ) + return 0.0; + return bound( MIN_FPS, demo.header.host_fps, MAX_FPS ); +} + +/* +==================== +CL_WriteDemoCmdHeader + +Writes the demo command header and time-delta +==================== +*/ +void CL_WriteDemoCmdHeader( byte cmd, file_t *file ) +{ + float dt; + + Assert( cmd >= 1 && cmd <= dem_lastcmd ); + if( !file ) return; + + // command + FS_Write( file, &cmd, sizeof( byte )); + + // time offset + dt = (float)(CL_GetDemoRecordClock() - demo.starttime); + FS_Write( file, &dt, sizeof( float )); +} + +/* +==================== +CL_WriteDemoJumpTime + +Update level time on a next level +==================== +*/ +void CL_WriteDemoJumpTime( void ) +{ + if( cls.demowaiting || !cls.demofile ) + return; + + demo.starttime = CL_GetDemoRecordClock(); // setup the demo starttime + + // demo playback should read this as an incoming message. + // write the client's realtime value out so we can synchronize the reads. + CL_WriteDemoCmdHeader( dem_jumptime, cls.demofile ); +} + +/* +==================== +CL_WriteDemoUserCmd + +Writes the current user cmd +==================== +*/ +void CL_WriteDemoUserCmd( int cmdnumber ) +{ + sizebuf_t buf; + word bytes; + byte data[1024]; + + if( !cls.demorecording || !cls.demofile ) + return; + + CL_WriteDemoCmdHeader( dem_usercmd, cls.demofile ); + + FS_Write( cls.demofile, &cls.netchan.outgoing_sequence, sizeof( int )); + FS_Write( cls.demofile, &cmdnumber, sizeof( int )); + + // write usercmd_t + MSG_Init( &buf, "UserCmd", data, sizeof( data )); + CL_WriteUsercmd( &buf, -1, cmdnumber ); // always no delta + + bytes = MSG_GetNumBytesWritten( &buf ); + + FS_Write( cls.demofile, &bytes, sizeof( word )); + FS_Write( cls.demofile, data, bytes ); +} + +/* +==================== +CL_WriteDemoSequence + +Save state of cls.netchan sequences +so that we can play the demo correctly. +==================== +*/ +void CL_WriteDemoSequence( file_t *file ) +{ + Assert( file != NULL ); + + FS_Write( file, &cls.netchan.incoming_sequence, sizeof( int )); + FS_Write( file, &cls.netchan.incoming_acknowledged, sizeof( int )); + FS_Write( file, &cls.netchan.incoming_reliable_acknowledged, sizeof( int )); + FS_Write( file, &cls.netchan.incoming_reliable_sequence, sizeof( int )); + FS_Write( file, &cls.netchan.outgoing_sequence, sizeof( int )); + FS_Write( file, &cls.netchan.reliable_sequence, sizeof( int )); + FS_Write( file, &cls.netchan.last_reliable_sequence, sizeof( int )); +} + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ +void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ) +{ + file_t *file = startup ? cls.demoheader : cls.demofile; + int swlen; + byte c; + + if( !file ) return; + + // past the start but not recording a demo. + if( !startup && !cls.demorecording ) + return; + + swlen = MSG_GetNumBytesWritten( msg ) - start; + if( swlen <= 0 ) return; + + if( !startup ) demo.framecount++; + + // demo playback should read this as an incoming message. + c = (cls.state != ca_active) ? dem_norewind : dem_read; + + CL_WriteDemoCmdHeader( c, file ); + CL_WriteDemoSequence( file ); + + // write the length out. + FS_Write( file, &swlen, sizeof( int )); + + // output the buffer. Skip the network packet stuff. + FS_Write( file, MSG_GetData( msg ) + start, swlen ); +} + +/* +==================== +CL_WriteDemoUserMessage + +Dumps the user message (demoaction) +==================== +*/ +void CL_WriteDemoUserMessage( const byte *buffer, size_t size ) +{ + if( !cls.demorecording || cls.demowaiting ) + return; + + if( !cls.demofile || !buffer || size <= 0 ) + return; + + CL_WriteDemoCmdHeader( dem_userdata, cls.demofile ); + + // write the length out. + FS_Write( cls.demofile, &size, sizeof( int )); + + // output the buffer. + FS_Write( cls.demofile, buffer, size ); +} + +/* +==================== +CL_WriteDemoHeader + +Write demo header +==================== +*/ +void CL_WriteDemoHeader( const char *name ) +{ + long copysize; + long savepos; + long curpos; + + Con_Printf( "recording to %s.\n", name ); + cls.demofile = FS_Open( name, "wb", false ); + cls.demotime = 0.0; + + if( !cls.demofile ) + { + MsgDev( D_ERROR, "couldn't open %s.\n", name ); + return; + } + + cls.demorecording = true; + cls.demowaiting = true; // don't start saving messages until a non-delta compressed message is received + + memset( &demo.header, 0, sizeof( demo.header )); + + demo.header.id = IDEMOHEADER; + demo.header.dem_protocol = DEMO_PROTOCOL; + demo.header.net_protocol = PROTOCOL_VERSION; + demo.header.host_fps = bound( MIN_FPS, host_maxfps->value, MAX_FPS ); + Q_strncpy( demo.header.mapname, clgame.mapname, sizeof( demo.header.mapname )); + Q_strncpy( demo.header.comment, clgame.maptitle, sizeof( demo.header.comment )); + Q_strncpy( demo.header.gamedir, FS_Gamedir(), sizeof( demo.header.gamedir )); + + // write header + FS_Write( cls.demofile, &demo.header, sizeof( demo.header )); + + demo.directory.numentries = 2; + demo.directory.entries = Mem_Alloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); + + // DIRECTORY ENTRY # 0 + demo.entry = &demo.directory.entries[0]; // only one here. + demo.entry->entrytype = DEMO_STARTUP; + demo.entry->playback_time = 0.0f; // startup takes 0 time. + demo.entry->offset = FS_Tell( cls.demofile ); // position for this chunk. + + // finish off the startup info. + CL_WriteDemoCmdHeader( dem_stop, cls.demoheader ); + + // now copy the stuff we cached from the server. + copysize = savepos = FS_Tell( cls.demoheader ); + + FS_Seek( cls.demoheader, 0, SEEK_SET ); + + FS_FileCopy( cls.demofile, cls.demoheader, copysize ); + + // jump back to end, in case we record another demo for this session. + FS_Seek( cls.demoheader, savepos, SEEK_SET ); + + demo.starttime = CL_GetDemoRecordClock(); // setup the demo starttime + demo.realstarttime = demo.starttime; + demo.framecount = 0; + cls.td_startframe = host.framecount; + cls.td_lastframe = -1; // get a new message this frame + + // now move on to entry # 1, the first data chunk. + curpos = FS_Tell( cls.demofile ); + demo.entry->length = curpos - demo.entry->offset; + + // now we are writing the first real lump. + demo.entry = &demo.directory.entries[1]; // first real data lump + demo.entry->entrytype = DEMO_NORMAL; + demo.entry->playback_time = 0.0f; // startup takes 0 time. + + demo.entry->offset = FS_Tell( cls.demofile ); + + // demo playback should read this as an incoming message. + // write the client's realtime value out so we can synchronize the reads. + CL_WriteDemoCmdHeader( dem_jumptime, cls.demofile ); + + if( clgame.hInstance ) clgame.dllFuncs.pfnReset(); + + Cbuf_InsertText( "fullupdate\n" ); + Cbuf_Execute(); +} + +/* +================= +CL_StopRecord + +finish recording demo +================= +*/ +void CL_StopRecord( void ) +{ + int i, curpos; + float stoptime; + int frames; + + if( !cls.demorecording ) return; + + // demo playback should read this as an incoming message. + CL_WriteDemoCmdHeader( dem_stop, cls.demofile ); + + stoptime = CL_GetDemoRecordClock(); + if( clgame.hInstance ) clgame.dllFuncs.pfnReset(); + + curpos = FS_Tell( cls.demofile ); + demo.entry->length = curpos - demo.entry->offset; + demo.entry->playback_time = stoptime - demo.realstarttime; + demo.entry->playback_frames = demo.framecount; + + // Now write out the directory and free it and touch up the demo header. + FS_Write( cls.demofile, &demo.directory.numentries, sizeof( int )); + + for( i = 0; i < demo.directory.numentries; i++ ) + FS_Write( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); + + Mem_Free( demo.directory.entries ); + demo.directory.numentries = 0; + + demo.header.directory_offset = curpos; + FS_Seek( cls.demofile, 0, SEEK_SET ); + FS_Write( cls.demofile, &demo.header, sizeof( demo.header )); + + FS_Close( cls.demofile ); + cls.demofile = NULL; + cls.demorecording = false; + cls.demoname[0] = '\0'; + cls.td_lastframe = host.framecount; + gameui.globals->demoname[0] = '\0'; + demo.header.host_fps = 0.0; + + frames = cls.td_lastframe - cls.td_startframe; + Con_Printf( "Completed demo\nRecording time: %02d:%02d, frames %i\n", (int)(cls.demotime / 60.0f), (int)fmod(cls.demotime, 60.0f), frames ); + cls.demotime = 0.0; +} + +/* +================= +CL_DrawDemoRecording +================= +*/ +void CL_DrawDemoRecording( void ) +{ + char string[64]; + rgba_t color = { 255, 255, 255, 255 }; + long pos; + int len; + + if(!( host_developer.value && cls.demorecording )) + return; + + pos = FS_Tell( cls.demofile ); + Q_snprintf( string, sizeof( string ), "^1RECORDING:^7 %s: %s time: %02d:%02d", cls.demoname, + Q_memprint( pos ), (int)(cls.demotime / 60.0f ), (int)fmod( cls.demotime, 60.0f )); + + Con_DrawStringLen( string, &len, NULL ); + Con_DrawString(( glState.width - len ) >> 1, glState.height >> 2, string, color ); +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ +/* +================= +CL_ReadDemoCmdHeader + +read the demo command +================= +*/ +void CL_ReadDemoCmdHeader( byte *cmd, float *dt ) +{ + // read the command + FS_Read( cls.demofile, cmd, sizeof( byte )); + Assert( *cmd >= 1 && *cmd <= dem_lastcmd ); + + // read the timestamp + FS_Read( cls.demofile, dt, sizeof( float )); +} + +/* +================= +CL_ReadDemoUserCmd + +read the demo usercmd for predicting +and smooth movement during playback the demo +================= +*/ +void CL_ReadDemoUserCmd( qboolean discard ) +{ + byte data[1024]; + int cmdnumber; + int outgoing_sequence; + runcmd_t *pcmd; + word bytes; + + FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); + FS_Read( cls.demofile, &cmdnumber, sizeof( int )); + FS_Read( cls.demofile, &bytes, sizeof( short )); + FS_Read( cls.demofile, data, bytes ); + + if( !discard ) + { + usercmd_t nullcmd; + sizebuf_t buf; + demoangle_t *a; + + memset( &nullcmd, 0, sizeof( nullcmd )); + MSG_Init( &buf, "UserCmd", data, sizeof( data )); + + pcmd = &cl.commands[cmdnumber & CL_UPDATE_MASK]; + pcmd->processedfuncs = false; + pcmd->senttime = 0.0f; + pcmd->receivedtime = 0.1f; + pcmd->frame_lerp = 0.1f; + pcmd->heldback = false; + pcmd->sendsize = 1; + + // always delta'ing from null + cl.cmd = &pcmd->cmd; + + MSG_ReadDeltaUsercmd( &buf, &nullcmd, cl.cmd ); + + // make sure what interp info contain angles from different frames + // or lerping will stop working + if( demo.lasttime != demo.timestamp ) + { + // select entry into circular buffer + demo.angle_position = (demo.angle_position + 1) & ANGLE_MASK; + a = &demo.cmds[demo.angle_position]; + + // record update + a->starttime = demo.timestamp; + VectorCopy( cl.cmd->viewangles, a->viewangles ); + demo.lasttime = demo.timestamp; + } + + // NOTE: we need to have the current outgoing sequence correct + // so we can do prediction correctly during playback + cls.netchan.outgoing_sequence = outgoing_sequence; + } +} + +/* +================= +CL_ReadDemoSequence + +read netchan sequences +================= +*/ +void CL_ReadDemoSequence( qboolean discard ) +{ + int incoming_sequence; + int incoming_acknowledged; + int incoming_reliable_acknowledged; + int incoming_reliable_sequence; + int outgoing_sequence; + int reliable_sequence; + int last_reliable_sequence; + + FS_Read( cls.demofile, &incoming_sequence, sizeof( int )); + FS_Read( cls.demofile, &incoming_acknowledged, sizeof( int )); + FS_Read( cls.demofile, &incoming_reliable_acknowledged, sizeof( int )); + FS_Read( cls.demofile, &incoming_reliable_sequence, sizeof( int )); + FS_Read( cls.demofile, &outgoing_sequence, sizeof( int )); + FS_Read( cls.demofile, &reliable_sequence, sizeof( int )); + FS_Read( cls.demofile, &last_reliable_sequence, sizeof( int )); + + if( discard ) return; + + cls.netchan.incoming_sequence = incoming_sequence; + cls.netchan.incoming_acknowledged = incoming_acknowledged; + cls.netchan.incoming_reliable_acknowledged = incoming_reliable_acknowledged; + cls.netchan.incoming_reliable_sequence = incoming_reliable_sequence; + cls.netchan.outgoing_sequence = outgoing_sequence; + cls.netchan.reliable_sequence = reliable_sequence; + cls.netchan.last_reliable_sequence = last_reliable_sequence; +} + +/* +================= +CL_DemoAborted +================= +*/ +void CL_DemoAborted( void ) +{ + if( cls.demofile ) + FS_Close( cls.demofile ); + cls.demoplayback = false; + cls.changedemo = false; + cls.timedemo = false; + demo.framecount = 0; + cls.demofile = NULL; + cls.demonum = -1; + + Cvar_SetValue( "v_dark", 0.0f ); +} + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) +{ + if( cls.demonum != -1 ) + cls.changedemo = true; + + CL_StopPlayback(); + + if( !CL_NextDemo() && host_developer.value <= DEV_NONE ) + UI_SetActiveMenu( true ); + + Cvar_SetValue( "v_dark", 0.0f ); +} + +/* +================= +CL_DemoMoveToNextSection + +returns true on success, false on failure +g-cont. probably captain obvious mode is ON +================= +*/ +qboolean CL_DemoMoveToNextSection( void ) +{ + if( ++demo.entryIndex >= demo.directory.numentries ) + { + // done + CL_DemoCompleted(); + return false; + } + + // switch to next section, we got a dem_stop + demo.entry = &demo.directory.entries[demo.entryIndex]; + + // ready to continue reading, reset clock. + FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); + + // time is now relative to this chunk's clock. + demo.starttime = CL_GetDemoPlaybackClock(); + demo.framecount = 0; + + return true; +} + +qboolean CL_ReadRawNetworkData( byte *buffer, size_t *length ) +{ + int msglen = 0; + + Assert( buffer != NULL ); + Assert( length != NULL ); + + *length = 0; // assume we fail + FS_Read( cls.demofile, &msglen, sizeof( int )); + + if( msglen < 0 ) + { + MsgDev( D_ERROR, "Demo message length < 0\n" ); + CL_DemoCompleted(); + return false; + } + + if( msglen > MAX_INIT_MSG ) + { + MsgDev( D_ERROR, "Demo message %i > %i\n", msglen, MAX_INIT_MSG ); + CL_DemoCompleted(); + return false; + } + + if( msglen > 0 ) + { + if( FS_Read( cls.demofile, buffer, msglen ) != msglen ) + { + MsgDev( D_ERROR, "Error reading demo message data\n" ); + CL_DemoCompleted(); + return false; + } + } + + *length = msglen; + + if( cls.state != ca_active ) + Cbuf_Execute(); + + return true; +} + +/* +================= +CL_DemoReadMessage + +reads demo data and write it to client +================= +*/ +qboolean CL_DemoReadMessage( byte *buffer, size_t *length ) +{ + size_t curpos = 0, lastpos = 0; + float fElapsedTime = 0.0f; + qboolean swallowmessages = true; + static int tdlastdemoframe = 0; + byte *userbuf = NULL; + size_t size; + byte cmd; + + if( !cls.demofile ) + { + MsgDev( D_ERROR, "tried to read a demo message with no demo file\n" ); + CL_DemoCompleted(); + return false; + } + + if(( !cl.background && ( cl.paused || cls.key_dest != key_game )) || cls.key_dest == key_console ) + { + demo.starttime += host.frametime; + return false; // paused + } + + do + { + qboolean bSkipMessage = false; + + if( !cls.demofile ) break; + curpos = FS_Tell( cls.demofile ); + + CL_ReadDemoCmdHeader( &cmd, &demo.timestamp ); + + fElapsedTime = CL_GetDemoPlaybackClock() - demo.starttime; + if( !cls.timedemo ) bSkipMessage = ((demo.timestamp - cl_serverframetime()) >= fElapsedTime) ? true : false; + if( cls.changelevel ) demo.framecount = 1; + + // changelevel issues + if( demo.framecount <= 2 && ( fElapsedTime - demo.timestamp ) > host.frametime ) + demo.starttime = CL_GetDemoPlaybackClock(); + + // not ready for a message yet, put it back on the file. + if( cmd != dem_norewind && cmd != dem_stop && bSkipMessage ) + { + // never skip first message + if( demo.framecount != 0 ) + { + FS_Seek( cls.demofile, curpos, SEEK_SET ); + return false; // not time yet. + } + } + + // we already have the usercmd_t for this frame + // don't read next usercmd_t so predicting will work properly + if( cmd == dem_usercmd && lastpos != 0 && demo.framecount != 0 ) + { + FS_Seek( cls.demofile, lastpos, SEEK_SET ); + return false; // not time yet. + } + + // COMMAND HANDLERS + switch( cmd ) + { + case dem_jumptime: + demo.starttime = CL_GetDemoPlaybackClock(); + return false; // time is changed, skip frame + case dem_stop: + CL_DemoMoveToNextSection(); + return false; // header is ended, skip frame + case dem_userdata: + FS_Read( cls.demofile, &size, sizeof( int )); + userbuf = Mem_Alloc( cls.mempool, size ); + FS_Read( cls.demofile, userbuf, size ); + + if( clgame.hInstance ) + clgame.dllFuncs.pfnDemo_ReadBuffer( size, userbuf ); + Mem_Free( userbuf ); + userbuf = NULL; + break; + case dem_usercmd: + CL_ReadDemoUserCmd( false ); + lastpos = FS_Tell( cls.demofile ); + break; + default: + swallowmessages = false; + break; + } + } while( swallowmessages ); + + // If we are playing back a timedemo, and we've already passed on a + // frame update for this host_frame tag, then we'll just skip this message. + if( cls.timedemo && ( tdlastdemoframe == host.framecount )) + { + FS_Seek( cls.demofile, FS_Tell ( cls.demofile ) - 5, SEEK_SET ); + return false; + } + + tdlastdemoframe = host.framecount; + + if( !cls.demofile ) + return false; + + // if not on "LOADING" section, check a few things + if( demo.entryIndex ) + { + // We are now on the second frame of a new section, + // if so, reset start time (unless in a timedemo) + if( demo.framecount == 1 && !cls.timedemo ) + { + // cheat by moving the relative start time forward. + demo.starttime = CL_GetDemoPlaybackClock(); + } + } + + demo.framecount++; + CL_ReadDemoSequence( false ); + + return CL_ReadRawNetworkData( buffer, length ); +} + +void CL_DemoFindInterpolatedViewAngles( float t, float *frac, demoangle_t **prev, demoangle_t **next ) +{ + int i, i0, i1, imod; + float at; + + if( cls.timedemo ) return; + + imod = demo.angle_position - 1; + i0 = (imod + 1) & ANGLE_MASK; + i1 = (imod + 0) & ANGLE_MASK; + + if( demo.cmds[i0].starttime >= t ) + { + for( i = 0; i < ANGLE_BACKUP - 2; i++ ) + { + at = demo.cmds[imod & ANGLE_MASK].starttime; + if( at == 0.0f ) break; + + if( at < t ) + { + i0 = (imod + 1) & ANGLE_MASK; + i1 = (imod + 0) & ANGLE_MASK; + break; + } + imod--; + } + } + + *next = &demo.cmds[i0]; + *prev = &demo.cmds[i1]; + + // avoid division by zero (probably this should never happens) + if((*prev)->starttime == (*next)->starttime ) + { + *prev = *next; + *frac = 0.0f; + return; + } + + // time spans the two entries + *frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime ); + *frac = bound( 0.0f, *frac, 1.0f ); +} + +/* +============== +CL_DemoInterpolateAngles + +We can predict or inpolate player movement with standed client code +but viewangles interpolate here +============== +*/ +void CL_DemoInterpolateAngles( void ) +{ + float curtime = (CL_GetDemoPlaybackClock() - demo.starttime) - host.frametime; + demoangle_t *prev = NULL, *next = NULL; + float frac = 0.0f; + + if( curtime > demo.timestamp ) + curtime = demo.timestamp; // don't run too far + + CL_DemoFindInterpolatedViewAngles( curtime, &frac, &prev, &next ); + + if( prev && next ) + { + vec4_t q, q1, q2; + + AngleQuaternion( next->viewangles, q1, false ); + AngleQuaternion( prev->viewangles, q2, false ); + QuaternionSlerp( q2, q1, frac, q ); + QuaternionAngle( q, cl.viewangles ); + } + else VectorCopy( cl.cmd->viewangles, cl.viewangles ); +} + +/* +============== +CL_FinishTimeDemo + +show stats +============== +*/ +void CL_FinishTimeDemo( void ) +{ + int frames; + double time; + + cls.timedemo = false; + + // the first frame didn't count + frames = (host.framecount - cls.td_startframe) - 1; + time = host.realtime - cls.td_starttime; + if( !time ) time = 1.0; + + Con_Printf( "%i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); +} + +/* +============== +CL_StopPlayback + +Called when a demo file runs out, or the user starts a game +============== +*/ +void CL_StopPlayback( void ) +{ + if( !cls.demoplayback ) return; + + // release demofile + FS_Close( cls.demofile ); + cls.demoplayback = false; + demo.framecount = 0; + cls.demofile = NULL; + + cls.olddemonum = Q_max( -1, cls.demonum - 1 ); + Mem_Free( demo.directory.entries ); + cls.td_lastframe = host.framecount; + demo.directory.numentries = 0; + demo.directory.entries = NULL; + demo.header.host_fps = 0.0; + demo.entry = NULL; + + cls.demoname[0] = '\0'; // clear demoname too + gameui.globals->demoname[0] = '\0'; + + if( cls.timedemo ) + CL_FinishTimeDemo(); + + if( cls.changedemo ) + { + S_StopAllSounds( true ); + S_StopBackgroundTrack(); + } + else + { + // let game known about demo state + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + cls.state = ca_disconnected; + S_StopBackgroundTrack(); + cls.connect_time = 0; + cls.demonum = -1; + cls.signon = 0; + + // and finally clear the state + CL_ClearState (); + } +} + +/* +================== +CL_GetDemoComment +================== +*/ +qboolean CL_GetDemoComment( const char *demoname, char *comment ) +{ + file_t *demfile; + demoheader_t demohdr; + demodirectory_t directory; + demoentry_t entry; + float playtime = 0.0f; + int i; + + if( !comment ) return false; + + demfile = FS_Open( demoname, "rb", false ); + if( !demfile ) + { + Q_strncpy( comment, "", MAX_STRING ); + return false; + } + + // read in the m_DemoHeader + FS_Read( demfile, &demohdr, sizeof( demoheader_t )); + + if( demohdr.id != IDEMOHEADER ) + { + FS_Close( demfile ); + Q_strncpy( comment, "", MAX_STRING ); + return false; + } + + if( demohdr.net_protocol != PROTOCOL_VERSION || demohdr.dem_protocol != DEMO_PROTOCOL ) + { + FS_Close( demfile ); + Q_strncpy( comment, "", MAX_STRING ); + return false; + } + + // now read in the directory structure. + FS_Seek( demfile, demohdr.directory_offset, SEEK_SET ); + FS_Read( demfile, &directory.numentries, sizeof( int )); + + if( directory.numentries < 1 || directory.numentries > 1024 ) + { + FS_Close( demfile ); + Q_strncpy( comment, "", MAX_STRING ); + return false; + } + + for( i = 0; i < directory.numentries; i++ ) + { + FS_Read( demfile, &entry, sizeof( demoentry_t )); + playtime += entry.playback_time; + } + + // split comment to sections + Q_strncpy( comment, demohdr.mapname, CS_SIZE ); + Q_strncpy( comment + CS_SIZE, demohdr.comment, CS_SIZE ); + Q_strncpy( comment + CS_SIZE * 2, va( "%g sec", playtime ), CS_TIME ); + + // all done + FS_Close( demfile ); + + return true; +} + +/* +================== +CL_NextDemo + +Called when a demo finishes +================== +*/ +qboolean CL_NextDemo( void ) +{ + char str[MAX_QPATH]; + + if( cls.demonum == -1 ) + return false; // don't play demos + S_StopAllSounds( true ); + + if( !cls.demos[cls.demonum][0] || cls.demonum == MAX_DEMOS ) + { + cls.demonum = 0; + if( !cls.demos[cls.demonum][0] ) + { + Con_Printf( "no demos listed with startdemos\n" ); + cls.demonum = -1; + return false; + } + } + + Q_snprintf( str, MAX_STRING, "playdemo %s\n", cls.demos[cls.demonum] ); + Cbuf_InsertText( str ); + cls.demonum++; + + return true; +} + +/* +================== +CL_DemoGetName +================== +*/ +void CL_DemoGetName( int lastnum, char *filename ) +{ + int a, b, c, d; + + if( !filename ) return; + if( lastnum < 0 || lastnum > 9999 ) + { + // bound + Q_strcpy( filename, "demo9999" ); + return; + } + + a = lastnum / 1000; + lastnum -= a * 1000; + b = lastnum / 100; + lastnum -= b * 100; + c = lastnum / 10; + lastnum -= c * 10; + d = lastnum; + + Q_sprintf( filename, "demo%i%i%i%i", a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record +Begins recording a demo from the current position +==================== +*/ +void CL_Record_f( void ) +{ + const char *name; + string demoname, demopath, demoshot; + int n; + + if( Cmd_Argc() == 1 ) + { + name = "new"; + } + else if( Cmd_Argc() == 2 ) + { + name = Cmd_Argv( 1 ); + } + else + { + Con_Printf( S_USAGE "record \n" ); + return; + } + + if( cls.demorecording ) + { + Con_Printf( "Already recording.\n"); + return; + } + + if( cls.demoplayback ) + { + Con_Printf( "Can't record during demo playback.\n"); + return; + } + + if( !cls.demoheader || cls.state != ca_active ) + { + Con_Printf( "You must be in a level to record.\n"); + return; + } + + if( !Q_stricmp( name, "new" )) + { + // scan for a free filename + for( n = 0; n < 10000; n++ ) + { + CL_DemoGetName( n, demoname ); + if( !FS_FileExists( va( "demos/%s.dem", demoname ), true )) + break; + } + + if( n == 10000 ) + { + Con_Printf( S_ERROR "no free slots for demo recording\n" ); + return; + } + } + else Q_strncpy( demoname, name, sizeof( demoname )); + + // open the demo file + Q_sprintf( demopath, "demos/%s.dem", demoname ); + Q_sprintf( demoshot, "demos/%s.bmp", demoname ); + + // unload previous image from memory (it's will be overwritten) + GL_FreeImage( demoshot ); + + // make sure what old demo is removed + if( FS_FileExists( demopath, false )) FS_Delete( demopath ); + if( FS_FileExists( demoshot, false )) FS_Delete( demoshot ); + + // write demoshot for preview + Cbuf_AddText( va( "demoshot \"%s\"\n", demoname )); + Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); + Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); + + CL_WriteDemoHeader( demopath ); +} + +/* +==================== +CL_PlayDemo_f + +playdemo +==================== +*/ +void CL_PlayDemo_f( void ) +{ + string filename; + string demoname; + int i; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "playdemo \n" ); + return; + } + + if( cls.demoplayback ) + { + CL_StopPlayback(); + } + + if( cls.demorecording ) + { + Con_Printf( "Can't playback during demo record.\n"); + return; + } + + Q_strncpy( demoname, Cmd_Argv( 1 ), sizeof( demoname ) - 1 ); + Q_snprintf( filename, sizeof( filename ), "demos/%s.dem", demoname ); + + if( !FS_FileExists( filename, true )) + { + MsgDev( D_ERROR, "couldn't open %s\n", filename ); + CL_DemoAborted(); + return; + } + + cls.demofile = FS_Open( filename, "rb", true ); + Q_strncpy( cls.demoname, demoname, sizeof( cls.demoname )); + Q_strncpy( gameui.globals->demoname, demoname, sizeof( gameui.globals->demoname )); + + // read in the m_DemoHeader + FS_Read( cls.demofile, &demo.header, sizeof( demoheader_t )); + + if( demo.header.id != IDEMOHEADER ) + { + MsgDev( D_ERROR, "%s is not a demo file\n", filename ); + CL_DemoAborted(); + return; + } + + if( demo.header.net_protocol != PROTOCOL_VERSION || demo.header.dem_protocol != DEMO_PROTOCOL ) + { + if( demo.header.dem_protocol != DEMO_PROTOCOL ) + MsgDev( D_ERROR, "playdemo: demo protocol outdated (%i should be %i)\n", demo.header.dem_protocol, DEMO_PROTOCOL ); + + if( demo.header.net_protocol != PROTOCOL_VERSION ) + MsgDev( D_ERROR, "playdemo: net protocol outdated (%i should be %i)\n", demo.header.net_protocol, PROTOCOL_VERSION ); + CL_DemoAborted(); + return; + } + + // now read in the directory structure. + FS_Seek( cls.demofile, demo.header.directory_offset, SEEK_SET ); + FS_Read( cls.demofile, &demo.directory.numentries, sizeof( int )); + + if( demo.directory.numentries < 1 || demo.directory.numentries > 1024 ) + { + MsgDev( D_ERROR, "demo had bogus # of directory entries: %i\n", demo.directory.numentries ); + CL_DemoAborted(); + return; + } + + if( cls.changedemo ) + { + S_StopAllSounds( true ); + SCR_BeginLoadingPlaque( false ); + + CL_ClearState (); + CL_InitEdicts (); // re-arrange edicts + } + else + { + // NOTE: at this point demo is still valid + CL_Disconnect(); + Host_ShutdownServer(); + + Con_FastClose(); + UI_SetActiveMenu( false ); + } + + // allocate demo entries + demo.directory.entries = Mem_Alloc( cls.mempool, sizeof( demoentry_t ) * demo.directory.numentries ); + + for( i = 0; i < demo.directory.numentries; i++ ) + { + FS_Read( cls.demofile, &demo.directory.entries[i], sizeof( demoentry_t )); + } + + demo.entryIndex = 0; + demo.entry = &demo.directory.entries[demo.entryIndex]; + + FS_Seek( cls.demofile, demo.entry->offset, SEEK_SET ); + + cls.demoplayback = true; + cls.state = ca_connected; + cl.background = (cls.demonum != -1) ? true : false; + cls.spectator = false; + cls.signon = 0; + + demo.starttime = CL_GetDemoPlaybackClock(); // for determining whether to read another message + + Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); + + memset( demo.cmds, 0, sizeof( demo.cmds )); + demo.angle_position = 1; + demo.framecount = 0; + cls.lastoutgoingcommand = -1; + cls.nextcmdtime = host.realtime; + cl.last_command_ack = -1; + + // g-cont. is this need? + Q_strncpy( cls.servername, demoname, sizeof( cls.servername )); + + // begin a playback demo +} + +/* +==================== +CL_TimeDemo_f + +timedemo +==================== +*/ +void CL_TimeDemo_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "timedemo \n" ); + return; + } + + CL_PlayDemo_f (); + + // cls.td_starttime will be grabbed at the second frame of the demo, so + // all the loading time doesn't get counted + cls.timedemo = true; + cls.td_starttime = host.realtime; + cls.td_startframe = host.framecount; + cls.td_lastframe = -1; // get a new message this frame +} + +/* +================== +CL_StartDemos_f +================== +*/ +void CL_StartDemos_f( void ) +{ + int i, c; + + if( cls.key_dest != key_menu ) + { + Con_Printf( "'startdemos' is not valid from the console\n" ); + return; + } + + c = Cmd_Argc() - 1; + if( c > MAX_DEMOS ) + { + MsgDev( D_WARN, "Host_StartDemos: max %i demos in demoloop\n", MAX_DEMOS ); + c = MAX_DEMOS; + } + + Con_Printf( "%i demo%s in loop\n", c, (c > 1) ? "s" : "" ); + + for( i = 1; i < c + 1; i++ ) + Q_strncpy( cls.demos[i-1], Cmd_Argv( i ), sizeof( cls.demos[0] )); + + if( !SV_Active() && !cls.demoplayback ) + { + // run demos loop in background mode + Cvar_SetValue( "v_dark", 1.0f ); + cls.demonum = 0; + CL_NextDemo (); + } + else cls.demonum = -1; +} + +/* +================== +CL_Demos_f + +Return to looping demos +================== +*/ +void CL_Demos_f( void ) +{ + if( cls.key_dest != key_menu ) + { + Con_Printf( "'demos' is not valid from the console\n" ); + return; + } + + cls.demonum = cls.olddemonum; + + if( cls.demonum == -1 ) + cls.demonum = 0; + + if( !SV_Active() && !cls.demoplayback ) + { + // run demos loop in background mode + cls.changedemo = true; + CL_NextDemo (); + } +} + + +/* +==================== +CL_Stop_f + +stop any client activity +==================== +*/ +void CL_Stop_f( void ) +{ + // stop all + CL_StopRecord(); + CL_StopPlayback(); + SCR_StopCinematic(); + + // stop background track that was runned from the console + if( !SV_Active( )) + { + S_StopBackgroundTrack(); + } +} \ No newline at end of file diff --git a/engine/client/cl_events.c b/engine/client/cl_events.c new file mode 100644 index 00000000..cbcb3519 --- /dev/null +++ b/engine/client/cl_events.c @@ -0,0 +1,505 @@ +/* +cl_events.c - client-side event system implementation +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "event_flags.h" +#include "net_encode.h" +#include "con_nprint.h" + +/* +=============== +CL_ResetEvent + +=============== +*/ +void CL_ResetEvent( event_info_t *ei ) +{ + ei->index = 0; + memset( &ei->args, 0, sizeof( ei->args )); + ei->fire_time = 0.0; + ei->flags = 0; +} + +/* +============= +CL_CalcPlayerVelocity + +compute velocity for a given client +============= +*/ +void CL_CalcPlayerVelocity( int idx, vec3_t velocity ) +{ + clientdata_t *pcd; + vec3_t delta; + double dt; + + VectorClear( velocity ); + + if( idx <= 0 || idx > cl.maxclients ) + return; + + if( idx == cl.playernum + 1 ) + { + pcd = &cl.frames[cl.parsecountmod].clientdata; + VectorCopy( pcd->velocity, velocity ); + } + else + { + dt = clgame.entities[idx].curstate.animtime - clgame.entities[idx].prevstate.animtime; + + if( dt != 0.0 ) + { + VectorSubtract( clgame.entities[idx].curstate.velocity, clgame.entities[idx].prevstate.velocity, delta ); + VectorScale( delta, 1.0 / dt, velocity ); + } + else + { + VectorCopy( clgame.entities[idx].curstate.velocity, velocity ); + } + } +} + +/* +============= +CL_DescribeEvent + +============= +*/ +void CL_DescribeEvent( int slot, int flags, const char *eventname ) +{ + int idx = (slot & 31); + con_nprint_t info; + + if( !eventname || !cl_showevents->value ) + return; + + // mark reliable as green and unreliable as red + if( FBitSet( flags, FEV_RELIABLE )) + VectorSet( info.color, 0.0f, 1.0f, 0.0f ); + else VectorSet( info.color, 1.0f, 0.0f, 0.0f ); + + info.time_to_live = 0.5f; + info.index = idx; + + Con_NXPrintf( &info, "%i %f %s", slot, cl.time, eventname ); +} + +/* +============= +CL_SetEventIndex + +============= +*/ +void CL_SetEventIndex( const char *szEvName, int ev_index ) +{ + cl_user_event_t *ev; + int i; + + if( !szEvName || !*szEvName ) + return; // ignore blank names + + // search event by name to link with + for( i = 0; i < MAX_EVENTS; i++ ) + { + ev = clgame.events[i]; + if( !ev ) break; + + if( !Q_stricmp( ev->name, szEvName )) + { + ev->index = ev_index; + return; + } + } +} + +/* +============= +CL_EventIndex + +============= +*/ +word CL_EventIndex( const char *name ) +{ + int i; + + if( !COM_CheckString( name )) + return 0; + + for( i = 1; i < MAX_EVENTS && cl.event_precache[i][0]; i++ ) + { + if( !Q_stricmp( cl.event_precache[i], name )) + return i; + } + return 0; +} + +/* +============= +CL_RegisterEvent + +============= +*/ +void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func ) +{ + cl_user_event_t *ev; + + if( lastnum == MAX_EVENTS ) + { + MsgDev( D_ERROR, "CL_RegisterEvent: MAX_EVENTS hit!\n" ); + return; + } + + // clear existing or allocate new one + if( !clgame.events[lastnum] ) + clgame.events[lastnum] = Mem_Alloc( cls.mempool, sizeof( cl_user_event_t )); + else memset( clgame.events[lastnum], 0, sizeof( cl_user_event_t )); + + ev = clgame.events[lastnum]; + + // NOTE: ev->index will be set later + Q_strncpy( ev->name, szEvName, MAX_QPATH ); + ev->func = func; +} + +/* +============= +CL_FireEvent + +============= +*/ +qboolean CL_FireEvent( event_info_t *ei, int slot ) +{ + cl_user_event_t *ev; + const char *name; + int i, idx; + + if( !ei || !ei->index ) + return false; + + // get the func pointer + for( i = 0; i < MAX_EVENTS; i++ ) + { + ev = clgame.events[i]; + + if( !ev ) + { + idx = bound( 1, ei->index, ( MAX_EVENTS - 1 )); + MsgDev( D_ERROR, "CL_FireEvent: %s not precached\n", cl.event_precache[idx] ); + break; + } + + if( ev->index == ei->index ) + { + if( ev->func ) + { + CL_DescribeEvent( slot, ei->flags, cl.event_precache[ei->index] ); + ev->func( &ei->args ); + return true; + } + + name = cl.event_precache[ei->index]; + MsgDev( D_ERROR, "CL_FireEvent: %s not hooked\n", name ); + break; + } + } + + return false; +} + +/* +============= +CL_FireEvents + +called right before draw frame +============= +*/ +void CL_FireEvents( void ) +{ + event_state_t *es; + event_info_t *ei; + int i; + + es = &cl.events; + + for( i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + ei = &es->ei[i]; + + if( ei->index == 0 ) + continue; + + // delayed event! + if( ei->fire_time && ( ei->fire_time > cl.time )) + continue; + + CL_FireEvent( ei, i ); + + // zero out the remaining fields + CL_ResetEvent( ei ); + } +} + +/* +============= +CL_FindEvent + +find first empty event +============= +*/ +event_info_t *CL_FindEmptyEvent( void ) +{ + int i; + event_state_t *es; + event_info_t *ei; + + es = &cl.events; + + // look for first slot where index is != 0 + for( i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + ei = &es->ei[i]; + if( ei->index != 0 ) + continue; + return ei; + } + + // no slots available + return NULL; +} + +/* +============= +CL_FindEvent + +replace only unreliable events +============= +*/ +event_info_t *CL_FindUnreliableEvent( void ) +{ + event_state_t *es; + event_info_t *ei; + int i; + + es = &cl.events; + + for ( i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + ei = &es->ei[i]; + if( ei->index != 0 ) + { + // it's reliable, so skip it + if( FBitSet( ei->flags, FEV_RELIABLE )) + continue; + } + return ei; + } + + // this should never happen + return NULL; +} + +/* +============= +CL_QueueEvent + +============= +*/ +void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ) +{ + event_info_t *ei; + + // find a normal slot + ei = CL_FindEmptyEvent(); + + if( !ei ) + { + if( FBitSet( flags, FEV_RELIABLE )) + { + ei = CL_FindUnreliableEvent(); + } + + if( !ei ) return; + } + + ei->index = index; + ei->packet_index = 0; + ei->fire_time = delay ? (cl.time + delay) : 0.0f; + ei->flags = flags; + ei->args = *args; +} + +/* +============= +CL_ParseReliableEvent + +============= +*/ +void CL_ParseReliableEvent( sizebuf_t *msg ) +{ + int event_index; + event_args_t nullargs, args; + float delay = 0.0f; + + memset( &nullargs, 0, sizeof( nullargs )); + + event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); + + if( MSG_ReadOneBit( msg )) + delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); + + // reliable events not use delta-compression just null-compression + MSG_ReadDeltaEvent( msg, &nullargs, &args ); + + if( args.entindex > 0 && args.entindex <= cl.maxclients ) + args.angles[PITCH] *= -3.0f; + + CL_QueueEvent( FEV_RELIABLE|FEV_SERVER, event_index, delay, &args ); +} + + +/* +============= +CL_ParseEvent + +============= +*/ +void CL_ParseEvent( sizebuf_t *msg ) +{ + int event_index; + int i, num_events; + int packet_index; + event_args_t nullargs, args; + entity_state_t *state; + float delay; + + memset( &nullargs, 0, sizeof( nullargs )); + memset( &args, 0, sizeof( args )); + + num_events = MSG_ReadUBitLong( msg, 5 ); + + // parse events queue + for( i = 0 ; i < num_events; i++ ) + { + event_index = MSG_ReadUBitLong( msg, MAX_EVENT_BITS ); + + if( MSG_ReadOneBit( msg )) + packet_index = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + else packet_index = -1; + + if( MSG_ReadOneBit( msg )) + { + MSG_ReadDeltaEvent( msg, &nullargs, &args ); + } + + if( MSG_ReadOneBit( msg )) + delay = (float)MSG_ReadWord( msg ) * (1.0f / 100.0f); + else delay = 0.0f; + + if( packet_index != -1 ) + { + frame_t *frame = &cl.frames[cl.parsecountmod]; + + if( packet_index < frame->num_entities ) + { + state = &cls.packet_entities[(frame->first_entity+packet_index)%cls.num_client_entities]; + args.entindex = state->number; + + if( VectorIsNull( args.origin )) + VectorCopy( state->origin, args.origin ); + + if( VectorIsNull( args.angles )) + VectorCopy( state->angles, args.angles ); + + COM_NormalizeAngles( args.angles ); + + if( state->number > 0 && state->number <= cl.maxclients ) + { + args.angles[PITCH] *= -3.0f; + CL_CalcPlayerVelocity( state->number, args.velocity ); + args.ducking = ( state->usehull == 1 ); + } + } + else + { + if( args.entindex != 0 ) + { + if( args.entindex > 0 && args.entindex <= cl.maxclients ) + args.angles[PITCH] /= -3.0f; + } + else + { + MsgDev( D_WARN, "CL_ParseEvent: Received non-packet entity index 0 for event\n" ); + } + } + + // Place event on queue + CL_QueueEvent( FEV_SERVER, event_index, delay, &args ); + } + } +} + +/* +============= +CL_PlaybackEvent + +============= +*/ +void CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + event_args_t args; + + if( flags & FEV_SERVER ) + { + MsgDev( D_WARN, "CL_PlaybackEvent: event with FEV_SERVER flag!\n" ); + return; + } + + // first check event for out of bounds + if( eventindex < 1 || eventindex > MAX_EVENTS ) + { + MsgDev( D_ERROR, "CL_PlaybackEvent: invalid eventindex %i\n", eventindex ); + return; + } + + // check event for precached + if( !CL_EventIndex( cl.event_precache[eventindex] )) + { + MsgDev( D_ERROR, "CL_PlaybackEvent: event %i was not precached\n", eventindex ); + return; + } + + flags |= FEV_CLIENT; // it's a client event + flags &= ~(FEV_NOTHOST|FEV_HOSTONLY|FEV_GLOBAL); + if( delay < 0.0f ) delay = 0.0f; // fixup negative delays + + memset( &args, 0, sizeof( args )); + + VectorCopy( origin, args.origin ); + VectorCopy( angles, args.angles ); + VectorCopy( cl.simvel, args.velocity ); + args.entindex = cl.playernum + 1; + args.ducking = ( cl.local.usehull == 1 ); + + args.fparam1 = fparam1; + args.fparam2 = fparam2; + args.iparam1 = iparam1; + args.iparam2 = iparam2; + args.bparam1 = bparam1; + args.bparam2 = bparam2; + + CL_QueueEvent( flags, eventindex, delay, &args ); +} \ No newline at end of file diff --git a/engine/client/cl_frame.c b/engine/client/cl_frame.c new file mode 100644 index 00000000..1e6b8ed4 --- /dev/null +++ b/engine/client/cl_frame.c @@ -0,0 +1,1326 @@ +/* +cl_frame.c - client world snapshot +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "entity_types.h" +#include "gl_local.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "studio.h" +#include "dlight.h" +#include "sound.h" +#include "input.h" + +#define STUDIO_INTERPOLATION_FIX + +/* +================== +CL_IsPlayerIndex + +detect player entity +================== +*/ +qboolean CL_IsPlayerIndex( int idx ) +{ + return ( idx >= 1 && idx <= cl.maxclients ); +} + +/* +========================================================================= + +FRAME INTERPOLATION + +========================================================================= +*/ +/* +================== +CL_UpdatePositions + +Store another position into interpolation circular buffer +================== +*/ +void CL_UpdatePositions( cl_entity_t *ent ) +{ + position_history_t *ph; + + ent->current_position = (ent->current_position + 1) & HISTORY_MASK; + ph = &ent->ph[ent->current_position]; + + VectorCopy( ent->curstate.origin, ph->origin ); + VectorCopy( ent->curstate.angles, ph->angles ); + ph->animtime = ent->curstate.animtime; // !!! +} + +/* +================== +CL_ResetPositions + +Interpolation init or reset after teleporting +================== +*/ +void CL_ResetPositions( cl_entity_t *ent ) +{ + position_history_t store; + + if( !ent ) return; + + store = ent->ph[ent->current_position]; + ent->current_position = 1; + + memset( ent->ph, 0, sizeof( position_history_t ) * HISTORY_MAX ); + memcpy( &ent->ph[1], &store, sizeof( position_history_t )); + memcpy( &ent->ph[0], &store, sizeof( position_history_t )); +} + +/* +================== +CL_EntityTeleported + +check for instant movement in case +we don't want interpolate this +================== +*/ +qboolean CL_EntityTeleported( cl_entity_t *ent ) +{ + int len, maxlen; + vec3_t delta; + + VectorSubtract( ent->curstate.origin, ent->prevstate.origin, delta ); + + // compute potential max movement in units per frame and compare with entity movement + maxlen = ( clgame.movevars.maxvelocity * ( 1.0 / GAME_FPS )); + len = VectorLength( delta ); + + return (len > maxlen); +} + +/* +================== +CL_CompareTimestamps + +round-off floating errors +================== +*/ +qboolean CL_CompareTimestamps( float t1, float t2 ) +{ + int iTime1 = t1 * 1000; + int iTime2 = t2 * 1000; + + return (( iTime1 - iTime2 ) <= 1 ); +} + +/* +================== +CL_EntityIgnoreLerp + +some ents will be ignore lerping +================== +*/ +qboolean CL_EntityIgnoreLerp( cl_entity_t *e ) +{ + if( e->model && e->model->type == mod_alias ) + return false; + + return (e->curstate.movetype == MOVETYPE_NONE) ? true : false; +} + +/* +================== +CL_EntityCustomLerp + +================== +*/ +qboolean CL_EntityCustomLerp( cl_entity_t *e ) +{ + switch( e->curstate.movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_STEP: + case MOVETYPE_WALK: + case MOVETYPE_FLY: + case MOVETYPE_COMPOUND: + return false; + } + + return true; +} + +/* +================== +CL_ParametricMove + +check for parametrical moved entities +================== +*/ +qboolean CL_ParametricMove( cl_entity_t *ent ) +{ + float frac, dt, t; + vec3_t delta; + + if( ent->curstate.starttime == 0.0f || ent->curstate.impacttime == 0.0f ) + return false; + + VectorSubtract( ent->curstate.endpos, ent->curstate.startpos, delta ); + dt = ent->curstate.impacttime - ent->curstate.starttime; + + if( dt != 0.0f ) + { + if( ent->lastmove > cl.time ) + t = ent->lastmove; + else t = cl.time; + + frac = ( t - ent->curstate.starttime ) / dt; + frac = bound( 0.0f, frac, 1.0f ); + VectorMA( ent->curstate.startpos, frac, delta, ent->curstate.origin ); + + ent->lastmove = t; + } + + VectorNormalize( delta ); + if( VectorLength( delta ) > 0.0f ) + VectorAngles( delta, ent->curstate.angles ); // re-aim projectile + + return true; +} + +/* +==================== +CL_UpdateLatchedVars + +==================== +*/ +void CL_UpdateLatchedVars( cl_entity_t *ent ) +{ + if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio )) + return; // below fields used only for alias and studio interpolation + + VectorCopy( ent->prevstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->prevstate.angles, ent->latched.prevangles ); + + if( ent->model->type == mod_alias ) + ent->latched.prevframe = ent->prevstate.frame; + ent->latched.prevanimtime = ent->prevstate.animtime; + + if( ent->curstate.sequence != ent->prevstate.sequence ) + { + memcpy( ent->prevstate.blending, ent->latched.prevseqblending, sizeof( ent->prevstate.blending )); + ent->latched.prevsequence = ent->prevstate.sequence; + ent->latched.sequencetime = ent->curstate.animtime; + } + + memcpy( ent->latched.prevcontroller, ent->prevstate.controller, sizeof( ent->latched.prevcontroller )); + memcpy( ent->latched.prevblending, ent->prevstate.blending, sizeof( ent->latched.prevblending )); +} + +/* +==================== +CL_ResetLatchedVars + +==================== +*/ +void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset ) +{ + if( !ent->model || ( ent->model->type != mod_alias && ent->model->type != mod_studio )) + return; // below fields used only for alias and studio interpolation + + if( full_reset ) + { + // don't modify for sprites to avoid broke sprite interp + memcpy( ent->latched.prevblending, ent->curstate.blending, sizeof( ent->latched.prevblending )); + ent->latched.sequencetime = ent->curstate.animtime; + memcpy( ent->latched.prevcontroller, ent->curstate.controller, sizeof( ent->latched.prevcontroller )); + if( ent->model->type == mod_studio ) + ent->latched.prevframe = CL_GetStudioEstimatedFrame( ent ); + else if( ent->model->type == mod_alias ) + ent->latched.prevframe = ent->curstate.frame; + ent->prevstate = ent->curstate; + } + + ent->latched.prevanimtime = ent->curstate.animtime = cl.mtime[0]; + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->curstate.angles, ent->latched.prevangles ); + ent->latched.prevsequence = ent->curstate.sequence; +} + +/* +================== +CL_ProcessEntityUpdate + +apply changes since new frame received +================== +*/ +void CL_ProcessEntityUpdate( cl_entity_t *ent ) +{ + qboolean parametric; + + ent->model = CL_ModelHandle( ent->curstate.modelindex ); + ent->index = ent->curstate.number; + + // g-cont. make sure what it's no broke XashXT physics + COM_NormalizeAngles( ent->curstate.angles ); + + parametric = CL_ParametricMove( ent ); + + // allow interpolation on bmodels too + if( ent->model && ent->model->type == mod_brush ) + ent->curstate.animtime = ent->curstate.msg_time; + + if( CL_EntityCustomLerp( ent ) && !parametric ) + ent->curstate.animtime = ent->curstate.msg_time; + + if( !CL_CompareTimestamps( ent->curstate.animtime, ent->prevstate.animtime ) || CL_EntityIgnoreLerp( ent )) + { + CL_UpdateLatchedVars( ent ); + CL_UpdatePositions( ent ); + } + + // g-cont. it should be done for all the players? + if( ent->player && !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) + ent->curstate.angles[PITCH] /= -3.0f; + + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + + // initialize attachments for now + VectorCopy( ent->origin, ent->attachment[0] ); + VectorCopy( ent->origin, ent->attachment[1] ); + VectorCopy( ent->origin, ent->attachment[2] ); + VectorCopy( ent->origin, ent->attachment[3] ); +} + +/* +================== +CL_FindInterpolationUpdates + +find two timestamps +================== +*/ +qboolean CL_FindInterpolationUpdates( cl_entity_t *ent, float targettime, position_history_t **ph0, position_history_t **ph1 ) +{ + qboolean extrapolate = true; + int i, i0, i1, imod; + float at; + + imod = ent->current_position; + i0 = (imod - 0) & HISTORY_MASK; // curpos (lerp end) + i1 = (imod - 1) & HISTORY_MASK; // oldpos (lerp start) + + for( i = 1; i < HISTORY_MAX - 1; i++ ) + { + at = ent->ph[( imod - i ) & HISTORY_MASK].animtime; + if( at == 0.0 ) break; + + if( targettime > at ) + { + // found it + i0 = (( imod - i ) + 1 ) & HISTORY_MASK; + i1 = (( imod - i ) + 0 ) & HISTORY_MASK; + extrapolate = false; + break; + } + } + + if( ph0 != NULL ) *ph0 = &ent->ph[i0]; + if( ph1 != NULL ) *ph1 = &ent->ph[i1]; + + return extrapolate; +} + +/* +================== +CL_PureOrigin + +non-local players interpolation +================== +*/ +void CL_PureOrigin( cl_entity_t *ent, float t, vec3_t outorigin, vec3_t outangles ) +{ + qboolean extrapolate; + float t1, t0, frac; + position_history_t *ph0, *ph1; + vec3_t delta; + + // NOTE: ph0 is next, ph1 is a prev + extrapolate = CL_FindInterpolationUpdates( ent, t, &ph0, &ph1 ); + + if ( !ph0 || !ph1 ) + return; + + t0 = ph0->animtime; + t1 = ph1->animtime; + + if( t0 != 0.0f ) + { + vec4_t q, q1, q2; + + VectorSubtract( ph0->origin, ph1->origin, delta ); + + if( t0 != t1 ) + frac = ( t - t1 ) / ( t0 - t1 ); + else frac = 1.0f; + + frac = bound( 0.0f, frac, 1.2f ); + + VectorMA( ph1->origin, frac, delta, outorigin ); + + AngleQuaternion( ph0->angles, q1, false ); + AngleQuaternion( ph1->angles, q2, false ); + QuaternionSlerp( q2, q1, frac, q ); + QuaternionAngle( q, outangles ); + } + else + { + // no backup found + VectorCopy( ph1->origin, outorigin ); + VectorCopy( ph1->angles, outangles ); + } +} + +/* +================== +CL_InterpolateModel + +non-players interpolation +================== +*/ +int CL_InterpolateModel( cl_entity_t *e ) +{ + position_history_t *ph0 = NULL, *ph1 = NULL; + vec3_t origin, angles, delta; + float t, t1, t2, frac; + vec4_t q, q1, q2; + + VectorCopy( e->curstate.origin, e->origin ); + VectorCopy( e->curstate.angles, e->angles ); + + if( cls.timedemo || !e->model ) + return 1; + + if( cl.maxclients <= 1 && !FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + return 1; + + if( e->model->type == mod_brush && !cl_bmodelinterp->value ) + return 1; + + if( cl.local.moving && cl.local.onground == e->index ) + return 1; + + if( cl.maxclients <= 1 && FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + t = cl.time - cl_serverframetime(); + else t = cl.time - cl_interp->value; + + CL_FindInterpolationUpdates( e, t, &ph0, &ph1 ); + + if( ph0 == NULL || ph1 == NULL ) + return 0; + + t1 = ph1->animtime; + t2 = ph0->animtime; + + if( t - t1 < 0.0f ) + return 0; + + if( t1 == 0.0f ) + { + VectorCopy( ph0->origin, e->origin ); + VectorCopy( ph0->angles, e->angles ); + return 0; + } + + if( t2 == t1 ) + { + VectorCopy( ph0->origin, e->origin ); + VectorCopy( ph0->angles, e->angles ); + return 1; + } + + VectorSubtract( ph0->origin, ph1->origin, delta ); + frac = (t - t1) / (t2 - t1); + + if( frac < 0.0f ) + return 0; + + if( frac > 1.0f ) + frac = 1.0f; + + VectorMA( ph1->origin, frac, delta, origin ); + + AngleQuaternion( ph0->angles, q1, false ); + AngleQuaternion( ph1->angles, q2, false ); + QuaternionSlerp( q2, q1, frac, q ); + QuaternionAngle( q, angles ); + + VectorCopy( origin, e->origin ); + VectorCopy( angles, e->angles ); + + return 1; +} + +/* +============= +CL_ComputePlayerOrigin + +interpolate non-local clients +============= +*/ +void CL_ComputePlayerOrigin( cl_entity_t *ent ) +{ + float targettime; + vec3_t origin; + vec3_t angles; + + if( !ent->player || ent->index == ( cl.playernum + 1 )) + return; + + targettime = cl.time - cl_interp->value; + CL_PureOrigin( ent, targettime, origin, angles ); + + VectorCopy( angles, ent->angles ); + VectorCopy( origin, ent->origin ); +} + +/* +================= +CL_ProcessPlayerState + +process player states after the new packet has received +================= +*/ +void CL_ProcessPlayerState( int playerindex, entity_state_t *state ) +{ + entity_state_t *ps; + + ps = &cl.frames[cl.parsecountmod].playerstate[playerindex]; + ps->number = state->number; + ps->messagenum = cl.parsecount; + ps->msg_time = cl.mtime[0]; + + clgame.dllFuncs.pfnProcessPlayerState( ps, state ); +} + +/* +================= +CL_ResetLatchedState + +reset latched state if this frame entity was teleported +or just EF_NOINTERP was set +================= +*/ +void CL_ResetLatchedState( int pnum, frame_t *frame, cl_entity_t *ent ) +{ + if( CHECKVISBIT( frame->flags, pnum )) + { + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->curstate.angles, ent->latched.prevangles ); + + CL_ResetLatchedVars( ent, true ); + CL_ResetPositions( ent ); + + // parametric interpolation will starts at this point + if( ent->curstate.starttime != 0.0f && ent->curstate.impacttime != 0.0f ) + ent->lastmove = cl.time; + } +} + +/* +================= +CL_ProcessPacket + +process player states after the new packet has received +================= +*/ +void CL_ProcessPacket( frame_t *frame ) +{ + entity_state_t *state; + cl_entity_t *ent; + int pnum; + + for( pnum = 0; pnum < frame->num_entities; pnum++ ) + { + // request the entity state from circular buffer + state = &cls.packet_entities[(frame->first_entity+pnum) % cls.num_client_entities]; + state->messagenum = cl.parsecount; + state->msg_time = cl.mtime[0]; + + // mark all the players + ent = &clgame.entities[state->number]; + ent->player = CL_IsPlayerIndex( state->number ); + + if( state->number == ( cl.playernum + 1 )) + clgame.dllFuncs.pfnTxferLocalOverrides( state, &frame->clientdata ); + + // shuffle states + ent->prevstate = ent->curstate; + ent->curstate = *state; + + CL_ProcessEntityUpdate( ent ); + CL_ResetLatchedState( pnum, frame, ent ); + if( !ent->player ) continue; + + CL_ProcessPlayerState(( state->number - 1 ), state ); + + if( state->number == ( cl.playernum + 1 )) + CL_CheckPredictionError(); + } +} + +/* +========================================================================= + +FRAME PARSING + +========================================================================= +*/ +/* +================= +CL_FlushEntityPacket + +Read and ignore whole entity packet. +================= +*/ +void CL_FlushEntityPacket( sizebuf_t *msg ) +{ + int newnum; + entity_state_t from, to; + + memset( &from, 0, sizeof( from )); + + cl.frames[cl.parsecountmod].valid = false; + cl.validsequence = 0; // can't render a frame + + // read it all, but ignore it + while( 1 ) + { + newnum = MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ); + if( newnum == LAST_EDICT ) break; // done + + if( MSG_CheckOverflow( msg )) + Host_Error( "CL_FlushEntityPacket: overflow\n" ); + + MSG_ReadDeltaEntity( msg, &from, &to, newnum, CL_IsPlayerIndex( newnum ), cl.mtime[0] ); + } +} + +/* +================= +CL_DeltaEntity + +processing delta update +================= +*/ +void CL_DeltaEntity( sizebuf_t *msg, frame_t *frame, int newnum, entity_state_t *old, qboolean has_update ) +{ + cl_entity_t *ent; + entity_state_t *state; + qboolean newent = (old) ? false : true; + int pack = frame->num_entities; + qboolean player = CL_IsPlayerIndex( newnum ); + qboolean alive = true; + + // alloc next slot to store update + state = &cls.packet_entities[cls.next_client_entities % cls.num_client_entities]; + + if(( newnum < 0 ) || ( newnum >= clgame.maxEntities )) + { + MsgDev( D_ERROR, "CL_DeltaEntity: invalid newnum: %d\n", newnum ); + if( has_update ) + MSG_ReadDeltaEntity( msg, old, state, newnum, player, cl.mtime[0] ); + return; + } + + ent = CL_EDICT_NUM( newnum ); + ent->index = newnum; // enumerate entity index + if( newent ) old = &ent->baseline; + + if( has_update ) + alive = MSG_ReadDeltaEntity( msg, old, state, newnum, player, cl.mtime[0] ); + else memcpy( state, old, sizeof( entity_state_t )); + + if( !alive ) + { + CL_KillDeadBeams( ent ); // release dead beams +#if 0 + // this is for reference + if( state->number == -1 ) + Con_DPrintf( "Entity %i was removed from server\n", newnum ); + else Con_Dprintf( "Entity %i was removed from delta-message\n", newnum ); +#endif + return; + } + + if( newent ) + { + // interpolation must be reset + SETVISBIT( frame->flags, pack ); + + // release beams from previous entity + CL_KillDeadBeams( ent ); + } + + // add entity to packet + cls.next_client_entities++; + frame->num_entities++; +} + +/* +================== +CL_ParsePacketEntities + +An svc_packetentities has just been parsed, deal with the +rest of the data stream. +================== +*/ +int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ) +{ + frame_t *newframe, *oldframe; + int oldindex, newnum, oldnum; + int playerbytes = 0; + int oldpacket; + int bufStart; + entity_state_t *oldent; + qboolean player; + int count; + + // save first uncompressed packet as timestamp + if( cls.changelevel && !delta && cls.demorecording ) + CL_WriteDemoJumpTime(); + + // sentinel count. save it for debug checking + count = ( MSG_ReadUBitLong( msg, MAX_VISIBLE_PACKET_BITS ) + 1 ); + newframe = &cl.frames[cl.parsecountmod]; + + // allocate parse entities + memset( newframe->flags, 0, sizeof( newframe->flags )); + newframe->first_entity = cls.next_client_entities; + newframe->num_entities = 0; + newframe->valid = true; // assume valid + + if( delta ) + { + int subtracted; + + oldpacket = MSG_ReadByte( msg ); + subtracted = ( cls.netchan.incoming_sequence - oldpacket ) & 0xFF; + + if( subtracted == 0 ) + { + Con_NPrintf( 2, "^3Warning:^1 update too old\n^7\n" ); + CL_FlushEntityPacket( msg ); + return playerbytes; + } + + if( subtracted >= CL_UPDATE_MASK ) + { + // we can't use this, it is too old + Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); + CL_FlushEntityPacket( msg ); + return playerbytes; + } + + oldframe = &cl.frames[oldpacket & CL_UPDATE_MASK]; + + if(( cls.next_client_entities - oldframe->first_entity ) > ( cls.num_client_entities - NUM_PACKET_ENTITIES )) + { + MsgDev( D_NOTE, "CL_ParsePacketEntities: delta frame is too old (flush)\n"); + Con_NPrintf( 2, "^3Warning:^1 delta frame is too old^7\n" ); + CL_FlushEntityPacket( msg ); + return playerbytes; + } + } + else + { + // this is a full update that we can start delta compressing from now + oldframe = NULL; + oldpacket = -1; // delta too old or is initial message + cl.send_reply = true; // send reply + cls.demowaiting = false; // we can start recording now + } + + // mark current delta state + cl.validsequence = cls.netchan.incoming_sequence; + + oldent = NULL; + oldindex = 0; + + if( !oldframe ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + if( oldindex >= oldframe->num_entities ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; + oldnum = oldent->number; + } + } + + while( 1 ) + { + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + if( newnum == LAST_EDICT ) break; // end of packet entities + + if( MSG_CheckOverflow( msg )) + Host_Error( "CL_ParsePacketEntities: overflow\n" ); + player = CL_IsPlayerIndex( newnum ); + + while( oldnum < newnum ) + { + // one or more entities from the old packet are unchanged + CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); + oldindex++; + + if( oldindex >= oldframe->num_entities ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; + oldnum = oldent->number; + } + } + + if( oldnum == newnum ) + { + // delta from previous state + bufStart = MSG_GetNumBytesRead( msg ); + CL_DeltaEntity( msg, newframe, newnum, oldent, true ); + if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; + oldindex++; + + if( oldindex >= oldframe->num_entities ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; + oldnum = oldent->number; + } + continue; + } + + if( oldnum > newnum ) + { + // delta from baseline ? + bufStart = MSG_GetNumBytesRead( msg ); + CL_DeltaEntity( msg, newframe, newnum, NULL, true ); + if( player ) playerbytes += MSG_GetNumBytesRead( msg ) - bufStart; + continue; + } + } + + // any remaining entities in the old frame are copied over + while( oldnum != MAX_ENTNUMBER ) + { + // one or more entities from the old packet are unchanged + CL_DeltaEntity( msg, newframe, oldnum, oldent, false ); + oldindex++; + + if( oldindex >= oldframe->num_entities ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + oldent = &cls.packet_entities[(oldframe->first_entity+oldindex) % cls.num_client_entities]; + oldnum = oldent->number; + } + } + + if( newframe->num_entities != count && newframe->num_entities != 0 ) + MsgDev( D_WARN, "CL_Parse%sPacketEntities: (%i should be %i)\n", delta ? "Delta" : "", newframe->num_entities, count ); + + if( !newframe->valid ) + return playerbytes; // frame is not valid but message was parsed + + // now process packet. + CL_ProcessPacket( newframe ); + + // add new entities into physic lists + CL_SetSolidEntities(); + + // first update is the final signon stage where we actually receive an entity (i.e., the world at least) + if( cls.signon == ( SIGNONS - 1 )) + { + // we are done with signon sequence. + cls.signon = SIGNONS; + + // Clear loading plaque. + CL_SignonReply (); + } + + return playerbytes; +} + +/* +========================================================================== + +INTERPOLATE BETWEEN FRAMES TO GET RENDERING PARMS + +========================================================================== +*/ +/* +============= +CL_AddVisibleEntity + +all the visible entities should pass this filter +============= +*/ +qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ) +{ + if( !ent || !ent->model ) + return false; + + // check for adding this entity + if( !clgame.dllFuncs.pfnAddEntity( entityType, ent, ent->model->name )) + { + // local player was reject by game code, so ignore any effects + if( RP_LOCALCLIENT( ent )) + cl.local.apply_effects = false; + return false; + } + + // don't add the player in firstperson mode + if( RP_LOCALCLIENT( ent ) && !CL_IsThirdPerson( ) && ( ent->index == cl.viewentity )) + return false; + + if( entityType == ET_BEAM ) + { + CL_AddCustomBeam( ent ); + return true; + } + else if( !R_AddEntity( ent, entityType )) + { + return false; + } + + // because pTemp->entity.curstate.effects + // is already occupied by FTENT_FLICKER + if( entityType != ET_TEMPENTITY ) + { + // no reason to do it twice + if( RP_LOCALCLIENT( ent )) + cl.local.apply_effects = false; + + // apply client-side effects + CL_AddEntityEffects( ent ); + + // alias & studiomodel efefcts + CL_AddModelEffects( ent ); + } + + return true; +} + +/* +============= +CL_LinkCustomEntity + +Add server beam to draw list +============= +*/ +void CL_LinkCustomEntity( cl_entity_t *ent, entity_state_t *state ) +{ + ent->curstate.movetype = state->modelindex; // !!! + + if( ent->model->type != mod_sprite ) + MsgDev( D_WARN, "bad model on beam ( %s )\n", ent->model->name ); + + ent->latched.prevsequence = ent->curstate.sequence; + VectorCopy( ent->origin, ent->latched.prevorigin ); + VectorCopy( ent->angles, ent->latched.prevangles ); + ent->prevstate = ent->curstate; + + CL_AddVisibleEntity( ent, ET_BEAM ); +} + +/* +============= +CL_LinkPlayers + +Create visible entities in the correct position +for all current players +============= +*/ +void CL_LinkPlayers( frame_t *frame ) +{ + entity_state_t *state; + cl_entity_t *ent; + int i; + + ent = CL_GetLocalPlayer(); + + // apply muzzleflash to weaponmodel + if( ent && FBitSet( ent->curstate.effects, EF_MUZZLEFLASH )) + SetBits( clgame.viewent.curstate.effects, EF_MUZZLEFLASH ); + cl.local.apply_effects = true; + + // check all the clients but add only visible + for( i = 0, state = frame->playerstate; i < MAX_CLIENTS; i++, state++ ) + { + if( state->messagenum != cl.parsecount ) + continue; // not present this frame + + if( !state->modelindex || FBitSet( state->effects, EF_NODRAW )) + continue; + + ent = &clgame.entities[i + 1]; + + // fixup the player indexes... + if( ent->index != ( i + 1 )) ent->index = (i + 1); + + if( i == cl.playernum ) + { + VectorCopy( state->origin, ent->origin ); + VectorCopy( state->origin, ent->prevstate.origin ); + VectorCopy( state->origin, ent->curstate.origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + } + + if( FBitSet( ent->curstate.effects, EF_NOINTERP )) + CL_ResetLatchedVars( ent, false ); + + if( CL_EntityTeleported( ent )) + { + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->curstate.angles, ent->latched.prevangles ); + CL_ResetPositions( ent ); + } + + if ( i == cl.playernum ) + { + VectorCopy( cl.simorg, ent->origin ); + } + else + { + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + + // interpolate non-local clients + CL_ComputePlayerOrigin( ent ); + } + + VectorCopy( ent->origin, ent->attachment[0] ); + VectorCopy( ent->origin, ent->attachment[1] ); + VectorCopy( ent->origin, ent->attachment[2] ); + VectorCopy( ent->origin, ent->attachment[3] ); + + CL_AddVisibleEntity( ent, ET_PLAYER ); + } + + // apply local player effects if entity is not added + if( cl.local.apply_effects ) CL_AddEntityEffects( CL_GetLocalPlayer( )); +} + +/* +=============== +CL_LinkPacketEntities + +=============== +*/ +void CL_LinkPacketEntities( frame_t *frame ) +{ + cl_entity_t *ent; + entity_state_t *state; + qboolean parametric; + int i; + + for( i = 0; i < frame->num_entities; i++ ) + { + state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities]; + + // clients are should be done in CL_LinkPlayers + if( state->number >= 1 && state->number <= cl.maxclients ) + continue; + + // if set to invisible, skip + if( !state->modelindex || FBitSet( state->effects, EF_NODRAW )) + continue; + + ent = CL_GetEntityByIndex( state->number ); + + if( !ent ) + { + MsgDev( D_ERROR, "CL_LinkPacketEntity: bad entity %i\n", state->number ); + continue; + } + + ent->curstate = *state; + + // XASH SPECIFIC + if( ent->curstate.rendermode == kRenderNormal && ent->curstate.renderfx == kRenderFxNone ) + ent->curstate.renderamt = 255.0f; + + if( !ent->model ) continue; + + if( ent->curstate.rendermode == kRenderNormal ) + { + // auto 'solid' faces + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + ent->curstate.rendermode = kRenderTransAlpha; + ent->curstate.renderamt = 255; + } + } + + parametric = ( ent->curstate.impacttime != 0.0f && ent->curstate.starttime != 0.0f ); + + if( !parametric && ent->curstate.movetype != MOVETYPE_COMPOUND ) + { + if( ent->curstate.animtime == ent->prevstate.animtime && !VectorCompare( ent->curstate.origin, ent->prevstate.origin )) + ent->lastmove = cl.time + 0.2; + + if( FBitSet( ent->curstate.eflags, EFLAG_SLERP )) + { + if( ent->curstate.animtime != 0.0f && ( ent->model->type == mod_alias || ent->model->type == mod_studio )) + { +#ifdef STUDIO_INTERPOLATION_FIX + if( ent->lastmove >= cl.time ) + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + ent->curstate.movetype = MOVETYPE_STEP; +#else + if( ent->lastmove >= cl.time ) + { + CL_ResetLatchedVars( ent, true ); + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->curstate.angles, ent->latched.prevangles ); + + // disable step interpolation in client.dll + ent->curstate.movetype = MOVETYPE_NONE; + } + else + { + // restore step interpolation in client.dll + ent->curstate.movetype = MOVETYPE_STEP; + } +#endif + } + } + } + + if( ent->model->type == mod_brush ) + { + CL_InterpolateModel( ent ); + } + else + { + if( parametric ) + { + CL_ParametricMove( ent ); + + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + } + else if( CL_EntityCustomLerp( ent )) + { + if ( !CL_InterpolateModel( ent )) + continue; + } + else if( ent->curstate.movetype == MOVETYPE_STEP && !NET_IsLocalAddress( cls.netchan.remote_address )) + { + if( !CL_InterpolateModel( ent )) + continue; + } + else + { + // no interpolation right now + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + } + + if( ent->model->type == mod_studio ) + { + if( ent->curstate.movetype == MOVETYPE_STEP && FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) + R_StudioLerpMovement( ent, cl.time, ent->origin, ent->angles ); + } + } + + if( !FBitSet( state->entityType, ENTITY_NORMAL )) + { + CL_LinkCustomEntity( ent, state ); + continue; + } + + if( ent->model->type != mod_brush ) + { + // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug + if( !ent->curstate.rendercolor.r && !ent->curstate.rendercolor.g && !ent->curstate.rendercolor.b ) + ent->curstate.rendercolor.r = ent->curstate.rendercolor.g = ent->curstate.rendercolor.b = 255; + } + + if( ent->curstate.aiment != 0 && ent->curstate.movetype != MOVETYPE_COMPOUND ) + ent->curstate.movetype = MOVETYPE_FOLLOW; + + if( FBitSet( ent->curstate.effects, EF_NOINTERP )) + CL_ResetLatchedVars( ent, false ); + + if( CL_EntityTeleported( ent )) + { + VectorCopy( ent->curstate.origin, ent->latched.prevorigin ); + VectorCopy( ent->curstate.angles, ent->latched.prevangles ); + CL_ResetPositions( ent ); + } + + VectorCopy( ent->origin, ent->attachment[0] ); + VectorCopy( ent->origin, ent->attachment[1] ); + VectorCopy( ent->origin, ent->attachment[2] ); + VectorCopy( ent->origin, ent->attachment[3] ); + + CL_AddVisibleEntity( ent, ET_NORMAL ); + } +} + +/* +=============== +CL_MoveThirdpersonCamera + +think thirdperson +=============== +*/ +void CL_MoveThirdpersonCamera( void ) +{ + if( cls.state == ca_disconnected || cls.state == ca_cinematic ) + return; + + // think thirdperson camera + clgame.dllFuncs.CAM_Think (); +} + +/* +=============== +CL_EmitEntities + +add visible entities to refresh list +process frame interpolation etc +=============== +*/ +void CL_EmitEntities( void ) +{ + if( cl.paused ) return; // don't waste time + + R_ClearScene (); + + // not in server yet, no entities to redraw + if( cls.state != ca_active || !cl.validsequence ) + return; + + // make sure we have at least one valid update + if( !cl.frames[cl.parsecountmod].valid ) + return; + + // compute last interpolation amount + CL_UpdateFrameLerp (); + + // set client ideal pitch when mlook is disabled + CL_SetIdealPitch (); + + // clear the scene befor start new frame + if( clgame.drawFuncs.R_ClearScene != NULL ) + clgame.drawFuncs.R_ClearScene(); + + // link all the visible clients first + CL_LinkPlayers ( &cl.frames[cl.parsecountmod] ); + + // link all the entities that actually have update + CL_LinkPacketEntities ( &cl.frames[cl.parsecountmod] ); + + // link custom user temp entities + clgame.dllFuncs.pfnCreateEntities(); + + // evaluate temp entities + CL_TempEntUpdate (); + + // fire events (client and server) + CL_FireEvents (); + + // handle spectator camera movement + CL_MoveSpectatorCamera(); + + // perfomance test + CL_TestLights(); +} + +/* +========================================================================== + +SOUND ENGINE IMPLEMENTATION + +========================================================================== +*/ +qboolean CL_GetEntitySpatialization( channel_t *ch ) +{ + cl_entity_t *ent; + qboolean valid_origin; + + if( ch->entnum == 0 ) + { + ch->staticsound = true; + return true; // static sound + } + + if(( ch->entnum - 1 ) == cl.playernum ) + { + VectorCopy( RI.vieworg, ch->origin ); + return true; + } + + valid_origin = VectorIsNull( ch->origin ) ? false : true; + ent = CL_GetEntityByIndex( ch->entnum ); + + // entity is not present on the client but has valid origin + if( !ent || !ent->index || ent->curstate.messagenum == 0 ) + return valid_origin; + +#if 0 + // uncomment this if you want enable additional check by PVS + if( ent->curstate.messagenum != cl.parsecount ) + return valid_origin; +#endif + ch->movetype = ent->curstate.movetype; + + // setup origin + VectorAverage( ent->curstate.mins, ent->curstate.maxs, ch->origin ); + VectorAdd( ch->origin, ent->curstate.origin, ch->origin ); + + // setup mins\maxs + VectorAdd( ent->curstate.mins, ent->curstate.origin, ch->absmin ); + VectorAdd( ent->curstate.maxs, ent->curstate.origin, ch->absmax ); + + // setup radius + if( ent->model != NULL && ent->model->radius ) ch->radius = ent->model->radius; + else ch->radius = RadiusFromBounds( ent->curstate.mins, ent->curstate.maxs ); + + return true; +} + +qboolean CL_GetMovieSpatialization( rawchan_t *ch ) +{ + // UNDONE + return false; +} + +void CL_ExtraUpdate( void ) +{ + clgame.dllFuncs.IN_Accumulate(); + S_ExtraUpdate(); +} \ No newline at end of file diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c new file mode 100644 index 00000000..b3f49ed9 --- /dev/null +++ b/engine/client/cl_game.c @@ -0,0 +1,3900 @@ +/* +cl_game.c - client dll interaction +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "triangleapi.h" +#include "r_efx.h" +#include "demo_api.h" +#include "ivoicetweak.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "input.h" +#include "shake.h" +#include "sprite.h" +#include "gl_local.h" +#include "library.h" +#include "vgui_draw.h" +#include "sound.h" // SND_STOP_LOOPING + +#define MAX_LINELENGTH 80 +#define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels) +#define TEXT_MSGNAME "TextMessage%i" + +char cl_textbuffer[MAX_TEXTCHANNELS][2048]; +client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS]; + +static dllfunc_t cdll_exports[] = +{ +{ "Initialize", (void **)&clgame.dllFuncs.pfnInitialize }, +{ "HUD_VidInit", (void **)&clgame.dllFuncs.pfnVidInit }, +{ "HUD_Init", (void **)&clgame.dllFuncs.pfnInit }, +{ "HUD_Shutdown", (void **)&clgame.dllFuncs.pfnShutdown }, +{ "HUD_Redraw", (void **)&clgame.dllFuncs.pfnRedraw }, +{ "HUD_UpdateClientData", (void **)&clgame.dllFuncs.pfnUpdateClientData }, +{ "HUD_Reset", (void **)&clgame.dllFuncs.pfnReset }, +{ "HUD_PlayerMove", (void **)&clgame.dllFuncs.pfnPlayerMove }, +{ "HUD_PlayerMoveInit", (void **)&clgame.dllFuncs.pfnPlayerMoveInit }, +{ "HUD_PlayerMoveTexture", (void **)&clgame.dllFuncs.pfnPlayerMoveTexture }, +{ "HUD_ConnectionlessPacket", (void **)&clgame.dllFuncs.pfnConnectionlessPacket }, +{ "HUD_GetHullBounds", (void **)&clgame.dllFuncs.pfnGetHullBounds }, +{ "HUD_Frame", (void **)&clgame.dllFuncs.pfnFrame }, +{ "HUD_PostRunCmd", (void **)&clgame.dllFuncs.pfnPostRunCmd }, +{ "HUD_Key_Event", (void **)&clgame.dllFuncs.pfnKey_Event }, +{ "HUD_AddEntity", (void **)&clgame.dllFuncs.pfnAddEntity }, +{ "HUD_CreateEntities", (void **)&clgame.dllFuncs.pfnCreateEntities }, +{ "HUD_StudioEvent", (void **)&clgame.dllFuncs.pfnStudioEvent }, +{ "HUD_TxferLocalOverrides", (void **)&clgame.dllFuncs.pfnTxferLocalOverrides }, +{ "HUD_ProcessPlayerState", (void **)&clgame.dllFuncs.pfnProcessPlayerState }, +{ "HUD_TxferPredictionData", (void **)&clgame.dllFuncs.pfnTxferPredictionData }, +{ "HUD_TempEntUpdate", (void **)&clgame.dllFuncs.pfnTempEntUpdate }, +{ "HUD_DrawNormalTriangles", (void **)&clgame.dllFuncs.pfnDrawNormalTriangles }, +{ "HUD_DrawTransparentTriangles", (void **)&clgame.dllFuncs.pfnDrawTransparentTriangles }, +{ "HUD_GetUserEntity", (void **)&clgame.dllFuncs.pfnGetUserEntity }, +{ "Demo_ReadBuffer", (void **)&clgame.dllFuncs.pfnDemo_ReadBuffer }, +{ "CAM_Think", (void **)&clgame.dllFuncs.CAM_Think }, +{ "CL_IsThirdPerson", (void **)&clgame.dllFuncs.CL_IsThirdPerson }, +{ "CL_CameraOffset", (void **)&clgame.dllFuncs.CL_CameraOffset }, // unused callback. Now camera code is completely moved to the user area +{ "CL_CreateMove", (void **)&clgame.dllFuncs.CL_CreateMove }, +{ "IN_ActivateMouse", (void **)&clgame.dllFuncs.IN_ActivateMouse }, +{ "IN_DeactivateMouse", (void **)&clgame.dllFuncs.IN_DeactivateMouse }, +{ "IN_MouseEvent", (void **)&clgame.dllFuncs.IN_MouseEvent }, +{ "IN_Accumulate", (void **)&clgame.dllFuncs.IN_Accumulate }, +{ "IN_ClearStates", (void **)&clgame.dllFuncs.IN_ClearStates }, +{ "V_CalcRefdef", (void **)&clgame.dllFuncs.pfnCalcRefdef }, +{ "KB_Find", (void **)&clgame.dllFuncs.KB_Find }, +{ NULL, NULL } +}; + +// optional exports +static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher +{ +{ "HUD_GetStudioModelInterface", (void **)&clgame.dllFuncs.pfnGetStudioModelInterface }, +{ "HUD_DirectorMessage", (void **)&clgame.dllFuncs.pfnDirectorMessage }, +{ "HUD_VoiceStatus", (void **)&clgame.dllFuncs.pfnVoiceStatus }, +{ "HUD_ChatInputPosition", (void **)&clgame.dllFuncs.pfnChatInputPosition }, +{ "HUD_GetRenderInterface", (void **)&clgame.dllFuncs.pfnGetRenderInterface }, // Xash3D ext +{ "HUD_ClipMoveToEntity", (void **)&clgame.dllFuncs.pfnClipMoveToEntity }, // Xash3D ext +{ NULL, NULL } +}; + +static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ); + +/* +==================== +CL_GetEntityByIndex + +Render callback for studio models +==================== +*/ +cl_entity_t *CL_GetEntityByIndex( int index ) +{ + if( !clgame.entities ) // not in game yet + return NULL; + + if( index < 0 || index >= clgame.maxEntities ) + return NULL; + + if( index == 0 ) + return clgame.entities; + + return CL_EDICT_NUM( index ); +} + +/* +================ +CL_ModelHandle + +get model handle by index +================ +*/ +model_t *CL_ModelHandle( int modelindex ) +{ + if( modelindex < 0 || modelindex >= MAX_MODELS ) + return NULL; + return cl.models[modelindex]; +} + +/* +==================== +CL_IsThirdPerson + +returns true if thirdperson is enabled +==================== +*/ +qboolean CL_IsThirdPerson( void ) +{ + cl.local.thirdperson = clgame.dllFuncs.CL_IsThirdPerson(); + + if( cl.local.thirdperson ) + return true; + return false; +} + +/* +==================== +CL_GetPlayerInfo + +get player info by render request +==================== +*/ +player_info_t *CL_GetPlayerInfo( int playerIndex ) +{ + if( playerIndex < 0 || playerIndex >= cl.maxclients ) + return NULL; + + return &cl.players[playerIndex]; +} + +/* +==================== +CL_CreatePlaylist + +Create a default valve playlist +==================== +*/ +void CL_CreatePlaylist( const char *filename ) +{ + file_t *f; + + f = FS_Open( filename, "w", false ); + if( !f ) return; + + // make standard cdaudio playlist + FS_Print( f, "blank\n" ); // #1 + FS_Print( f, "Half-Life01.mp3\n" ); // #2 + FS_Print( f, "Prospero01.mp3\n" ); // #3 + FS_Print( f, "Half-Life12.mp3\n" ); // #4 + FS_Print( f, "Half-Life07.mp3\n" ); // #5 + FS_Print( f, "Half-Life10.mp3\n" ); // #6 + FS_Print( f, "Suspense01.mp3\n" ); // #7 + FS_Print( f, "Suspense03.mp3\n" ); // #8 + FS_Print( f, "Half-Life09.mp3\n" ); // #9 + FS_Print( f, "Half-Life02.mp3\n" ); // #10 + FS_Print( f, "Half-Life13.mp3\n" ); // #11 + FS_Print( f, "Half-Life04.mp3\n" ); // #12 + FS_Print( f, "Half-Life15.mp3\n" ); // #13 + FS_Print( f, "Half-Life14.mp3\n" ); // #14 + FS_Print( f, "Half-Life16.mp3\n" ); // #15 + FS_Print( f, "Suspense02.mp3\n" ); // #16 + FS_Print( f, "Half-Life03.mp3\n" ); // #17 + FS_Print( f, "Half-Life08.mp3\n" ); // #18 + FS_Print( f, "Prospero02.mp3\n" ); // #19 + FS_Print( f, "Half-Life05.mp3\n" ); // #20 + FS_Print( f, "Prospero04.mp3\n" ); // #21 + FS_Print( f, "Half-Life11.mp3\n" ); // #22 + FS_Print( f, "Half-Life06.mp3\n" ); // #23 + FS_Print( f, "Prospero03.mp3\n" ); // #24 + FS_Print( f, "Half-Life17.mp3\n" ); // #25 + FS_Print( f, "Prospero05.mp3\n" ); // #26 + FS_Print( f, "Suspense05.mp3\n" ); // #27 + FS_Print( f, "Suspense07.mp3\n" ); // #28 + FS_Close( f ); +} + +/* +==================== +CL_InitCDAudio + +Initialize CD playlist +==================== +*/ +void CL_InitCDAudio( const char *filename ) +{ + char *afile, *pfile; + string token; + int c = 0; + + if( !FS_FileExists( filename, false )) + { + // create a default playlist + CL_CreatePlaylist( filename ); + } + + afile = FS_LoadFile( filename, NULL, false ); + if( !afile ) return; + + pfile = afile; + + // format: trackname\n [num] + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_stricmp( token, "blank" )) token[0] = '\0'; + Q_strncpy( clgame.cdtracks[c], token, sizeof( clgame.cdtracks[0] )); + + if( ++c > MAX_CDTRACKS - 1 ) + { + MsgDev( D_WARN, "CD_Init: too many tracks %i in %s\n", filename, MAX_CDTRACKS ); + break; + } + } + + Mem_Free( afile ); +} + +/* +==================== +CL_PointContents + +Return contents for point +==================== +*/ +int CL_PointContents( const vec3_t p ) +{ + int cont = CL_TruePointContents( p ); + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +/* +============= +CL_AdjustXPos + +adjust text by x pos +============= +*/ +static int CL_AdjustXPos( float x, int width, int totalWidth ) +{ + int xPos; + + if( x == -1 ) + { + xPos = ( glState.width - width ) * 0.5f; + } + else + { + if ( x < 0 ) + xPos = (1.0f + x) * glState.width - totalWidth; // Alight right + else // align left + xPos = x * glState.width; + } + + if( xPos + width > glState.width ) + xPos = glState.width - width; + else if( xPos < 0 ) + xPos = 0; + + return xPos; +} + +/* +============= +CL_AdjustYPos + +adjust text by y pos +============= +*/ +static int CL_AdjustYPos( float y, int height ) +{ + int yPos; + + if( y == -1 ) // centered? + { + yPos = ( glState.height - height ) * 0.5f; + } + else + { + // Alight bottom? + if( y < 0 ) + yPos = (1.0f + y) * glState.height - height; // Alight bottom + else // align top + yPos = y * glState.height; + } + + if( yPos + height > glState.height ) + yPos = glState.height - height; + else if( yPos < 0 ) + yPos = 0; + + return yPos; +} + +/* +============= +CL_CenterPrint + +print centerscreen message +============= +*/ +void CL_CenterPrint( const char *text, float y ) +{ + int length = 0; + int width = 0; + char *s; + + if( !COM_CheckString( text )) + return; + + clgame.centerPrint.lines = 1; + clgame.centerPrint.totalWidth = 0; + clgame.centerPrint.time = cl.mtime[0]; // allow pause for centerprint + Q_strncpy( clgame.centerPrint.message, text, sizeof( clgame.centerPrint.message )); + s = clgame.centerPrint.message; + + // count the number of lines for centering + while( *s ) + { + if( *s == '\n' ) + { + clgame.centerPrint.lines++; + if( width > clgame.centerPrint.totalWidth ) + clgame.centerPrint.totalWidth = width; + width = 0; + } + else width += clgame.scrInfo.charWidths[*s]; + s++; + length++; + } + + clgame.centerPrint.totalHeight = ( clgame.centerPrint.lines * clgame.scrInfo.iCharHeight ); + clgame.centerPrint.y = CL_AdjustYPos( y, clgame.centerPrint.totalHeight ); +} + +/* +==================== +SPR_AdjustSize + +draw hudsprite routine +==================== +*/ +static void SPR_AdjustSize( float *x, float *y, float *w, float *h ) +{ + float xscale, yscale; + + if( !x && !y && !w && !h ) return; + + // scale for screen sizes + xscale = glState.width / (float)clgame.scrInfo.iWidth; + yscale = glState.height / (float)clgame.scrInfo.iHeight; + + if( x ) *x *= xscale; + if( y ) *y *= yscale; + if( w ) *w *= xscale; + if( h ) *h *= yscale; +} + +/* +==================== +PictAdjustSize + +draw hudsprite routine +==================== +*/ +void PicAdjustSize( float *x, float *y, float *w, float *h ) +{ + float xscale, yscale; + + if( !clgame.ds.adjust_size ) return; + if( !x && !y && !w && !h ) return; + + // scale for screen sizes + xscale = glState.width / (float)clgame.scrInfo.iWidth; + yscale = glState.height / (float)clgame.scrInfo.iHeight; + + if( x ) *x *= xscale; + if( y ) *y *= yscale; + if( w ) *w *= xscale; + if( h ) *h *= yscale; +} + +static qboolean SPR_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) +{ + float dudx, dvdy; + + // clip sub rect to sprite + if(( width == 0 ) || ( height == 0 )) + return false; + + if( *x + *width <= clgame.ds.scissor_x ) + return false; + if( *x >= clgame.ds.scissor_x + clgame.ds.scissor_width ) + return false; + if( *y + *height <= clgame.ds.scissor_y ) + return false; + if( *y >= clgame.ds.scissor_y + clgame.ds.scissor_height ) + return false; + + dudx = (*u1 - *u0) / *width; + dvdy = (*v1 - *v0) / *height; + + if( *x < clgame.ds.scissor_x ) + { + *u0 += (clgame.ds.scissor_x - *x) * dudx; + *width -= clgame.ds.scissor_x - *x; + *x = clgame.ds.scissor_x; + } + + if( *x + *width > clgame.ds.scissor_x + clgame.ds.scissor_width ) + { + *u1 -= (*x + *width - (clgame.ds.scissor_x + clgame.ds.scissor_width)) * dudx; + *width = clgame.ds.scissor_x + clgame.ds.scissor_width - *x; + } + + if( *y < clgame.ds.scissor_y ) + { + *v0 += (clgame.ds.scissor_y - *y) * dvdy; + *height -= clgame.ds.scissor_y - *y; + *y = clgame.ds.scissor_y; + } + + if( *y + *height > clgame.ds.scissor_y + clgame.ds.scissor_height ) + { + *v1 -= (*y + *height - (clgame.ds.scissor_y + clgame.ds.scissor_height)) * dvdy; + *height = clgame.ds.scissor_y + clgame.ds.scissor_height - *y; + } + + return true; +} + +/* +==================== +SPR_DrawGeneric + +draw hudsprite routine +==================== +*/ +static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc ) +{ + float s1, s2, t1, t2; + int texnum; + + if( width == -1 && height == -1 ) + { + int w, h; + + // assume we get sizes from image + R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite ); + + width = w; + height = h; + } + + if( prc ) + { + wrect_t rc; + + rc = *prc; + + // Sigh! some stupid modmakers set wrong rectangles in hud.txt + if( rc.left <= 0 || rc.left >= width ) rc.left = 0; + if( rc.top <= 0 || rc.top >= height ) rc.top = 0; + if( rc.right <= 0 || rc.right > width ) rc.right = width; + if( rc.bottom <= 0 || rc.bottom > height ) rc.bottom = height; + + // calc user-defined rectangle + s1 = (float)rc.left / width; + t1 = (float)rc.top / height; + s2 = (float)rc.right / width; + t2 = (float)rc.bottom / height; + width = rc.right - rc.left; + height = rc.bottom - rc.top; + } + else + { + s1 = t1 = 0.0f; + s2 = t2 = 1.0f; + } + + // pass scissor test if supposed + if( clgame.ds.scissor_test && !SPR_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + return; + + // scale for screen sizes + SPR_AdjustSize( &x, &y, &width, &height ); + texnum = R_GetSpriteTexture( clgame.ds.pSprite, frame ); + pglColor4ubv( clgame.ds.spriteColor ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, texnum ); +} + +/* +============= +CL_DrawCenterPrint + +called each frame +============= +*/ +void CL_DrawCenterPrint( void ) +{ + char *pText; + int i, j, x, y; + int width, lineLength; + byte *colorDefault, line[MAX_LINELENGTH]; + int charWidth, charHeight; + + if( !clgame.centerPrint.time ) + return; + + if(( cl.time - clgame.centerPrint.time ) >= scr_centertime->value ) + { + // time expired + clgame.centerPrint.time = 0.0f; + return; + } + + y = clgame.centerPrint.y; // start y + colorDefault = g_color_table[7]; + pText = clgame.centerPrint.message; + Con_DrawCharacterLen( 0, NULL, &charHeight ); + + for( i = 0; i < clgame.centerPrint.lines; i++ ) + { + lineLength = 0; + width = 0; + + while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH ) + { + byte c = *pText; + line[lineLength] = c; + Con_DrawCharacterLen( c, &charWidth, NULL ); + width += charWidth; + lineLength++; + pText++; + } + + if( lineLength == MAX_LINELENGTH ) + lineLength--; + + pText++; // Skip LineFeed + line[lineLength] = 0; + + x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth ); + + for( j = 0; j < lineLength; j++ ) + { + if( x >= 0 && y >= 0 && x <= glState.width ) + x += Con_DrawCharacter( x, y, line[j], colorDefault ); + } + y += charHeight; + } +} + +/* +============= +CL_DrawScreenFade + +fill screen with specfied color +can be modulated +============= +*/ +void CL_DrawScreenFade( void ) +{ + screenfade_t *sf = &clgame.fade; + int iFadeAlpha, testFlags; + + // keep pushing reset time out indefinitely + if( sf->fadeFlags & FFADE_STAYOUT ) + sf->fadeReset = cl.time + 0.1f; + + if( sf->fadeReset == 0.0f && sf->fadeEnd == 0.0f ) + return; // inactive + + // all done? + if(( cl.time > sf->fadeReset ) && ( cl.time > sf->fadeEnd )) + { + memset( &clgame.fade, 0, sizeof( clgame.fade )); + return; + } + + testFlags = (sf->fadeFlags & ~FFADE_MODULATE); + + // fading... + if( testFlags == FFADE_STAYOUT ) + { + iFadeAlpha = sf->fadealpha; + } + else + { + iFadeAlpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time ); + if( sf->fadeFlags & FFADE_OUT ) iFadeAlpha += sf->fadealpha; + iFadeAlpha = bound( 0, iFadeAlpha, sf->fadealpha ); + } + + pglColor4ub( sf->fader, sf->fadeg, sf->fadeb, iFadeAlpha ); + + if( sf->fadeFlags & FFADE_MODULATE ) + GL_SetRenderMode( kRenderTransAdd ); + else GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( 0, 0, glState.width, glState.height, 0, 0, 1, 1, tr.whiteTexture ); + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +==================== +CL_InitTitles + +parse all messages that declared in titles.txt +and hold them into permament memory pool +==================== +*/ +static void CL_InitTitles( const char *filename ) +{ + size_t fileSize; + byte *pMemFile; + int i; + + // initialize text messages (game_text) + for( i = 0; i < MAX_TEXTCHANNELS; i++ ) + { + cl_textmessage[i].pName = _copystring( clgame.mempool, va( TEXT_MSGNAME, i ), __FILE__, __LINE__ ); + cl_textmessage[i].pMessage = cl_textbuffer[i]; + } + + // clear out any old data that's sitting around. + if( clgame.titles ) Mem_Free( clgame.titles ); + + clgame.titles = NULL; + clgame.numTitles = 0; + + pMemFile = FS_LoadFile( filename, &fileSize, false ); + if( !pMemFile ) return; + + CL_TextMessageParse( pMemFile, fileSize ); + Mem_Free( pMemFile ); +} + +/* +==================== +CL_HudMessage + +Template to show hud messages +==================== +*/ +void CL_HudMessage( const char *pMessage ) +{ + if( !COM_CheckString( pMessage )) return; + CL_DispatchUserMessage( "HudText", Q_strlen( pMessage ), (void *)pMessage ); +} + +/* +==================== +CL_ParseTextMessage + +Parse TE_TEXTMESSAGE +==================== +*/ +void CL_ParseTextMessage( sizebuf_t *msg ) +{ + static int msgindex = 0; + client_textmessage_t *text; + int channel; + + // read channel ( 0 - auto) + channel = MSG_ReadByte( msg ); + + if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 )) + { + channel = msgindex; + msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1); + } + + // grab message channel + text = &cl_textmessage[channel]; + + text->x = (float)(MSG_ReadShort( msg ) / 8192.0f); + text->y = (float)(MSG_ReadShort( msg ) / 8192.0f); + text->effect = MSG_ReadByte( msg ); + text->r1 = MSG_ReadByte( msg ); + text->g1 = MSG_ReadByte( msg ); + text->b1 = MSG_ReadByte( msg ); + text->a1 = MSG_ReadByte( msg ); + text->r2 = MSG_ReadByte( msg ); + text->g2 = MSG_ReadByte( msg ); + text->b2 = MSG_ReadByte( msg ); + text->a2 = MSG_ReadByte( msg ); + text->fadein = (float)(MSG_ReadShort( msg ) / 256.0f ); + text->fadeout = (float)(MSG_ReadShort( msg ) / 256.0f ); + text->holdtime = (float)(MSG_ReadShort( msg ) / 256.0f ); + + if( text->effect == 2 ) + text->fxtime = (float)(MSG_ReadShort( msg ) / 256.0f ); + else text->fxtime = 0.0f; + + // to prevent grab too long messages + Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 ); + + CL_HudMessage( text->pName ); +} + +/* +================ +CL_ParseFinaleCutscene + +show display finale or cutscene message +================ +*/ +void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ) +{ + static int msgindex = 0; + client_textmessage_t *text; + int channel; + + cl.intermission = level; + + channel = msgindex; + msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1); + + // grab message channel + text = &cl_textmessage[channel]; + + // NOTE: svc_finale and svc_cutscene has a + // predefined settings like Quake-style + text->x = -1.0f; + text->y = 0.15f; + text->effect = 2; // scan out effect + text->r1 = 245; + text->g1 = 245; + text->b1 = 245; + text->a1 = 0; // unused + text->r2 = 0; + text->g2 = 0; + text->b2 = 0; + text->a2 = 0; + text->fadein = 0.15f; + text->fadeout = 0.0f; + text->holdtime = 99999.0f; + text->fxtime = 0.0f; + + // to prevent grab too long messages + Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 ); + + if( *text->pMessage == '\0' ) + return; // no real text + + CL_HudMessage( text->pName ); +} + +/* +==================== +CL_GetLocalPlayer + +Render callback for studio models +==================== +*/ +cl_entity_t *CL_GetLocalPlayer( void ) +{ + cl_entity_t *player; + + player = CL_EDICT_NUM( cl.playernum + 1 ); + Assert( player != NULL ); + + return player; +} + +/* +==================== +CL_GetMaxlients + +Render callback for studio models +==================== +*/ +int CL_GetMaxClients( void ) +{ + return cl.maxclients; +} + +/* +==================== +CL_SoundFromIndex + +return soundname from index +==================== +*/ +const char *CL_SoundFromIndex( int index ) +{ + sfx_t *sfx = NULL; + int hSound; + + // make sure what we in-bounds + index = bound( 0, index, MAX_SOUNDS ); + hSound = cl.sound_index[index]; + + if( !hSound ) + { + MsgDev( D_ERROR, "CL_SoundFromIndex: invalid sound index %i\n", index ); + return NULL; + } + + sfx = S_GetSfxByHandle( hSound ); + if( !sfx ) + { + MsgDev( D_ERROR, "CL_SoundFromIndex: bad sfx for index %i\n", index ); + return NULL; + } + + return sfx->name; +} + +/* +========= +SPR_EnableScissor + +========= +*/ +static void SPR_EnableScissor( int x, int y, int width, int height ) +{ + // check bounds + x = bound( 0, x, clgame.scrInfo.iWidth ); + y = bound( 0, y, clgame.scrInfo.iHeight ); + width = bound( 0, width, clgame.scrInfo.iWidth - x ); + height = bound( 0, height, clgame.scrInfo.iHeight - y ); + + clgame.ds.scissor_x = x; + clgame.ds.scissor_width = width; + clgame.ds.scissor_y = y; + clgame.ds.scissor_height = height; + clgame.ds.scissor_test = true; +} + +/* +========= +SPR_DisableScissor + +========= +*/ +static void SPR_DisableScissor( void ) +{ + clgame.ds.scissor_x = 0; + clgame.ds.scissor_width = 0; + clgame.ds.scissor_y = 0; + clgame.ds.scissor_height = 0; + clgame.ds.scissor_test = false; +} + +/* +==================== +CL_DrawCrosshair + +Render crosshair +==================== +*/ +void CL_DrawCrosshair( void ) +{ + int x, y, width, height; + + if( !clgame.ds.pCrosshair || !cl_crosshair->value ) + return; + + // any camera on or client is died + if( cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 )) + return; + + // get crosshair dimension + width = clgame.ds.rcCrosshair.right - clgame.ds.rcCrosshair.left; + height = clgame.ds.rcCrosshair.bottom - clgame.ds.rcCrosshair.top; + + x = clgame.viewport[0] + ( clgame.viewport[2] >> 1 ); + y = clgame.viewport[1] + ( clgame.viewport[3] >> 1 ); + + // g-cont - cl.crosshairangle is the autoaim angle. + // if we're not using autoaim, just draw in the middle of the screen + if( !VectorIsNull( cl.crosshairangle )) + { + vec3_t angles; + vec3_t forward; + vec3_t point, screen; + + VectorAdd( RI.viewangles, cl.crosshairangle, angles ); + AngleVectors( angles, forward, NULL, NULL ); + VectorAdd( RI.vieworg, forward, point ); + R_WorldToScreen( point, screen ); + + x += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f; + y += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f; + } + + // move at center the screen + x -= 0.5f * width; + y -= 0.5f * height; + + clgame.ds.pSprite = clgame.ds.pCrosshair; + *(int *)clgame.ds.spriteColor = *(int *)clgame.ds.rgbaCrosshair; + + SPR_EnableScissor( x, y, width, height ); + pfnSPR_DrawHoles( 0, x, y, &clgame.ds.rcCrosshair ); + SPR_DisableScissor(); +} + +/* +============= +CL_DrawLoading + +draw loading progress bar +============= +*/ +static void CL_DrawLoading( float percent ) +{ + int x, y, width, height, right; + float xscale, yscale, step, s2; + + R_GetTextureParms( &width, &height, cls.loadingBar ); + x = ( clgame.scrInfo.iWidth - width ) >> 1; + y = ( clgame.scrInfo.iHeight - height) >> 1; + + xscale = glState.width / (float)clgame.scrInfo.iWidth; + yscale = glState.height / (float)clgame.scrInfo.iHeight; + + x *= xscale; + y *= yscale; + width *= xscale; + height *= yscale; + + if( cl_allow_levelshots->value ) + { + pglColor4ub( 128, 128, 128, 255 ); + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar ); + + step = (float)width / 100.0f; + right = (int)ceil( percent * step ); + s2 = (float)right / width; + width = right; + + pglColor4ub( 208, 152, 0, 255 ); + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, 0, 0, s2, 1, cls.loadingBar ); + pglColor4ub( 255, 255, 255, 255 ); + } + else + { + pglColor4ub( 255, 255, 255, 255 ); + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar ); + } +} + +/* +============= +CL_DrawPause + +draw pause sign +============= +*/ +static void CL_DrawPause( void ) +{ + int x, y, width, height; + float xscale, yscale; + + R_GetTextureParms( &width, &height, cls.pauseIcon ); + x = ( clgame.scrInfo.iWidth - width ) >> 1; + y = ( clgame.scrInfo.iHeight - height) >> 1; + + xscale = glState.width / (float)clgame.scrInfo.iWidth; + yscale = glState.height / (float)clgame.scrInfo.iHeight; + + x *= xscale; + y *= yscale; + width *= xscale; + height *= yscale; + + pglColor4ub( 255, 255, 255, 255 ); + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.pauseIcon ); +} + +void CL_DrawHUD( int state ) +{ + if( state == CL_ACTIVE && !cl.video_prepped ) + state = CL_LOADING; + + if( state == CL_ACTIVE && cl.paused ) + state = CL_PAUSED; + + switch( state ) + { + case CL_ACTIVE: + if( !cl.intermission ) + CL_DrawScreenFade (); + CL_DrawCrosshair (); + CL_DrawCenterPrint (); + clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission ); + if( cl.intermission ) CL_DrawScreenFade (); + break; + case CL_PAUSED: + CL_DrawScreenFade (); + CL_DrawCrosshair (); + CL_DrawCenterPrint (); + clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission ); + CL_DrawPause(); + break; + case CL_LOADING: + CL_DrawLoading( scr_loading->value ); + break; + case CL_CHANGELEVEL: + if( cls.draw_changelevel ) + { + CL_DrawLoading( 100.0f ); + cls.draw_changelevel = false; + } + break; + } +} + +void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ) +{ + int i; + + if( !pszName || !*pszName ) + Host_Error( "CL_LinkUserMessage: bad message name\n" ); + + if( svc_num <= svc_lastmsg ) + Host_Error( "CL_LinkUserMessage: tried to hook a system message \"%s\"\n", svc_strings[svc_num] ); + + // see if already hooked + for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) + { + // NOTE: no check for DispatchFunc, check only name + if( !Q_strcmp( clgame.msg[i].name, pszName )) + { + clgame.msg[i].number = svc_num; + clgame.msg[i].size = iSize; + return; + } + } + + if( i == MAX_USER_MESSAGES ) + { + Host_Error( "CL_LinkUserMessage: MAX_USER_MESSAGES hit!\n" ); + return; + } + + // register new message without DispatchFunc, so we should parse it properly + Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name )); + clgame.msg[i].number = svc_num; + clgame.msg[i].size = iSize; +} + +void CL_FreeEntity( cl_entity_t *pEdict ) +{ + Assert( pEdict != NULL ); + R_RemoveEfrags( pEdict ); + CL_KillDeadBeams( pEdict ); +} + +void CL_ClearWorld( void ) +{ + cl_entity_t *world; + + world = clgame.entities; + world->curstate.modelindex = 1; // world model + world->curstate.solid = SOLID_BSP; + world->curstate.movetype = MOVETYPE_PUSH; + world->model = cl.worldmodel; + world->index = 0; + + clgame.ds.cullMode = GL_FRONT; + clgame.numStatics = 0; +} + +void CL_InitEdicts( void ) +{ + Assert( clgame.entities == NULL ); + + if( !clgame.mempool ) return; // Host_Error without client + + CL_UPDATE_BACKUP = ( cl.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; + cls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES; + cls.packet_entities = Z_Realloc( cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities ); + clgame.entities = Mem_Alloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities ); + clgame.static_entities = Mem_Alloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES ); + clgame.numStatics = 0; + + if(( clgame.maxRemapInfos - 1 ) != clgame.maxEntities ) + { + CL_ClearAllRemaps (); // purge old remap info + clgame.maxRemapInfos = clgame.maxEntities + 1; + clgame.remap_info = (remap_info_t **)Mem_Alloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos ); + } + + if( clgame.drawFuncs.R_ProcessEntData != NULL ) + { + // let the client.dll free custom data + clgame.drawFuncs.R_ProcessEntData( true ); + } +} + +void CL_FreeEdicts( void ) +{ + if( clgame.drawFuncs.R_ProcessEntData != NULL ) + { + // let the client.dll free custom data + clgame.drawFuncs.R_ProcessEntData( false ); + } + + if( clgame.entities ) + Mem_Free( clgame.entities ); + clgame.entities = NULL; + + if( clgame.static_entities ) + Mem_Free( clgame.static_entities ); + clgame.static_entities = NULL; + + if( cls.packet_entities ) + Z_Free( cls.packet_entities ); + + cls.packet_entities = NULL; + cls.num_client_entities = 0; + cls.next_client_entities = 0; + clgame.numStatics = 0; +} + +void CL_ClearEdicts( void ) +{ + if( clgame.entities != NULL ) + return; + + // in case we stopped with error + clgame.maxEntities = 2; + CL_InitEdicts(); +} + +/* +================== +CL_ClearSpriteTextures + +free studio cache on change level +================== +*/ +void CL_ClearSpriteTextures( void ) +{ + int i; + + for( i = 1; i < MAX_CLIENT_SPRITES; i++ ) + clgame.sprites[i].needload = NL_UNREFERENCED; +} + +/* +============= +CL_LoadHudSprite + +upload sprite frames +============= +*/ +static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags ) +{ + byte *buf; + size_t size; + qboolean loaded; + + Assert( m_pSprite != NULL ); + + Q_strncpy( m_pSprite->name, szSpriteName, sizeof( m_pSprite->name )); + + // it's hud sprite, make difference names to prevent free shared textures + if( type == SPR_CLIENT || type == SPR_HUDSPRITE ) + SetBits( m_pSprite->flags, MODEL_CLIENT ); + m_pSprite->numtexinfo = texFlags; // store texFlags into numtexinfo + + if( FS_FileSize( szSpriteName, false ) == -1 ) + { + if( cls.state != ca_active && cl.maxclients > 1 ) + { + // trying to download sprite from server + CL_AddClientResource( szSpriteName, t_model ); + m_pSprite->needload = NL_NEEDS_LOADED; + return true; + } + else + { + Mod_UnloadSpriteModel( m_pSprite ); + return false; + } + } + + buf = FS_LoadFile( szSpriteName, &size, false ); + ASSERT( buf != NULL ); + + if( type == SPR_MAPSPRITE ) + Mod_LoadMapSprite( m_pSprite, buf, size, &loaded ); + else Mod_LoadSpriteModel( m_pSprite, buf, &loaded, texFlags ); + + Mem_Free( buf ); + + if( !loaded ) + { + Mod_UnloadSpriteModel( m_pSprite ); + return false; + } + + m_pSprite->needload = NL_PRESENT; + + return true; +} + +/* +============= +CL_LoadSpriteModel + +some sprite models is exist only at client: HUD sprites, +tent sprites or overview images +============= +*/ +static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFlags ) +{ + char name[MAX_QPATH]; + model_t *mod; + int i; + + if( !COM_CheckString( filename )) + { + MsgDev( D_ERROR, "CL_LoadSpriteModel: bad name!\n" ); + return NULL; + } + + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + // slot 0 isn't used + for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + { + if( !Q_stricmp( mod->name, name )) + { + if( mod->needload == NL_NEEDS_LOADED ) + { + if( CL_LoadHudSprite( name, mod, type, texFlags )) + return mod; + } + + // prolonge registration + mod->needload = NL_PRESENT; + return mod; + } + } + + // find a free model slot spot + for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + if( !mod->name[0] ) break; // this is a valid spot + + if( i == MAX_CLIENT_SPRITES ) + { + Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES ); + return NULL; + } + + // load new map sprite + if( CL_LoadHudSprite( name, mod, type, texFlags )) + return mod; + return NULL; +} + +/* +============= +CL_LoadClientSprite + +load sprites for temp ents +============= +*/ +model_t *CL_LoadClientSprite( const char *filename ) +{ + return CL_LoadSpriteModel( filename, SPR_CLIENT, 0 ); +} + +/* +=============================================================================== + CGame Builtin Functions + +=============================================================================== +*/ +/* +========= +pfnSPR_LoadExt + +========= +*/ +HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ) +{ + model_t *spr; + + if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL ) + return 0; + + return (spr - clgame.sprites); // return index +} + +/* +========= +pfnSPR_Load + +========= +*/ +HSPRITE pfnSPR_Load( const char *szPicName ) +{ + model_t *spr; + + if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL ) + return 0; + + return (spr - clgame.sprites); // return index +} + +/* +============= +CL_GetSpritePointer + +============= +*/ +const model_t *CL_GetSpritePointer( HSPRITE hSprite ) +{ + model_t *mod; + + if( hSprite <= 0 || hSprite >= MAX_CLIENT_SPRITES ) + return NULL; // bad image + mod = &clgame.sprites[hSprite]; + + if( mod->needload == NL_NEEDS_LOADED ) + { + int type = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE; + + if( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo )) + return mod; + } + + if( mod->mempool ) + { + mod->needload = NL_PRESENT; + return mod; + } + + return NULL; +} + +/* +========= +pfnSPR_Frames + +========= +*/ +static int pfnSPR_Frames( HSPRITE hPic ) +{ + int numFrames; + + R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic )); + + return numFrames; +} + +/* +========= +pfnSPR_Height + +========= +*/ +static int pfnSPR_Height( HSPRITE hPic, int frame ) +{ + int sprHeight; + + R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic )); + + return sprHeight; +} + +/* +========= +pfnSPR_Width + +========= +*/ +static int pfnSPR_Width( HSPRITE hPic, int frame ) +{ + int sprWidth; + + R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic )); + + return sprWidth; +} + +/* +========= +pfnSPR_Set + +========= +*/ +static void pfnSPR_Set( HSPRITE hPic, int r, int g, int b ) +{ + clgame.ds.pSprite = CL_GetSpritePointer( hPic ); + clgame.ds.spriteColor[0] = bound( 0, r, 255 ); + clgame.ds.spriteColor[1] = bound( 0, g, 255 ); + clgame.ds.spriteColor[2] = bound( 0, b, 255 ); + clgame.ds.spriteColor[3] = 255; +} + +/* +========= +pfnSPR_Draw + +========= +*/ +static void pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc ) +{ + pglDisable( GL_BLEND ); + + SPR_DrawGeneric( frame, x, y, -1, -1, prc ); +} + +/* +========= +pfnSPR_DrawHoles + +========= +*/ +static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ) +{ + pglEnable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglEnable( GL_BLEND ); + + SPR_DrawGeneric( frame, x, y, -1, -1, prc ); + + pglDisable( GL_ALPHA_TEST ); + pglDisable( GL_BLEND ); +} + +/* +========= +pfnSPR_DrawAdditive + +========= +*/ +static void pfnSPR_DrawAdditive( int frame, int x, int y, const wrect_t *prc ) +{ + pglEnable( GL_BLEND ); + pglBlendFunc( GL_ONE, GL_ONE ); + + SPR_DrawGeneric( frame, x, y, -1, -1, prc ); + + pglDisable( GL_BLEND ); +} + +/* +========= +pfnSPR_GetList + +for parsing half-life scripts - hud.txt etc +========= +*/ +static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount ) +{ + client_sprite_t *pList; + int index, numSprites = 0; + char *afile, *pfile; + string token; + byte *pool; + + if( piCount ) *piCount = 0; + + if( !clgame.itemspath[0] ) // typically it's sprites\*.txt + COM_ExtractFilePath( psz, clgame.itemspath ); + + afile = FS_LoadFile( psz, NULL, false ); + if( !afile ) return NULL; + + pfile = afile; + pfile = COM_ParseFile( pfile, token ); + numSprites = Q_atoi( token ); + + if( !cl.video_prepped ) pool = cls.mempool; // static memory + else pool = com_studiocache; // temporary + + // name, res, pic, x, y, w, h + // NOTE: we must use com_studiocache because it will be purge on next restart or change map + pList = Mem_Alloc( pool, sizeof( client_sprite_t ) * numSprites ); + + for( index = 0; index < numSprites; index++ ) + { + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) + break; + + Q_strncpy( pList[index].szName, token, sizeof( pList[index].szName )); + + // read resolution + pfile = COM_ParseFile( pfile, token ); + pList[index].iRes = Q_atoi( token ); + + // read spritename + pfile = COM_ParseFile( pfile, token ); + Q_strncpy( pList[index].szSprite, token, sizeof( pList[index].szSprite )); + + // parse rectangle + pfile = COM_ParseFile( pfile, token ); + pList[index].rc.left = Q_atoi( token ); + + pfile = COM_ParseFile( pfile, token ); + pList[index].rc.top = Q_atoi( token ); + + pfile = COM_ParseFile( pfile, token ); + pList[index].rc.right = pList[index].rc.left + Q_atoi( token ); + + pfile = COM_ParseFile( pfile, token ); + pList[index].rc.bottom = pList[index].rc.top + Q_atoi( token ); + + if( piCount ) (*piCount)++; + } + + if( index < numSprites ) + MsgDev( D_WARN, "SPR_GetList: unexpected end of %s (%i should be %i)\n", psz, numSprites, index ); + + Mem_Free( afile ); + + return pList; +} + +/* +============= +CL_FillRGBA + +============= +*/ +void CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a ) +{ + r = bound( 0, r, 255 ); + g = bound( 0, g, 255 ); + b = bound( 0, b, 255 ); + a = bound( 0, a, 255 ); + + SPR_AdjustSize( (float *)&x, (float *)&y, (float *)&w, (float *)&h ); + + pglDisable( GL_TEXTURE_2D ); + pglEnable( GL_BLEND ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f ); + + pglBegin( GL_QUADS ); + pglVertex2f( x, y ); + pglVertex2f( x + w, y ); + pglVertex2f( x + w, y + h ); + pglVertex2f( x, y + h ); + pglEnd (); + + pglColor3f( 1.0f, 1.0f, 1.0f ); + pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_BLEND ); +} + +/* +============= +pfnGetScreenInfo + +get actual screen info +============= +*/ +static int pfnGetScreenInfo( SCREENINFO *pscrinfo ) +{ + // setup screen info + clgame.scrInfo.iSize = sizeof( clgame.scrInfo ); + clgame.scrInfo.iFlags = SCRINFO_SCREENFLASH; + + if( Cvar_VariableInteger( "hud_scale" )) + { + if( glState.width < 640 ) + { + // virtual screen space 320x200 + clgame.scrInfo.iWidth = 320; + clgame.scrInfo.iHeight = 200; + } + else + { + // virtual screen space 640x480 + clgame.scrInfo.iWidth = 640; + clgame.scrInfo.iHeight = 480; + } + clgame.scrInfo.iFlags |= SCRINFO_STRETCHED; + } + else + { + clgame.scrInfo.iWidth = glState.width; + clgame.scrInfo.iHeight = glState.height; + clgame.scrInfo.iFlags &= ~SCRINFO_STRETCHED; + } + + if( !pscrinfo ) return 0; + + if( pscrinfo->iSize != clgame.scrInfo.iSize ) + clgame.scrInfo.iSize = pscrinfo->iSize; + + // copy screeninfo out + memcpy( pscrinfo, &clgame.scrInfo, clgame.scrInfo.iSize ); + + return 1; +} + +/* +============= +pfnSetCrosshair + +setup crosshair +============= +*/ +static void pfnSetCrosshair( HSPRITE hspr, wrect_t rc, int r, int g, int b ) +{ + clgame.ds.rgbaCrosshair[0] = (byte)r; + clgame.ds.rgbaCrosshair[1] = (byte)g; + clgame.ds.rgbaCrosshair[2] = (byte)b; + clgame.ds.rgbaCrosshair[3] = (byte)0xFF; + clgame.ds.pCrosshair = CL_GetSpritePointer( hspr ); + clgame.ds.rcCrosshair = rc; +} + +/* +============= +pfnHookUserMsg + +============= +*/ +static int pfnHookUserMsg( const char *pszName, pfnUserMsgHook pfn ) +{ + int i; + + // ignore blank names or invalid callbacks + if( !pszName || !*pszName || !pfn ) + return 0; + + for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ ) + { + // see if already hooked + if( !Q_strcmp( clgame.msg[i].name, pszName )) + return 1; + } + + if( i == MAX_USER_MESSAGES ) + { + Host_Error( "HookUserMsg: MAX_USER_MESSAGES hit!\n" ); + return 0; + } + + // hook new message + Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name )); + clgame.msg[i].func = pfn; + + return 1; +} + +/* +============= +pfnServerCmd + +============= +*/ +static int pfnServerCmd( const char *szCmdString ) +{ + string buf; + + if( !szCmdString || !szCmdString[0] ) + return 0; + + // just like the client typed "cmd xxxxx" at the console + Q_snprintf( buf, sizeof( buf ) - 1, "cmd %s\n", szCmdString ); + Cbuf_AddText( buf ); + + return 1; +} + +/* +============= +pfnClientCmd + +============= +*/ +static int pfnClientCmd( const char *szCmdString ) +{ + if( !szCmdString || !szCmdString[0] ) + return 0; + + Cbuf_AddText( szCmdString ); + Cbuf_AddText( "\n" ); + return 1; +} + +/* +============= +pfnGetPlayerInfo + +============= +*/ +static void pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo ) +{ + player_info_t *player; + + ent_num -= 1; // player list if offset by 1 from ents + + if( ent_num >= cl.maxclients || ent_num < 0 || !cl.players[ent_num].name[0] ) + { + pinfo->name = NULL; + pinfo->thisplayer = false; + return; + } + + player = &cl.players[ent_num]; + pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false; + pinfo->name = player->name; + pinfo->model = player->model; + pinfo->spectator = player->spectator; + pinfo->ping = player->ping; + pinfo->packetloss = player->packet_loss; + pinfo->topcolor = player->topcolor; + pinfo->bottomcolor = player->bottomcolor; +} + +/* +============= +pfnPlaySoundByName + +============= +*/ +static void pfnPlaySoundByName( const char *szSound, float volume ) +{ + int hSound = S_RegisterSound( szSound ); + S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING ); +} + +/* +============= +pfnPlaySoundByIndex + +============= +*/ +static void pfnPlaySoundByIndex( int iSound, float volume ) +{ + int hSound; + + // make sure what we in-bounds + iSound = bound( 0, iSound, MAX_SOUNDS ); + hSound = cl.sound_index[iSound]; + + if( !hSound ) + { + MsgDev( D_ERROR, "CL_PlaySoundByIndex: invalid sound handle %i\n", iSound ); + return; + } + S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING ); +} + +/* +============= +pfnTextMessageGet + +returns specified message from titles.txt +============= +*/ +client_textmessage_t *CL_TextMessageGet( const char *pName ) +{ + int i; + + // first check internal messages + for( i = 0; i < MAX_TEXTCHANNELS; i++ ) + { + if( !Q_strcmp( pName, va( TEXT_MSGNAME, i ))) + return cl_textmessage + i; + } + + // find desired message + for( i = 0; i < clgame.numTitles; i++ ) + { + if( !Q_stricmp( pName, clgame.titles[i].pName )) + return clgame.titles + i; + } + return NULL; // found nothing +} + +/* +============= +pfnDrawCharacter + +returns drawed chachter width (in real screen pixels) +============= +*/ +static int pfnDrawCharacter( int x, int y, int number, int r, int g, int b ) +{ + if( !cls.creditsFont.valid ) + return 0; + + number &= 255; + + if( number < 32 ) return 0; + if( y < -clgame.scrInfo.iCharHeight ) + return 0; + + clgame.ds.adjust_size = true; + pfnPIC_Set( cls.creditsFont.hFontTexture, r, g, b, 255 ); + pfnPIC_DrawAdditive( x, y, -1, -1, &cls.creditsFont.fontRc[number] ); + clgame.ds.adjust_size = false; + + return clgame.scrInfo.charWidths[number]; +} + +/* +============= +pfnDrawConsoleString + +drawing string like a console string +============= +*/ +int pfnDrawConsoleString( int x, int y, char *string ) +{ + int drawLen; + + if( !string || !*string ) return 0; // silent ignore + Con_SetFont( con_fontsize->value ); + + clgame.ds.adjust_size = true; + drawLen = Con_DrawString( x, y, string, clgame.ds.textColor ); + MakeRGBA( clgame.ds.textColor, 255, 255, 255, 255 ); + clgame.ds.adjust_size = false; + + Con_RestoreFont(); + + return (x + drawLen); // exclude color prexfixes +} + +/* +============= +pfnDrawSetTextColor + +set color for anything +============= +*/ +void pfnDrawSetTextColor( float r, float g, float b ) +{ + // bound color and convert to byte + clgame.ds.textColor[0] = (byte)bound( 0, r * 255, 255 ); + clgame.ds.textColor[1] = (byte)bound( 0, g * 255, 255 ); + clgame.ds.textColor[2] = (byte)bound( 0, b * 255, 255 ); + clgame.ds.textColor[3] = (byte)0xFF; +} + +/* +============= +pfnDrawConsoleStringLen + +compute string length in screen pixels +============= +*/ +void pfnDrawConsoleStringLen( const char *pText, int *length, int *height ) +{ + Con_SetFont( con_fontsize->value ); + Con_DrawStringLen( pText, length, height ); + Con_RestoreFont(); +} + +/* +============= +pfnConsolePrint + +prints directly into console (can skip notify) +============= +*/ +static void pfnConsolePrint( const char *string ) +{ + Con_Printf( "%s", string ); +} + +/* +============= +pfnCenterPrint + +holds and fade message at center of screen +like trigger_multiple message in q1 +============= +*/ +static void pfnCenterPrint( const char *string ) +{ + CL_CenterPrint( string, 0.25f ); +} + +/* +========= +GetWindowCenterX + +========= +*/ +static int pfnGetWindowCenterX( void ) +{ + return host.window_center_x; +} + +/* +========= +GetWindowCenterY + +========= +*/ +static int pfnGetWindowCenterY( void ) +{ + return host.window_center_y; +} + +/* +============= +pfnGetViewAngles + +return interpolated angles from previous frame +============= +*/ +static void pfnGetViewAngles( float *angles ) +{ + if( angles ) VectorCopy( cl.viewangles, angles ); +} + +/* +============= +pfnSetViewAngles + +return interpolated angles from previous frame +============= +*/ +static void pfnSetViewAngles( float *angles ) +{ + if( angles ) VectorCopy( angles, cl.viewangles ); +} + +/* +============= +pfnPhysInfo_ValueForKey + +============= +*/ +static const char* pfnPhysInfo_ValueForKey( const char *key ) +{ + return Info_ValueForKey( cls.physinfo, key ); +} + +/* +============= +pfnServerInfo_ValueForKey + +============= +*/ +static const char* pfnServerInfo_ValueForKey( const char *key ) +{ + return Info_ValueForKey( cl.serverinfo, key ); +} + +/* +============= +pfnGetClientMaxspeed + +value that come from server +============= +*/ +static float pfnGetClientMaxspeed( void ) +{ + return cl.local.maxspeed; +} + +/* +============= +pfnGetMousePosition + +============= +*/ +static void pfnGetMousePosition( int *mx, int *my ) +{ + POINT curpos; + + GetCursorPos( &curpos ); + ScreenToClient( host.hWnd, &curpos ); + + if( mx ) *mx = curpos.x; + if( my ) *my = curpos.y; +} + +/* +============= +pfnIsNoClipping + +============= +*/ +int pfnIsNoClipping( void ) +{ + return ( cl.frames[cl.parsecountmod].playerstate[cl.playernum].movetype == MOVETYPE_NOCLIP ); +} + +/* +============= +pfnGetViewModel + +============= +*/ +static cl_entity_t* pfnGetViewModel( void ) +{ + return &clgame.viewent; +} + +/* +============= +pfnGetClientTime + +============= +*/ +static float pfnGetClientTime( void ) +{ + return cl.time; +} + +/* +============= +pfnCalcShake + +============= +*/ +void pfnCalcShake( void ) +{ + int i; + float fraction, freq; + float localAmp; + + if( clgame.shake.time == 0 ) + return; + + if(( cl.time > clgame.shake.time ) || clgame.shake.amplitude <= 0 || clgame.shake.frequency <= 0 ) + { + memset( &clgame.shake, 0, sizeof( clgame.shake )); + return; + } + + if( cl.time > clgame.shake.next_shake ) + { + // higher frequency means we recalc the extents more often and perturb the display again + clgame.shake.next_shake = cl.time + ( 1.0f / clgame.shake.frequency ); + + // compute random shake extents (the shake will settle down from this) + for( i = 0; i < 3; i++ ) + clgame.shake.offset[i] = COM_RandomFloat( -clgame.shake.amplitude, clgame.shake.amplitude ); + clgame.shake.angle = COM_RandomFloat( -clgame.shake.amplitude * 0.25f, clgame.shake.amplitude * 0.25f ); + } + + // ramp down amplitude over duration (fraction goes from 1 to 0 linearly with slope 1/duration) + fraction = ( clgame.shake.time - cl.time ) / clgame.shake.duration; + + // ramp up frequency over duration + if( fraction ) + { + freq = ( clgame.shake.frequency / fraction ); + } + else + { + freq = 0; + } + + // square fraction to approach zero more quickly + fraction *= fraction; + + // Sine wave that slowly settles to zero + fraction = fraction * sin( cl.time * freq ); + + // add to view origin + VectorScale( clgame.shake.offset, fraction, clgame.shake.applied_offset ); + + // add to roll + clgame.shake.applied_angle = clgame.shake.angle * fraction; + + // drop amplitude a bit, less for higher frequency shakes + localAmp = clgame.shake.amplitude * ( host.frametime / ( clgame.shake.duration * clgame.shake.frequency )); + clgame.shake.amplitude -= localAmp; +} + +/* +============= +pfnApplyShake + +============= +*/ +void pfnApplyShake( float *origin, float *angles, float factor ) +{ + if( origin ) VectorMA( origin, factor, clgame.shake.applied_offset, origin ); + if( angles ) angles[ROLL] += clgame.shake.applied_angle * factor; +} + +/* +============= +pfnIsSpectateOnly + +============= +*/ +static int pfnIsSpectateOnly( void ) +{ + return (cls.spectator != 0); +} + +/* +============= +pfnPointContents + +============= +*/ +static int pfnPointContents( const float *p, int *truecontents ) +{ + int cont, truecont; + + truecont = cont = CL_TruePointContents( p ); + if( truecontents ) *truecontents = truecont; + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +/* +============= +pfnTraceLine + +============= +*/ +static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL ); + break; + } + + clgame.pmove->usehull = old_usehull; + + return &tr; +} + +static void pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin ) +{ + int hSound = S_RegisterSound( szSound ); + S_StartSound( origin, 0, CHAN_AUTO, hSound, volume, ATTN_NORM, PITCH_NORM, 0 ); +} + +/* +============= +pfnPrecacheEvent + +============= +*/ +static word pfnPrecacheEvent( int type, const char* psz ) +{ + return CL_EventIndex( psz ); +} + +/* +============= +pfnHookEvent + +============= +*/ +static void pfnHookEvent( const char *filename, pfnEventHook pfn ) +{ + char name[64]; + cl_user_event_t *ev; + int i; + + // ignore blank names + if( !filename || !*filename ) + return; + + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + // find an empty slot + for( i = 0; i < MAX_EVENTS; i++ ) + { + ev = clgame.events[i]; + if( !ev ) break; + + if( !Q_stricmp( name, ev->name ) && ev->func != NULL ) + { + MsgDev( D_WARN, "CL_HookEvent: %s already hooked!\n", name ); + return; + } + } + + CL_RegisterEvent( i, name, pfn ); +} + +/* +============= +pfnKillEvent + +============= +*/ +static void pfnKillEvents( int entnum, const char *eventname ) +{ + int i; + event_state_t *es; + event_info_t *ei; + int eventIndex = CL_EventIndex( eventname ); + + if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) + return; + + if( entnum < 0 || entnum > clgame.maxEntities ) + return; + + es = &cl.events; + + // find all events with specified index and kill it + for( i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + ei = &es->ei[i]; + + if( ei->index == eventIndex && ei->entity_index == entnum ) + { + CL_ResetEvent( ei ); + break; + } + } +} + +/* +============= +pfnPlaySound + +============= +*/ +void pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch ) +{ + S_StartSound( org, ent, chan, S_RegisterSound( samp ), vol, attn, pitch, flags ); +} + +/* +============= +CL_FindModelIndex + +============= +*/ +int CL_FindModelIndex( const char *m ) +{ + char filepath[MAX_QPATH]; + static float lasttimewarn; + int i; + + if( !COM_CheckString( m )) + return 0; + + Q_strncpy( filepath, m, sizeof( filepath )); + COM_FixSlashes( filepath ); + + for( i = 0; i < cl.nummodels; i++ ) + { + if( !cl.models[i+1] ) + continue; + + if( !Q_stricmp( cl.models[i+1]->name, filepath )) + return i+1; + } + + if( lasttimewarn < host.realtime ) + { + // tell user about problem (but don't spam console) + Con_Printf( S_ERROR "%s not precached\n", filepath ); + lasttimewarn = host.realtime + 1.0f; + } + + return 0; +} + +/* +============= +pfnIsLocal + +============= +*/ +int pfnIsLocal( int playernum ) +{ + if( playernum == cl.playernum ) + return true; + return false; +} + +/* +============= +pfnLocalPlayerDucking + +============= +*/ +int pfnLocalPlayerDucking( void ) +{ + return (cl.local.usehull == 1) ? true : false; +} + +/* +============= +pfnLocalPlayerViewheight + +============= +*/ +void pfnLocalPlayerViewheight( float *view_ofs ) +{ + if( view_ofs ) VectorCopy( cl.viewheight, view_ofs ); +} + +/* +============= +pfnLocalPlayerBounds + +============= +*/ +void pfnLocalPlayerBounds( int hull, float *mins, float *maxs ) +{ + if( hull >= 0 && hull < 4 ) + { + if( mins ) VectorCopy( clgame.pmove->player_mins[hull], mins ); + if( maxs ) VectorCopy( clgame.pmove->player_maxs[hull], maxs ); + } +} + +/* +============= +pfnIndexFromTrace + +============= +*/ +int pfnIndexFromTrace( struct pmtrace_s *pTrace ) +{ + if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent ) + { + // return cl.entities number + return clgame.pmove->physents[pTrace->ent].info; + } + return -1; +} + +/* +============= +pfnGetPhysent + +============= +*/ +physent_t *pfnGetPhysent( int idx ) +{ + if( idx >= 0 && idx < clgame.pmove->numphysent ) + { + // return physent + return &clgame.pmove->physents[idx]; + } + return NULL; +} + +/* +============= +pfnGetVisent + +============= +*/ +physent_t *pfnGetVisent( int idx ) +{ + if( idx >= 0 && idx < clgame.pmove->numvisent ) + { + // return physent + return &clgame.pmove->visents[idx]; + } + return NULL; +} + +/* +============= +pfnSetTraceHull + +============= +*/ +void CL_SetTraceHull( int hull ) +{ + clgame.pmove->usehull = bound( 0, hull, 3 ); + +} + +/* +============= +pfnPlayerTrace + +============= +*/ +void CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ) +{ + if( !tr ) return; + *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); +} + +/* +============= +pfnPlayerTraceExt + +============= +*/ +void CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ) +{ + if( !tr ) return; + *tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pfnIgnore ); +} + +/* +============= +pfnTraceTexture + +============= +*/ +static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= clgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &clgame.pmove->physents[ground]; + return PM_TraceTexture( pe, vstart, vend ); +} + +/* +============= +pfnTraceSurface + +============= +*/ +static struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= clgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &clgame.pmove->physents[ground]; + return PM_TraceSurface( pe, vstart, vend ); +} + +/* +============= +pfnGetMovevars + +============= +*/ +static movevars_t *pfnGetMoveVars( void ) +{ + return &clgame.movevars; +} + +/* +============= +pfnStopAllSounds + +============= +*/ +void pfnStopAllSounds( int ent, int entchannel ) +{ + S_StopSound( ent, entchannel, NULL ); +} + +/* +============= +CL_LoadModel + +============= +*/ +model_t *CL_LoadModel( const char *modelname, int *index ) +{ + int i; + + if( index ) *index = -1; + + if(( i = CL_FindModelIndex( modelname )) == 0 ) + return NULL; + + if( index ) *index = i; + + return CL_ModelHandle( i ); +} + +int CL_AddEntity( int entityType, cl_entity_t *pEnt ) +{ + if( !pEnt ) return false; + + // clear effects for all temp entities + if( !pEnt->index ) pEnt->curstate.effects = 0; + + // let the render reject entity without model + return CL_AddVisibleEntity( pEnt, entityType ); +} + +/* +============= +pfnGetGameDirectory + +============= +*/ +const char *pfnGetGameDirectory( void ) +{ + static char szGetGameDir[MAX_SYSPATH]; + + Q_sprintf( szGetGameDir, "%s/%s", host.rootdir, GI->gamedir ); + return szGetGameDir; +} + +/* +============= +Key_LookupBinding + +============= +*/ +const char *Key_LookupBinding( const char *pBinding ) +{ + return Key_KeynumToString( Key_GetKey( pBinding )); +} + +/* +============= +pfnGetLevelName + +============= +*/ +static const char *pfnGetLevelName( void ) +{ + static char mapname[64]; + + if( cls.state >= ca_connected ) + Q_snprintf( mapname, sizeof( mapname ), "maps/%s.bsp", clgame.mapname ); + else mapname[0] = '\0'; // not in game + + return mapname; +} + +/* +============= +pfnGetScreenFade + +============= +*/ +static void pfnGetScreenFade( struct screenfade_s *fade ) +{ + if( fade ) *fade = clgame.fade; +} + +/* +============= +pfnSetScreenFade + +============= +*/ +static void pfnSetScreenFade( struct screenfade_s *fade ) +{ + if( fade ) clgame.fade = *fade; +} + +/* +============= +pfnLoadMapSprite + +============= +*/ +model_t *pfnLoadMapSprite( const char *filename ) +{ + return CL_LoadSpriteModel( filename, SPR_MAPSPRITE, 0 ); +} + +/* +============= +PlayerInfo_ValueForKey + +============= +*/ +const char *PlayerInfo_ValueForKey( int playerNum, const char *key ) +{ + // find the player + if(( playerNum > cl.maxclients ) || ( playerNum < 1 )) + return NULL; + + if( !cl.players[playerNum-1].name[0] ) + return NULL; + + return Info_ValueForKey( cl.players[playerNum-1].userinfo, key ); +} + +/* +============= +PlayerInfo_SetValueForKey + +============= +*/ +void PlayerInfo_SetValueForKey( const char *key, const char *value ) +{ + convar_t *var; + + if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value )) + return; // no changes ? + + var = Cvar_FindVar( key ); + + if( var && FBitSet( var->flags, FCVAR_USERINFO )) + { + Cvar_DirectSet( var, value ); + } + else if( Info_SetValueForStarKey( cls.userinfo, key, value, MAX_INFO_STRING )) + { + // time to update server copy of userinfo + CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", key, value ); + } +} + +/* +============= +pfnGetPlayerUniqueID + +============= +*/ +qboolean pfnGetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + if( iPlayer < 1 || iPlayer > cl.maxclients ) + return false; + + // make sure there is a player here.. + if( !cl.players[iPlayer-1].userinfo[0] || !cl.players[iPlayer-1].name[0] ) + return false; + + memcpy( playerID, cl.players[iPlayer-1].hashedcdkey, 16 ); + return true; +} + +/* +============= +pfnGetTrackerIDForPlayer + +obsolete, unused +============= +*/ +int pfnGetTrackerIDForPlayer( int playerSlot ) +{ + return 0; +} + +/* +============= +pfnGetPlayerForTrackerID + +obsolete, unused +============= +*/ +int pfnGetPlayerForTrackerID( int trackerID ) +{ + return 0; +} + +/* +============= +pfnServerCmdUnreliable + +============= +*/ +int pfnServerCmdUnreliable( char *szCmdString ) +{ + if( !szCmdString || !szCmdString[0] ) + return 0; + + MSG_BeginClientCmd( &cls.datagram, clc_stringcmd ); + MSG_WriteString( &cls.datagram, szCmdString ); + + return 1; +} + +/* +============= +pfnGetMousePos + +============= +*/ +void pfnGetMousePos( struct tagPOINT *ppt ) +{ + GetCursorPos( ppt ); +} + +/* +============= +pfnSetMousePos + +============= +*/ +void pfnSetMousePos( int mx, int my ) +{ + SetCursorPos( mx, my ); +} + +/* +============= +pfnSetMouseEnable + +legacy of dinput code +============= +*/ +void pfnSetMouseEnable( qboolean fEnable ) +{ +} + +/* +============= +pfnParseFile + +handle colon separately +============= +*/ +char *pfnParseFile( char *data, char *token ) +{ + char *out; + + host.com_handlecolon = true; + out = COM_ParseFile( data, token ); + host.com_handlecolon = false; + + return out; +} + +/* +================= +TriApi implementation + +================= +*/ +/* +============= +TriRenderMode + +set rendermode +============= +*/ +void TriRenderMode( int mode ) +{ + switch( mode ) + { + case kRenderNormal: + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_BLEND ); + pglDepthMask( GL_TRUE ); + break; + case kRenderTransAlpha: + pglEnable( GL_BLEND ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglDepthMask( GL_FALSE ); + break; + case kRenderTransColor: + case kRenderTransTexture: + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + break; + case kRenderGlow: + case kRenderTransAdd: + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + pglEnable( GL_BLEND ); + pglDepthMask( GL_FALSE ); + break; + } + + clgame.ds.renderMode = mode; +} + +/* +============= +TriBegin + +begin triangle sequence +============= +*/ +void TriBegin( int mode ) +{ + switch( mode ) + { + case TRI_POINTS: + mode = GL_POINTS; + break; + case TRI_TRIANGLES: + mode = GL_TRIANGLES; + break; + case TRI_TRIANGLE_FAN: + mode = GL_TRIANGLE_FAN; + break; + case TRI_QUADS: + mode = GL_QUADS; + break; + case TRI_LINES: + mode = GL_LINES; + break; + case TRI_TRIANGLE_STRIP: + mode = GL_TRIANGLE_STRIP; + break; + case TRI_QUAD_STRIP: + mode = GL_QUAD_STRIP; + break; + case TRI_POLYGON: + default: mode = GL_POLYGON; + break; + } + + pglBegin( mode ); +} + +/* +============= +TriEnd + +draw triangle sequence +============= +*/ +void TriEnd( void ) +{ + pglEnd(); +} + +/* +============= +TriColor4f + +============= +*/ +void TriColor4f( float r, float g, float b, float a ) +{ + if( clgame.ds.renderMode == kRenderTransAlpha ) + pglColor4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f ); + else pglColor4f( r * a, g * a, b * a, 1.0 ); + + clgame.ds.triRGBA[0] = r; + clgame.ds.triRGBA[1] = g; + clgame.ds.triRGBA[2] = b; + clgame.ds.triRGBA[3] = a; +} + +/* +============= +TriColor4ub + +============= +*/ +void TriColor4ub( byte r, byte g, byte b, byte a ) +{ + clgame.ds.triRGBA[0] = r * (1.0f / 255.0f); + clgame.ds.triRGBA[1] = g * (1.0f / 255.0f); + clgame.ds.triRGBA[2] = b * (1.0f / 255.0f); + clgame.ds.triRGBA[3] = a * (1.0f / 255.0f); + + pglColor4f( clgame.ds.triRGBA[0], clgame.ds.triRGBA[1], clgame.ds.triRGBA[2], 1.0f ); +} + +/* +============= +TriTexCoord2f + +============= +*/ +void TriTexCoord2f( float u, float v ) +{ + pglTexCoord2f( u, v ); +} + +/* +============= +TriVertex3fv + +============= +*/ +void TriVertex3fv( const float *v ) +{ + pglVertex3fv( v ); +} + +/* +============= +TriVertex3f + +============= +*/ +void TriVertex3f( float x, float y, float z ) +{ + pglVertex3f( x, y, z ); +} + +/* +============= +TriBrightness + +============= +*/ +void TriBrightness( float brightness ) +{ + float r, g, b; + + r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness; + g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness; + b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness; + + pglColor4f( r, g, b, 1.0f ); +} + +/* +============= +TriCullFace + +============= +*/ +void TriCullFace( int mode ) +{ + switch( mode ) + { + case TRI_FRONT: + clgame.ds.cullMode = GL_FRONT; + break; + default: + clgame.ds.cullMode = GL_NONE; + break; + } + + GL_Cull( clgame.ds.cullMode ); +} + +/* +============= +TriSpriteTexture + +bind current texture +============= +*/ +int TriSpriteTexture( model_t *pSpriteModel, int frame ) +{ + int gl_texturenum; + + if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 ) + return 0; + + if( gl_texturenum <= 0 || gl_texturenum > MAX_TEXTURES ) + gl_texturenum = tr.defaultTexture; + + GL_Bind( GL_TEXTURE0, gl_texturenum ); + + return 1; +} + +/* +============= +TriWorldToScreen + +convert world coordinates (x,y,z) into screen (x, y) +============= +*/ +int TriWorldToScreen( float *world, float *screen ) +{ + int retval; + + retval = R_WorldToScreen( world, screen ); + + screen[0] = 0.5f * screen[0] * (float)clgame.viewport[2]; + screen[1] = -0.5f * screen[1] * (float)clgame.viewport[3]; + screen[0] += 0.5f * (float)clgame.viewport[2]; + screen[1] += 0.5f * (float)clgame.viewport[3]; + + return retval; +} + +/* +============= +TriFog + +enables global fog on the level +============= +*/ +void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) +{ + // overrided by internal fog + if( RI.fogEnabled ) return; + RI.fogCustom = bOn; + + // check for invalid parms + if( flEnd <= flStart ) + { + RI.fogCustom = false; + pglDisable( GL_FOG ); + return; + } + + if( RI.fogCustom ) + pglEnable( GL_FOG ); + else pglDisable( GL_FOG ); + + // copy fog params + RI.fogColor[0] = flFogColor[0] / 255.0f; + RI.fogColor[1] = flFogColor[1] / 255.0f; + RI.fogColor[2] = flFogColor[2] / 255.0f; + RI.fogStart = flStart; + RI.fogColor[3] = 1.0f; + RI.fogDensity = 0.0f; + RI.fogSkybox = true; + RI.fogEnd = flEnd; + + pglFogi( GL_FOG_MODE, GL_LINEAR ); + pglFogfv( GL_FOG_COLOR, RI.fogColor ); + pglFogf( GL_FOG_START, RI.fogStart ); + pglFogf( GL_FOG_END, RI.fogEnd ); + pglHint( GL_FOG_HINT, GL_NICEST ); +} + +/* +============= +TriGetMatrix + +very strange export +============= +*/ +void TriGetMatrix( const int pname, float *matrix ) +{ + pglGetFloatv( pname, matrix ); +} + +/* +============= +TriBoxInPVS + +check box in pvs (absmin, absmax) +============= +*/ +int TriBoxInPVS( float *mins, float *maxs ) +{ + return Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )); +} + +/* +============= +TriLightAtPoint + +NOTE: dlights are ignored +============= +*/ +void TriLightAtPoint( float *pos, float *value ) +{ + colorVec vLightColor; + + if( !pos || !value ) return; + + vLightColor = R_LightPoint( pos ); + + value[0] = vLightColor.r; + value[1] = vLightColor.g; + value[2] = vLightColor.b; +} + +/* +============= +TriColor4fRendermode + +Heavy legacy of Quake... +============= +*/ +void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ) +{ + if( clgame.ds.renderMode == kRenderTransAlpha ) + { + clgame.ds.triRGBA[3] = a / 255.0f; + pglColor4f( r, g, b, a ); + } + else pglColor4f( r * a, g * a, b * a, 1.0f ); +} + +/* +============= +TriForParams + +============= +*/ +void TriFogParams( float flDensity, int iFogSkybox ) +{ + RI.fogDensity = flDensity; + RI.fogSkybox = iFogSkybox; +} + +/* +================= +DemoApi implementation + +================= +*/ +/* +================= +Demo_IsRecording + +================= +*/ +static int Demo_IsRecording( void ) +{ + return cls.demorecording; +} + +/* +================= +Demo_IsPlayingback + +================= +*/ +static int Demo_IsPlayingback( void ) +{ + return cls.demoplayback; +} + +/* +================= +Demo_IsTimeDemo + +================= +*/ +static int Demo_IsTimeDemo( void ) +{ + return cls.timedemo; +} + +/* +================= +Demo_WriteBuffer + +================= +*/ +static void Demo_WriteBuffer( int size, byte *buffer ) +{ + CL_WriteDemoUserMessage( buffer, size ); +} + +/* +================= +NetworkApi implementation + +================= +*/ +/* +================= +NetAPI_InitNetworking + +================= +*/ +void NetAPI_InitNetworking( void ) +{ + NET_Config( true ); // allow remote +} + +/* +================= +NetAPI_InitNetworking + +================= +*/ +void NetAPI_Status( net_status_t *status ) +{ + qboolean connected = false; + int packet_loss = 0; + + Assert( status != NULL ); + + if( cls.state > ca_disconnected && cls.state != ca_cinematic ) + connected = true; + + if( cls.state == ca_active ) + packet_loss = bound( 0, (int)cls.packet_loss, 100 ); + + status->connected = connected; + status->connection_time = (connected) ? (host.realtime - cls.netchan.connect_time) : 0.0; + status->latency = (connected) ? cl.frames[cl.parsecountmod].latency : 0.0; + status->remote_address = cls.netchan.remote_address; + status->packet_loss = packet_loss; + status->local_address = net_local; + status->rate = rate->value; +} + +/* +================= +NetAPI_SendRequest + +================= +*/ +void NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response ) +{ + net_request_t *nr = NULL; + string req; + int i; + + if( !response ) + { + MsgDev( D_ERROR, "Net_SendRequest: no callbcak specified for request with context %i!\n", context ); + return; + } + + if( remote_address->type >= NA_IPX ) + return; // IPX no longer support + + // find a free request + for( i = 0; i < MAX_REQUESTS; i++ ) + { + nr = &clgame.net_requests[i]; + if( !nr->pfnFunc ) break; + } + + if( i == MAX_REQUESTS ) + { + double max_timeout = 0; + + // no free requests? use oldest + for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ ) + { + if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout ) + { + max_timeout = host.realtime - clgame.net_requests[i].timesend; + nr = &clgame.net_requests[i]; + } + } + } + + Assert( nr != NULL ); + + // clear slot + memset( nr, 0, sizeof( *nr )); + + // create a new request + nr->timesend = host.realtime; + nr->timeout = nr->timesend + timeout; + nr->pfnFunc = response; + nr->resp.context = context; + nr->resp.type = request; + nr->resp.remote_address = *remote_address; + nr->flags = flags; + + if( request == NETAPI_REQUEST_SERVERLIST ) + { + char fullquery[512] = "1\xFF" "0.0.0.0:0\0" "\\gamedir\\"; + + // make sure what port is specified + if( !nr->resp.remote_address.port ) nr->resp.remote_address.port = MSG_BigShort( PORT_MASTER ); + + // grab the list from the master server + Q_strcpy( &fullquery[22], GI->gamedir ); + NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamedir ) + 23, fullquery, nr->resp.remote_address ); + clgame.request_type = NET_REQUEST_CLIENT; + clgame.master_request = nr; // holds the master request unitl the master acking + } + else + { + // local servers request + Q_snprintf( req, sizeof( req ), "netinfo %i %i %i", PROTOCOL_VERSION, context, request ); + Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, req ); + } +} + +/* +================= +NetAPI_CancelRequest + +================= +*/ +void NetAPI_CancelRequest( int context ) +{ + net_request_t *nr; + int i; + + // find a specified request + for( i = 0; i < MAX_REQUESTS; i++ ) + { + nr = &clgame.net_requests[i]; + + if( clgame.net_requests[i].resp.context == context ) + { + if( nr->pfnFunc ) + { + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + nr->resp.ping = host.realtime - nr->timesend; + nr->pfnFunc( &nr->resp ); + } + + if( clgame.net_requests[i].resp.type == NETAPI_REQUEST_SERVERLIST && &clgame.net_requests[i] == clgame.master_request ) + { + if( clgame.request_type == NET_REQUEST_CLIENT ) + clgame.request_type = NET_REQUEST_CANCEL; + clgame.master_request = NULL; + } + + memset( &clgame.net_requests[i], 0, sizeof( net_request_t )); + break; + } + } +} + +/* +================= +NetAPI_CancelAllRequests + +================= +*/ +void NetAPI_CancelAllRequests( void ) +{ + net_request_t *nr; + int i; + + // tell the user about cancel + for( i = 0; i < MAX_REQUESTS; i++ ) + { + nr = &clgame.net_requests[i]; + if( !nr->pfnFunc ) continue; // not used + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + nr->resp.ping = host.realtime - nr->timesend; + nr->pfnFunc( &nr->resp ); + } + + memset( clgame.net_requests, 0, sizeof( clgame.net_requests )); + clgame.request_type = NET_REQUEST_CANCEL; + clgame.master_request = NULL; +} + +/* +================= +NetAPI_AdrToString + +================= +*/ +char *NetAPI_AdrToString( netadr_t *a ) +{ + return NET_AdrToString( *a ); +} + +/* +================= +NetAPI_CompareAdr + +================= +*/ +int NetAPI_CompareAdr( netadr_t *a, netadr_t *b ) +{ + return NET_CompareAdr( *a, *b ); +} + +/* +================= +NetAPI_StringToAdr + +================= +*/ +int NetAPI_StringToAdr( char *s, netadr_t *a ) +{ + return NET_StringToAdr( s, a ); +} + +/* +================= +NetAPI_ValueForKey + +================= +*/ +const char *NetAPI_ValueForKey( const char *s, const char *key ) +{ + return Info_ValueForKey( s, key ); +} + +/* +================= +NetAPI_RemoveKey + +================= +*/ +void NetAPI_RemoveKey( char *s, const char *key ) +{ + Info_RemoveKey( s, key ); +} + +/* +================= +NetAPI_SetValueForKey + +================= +*/ +void NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize ) +{ + if( key[0] == '*' ) return; + Info_SetValueForStarKey( s, key, value, maxsize ); +} + + +/* +================= +IVoiceTweak implementation + +TODO: implement +================= +*/ +/* +================= +Voice_StartVoiceTweakMode + +================= +*/ +int Voice_StartVoiceTweakMode( void ) +{ + return 0; +} + +/* +================= +Voice_EndVoiceTweakMode + +================= +*/ +void Voice_EndVoiceTweakMode( void ) +{ +} + +/* +================= +Voice_SetControlFloat + +================= +*/ +void Voice_SetControlFloat( VoiceTweakControl iControl, float value ) +{ +} + +/* +================= +Voice_GetControlFloat + +================= +*/ +float Voice_GetControlFloat( VoiceTweakControl iControl ) +{ + return 1.0f; +} + +// shared between client and server +triangleapi_t gTriApi = +{ + TRI_API_VERSION, + TriRenderMode, + TriBegin, + TriEnd, + TriColor4f, + TriColor4ub, + TriTexCoord2f, + TriVertex3fv, + TriVertex3f, + TriBrightness, + TriCullFace, + TriSpriteTexture, + R_WorldToScreen, // NOTE: XPROJECT, YPROJECT should be done in client.dll + TriFog, + R_ScreenToWorld, + TriGetMatrix, + TriBoxInPVS, + TriLightAtPoint, + TriColor4fRendermode, + TriFogParams, +}; + +static efx_api_t gEfxApi = +{ + R_AllocParticle, + R_BlobExplosion, + R_Blood, + R_BloodSprite, + R_BloodStream, + R_BreakModel, + R_Bubbles, + R_BubbleTrail, + R_BulletImpactParticles, + R_EntityParticles, + R_Explosion, + R_FizzEffect, + R_FireField, + R_FlickerParticles, + R_FunnelSprite, + R_Implosion, + R_LargeFunnel, + R_LavaSplash, + R_MultiGunshot, + R_MuzzleFlash, + R_ParticleBox, + R_ParticleBurst, + R_ParticleExplosion, + R_ParticleExplosion2, + R_ParticleLine, + R_PlayerSprites, + R_Projectile, + R_RicochetSound, + R_RicochetSprite, + R_RocketFlare, + R_RocketTrail, + R_RunParticleEffect, + R_ShowLine, + R_SparkEffect, + R_SparkShower, + R_SparkStreaks, + R_Spray, + R_Sprite_Explode, + R_Sprite_Smoke, + R_Sprite_Spray, + R_Sprite_Trail, + R_Sprite_WallPuff, + R_StreakSplash, + R_TracerEffect, + R_UserTracerParticle, + R_TracerParticles, + R_TeleportSplash, + R_TempSphereModel, + R_TempModel, + R_DefaultSprite, + R_TempSprite, + CL_DecalIndex, + CL_DecalIndexFromName, + CL_DecalShoot, + R_AttachTentToPlayer, + R_KillAttachedTents, + R_BeamCirclePoints, + R_BeamEntPoint, + R_BeamEnts, + R_BeamFollow, + R_BeamKill, + R_BeamLightning, + R_BeamPoints, + R_BeamRing, + CL_AllocDlight, + CL_AllocElight, + CL_TempEntAlloc, + CL_TempEntAllocNoModel, + CL_TempEntAllocHigh, + CL_TempEntAllocCustom, + R_GetPackedColor, + R_LookupColor, + CL_DecalRemoveAll, + CL_FireCustomDecal, +}; + +static event_api_t gEventApi = +{ + EVENT_API_VERSION, + pfnPlaySound, + S_StopSound, + CL_FindModelIndex, + pfnIsLocal, + pfnLocalPlayerDucking, + pfnLocalPlayerViewheight, + pfnLocalPlayerBounds, + pfnIndexFromTrace, + pfnGetPhysent, + CL_SetUpPlayerPrediction, + CL_PushPMStates, + CL_PopPMStates, + CL_SetSolidPlayers, + CL_SetTraceHull, + CL_PlayerTrace, + CL_WeaponAnim, + pfnPrecacheEvent, + CL_PlaybackEvent, + pfnTraceTexture, + pfnStopAllSounds, + pfnKillEvents, + CL_PlayerTraceExt, // Xash3D added + CL_SoundFromIndex, + pfnTraceSurface, + pfnGetMoveVars, + CL_VisTraceLine, + pfnGetVisent, +}; + +static demo_api_t gDemoApi = +{ + Demo_IsRecording, + Demo_IsPlayingback, + Demo_IsTimeDemo, + Demo_WriteBuffer, +}; + +static net_api_t gNetApi = +{ + NetAPI_InitNetworking, + NetAPI_Status, + NetAPI_SendRequest, + NetAPI_CancelRequest, + NetAPI_CancelAllRequests, + NetAPI_AdrToString, + NetAPI_CompareAdr, + NetAPI_StringToAdr, + NetAPI_ValueForKey, + NetAPI_RemoveKey, + NetAPI_SetValueForKey, +}; + +static IVoiceTweak gVoiceApi = +{ + Voice_StartVoiceTweakMode, + Voice_EndVoiceTweakMode, + Voice_SetControlFloat, + Voice_GetControlFloat, +}; + +// engine callbacks +static cl_enginefunc_t gEngfuncs = +{ + pfnSPR_Load, + pfnSPR_Frames, + pfnSPR_Height, + pfnSPR_Width, + pfnSPR_Set, + pfnSPR_Draw, + pfnSPR_DrawHoles, + pfnSPR_DrawAdditive, + SPR_EnableScissor, + SPR_DisableScissor, + pfnSPR_GetList, + CL_FillRGBA, + pfnGetScreenInfo, + pfnSetCrosshair, + pfnCvar_RegisterClientVariable, + Cvar_VariableValue, + Cvar_VariableString, + Cmd_AddClientCommand, + pfnHookUserMsg, + pfnServerCmd, + pfnClientCmd, + pfnGetPlayerInfo, + pfnPlaySoundByName, + pfnPlaySoundByIndex, + AngleVectors, + CL_TextMessageGet, + pfnDrawCharacter, + pfnDrawConsoleString, + pfnDrawSetTextColor, + pfnDrawConsoleStringLen, + pfnConsolePrint, + pfnCenterPrint, + pfnGetWindowCenterX, + pfnGetWindowCenterY, + pfnGetViewAngles, + pfnSetViewAngles, + CL_GetMaxClients, + Cvar_SetValue, + Cmd_Argc, + Cmd_Argv, + Con_Printf, + Con_DPrintf, + Con_NPrintf, + Con_NXPrintf, + pfnPhysInfo_ValueForKey, + pfnServerInfo_ValueForKey, + pfnGetClientMaxspeed, + COM_CheckParm, + Key_Event, + pfnGetMousePosition, + pfnIsNoClipping, + CL_GetLocalPlayer, + pfnGetViewModel, + CL_GetEntityByIndex, + pfnGetClientTime, + pfnCalcShake, + pfnApplyShake, + pfnPointContents, + CL_WaterEntity, + pfnTraceLine, + CL_LoadModel, + CL_AddEntity, + CL_GetSpritePointer, + pfnPlaySoundByNameAtLocation, + pfnPrecacheEvent, + CL_PlaybackEvent, + CL_WeaponAnim, + COM_RandomFloat, + COM_RandomLong, + pfnHookEvent, + Con_Visible, + pfnGetGameDirectory, + pfnCVarGetPointer, + Key_LookupBinding, + pfnGetLevelName, + pfnGetScreenFade, + pfnSetScreenFade, + VGui_GetPanel, + VGui_ViewportPaintBackground, + COM_LoadFile, + pfnParseFile, + COM_FreeFile, + &gTriApi, + &gEfxApi, + &gEventApi, + &gDemoApi, + &gNetApi, + &gVoiceApi, + pfnIsSpectateOnly, + pfnLoadMapSprite, + COM_AddAppDirectoryToSearchPath, + COM_ExpandFilename, + PlayerInfo_ValueForKey, + PlayerInfo_SetValueForKey, + pfnGetPlayerUniqueID, + pfnGetTrackerIDForPlayer, + pfnGetPlayerForTrackerID, + pfnServerCmdUnreliable, + pfnGetMousePos, + pfnSetMousePos, + pfnSetMouseEnable, +}; + +void CL_UnloadProgs( void ) +{ + if( !clgame.hInstance ) return; + + CL_FreeEdicts(); + CL_FreeTempEnts(); + CL_FreeViewBeams(); + CL_FreeParticles(); + CL_ClearAllRemaps(); + Mod_ClearUserData(); + VGui_Shutdown(); + + // NOTE: HLFX 0.5 has strange bug: hanging on exit if no map was loaded + if( Q_stricmp( GI->gamedir, "hlfx" ) || GI->version != 0.5f ) + clgame.dllFuncs.pfnShutdown(); + + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY ); + + COM_FreeLibrary( clgame.hInstance ); + Mem_FreePool( &cls.mempool ); + Mem_FreePool( &clgame.mempool ); + memset( &clgame, 0, sizeof( clgame )); + + Cvar_Unlink( FCVAR_CLIENTDLL ); + Cmd_Unlink( CMD_CLIENTDLL ); +} + +qboolean CL_LoadProgs( const char *name ) +{ + static playermove_t gpMove; + const dllfunc_t *func; + CL_EXPORT_FUNCS GetClientAPI; // single export + qboolean critical_exports = true; + + if( clgame.hInstance ) CL_UnloadProgs(); + + // initialize PlayerMove + clgame.pmove = &gpMove; + + cls.mempool = Mem_AllocPool( "Client Static Pool" ); + clgame.mempool = Mem_AllocPool( "Client Edicts Zone" ); + clgame.entities = NULL; + + clgame.hInstance = COM_LoadLibrary( name, false, false ); + if( !clgame.hInstance ) return false; + + // clear exports + for( func = cdll_exports; func && func->name; func++ ) + *func->func = NULL; + + // trying to get single export + if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "GetClientAPI" )) != NULL ) + { + MsgDev( D_NOTE, "CL_LoadProgs: found single callback export\n" ); + + // trying to fill interface now + GetClientAPI( &clgame.dllFuncs ); + + // check critical functions again + for( func = cdll_exports; func && func->name; func++ ) + { + if( func->func == NULL ) + break; // BAH critical function was missed + } + + // because all the exports are loaded through function 'F" + if( !func || !func->name ) + critical_exports = false; + } + + for( func = cdll_exports; func && func->name != NULL; func++ ) + { + if( *func->func != NULL ) + continue; // already get through 'F' + + // functions are cleared before all the extensions are evaluated + if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL ) + { + MsgDev( D_NOTE, "CL_LoadProgs: failed to get address of %s proc\n", func->name ); + + if( critical_exports ) + { + COM_FreeLibrary( clgame.hInstance ); + clgame.hInstance = NULL; + return false; + } + } + } + + // it may be loaded through 'GetClientAPI' so we don't need to clear them + if( critical_exports ) + { + // clear new exports + for( func = cdll_new_exports; func && func->name; func++ ) + *func->func = NULL; + } + + for( func = cdll_new_exports; func && func->name != NULL; func++ ) + { + if( *func->func != NULL ) + continue; // already get through 'F' + + // functions are cleared before all the extensions are evaluated + // NOTE: new exports can be missed without stop the engine + if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL ) + MsgDev( D_NOTE, "CL_LoadProgs: failed to get address of %s proc\n", func->name ); + } + + if( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION )) + { + COM_FreeLibrary( clgame.hInstance ); + MsgDev( D_NOTE, "CL_LoadProgs: can't init client API\n" ); + clgame.hInstance = NULL; + return false; + } + + Cvar_FullSet( "host_clientloaded", "1", FCVAR_READ_ONLY ); + + clgame.maxRemapInfos = 0; // will be alloc on first call CL_InitEdicts(); + clgame.maxEntities = 2; // world + localclient (have valid entities not in game) + + CL_InitCDAudio( "media/cdaudio.txt" ); + CL_InitTitles( "titles.txt" ); + CL_InitParticles (); + CL_InitViewBeams (); + CL_InitTempEnts (); + + if( !R_InitRenderAPI()) // Xash3D extension + MsgDev( D_WARN, "CL_LoadProgs: couldn't get render API\n" ); + + CL_InitEdicts (); // initailize local player and world + CL_InitClientMove(); // initialize pm_shared + + // initialize game + clgame.dllFuncs.pfnInit(); + + CL_InitStudioAPI( ); + + // initialize VGui + VGui_Startup (); + + // trying to grab them from client.dll + cl_righthand = Cvar_FindVar( "cl_righthand" ); + + if( cl_righthand == NULL ) + cl_righthand = Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" ); + + return true; +} \ No newline at end of file diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c new file mode 100644 index 00000000..177760bb --- /dev/null +++ b/engine/client/cl_gameui.c @@ -0,0 +1,1064 @@ +/* +cl_menu.c - menu dlls interaction +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "gl_local.h" +#include "library.h" +#include "input.h" + +static MENUAPI GetMenuAPI; +static void UI_UpdateUserinfo( void ); + +gameui_static_t gameui; + +void UI_UpdateMenu( float realtime ) +{ + if( !gameui.hInstance ) return; + + // menu time (not paused, not clamped) + gameui.globals->time = host.realtime; + gameui.globals->frametime = host.realframetime; + gameui.globals->demoplayback = cls.demoplayback; + gameui.globals->demorecording = cls.demorecording; + gameui.globals->allow_console = host.allow_console; + + gameui.dllFuncs.pfnRedraw( realtime ); + UI_UpdateUserinfo(); +} + +void UI_KeyEvent( int key, qboolean down ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnKeyEvent( key, down ); +} + +void UI_MouseMove( int x, int y ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnMouseMove( x, y ); +} + +void UI_SetActiveMenu( qboolean fActive ) +{ + movie_state_t *cin_state; + + if( !gameui.hInstance ) + { + if( !fActive ) + Key_SetKeyDest( key_game ); + return; + } + + gameui.drawLogo = fActive; + gameui.dllFuncs.pfnSetActiveMenu( fActive ); + + if( !fActive ) + { + // close logo when menu is shutdown + cin_state = AVI_GetState( CIN_LOGO ); + AVI_CloseVideo( cin_state ); + } +} + +void UI_AddServerToList( netadr_t adr, const char *info ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnAddServerToList( adr, info ); +} + +void UI_GetCursorPos( int *pos_x, int *pos_y ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnGetCursorPos( pos_x, pos_y ); +} + +void UI_SetCursorPos( int pos_x, int pos_y ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnSetCursorPos( pos_x, pos_y ); +} + +void UI_ShowCursor( qboolean show ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnShowCursor( show ); +} + +qboolean UI_CreditsActive( void ) +{ + if( !gameui.hInstance ) return 0; + return gameui.dllFuncs.pfnCreditsActive(); +} + +void UI_CharEvent( int key ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnCharEvent( key ); +} + +qboolean UI_MouseInRect( void ) +{ + if( !gameui.hInstance ) return 1; + return gameui.dllFuncs.pfnMouseInRect(); +} + +qboolean UI_IsVisible( void ) +{ + if( !gameui.hInstance ) return 0; + return gameui.dllFuncs.pfnIsVisible(); +} + +static void UI_DrawLogo( const char *filename, float x, float y, float width, float height ) +{ + static float cin_time; + static int last_frame = -1; + byte *cin_data = NULL; + movie_state_t *cin_state; + int cin_frame; + qboolean redraw = false; + + if( !gameui.drawLogo ) return; + cin_state = AVI_GetState( CIN_LOGO ); + + if( !AVI_IsActive( cin_state )) + { + string path; + const char *fullpath; + + // run cinematic if not + Q_snprintf( path, sizeof( path ), "media/%s", filename ); + COM_DefaultExtension( path, ".avi" ); + fullpath = FS_GetDiskPath( path, false ); + + if( FS_FileExists( path, false ) && !fullpath ) + { + MsgDev( D_ERROR, "Couldn't load %s from packfile. Please extract it\n", path ); + gameui.drawLogo = false; + return; + } + + AVI_OpenVideo( cin_state, fullpath, false, true ); + if( !( AVI_GetVideoInfo( cin_state, &gameui.logo_xres, &gameui.logo_yres, &gameui.logo_length ))) + { + AVI_CloseVideo( cin_state ); + gameui.drawLogo = false; + return; + } + + cin_time = 0.0f; + last_frame = -1; + } + + if( width <= 0 || height <= 0 ) + { + // precache call, don't draw + cin_time = 0.0f; + last_frame = -1; + return; + } + + // advances cinematic time (ignores maxfps and host_framerate settings) + cin_time += host.realframetime; + + // restarts the cinematic + if( cin_time > gameui.logo_length ) + cin_time = 0.0f; + + // read the next frame + cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); + + if( cin_frame != last_frame ) + { + cin_data = AVI_GetVideoFrame( cin_state, cin_frame ); + last_frame = cin_frame; + redraw = true; + } + + R_DrawStretchRaw( x, y, width, height, gameui.logo_xres, gameui.logo_yres, cin_data, redraw ); +} + +static int UI_GetLogoWidth( void ) +{ + return gameui.logo_xres; +} + +static int UI_GetLogoHeight( void ) +{ + return gameui.logo_yres; +} + +static float UI_GetLogoLength( void ) +{ + return gameui.logo_length; +} + +static void UI_UpdateUserinfo( void ) +{ + player_info_t *player; + + if( !host.userinfo_changed ) + return; + + player = &gameui.playerinfo; + + Q_strncpy( player->userinfo, cls.userinfo, sizeof( player->userinfo )); + Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name )); + Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model )); + player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); + player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); + host.userinfo_changed = false; // we got it +} + +void Host_Credits( void ) +{ + if( !gameui.hInstance ) return; + gameui.dllFuncs.pfnFinalCredits(); +} + +static void UI_ConvertGameInfo( GAMEINFO *out, gameinfo_t *in ) +{ + Q_strncpy( out->gamefolder, in->gamefolder, sizeof( out->gamefolder )); + Q_strncpy( out->startmap, in->startmap, sizeof( out->startmap )); + Q_strncpy( out->trainmap, in->trainmap, sizeof( out->trainmap )); + Q_strncpy( out->title, in->title, sizeof( out->title )); + Q_strncpy( out->version, va( "%g", in->version ), sizeof( out->version )); + + Q_strncpy( out->game_url, in->game_url, sizeof( out->game_url )); + Q_strncpy( out->update_url, in->update_url, sizeof( out->update_url )); + Q_strncpy( out->size, Q_pretifymem( in->size, 0 ), sizeof( out->size )); + Q_strncpy( out->type, in->type, sizeof( out->type )); + Q_strncpy( out->date, in->date, sizeof( out->date )); + + out->gamemode = in->gamemode; + + if( in->nomodels ) + out->flags |= GFL_NOMODELS; +} + +static qboolean PIC_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 ) +{ + float dudx, dvdy; + + // clip sub rect to sprite + if(( width == 0 ) || ( height == 0 )) + return false; + + if( *x + *width <= gameui.ds.scissor_x ) + return false; + if( *x >= gameui.ds.scissor_x + gameui.ds.scissor_width ) + return false; + if( *y + *height <= gameui.ds.scissor_y ) + return false; + if( *y >= gameui.ds.scissor_y + gameui.ds.scissor_height ) + return false; + + dudx = (*u1 - *u0) / *width; + dvdy = (*v1 - *v0) / *height; + + if( *x < gameui.ds.scissor_x ) + { + *u0 += (gameui.ds.scissor_x - *x) * dudx; + *width -= gameui.ds.scissor_x - *x; + *x = gameui.ds.scissor_x; + } + + if( *x + *width > gameui.ds.scissor_x + gameui.ds.scissor_width ) + { + *u1 -= (*x + *width - (gameui.ds.scissor_x + gameui.ds.scissor_width)) * dudx; + *width = gameui.ds.scissor_x + gameui.ds.scissor_width - *x; + } + + if( *y < gameui.ds.scissor_y ) + { + *v0 += (gameui.ds.scissor_y - *y) * dvdy; + *height -= gameui.ds.scissor_y - *y; + *y = gameui.ds.scissor_y; + } + + if( *y + *height > gameui.ds.scissor_y + gameui.ds.scissor_height ) + { + *v1 -= (*y + *height - (gameui.ds.scissor_y + gameui.ds.scissor_height)) * dvdy; + *height = gameui.ds.scissor_y + gameui.ds.scissor_height - *y; + } + return true; +} + +/* +==================== +PIC_DrawGeneric + +draw hudsprite routine +==================== +*/ +static void PIC_DrawGeneric( float x, float y, float width, float height, const wrect_t *prc ) +{ + float s1, s2, t1, t2; + int w, h; + + // assume we get sizes from image + R_GetTextureParms( &w, &h, gameui.ds.gl_texturenum ); + + if( prc ) + { + // calc user-defined rectangle + s1 = (float)prc->left / (float)w; + t1 = (float)prc->top / (float)h; + s2 = (float)prc->right / (float)w; + t2 = (float)prc->bottom / (float)h; + + if( width == -1 && height == -1 ) + { + width = prc->right - prc->left; + height = prc->bottom - prc->top; + } + } + else + { + s1 = t1 = 0.0f; + s2 = t2 = 1.0f; + } + + if( width == -1 && height == -1 ) + { + width = w; + height = h; + } + + // pass scissor test if supposed + if( gameui.ds.scissor_test && !PIC_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + return; + + PicAdjustSize( &x, &y, &width, &height ); + R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, gameui.ds.gl_texturenum ); + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +=============================================================================== + MainUI Builtin Functions + +=============================================================================== +*/ +/* +========= +pfnPIC_Load + +========= +*/ +static HIMAGE pfnPIC_Load( const char *szPicName, const byte *image_buf, long image_size, long flags ) +{ + HIMAGE tx; + + if( !szPicName || !*szPicName ) + { + MsgDev( D_ERROR, "CL_LoadImage: bad name!\n" ); + return 0; + } + + // add default parms to image + SetBits( flags, TF_IMAGE ); + + Image_SetForceFlags( IL_LOAD_DECAL ); // allow decal images for menu + tx = GL_LoadTexture( szPicName, image_buf, image_size, flags, NULL ); + Image_ClearForceFlags(); + + return tx; +} + +/* +========= +pfnPIC_Width + +========= +*/ +static int pfnPIC_Width( HIMAGE hPic ) +{ + int picWidth; + + R_GetTextureParms( &picWidth, NULL, hPic ); + + return picWidth; +} + +/* +========= +pfnPIC_Height + +========= +*/ +static int pfnPIC_Height( HIMAGE hPic ) +{ + int picHeight; + + R_GetTextureParms( NULL, &picHeight, hPic ); + + return picHeight; +} + +/* +========= +pfnPIC_Set + +========= +*/ +void pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ) +{ + gameui.ds.gl_texturenum = hPic; + r = bound( 0, r, 255 ); + g = bound( 0, g, 255 ); + b = bound( 0, b, 255 ); + a = bound( 0, a, 255 ); + pglColor4ub( r, g, b, a ); +} + +/* +========= +pfnPIC_Draw + +========= +*/ +void pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ) +{ + GL_SetRenderMode( kRenderNormal ); + PIC_DrawGeneric( x, y, width, height, prc ); +} + +/* +========= +pfnPIC_DrawTrans + +========= +*/ +void pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ) +{ + GL_SetRenderMode( kRenderTransTexture ); + PIC_DrawGeneric( x, y, width, height, prc ); +} + +/* +========= +pfnPIC_DrawHoles + +========= +*/ +void pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ) +{ + GL_SetRenderMode( kRenderTransAlpha ); + PIC_DrawGeneric( x, y, width, height, prc ); +} + +/* +========= +pfnPIC_DrawAdditive + +========= +*/ +void pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ) +{ + GL_SetRenderMode( kRenderTransAdd ); + PIC_DrawGeneric( x, y, width, height, prc ); +} + +/* +========= +pfnPIC_EnableScissor + +========= +*/ +static void pfnPIC_EnableScissor( int x, int y, int width, int height ) +{ + // check bounds + x = bound( 0, x, gameui.globals->scrWidth ); + y = bound( 0, y, gameui.globals->scrHeight ); + width = bound( 0, width, gameui.globals->scrWidth - x ); + height = bound( 0, height, gameui.globals->scrHeight - y ); + + gameui.ds.scissor_x = x; + gameui.ds.scissor_width = width; + gameui.ds.scissor_y = y; + gameui.ds.scissor_height = height; + gameui.ds.scissor_test = true; +} + +/* +========= +pfnPIC_DisableScissor + +========= +*/ +static void pfnPIC_DisableScissor( void ) +{ + gameui.ds.scissor_x = 0; + gameui.ds.scissor_width = 0; + gameui.ds.scissor_y = 0; + gameui.ds.scissor_height = 0; + gameui.ds.scissor_test = false; +} + +/* +============= +pfnFillRGBA + +============= +*/ +static void pfnFillRGBA( int x, int y, int width, int height, int r, int g, int b, int a ) +{ + r = bound( 0, r, 255 ); + g = bound( 0, g, 255 ); + b = bound( 0, b, 255 ); + a = bound( 0, a, 255 ); + pglColor4ub( r, g, b, a ); + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, tr.whiteTexture ); + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +============= +pfnClientCmd + +============= +*/ +static void pfnClientCmd( int exec_now, const char *szCmdString ) +{ + if( !szCmdString || !szCmdString[0] ) + return; + + Cbuf_AddText( szCmdString ); + Cbuf_AddText( "\n" ); + + // client command executes immediately + if( exec_now ) Cbuf_Execute(); +} + +/* +============= +pfnPlaySound + +============= +*/ +static void pfnPlaySound( const char *szSound ) +{ + if( !COM_CheckString( szSound )) return; + S_StartLocalSound( szSound, VOL_NORM, false ); +} + +/* +============= +pfnDrawCharacter + +quakefont draw character +============= +*/ +static void pfnDrawCharacter( int ix, int iy, int iwidth, int iheight, int ch, int ulRGBA, HIMAGE hFont ) +{ + rgba_t color; + float row, col, size; + float s1, t1, s2, t2; + float x = ix, y = iy; + float width = iwidth; + float height = iheight; + + ch &= 255; + + if( ch == ' ' ) return; + if( y < -height ) return; + + color[3] = (ulRGBA & 0xFF000000) >> 24; + color[0] = (ulRGBA & 0xFF0000) >> 16; + color[1] = (ulRGBA & 0xFF00) >> 8; + color[2] = (ulRGBA & 0xFF) >> 0; + pglColor4ubv( color ); + + col = (ch & 15) * 0.0625f + (0.5f / 256.0f); + row = (ch >> 4) * 0.0625f + (0.5f / 256.0f); + size = 0.0625f - (1.0f / 256.0f); + + s1 = col; + t1 = row; + s2 = s1 + size; + t2 = t1 + size; + + // pass scissor test if supposed + if( gameui.ds.scissor_test && !PIC_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 )) + return; + + GL_SetRenderMode( kRenderTransTexture ); + R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, hFont ); + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +============= +UI_DrawConsoleString + +drawing string like a console string +============= +*/ +static int UI_DrawConsoleString( int x, int y, const char *string ) +{ + int drawLen; + + if( !string || !*string ) return 0; // silent ignore + drawLen = Con_DrawString( x, y, string, gameui.ds.textColor ); + MakeRGBA( gameui.ds.textColor, 255, 255, 255, 255 ); + + return (x + drawLen); // exclude color prexfixes +} + +/* +============= +pfnDrawSetTextColor + +set color for anything +============= +*/ +static void UI_DrawSetTextColor( int r, int g, int b, int alpha ) +{ + // bound color and convert to byte + gameui.ds.textColor[0] = r; + gameui.ds.textColor[1] = g; + gameui.ds.textColor[2] = b; + gameui.ds.textColor[3] = alpha; +} + +/* +==================== +pfnGetPlayerModel + +for drawing playermodel previews +==================== +*/ +static cl_entity_t* pfnGetPlayerModel( void ) +{ + return &gameui.playermodel; +} + +/* +==================== +pfnSetPlayerModel + +for drawing playermodel previews +==================== +*/ +static void pfnSetPlayerModel( cl_entity_t *ent, const char *path ) +{ + ent->model = Mod_ForName( path, false, false ); + ent->curstate.modelindex = MAX_MODELS; // unreachable index +} + +/* +==================== +pfnClearScene + +for drawing playermodel previews +==================== +*/ +static void pfnClearScene( void ) +{ + R_PushScene(); + R_ClearScene(); +} + +/* +==================== +pfnRenderScene + +for drawing playermodel previews +==================== +*/ +static void pfnRenderScene( const ref_viewpass_t *rvp ) +{ + // to avoid division by zero + if( !rvp || rvp->fov_x <= 0.0f || rvp->fov_y <= 0.0f ) + return; + + // don't allow special modes from menu + ((ref_viewpass_t *)&rvp)->flags = 0; + + R_Set2DMode( false ); + R_RenderFrame( rvp ); + R_Set2DMode( true ); + R_PopScene(); +} + +/* +==================== +pfnAddEntity + +adding player model into visible list +==================== +*/ +static int pfnAddEntity( int entityType, cl_entity_t *ent ) +{ + if( !R_AddEntity( ent, entityType )) + return false; + return true; +} + +/* +==================== +pfnClientJoin + +send client connect +==================== +*/ +static void pfnClientJoin( const netadr_t adr ) +{ + Cbuf_AddText( va( "connect %s\n", NET_AdrToString( adr ))); +} + +/* +==================== +pfnKeyGetOverstrikeMode + +get global key overstrike state +==================== +*/ +static int pfnKeyGetOverstrikeMode( void ) +{ + return host.key_overstrike; +} + +/* +==================== +pfnKeySetOverstrikeMode + +set global key overstrike mode +==================== +*/ +static void pfnKeySetOverstrikeMode( int fActive ) +{ + host.key_overstrike = fActive; +} + +/* +==================== +pfnKeyGetState + +returns kbutton struct if found +==================== +*/ +static void *pfnKeyGetState( const char *name ) +{ + if( clgame.dllFuncs.KB_Find ) + return clgame.dllFuncs.KB_Find( name ); + return NULL; +} + +/* +========= +pfnMemAlloc + +========= +*/ +static void *pfnMemAlloc( size_t cb, const char *filename, const int fileline ) +{ + return _Mem_Alloc( gameui.mempool, cb, filename, fileline ); +} + +/* +========= +pfnMemFree + +========= +*/ +static void pfnMemFree( void *mem, const char *filename, const int fileline ) +{ + _Mem_Free( mem, filename, fileline ); +} + +/* +========= +pfnGetGameInfo + +========= +*/ +static int pfnGetGameInfo( GAMEINFO *pgameinfo ) +{ + if( !pgameinfo ) return 0; + + *pgameinfo = gameui.gameInfo; + return 1; +} + +/* +========= +pfnGetGamesList + +========= +*/ +static GAMEINFO **pfnGetGamesList( int *numGames ) +{ + if( numGames ) *numGames = SI.numgames; + return gameui.modsInfo; +} + +/* +========= +pfnGetFilesList + +release prev search on a next call +========= +*/ +static char **pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) +{ + static search_t *t = NULL; + + if( t ) Mem_Free( t ); // release prev search + + t = FS_Search( pattern, true, gamedironly ); + if( !t ) + { + if( numFiles ) *numFiles = 0; + return NULL; + } + + if( numFiles ) *numFiles = t->numfilenames; + return t->filenames; +} + +/* +========= +pfnGetClipboardData + +pointer must be released in call place +========= +*/ +static char *pfnGetClipboardData( void ) +{ + return Sys_GetClipboardData(); +} + +/* +========= +pfnCheckGameDll + +========= +*/ +int pfnCheckGameDll( void ) +{ + void *hInst; + + if( SV_Initialized( )) return true; + + if(( hInst = COM_LoadLibrary( GI->game_dll, true, false )) != NULL ) + { + COM_FreeLibrary( hInst ); + return true; + } + return false; +} + +/* +========= +pfnChangeInstance + +========= +*/ +static void pfnChangeInstance( const char *newInstance, const char *szFinalMessage ) +{ + if( !szFinalMessage ) szFinalMessage = ""; + if( !newInstance || !*newInstance ) return; + + Host_NewInstance( newInstance, szFinalMessage ); +} + +/* +========= +pfnHostEndGame + +========= +*/ +static void pfnHostEndGame( const char *szFinalMessage ) +{ + if( !szFinalMessage ) szFinalMessage = ""; + Host_EndGame( true, szFinalMessage ); +} + +/* +========= +pfnStartBackgroundTrack + +========= +*/ +static void pfnStartBackgroundTrack( const char *introTrack, const char *mainTrack ) +{ + S_StartBackgroundTrack( introTrack, mainTrack, 0, false ); +} + +// engine callbacks +static ui_enginefuncs_t gEngfuncs = +{ + pfnPIC_Load, + GL_FreeImage, + pfnPIC_Width, + pfnPIC_Height, + pfnPIC_Set, + pfnPIC_Draw, + pfnPIC_DrawHoles, + pfnPIC_DrawTrans, + pfnPIC_DrawAdditive, + pfnPIC_EnableScissor, + pfnPIC_DisableScissor, + pfnFillRGBA, + pfnCvar_RegisterGameUIVariable, + Cvar_VariableValue, + Cvar_VariableString, + Cvar_Set, + Cvar_SetValue, + Cmd_AddGameUICommand, + pfnClientCmd, + Cmd_RemoveCommand, + Cmd_Argc, + Cmd_Argv, + Cmd_Args, + Con_Printf, + Con_DPrintf, + UI_NPrintf, + UI_NXPrintf, + pfnPlaySound, + UI_DrawLogo, + UI_GetLogoWidth, + UI_GetLogoHeight, + UI_GetLogoLength, + pfnDrawCharacter, + UI_DrawConsoleString, + UI_DrawSetTextColor, + Con_DrawStringLen, + Con_DefaultColor, + pfnGetPlayerModel, + pfnSetPlayerModel, + pfnClearScene, + pfnRenderScene, + pfnAddEntity, + Host_Error, + FS_FileExists, + pfnGetGameDir, + Cmd_CheckMapsList, + CL_Active, + pfnClientJoin, + COM_LoadFileForMe, + COM_ParseFile, + COM_FreeFile, + Key_ClearStates, + Key_SetKeyDest, + Key_KeynumToString, + Key_GetBinding, + Key_SetBinding, + Key_IsDown, + pfnKeyGetOverstrikeMode, + pfnKeySetOverstrikeMode, + pfnKeyGetState, + pfnMemAlloc, + pfnMemFree, + pfnGetGameInfo, + pfnGetGamesList, + pfnGetFilesList, + SV_GetSaveComment, + CL_GetDemoComment, + pfnCheckGameDll, + pfnGetClipboardData, + Sys_ShellExecute, + Host_WriteServerConfig, + pfnChangeInstance, + pfnStartBackgroundTrack, + pfnHostEndGame, + COM_RandomFloat, + COM_RandomLong, + IN_SetCursor, + pfnIsMapValid, + GL_ProcessTexture, + COM_CompareFileTime, + VID_GetModeString, +}; + +void UI_UnloadProgs( void ) +{ + if( !gameui.hInstance ) return; + + // deinitialize game + gameui.dllFuncs.pfnShutdown(); + + Cvar_FullSet( "host_gameuiloaded", "0", FCVAR_READ_ONLY ); + + COM_FreeLibrary( gameui.hInstance ); + Mem_FreePool( &gameui.mempool ); + memset( &gameui, 0, sizeof( gameui )); + + Cvar_Unlink( FCVAR_GAMEUIDLL ); + Cmd_Unlink( CMD_GAMEUIDLL ); +} + +qboolean UI_LoadProgs( void ) +{ + static ui_enginefuncs_t gpEngfuncs; + static ui_globalvars_t gpGlobals; + int i; + + if( gameui.hInstance ) UI_UnloadProgs(); + + // setup globals + gameui.globals = &gpGlobals; + + if(( gameui.hInstance = COM_LoadLibrary( va( "%s/menu.dll", GI->dll_path ), false, false )) == NULL ) + { + if(( gameui.hInstance = COM_LoadLibrary( "menu.dll", false, true )) == NULL ) + return false; + } + + if(( GetMenuAPI = (MENUAPI)COM_GetProcAddress( gameui.hInstance, "GetMenuAPI" )) == NULL ) + { + COM_FreeLibrary( gameui.hInstance ); + MsgDev( D_NOTE, "UI_LoadProgs: can't init menu API\n" ); + gameui.hInstance = NULL; + return false; + } + + // make local copy of engfuncs to prevent overwrite it with user dll + memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); + + gameui.mempool = Mem_AllocPool( "Menu Pool" ); + + if( !GetMenuAPI( &gameui.dllFuncs, &gpEngfuncs, gameui.globals )) + { + COM_FreeLibrary( gameui.hInstance ); + MsgDev( D_NOTE, "UI_LoadProgs: can't init menu API\n" ); + Mem_FreePool( &gameui.mempool ); + gameui.hInstance = NULL; + return false; + } + + Cvar_FullSet( "host_gameuiloaded", "1", FCVAR_READ_ONLY ); + + // setup gameinfo + for( i = 0; i < SI.numgames; i++ ) + { + gameui.modsInfo[i] = Mem_Alloc( gameui.mempool, sizeof( GAMEINFO )); + UI_ConvertGameInfo( gameui.modsInfo[i], SI.games[i] ); + } + + UI_ConvertGameInfo( &gameui.gameInfo, SI.GameInfo ); // current gameinfo + + // setup globals + gameui.globals->allow_console = host.allow_console; + + // initialize game + gameui.dllFuncs.pfnInit(); + + return true; +} \ No newline at end of file diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c new file mode 100644 index 00000000..0bb84307 --- /dev/null +++ b/engine/client/cl_main.c @@ -0,0 +1,2834 @@ +/* +cl_main.c - client main loop +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "cl_tent.h" +#include "gl_local.h" +#include "input.h" +#include "../cl_dll/kbutton.h" +#include "vgui_draw.h" + +#define MAX_TOTAL_CMDS 32 +#define MAX_CMD_BUFFER 8000 +#define CONNECTION_PROBLEM_TIME 15.0 // 15 seconds +#define CL_CONNECTION_RETRIES 10 +#define CL_TEST_RETRIES 5 + +CVAR_DEFINE_AUTO( mp_decals, "300", FCVAR_ARCHIVE, "decals limit in multiplayer" ); +CVAR_DEFINE_AUTO( dev_overview, "0", 0, "draw level in overview-mode" ); +CVAR_DEFINE_AUTO( cl_resend, "6.0", 0, "time to resend connect" ); +CVAR_DEFINE_AUTO( cl_allow_download, "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); +CVAR_DEFINE_AUTO( cl_allow_upload, "1", FCVAR_ARCHIVE, "allow to uploading resources to the server" ); +CVAR_DEFINE_AUTO( cl_download_ingame, "1", FCVAR_ARCHIVE, "allow to downloading resources while client is active" ); +CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" ); +CVAR_DEFINE_AUTO( cl_logocolor, "orange", FCVAR_ARCHIVE, "player logo color" ); +CVAR_DEFINE_AUTO( cl_test_bandwidth, "1", FCVAR_ARCHIVE, "test network bandwith before connection" ); +convar_t *rcon_client_password; +convar_t *rcon_address; +convar_t *cl_timeout; +convar_t *cl_nopred; +convar_t *cl_showfps; +convar_t *cl_nodelta; +convar_t *cl_crosshair; +convar_t *cl_cmdbackup; +convar_t *cl_showerror; +convar_t *cl_bmodelinterp; +convar_t *cl_draw_particles; +convar_t *cl_draw_tracers; +convar_t *cl_lightstyle_lerping; +convar_t *cl_idealpitchscale; +convar_t *cl_nosmooth; +convar_t *cl_smoothtime; +convar_t *cl_clockreset; +convar_t *cl_fixtimerate; +convar_t *cl_solid_players; +convar_t *cl_draw_beams; +convar_t *cl_updaterate; +convar_t *cl_showevents; +convar_t *cl_cmdrate; +convar_t *cl_interp; +convar_t *cl_dlmax; +convar_t *cl_lw; + +// +// userinfo +// +convar_t *name; +convar_t *model; +convar_t *topcolor; +convar_t *bottomcolor; +convar_t *rate; + +client_t cl; +client_static_t cls; +clgame_static_t clgame; + +//====================================================================== +qboolean CL_Active( void ) +{ + return ( cls.state == ca_active ); +} + +qboolean CL_Initialized( void ) +{ + return cls.initialized; +} + +//====================================================================== +qboolean CL_IsInGame( void ) +{ + if( host.type == HOST_DEDICATED ) + return true; // always active for dedicated servers + + if( cl.background || CL_GetMaxClients() > 1 ) + return true; // always active for multiplayer or background map + + return ( cls.key_dest == key_game ); // active if not menu or console +} + +qboolean CL_IsInMenu( void ) +{ + return ( cls.key_dest == key_menu ); +} + +qboolean CL_IsInConsole( void ) +{ + return ( cls.key_dest == key_console ); +} + +qboolean CL_IsIntermission( void ) +{ + return cl.intermission; +} + +qboolean CL_IsPlaybackDemo( void ) +{ + return cls.demoplayback; +} + +qboolean CL_IsRecordDemo( void ) +{ + return cls.demorecording; +} + +qboolean CL_IsTimeDemo( void ) +{ + return cls.timedemo; +} + +qboolean CL_DisableVisibility( void ) +{ + return cls.envshot_disable_vis; +} + +qboolean CL_IsBackgroundDemo( void ) +{ + return ( cls.demoplayback && cls.demonum != -1 ); +} + +qboolean CL_IsBackgroundMap( void ) +{ + return ( cl.background && !cls.demoplayback ); +} + +char *CL_Userinfo( void ) +{ + return cls.userinfo; +} + +int CL_IsDevOverviewMode( void ) +{ + if( dev_overview.value > 0.0f ) + { + if( host_developer.value || cls.spectator ) + return (int)dev_overview.value; + } + + return 0; +} + +/* +=============== +CL_CheckClientState + +finalize connection process and begin new frame +with new cls.state +=============== +*/ +void CL_CheckClientState( void ) +{ + // first update is the pre-final signon stage + if(( cls.state == ca_connected || cls.state == ca_validate ) && ( cls.signon == SIGNONS )) + { + cls.state = ca_active; + cls.changelevel = false; // changelevel is done + cls.changedemo = false; // changedemo is done + cl.first_frame = true; // first rendering frame + + SCR_MakeLevelShot(); // make levelshot if needs + Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar + Netchan_ReportFlow( &cls.netchan ); + + Con_DPrintf( "client connected at %.2f sec\n", Sys_DoubleTime() - cls.timestart ); + if(( cls.demoplayback || cls.disable_servercount != cl.servercount ) && cl.video_prepped ) + SCR_EndLoadingPlaque(); // get rid of loading plaque + } +} + +int CL_GetFragmentSize( void *unused ) +{ + if( Netchan_IsLocal( &cls.netchan )) + return FRAGMENT_LOCAL_SIZE; + + return FRAGMENT_MAX_SIZE; +} + +/* +===================== +CL_SignonReply + +An svc_signonnum has been received, perform a client side setup +===================== +*/ +void CL_SignonReply( void ) +{ + // g-cont. my favorite message :-) + Con_DPrintf( "CL_SignonReply: %i\n", cls.signon ); + + switch( cls.signon ) + { + case 1: + CL_ServerCommand( true, "begin" ); + if( host_developer.value >= DEV_EXTENDED ) + Mem_PrintStats(); + break; + case 2: + if( cl.proxy_redirect && !cls.spectator ) + CL_Disconnect(); + cl.proxy_redirect = false; + break; + } +} + +/* +=============== +CL_LerpPoint + +Determines the fraction between the last two messages that the objects +should be put at. +=============== +*/ +static float CL_LerpPoint( void ) +{ + float f, frac = 1.0f; + + f = cl_serverframetime(); + + if( f == 0.0f || cls.timedemo ) + { + cl.time = cl.mtime[0]; + + // g-cont. probably this is redundant + if( cls.demoplayback ) + cl.oldtime = cl.mtime[0] - cl_clientframetime(); + + return 1.0f; + } + + if( cl_interp->value > 0.001f && !FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + { + // manual lerp value (goldsrc mode) + frac = ( cl.time - cl.mtime[0] ) / cl_interp->value; + } + else if( f > 0.001f ) + { + // automatic lerp (classic mode) + frac = ( cl.time - cl.mtime[1] ) / f; + } + + return frac; +} + +/* +=============== +CL_DriftInterpolationAmount + +Drift interpolation value (this is used for server unlag system) +=============== +*/ +int CL_DriftInterpolationAmount( int goal ) +{ + float fgoal, maxmove, diff; + int msec; + + fgoal = (float)goal / 1000.0f; + + if( fgoal != cl.local.interp_amount ) + { + maxmove = host.frametime * 0.05; + diff = fgoal - cl.local.interp_amount; + diff = bound( -maxmove, diff, maxmove ); + cl.local.interp_amount += diff; + } + + msec = cl.local.interp_amount * 1000.0f; + msec = bound( 0, msec, 100 ); + + return msec; +} + +/* +=============== +CL_ComputeClientInterpolationAmount + +Validate interpolation cvars, calc interpolation window +=============== +*/ +void CL_ComputeClientInterpolationAmount( usercmd_t *cmd ) +{ + int min_interp = MIN_EX_INTERP; + int max_interp = MAX_EX_INTERP; + int interpolation_msec; + qboolean forced = false; + + if( cl_updaterate->value < MIN_UPDATERATE ) + { + Con_Printf( "cl_updaterate minimum is %f, resetting to default (20)\n", MIN_UPDATERATE ); + Cvar_Reset( "cl_updaterate" ); + } + + if( cl_updaterate->value > MAX_UPDATERATE ) + { + Con_Printf( "cl_updaterate clamped at maximum (%f)\n", MAX_UPDATERATE ); + Cvar_SetValue( "cl_updaterate", MAX_UPDATERATE ); + } + + if( cls.spectator ) + max_interp = 200; + + min_interp = 1000.0f / cl_updaterate->value; + min_interp = Q_max( 1, min_interp ); + interpolation_msec = cl_interp->value * 1000.0f; + + if(( interpolation_msec + 1 ) < min_interp ) + { + Con_Printf( "ex_interp forced up to %i msec\n", interpolation_msec ); + interpolation_msec = min_interp; + forced = true; + } + else if(( interpolation_msec - 1 ) > max_interp ) + { + Con_Printf( "ex_interp forced down to %i msec\n", interpolation_msec ); + interpolation_msec = max_interp; + forced = true; + } + + if( forced ) Cvar_SetValue( "ex_interp", (float)interpolation_msec * 0.001f ); + interpolation_msec = bound( min_interp, interpolation_msec, max_interp ); + + cmd->lerp_msec = CL_DriftInterpolationAmount( interpolation_msec ); +} + +/* +================= +CL_ComputePacketLoss + +================= +*/ +void CL_ComputePacketLoss( void ) +{ + int i, frm; + frame_t *frame; + int count = 0; + int lost = 0; + + if( host.realtime < cls.packet_loss_recalc_time ) + return; + + // recalc every second + cls.packet_loss_recalc_time = host.realtime + 1.0; + + // compuate packet loss + for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ ) + { + frm = i; + frame = &cl.frames[frm & CL_UPDATE_MASK]; + + if( frame->receivedtime == -1.0 ) + lost++; + count++; + } + + if( count <= 0 ) cls.packet_loss = 0.0f; + else cls.packet_loss = ( 100.0f * (float)lost ) / (float)count; +} + +/* +================= +CL_UpdateFrameLerp + +================= +*/ +void CL_UpdateFrameLerp( void ) +{ + if( cls.state != ca_active || !cl.validsequence ) + return; + + // compute last interpolation amount + cl.lerpFrac = CL_LerpPoint(); + + cl.commands[(cls.netchan.outgoing_sequence - 1) & CL_UPDATE_MASK].frame_lerp = cl.lerpFrac; +} + +void CL_FindInterpolatedAddAngle( float t, float *frac, pred_viewangle_t **prev, pred_viewangle_t **next ) +{ + int i, i0, i1, imod; + float at; + + imod = cl.angle_position - 1; + i0 = (imod + 1) & ANGLE_MASK; + i1 = (imod + 0) & ANGLE_MASK; + + if( cl.predicted_angle[i0].starttime >= t ) + { + for( i = 0; i < ANGLE_BACKUP - 2; i++ ) + { + at = cl.predicted_angle[imod & ANGLE_MASK].starttime; + if( at == 0.0f ) break; + + if( at < t ) + { + i0 = (imod + 1) & ANGLE_MASK; + i1 = (imod + 0) & ANGLE_MASK; + break; + } + imod--; + } + } + + *next = &cl.predicted_angle[i0]; + *prev = &cl.predicted_angle[i1]; + + // avoid division by zero (probably this should never happens) + if((*prev)->starttime == (*next)->starttime ) + { + *prev = *next; + *frac = 0.0f; + return; + } + + // time spans the two entries + *frac = ( t - (*prev)->starttime ) / ((*next)->starttime - (*prev)->starttime ); + *frac = bound( 0.0f, *frac, 1.0f ); +} + +void CL_ApplyAddAngle( void ) +{ + float curtime = cl.time - cl_serverframetime(); + pred_viewangle_t *prev = NULL, *next = NULL; + float addangletotal = 0.0f; + float amove, frac = 0.0f; + + CL_FindInterpolatedAddAngle( curtime, &frac, &prev, &next ); + + if( prev && next ) + addangletotal = prev->total + frac * ( next->total - prev->total ); + else addangletotal = cl.prevaddangletotal; + + amove = addangletotal - cl.prevaddangletotal; + + // update input angles + cl.viewangles[YAW] += amove; + + // remember last total + cl.prevaddangletotal = addangletotal; +} + + +/* +======================================================================= + +CLIENT MOVEMENT COMMUNICATION + +======================================================================= +*/ +/* +=============== +CL_ProcessShowTexturesCmds + +navigate around texture atlas +=============== +*/ +qboolean CL_ProcessShowTexturesCmds( usercmd_t *cmd ) +{ + static int oldbuttons; + int changed; + int pressed, released; + + if( !gl_showtextures->value || CL_IsDevOverviewMode( )) + return false; + + changed = (oldbuttons ^ cmd->buttons); + pressed = changed & cmd->buttons; + released = changed & (~cmd->buttons); + + if( released & ( IN_RIGHT|IN_MOVERIGHT )) + Cvar_SetValue( "r_showtextures", gl_showtextures->value + 1 ); + if( released & ( IN_LEFT|IN_MOVELEFT )) + Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + oldbuttons = cmd->buttons; + + return true; +} + +/* +=============== +CL_ProcessOverviewCmds + +Transform user movement into overview adjust +=============== +*/ +qboolean CL_ProcessOverviewCmds( usercmd_t *cmd ) +{ + ref_overview_t *ov = &clgame.overView; + int sign = 1; + float size = world.size[!ov->rotated] / world.size[ov->rotated]; + float step = (2.0f / size) * host.realframetime; + float step2 = step * 100.0f * (2.0f / ov->flZoom); + + if( !CL_IsDevOverviewMode() || gl_showtextures->value ) + return false; + + if( ov->flZoom < 0.0f ) sign = -1; + + if( cmd->upmove > 0.0f ) ov->zNear += step; + else if( cmd->upmove < 0.0f ) ov->zNear -= step; + + if( cmd->buttons & IN_JUMP ) ov->zFar += step; + else if( cmd->buttons & IN_DUCK ) ov->zFar -= step; + + if( cmd->buttons & IN_FORWARD ) ov->origin[ov->rotated] -= sign * step2; + else if( cmd->buttons & IN_BACK ) ov->origin[ov->rotated] += sign * step2; + + if( ov->rotated ) + { + if( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT )) + ov->origin[0] -= sign * step2; + else if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT )) + ov->origin[0] += sign * step2; + } + else + { + if( cmd->buttons & ( IN_RIGHT|IN_MOVERIGHT )) + ov->origin[1] += sign * step2; + else if( cmd->buttons & ( IN_LEFT|IN_MOVELEFT )) + ov->origin[1] -= sign * step2; + } + + if( cmd->buttons & IN_ATTACK ) ov->flZoom += step; + else if( cmd->buttons & IN_ATTACK2 ) ov->flZoom -= step; + + if( ov->flZoom == 0.0f ) ov->flZoom = 0.0001f; // to prevent disivion by zero + + return true; +} + +/* +================= +CL_UpdateClientData + +tell the client.dll about player origin, angles, fov, etc +================= +*/ +void CL_UpdateClientData( void ) +{ + client_data_t cdat; + + if( cls.state != ca_active ) + return; + + memset( &cdat, 0, sizeof( cdat ) ); + + VectorCopy( cl.viewangles, cdat.viewangles ); + VectorCopy( clgame.entities[cl.viewentity].origin, cdat.origin ); + cdat.iWeaponBits = cl.local.weapons; + cdat.fov = cl.local.scr_fov; + + if( clgame.dllFuncs.pfnUpdateClientData( &cdat, cl.time )) + { + // grab changes if successful + VectorCopy( cdat.viewangles, cl.viewangles ); + cl.local.scr_fov = cdat.fov; + } +} + +/* +================= +CL_CreateCmd +================= +*/ +void CL_CreateCmd( void ) +{ + usercmd_t cmd; + runcmd_t *pcmd; + vec3_t angles; + qboolean active; + int input_override; + int i, ms; + + if( cls.state < ca_connected || cls.state == ca_cinematic ) + return; + + // store viewangles in case it's will be freeze + VectorCopy( cl.viewangles, angles ); + ms = bound( 1, host.frametime * 1000, 255 ); + memset( &cmd, 0, sizeof( cmd )); + input_override = 0; + + CL_SetSolidEntities(); + CL_PushPMStates(); + CL_SetSolidPlayers( cl.playernum ); + + // message we are constructing. + i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; + pcmd = &cl.commands[i]; + pcmd->processedfuncs = false; + + if( !cls.demoplayback ) + { + pcmd->senttime = host.realtime; + memset( &pcmd->cmd, 0, sizeof( pcmd->cmd )); + pcmd->receivedtime = -1.0; + pcmd->heldback = false; + pcmd->sendsize = 0; + CL_ApplyAddAngle(); + } + + active = (( cls.signon == SIGNONS ) && !cl.paused && !cls.demoplayback ); + clgame.dllFuncs.CL_CreateMove( host.frametime, &pcmd->cmd, active ); + + CL_PopPMStates(); + + if( !cls.demoplayback ) + { + CL_ComputeClientInterpolationAmount( &pcmd->cmd ); + pcmd->cmd.lightlevel = cl.local.light_level; + pcmd->cmd.msec = ms; + } + + input_override |= CL_ProcessOverviewCmds( &pcmd->cmd ); + input_override |= CL_ProcessShowTexturesCmds( &pcmd->cmd ); + + if(( cl.background && !cls.demoplayback ) || input_override || cls.changelevel ) + { + VectorCopy( angles, pcmd->cmd.viewangles ); + VectorCopy( angles, cl.viewangles ); + if( !cl.background ) pcmd->cmd.msec = 0; + } + + // demo always have commands so don't overwrite them + if( !cls.demoplayback ) cl.cmd = &pcmd->cmd; + + // predict all unacknowledged movements + CL_PredictMovement( false ); +} + +void CL_WriteUsercmd( sizebuf_t *msg, int from, int to ) +{ + usercmd_t nullcmd; + usercmd_t *f, *t; + + Assert( from == -1 || ( from >= 0 && from < MULTIPLAYER_BACKUP )); + Assert( to >= 0 && to < MULTIPLAYER_BACKUP ); + + if( from == -1 ) + { + memset( &nullcmd, 0, sizeof( nullcmd )); + f = &nullcmd; + } + else + { + f = &cl.commands[from].cmd; + } + + t = &cl.commands[to].cmd; + + // write it into the buffer + MSG_WriteDeltaUsercmd( msg, f, t ); +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds +=================== +*/ +void CL_WritePacket( void ) +{ + sizebuf_t buf; + qboolean send_command = false; + byte data[MAX_CMD_BUFFER]; + int i, from, to, key, size; + int numbackup = 2; + int numcmds; + int newcmds; + int cmdnumber; + + // don't send anything if playing back a demo + if( cls.demoplayback || cls.state < ca_connected || cls.state == ca_cinematic ) + return; + + CL_ComputePacketLoss (); + + MSG_Init( &buf, "ClientData", data, sizeof( data )); + + // Determine number of backup commands to send along + numbackup = bound( 0, cl_cmdbackup->value, MAX_BACKUP_COMMANDS ); + if( cls.state == ca_connected ) numbackup = 0; + + // clamp cmdrate + if( cl_cmdrate->value < 0.0f ) Cvar_SetValue( "cl_cmdrate", 0.0f ); + else if( cl_cmdrate->value > 100.0f ) Cvar_SetValue( "cl_cmdrate", 100.0f ); + + // Check to see if we can actually send this command + + // In single player, send commands as fast as possible + // Otherwise, only send when ready and when not choking bandwidth + if( cl.maxclients == 1 || ( NET_IsLocalAddress( cls.netchan.remote_address ) && !host_limitlocal->value )) + send_command = true; + + if(( host.realtime >= cls.nextcmdtime ) && Netchan_CanPacket( &cls.netchan, true )) + send_command = true; + + if( cl.send_reply ) + { + cl.send_reply = false; + send_command = true; + } + + // spectator is not sending cmds to server + if( cls.spectator && cls.state == ca_active && cl.delta_sequence == cl.validsequence ) + { + if( !( cls.demorecording && cls.demowaiting ) && cls.nextcmdtime + 1.0f > host.realtime ) + return; + } + + if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) + { + if(( host.realtime - cls.netchan.last_received ) > CONNECTION_PROBLEM_TIME ) + { + Con_NPrintf( 1, "^3Warning:^1 Connection Problem^7\n" ); + cl.validsequence = 0; + } + } + + if( cl_nodelta->value ) + cl.validsequence = 0; + + if( send_command ) + { + int outgoing_sequence; + + if( cl_cmdrate->value > 0 ) // clamped between 10 and 100 fps + cls.nextcmdtime = host.realtime + bound( 0.1f, ( 1.0f / cl_cmdrate->value ), 0.01f ); + else cls.nextcmdtime = host.realtime; // always able to send right away + + if( cls.lastoutgoingcommand == -1 ) + { + outgoing_sequence = cls.netchan.outgoing_sequence; + cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; + } + else outgoing_sequence = cls.lastoutgoingcommand + 1; + + // begin a client move command + MSG_BeginClientCmd( &buf, clc_move ); + + // save the position for a checksum byte + key = MSG_GetRealBytesWritten( &buf ); + MSG_WriteByte( &buf, 0 ); + + // write packet lossage percentation + MSG_WriteByte( &buf, cls.packet_loss ); + + // say how many backups we'll be sending + MSG_WriteByte( &buf, numbackup ); + + // how many real commands have queued up + newcmds = ( cls.netchan.outgoing_sequence - cls.lastoutgoingcommand ); + + // put an upper/lower bound on this + newcmds = bound( 0, newcmds, MAX_TOTAL_CMDS ); + if( cls.state == ca_connected ) newcmds = 0; + + MSG_WriteByte( &buf, newcmds ); + + numcmds = newcmds + numbackup; + from = -1; + + for( i = numcmds - 1; i >= 0; i-- ) + { + cmdnumber = ( cls.netchan.outgoing_sequence - i ) & CL_UPDATE_MASK; + + to = cmdnumber; + CL_WriteUsercmd( &buf, from, to ); + from = to; + + if( MSG_CheckOverflow( &buf )) + Host_Error( "CL_WritePacket: overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); + } + + // calculate a checksum over the move commands + size = MSG_GetRealBytesWritten( &buf ) - key - 1; + buf.pData[key] = CRC32_BlockSequence( buf.pData + key + 1, size, cls.netchan.outgoing_sequence ); + + // message we are constructing. + i = cls.netchan.outgoing_sequence & CL_UPDATE_MASK; + + // determine if we need to ask for a new set of delta's. + if( cl.validsequence && (cls.state == ca_active) && !( cls.demorecording && cls.demowaiting )) + { + cl.delta_sequence = cl.validsequence; + + MSG_BeginClientCmd( &buf, clc_delta ); + MSG_WriteByte( &buf, cl.validsequence & 0xFF ); + } + else + { + // request delta compression of entities + cl.delta_sequence = -1; + } + + if( MSG_CheckOverflow( &buf )) + Host_Error( "CL_WritePacket: overflowed command buffer (%i bytes)\n", MAX_CMD_BUFFER ); + + // remember outgoing command that we are sending + cls.lastoutgoingcommand = cls.netchan.outgoing_sequence; + + // update size counter for netgraph + cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].sendsize = MSG_GetNumBytesWritten( &buf ); + cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = false; + + // composite the rest of the datagram.. + if( MSG_GetNumBitsWritten( &cls.datagram ) <= MSG_GetNumBitsLeft( &buf )) + MSG_WriteBits( &buf, MSG_GetData( &cls.datagram ), MSG_GetNumBitsWritten( &cls.datagram )); + MSG_Clear( &cls.datagram ); + + // deliver the message (or update reliable) + Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); + } + else + { + // mark command as held back so we'll send it next time + cl.commands[cls.netchan.outgoing_sequence & CL_UPDATE_MASK].heldback = true; + + // increment sequence number so we can detect that we've held back packets. + cls.netchan.outgoing_sequence++; + } + + if( cls.demorecording && numbackup > 0 ) + { + // Back up one because we've incremented outgoing_sequence each frame by 1 unit + cmdnumber = ( cls.netchan.outgoing_sequence - 1 ) & CL_UPDATE_MASK; + CL_WriteDemoUserCmd( cmdnumber ); + } + + // update download/upload slider. + Netchan_UpdateProgress( &cls.netchan ); +} + +/* +================= +CL_SendCommand + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCommand( void ) +{ + // we create commands even if a demo is playing, + CL_CreateCmd(); + + // clc_move, userinfo etc + CL_WritePacket(); +} + +/* +================== +CL_BeginUpload_f +================== +*/ +void CL_BeginUpload_f( void ) +{ + char *name; + resource_t custResource; + byte *buf = NULL; + int size = 0; + byte md5[16]; + + name = Cmd_Argv( 1 ); + + if( !COM_CheckString( name )) + { + MsgDev( D_ERROR, "upload without filename\n" ); + return; + } + + if( !cl_allow_upload.value ) + { + MsgDev( D_WARN, "ingoring decal upload ( cl_allow_upload is 0 )\n" ); + return; + } + + if( Q_strlen( name ) != 36 || Q_strnicmp( name, "!MD5", 4 )) + { + Con_Printf( "Ingoring upload of non-customization\n" ); + return; + } + + memset( &custResource, 0, sizeof( custResource )); + COM_HexConvert( name + 4, 32, md5 ); + + if( HPAK_ResourceForHash( CUSTOM_RES_PATH, md5, &custResource )) + { + if( memcmp( md5, custResource.rgucMD5_hash, 16 )) + { + MsgDev( D_REPORT, "Bogus data retrieved from %s, attempting to delete entry\n", CUSTOM_RES_PATH ); + HPAK_RemoveLump( CUSTOM_RES_PATH, &custResource ); + return; + } + + if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &custResource, &buf, &size )) + { + byte md5[16]; + MD5Context_t ctx; + + memset( &ctx, 0, sizeof( ctx )); + MD5Init( &ctx ); + MD5Update( &ctx, buf, size ); + MD5Final( md5, &ctx ); + + if( memcmp( custResource.rgucMD5_hash, md5, 16 )) + { + MsgDev( D_REPORT, "HPAK_AddLump called with bogus lump, md5 mismatch\n" ); + MsgDev( D_REPORT, "Purported: %s\n", MD5_Print( custResource.rgucMD5_hash ) ); + MsgDev( D_REPORT, "Actual : %s\n", MD5_Print( md5 ) ); + MsgDev( D_REPORT, "Removing conflicting lump\n" ); + HPAK_RemoveLump( CUSTOM_RES_PATH, &custResource ); + return; + } + } + } + + if( buf && size ) + { + Netchan_CreateFileFragmentsFromBuffer( &cls.netchan, name, buf, size ); + Netchan_FragSend( &cls.netchan ); + Mem_Free( buf ); + } + else + { + MsgDev( D_REPORT, "ingoring customization upload, couldn't find decal locally\n" ); + } +} + +/* +================== +CL_Quit_f +================== +*/ +void CL_Quit_f( void ) +{ + CL_Disconnect(); + Sys_Quit(); +} + +/* +================ +CL_Drop + +Called after an Host_Error was thrown +================ +*/ +void CL_Drop( void ) +{ + if( !cls.initialized ) + return; + CL_Disconnect(); +} + +/* +======================= +CL_GetCDKeyHash() + +Connections will now use a hashed cd key value +======================= +*/ +char *CL_GetCDKeyHash( void ) +{ + const char *keyBuffer; + static char szHashedKeyBuffer[256]; + int nKeyLength = 0; + byte digest[17]; // The MD5 Hash + MD5Context_t ctx; + + keyBuffer = Sys_GetMachineKey( &nKeyLength ); + + // now get the md5 hash of the key + memset( &ctx, 0, sizeof( ctx )); + memset( digest, 0, sizeof( digest )); + + MD5Init( &ctx ); + MD5Update( &ctx, (byte *)keyBuffer, nKeyLength ); + MD5Final( digest, &ctx ); + digest[16] = '\0'; + + memset( szHashedKeyBuffer, 0, 256 ); + Q_strncpy( szHashedKeyBuffer, MD5_Print( digest ), sizeof( szHashedKeyBuffer )); + + return szHashedKeyBuffer; +} + +/* +======================= +CL_SendConnectPacket + +We have gotten a challenge from the server, so try and +connect. +====================== +*/ +void CL_SendConnectPacket( void ) +{ + char protinfo[MAX_INFO_STRING]; + char *qport; + char *key; + netadr_t adr; + + if( !NET_StringToAdr( cls.servername, &adr )) + { + Con_Printf( "CL_SendConnectPacket: bad server address\n"); + cls.connect_time = 0; + return; + } + + if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); + qport = Cvar_VariableString( "net_qport" ); + key = CL_GetCDKeyHash(); + + memset( protinfo, 0, sizeof( protinfo )); + Info_SetValueForKey( protinfo, "uuid", key, sizeof( protinfo )); + Info_SetValueForKey( protinfo, "qport", qport, sizeof( protinfo )); + + Netchan_OutOfBandPrint( NS_CLIENT, adr, "connect %i %i \"%s\" \"%s\"\n", PROTOCOL_VERSION, cls.challenge, protinfo, cls.userinfo ); + cls.timestart = Sys_DoubleTime(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) +{ + netadr_t adr; + + // if the local server is running and we aren't then connect + if( cls.state == ca_disconnected && SV_Active( )) + { + cls.signon = 0; + cls.state = ca_connecting; + Q_strncpy( cls.servername, "localhost", sizeof( cls.servername )); + // we don't need a challenge on the localhost + CL_SendConnectPacket(); + return; + } + + // resend if we haven't gotten a reply yet + if( cls.demoplayback || cls.state != ca_connecting ) + return; + + if( cl_resend.value < CL_MIN_RESEND_TIME ) + Cvar_SetValue( "cl_resend", CL_MIN_RESEND_TIME ); + else if( cl_resend.value > CL_MAX_RESEND_TIME ) + Cvar_SetValue( "cl_resend", CL_MAX_RESEND_TIME ); + + if(( host.realtime - cls.connect_time ) < cl_resend.value ) + return; + + if( !NET_StringToAdr( cls.servername, &adr )) + { + MsgDev( D_ERROR, "CL_CheckForResend: bad server address\n" ); + CL_Disconnect(); + return; + } + + // only retry so many times before failure. + if( cls.connect_retry >= CL_CONNECTION_RETRIES ) + { + MsgDev( D_ERROR, "CL_CheckForResend: couldn't connected\n" ); + CL_Disconnect(); + return; + } + + if( adr.port == 0 ) adr.port = MSG_BigShort( PORT_SERVER ); + + if( cls.connect_retry == CL_TEST_RETRIES ) + { + // too many fails use default connection method + Netchan_OutOfBandPrint( NS_CLIENT, adr, "getchallenge\n" ); + Cvar_SetValue( "cl_dlmax", FRAGMENT_MAX_SIZE ); + cls.connect_time = host.realtime; + cls.connect_retry++; + return; + } + + cls.max_fragment_size = Q_max( FRAGMENT_MAX_SIZE, cls.max_fragment_size >> Q_min( 1, cls.connect_retry )); + cls.connect_time = host.realtime; // for retransmit requests + cls.connect_retry++; + + Con_Printf( "Connecting to %s...\n", cls.servername ); + + if( cl_test_bandwidth.value ) + Netchan_OutOfBandPrint( NS_CLIENT, adr, "bandwidth %i %i\n", PROTOCOL_VERSION, cls.max_fragment_size ); + else Netchan_OutOfBandPrint( NS_CLIENT, adr, "getchallenge\n" ); +} + +resource_t *CL_AddResource( resourcetype_t type, const char *name, int size, qboolean bFatalIfMissing, int index ) +{ + resource_t *r = &cl.resourcelist[cl.num_resources]; + + if( cl.num_resources >= MAX_RESOURCES ) + Host_Error( "Too many resources on client\n" ); + cl.num_resources++; + + Q_strncpy( r->szFileName, name, sizeof( r->szFileName )); + r->ucFlags |= bFatalIfMissing ? RES_FATALIFMISSING : 0; + r->nDownloadSize = size; + r->nIndex = index; + r->type = type; + + return r; +} + +void CL_CreateResourceList( void ) +{ + char szFileName[MAX_OSPATH]; + byte rgucMD5_hash[16]; + resource_t *pNewResource; + int nSize; + file_t *fp; + + HPAK_FlushHostQueue(); + cl.num_resources = 0; + + Q_snprintf( szFileName, sizeof( szFileName ), "logos/remapped.bmp" ); + memset( rgucMD5_hash, 0, sizeof( rgucMD5_hash )); + + fp = FS_Open( szFileName, "rb", true ); + + if( fp ) + { + MD5_HashFile( rgucMD5_hash, szFileName, NULL ); + nSize = FS_FileLength( fp ); + + if( nSize != 0 ) + { + pNewResource = CL_AddResource( t_decal, szFileName, nSize, false, 0 ); + + if( pNewResource ) + { + SetBits( pNewResource->ucFlags, RES_CUSTOM ); + memcpy( pNewResource->rgucMD5_hash, rgucMD5_hash, 16 ); + HPAK_AddLump( false, CUSTOM_RES_PATH, pNewResource, NULL, fp ); + } + } + FS_Close( fp ); + } +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) +{ + string server; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "connect \n" ); + return; + } + + Q_strncpy( server, Cmd_Argv( 1 ), sizeof( server )); + + // if running a local server, kill it and reissue +// if( SV_Active( )) Host_ShutdownServer(); + NET_Config( true ); // allow remote + + Con_Printf( "server %s\n", server ); + CL_Disconnect(); + + cls.state = ca_connecting; + Q_strncpy( cls.servername, server, sizeof( cls.servername )); + cls.connect_time = MAX_HEARTBEAT; // CL_CheckForResend() will fire immediately + cls.max_fragment_size = FRAGMENT_MAX_SIZE; // guess a we can establish connection with maximum fragment size + cls.connect_retry = 0; + cls.spectator = false; + cls.signon = 0; +} + +/* +===================== +CL_Rcon_f + +Send the rest of the command line over as +an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) +{ + char message[1024]; + netadr_t to; + int i; + + if( !COM_CheckString( rcon_client_password->string )) + { + Con_Printf( "You must set 'rcon_password' before issuing an rcon command.\n" ); + return; + } + + message[0] = (char)255; + message[1] = (char)255; + message[2] = (char)255; + message[3] = (char)255; + message[4] = 0; + + NET_Config( true ); // allow remote + + Q_strcat( message, "rcon " ); + Q_strcat( message, rcon_client_password->string ); + Q_strcat( message, " " ); + + for( i = 1; i < Cmd_Argc(); i++ ) + { + Q_strcat( message, Cmd_Argv( i )); + Q_strcat( message, " " ); + } + + if( cls.state >= ca_connected ) + { + to = cls.netchan.remote_address; + } + else + { + if( !COM_CheckString( rcon_address->string )) + { + Con_Printf( "You must either be connected or set the 'rcon_address' cvar to issue rcon commands\n" ); + return; + } + + NET_StringToAdr( rcon_address->string, &to ); + if( to.port == 0 ) to.port = MSG_BigShort( PORT_SERVER ); + } + + NET_SendPacket( NS_CLIENT, Q_strlen( message ) + 1, message, to ); +} + + +/* +===================== +CL_ClearState + +===================== +*/ +void CL_ClearState( void ) +{ + int i; + + CL_ClearResourceLists(); + + for( i = 0; i < MAX_CLIENTS; i++ ) + COM_ClearCustomizationList( &cl.players[i].customdata, false ); + + S_StopAllSounds ( true ); + CL_ClearEffects (); + CL_FreeEdicts (); + + CL_ClearPhysEnts (); + NetAPI_CancelAllRequests(); + + // wipe the entire cl structure + memset( &cl, 0, sizeof( cl )); + MSG_Clear( &cls.netchan.message ); + memset( &clgame.fade, 0, sizeof( clgame.fade )); + memset( &clgame.shake, 0, sizeof( clgame.shake )); + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + cl.maxclients = 1; // allow to drawing player in menu + cl.mtime[0] = cl.mtime[1] = 1.0f; // because level starts from 1.0f second + cls.signon = 0; + + cl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded; + cl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand; + + CL_CreateResourceList(); + CL_ClearSpriteTextures(); // now all hud sprites are invalid + + cl.local.interp_amount = 0.1f; + cl.local.scr_fov = 90.0f; + + Cvar_SetValue( "scr_download", -1.0f ); + Cvar_SetValue( "scr_loading", 0.0f ); + host.allow_console = host.allow_console_init; +} + +/* +===================== +CL_SendDisconnectMessage + +Sends a disconnect message to the server +===================== +*/ +void CL_SendDisconnectMessage( void ) +{ + sizebuf_t buf; + byte data[32]; + + if( cls.state == ca_disconnected ) return; + + MSG_Init( &buf, "LastMessage", data, sizeof( data )); + MSG_BeginClientCmd( &buf, clc_stringcmd ); + MSG_WriteString( &buf, "disconnect" ); + + if( !cls.netchan.remote_address.type ) + cls.netchan.remote_address.type = NA_LOOPBACK; + + // make sure message will be delivered + Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); + Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); + Netchan_TransmitBits( &cls.netchan, MSG_GetNumBitsWritten( &buf ), MSG_GetData( &buf )); +} + +/* +===================== +CL_Reconnect + +build a request to reconnect client +===================== +*/ +void CL_Reconnect( qboolean setup_netchan ) +{ + if( setup_netchan ) + { + Netchan_Setup( NS_CLIENT, &cls.netchan, net_from, Cvar_VariableInteger( "net_qport" ), NULL, CL_GetFragmentSize ); + } + else + { + // clear channel and stuff + Netchan_Clear( &cls.netchan ); + MSG_Clear( &cls.netchan.message ); + } + + cls.demonum = cls.movienum = -1; // not in the demo loop now + cls.state = ca_connected; + cls.signon = 0; + + CL_ServerCommand( true, "new" ); + + cl.validsequence = 0; // haven't gotten a valid frame update yet + cl.delta_sequence = -1; // we'll request a full delta from the baseline + cls.lastoutgoingcommand = -1; // we don't have a backed up cmd history yet + cls.nextcmdtime = host.realtime; // we can send a cmd right away + cl.last_command_ack = -1; + + CL_StartupDemoHeader (); +} + +/* +===================== +CL_Disconnect + +Goes from a connected state to full screen console state +Sends a disconnect message to the server +This is also called on Host_Error, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( void ) +{ + if( cls.state == ca_disconnected ) + return; + + cls.connect_time = 0; + cls.changedemo = false; + cls.max_fragment_size = FRAGMENT_MAX_SIZE; // reset fragment size + CL_Stop_f(); + + // send a disconnect message to the server + CL_SendDisconnectMessage(); + CL_ClearState (); + + S_StopBackgroundTrack (); + SCR_EndLoadingPlaque (); // get rid of loading plaque + + // clear the network channel, too. + Netchan_Clear( &cls.netchan ); + + cls.state = ca_disconnected; + cls.connect_retry = 0; + cls.signon = 0; + + // back to menu in non-developer mode + if( host_developer.value || CL_IsInMenu( )) + return; + + UI_SetActiveMenu( true ); +} + +void CL_Disconnect_f( void ) +{ + if( Host_IsLocalClient( )) + Host_EndGame( true, "disconnected from server\n" ); + else CL_Disconnect(); +} + +void CL_Crashed( void ) +{ + // already freed + if( host.status == HOST_CRASHED ) return; + if( host.type != HOST_NORMAL ) return; + if( !cls.initialized ) return; + + host.status = HOST_CRASHED; + + CL_Stop_f(); // stop any demos + + // send a disconnect message to the server + CL_SendDisconnectMessage(); + + Host_WriteOpenGLConfig(); + Host_WriteConfig(); // write config +} + +/* +================= +CL_LocalServers_f +================= +*/ +void CL_LocalServers_f( void ) +{ + netadr_t adr; + + Con_Printf( "Scanning for servers on the local network area...\n" ); + NET_Config( true ); // allow remote + + // send a broadcast packet + adr.type = NA_BROADCAST; + adr.port = MSG_BigShort( PORT_SERVER ); + + Netchan_OutOfBandPrint( NS_CLIENT, adr, "info %i", PROTOCOL_VERSION ); +} + +/* +================= +CL_InternetServers_f +================= +*/ +void CL_InternetServers_f( void ) +{ + netadr_t adr; + char fullquery[512] = "1\xFF" "0.0.0.0:0\0" "\\gamedir\\"; + + Con_Printf( "Scanning for servers on the internet area...\n" ); + NET_Config( true ); // allow remote + + if( !NET_StringToAdr( MASTERSERVER_ADR, &adr ) ) + MsgDev( D_ERROR, "Can't resolve adr: %s\n", MASTERSERVER_ADR ); + + Q_strcpy( &fullquery[22], GI->gamedir ); + + NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamedir ) + 23, fullquery, adr ); + + // now we clearing the vgui request + if( clgame.master_request != NULL ) + memset( clgame.master_request, 0, sizeof( net_request_t )); + clgame.request_type = NET_REQUEST_GAMEUI; +} + +/* +================= +CL_Reconnect_f + +The server is changing levels +================= +*/ +void CL_Reconnect_f( void ) +{ + if( cls.state == ca_disconnected ) + return; + + S_StopAllSounds ( true ); + + if( cls.state == ca_connected ) + { + CL_Reconnect( false ); + return; + } + + if( COM_CheckString( cls.servername )) + { + if( cls.state >= ca_connected ) + CL_Disconnect(); + + cls.connect_time = MAX_HEARTBEAT; // fire immediately + cls.demonum = cls.movienum = -1; // not in the demo loop now + cls.state = ca_connecting; + cls.signon = 0; + + Con_Printf( "reconnecting...\n" ); + } +} + +/* +================= +CL_FixupColorStringsForInfoString + +all the keys and values must be ends with ^7 +================= +*/ +void CL_FixupColorStringsForInfoString( const char *in, char *out ) +{ + qboolean hasPrefix = false; + qboolean endOfKeyVal = false; + int color = 7; + int count = 0; + + if( *in == '\\' ) + { + *out++ = *in++; + count++; + } + + while( *in && count < MAX_INFO_STRING ) + { + if( IsColorString( in )) + color = ColorIndex( *(in+1)); + + // color the not reset while end of key (or value) was found! + if( *in == '\\' && color != 7 ) + { + if( IsColorString( out - 2 )) + { + *(out - 1) = '7'; + } + else + { + *out++ = '^'; + *out++ = '7'; + count += 2; + } + color = 7; + } + + *out++ = *in++; + count++; + } + + // check the remaining value + if( color != 7 ) + { + // if the ends with another color rewrite it + if( IsColorString( out - 2 )) + { + *(out - 1) = '7'; + } + else + { + *out++ = '^'; + *out++ = '7'; + count += 2; + } + } + + *out = '\0'; +} + +/* +================= +CL_ParseStatusMessage + +Handle a reply from a info +================= +*/ +void CL_ParseStatusMessage( netadr_t from, sizebuf_t *msg ) +{ + static char infostring[MAX_INFO_STRING+8]; + char *s = MSG_ReadString( msg ); + + CL_FixupColorStringsForInfoString( s, infostring ); + + // more info about servers + Con_Printf( "Server: %s, Game: %s\n", NET_AdrToString( from ), Info_ValueForKey( infostring, "gamedir" )); + + UI_AddServerToList( from, infostring ); +} + +/* +================= +CL_ParseNETInfoMessage + +Handle a reply from a netinfo +================= +*/ +void CL_ParseNETInfoMessage( netadr_t from, sizebuf_t *msg, const char *s ) +{ + net_request_t *nr; + static char infostring[MAX_INFO_STRING+8]; + int i, context, type; + int errorBits = 0; + char *val; + + context = Q_atoi( Cmd_Argv( 1 )); + type = Q_atoi( Cmd_Argv( 2 )); + while( *s != '\\' ) s++; // fetching infostring + + // check for errors + val = Info_ValueForKey( s, "neterror" ); + + if( !Q_stricmp( val, "protocol" )) + SetBits( errorBits, NET_ERROR_PROTO_UNSUPPORTED ); + else if( !Q_stricmp( val, "undefined" )) + SetBits( errorBits, NET_ERROR_UNDEFINED ); + + CL_FixupColorStringsForInfoString( s, infostring ); + + // find a request with specified context + for( i = 0; i < MAX_REQUESTS; i++ ) + { + nr = &clgame.net_requests[i]; + + if( nr->resp.context == context && nr->resp.type == type ) + { + // setup the answer + nr->resp.response = infostring; + nr->resp.remote_address = from; + nr->resp.error = NET_SUCCESS; + nr->resp.ping = host.realtime - nr->timesend; + + if( nr->timeout <= host.realtime ) + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + SetBits( nr->resp.error, errorBits ); // misc error bits + + nr->pfnFunc( &nr->resp ); + + if( !FBitSet( nr->flags, FNETAPI_MULTIPLE_RESPONSE )) + memset( nr, 0, sizeof( *nr )); // done + return; + } + } +} + +/* +================= +CL_ProcessNetRequests + +check for timeouts +================= +*/ +void CL_ProcessNetRequests( void ) +{ + net_request_t *nr; + int i; + + // find a request with specified context + for( i = 0; i < MAX_REQUESTS; i++ ) + { + nr = &clgame.net_requests[i]; + if( !nr->pfnFunc ) continue; // not used + + if( nr->timeout <= host.realtime ) + { + // setup the answer + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + nr->resp.ping = host.realtime - nr->timesend; + + nr->pfnFunc( &nr->resp ); + memset( nr, 0, sizeof( *nr )); // done + } + } +} + +//=================================================================== +/* +=============== +CL_SetupOverviewParams + +Get initial overview values +=============== +*/ +void CL_SetupOverviewParams( void ) +{ + ref_overview_t *ov = &clgame.overView; + float mapAspect, screenAspect, aspect; + + ov->rotated = ( world.size[1] <= world.size[0] ) ? true : false; + + // calculate nearest aspect + mapAspect = world.size[!ov->rotated] / world.size[ov->rotated]; + screenAspect = (float)glState.width / (float)glState.height; + aspect = Q_max( mapAspect, screenAspect ); + + ov->zNear = world.maxs[2]; + ov->zFar = world.mins[2]; + ov->flZoom = ( 8192.0f / world.size[ov->rotated] ) / aspect; + + VectorAverage( world.mins, world.maxs, ov->origin ); + + memset( &cls.spectator_state, 0, sizeof( cls.spectator_state )); + + if( cls.spectator ) + { + cls.spectator_state.playerstate.friction = 1; + cls.spectator_state.playerstate.gravity = 1; + cls.spectator_state.playerstate.number = cl.playernum + 1; + cls.spectator_state.playerstate.usehull = 1; + cls.spectator_state.playerstate.movetype = MOVETYPE_NOCLIP; + cls.spectator_state.client.maxspeed = clgame.movevars.spectatormaxspeed; + } +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) +{ + char *args; + char *c, buf[MAX_SYSPATH]; + int len = sizeof( buf ); + int dataoffset = 0; + netadr_t servadr; + + MSG_Clear( msg ); + MSG_ReadLong( msg ); // skip the -1 + + args = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( args ); + c = Cmd_Argv( 0 ); + + MsgDev( D_NOTE, "CL_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), c ); + + // server connection + if( !Q_strcmp( c, "client_connect" )) + { + if( cls.state == ca_connected ) + { + MsgDev( D_ERROR, "dup connect received. ignored\n"); + return; + } + + CL_Reconnect( true ); + UI_SetActiveMenu( cl.background ); + } + else if( !Q_strcmp( c, "info" )) + { + // server responding to a status broadcast + CL_ParseStatusMessage( from, msg ); + } + else if( !Q_strcmp( c, "netinfo" )) + { + // server responding to a status broadcast + CL_ParseNETInfoMessage( from, msg, args ); + } + else if( !Q_strcmp( c, "cmd" )) + { + // remote command from gui front end + if( !NET_IsLocalAddress( from )) + { + Con_Printf( "Command packet from remote host. Ignored.\n" ); + return; + } + + ShowWindow( host.hWnd, SW_RESTORE ); + SetForegroundWindow ( host.hWnd ); + args = MSG_ReadString( msg ); + Cbuf_AddText( args ); + Cbuf_AddText( "\n" ); + } + else if( !Q_strcmp( c, "print" )) + { + // print command from somewhere + Con_Printf( "%s", MSG_ReadString( msg )); + } + else if( !Q_strcmp( c, "testpacket" )) + { + byte recv_buf[NET_MAX_FRAGMENT]; + dword crcValue = MSG_ReadLong( msg ); + int realsize = MSG_GetMaxBytes( msg ) - MSG_GetNumBytesRead( msg ); + dword crcValue2 = 0; + + if( cls.max_fragment_size != MSG_GetMaxBytes( msg )) + { + if( cls.connect_retry >= CL_TEST_RETRIES ) + { + // too many fails use default connection method + Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); + Cvar_SetValue( "cl_dlmax", FRAGMENT_MAX_SIZE ); + cls.connect_time = host.realtime; + return; + } + + // if we waiting more than cl_timeout or packet was trashed + Msg( "got testpacket, size mismatched %d should be %d\n", MSG_GetMaxBytes( msg ), cls.max_fragment_size ); + cls.connect_time = MAX_HEARTBEAT; + return; // just wait for a next responce + } + + // reading test buffer + MSG_ReadBytes( msg, recv_buf, realsize ); + + // procssing the CRC + CRC32_ProcessBuffer( &crcValue2, recv_buf, realsize ); + + if( crcValue == crcValue2 ) + { + // packet was sucessfully delivered, adjust the fragment size and get challenge + Msg( "CRC %p is matched, get challenge, fragment size %d\n", crcValue, cls.max_fragment_size ); + Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); + Cvar_SetValue( "cl_dlmax", cls.max_fragment_size ); + cls.connect_time = host.realtime; + } + else + { + if( cls.connect_retry >= CL_TEST_RETRIES ) + { + // too many fails use default connection method + Netchan_OutOfBandPrint( NS_CLIENT, from, "getchallenge\n" ); + Cvar_SetValue( "cl_dlmax", FRAGMENT_MAX_SIZE ); + cls.connect_time = host.realtime; + return; + } + + Msg( "got testpacket, CRC mismatched %p should be %p, trying next fragment size %d\n", crcValue2, crcValue, cls.max_fragment_size >> 1 ); + // trying the next size of packet + cls.connect_time = MAX_HEARTBEAT; + } + } + else if( !Q_strcmp( c, "ping" )) + { + // ping from somewhere + Netchan_OutOfBandPrint( NS_CLIENT, from, "ack" ); + } + else if( !Q_strcmp( c, "challenge" )) + { + // challenge from the server we are connecting to + cls.challenge = Q_atoi( Cmd_Argv( 1 )); + CL_SendConnectPacket(); + return; + } + else if( !Q_strcmp( c, "echo" )) + { + // echo request from server + Netchan_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv( 1 )); + } + else if( !Q_strcmp( c, "disconnect" )) + { + // a disconnect message from the server, which will happen if the server + // dropped the connection but it is still getting packets from us + CL_Disconnect_f(); + } + else if( !Q_strcmp( c, "f" )) + { + // serverlist got from masterserver + while( MSG_GetNumBitsLeft( msg ) > 8 ) + { + MSG_ReadBytes( msg, servadr.ip, sizeof( servadr.ip )); // 4 bytes for IP + servadr.port = MSG_ReadShort( msg ); // 2 bytes for Port + servadr.type = NA_IP; + + // list is ends here + if( !servadr.port ) + { + if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) + { + net_request_t *nr = clgame.master_request; + net_adrlist_t *list, **prev; + + // setup the answer + nr->resp.remote_address = from; + nr->resp.error = NET_SUCCESS; + nr->resp.ping = host.realtime - nr->timesend; + + if( nr->timeout <= host.realtime ) + SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); + + Con_Printf( "serverlist call: %s\n", NET_AdrToString( from )); + nr->pfnFunc( &nr->resp ); + + // throw the list, now it will be stored in user area + prev = &((net_adrlist_t *)nr->resp.response); + + while( 1 ) + { + list = *prev; + if( !list ) break; + + // throw out any variables the game created + *prev = list->next; + Mem_Free( list ); + } + memset( nr, 0, sizeof( *nr )); // done + clgame.request_type = NET_REQUEST_CANCEL; + clgame.master_request = NULL; + } + break; + } + + if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) + { + net_request_t *nr = clgame.master_request; + net_adrlist_t *list; + + // adding addresses into list + list = Z_Malloc( sizeof( *list )); + list->remote_address = servadr; + list->next = nr->resp.response; + nr->resp.response = list; + } + else if( clgame.request_type == NET_REQUEST_GAMEUI ) + { + NET_Config( true ); // allow remote + Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); + } + } + } + else if( clgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) + { + // user out of band message (must be handled in CL_ConnectionlessPacket) + if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, buf ); + } + else MsgDev( D_ERROR, "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); +} + +/* +==================== +CL_GetMessage + +Handles recording and playback of demos, on top of NET_ code +==================== +*/ +int CL_GetMessage( byte *data, size_t *length ) +{ + if( cls.demoplayback ) + { + if( CL_DemoReadMessage( data, length )) + return true; + return false; + } + + if( NET_GetPacket( NS_CLIENT, &net_from, data, length )) + return true; + return false; +} + +/* +================= +CL_ReadNetMessage +================= +*/ +void CL_ReadNetMessage( void ) +{ + size_t curSize; + + while( CL_GetMessage( net_message_buffer, &curSize )) + { + MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); + + // check for connectionless packet (0xffffffff) first + if( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) + { + CL_ConnectionlessPacket( net_from, &net_message ); + continue; + } + + // can't be a valid sequenced packet + if( cls.state < ca_connected ) continue; + + if( !cls.demoplayback && MSG_GetMaxBytes( &net_message ) < 8 ) + { + MsgDev( D_WARN, "%s: runt packet\n", NET_AdrToString( net_from )); + continue; + } + + // packet from server + if( !cls.demoplayback && !NET_CompareAdr( net_from, cls.netchan.remote_address )) + { + MsgDev( D_ERROR, "CL_ReadPackets: %s:sequenced packet without connection\n", NET_AdrToString( net_from )); + continue; + } + + if( !cls.demoplayback && !Netchan_Process( &cls.netchan, &net_message )) + continue; // wasn't accepted for some reason + + CL_ParseServerMessage( &net_message, true ); + cl.send_reply = true; + } + + // build list of all solid entities per next frame (exclude clients) + CL_SetSolidEntities(); + + // check for fragmentation/reassembly related packets. + if( cls.state != ca_disconnected && Netchan_IncomingReady( &cls.netchan )) + { + // process the incoming buffer(s) + if( Netchan_CopyNormalFragments( &cls.netchan, &net_message, &curSize )) + { + MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); + CL_ParseServerMessage( &net_message, false ); + } + + if( Netchan_CopyFileFragments( &cls.netchan, &net_message )) + { + // remove from resource request stuff. + CL_ProcessFile( true, cls.netchan.incomingfilename ); + } + } + + Netchan_UpdateProgress( &cls.netchan ); + + // check requests for time-expire + CL_ProcessNetRequests(); +} + +/* +================= +CL_ReadPackets + +Updates the local time and reads/handles messages +on client net connection. +================= +*/ +void CL_ReadPackets( void ) +{ + // decide the simulation time + cl.oldtime = cl.time; + + if( !cls.demoplayback && !cl.paused ) + cl.time += host.frametime; + + // demo time + if( cls.demorecording && !cls.demowaiting ) + cls.demotime += host.frametime; + + CL_ReadNetMessage(); +#if 0 + // keep cheat cvars are unchanged + if( cl.maxclients > 1 && cls.state == ca_active && !host_developer.value ) + Cvar_SetCheatState(); +#endif + // singleplayer never has connection timeout + if( NET_IsLocalAddress( cls.netchan.remote_address )) + return; + + // hot precache and downloading resources + if( cls.signon == SIGNONS && cl.lastresourcecheck < host.realtime ) + { + if( !cls.dl.custom && cl.resourcesneeded.pNext != &cl.resourcesneeded ) + { + // check resource for downloading and precache + CL_EstimateNeededResources(); + CL_BatchResourceRequest( false ); + cls.dl.custom = true; + } + cl.lastresourcecheck = host.realtime + 5.0f; // don't checking too often + } + + // if in the debugger last frame, don't timeout + if( host.frametime > 5.0f ) cls.netchan.last_received = Sys_DoubleTime(); + + // check timeout + if( cls.state >= ca_connected && cls.state != ca_cinematic && !cls.demoplayback ) + { + if( host.realtime - cls.netchan.last_received > cl_timeout->value ) + { + Con_Printf( "\nServer connection timed out.\n" ); + CL_Disconnect(); + return; + } + } + +} + +/* +==================== +CL_CleanFileName + +Replace the displayed name for some resources +==================== +*/ +const char *CL_CleanFileName( const char *filename ) +{ + const char *pfilename = filename; + + if( COM_CheckString( filename ) && filename[0] == '!' ) + pfilename = "customization"; + return pfilename; +} + + +/* +==================== +CL_RegisterCustomization + +register custom resource for player +==================== +*/ +void CL_RegisterCustomization( resource_t *resource ) +{ + qboolean bFound = false; + customization_t *pList; + + for( pList = cl.players[resource->playernum].customdata.pNext; pList; pList = pList->pNext ) + { + if( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 )) + { + bFound = true; + break; + } + } + + if( !bFound ) + { + player_info_t *player = &cl.players[resource->playernum]; + + if( !COM_CreateCustomization( &player->customdata, resource, resource->playernum, FCUST_FROMHPAK, NULL, NULL )) + Con_Printf( "Unable to create custom decal for player %i\n", resource->playernum ); + } + else + { + Con_DPrintf( "Duplicate resource received and ignored.\n" ); + } +} + +/* +==================== +CL_ProcessFile + +A file has been received via the fragmentation/reassembly layer, put it in the right spot and + see if we have finished downloading files. +==================== +*/ +void CL_ProcessFile( qboolean successfully_received, const char *filename ) +{ + int sound_len = Q_strlen( DEFAULT_SOUNDPATH ); + byte rgucMD5_hash[16]; + const char *pfilename; + resource_t *p; + + if( COM_CheckString( filename ) && successfully_received ) + { + if( filename[0] != '!' ) + Con_Printf( "processing %s\n", filename ); + } + else if( !successfully_received ) + { + Con_Printf( S_ERROR "server failed to transmit file '%s'\n", CL_CleanFileName( filename )); + } + + pfilename = filename; + + if( !Q_strnicmp( filename, DEFAULT_SOUNDPATH, sound_len )) + pfilename += sound_len; + + for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) + { + if( !Q_strnicmp( filename, "!MD5", 4 )) + { + COM_HexConvert( filename + 4, 32, rgucMD5_hash ); + + if( !memcmp( p->rgucMD5_hash, rgucMD5_hash, 16 )) + break; + } + else + { + if( p->type == t_generic ) + { + if( !Q_stricmp( p->szFileName, filename )) + break; + } + else + { + if( !Q_stricmp( p->szFileName, pfilename )) + break; + } + } + } + + if( p != &cl.resourcesneeded ) + { + if( successfully_received ) + ClearBits( p->ucFlags, RES_WASMISSING ); + + if( filename[0] == '!' ) + { + if( cls.netchan.tempbuffer ) + { + if( p->nDownloadSize == cls.netchan.tempbuffersize ) + { + if( p->ucFlags & RES_CUSTOM ) + { + HPAK_AddLump( true, CUSTOM_RES_PATH, p, cls.netchan.tempbuffer, NULL ); + CL_RegisterCustomization( p ); + } + } + else + { + Con_Printf( "Downloaded %i bytes for purported %i byte file, ignoring download\n", p->nDownloadSize ); + } + + if( cls.netchan.tempbuffer ) + Mem_Free( cls.netchan.tempbuffer ); + } + + cls.netchan.tempbuffersize = 0; + cls.netchan.tempbuffer = NULL; + } + + // moving to 'onhandle' list even if file was missed + CL_MoveToOnHandList( p ); + } + + if( cls.state != ca_disconnected ) + { + host.downloadcount = 0; + + for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) + host.downloadcount++; + + if( cl.resourcesneeded.pNext == &cl.resourcesneeded ) + { + byte msg_buf[MAX_INIT_MSG]; + sizebuf_t msg; + + MSG_Init( &msg, "Resource Registration", msg_buf, sizeof( msg_buf )); + + if( CL_PrecacheResources( )) + CL_RegisterResources( &msg ); + + if( MSG_GetNumBytesWritten( &msg ) > 0 ) + { + Netchan_CreateFragments( &cls.netchan, &msg ); + Netchan_FragSend( &cls.netchan ); + } + } + + if( cls.netchan.tempbuffer ) + { + Con_Printf( "Received a decal %s, but didn't find it in resources needed list!\n", pfilename ); + Mem_Free( cls.netchan.tempbuffer ); + } + + cls.netchan.tempbuffer = NULL; + cls.netchan.tempbuffersize = 0; + } +} + +/* +==================== +CL_ServerCommand + +send command to a server +==================== +*/ +void CL_ServerCommand( qboolean reliable, char *fmt, ... ) +{ + char string[MAX_SYSPATH]; + va_list argptr; + + if( cls.state < ca_connecting ) + return; + + va_start( argptr, fmt ); + Q_vsprintf( string, fmt, argptr ); + va_end( argptr ); + + if( reliable ) + { + MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); + MSG_WriteString( &cls.netchan.message, string ); + } + else + { + MSG_BeginClientCmd( &cls.datagram, clc_stringcmd ); + MSG_WriteString( &cls.datagram, string ); + } +} + +//============================================================================= +/* +============== +CL_SetInfo_f +============== +*/ +void CL_SetInfo_f( void ) +{ + convar_t *var; + + if( Cmd_Argc() == 1 ) + { + Con_Printf( "User info settings:\n" ); + Info_Print( cls.userinfo ); + Con_Printf( "Total %i symbols\n", Q_strlen( cls.userinfo )); + return; + } + + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "setinfo [ ]\n" ); + return; + } + + // NOTE: some userinfo comed from cvars, e.g. cl_lw but we can call "setinfo cl_lw 1" + // without real cvar changing. So we need to lookup for cvar first to make sure what + // our key is not linked with console variable + var = Cvar_FindVar( Cmd_Argv( 1 )); + + // make sure what cvar is existed and really part of userinfo + if( var && FBitSet( var->flags, FCVAR_USERINFO )) + { + Cvar_DirectSet( var, Cmd_Argv( 2 )); + } + else if( Info_SetValueForKey( cls.userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_INFO_STRING )) + { + // send update only on successfully changed userinfo + Cmd_ForwardToServer (); + } +} + +/* +============== +CL_Physinfo_f +============== +*/ +void CL_Physinfo_f( void ) +{ + Con_Printf( "Phys info settings:\n" ); + Info_Print( cls.physinfo ); + Con_Printf( "Total %i symbols\n", Q_strlen( cls.physinfo )); +} + +qboolean CL_PrecacheResources( void ) +{ + resource_t *pRes; + + // NOTE: world need to be loaded as first model + for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) + { + if( FBitSet( pRes->ucFlags, RES_PRECACHED )) + continue; + + if( pRes->type != t_model || pRes->nIndex != WORLD_INDEX ) + continue; + + cl.models[pRes->nIndex] = Mod_LoadWorld( pRes->szFileName, true ); + SetBits( pRes->ucFlags, RES_PRECACHED ); + cl.nummodels = 1; + break; + } + + // then we set up all the world submodels + for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) + { + if( FBitSet( pRes->ucFlags, RES_PRECACHED )) + continue; + + if( pRes->type == t_model && pRes->szFileName[0] == '*' ) + { + cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, false ); + cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); + SetBits( pRes->ucFlags, RES_PRECACHED ); + + if( cl.models[pRes->nIndex] == NULL ) + { + MsgDev( D_ERROR, "submodel %s not found\n", pRes->szFileName ); + + if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) + { + CL_Disconnect_f(); + return false; + } + } + } + } + + if( cls.state != ca_active ) + S_BeginRegistration(); + + // precache all the remaining resources where order is doesn't matter + for( pRes = cl.resourcesonhand.pNext; pRes && pRes != &cl.resourcesonhand; pRes = pRes->pNext ) + { + if( FBitSet( pRes->ucFlags, RES_PRECACHED )) + continue; + + switch( pRes->type ) + { + case t_sound: + if( pRes->nIndex != -1 ) + { + if( FBitSet( pRes->ucFlags, RES_WASMISSING )) + { + cl.sound_precache[pRes->nIndex][0] = 0; + cl.sound_index[pRes->nIndex] = 0; + } + else + { + Q_strncpy( cl.sound_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.sound_precache[0] )); + cl.sound_index[pRes->nIndex] = S_RegisterSound( pRes->szFileName ); + + if( !cl.sound_index[pRes->nIndex] ) + { + if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) + { + S_EndRegistration(); + CL_Disconnect_f(); + return false; + } + } + } + } + else + { + // client sounds + S_RegisterSound( pRes->szFileName ); + } + break; + case t_skin: + break; + case t_model: + cl.nummodels = Q_max( cl.nummodels, pRes->nIndex + 1 ); + if( pRes->szFileName[0] != '*' ) + { + if( pRes->nIndex != -1 ) + { + cl.models[pRes->nIndex] = Mod_ForName( pRes->szFileName, false, true ); + + if( cl.models[pRes->nIndex] == NULL ) + { + if( FBitSet( pRes->ucFlags, RES_FATALIFMISSING )) + { + S_EndRegistration(); + CL_Disconnect_f(); + return false; + } + } + } + else + { + CL_LoadClientSprite( pRes->szFileName ); + } + } + break; + case t_decal: + if( !FBitSet( pRes->ucFlags, RES_CUSTOM )) + Q_strncpy( host.draw_decals[pRes->nIndex], pRes->szFileName, sizeof( host.draw_decals[0] )); + break; + case t_generic: + Q_strncpy( cl.files_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.files_precache[0] )); + cl.numfiles = Q_max( cl.numfiles, pRes->nIndex + 1 ); + break; + case t_eventscript: + Q_strncpy( cl.event_precache[pRes->nIndex], pRes->szFileName, sizeof( cl.event_precache[0] )); + CL_SetEventIndex( cl.event_precache[pRes->nIndex], pRes->nIndex ); + break; + default: + MsgDev( D_REPORT, "unknown resource type\n" ); + break; + } + + SetBits( pRes->ucFlags, RES_PRECACHED ); + } + + // make sure modelcount is in-range + cl.nummodels = bound( 0, cl.nummodels, MAX_MODELS ); + cl.numfiles = bound( 0, cl.numfiles, MAX_CUSTOM ); + + if( cls.state != ca_active ) + S_EndRegistration(); + + return true; +} + +/* +================== +CL_FullServerinfo_f + +Sent by server when serverinfo changes +================== +*/ +void CL_FullServerinfo_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "fullserverinfo \n" ); + return; + } + + Q_strncpy( cl.serverinfo, Cmd_Argv( 1 ), sizeof( cl.serverinfo )); +} + +/* +================= +CL_Escape_f + +Escape to menu from game +================= +*/ +void CL_Escape_f( void ) +{ + if( cls.key_dest == key_menu ) + return; + + // the final credits is running + if( UI_CreditsActive( )) return; + + if( cls.state == ca_cinematic ) + SCR_NextMovie(); // jump to next movie + else UI_SetActiveMenu( true ); +} + +/* +================= +CL_InitLocal +================= +*/ +void CL_InitLocal( void ) +{ + cls.state = ca_disconnected; + cls.signon = 0; + + cl.resourcesneeded.pNext = cl.resourcesneeded.pPrev = &cl.resourcesneeded; + cl.resourcesonhand.pNext = cl.resourcesonhand.pPrev = &cl.resourcesonhand; + + Cvar_RegisterVariable( &mp_decals ); + Cvar_RegisterVariable( &dev_overview ); + Cvar_RegisterVariable( &cl_resend ); + Cvar_RegisterVariable( &cl_allow_upload ); + Cvar_RegisterVariable( &cl_allow_download ); + Cvar_RegisterVariable( &cl_download_ingame ); + Cvar_RegisterVariable( &cl_logofile ); + Cvar_RegisterVariable( &cl_logocolor ); + Cvar_RegisterVariable( &cl_test_bandwidth ); + + // register our variables + cl_crosshair = Cvar_Get( "crosshair", "1", FCVAR_ARCHIVE, "show weapon chrosshair" ); + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0, "disable delta-compression for server messages" ); + cl_idealpitchscale = Cvar_Get( "cl_idealpitchscale", "0.8", 0, "how much to look up/down slopes and stairs when not using freelook" ); + cl_solid_players = Cvar_Get( "cl_solid_players", "1", 0, "Make all players not solid (can't traceline them)" ); + cl_interp = Cvar_Get( "ex_interp", "0.1", FCVAR_ARCHIVE, "Interpolate object positions starting this many seconds in past" ); + cl_timeout = Cvar_Get( "cl_timeout", "60", 0, "connect timeout (in-seconds)" ); + + rcon_client_password = Cvar_Get( "rcon_password", "", 0, "remote control client password" ); + rcon_address = Cvar_Get( "rcon_address", "", 0, "remote control address" ); + + // userinfo + cl_nopred = Cvar_Get( "cl_nopred", "0", FCVAR_ARCHIVE|FCVAR_USERINFO, "disable client movement prediction" ); + name = Cvar_Get( "name", Sys_GetCurrentUser(), FCVAR_USERINFO|FCVAR_ARCHIVE|FCVAR_PRINTABLEONLY, "player name" ); + model = Cvar_Get( "model", "", FCVAR_USERINFO|FCVAR_ARCHIVE, "player model ('player' is a singleplayer model)" ); + cl_updaterate = Cvar_Get( "cl_updaterate", "20", FCVAR_USERINFO|FCVAR_ARCHIVE, "refresh rate of server messages" ); + cl_dlmax = Cvar_Get( "cl_dlmax", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "max allowed fragment size on download resources" ); + rate = Cvar_Get( "rate", "3500", FCVAR_USERINFO|FCVAR_ARCHIVE, "player network rate" ); + topcolor = Cvar_Get( "topcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player top color" ); + bottomcolor = Cvar_Get( "bottomcolor", "0", FCVAR_USERINFO|FCVAR_ARCHIVE, "player bottom color" ); + cl_lw = Cvar_Get( "cl_lw", "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable client weapon predicting" ); + Cvar_Get( "cl_lc", "1", FCVAR_ARCHIVE|FCVAR_USERINFO, "enable lag compensation" ); + Cvar_Get( "password", "", FCVAR_USERINFO, "server password" ); + Cvar_Get( "team", "", FCVAR_USERINFO, "player team" ); + Cvar_Get( "skin", "", FCVAR_USERINFO, "player skin" ); + + cl_showfps = Cvar_Get( "cl_showfps", "1", FCVAR_ARCHIVE, "show client fps" ); + cl_nosmooth = Cvar_Get( "cl_nosmooth", "0", FCVAR_ARCHIVE, "disable smooth up stair climbing and interpolate position in multiplayer" ); + cl_smoothtime = Cvar_Get( "cl_smoothtime", "0.1", FCVAR_ARCHIVE, "time to smooth up" ); + cl_cmdbackup = Cvar_Get( "cl_cmdbackup", "10", FCVAR_ARCHIVE, "how many additional history commands are sent" ); + cl_cmdrate = Cvar_Get( "cl_cmdrate", "30", FCVAR_ARCHIVE, "Max number of command packets sent to server per second" ); + cl_draw_particles = Cvar_Get( "r_drawparticles", "1", FCVAR_CHEAT, "render particles" ); + cl_draw_tracers = Cvar_Get( "r_drawtracers", "1", FCVAR_CHEAT, "render tracers" ); + cl_draw_beams = Cvar_Get( "r_drawbeams", "1", FCVAR_CHEAT, "render beams" ); + cl_lightstyle_lerping = Cvar_Get( "cl_lightstyle_lerping", "0", FCVAR_ARCHIVE, "enables animated light lerping (perfomance option)" ); + cl_showerror = Cvar_Get( "cl_showerror", "0", FCVAR_ARCHIVE, "show prediction error" ); + cl_bmodelinterp = Cvar_Get( "cl_bmodelinterp", "1", FCVAR_ARCHIVE, "enable bmodel interpolation" ); + cl_clockreset = Cvar_Get( "cl_clockreset", "0.1", FCVAR_ARCHIVE, "frametime delta maximum value before reset" ); + cl_fixtimerate = Cvar_Get( "cl_fixtimerate", "7.5", FCVAR_ARCHIVE, "time in msec to client clock adjusting" ); + Cvar_Get( "hud_scale", "0", FCVAR_ARCHIVE|FCVAR_LATCH, "scale hud at current resolution" ); + Cvar_Get( "cl_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); + cl_showevents = Cvar_Get( "cl_showevents", "0", FCVAR_ARCHIVE, "show events playback" ); + + // these two added to shut up CS 1.5 about 'unknown' commands + Cvar_Get( "lightgamma", "1", FCVAR_ARCHIVE, "ambient lighting level (legacy, unused)" ); + Cvar_Get( "direct", "1", FCVAR_ARCHIVE, "direct lighting level (legacy, unused)" ); + + // server commands + Cmd_AddCommand ("noclip", NULL, "enable or disable no clipping mode" ); + Cmd_AddCommand ("notarget", NULL, "notarget mode (monsters do not see you)" ); + Cmd_AddCommand ("fullupdate", NULL, "re-init HUD on start demo recording" ); + Cmd_AddCommand ("give", NULL, "give specified item or weapon" ); + Cmd_AddCommand ("drop", NULL, "drop current/specified item or weapon" ); + Cmd_AddCommand ("gametitle", NULL, "show game logo" ); + Cmd_AddCommand ("god", NULL, "enable godmode" ); + Cmd_AddCommand ("fov", NULL, "set client field of view" ); + Cmd_AddCommand ("log", NULL, "logging server events" ); + + // register our commands + Cmd_AddCommand ("pause", NULL, "pause the game (if the server allows pausing)" ); + Cmd_AddCommand ("localservers", CL_LocalServers_f, "collect info about local servers" ); + Cmd_AddCommand ("internetservers", CL_InternetServers_f, "collect info about internet servers" ); + Cmd_AddCommand ("cd", CL_PlayCDTrack_f, "Play cd-track (not real cd-player of course)" ); + Cmd_AddCommand ("mp3", CL_PlayCDTrack_f, "Play mp3-track (based on virtual cd-player)" ); + + Cmd_AddCommand ("setinfo", CL_SetInfo_f, "examine or change the userinfo string (alias of userinfo)" ); + Cmd_AddCommand ("userinfo", CL_SetInfo_f, "examine or change the userinfo string (alias of setinfo)" ); + Cmd_AddCommand ("physinfo", CL_Physinfo_f, "print current client physinfo" ); + Cmd_AddCommand ("disconnect", CL_Disconnect_f, "disconnect from server" ); + Cmd_AddCommand ("record", CL_Record_f, "record a demo" ); + Cmd_AddCommand ("playdemo", CL_PlayDemo_f, "play a demo" ); + Cmd_AddCommand ("timedemo", CL_TimeDemo_f, "demo benchmark" ); + Cmd_AddCommand ("killdemo", CL_DeleteDemo_f, "delete a specified demo file and demoshot" ); + Cmd_AddCommand ("startdemos", CL_StartDemos_f, "start playing back the selected demos sequentially" ); + Cmd_AddCommand ("demos", CL_Demos_f, "restart looping demos defined by the last startdemos command" ); + Cmd_AddCommand ("movie", CL_PlayVideo_f, "play a movie" ); + Cmd_AddCommand ("stop", CL_Stop_f, "stop playing or recording a demo" ); + Cmd_AddCommand ("info", NULL, "collect info about local servers with specified protocol" ); + Cmd_AddCommand ("escape", CL_Escape_f, "escape from game to menu" ); + Cmd_AddCommand ("togglemenu", CL_Escape_f, "toggle between game and menu" ); + Cmd_AddCommand ("pointfile", CL_ReadPointFile_f, "show leaks on a map (if present of course)" ); + Cmd_AddCommand ("linefile", CL_ReadLineFile_f, "show leaks on a map (if present of course)" ); + Cmd_AddCommand ("fullserverinfo", CL_FullServerinfo_f, "sent by server when serverinfo changes" ); + Cmd_AddCommand ("upload", CL_BeginUpload_f, "uploading file to the server" ); + + Cmd_AddCommand ("quit", CL_Quit_f, "quit from game" ); + Cmd_AddCommand ("exit", CL_Quit_f, "quit from game" ); + + Cmd_AddCommand ("screenshot", CL_ScreenShot_f, "takes a screenshot of the next rendered frame" ); + Cmd_AddCommand ("snapshot", CL_SnapShot_f, "takes a snapshot of the next rendered frame" ); + Cmd_AddCommand ("envshot", CL_EnvShot_f, "takes a six-sides cubemap shot with specified name" ); + Cmd_AddCommand ("skyshot", CL_SkyShot_f, "takes a six-sides envmap (skybox) shot with specified name" ); + Cmd_AddCommand ("levelshot", CL_LevelShot_f, "same as \"screenshot\", used for create plaque images" ); + Cmd_AddCommand ("saveshot", CL_SaveShot_f, "used for create save previews with LoadGame menu" ); + Cmd_AddCommand ("demoshot", CL_DemoShot_f, "used for create demo previews with PlayDemo menu" ); + + Cmd_AddCommand ("connect", CL_Connect_f, "connect to a server by hostname" ); + Cmd_AddCommand ("reconnect", CL_Reconnect_f, "reconnect to current level" ); + + Cmd_AddCommand ("rcon", CL_Rcon_f, "sends a command to the server console (rcon_password and rcon_address required)" ); +} + +//============================================================================ +/* +================== +CL_AdjustClock + +slowly adjuct client clock +to smooth lag effect +================== +*/ +void CL_AdjustClock( void ) +{ + if( cl.timedelta == 0.0f || !cl_fixtimerate->value ) + return; + + if( cl_fixtimerate->value < 0.0f ) + Cvar_SetValue( "cl_fixtimerate", 7.5f ); + + if( fabs( cl.timedelta ) >= 0.001f ) + { + double msec, adjust, sign; + + msec = ( cl.timedelta * 1000.0 ); + sign = ( msec < 0 ) ? 1.0 : -1.0; + msec = fabs( msec ); + adjust = sign * ( cl_fixtimerate->value / 1000.0 ); + + if( fabs( adjust ) < fabs( cl.timedelta )) + { + cl.timedelta += adjust; + cl.time += adjust; + } + + if( cl.oldtime > cl.time ) + cl.oldtime = cl.time; + } +} + +/* +================== +Host_ClientBegin + +================== +*/ +void Host_ClientBegin( void ) +{ + // if client is not active, do nothing + if( !cls.initialized ) return; + + // exec console commands + Cbuf_Execute (); + + // finalize connection process if needs + CL_CheckClientState(); + + // tell the client.dll about client data + CL_UpdateClientData(); + + // if running the server locally, make intentions now + if( SV_Active( )) CL_SendCommand (); +} + +/* +================== +Host_ClientFrame + +================== +*/ +void Host_ClientFrame( void ) +{ + // if client is not active, do nothing + if( !cls.initialized ) return; + + // if running the server remotely, send intentions now after + // the incoming messages have been read + if( !SV_Active( )) CL_SendCommand (); + + clgame.dllFuncs.pfnFrame( host.frametime ); + + // remember last received framenum + CL_SetLastUpdate (); + + // read updates from server + CL_ReadPackets (); + + // do prediction again in case we got + // a new portion updates from server + CL_RedoPrediction (); + + // TODO: implement +// Voice_Idle( host.frametime ); + + // emit visible entities + CL_EmitEntities (); + + // in case we lost connection + CL_CheckForResend (); + + // procssing resources on handle + while( CL_RequestMissingResources( )); + + // handle thirdperson camera + CL_MoveThirdpersonCamera(); + + // handle spectator movement + CL_MoveSpectatorCamera(); + + // catch changes video settings + VID_CheckChanges(); + + // process VGUI + VGui_RunFrame (); + + // update the screen + SCR_UpdateScreen (); + + // update audio + SND_UpdateSound (); + + // animate lightestyles + CL_RunLightStyles (); + + // decay dynamic lights + CL_DecayLights (); + + // play avi-files + SCR_RunCinematic (); + + // adjust client time + CL_AdjustClock (); +} + +//============================================================================ + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) +{ + if( host.type == HOST_DEDICATED ) + return; // nothing running on the client + + CL_InitLocal(); + + R_Init(); // init renderer + S_Init(); // init sound + + // unreliable buffer. unsed for unreliable commands and voice stream + MSG_Init( &cls.datagram, "cls.datagram", cls.datagram_buf, sizeof( cls.datagram_buf )); + + if( !CL_LoadProgs( va( "%s/client.dll", GI->dll_path ))) + Host_Error( "can't initialize client.dll\n" ); + + cls.initialized = true; + cl.maxclients = 1; // allow to drawing player in menu + cls.olddemonum = -1; + cls.demonum = -1; +} + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown( void ) +{ + // already freed + if( !cls.initialized ) return; + cls.initialized = false; + + Con_Printf( "CL_Shutdown()\n" ); + + Host_WriteOpenGLConfig (); + Host_WriteVideoConfig (); + + CL_CloseDemoHeader(); + IN_Shutdown (); + SCR_Shutdown (); + CL_UnloadProgs (); + + FS_Delete( "demoheader.tmp" ); // remove tmp file + SCR_FreeCinematic (); // release AVI's *after* client.dll because custom renderer may use them + S_Shutdown (); + R_Shutdown (); + + Con_Shutdown (); +} \ No newline at end of file diff --git a/engine/client/cl_netgraph.c b/engine/client/cl_netgraph.c new file mode 100644 index 00000000..9f6b5734 --- /dev/null +++ b/engine/client/cl_netgraph.c @@ -0,0 +1,683 @@ +/* +cl_netgraph.c - Draw Net statistics (borrowed from Xash3D SDL code) +Copyright (C) 2016 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" + +#define NET_TIMINGS 1024 +#define NET_TIMINGS_MASK (NET_TIMINGS - 1) +#define LATENCY_AVG_FRAC 0.5 +#define FRAMERATE_AVG_FRAC 0.5 +#define PACKETLOSS_AVG_FRAC 0.5 +#define PACKETCHOKE_AVG_FRAC 0.5 +#define NETGRAPH_LERP_HEIGHT 24 +#define NETGRAPH_NET_COLORS 5 +#define NUM_LATENCY_SAMPLES 8 + +convar_t *net_graph; +convar_t *net_graphpos; +convar_t *net_graphwidth; +convar_t *net_graphheight; +convar_t *net_graphsolid; +convar_t *net_scale; + +static struct packet_latency_t +{ + int latency; + int choked; +} netstat_packet_latency[NET_TIMINGS]; + +static struct cmdinfo_t +{ + float cmd_lerp; + int size; + qboolean sent; +} netstat_cmdinfo[NET_TIMINGS]; + +static byte netcolors[NETGRAPH_NET_COLORS+NETGRAPH_LERP_HEIGHT][4] = +{ + { 255, 0, 0, 255 }, + { 0, 0, 255, 255 }, + { 240, 127, 63, 255 }, + { 255, 255, 0, 255 }, + { 63, 255, 63, 150 } + // other will be generated through NetGraph_InitColors() +}; + +static byte sendcolor[4] = { 88, 29, 130, 255 }; +static byte holdcolor[4] = { 255, 0, 0, 200 }; +static byte extrap_base_color[4] = { 255, 255, 255, 255 }; +static netbandwidthgraph_t netstat_graph[NET_TIMINGS]; +static float packet_loss; +static float packet_choke; +static float framerate = 0.0; +static int maxmsgbytes = 0; + +/* +========== +NetGraph_DrawRect + +NetGraph_FillRGBA shortcut +========== +*/ +static void NetGraph_DrawRect( wrect_t *rect, byte colors[4] ) +{ + pglColor4ubv( colors ); // color for this quad + + pglVertex2f( rect->left, rect->top ); + pglVertex2f( rect->left + rect->right, rect->top ); + pglVertex2f( rect->left + rect->right, rect->top + rect->bottom ); + pglVertex2f( rect->left, rect->top + rect->bottom ); +} + +/* +========== +NetGraph_AtEdge + +edge detect +========== +*/ +qboolean NetGraph_AtEdge( int x, int width ) +{ + if( x > 3 ) + { + if( x >= width - 4 ) + return true; + return false; + } + return true; +} + +/* +========== +NetGraph_InitColors + +init netgraph colors +========== +*/ +void NetGraph_InitColors( void ) +{ + byte mincolor[2][3]; + byte maxcolor[2][3]; + float dc[2][3]; + int i, hfrac; + float f; + + mincolor[0][0] = 63; + mincolor[0][1] = 0; + mincolor[0][2] = 100; + + maxcolor[0][0] = 0; + maxcolor[0][1] = 63; + maxcolor[0][2] = 255; + + mincolor[1][0] = 255; + mincolor[1][1] = 127; + mincolor[1][2] = 0; + + maxcolor[1][0] = 250; + maxcolor[1][1] = 0; + maxcolor[1][2] = 0; + + for( i = 0; i < 3; i++ ) + { + dc[0][i] = (float)(maxcolor[0][i] - mincolor[0][i]); + dc[1][i] = (float)(maxcolor[1][i] - mincolor[1][i]); + } + + hfrac = NETGRAPH_LERP_HEIGHT / 3; + + for( i = 0; i < NETGRAPH_LERP_HEIGHT; i++ ) + { + if( i < hfrac ) + { + f = (float)i / (float)hfrac; + VectorMA( mincolor[0], f, dc[0], netcolors[NETGRAPH_NET_COLORS + i] ); + } + else + { + f = (float)(i - hfrac) / (float)(NETGRAPH_LERP_HEIGHT - hfrac ); + VectorMA( mincolor[1], f, dc[1], netcolors[NETGRAPH_NET_COLORS + i] ); + } + } +} + +/* +========== +NetGraph_GetFrameData + +get frame data info, like chokes, packet losses, also update graph, packet and cmdinfo +========== +*/ +void NetGraph_GetFrameData( float *latency, int *latency_count ) +{ + int i, choke_count = 0, loss_count = 0; + double newtime = Sys_DoubleTime(); + static double nexttime = 0; + float loss, choke; + + *latency_count = 0; + *latency = 0.0f; + + if( newtime >= nexttime ) + { + // soft fading of net peak usage + maxmsgbytes = Q_max( 0, maxmsgbytes - 50 ); + nexttime = newtime + 0.05; + } + + for( i = cls.netchan.incoming_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.incoming_sequence; i++ ) + { + frame_t *f = cl.frames + ( i & CL_UPDATE_MASK ); + struct packet_latency_t *p = netstat_packet_latency + ( i & NET_TIMINGS_MASK ); + netbandwidthgraph_t *g = netstat_graph + ( i & NET_TIMINGS_MASK ); + + p->choked = f->choked; + if( p->choked ) choke_count++; + + if( !f->valid ) + { + p->latency = 9998; // broken delta + } + else if( f->receivedtime == -1.0 ) + { + p->latency = 9999; // dropped + loss_count++; + } + else if( f->receivedtime == -3.0 ) + { + p->latency = 9997; // skipped + } + else + { + int frame_latency = Q_min( 1.0f, f->latency ); + p->latency = (( frame_latency + 0.1 ) / 1.1 ) * ( net_graphheight->value - NETGRAPH_LERP_HEIGHT - 2 ); + + if( i > cls.netchan.incoming_sequence - NUM_LATENCY_SAMPLES ) + { + (*latency) += 1000.0f * f->latency; + (*latency_count)++; + } + } + + memcpy( g, &f->graphdata, sizeof( netbandwidthgraph_t )); + + if( g->msgbytes > maxmsgbytes ) + maxmsgbytes = g->msgbytes; + } + + if( maxmsgbytes > 1000 ) + maxmsgbytes = 1000; + + for( i = cls.netchan.outgoing_sequence - CL_UPDATE_BACKUP + 1; i <= cls.netchan.outgoing_sequence; i++ ) + { + netstat_cmdinfo[i & NET_TIMINGS_MASK].cmd_lerp = cl.commands[i & CL_UPDATE_MASK].frame_lerp; + netstat_cmdinfo[i & NET_TIMINGS_MASK].sent = cl.commands[i & CL_UPDATE_MASK].heldback ? false : true; + netstat_cmdinfo[i & NET_TIMINGS_MASK].size = cl.commands[i & CL_UPDATE_MASK].sendsize; + } + + // packet loss + loss = 100.0 * (float)loss_count / CL_UPDATE_BACKUP; + packet_loss = PACKETLOSS_AVG_FRAC * packet_loss + ( 1.0 - PACKETLOSS_AVG_FRAC ) * loss; + + // packet choke + choke = 100.0 * (float)choke_count / CL_UPDATE_BACKUP; + packet_choke = PACKETCHOKE_AVG_FRAC * packet_choke + ( 1.0 - PACKETCHOKE_AVG_FRAC ) * choke; +} + +/* +=========== +NetGraph_DrawTimes + +=========== +*/ +void NetGraph_DrawTimes( wrect_t rect, int x, int w ) +{ + int i, j, extrap_point = NETGRAPH_LERP_HEIGHT / 3, a, h; + rgba_t colors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 }; + wrect_t fill; + + for( a = 0; a < w; a++ ) + { + i = ( cls.netchan.outgoing_sequence - a ) & NET_TIMINGS_MASK; + h = ( netstat_cmdinfo[i].cmd_lerp / 3.0f ) * NETGRAPH_LERP_HEIGHT; + + fill.left = x + w - a - 1; + fill.right = fill.bottom = 1; + fill.top = rect.top + rect.bottom - 4; + + if( h >= extrap_point ) + { + int start = 0; + + h -= extrap_point; + fill.top -= extrap_point; + + if( !net_graphsolid->value ) + { + fill.top -= (h - 1); + start = (h - 1); + } + + for( j = start; j < h; j++ ) + { + NetGraph_DrawRect( &fill, netcolors[NETGRAPH_NET_COLORS + j + extrap_point] ); + fill.top--; + } + } + else + { + int oldh = h; + + fill.top -= h; + h = extrap_point - h; + + if( !net_graphsolid->value ) + h = 1; + + for( j = 0; j < h; j++ ) + { + NetGraph_DrawRect( &fill, netcolors[NETGRAPH_NET_COLORS + j + oldh] ); + fill.top--; + } + } + + fill.top = rect.top + rect.bottom - 4 - extrap_point; + + if( NetGraph_AtEdge( a, w )) + NetGraph_DrawRect( &fill, extrap_base_color ); + + fill.top = rect.top + rect.bottom - 4; + + if( netstat_cmdinfo[i].sent ) + NetGraph_DrawRect( &fill, sendcolor ); + else NetGraph_DrawRect( &fill, holdcolor ); + } +} + +//left = x +//right = width +//top = y +//bottom = height + +/* +=========== +NetGraph_DrawHatches + +=========== +*/ +void NetGraph_DrawHatches( int x, int y ) +{ + int ystep = (int)( 10.0f / net_scale->value ); + byte colorminor[4] = { 0, 63, 63, 200 }; + byte color[4] = { 0, 200, 0, 255 }; + wrect_t hatch = { x, 4, y, 1 }; + int starty; + + ystep = Q_max( ystep, 1 ); + + for( starty = hatch.top; hatch.top > 0 && ((starty - hatch.top) * net_scale->value < (maxmsgbytes + 50)); hatch.top -= ystep ) + { + if(!((int)((starty - hatch.top) * net_scale->value ) % 50 )) + { + NetGraph_DrawRect( &hatch, color ); + } + else if( ystep > 5 ) + { + NetGraph_DrawRect( &hatch, colorminor ); + } + } +} + +/* +=========== +NetGraph_DrawTextFields + +=========== +*/ +void NetGraph_DrawTextFields( int x, int y, int w, wrect_t rect, int count, float avg, int packet_loss, int packet_choke ) +{ + static int lastout; + rgba_t colors = { 0.9 * 255, 0.9 * 255, 0.7 * 255, 255 }; + int ptx = Q_max( x + w - NETGRAPH_LERP_HEIGHT - 1, 1 ); + int pty = Q_max( rect.top + rect.bottom - NETGRAPH_LERP_HEIGHT - 3, 1 ); + int out, i = ( cls.netchan.outgoing_sequence - 1 ) & NET_TIMINGS_MASK; + int j = cls.netchan.incoming_sequence & NET_TIMINGS_MASK; + int last_y = y - net_graphheight->value; + + if( count > 0 ) + { + avg = avg / (float)( count - ( host.frametime * FRAMERATE_AVG_FRAC )); + + if( cl_updaterate->value > 0.0f ) + avg -= 1000.0f / cl_updaterate->value; + + // can't be below zero + avg = Q_max( 0.0, avg ); + } + else avg = 0.0; + + // move rolling average + framerate = FRAMERATE_AVG_FRAC * host.frametime + ( 1.0 - FRAMERATE_AVG_FRAC ) * framerate; + Con_SetFont( 0 ); + + if( framerate > 0.0f ) + { + y -= net_graphheight->value; + + Con_DrawString( x, y, va( "%.1f fps" , 1.0f / framerate ), colors ); + + if( avg > 1.0f ) + Con_DrawString( x + 75, y, va( "%i ms" , (int)avg ), colors ); + + y += 15; + + out = netstat_cmdinfo[i].size; + if( !out ) out = lastout; + else lastout = out; + + Con_DrawString( x, y, va( "in : %i %.2f k/s", netstat_graph[j].msgbytes, cls.netchan.flow[FLOW_INCOMING].avgkbytespersec ), colors ); + y += 15; + + Con_DrawString( x, y, va( "out: %i %.2f k/s", out, cls.netchan.flow[FLOW_OUTGOING].avgkbytespersec ), colors ); + y += 15; + + if( net_graph->value > 2 ) + { + int loss = (int)(( packet_loss + PACKETLOSS_AVG_FRAC ) - 0.01 ); + int choke = (int)(( packet_choke + PACKETCHOKE_AVG_FRAC ) - 0.01 ); + + Con_DrawString( x, y, va( "loss: %i choke: %i", loss, choke ), colors ); + } + } + + if( net_graph->value < 3 ) + Con_DrawString( ptx, pty, va( "%i/s", (int)cl_cmdrate->value ), colors ); + + Con_DrawString( ptx, last_y, va( "%i/s" , (int)cl_updaterate->value ), colors ); + + Con_RestoreFont(); +} + +/* +=========== +NetGraph_DrawDataSegment + +=========== +*/ +int NetGraph_DrawDataSegment( wrect_t *fill, int bytes, byte r, byte g, byte b, byte a ) +{ + float h = bytes / net_scale->value; + byte colors[4] = { r, g, b, a }; + + fill->top -= (int)h; + + if( net_graphsolid->value ) + fill->bottom = (int)h; + else fill->bottom = 1; + + if( fill->top > 1 ) + { + NetGraph_DrawRect( fill, colors ); + return 1; + } + + return 0; +} + +/* +=========== +NetGraph_ColorForHeight + +color based on packet latency +=========== +*/ +void NetGraph_ColorForHeight( struct packet_latency_t *packet, byte color[4], int *ping ) +{ + switch( packet->latency ) + { + case 9999: + memcpy( color, netcolors[0], sizeof( byte ) * 4 ); // dropped + *ping = 0; + break; + case 9998: + memcpy( color, netcolors[1], sizeof( byte ) * 4 ); // invalid + *ping = 0; + break; + case 9997: + memcpy( color, netcolors[2], sizeof( byte ) * 4 ); // skipped + *ping = 0; + break; + default: + *ping = 1; + if( packet->choked ) + { + memcpy( color, netcolors[3], sizeof( byte ) * 4 ); + } + else + { + memcpy( color, netcolors[4], sizeof( byte ) * 4 ); + } + } +} + +/* +=========== +NetGraph_DrawDataUsage + +=========== +*/ +void NetGraph_DrawDataUsage( int x, int y, int w ) +{ + int a, i, h, lastvalidh = 0, ping; + int pingheight = net_graphheight->value - NETGRAPH_LERP_HEIGHT - 2; + wrect_t fill = { 0 }; + byte color[4]; + + for( a = 0; a < w; a++ ) + { + i = (cls.netchan.incoming_sequence - a) & NET_TIMINGS_MASK; + h = netstat_packet_latency[i].latency; + + NetGraph_ColorForHeight( &netstat_packet_latency[i], color, &ping ); + + if( !ping ) h = lastvalidh; + else lastvalidh = h; + + if( h > pingheight ) + h = pingheight; + + fill.left = x + w - a - 1; + fill.top = y - h; + fill.right = 1; + fill.bottom = ping ? 1: h; + + if( !ping ) + { + if( fill.bottom > 3 ) + { + fill.bottom = 2; + NetGraph_DrawRect( &fill, color ); + fill.top += fill.bottom - 2; + NetGraph_DrawRect( &fill, color ); + } + else + { + NetGraph_DrawRect( &fill, color ); + } + } + else + { + NetGraph_DrawRect( &fill, color ); + } + + fill.top = y; + fill.bottom = 1; + + color[0] = 0; + color[1] = 255; + color[2] = 0; + color[3] = 160; + + if( NetGraph_AtEdge( a, w )) + NetGraph_DrawRect( &fill, color ); + + if( net_graph->value < 2 ) + continue; + + color[0] = color[1] = color[2] = color[3] = 255; + fill.top = y - net_graphheight->value - 1; + fill.bottom = 1; + + if( NetGraph_AtEdge( a, w )) + NetGraph_DrawRect( &fill, color ); + + fill.top -= 1; + + if( netstat_packet_latency[i].latency > 9995 ) + continue; // skip invalid + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].client, 255, 0, 0, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].players, 255, 255, 0, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].entities, 255, 0, 255, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].tentities, 0, 0, 255, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].sound, 0, 255, 0, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].event, 0, 255, 255, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].usr, 200, 200, 200, 128 )) + continue; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].voicebytes, 255, 255, 255, 255 )) + continue; + + fill.top = y - net_graphheight->value - 1; + fill.bottom = 1; + fill.top -= 2; + + if( !NetGraph_DrawDataSegment( &fill, netstat_graph[i].msgbytes, 240, 240, 240, 128 )) + continue; + } + + if( net_graph->value >= 2 ) + NetGraph_DrawHatches( x, y - net_graphheight->value - 1 ); +} + +/* +=========== +NetGraph_GetScreenPos + +=========== +*/ +void NetGraph_GetScreenPos( wrect_t *rect, int *w, int *x, int *y ) +{ + rect->left = rect->top = 0; + rect->right = glState.width; + rect->bottom = glState.height; + + *w = Q_min( NET_TIMINGS, net_graphwidth->value ); + if( rect->right < *w + 10 ) + *w = rect->right - 10; + + // detect x and y position + switch( (int)net_graphpos->value ) + { + case 1: // right sided + *x = rect->left + rect->right - 5 - *w; + break; + case 2: // center + *x = rect->left + ( rect->right - 10 - *w ) / 2; + break; + default: // left sided + *x = rect->left + 5; + break; + } + + *y = rect->bottom + rect->top - NETGRAPH_LERP_HEIGHT - 5; +} + +/* +=========== +SCR_DrawNetGraph + +=========== +*/ +void SCR_DrawNetGraph( void ) +{ + wrect_t rect; + float avg_ping; + int ping_count; + int w, x, y; + + if( !host.allow_console ) + return; + + if( cls.state != ca_active ) + return; + + if( !net_graph->value ) + return; + + if( net_scale->value <= 0 ) + Cvar_SetValue( "net_scale", 0.1f ); + + NetGraph_GetScreenPos( &rect, &w, &x, &y ); + + NetGraph_GetFrameData( &avg_ping, &ping_count ); + + NetGraph_DrawTextFields( x, y, w, rect, ping_count, avg_ping, packet_loss, packet_choke ); + + if( net_graph->value < 3 ) + { + pglEnable( GL_BLEND ); + pglDisable( GL_TEXTURE_2D ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + pglBegin( GL_QUADS ); // draw all the fills as a long solid sequence of quads for speedup reasons + + // NOTE: fill colors without texture at this point + NetGraph_DrawDataUsage( x, y, w ); + NetGraph_DrawTimes( rect, x, w ); + + pglEnd(); + pglColor4ub( 255, 255, 255, 255 ); + pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_BLEND ); + } +} + +void CL_InitNetgraph( void ) +{ + net_graph = Cvar_Get( "net_graph", "0", FCVAR_ARCHIVE, "draw network usage graph" ); + net_graphpos = Cvar_Get( "net_graphpos", "1", FCVAR_ARCHIVE, "network usage graph position" ); + net_scale = Cvar_Get( "net_scale", "5", FCVAR_ARCHIVE, "network usage graph scale level" ); + net_graphwidth = Cvar_Get( "net_graphwidth", "192", FCVAR_ARCHIVE, "network usage graph width" ); + net_graphheight = Cvar_Get( "net_graphheight", "64", FCVAR_ARCHIVE, "network usage graph height" ); + net_graphsolid = Cvar_Get( "net_graphsolid", "1", FCVAR_ARCHIVE, "fill segments in network usage graph" ); + packet_loss = packet_choke = 0.0; + + NetGraph_InitColors(); +} \ No newline at end of file diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c new file mode 100644 index 00000000..1e84f97c --- /dev/null +++ b/engine/client/cl_parse.c @@ -0,0 +1,2499 @@ +/* +cl_parse.c - parse a message received from the server +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "net_encode.h" +#include "particledef.h" +#include "gl_local.h" +#include "cl_tent.h" +#include "shake.h" +#include "hltv.h" +#include "input.h" + +#define MSG_COUNT 32 // last 32 messages parsed +#define MSG_MASK (MSG_COUNT - 1) + +int CL_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; + +const char *svc_strings[svc_lastmsg+1] = +{ + "svc_bad", + "svc_nop", + "svc_disconnect", + "svc_event", + "svc_changing", + "svc_setview", + "svc_sound", + "svc_time", + "svc_print", + "svc_stufftext", + "svc_setangle", + "svc_serverdata", + "svc_lightstyle", + "svc_updateuserinfo", + "svc_deltatable", + "svc_clientdata", + "svc_resource", + "svc_pings", + "svc_particle", + "svc_restoresound", + "svc_spawnstatic", + "svc_event_reliable", + "svc_spawnbaseline", + "svc_temp_entity", + "svc_setpause", + "svc_signonnum", + "svc_centerprint", + "svc_unused27", + "svc_unused28", + "svc_unused29", + "svc_intermission", + "svc_finale", + "svc_cdtrack", + "svc_restore", + "svc_cutscene", + "svc_weaponanim", + "svc_bspdecal", + "svc_roomtype", + "svc_addangle", + "svc_usermessage", + "svc_packetentities", + "svc_deltapacketentities", + "svc_choke", + "svc_resourcelist", + "svc_deltamovevars", + "svc_resourcerequest", + "svc_customization", + "svc_crosshairangle", + "svc_soundfade", + "svc_filetxferfailed", + "svc_hltv", + "svc_director", + "svc_voiceinit", + "svc_voicedata", + "svc_unused54", + "svc_unused55", + "svc_resourcelocation", + "svc_querycvarvalue", + "svc_querycvarvalue2", +}; + +typedef struct +{ + int command; + int starting_offset; + int frame_number; +} oldcmd_t; + +typedef struct +{ + oldcmd_t oldcmd[MSG_COUNT]; + int currentcmd; + qboolean parsing; +} msg_debug_t; + +static msg_debug_t cls_message_debug; +static int starting_count; + +const char *CL_MsgInfo( int cmd ) +{ + static string sz; + + Q_strcpy( sz, "???" ); + + if( cmd >= 0 && cmd <= svc_lastmsg ) + { + // get engine message name + Q_strncpy( sz, svc_strings[cmd], sizeof( sz )); + } + else if( cmd > svc_lastmsg && cmd <= ( svc_lastmsg + MAX_USER_MESSAGES )) + { + int i; + + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + if( clgame.msg[i].number == cmd ) + { + Q_strncpy( sz, clgame.msg[i].name, sizeof( sz )); + break; + } + } + } + return sz; +} + +/* +===================== +CL_Parse_RecordCommand + +record new message params into debug buffer +===================== +*/ +void CL_Parse_RecordCommand( int cmd, int startoffset ) +{ + int slot; + + if( cmd == svc_nop ) return; + + slot = ( cls_message_debug.currentcmd++ & MSG_MASK ); + cls_message_debug.oldcmd[slot].command = cmd; + cls_message_debug.oldcmd[slot].starting_offset = startoffset; + cls_message_debug.oldcmd[slot].frame_number = host.framecount; +} + +/* +===================== +CL_WriteErrorMessage + +write net_message into buffer.dat for debugging +===================== +*/ +void CL_WriteErrorMessage( int current_count, sizebuf_t *msg ) +{ + const char *buffer_file = "buffer.dat"; + file_t *fp; + + fp = FS_Open( buffer_file, "wb", false ); + if( !fp ) return; + + FS_Write( fp, &starting_count, sizeof( int )); + FS_Write( fp, ¤t_count, sizeof( int )); + FS_Write( fp, MSG_GetData( msg ), MSG_GetMaxBytes( msg )); + FS_Close( fp ); + + Con_Printf( "Wrote erroneous message to %s\n", buffer_file ); +} + +/* +===================== +CL_WriteMessageHistory + +list last 32 messages for debugging net troubleshooting +===================== +*/ +void CL_WriteMessageHistory( void ) +{ + oldcmd_t *old, *failcommand; + sizebuf_t *msg = &net_message; + int i, thecmd; + + if( !cls.initialized || cls.state == ca_disconnected ) + return; + + if( !cls_message_debug.parsing ) + return; + + Con_Printf( "Last %i messages parsed.\n", MSG_COUNT ); + + // finish here + thecmd = cls_message_debug.currentcmd - 1; + thecmd -= ( MSG_COUNT - 1 ); // back up to here + + for( i = 0; i < MSG_COUNT - 1; i++ ) + { + thecmd &= MSG_MASK; + old = &cls_message_debug.oldcmd[thecmd]; + Con_Printf( "%i %04i %s\n", old->frame_number, old->starting_offset, CL_MsgInfo( old->command )); + thecmd++; + } + + failcommand = &cls_message_debug.oldcmd[thecmd]; + Con_Printf( "BAD: %3i:%s\n", MSG_GetNumBytesRead( msg ) - 1, CL_MsgInfo( failcommand->command )); + if( host_developer.value >= DEV_EXTENDED ) + CL_WriteErrorMessage( MSG_GetNumBytesRead( msg ) - 1, msg ); + cls_message_debug.parsing = false; +} + +/* +=============== +CL_UserMsgStub + +Default stub for missed callbacks +=============== +*/ +int CL_UserMsgStub( const char *pszName, int iSize, void *pbuf ) +{ + return 1; +} + +/* +================== +CL_ParseViewEntity + +================== +*/ +void CL_ParseViewEntity( sizebuf_t *msg ) +{ + cl.viewentity = MSG_ReadWord( msg ); + + // check entity bounds in case we want + // to use this directly in clgame.entities[] array + cl.viewentity = bound( 0, cl.viewentity, clgame.maxEntities - 1 ); +} + +/* +================== +CL_ParseSoundPacket + +================== +*/ +void CL_ParseSoundPacket( sizebuf_t *msg ) +{ + vec3_t pos; + int chan, sound; + float volume, attn; + int flags, pitch, entnum; + sound_t handle = 0; + + flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS ); + sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); + chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS ); + + if( FBitSet( flags, SND_VOLUME )) + volume = (float)MSG_ReadByte( msg ) / 255.0f; + else volume = VOL_NORM; + + if( FBitSet( flags, SND_ATTENUATION )) + attn = (float)MSG_ReadByte( msg ) / 64.0f; + else attn = ATTN_NONE; + + if( FBitSet( flags, SND_PITCH )) + pitch = MSG_ReadByte( msg ); + else pitch = PITCH_NORM; + + // entity reletive + entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + + // positioned in space + MSG_ReadVec3Coord( msg, pos ); + + if( FBitSet( flags, SND_SENTENCE )) + { + char sentenceName[32]; + + if( FBitSet( flags, SND_SEQUENCE )) + Q_snprintf( sentenceName, sizeof( sentenceName ), "!#%i", sound ); + else Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); + + handle = S_RegisterSound( sentenceName ); + } + else handle = cl.sound_index[sound]; // see precached sound + + if( !cl.audio_prepped ) + { + MsgDev( D_WARN, "CL_StartSoundPacket: ignore sound message: too early\n" ); + return; // too early + } + + // g-cont. sound and ambient sound have only difference with channel + if( chan == CHAN_STATIC ) + { + S_AmbientSound( pos, entnum, handle, volume, attn, pitch, flags ); + } + else + { + S_StartSound( pos, entnum, chan, handle, volume, attn, pitch, flags ); + } +} + +/* +================== +CL_ParseRestoreSoundPacket + +================== +*/ +void CL_ParseRestoreSoundPacket( sizebuf_t *msg ) +{ + vec3_t pos; + int chan, sound; + float volume, attn; + int flags, pitch, entnum; + double samplePos, forcedEnd; + int wordIndex; + sound_t handle = 0; + + flags = MSG_ReadUBitLong( msg, MAX_SND_FLAGS_BITS ); + sound = MSG_ReadUBitLong( msg, MAX_SOUND_BITS ); + chan = MSG_ReadUBitLong( msg, MAX_SND_CHAN_BITS ); + + if( flags & SND_VOLUME ) + volume = (float)MSG_ReadByte( msg ) / 255.0f; + else volume = VOL_NORM; + + if( flags & SND_ATTENUATION ) + attn = (float)MSG_ReadByte( msg ) / 64.0f; + else attn = ATTN_NONE; + + if( flags & SND_PITCH ) + pitch = MSG_ReadByte( msg ); + else pitch = PITCH_NORM; + + // entity reletive + entnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + + // positioned in space + MSG_ReadVec3Coord( msg, pos ); + + if( flags & SND_SENTENCE ) + { + char sentenceName[32]; + + if( flags & SND_SEQUENCE ) + Q_snprintf( sentenceName, sizeof( sentenceName ), "!#%i", sound ); + else Q_snprintf( sentenceName, sizeof( sentenceName ), "!%i", sound ); + + handle = S_RegisterSound( sentenceName ); + } + else handle = cl.sound_index[sound]; // see precached sound + + wordIndex = MSG_ReadByte( msg ); + + // 16 bytes here + MSG_ReadBytes( msg, &samplePos, sizeof( samplePos )); + MSG_ReadBytes( msg, &forcedEnd, sizeof( forcedEnd )); + + if( !cl.audio_prepped ) + { + MsgDev( D_WARN, "CL_RestoreSoundPacket: ignore sound message: too early\n" ); + return; // too early + } + + S_RestoreSound( pos, entnum, chan, handle, volume, attn, pitch, flags, samplePos, forcedEnd, wordIndex ); +} + +/* +================== +CL_ParseServerTime + +================== +*/ +void CL_ParseServerTime( sizebuf_t *msg ) +{ + double dt; + + cl.mtime[1] = cl.mtime[0]; + cl.mtime[0] = MSG_ReadFloat( msg ); + + if( cl.maxclients == 1 ) + cl.time = cl.mtime[0]; + + dt = cl.time - cl.mtime[0]; + + if( fabs( dt ) > cl_clockreset->value ) // 0.1 by default + { + cl.time = cl.mtime[0]; + cl.timedelta = 0.0f; + } + else if( dt != 0.0 ) + { + cl.timedelta = dt; + } + + if( cl.oldtime > cl.time ) + cl.oldtime = cl.time; +} + +/* +================== +CL_ParseSignon + +================== +*/ +void CL_ParseSignon( sizebuf_t *msg ) +{ + int i = MSG_ReadByte( msg ); + + if( i <= cls.signon ) + { + MsgDev( D_ERROR, "received signon %i when at %i\n", i, cls.signon ); + CL_Disconnect(); + return; + } + + cls.signon = i; + CL_SignonReply(); +} + +/* +================== +CL_ParseMovevars + +================== +*/ +void CL_ParseMovevars( sizebuf_t *msg ) +{ + Delta_InitClient (); // finalize client delta's + + MSG_ReadDeltaMovevars( msg, &clgame.oldmovevars, &clgame.movevars ); + + // water alpha is not allowed + if( !FBitSet( world.flags, FWORLD_WATERALPHA )) + clgame.movevars.wateralpha = 1.0f; + + // update sky if changed + if( Q_strcmp( clgame.oldmovevars.skyName, clgame.movevars.skyName ) && cl.video_prepped ) + R_SetupSky( clgame.movevars.skyName ); + + memcpy( &clgame.oldmovevars, &clgame.movevars, sizeof( movevars_t )); + clgame.entities->curstate.scale = clgame.movevars.waveHeight; + + // keep features an actual! + clgame.oldmovevars.features = clgame.movevars.features = host.features; +} + +/* +================== +CL_ParseParticles + +================== +*/ +void CL_ParseParticles( sizebuf_t *msg ) +{ + vec3_t org, dir; + int i, count, color; + float life; + + MSG_ReadVec3Coord( msg, org ); + + for( i = 0; i < 3; i++ ) + dir[i] = MSG_ReadChar( msg ) * 0.0625f; + + count = MSG_ReadByte( msg ); + color = MSG_ReadByte( msg ); + if( count == 255 ) count = 1024; + life = MSG_ReadByte( msg ) * 0.125f; + + if( life != 0.0f && count == 1 ) + { + particle_t *p; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die += life; + p->color = color; + p->type = pt_static; + + VectorCopy( org, p->org ); + VectorCopy( dir, p->vel ); + } + else R_RunParticleEffect( org, dir, color, count ); +} + +/* +================== +CL_ParseStaticEntity + +static client entity +================== +*/ +void CL_ParseStaticEntity( sizebuf_t *msg ) +{ + entity_state_t state; + cl_entity_t *ent; + int i; + + memset( &state, 0, sizeof( state )); + + state.modelindex = MSG_ReadShort( msg ); + state.sequence = MSG_ReadWord( msg ); + state.frame = MSG_ReadWord( msg ) * (1.0f / 128.0f); + state.colormap = MSG_ReadWord( msg ); + state.skin = MSG_ReadByte( msg ); + state.body = MSG_ReadByte( msg ); + state.scale = MSG_ReadCoord( msg ); + MSG_ReadVec3Coord( msg, state.origin ); + MSG_ReadVec3Angles( msg, state.angles ); + state.rendermode = MSG_ReadByte( msg ); + + if( state.rendermode != kRenderNormal ) + { + state.renderamt = MSG_ReadByte( msg ); + state.rendercolor.r = MSG_ReadByte( msg ); + state.rendercolor.g = MSG_ReadByte( msg ); + state.rendercolor.b = MSG_ReadByte( msg ); + state.renderfx = MSG_ReadByte( msg ); + } + + i = clgame.numStatics; + if( i >= MAX_STATIC_ENTITIES ) + { + MsgDev( D_ERROR, "CL_ParseStaticEntity: static entities limit exceeded!\n" ); + return; + } + + ent = &clgame.static_entities[i]; + clgame.numStatics++; + + ent->index = 0; // ??? + ent->baseline = state; + ent->curstate = state; + ent->prevstate = state; + + // statics may be respawned in game e.g. for demo recording + if( cls.state == ca_connected ) + ent->trivial_accept = INVALID_HANDLE; + + // setup the new static entity + VectorCopy( ent->curstate.origin, ent->origin ); + VectorCopy( ent->curstate.angles, ent->angles ); + ent->model = CL_ModelHandle( state.modelindex ); + ent->curstate.framerate = 1.0f; + CL_ResetLatchedVars( ent, true ); + + if( ent->curstate.rendermode == kRenderNormal && ent->model != NULL ) + { + // auto 'solid' faces + if( FBitSet( ent->model->flags, MODEL_TRANSPARENT ) && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + ent->curstate.rendermode = kRenderTransAlpha; + ent->curstate.renderamt = 255; + } + } + + R_AddEfrags( ent ); // add link +} + +/* +================== +CL_WeaponAnim + +Set new weapon animation +================== +*/ +void CL_WeaponAnim( int iAnim, int body ) +{ + cl_entity_t *view = &clgame.viewent; + + cl.local.weaponstarttime = 0.0f; + cl.local.weaponsequence = iAnim; + view->curstate.framerate = 1.0f; + view->curstate.body = body; + +#if 0 // g-cont. for GlowShell testing + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 128; + view->curstate.rendercolor.b = 0; + view->curstate.renderamt = 150; +#endif +} + +/* +================== +CL_ParseStaticDecal + +================== +*/ +void CL_ParseStaticDecal( sizebuf_t *msg ) +{ + vec3_t origin; + int decalIndex, entityIndex, modelIndex; + cl_entity_t *ent = NULL; + float scale; + int flags; + + MSG_ReadVec3Coord( msg, origin ); + decalIndex = MSG_ReadWord( msg ); + entityIndex = MSG_ReadShort( msg ); + + if( entityIndex > 0 ) + modelIndex = MSG_ReadWord( msg ); + else modelIndex = 0; + flags = MSG_ReadByte( msg ); + scale = (float)MSG_ReadWord( msg ) / 4096.0f; + + CL_FireCustomDecal( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, origin, flags, scale ); +} + +/* +================== +CL_ParseSoundFade + +================== +*/ +void CL_ParseSoundFade( sizebuf_t *msg ) +{ + float fadePercent, fadeOutSeconds; + float holdTime, fadeInSeconds; + + fadePercent = (float)MSG_ReadByte( msg ); + holdTime = (float)MSG_ReadByte( msg ); + fadeOutSeconds = (float)MSG_ReadByte( msg ); + fadeInSeconds = (float)MSG_ReadByte( msg ); + + S_FadeClientVolume( fadePercent, fadeOutSeconds, holdTime, fadeInSeconds ); +} + +/* +================== +CL_RequestMissingResources + +================== +*/ +qboolean CL_RequestMissingResources( void ) +{ + resource_t *p; + + if( !cls.dl.doneregistering && ( cls.dl.custom || cls.state == ca_validate )) + { + p = cl.resourcesneeded.pNext; + + if( p == &cl.resourcesneeded ) + { + cls.dl.doneregistering = true; + host.downloadcount = 0; + cls.dl.custom = false; + } + else if( !FBitSet( p->ucFlags, RES_WASMISSING )) + { + CL_MoveToOnHandList( cl.resourcesneeded.pNext ); + return true; + } + } + return false; +} + +void CL_BatchResourceRequest( qboolean initialize ) +{ + byte data[MAX_INIT_MSG]; + resource_t *p, *n; + sizebuf_t msg; + + MSG_Init( &msg, "Resource Batch", data, sizeof( data )); + + // client resources is not precached by server + if( initialize ) CL_AddClientResources(); + + for( p = cl.resourcesneeded.pNext; p && p != &cl.resourcesneeded; p = n ) + { + n = p->pNext; + + if( !FBitSet( p->ucFlags, RES_WASMISSING )) + { + CL_MoveToOnHandList( p ); + continue; + } + + if( cls.state == ca_active && !cl_download_ingame.value ) + { + Con_Printf( "skipping in game download of %s\n", p->szFileName ); + CL_MoveToOnHandList( p ); + continue; + } + + switch( p->type ) + { + case t_sound: + if( !CL_CheckFile( &msg, p )) + break; + CL_MoveToOnHandList( p ); + break; + case t_skin: + CL_MoveToOnHandList( p ); + break; + case t_model: + if( !CL_CheckFile( &msg, p )) + break; + CL_MoveToOnHandList( p ); + break; + case t_decal: + if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, p, NULL, NULL )) + { + if( !FBitSet( p->ucFlags, RES_REQUESTED )) + { + string filename; + + Q_snprintf( filename, sizeof( filename ), "!MD5%s", MD5_Print( p->rgucMD5_hash )); + MSG_BeginClientCmd( &msg, clc_stringcmd ); + MSG_WriteString( &msg, va( "dlfile %s", filename )); + SetBits( p->ucFlags, RES_REQUESTED ); + } + break; + } + CL_MoveToOnHandList( p ); + break; + case t_generic: + if( !COM_IsSafeFileToDownload( p->szFileName )) + { + CL_RemoveFromResourceList( p ); + MsgDev( D_WARN, "Invalid file type...skipping download of %s\n", p ); + Mem_Free( p ); + break; + } + if( !CL_CheckFile( &msg, p )) + break; + CL_MoveToOnHandList( p ); + break; + case t_eventscript: + if( !CL_CheckFile( &msg, p )) + break; + CL_MoveToOnHandList( p ); + break; + } + } + + if( cls.state != ca_disconnected ) + { + if( !MSG_GetNumBytesWritten( &msg ) && CL_PrecacheResources( )) + { + CL_RegisterResources( &msg ); + } + + Netchan_CreateFragments( &cls.netchan, &msg ); + Netchan_FragSend( &cls.netchan ); + } +} + +int CL_EstimateNeededResources( void ) +{ + resource_t *p; + int nTotalSize = 0; + int nSize; + + for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) + { + switch( p->type ) + { + case t_sound: + nSize = FS_FileSize( va( "%s%s", DEFAULT_SOUNDPATH, p->szFileName ), false ); + if( p->szFileName[0] != '*' && nSize == -1 ) + { + SetBits( p->ucFlags, RES_WASMISSING ); + nTotalSize += p->nDownloadSize; + } + break; + case t_model: + nSize = FS_FileSize( p->szFileName, false ); + if( p->szFileName[0] != '*' && p->ucFlags && nSize == -1 ) + { + SetBits( p->ucFlags, RES_WASMISSING ); + nTotalSize += p->nDownloadSize; + } + break; + case t_skin: + case t_generic: + case t_eventscript: + nSize = FS_FileSize( p->szFileName, false ); + if( nSize == -1 ) + { + SetBits( p->ucFlags, RES_WASMISSING ); + nTotalSize += p->nDownloadSize; + } + break; + case t_decal: + if( FBitSet( p->ucFlags, RES_CUSTOM )) + { + SetBits( p->ucFlags, RES_WASMISSING ); + nTotalSize += p->nDownloadSize; + } + break; + } + } + + return nTotalSize; +} + +void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ) +{ + resourceinfo_t ri; + + if( COM_CheckString( pszMessage )) + Con_DPrintf( "%s", pszMessage ); + + cls.dl.nTotalSize = COM_SizeofResourceList( &cl.resourcesneeded, &ri ); + cls.dl.nTotalToTransfer = CL_EstimateNeededResources(); + + if( bCustom ) + { + cls.dl.custom = true; + } + else + { + cls.state = ca_validate; + cls.dl.custom = false; + } + + cls.dl.doneregistering = false; + cls.dl.fLastStatusUpdate = host.realtime; + cls.dl.nRemainingToTransfer = cls.dl.nTotalToTransfer; + memset( cls.dl.rgStats, 0, sizeof( cls.dl.rgStats )); + cls.dl.nCurStat = 0; + + CL_BatchResourceRequest( !bCustom ); +} + +customization_t *CL_PlayerHasCustomization( int nPlayerNum, resourcetype_t type ) +{ + customization_t *pList; + + for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pList->pNext ) + { + if( pList->resource.type == type ) + return pList; + } + return NULL; +} + +void CL_RemoveCustomization( int nPlayerNum, customization_t *pRemove ) +{ + customization_t *pList; + customization_t *pNext; + + for( pList = cl.players[nPlayerNum].customdata.pNext; pList; pList = pNext ) + { + pNext = pList->pNext; + + if( pRemove != pList ) + continue; + + if( pList->bInUse && pList->pBuffer ) + Mem_Free( pList->pBuffer ); + + if( pList->bInUse && pList->pInfo ) + { + if( pList->resource.type == t_decal ) + { + if( cls.state == ca_active ) + R_DecalRemoveAll( pList->nUserData1 ); + FS_FreeImage( pList->pInfo ); + } + } + + cl.players[nPlayerNum].customdata.pNext = pNext; + Mem_Free( pList ); + break; + } +} + +/* +================== +CL_ParseCustomization + +================== +*/ +void CL_ParseCustomization( sizebuf_t *msg ) +{ + customization_t *pExistingCustomization; + customization_t *pList; + qboolean bFound; + resource_t *pRes; + int i; + + i = MSG_ReadByte( msg ); + if( i >= MAX_CLIENTS ) + Host_Error( "Bogus player index during customization parsing.\n" ); + + pRes = Mem_Alloc( cls.mempool, sizeof( resource_t )); + pRes->type = MSG_ReadByte( msg ); + + Q_strncpy( pRes->szFileName, MSG_ReadString( msg ), sizeof( pRes->szFileName )); + pRes->nIndex = MSG_ReadShort( msg ); + pRes->nDownloadSize = MSG_ReadLong( msg ); + pRes->ucFlags = MSG_ReadByte( msg ) & ~RES_WASMISSING; + pRes->pNext = pRes->pPrev = NULL; + + if( FBitSet( pRes->ucFlags, RES_CUSTOM )) + MSG_ReadBytes( msg, pRes->rgucMD5_hash, 16 ); + pRes->playernum = i; + + if( !cl_allow_download.value ) + { + Con_DPrintf( "Refusing new resource, cl_allow_download set to 0\n" ); + Mem_Free( pRes ); + return; + } + + if( cls.state == ca_active && !cl_download_ingame.value ) + { + Con_DPrintf( "Refusing new resource, cl_download_ingame set to 0\n" ); + Mem_Free( pRes ); + return; + } + + pExistingCustomization = CL_PlayerHasCustomization( i, pRes->type ); + + if( pExistingCustomization ) + CL_RemoveCustomization( i, pExistingCustomization ); + bFound = false; + + for( pList = cl.players[pRes->playernum].customdata.pNext; pList; pList = pList->pNext ) + { + if( !memcmp( pList->resource.rgucMD5_hash, pRes->rgucMD5_hash, 16 )) + { + bFound = true; + break; + } + } + + if( HPAK_GetDataPointer( CUSTOM_RES_PATH, pRes, NULL, NULL )) + { + qboolean bError = false; + + if( !bFound ) + { + pList = &cl.players[pRes->playernum].customdata; + + if( !COM_CreateCustomization( pList, pRes, pRes->playernum, FCUST_FROMHPAK, NULL, NULL )) + bError = true; + } + else + { + Con_DPrintf( "Duplicate resource ignored for local client\n" ); + } + + if( bError ) Con_DPrintf( "Error loading customization\n" ); + Mem_Free( pRes ); + } + else + { + SetBits( pRes->ucFlags, RES_WASMISSING ); + CL_AddToResourceList( pRes, &cl.resourcesneeded ); + Con_Printf( "Requesting %s from server\n", pRes->szFileName ); + CL_StartResourceDownloading( "Custom resource propagation...\n", true ); + } +} + +/* +================== +CL_ParseResourceRequest + +================== +*/ +void CL_ParseResourceRequest( sizebuf_t *msg ) +{ + byte buffer[MAX_INIT_MSG]; + int i, arg, nStartIndex; + sizebuf_t sbuf; + + MSG_Init( &sbuf, "ResourceBlock", buffer, sizeof( buffer )); + + arg = MSG_ReadLong( msg ); + nStartIndex = MSG_ReadLong( msg ); + + if( cl.servercount != arg ) + { + MsgDev( D_ERROR, "request resources from different level\n" ); + return; + } + + if( nStartIndex < 0 && nStartIndex > cl.num_resources ) + { + MsgDev( D_ERROR, "custom resource list request out of range\n" ); + return; + } + + MSG_BeginClientCmd( &sbuf, clc_resourcelist ); + MSG_WriteShort( &sbuf, cl.num_resources ); + + for( i = nStartIndex; i < cl.num_resources; i++ ) + { + MSG_WriteString( &sbuf, cl.resourcelist[i].szFileName ); + MSG_WriteByte( &sbuf, cl.resourcelist[i].type ); + MSG_WriteShort( &sbuf, cl.resourcelist[i].nIndex ); + MSG_WriteLong( &sbuf, cl.resourcelist[i].nDownloadSize ); + MSG_WriteByte( &sbuf, cl.resourcelist[i].ucFlags ); + + if( FBitSet( cl.resourcelist[i].ucFlags, RES_CUSTOM )) + MSG_WriteBytes( &sbuf, cl.resourcelist[i].rgucMD5_hash, 16 ); + } + + if( MSG_GetNumBytesWritten( &sbuf ) > 0 ) + { + Netchan_CreateFragments( &cls.netchan, &sbuf ); + Netchan_FragSend( &cls.netchan ); + } +} + +/* +================== +CL_CreateCustomizationList + +loading custom decal for self +================== +*/ +void CL_CreateCustomizationList( void ) +{ + resource_t *pResource; + player_info_t *pPlayer; + int i; + + pPlayer = &cl.players[cl.playernum]; + pPlayer->customdata.pNext = NULL; + + for( i = 0; i < cl.num_resources; i++ ) + { + pResource = &cl.resourcelist[i]; + + if( !COM_CreateCustomization( &pPlayer->customdata, pResource, cl.playernum, 0, NULL, NULL )) + Con_Printf( "problem with client customization %i, ignoring...", pResource ); + } +} + +/* +================== +CL_ParseFileTransferFailed + +================== +*/ +void CL_ParseFileTransferFailed( sizebuf_t *msg ) +{ + const char *name = MSG_ReadString( msg ); + + if( !cls.demoplayback ) + CL_ProcessFile( false, name ); +} + +/* +===================================================================== + + SERVER CONNECTING MESSAGES + +===================================================================== +*/ +/* +================== +CL_ParseServerData +================== +*/ +void CL_ParseServerData( sizebuf_t *msg ) +{ + char gamefolder[MAX_QPATH]; + qboolean background; + int i; + + MsgDev( D_NOTE, "Serverdata packet received.\n" ); + cls.timestart = Sys_DoubleTime(); + + cls.demowaiting = false; // server is changed + + // wipe the client_t struct + if( !cls.changelevel && !cls.changedemo ) + CL_ClearState (); + cls.state = ca_connected; + + // parse protocol version number + i = MSG_ReadLong( msg ); + + if( i != PROTOCOL_VERSION ) + Host_Error( "Server use invalid protocol (%i should be %i)\n", i, PROTOCOL_VERSION ); + + cl.servercount = MSG_ReadLong( msg ); + cl.checksum = MSG_ReadLong( msg ); + cl.playernum = MSG_ReadByte( msg ); + cl.maxclients = MSG_ReadByte( msg ); + clgame.maxEntities = MSG_ReadWord( msg ); + clgame.maxEntities = bound( 600, clgame.maxEntities, MAX_EDICTS ); + clgame.maxModels = MSG_ReadWord( msg ); + Q_strncpy( clgame.mapname, MSG_ReadString( msg ), MAX_STRING ); + Q_strncpy( clgame.maptitle, MSG_ReadString( msg ), MAX_STRING ); + background = MSG_ReadOneBit( msg ); + Q_strncpy( gamefolder, MSG_ReadString( msg ), MAX_QPATH ); + host.features = (uint)MSG_ReadLong( msg ); + + // receive the player hulls + for( i = 0; i < MAX_MAP_HULLS * 3; i++ ) + { + host.player_mins[i/3][i%3] = MSG_ReadChar( msg ); + host.player_maxs[i/3][i%3] = MSG_ReadChar( msg ); + } + + if( clgame.maxModels > MAX_MODELS ) + Con_Printf( S_WARN "server model limit is above client model limit %i > %i\n", clgame.maxModels, MAX_MODELS ); + + // Re-init hud video, especially if we changed game directories + clgame.dllFuncs.pfnVidInit(); + + if( Con_FixedFont( )) + { + // seperate the printfs so the server message can have a color + Con_Print( "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n" ); + Con_Print( va( "%c%s\n\n", 2, clgame.maptitle )); + } + + // multiplayer game? + if( cl.maxclients > 1 ) + { + // allow console in multiplayer games + host.allow_console = true; + + // loading user settings + CSCR_LoadDefaultCVars( "user.scr" ); + + if( r_decals->value > mp_decals.value ) + Cvar_SetValue( "r_decals", mp_decals.value ); + } + else Cvar_Reset( "r_decals" ); + + // set the background state + if( cls.demoplayback && ( cls.demonum != -1 )) + { + // re-init mouse + host.mouse_visible = false; + cl.background = true; + } + else cl.background = background; + + if( cl.background ) // tell the game parts about background state + Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); + else Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + + if( !cls.changelevel ) + { + // continue playing if we are changing level + S_StopBackgroundTrack (); + } + + if( !cls.changedemo ) + UI_SetActiveMenu( cl.background ); + else if( !cls.demoplayback ) + Key_SetKeyDest( key_menu ); + + // don't reset cursor in background mode + if( cl.background ) + IN_MouseRestorePos(); + + // will be changed later + cl.viewentity = cl.playernum + 1; + gameui.globals->maxClients = cl.maxclients; + Q_strncpy( gameui.globals->maptitle, clgame.maptitle, sizeof( gameui.globals->maptitle )); + + if( !cls.changelevel && !cls.changedemo ) + CL_InitEdicts (); // re-arrange edicts + + // get splash name + if( cls.demoplayback && ( cls.demonum != -1 )) + Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", cls.demoname, glState.wideScreen ? "16x9" : "4x3" )); + else Cvar_Set( "cl_levelshot_name", va( "levelshots/%s_%s", clgame.mapname, glState.wideScreen ? "16x9" : "4x3" )); + Cvar_SetValue( "scr_loading", 0.0f ); // reset progress bar + + if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + { + if( !FS_FileExists( va( "%s.bmp", cl_levelshot_name->string ), true )) + Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen + cls.scrshot_request = scrshot_plaque; // request levelshot even if exist (check filetime) + } + + for( i = 0; i < MAX_CLIENTS; i++ ) + COM_ClearCustomizationList( &cl.players[i].customdata, true ); + CL_CreateCustomizationList(); + + // request resources from server + CL_ServerCommand( true, "sendres %i\n", cl.servercount ); + + memset( &clgame.movevars, 0, sizeof( clgame.movevars )); + memset( &clgame.oldmovevars, 0, sizeof( clgame.oldmovevars )); + memset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint )); + cl.video_prepped = false; + cl.audio_prepped = false; +} + +/* +=================== +CL_ParseClientData +=================== +*/ +void CL_ParseClientData( sizebuf_t *msg ) +{ + float parsecounttime; + int i, j, command_ack; + clientdata_t *from_cd, *to_cd; + weapon_data_t *from_wd, *to_wd; + weapon_data_t nullwd[64]; + clientdata_t nullcd; + frame_t *frame; + int idx; + + // This is the last movement that the server ack'd + command_ack = cls.netchan.incoming_acknowledged; + + // this is the frame update that this message corresponds to + i = cls.netchan.incoming_sequence; + + // did we drop some frames? + if( i > cl.last_incoming_sequence + 1 ) + { + // mark as dropped + for( j = cl.last_incoming_sequence + 1; j < i; j++ ) + { + if( cl.frames[j & CL_UPDATE_MASK].receivedtime >= 0.0 ) + { + cl.frames[j & CL_UPDATE_MASK].receivedtime = -1.0f; + cl.frames[j & CL_UPDATE_MASK].latency = 0; + } + } + } + + cl.parsecount = i; // ack'd incoming messages. + cl.parsecountmod = cl.parsecount & CL_UPDATE_MASK; // index into window. + frame = &cl.frames[cl.parsecountmod]; // frame at index. + + frame->time = cl.mtime[0]; // mark network received time + frame->receivedtime = host.realtime; // time now that we are parsing. + + memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); + + // send time for that frame. + parsecounttime = cl.commands[command_ack & CL_UPDATE_MASK].senttime; + + // current time that we got a response to the command packet. + cl.commands[command_ack & CL_UPDATE_MASK].receivedtime = host.realtime; + + if( cl.last_command_ack != -1 ) + { + int last_predicted; + clientdata_t *pcd, *ppcd; + entity_state_t *ps, *pps; + weapon_data_t *wd, *pwd; + + if( !cls.spectator ) + { + last_predicted = ( cl.last_incoming_sequence + ( command_ack - cl.last_command_ack )) & CL_UPDATE_MASK; + + pps = &cl.predicted_frames[last_predicted].playerstate; + pwd = cl.predicted_frames[last_predicted].weapondata; + ppcd = &cl.predicted_frames[last_predicted].client; + + ps = &frame->playerstate[cl.playernum]; + wd = frame->weapondata; + pcd = &frame->clientdata; + } + else + { + ps = &cls.spectator_state.playerstate; + pps = &cls.spectator_state.playerstate; + pcd = &cls.spectator_state.client; + ppcd = &cls.spectator_state.client; + wd = cls.spectator_state.weapondata; + pwd = cls.spectator_state.weapondata; + } + + clgame.dllFuncs.pfnTxferPredictionData( ps, pps, pcd, ppcd, wd, pwd ); + } + + // do this after all packets read for this frame? + cl.last_command_ack = cls.netchan.incoming_acknowledged; + cl.last_incoming_sequence = cls.netchan.incoming_sequence; + + if( !cls.demoplayback ) + { + // calculate latency of this frame. + // sent time is set when usercmd is sent to server in CL_Move + // this is the # of seconds the round trip took. + float latency = host.realtime - parsecounttime; + + // fill into frame latency + frame->latency = latency; + + // negative latency makes no sense. Huge latency is a problem. + if( latency >= 0.0f && latency <= 2.0f ) + { + // drift the average latency towards the observed latency + // if round trip was fastest so far, just use that for latency value + // otherwise, move in 1 ms steps toward observed channel latency. + if( latency < cls.latency ) + cls.latency = latency; + else cls.latency += 0.001f; // drift up, so corrections are needed + } + } + else + { + frame->latency = 0.0f; + } + + // clientdata for spectators ends here + if( cls.spectator ) + { + cl.local.health = 1; + return; + } + + to_cd = &frame->clientdata; + to_wd = frame->weapondata; + + // clear to old value before delta parsing + if( MSG_ReadOneBit( msg )) + { + int delta_sequence = MSG_ReadByte( msg ); + + from_cd = &cl.frames[delta_sequence & CL_UPDATE_MASK].clientdata; + from_wd = cl.frames[delta_sequence & CL_UPDATE_MASK].weapondata; + } + else + { + memset( &nullcd, 0, sizeof( nullcd )); + memset( nullwd, 0, sizeof( nullwd )); + from_cd = &nullcd; + from_wd = nullwd; + } + + MSG_ReadClientData( msg, from_cd, to_cd, cl.mtime[0] ); + + for( i = 0; i < 64; i++ ) + { + // check for end of weapondata (and clientdata_t message) + if( !MSG_ReadOneBit( msg )) break; + + // read the weapon idx + idx = MSG_ReadUBitLong( msg, MAX_WEAPON_BITS ); + + MSG_ReadWeaponData( msg, &from_wd[idx], &to_wd[idx], cl.mtime[0] ); + } + + // make a local copy of physinfo + Q_strncpy( cls.physinfo, frame->clientdata.physinfo, sizeof( cls.physinfo )); + + cl.local.maxspeed = frame->clientdata.maxspeed; + cl.local.pushmsec = frame->clientdata.pushmsec; + cl.local.weapons = frame->clientdata.weapons; + cl.local.health = frame->clientdata.health; +} + +/* +================== +CL_ParseBaseline +================== +*/ +void CL_ParseBaseline( sizebuf_t *msg ) +{ + int i, newnum; + entity_state_t nullstate; + qboolean player; + cl_entity_t *ent; + + Delta_InitClient (); // finalize client delta's + + memset( &nullstate, 0, sizeof( nullstate )); + + while( 1 ) + { + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + if( newnum == LAST_EDICT ) break; // end of baselines + player = CL_IsPlayerIndex( newnum ); + + if( newnum >= clgame.maxEntities ) + Host_Error( "CL_AllocEdict: no free edicts\n" ); + + ent = CL_EDICT_NUM( newnum ); + memset( &ent->prevstate, 0, sizeof( ent->prevstate )); + ent->index = newnum; + + MSG_ReadDeltaEntity( msg, &ent->prevstate, &ent->baseline, newnum, player, 1.0f ); + } + + cl.instanced_baseline_count = MSG_ReadUBitLong( msg, 6 ); + + for( i = 0; i < cl.instanced_baseline_count; i++ ) + { + newnum = MSG_ReadUBitLong( msg, MAX_ENTITY_BITS ); + MSG_ReadDeltaEntity( msg, &nullstate, &cl.instanced_baseline[i], newnum, false, 1.0f ); + } +} + +/* +================ +CL_ParseLightStyle +================ +*/ +void CL_ParseLightStyle( sizebuf_t *msg ) +{ + int style; + const char *s; + float f; + + style = MSG_ReadByte( msg ); + s = MSG_ReadString( msg ); + f = MSG_ReadFloat( msg ); + + CL_SetLightstyle( style, s, f ); +} + +/* +================ +CL_ParseSetAngle + +set the view angle to this absolute value +================ +*/ +void CL_ParseSetAngle( sizebuf_t *msg ) +{ + cl.viewangles[0] = MSG_ReadBitAngle( msg, 16 ); + cl.viewangles[1] = MSG_ReadBitAngle( msg, 16 ); + cl.viewangles[2] = MSG_ReadBitAngle( msg, 16 ); +} + +/* +================ +CL_ParseAddAngle + +add the view angle yaw +================ +*/ +void CL_ParseAddAngle( sizebuf_t *msg ) +{ + pred_viewangle_t *a; + float delta_yaw; + + delta_yaw = MSG_ReadBitAngle( msg, 16 ); + + if( cl.maxclients <= 1 && !FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + { + cl.viewangles[YAW] += delta_yaw; + return; + } + + // update running counter + cl.addangletotal += delta_yaw; + + // select entry into circular buffer + cl.angle_position = (cl.angle_position + 1) & ANGLE_MASK; + a = &cl.predicted_angle[cl.angle_position]; + + // record update + a->starttime = cl.mtime[0]; + a->total = cl.addangletotal; +} + +/* +================ +CL_ParseCrosshairAngle + +offset crosshair angles +================ +*/ +void CL_ParseCrosshairAngle( sizebuf_t *msg ) +{ + cl.crosshairangle[0] = MSG_ReadChar( msg ) * 0.2f; + cl.crosshairangle[1] = MSG_ReadChar( msg ) * 0.2f; + cl.crosshairangle[2] = 0.0f; // not used for screen space +} + +/* +================ +CL_ParseRestore + +reading decals, etc. +================ +*/ +void CL_ParseRestore( sizebuf_t *msg ) +{ + string filename; + int i, mapCount; + char *pMapName; + + // mapname.HL2 + Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename )); + mapCount = MSG_ReadByte( msg ); + + // g-cont. acutally in Xash3D this does nothing. + // decals already restored on a server, and correctly transferred through levels + // but i'm leave this message for backward compatibility + for( i = 0; i < mapCount; i++ ) + { + pMapName = MSG_ReadString( msg ); + Con_Printf( "Loading decals from %s\n", pMapName ); + } +} + +/* +================ +CL_RegisterUserMessage + +register new user message or update existing +================ +*/ +void CL_RegisterUserMessage( sizebuf_t *msg ) +{ + char *pszName; + int svc_num, size; + + svc_num = MSG_ReadByte( msg ); + size = MSG_ReadWord( msg ); + pszName = MSG_ReadString( msg ); + + // important stuff + if( size == 0xFFFF ) size = -1; + svc_num = bound( 0, svc_num, 255 ); + + CL_LinkUserMessage( pszName, svc_num, size ); +} + +/* +================ +CL_UpdateUserinfo + +collect userinfo from all players +================ +*/ +void CL_UpdateUserinfo( sizebuf_t *msg ) +{ + int slot, id; + qboolean active; + player_info_t *player; + + slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); + + if( slot >= MAX_CLIENTS ) + Host_Error( "CL_ParseServerMessage: svc_updateuserinfo >= MAX_CLIENTS\n" ); + + id = MSG_ReadLong( msg ); // unique user ID + player = &cl.players[slot]; + active = MSG_ReadOneBit( msg ) ? true : false; + + if( active ) + { + Q_strncpy( player->userinfo, MSG_ReadString( msg ), sizeof( player->userinfo )); + Q_strncpy( player->name, Info_ValueForKey( player->userinfo, "name" ), sizeof( player->name )); + Q_strncpy( player->model, Info_ValueForKey( player->userinfo, "model" ), sizeof( player->model )); + player->topcolor = Q_atoi( Info_ValueForKey( player->userinfo, "topcolor" )); + player->bottomcolor = Q_atoi( Info_ValueForKey( player->userinfo, "bottomcolor" )); + player->spectator = Q_atoi( Info_ValueForKey( player->userinfo, "*hltv" )); + MSG_ReadBytes( msg, player->hashedcdkey, sizeof( player->hashedcdkey )); + + if( slot == cl.playernum ) memcpy( &gameui.playerinfo, player, sizeof( player_info_t )); + } + else memset( player, 0, sizeof( *player )); +} + +/* +============== +CL_ParseResource + +downloading and precache resource in-game +============== +*/ +void CL_ParseResource( sizebuf_t *msg ) +{ + resource_t *pResource; + + pResource = Mem_Alloc( cls.mempool, sizeof( resource_t )); + pResource->type = MSG_ReadUBitLong( msg, 4 ); + + Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName )); + pResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); + pResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 ); + pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING; + + if( FBitSet( pResource->ucFlags, RES_CUSTOM )) + MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash )); + + if( MSG_ReadOneBit( msg )) + MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved )); + + CL_AddToResourceList( pResource, &cl.resourcesneeded ); +} + +/* +================ +CL_UpdateUserPings + +collect pings and packet lossage from clients +================ +*/ +void CL_UpdateUserPings( sizebuf_t *msg ) +{ + int i, slot; + player_info_t *player; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + if( !MSG_ReadOneBit( msg )) break; // end of message + + slot = MSG_ReadUBitLong( msg, MAX_CLIENT_BITS ); + + if( slot >= MAX_CLIENTS ) + Host_Error( "CL_ParseServerMessage: svc_pings > MAX_CLIENTS\n" ); + + player = &cl.players[slot]; + player->ping = MSG_ReadUBitLong( msg, 12 ); + player->packet_loss = MSG_ReadUBitLong( msg, 7 ); + } +} + +void CL_SendConsistencyInfo( sizebuf_t *msg ) +{ + qboolean user_changed_diskfile; + vec3_t mins, maxs; + string filename; + CRC32_t crcFile; + byte md5[16]; + consistency_t *pc; + int i; + + if( !cl.need_force_consistency_response ) + return; + cl.need_force_consistency_response = false; + + MSG_BeginClientCmd( msg, clc_fileconsistency ); + + for( i = 0; i < cl.num_consistency; i++ ) + { + pc = &cl.consistency_list[i]; + + user_changed_diskfile = false; + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, pc->orig_index, MAX_MODEL_BITS ); + + if( pc->issound ) + Q_snprintf( filename, sizeof( filename ), "%s%s", DEFAULT_SOUNDPATH, pc->filename ); + else Q_strncpy( filename, pc->filename, sizeof( filename )); + + if( Q_strstr( filename, "models/" )) + { + CRC32_Init( &crcFile ); + CRC32_File( &crcFile, filename ); + crcFile = CRC32_Final( crcFile ); + user_changed_diskfile = !Mod_ValidateCRC( filename, crcFile ); + } + + switch( pc->check_type ) + { + case force_exactfile: + MD5_HashFile( md5, filename, NULL ); + pc->value = *(int *)md5; + + if( user_changed_diskfile ) + MSG_WriteUBitLong( msg, 0, 32 ); + else MSG_WriteUBitLong( msg, pc->value, 32 ); + break; + case force_model_samebounds: + case force_model_specifybounds: + if( !Mod_GetStudioBounds( filename, mins, maxs )) + Host_Error( "unable to find %s\n", filename ); + if( user_changed_diskfile ) + ClearBounds( maxs, mins ); // g-cont. especially swapped + MSG_WriteBytes( msg, mins, 12 ); + MSG_WriteBytes( msg, maxs, 12 ); + break; + default: + Host_Error( "Unknown consistency type %i\n", pc->check_type ); + break; + } + } + + MSG_WriteOneBit( msg, 0 ); +} + +/* +================== +CL_RegisterResources + +Clean up and move to next part of sequence. +================== +*/ +void CL_RegisterResources( sizebuf_t *msg ) +{ + model_t *mod; + int i; + + if( cls.dl.custom || cls.signon == SIGNONS && cls.state == ca_active ) + { + cls.dl.custom = false; + return; + } + + if( !cls.demoplayback ) + CL_SendConsistencyInfo( msg ); + + // All done precaching. + cl.worldmodel = CL_ModelHandle( 1 ); // get world pointer + + if( cl.worldmodel && cl.maxclients > 0 ) + { + ASSERT( clgame.entities != NULL ); + clgame.entities->model = cl.worldmodel; + + if( cls.state != ca_disconnected ) + { + Con_Printf( "Setting up renderer...\n" ); + + // load tempent sprites (glowshell, muzzleflashes etc) + CL_LoadClientSprites (); + + // invalidate all decal indexes + memset( cl.decal_index, 0, sizeof( cl.decal_index )); + cl.video_prepped = true; + cl.audio_prepped = true; + + CL_ClearWorld (); + + // tell rendering system we have a new set of models. + R_NewMap (); + + CL_SetupOverviewParams(); + + if( clgame.drawFuncs.R_NewMap != NULL ) + clgame.drawFuncs.R_NewMap(); + + // release unused SpriteTextures + for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + { + if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) + Mod_UnloadSpriteModel( mod ); + } + + Mod_FreeUnused (); + + if( host_developer.value <= DEV_NONE ) + Con_ClearNotify(); // clear any lines of console text + + // done with all resources, issue prespawn command. + // Include server count in case server disconnects and changes level during d/l + MSG_BeginClientCmd( msg, clc_stringcmd ); + MSG_WriteString( msg, va( "spawn %i", cl.servercount )); + } + } + else + { + MsgDev( D_ERROR, "client world model is NULL\n" ); + CL_Disconnect(); + } +} + +void CL_ParseConsistencyInfo( sizebuf_t *msg ) +{ + int lastcheck; + int delta; + int i; + int isdelta; + resource_t *pResource; + resource_t *skip_crc_change; + int skip; + consistency_t *pc; + byte nullbuffer[32]; + + memset( nullbuffer, 0, 32 ); + + cl.need_force_consistency_response = MSG_ReadOneBit( msg ); + pResource = cl.resourcesneeded.pNext; + + if( !cl.need_force_consistency_response ) + return; + + skip_crc_change = NULL; + lastcheck = 0; + + while( MSG_ReadOneBit( msg )) + { + isdelta = MSG_ReadOneBit( msg ); + + if( isdelta ) delta = MSG_ReadUBitLong( msg, 5 ) + lastcheck; + else delta = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); + + skip = delta - lastcheck; + + for( i = 0; i < skip; i++ ) + { + if( pResource != skip_crc_change && Q_strstr( pResource->szFileName, "models/" )) + Mod_NeedCRC( pResource->szFileName, false ); + pResource = pResource->pNext; + } + + if( cl.num_consistency >= MAX_MODELS ) + Host_Error( "CL_CheckConsistency: MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); + + pc = &cl.consistency_list[cl.num_consistency]; + cl.num_consistency++; + + memset( pc, 0, sizeof( consistency_t )); + pc->filename = pResource->szFileName; + pc->issound = (pResource->type == t_sound); + pc->orig_index = delta; + pc->value = 0; + + if( pResource->type == t_model && memcmp( nullbuffer, pResource->rguc_reserved, 32 )) + pc->check_type = pResource->rguc_reserved[0]; + + skip_crc_change = pResource; + lastcheck = delta; + } +} + +/* +============== +CL_ParseResourceList + +============== +*/ +void CL_ParseResourceList( sizebuf_t *msg ) +{ + resource_t *pResource; + int i, total; + + total = MSG_ReadUBitLong( msg, MAX_RESOURCE_BITS ); + + for( i = 0; i < total; i++ ) + { + pResource = Mem_Alloc( cls.mempool, sizeof( resource_t )); + pResource->type = MSG_ReadUBitLong( msg, 4 ); + + Q_strncpy( pResource->szFileName, MSG_ReadString( msg ), sizeof( pResource->szFileName )); + pResource->nIndex = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); + pResource->nDownloadSize = MSG_ReadSBitLong( msg, 24 ); + pResource->ucFlags = MSG_ReadUBitLong( msg, 3 ) & ~RES_WASMISSING; + + if( FBitSet( pResource->ucFlags, RES_CUSTOM )) + MSG_ReadBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash )); + + if( MSG_ReadOneBit( msg )) + MSG_ReadBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved )); + + CL_AddToResourceList( pResource, &cl.resourcesneeded ); + } + + CL_ParseConsistencyInfo( msg ); + + CL_StartResourceDownloading( "Verifying and downloading resources...\n", false ); +} + +/* +================== +CL_ParseVoiceInit + +================== +*/ +void CL_ParseVoiceInit( sizebuf_t *msg ) +{ + // TODO: ??? +} + +/* +================== +CL_ParseVoiceData + +================== +*/ +void CL_ParseVoiceData( sizebuf_t *msg ) +{ + // TODO: ??? +} + +/* +================== +CL_ParseResLocation + +================== +*/ +void CL_ParseResLocation( sizebuf_t *msg ) +{ + const char *url = MSG_ReadString( msg ); + + if( url && ( !Q_strnicmp( "http://", url, 7 ) || !Q_strnicmp( "https://", url, 8 ))) + { + const char *lastSlash = Q_strrchr( url, '/' ); + + if( lastSlash && lastSlash[1] == '\0' ) + Q_strncpy( cl.downloadUrl, url, sizeof( cl.downloadUrl )); + else Q_snprintf( cl.downloadUrl, sizeof( cl.downloadUrl ), "%s/", url ); + MsgDev( D_REPORT, "Using %s as primary download location\n", cl.downloadUrl ); + } +} + +/* +============== +CL_ParseHLTV + +spectator message (hltv) +sended from game.dll +============== +*/ +void CL_ParseHLTV( sizebuf_t *msg ) +{ + switch( MSG_ReadByte( msg )) + { + case HLTV_ACTIVE: + cl.proxy_redirect = true; + cls.spectator = true; + break; + case HLTV_STATUS: + MSG_ReadLong( msg ); + MSG_ReadShort( msg ); + MSG_ReadWord( msg ); + MSG_ReadLong( msg ); + MSG_ReadLong( msg ); + MSG_ReadWord( msg ); + break; + case HLTV_LISTEN: + cls.signon = SIGNONS; + NET_StringToAdr( MSG_ReadString( msg ), &cls.hltv_listen_address ); +// NET_JoinGroup( cls.netchan.sock, cls.hltv_listen_address ); + SCR_EndLoadingPlaque(); + break; + default: + MsgDev( D_ERROR, "CL_ParseHLTV: unknown HLTV command.\n" ); + break; + } +} + +/* +============== +CL_ParseDirector + +spectator message (director) +sended from game.dll +============== +*/ +void CL_ParseDirector( sizebuf_t *msg ) +{ + int iSize = MSG_ReadByte( msg ); + byte pbuf[256]; + + // parse user message into buffer + MSG_ReadBytes( msg, pbuf, iSize ); + clgame.dllFuncs.pfnDirectorMessage( iSize, pbuf ); +} + +/* +============== +CL_ParseScreenShake + +Set screen shake +============== +*/ +void CL_ParseScreenShake( sizebuf_t *msg ) +{ + clgame.shake.amplitude = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); + clgame.shake.duration = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<12)); + clgame.shake.frequency = (float)(word)MSG_ReadShort( msg ) * (1.0f / (float)(1<<8)); + clgame.shake.time = cl.time + max( clgame.shake.duration, 0.01f ); + clgame.shake.next_shake = 0.0f; // apply immediately +} + +/* +============== +CL_ParseScreenFade + +Set screen fade +============== +*/ +void CL_ParseScreenFade( sizebuf_t *msg ) +{ + float duration, holdTime; + screenfade_t *sf = &clgame.fade; + float flScale; + + duration = (float)MSG_ReadShort( msg ); + holdTime = (float)MSG_ReadShort( msg ); + sf->fadeFlags = MSG_ReadShort( msg ); + flScale = ( sf->fadeFlags & FFADE_LONGFADE ) ? (1.0f / 256.0f) : (1.0f / 4096.0f); + + sf->fader = MSG_ReadByte( msg ); + sf->fadeg = MSG_ReadByte( msg ); + sf->fadeb = MSG_ReadByte( msg ); + sf->fadealpha = MSG_ReadByte( msg ); + sf->fadeSpeed = 0.0f; + sf->fadeEnd = duration * flScale; + sf->fadeReset = holdTime * flScale; + + // calc fade speed + if( duration > 0 ) + { + if( sf->fadeFlags & FFADE_OUT ) + { + if( sf->fadeEnd ) + { + sf->fadeSpeed = -(float)sf->fadealpha / sf->fadeEnd; + } + + sf->fadeEnd += cl.time; + sf->fadeReset += sf->fadeEnd; + } + else + { + if( sf->fadeEnd ) + { + sf->fadeSpeed = (float)sf->fadealpha / sf->fadeEnd; + } + + sf->fadeReset += cl.time; + sf->fadeEnd += sf->fadeReset; + } + } +} + +/* +============== +CL_ParseCvarValue + +Find the client cvar value +and sent it back to the server +============== +*/ +void CL_ParseCvarValue( sizebuf_t *msg ) +{ + const char *cvarName = MSG_ReadString( msg ); + convar_t *cvar = Cvar_FindVar( cvarName ); + + // build the answer + MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue ); + MSG_WriteString( &cls.netchan.message, cvar ? cvar->string : "Not Found" ); +} + +/* +============== +CL_ParseCvarValue2 + +Find the client cvar value +and sent it back to the server +============== +*/ +void CL_ParseCvarValue2( sizebuf_t *msg ) +{ + int requestID = MSG_ReadLong( msg ); + const char *cvarName = MSG_ReadString( msg ); + convar_t *cvar = Cvar_FindVar( cvarName ); + + // build the answer + MSG_BeginClientCmd( &cls.netchan.message, clc_requestcvarvalue2 ); + MSG_WriteLong( &cls.netchan.message, requestID ); + MSG_WriteString( &cls.netchan.message, cvarName ); + + if( cvar ) + { + // cheater can change value ignoring Cvar_Set so we responce incorrect value + if( cvar->value != Q_atof( cvar->string )) + MSG_WriteString( &cls.netchan.message, va( "%s (%g)", cvar->string, cvar->value )); + else MSG_WriteString( &cls.netchan.message, cvar->string ); + } + else + { + MSG_WriteString( &cls.netchan.message, "Not Found" ); + } +} + +/* +============== +CL_DispatchUserMessage + +Dispatch user message by engine request +============== +*/ +qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf ) +{ + int i; + + if( !COM_CheckString( pszName )) + return false; + + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + // search for user message + if( !Q_strcmp( clgame.msg[i].name, pszName )) + break; + } + + if( i == MAX_USER_MESSAGES ) + { + Con_DPrintf( S_ERROR "UserMsg: bad message %s\n", pszName ); + return false; + } + + if( clgame.msg[i].func ) + { + clgame.msg[i].func( pszName, iSize, pbuf ); + } + else + { + Con_DPrintf( S_ERROR "UserMsg: No pfn %s %d\n", clgame.msg[i].name, clgame.msg[i].number ); + clgame.msg[i].func = CL_UserMsgStub; // throw warning only once + } + return true; +} + +/* +============== +CL_ParseUserMessage + +handles all user messages +============== +*/ +void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ) +{ + byte pbuf[MAX_USERMSG_LENGTH]; + int i, iSize; + + // NOTE: any user message is really parse at engine, not in client.dll + if( svc_num <= svc_lastmsg || svc_num > ( MAX_USER_MESSAGES + svc_lastmsg )) + { + // out or range + Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num ); + return; + } + + for( i = 0; i < MAX_USER_MESSAGES; i++ ) + { + // search for user message + if( clgame.msg[i].number == svc_num ) + break; + } + + if( i == MAX_USER_MESSAGES ) // probably unregistered + Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num ); + + // NOTE: some user messages handled into engine + if( !Q_strcmp( clgame.msg[i].name, "ScreenShake" )) + { + CL_ParseScreenShake( msg ); + return; + } + else if( !Q_strcmp( clgame.msg[i].name, "ScreenFade" )) + { + CL_ParseScreenFade( msg ); + return; + } + + iSize = clgame.msg[i].size; + + // message with variable sizes receive an actual size as first byte + if( iSize == -1 ) iSize = MSG_ReadWord( msg ); + + // parse user message into buffer + MSG_ReadBytes( msg, pbuf, iSize ); + + if( clgame.msg[i].func ) + { + clgame.msg[i].func( clgame.msg[i].name, iSize, pbuf ); + +#ifdef HACKS_RELATED_HLMODS + // run final credits for Half-Life because hl1 doesn't have call END_SECTION + if( !Q_stricmp( clgame.msg[i].name, "HudText" ) && !Q_stricmp( GI->gamefolder, "valve" )) + { + // it's a end, so we should run credits + if( !Q_strcmp( (char *)pbuf, "END3" )) + Host_Credits(); + } +#endif + } + else + { + Con_DPrintf( S_ERROR "UserMsg: No pfn %s %d\n", clgame.msg[i].name, clgame.msg[i].number ); + clgame.msg[i].func = CL_UserMsgStub; // throw warning only once + } +} + +/* +===================== +CL_ResetFrame +===================== +*/ +void CL_ResetFrame( frame_t *frame ) +{ + memset( &frame->graphdata, 0, sizeof( netbandwidthgraph_t )); + frame->receivedtime = host.realtime; + frame->valid = true; + frame->choked = false; + frame->latency = 0.0; + frame->time = cl.mtime[0]; +} + +/* +===================================================================== + +ACTION MESSAGES + +===================================================================== +*/ +/* +===================== +CL_ParseServerMessage + +dispatch messages +===================== +*/ +void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ) +{ + size_t bufStart, playerbytes; + int cmd, param1, param2; + int old_background; + + cls_message_debug.parsing = true; // begin parsing + starting_count = MSG_GetNumBytesRead( msg ); // updates each frame + + if( normal_message ) + { + // assume no entity/player update this packet + if( cls.state == ca_active ) + { + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].valid = false; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = false; + } + else + { + CL_ResetFrame( &cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK] ); + } + } + + // parse the message + while( 1 ) + { + if( MSG_CheckOverflow( msg )) + { + Host_Error( "CL_ParseServerMessage: overflow!\n" ); + return; + } + + // mark start position + bufStart = MSG_GetNumBytesRead( msg ); + + // end of message (align bits) + if( MSG_GetNumBitsLeft( msg ) < 8 ) + break; + + cmd = MSG_ReadServerCmd( msg ); + + // record command for debugging spew on parse problem + CL_Parse_RecordCommand( cmd, bufStart ); + + // other commands + switch( cmd ) + { + case svc_bad: + Host_Error( "svc_bad\n" ); + break; + case svc_nop: + // this does nothing + break; + case svc_disconnect: + CL_Drop (); + Host_AbortCurrentFrame (); + break; + case svc_event: + CL_ParseEvent( msg ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_changing: + old_background = cl.background; + if( MSG_ReadOneBit( msg )) + { + cls.changelevel = true; + S_StopAllSounds( true ); + + MsgDev( D_INFO, "Server changing, reconnecting\n" ); + + if( cls.demoplayback ) + { + SCR_BeginLoadingPlaque( cl.background ); + cls.changedemo = true; + } + + CL_ClearState (); + CL_InitEdicts (); // re-arrange edicts + } + else MsgDev( D_INFO, "Server disconnected, reconnecting\n" ); + + if( cls.demoplayback ) + { + cl.background = (cls.demonum != -1) ? true : false; + cls.state = ca_connected; + } + else + { + // g-cont. local client skip the challenge + if( SV_Active()) cls.state = ca_disconnected; + else cls.state = ca_connecting; + cl.background = old_background; + cls.connect_time = MAX_HEARTBEAT; + } + break; + case svc_setview: + CL_ParseViewEntity( msg ); + break; + case svc_sound: + CL_ParseSoundPacket( msg ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_time: + CL_ParseServerTime( msg ); + break; + case svc_print: + Con_Printf( "%s", MSG_ReadString( msg )); + break; + case svc_stufftext: + Cbuf_AddText( MSG_ReadString( msg )); + break; + case svc_setangle: + CL_ParseSetAngle( msg ); + break; + case svc_serverdata: + Cbuf_Execute(); // make sure any stuffed commands are done + CL_ParseServerData( msg ); + break; + case svc_lightstyle: + CL_ParseLightStyle( msg ); + break; + case svc_updateuserinfo: + CL_UpdateUserinfo( msg ); + break; + case svc_deltatable: + Delta_ParseTableField( msg ); + break; + case svc_clientdata: + CL_ParseClientData( msg ); + cl.frames[cl.parsecountmod].graphdata.client += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_resource: + CL_ParseResource( msg ); + break; + case svc_pings: + CL_UpdateUserPings( msg ); + break; + case svc_particle: + CL_ParseParticles( msg ); + break; + case svc_restoresound: + CL_ParseRestoreSoundPacket( msg ); + cl.frames[cl.parsecountmod].graphdata.sound += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_spawnstatic: + CL_ParseStaticEntity( msg ); + break; + case svc_event_reliable: + CL_ParseReliableEvent( msg ); + cl.frames[cl.parsecountmod].graphdata.event += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_spawnbaseline: + CL_ParseBaseline( msg ); + break; + case svc_temp_entity: + CL_ParseTempEntity( msg ); + cl.frames[cl.parsecountmod].graphdata.tentities += MSG_GetNumBytesRead( msg ) - bufStart; + break; + case svc_setpause: + cl.paused = ( MSG_ReadOneBit( msg ) != 0 ); + break; + case svc_signonnum: + CL_ParseSignon( msg ); + break; + case svc_centerprint: + CL_CenterPrint( MSG_ReadString( msg ), 0.25f ); + break; + case svc_intermission: + cl.intermission = 1; + break; + case svc_finale: + CL_ParseFinaleCutscene( msg, 2 ); + break; + case svc_cdtrack: + param1 = MSG_ReadByte( msg ); + param1 = bound( 1, param1, MAX_CDTRACKS ); // tracknum + param2 = MSG_ReadByte( msg ); + param2 = bound( 1, param2, MAX_CDTRACKS ); // loopnum + S_StartBackgroundTrack( clgame.cdtracks[param1-1], clgame.cdtracks[param2-1], 0, false ); + break; + case svc_restore: + CL_ParseRestore( msg ); + break; + case svc_cutscene: + CL_ParseFinaleCutscene( msg, 3 ); + break; + case svc_weaponanim: + param1 = MSG_ReadByte( msg ); // iAnim + param2 = MSG_ReadByte( msg ); // body + CL_WeaponAnim( param1, param2 ); + break; + case svc_bspdecal: + CL_ParseStaticDecal( msg ); + break; + case svc_roomtype: + param1 = MSG_ReadShort( msg ); + Cvar_SetValue( "room_type", param1 ); + break; + case svc_addangle: + CL_ParseAddAngle( msg ); + break; + case svc_usermessage: + CL_RegisterUserMessage( msg ); + break; + case svc_packetentities: + playerbytes = CL_ParsePacketEntities( msg, false ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_deltapacketentities: + playerbytes = CL_ParsePacketEntities( msg, true ); + cl.frames[cl.parsecountmod].graphdata.players += playerbytes; + cl.frames[cl.parsecountmod].graphdata.entities += MSG_GetNumBytesRead( msg ) - bufStart - playerbytes; + break; + case svc_choke: + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].choked = true; + cl.frames[cls.netchan.incoming_sequence & CL_UPDATE_MASK].receivedtime = -2.0; + break; + case svc_resourcelist: + CL_ParseResourceList( msg ); + break; + case svc_deltamovevars: + CL_ParseMovevars( msg ); + break; + case svc_resourcerequest: + CL_ParseResourceRequest( msg ); + break; + case svc_customization: + CL_ParseCustomization( msg ); + break; + case svc_crosshairangle: + CL_ParseCrosshairAngle( msg ); + break; + case svc_soundfade: + CL_ParseSoundFade( msg ); + break; + case svc_filetxferfailed: + CL_ParseFileTransferFailed( msg ); + break; + case svc_hltv: + CL_ParseHLTV( msg ); + break; + case svc_director: + CL_ParseDirector( msg ); + break; + case svc_voiceinit: + CL_ParseVoiceInit( msg ); + break; + case svc_voicedata: + CL_ParseVoiceData( msg ); + break; + case svc_resourcelocation: + CL_ParseResLocation( msg ); + break; + case svc_querycvarvalue: + CL_ParseCvarValue( msg ); + break; + case svc_querycvarvalue2: + CL_ParseCvarValue2( msg ); + break; + default: + CL_ParseUserMessage( msg, cmd ); + cl.frames[cl.parsecountmod].graphdata.usr += MSG_GetNumBytesRead( msg ) - bufStart; + break; + } + } + + cl.frames[cl.parsecountmod].graphdata.msgbytes += MSG_GetNumBytesRead( msg ) - starting_count; + cls_message_debug.parsing = false; // done + + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + if( !cls.demoplayback ) + { + if( cls.demorecording && !cls.demowaiting ) + { + CL_WriteDemoMessage( false, starting_count, msg ); + } + else if( cls.state != ca_active ) + { + CL_WriteDemoMessage( true, starting_count, msg ); + } + } +} \ No newline at end of file diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c new file mode 100644 index 00000000..347a6573 --- /dev/null +++ b/engine/client/cl_pmove.c @@ -0,0 +1,1410 @@ +/* +cl_pmove.c - client-side player physic +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "cl_tent.h" +#include "pm_local.h" +#include "particledef.h" +#include "studio.h" + +#define MAX_FORWARD 6 // forward probes for set idealpitch +#define MIN_CORRECTION_DISTANCE 0.25f // use smoothing if error is > this +#define MIN_PREDICTION_EPSILON 0.5f // complain if error is > this and we have cl_showerror set +#define MAX_PREDICTION_ERROR 64.0f // above this is assumed to be a teleport, don't smooth, etc. + +/* +============= +CL_ClearPhysEnts + +============= +*/ +void CL_ClearPhysEnts( void ) +{ + clgame.pmove->numtouch = 0; + clgame.pmove->numvisent = 0; + clgame.pmove->nummoveent = 0; + clgame.pmove->numphysent = 0; +} + +/* +============= +CL_PushPMStates + +============= +*/ +void CL_PushPMStates( void ) +{ + if( clgame.pushed ) + { + MsgDev( D_ERROR, "PushPMStates: stack overflow\n"); + } + else + { + clgame.oldphyscount = clgame.pmove->numphysent; + clgame.oldviscount = clgame.pmove->numvisent; + clgame.pushed = true; + } + +} + +/* +============= +CL_PopPMStates + +============= +*/ +void CL_PopPMStates( void ) +{ + if( clgame.pushed ) + { + clgame.pmove->numphysent = clgame.oldphyscount; + clgame.pmove->numvisent = clgame.oldviscount; + clgame.pushed = false; + } + else + { + MsgDev( D_ERROR, "PopPMStates: stack underflow\n"); + } +} + +/* +=============== +CL_IsPredicted +=============== +*/ +qboolean CL_IsPredicted( void ) +{ + if( cl_nopred->value || cl.intermission ) + return false; + return true; +} + +/* +=============== +CL_SetLastUpdate +=============== +*/ +void CL_SetLastUpdate( void ) +{ + cls.lastupdate_sequence = cls.netchan.incoming_sequence; +} + +/* +=============== +CL_RedoPrediction +=============== +*/ +void CL_RedoPrediction( void ) +{ + if ( cls.netchan.incoming_sequence != cls.lastupdate_sequence ) + { + CL_PredictMovement( true ); + CL_CheckPredictionError(); + } +} + +/* +=============== +CL_SetIdealPitch +=============== +*/ +void CL_SetIdealPitch( void ) +{ + float angleval, sinval, cosval; + int i, j, step, dir, steps; + float z[MAX_FORWARD]; + vec3_t top, bottom; + pmtrace_t tr; + + if( cl.local.onground == -1 ) + return; + + angleval = cl.viewangles[YAW] * M_PI2 / 360.0f; + SinCos( angleval, &sinval, &cosval ); + + // Now move forward by 36, 48, 60, etc. units from the eye position and drop lines straight down + // 160 or so units to see what's below + for( i = 0; i < MAX_FORWARD; i++ ) + { + top[0] = cl.simorg[0] + cosval * (i + 3.0f) * 12.0f; + top[1] = cl.simorg[1] + sinval * (i + 3.0f) * 12.0f; + top[2] = cl.simorg[2] + cl.viewheight[2]; + + bottom[0] = top[0]; + bottom[1] = top[1]; + bottom[2] = top[2] - 160.0f; + + // skip any monsters (only world and brush models) + tr = CL_TraceLine( top, bottom, PM_STUDIO_BOX ); + if( tr.allsolid ) return; // looking at a wall, leave ideal the way is was + + if( tr.fraction == 1.0f ) + return; // near a dropoff + + z[i] = top[2] + tr.fraction * (bottom[2] - top[2]); + } + + dir = 0; + steps = 0; + + for( j = 1; j < i; j++ ) + { + step = z[j] - z[j-1]; + if( step > -ON_EPSILON && step < ON_EPSILON ) + continue; + + if( dir && ( step-dir > ON_EPSILON || step-dir < -ON_EPSILON )) + return; // mixed changes + + steps++; + dir = step; + } + + if( !dir ) + { + cl.local.idealpitch = 0.0f; + return; + } + + if( steps < 2 ) return; + cl.local.idealpitch = -dir * cl_idealpitchscale->value; +} + +/* +================== +CL_PlayerTeleported + +check for instant movement in case +we don't want interpolate this +================== +*/ +qboolean CL_PlayerTeleported( local_state_t *from, local_state_t *to ) +{ + int len, maxlen; + vec3_t delta; + + VectorSubtract( to->playerstate.origin, from->playerstate.origin, delta ); + + // compute potential max movement in units per frame and compare with entity movement + maxlen = ( clgame.movevars.maxvelocity * ( 1.0 / GAME_FPS )); + len = VectorLength( delta ); + + return (len > maxlen); +} + +/* +=================== +CL_CheckPredictionError +=================== +*/ +void CL_CheckPredictionError( void ) +{ + int frame, cmd; + static int pos = 0; + vec3_t delta; + float dist; + + if( !CL_IsPredicted( )) + return; + + // calculate the last usercmd_t we sent that the server has processed + frame = ( cls.netchan.incoming_acknowledged ) & CL_UPDATE_MASK; + cmd = cl.parsecountmod; + + // compare what the server returned with what we had predicted it to be + VectorSubtract( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame], delta ); + dist = VectorLength( delta ); + + // save the prediction error for interpolation + if( dist > MAX_PREDICTION_ERROR ) + { + if( cl_showerror->value && host_developer.value ) + Con_NPrintf( 10 + ( ++pos & 3 ), "^3player teleported:^7 %.3f units\n", dist ); + + // a teleport or something or gamepaused + VectorClear( cl.local.prediction_error ); + } + else + { + if( cl_showerror->value && dist > MIN_PREDICTION_EPSILON && host_developer.value ) + Con_NPrintf( 10 + ( ++pos & 3 ), "^1prediction error:^7 %.3f units\n", dist ); + + VectorCopy( cl.frames[cmd].playerstate[cl.playernum].origin, cl.local.predicted_origins[frame] ); + + // save for error interpolation + VectorCopy( delta, cl.local.prediction_error ); + + if(( dist > MIN_CORRECTION_DISTANCE ) && (( cl.maxclients > 1 ) || FBitSet( host.features, ENGINE_FIXED_FRAMERATE ))) + cls.correction_time = cl_smoothtime->value; + } +} + +/* +============= +CL_SetUpPlayerPrediction + +Calculate the new position of players, without other player clipping +We do this to set up real player prediction. +Players are predicted twice, first without clipping other players, +then with clipping against them. +This sets up the first phase. +============= +*/ +void CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient ) +{ + entity_state_t *state; + predicted_player_t *player; + cl_entity_t *ent; + int i; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + state = &cl.frames[cl.parsecountmod].playerstate[i]; + player = &cls.predicted_players[i]; + + player->active = false; + + if( state->messagenum != cl.parsecount ) + continue; // not present this frame + + if( !state->modelindex ) + continue; + + player->active = true; + player->movetype = state->movetype; + player->solid = state->solid; + player->usehull = state->usehull; + + if( FBitSet( state->effects, EF_NODRAW ) && !bIncludeLocalClient && ( cl.playernum == i )) + continue; + + // note that the local player is special, since he moves locally + // we use his last predicted postition + if( cl.playernum == i ) + { + VectorCopy( state->origin, player->origin ); + VectorCopy( state->angles, player->angles ); + } + else + { + ent = CL_GetEntityByIndex( i + 1 ); + + CL_ComputePlayerOrigin( ent ); + + VectorCopy( ent->origin, player->origin ); + VectorCopy( ent->angles, player->angles ); + } + } +} + +void CL_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr ) +{ + Assert( tr != NULL ); + + if( clgame.dllFuncs.pfnClipMoveToEntity != NULL ) + { + // do custom sweep test + clgame.dllFuncs.pfnClipMoveToEntity( pe, start, mins, maxs, end, tr ); + } + else + { + // function is missed, so we didn't hit anything + tr->allsolid = false; + } +} + +static void CL_CopyEntityToPhysEnt( physent_t *pe, entity_state_t *state, qboolean visent ) +{ + model_t *mod = CL_ModelHandle( state->modelindex ); + + pe->player = 0; + + if( state->number >= 1 && state->number <= cl.maxclients ) + pe->player = state->number; + + if( pe->player ) + { + // client or bot + Q_snprintf( pe->name, sizeof( pe->name ), "player %i", pe->player - 1 ); + } + else + { + // otherwise copy the modelname + Q_strncpy( pe->name, mod->name, sizeof( pe->name )); + } + + pe->model = pe->studiomodel = NULL; + + VectorCopy( state->mins, pe->mins ); + VectorCopy( state->maxs, pe->maxs ); + + if( state->solid == SOLID_BBOX ) + { + if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX )) + pe->studiomodel = mod; + } + else + { + if( pe->solid != SOLID_BSP && ( mod != NULL ) && ( mod->type == mod_studio )) + pe->studiomodel = mod; + else pe->model = mod; + } + + // rare case: not solid entities in vistrace + if( visent && VectorIsNull( pe->mins )) + { + VectorCopy( mod->mins, pe->mins ); + VectorCopy( mod->maxs, pe->maxs ); + } + + pe->info = state->number; + VectorCopy( state->origin, pe->origin ); + VectorCopy( state->angles, pe->angles ); + + pe->solid = state->solid; + pe->rendermode = state->rendermode; + pe->skin = state->skin; + pe->frame = state->frame; + pe->sequence = state->sequence; + + memcpy( &pe->controller[0], &state->controller[0], sizeof( pe->controller )); + memcpy( &pe->blending[0], &state->blending[0], sizeof( pe->blending )); + + pe->movetype = state->movetype; + pe->takedamage = (pe->player) ? DAMAGE_YES : DAMAGE_NO; + pe->team = state->team; + pe->classnumber = state->playerclass; + pe->blooddecal = 0; // unused in GoldSrc + + // for mods + pe->iuser1 = state->iuser1; + pe->iuser2 = state->iuser2; + pe->iuser3 = state->iuser3; + pe->iuser4 = state->iuser4; + pe->fuser1 = state->fuser1; + pe->fuser2 = state->fuser2; + pe->fuser3 = state->fuser3; + pe->fuser4 = state->fuser4; + + VectorCopy( state->vuser1, pe->vuser1 ); + VectorCopy( state->vuser2, pe->vuser2 ); + VectorCopy( state->vuser3, pe->vuser3 ); + VectorCopy( state->vuser4, pe->vuser4 ); +} + +/* +==================== +CL_AddLinksToPmove + +collect solid entities +==================== +*/ +void CL_AddLinksToPmove( frame_t *frame ) +{ + entity_state_t *state; + model_t *model; + physent_t *pe; + int i; + + if( !frame->valid ) return; + + for( i = 0; i < frame->num_entities; i++ ) + { + state = &cls.packet_entities[(frame->first_entity + i) % cls.num_client_entities]; + + if( state->number >= 1 && state->number <= cl.maxclients ) + continue; + + if( !state->modelindex ) + continue; + + model = CL_ModelHandle( state->modelindex ); + if( !model ) continue; + + if(( state->owner != 0 ) && ( state->owner == cl.playernum + 1 )) + continue; + + if(( model->hulls[1].lastclipnode || model->type == mod_studio ) && clgame.pmove->numvisent < MAX_PHYSENTS ) + { + pe = &clgame.pmove->visents[clgame.pmove->numvisent]; + CL_CopyEntityToPhysEnt( pe, state, true ); + clgame.pmove->numvisent++; + } + + if( state->solid == SOLID_TRIGGER || ( state->solid == SOLID_NOT && state->skin >= CONTENTS_EMPTY )) + continue; + + // dead body + if( state->mins[2] == 0.0f && state->maxs[2] == 1.0f ) + continue; + + // can't collide with zeroed hull + if( VectorIsNull( state->mins ) && VectorIsNull( state->maxs )) + continue; + + if ( !model->hulls[1].lastclipnode && model->type != mod_studio ) + continue; + + if( state->solid == SOLID_NOT && state->skin < CONTENTS_EMPTY ) + { + if( clgame.pmove->nummoveent >= MAX_MOVEENTS ) + continue; + + pe = &clgame.pmove->moveents[clgame.pmove->nummoveent]; + CL_CopyEntityToPhysEnt( pe, state, false ); + clgame.pmove->nummoveent++; + } + else + { + // reserve slots for all the clients + if( clgame.pmove->numphysent >= ( MAX_PHYSENTS - cl.maxclients )) + continue; + + pe = &clgame.pmove->physents[clgame.pmove->numphysent]; + CL_CopyEntityToPhysEnt( pe, state, false ); + clgame.pmove->numphysent++; + } + } +} + +/* +=============== +CL_SetSolidEntities + +Builds all the pmove physents for the current frame +=============== +*/ +void CL_SetSolidEntities( void ) +{ + physent_t *pe = clgame.pmove->physents; + + // setup physents + clgame.pmove->numvisent = 1; + clgame.pmove->numphysent = 1; + clgame.pmove->nummoveent = 0; + + memset( clgame.pmove->physents, 0, sizeof( physent_t )); + memset( clgame.pmove->visents, 0, sizeof( physent_t )); + + pe->model = cl.worldmodel; + if( pe->model ) Q_strncpy( pe->name, pe->model->name, sizeof( pe->name )); + pe->takedamage = DAMAGE_YES; + pe->solid = SOLID_BSP; + + // share to visents + clgame.pmove->visents[0] = clgame.pmove->physents[0]; + + // add all other entities exlucde players + CL_AddLinksToPmove( &cl.frames[cl.parsecountmod] ); +} + +/* +=============== +CL_SetSolidPlayers + +Builds all the pmove physents for the current frame +Note that CL_SetUpPlayerPrediction() must be called first! +pmove must be setup with world and solid entity hulls before calling +(via CL_PredictMove) +=============== +*/ +void CL_SetSolidPlayers( int playernum ) +{ + entity_state_t *state; + predicted_player_t *player; + physent_t *pe; + int i; + + if( !cl_solid_players->value ) + return; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + state = &cl.frames[cl.parsecountmod].playerstate[i]; + player = &cls.predicted_players[i]; + + if( playernum == -1 ) + { + if( i != cl.playernum && !player->active ) + continue; + } + else + { + if( !player->active ) + continue; // not present this frame + + // the player object never gets added + if( playernum == i ) + continue; + } + + if( player->solid == SOLID_NOT ) + continue; // dead body + + if( clgame.pmove->numphysent >= MAX_PHYSENTS ) + break; + + pe = &clgame.pmove->physents[clgame.pmove->numphysent]; + CL_CopyEntityToPhysEnt( pe, state, false ); + clgame.pmove->numphysent++; + + // some fields needs to be override from cls.predicted_players + VectorCopy( player->origin, pe->origin ); + VectorCopy( player->angles, pe->angles ); + VectorCopy( clgame.pmove->player_mins[player->usehull], pe->mins ); + VectorCopy( clgame.pmove->player_maxs[player->usehull], pe->maxs ); + pe->movetype = player->movetype; + pe->solid = player->solid; + } +} + +/* +============= +CL_TruePointContents + +============= +*/ +int CL_TruePointContents( const vec3_t p ) +{ + int i, contents; + int oldhull; + hull_t *hull; + vec3_t test, offset; + physent_t *pe; + + // sanity check + if( !p ) return CONTENTS_NONE; + + oldhull = clgame.pmove->usehull; + + // get base contents from world + contents = PM_HullPointContents( &cl.worldmodel->hulls[0], 0, p ); + + for( i = 0; i < clgame.pmove->nummoveent; i++ ) + { + pe = &clgame.pmove->moveents[i]; + + if( pe->solid != SOLID_NOT ) // disabled ? + continue; + + // only brushes can have special contents + if( !pe->model || pe->model->type != mod_brush ) + continue; + + // check water brushes accuracy + clgame.pmove->usehull = 2; + hull = PM_HullForBsp( pe, clgame.pmove, offset ); + clgame.pmove->usehull = oldhull; + + // offset the test point appropriately for this hull. + VectorSubtract( p, offset, test ); + + if( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles )) + { + matrix4x4 matrix; + + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, p, test ); + } + + // test hull for intersection with this model + if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY ) + continue; + + // compare contents ranking + if( RankForContents( pe->skin ) > RankForContents( contents )) + contents = pe->skin; // new content has more priority + } + + return contents; +} + +/* +============= +CL_WaterEntity + +============= +*/ +int CL_WaterEntity( const float *rgflPos ) +{ + physent_t *pe; + hull_t *hull; + vec3_t test, offset; + int i, oldhull; + + if( !rgflPos ) return -1; + + oldhull = clgame.pmove->usehull; + + for( i = 0; i < clgame.pmove->nummoveent; i++ ) + { + pe = &clgame.pmove->moveents[i]; + + if( pe->solid != SOLID_NOT ) // disabled ? + continue; + + // only brushes can have special contents + if( !pe->model || pe->model->type != mod_brush ) + continue; + + // check water brushes accuracy + clgame.pmove->usehull = 2; + hull = PM_HullForBsp( pe, clgame.pmove, offset ); + clgame.pmove->usehull = oldhull; + + // offset the test point appropriately for this hull. + VectorSubtract( rgflPos, offset, test ); + + if( FBitSet( pe->model->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( pe->angles )) + { + matrix4x4 matrix; + + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, rgflPos, test ); + } + + // test hull for intersection with this model + if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY ) + continue; + + // found water entity + return pe->info; + } + return -1; +} + +/* +============= +CL_TraceLine + +a simple engine traceline +============= +*/ +pmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags ) +{ + int old_usehull; + pmtrace_t tr; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = 2; + tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numphysent, clgame.pmove->physents, -1, NULL ); + clgame.pmove->usehull = old_usehull; + + return tr; +} + +/* +============= +CL_VisTraceLine + +trace by visible objects (thats can be non-solid) +============= +*/ +pmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags ) +{ + int old_usehull; + static pmtrace_t tr; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = 2; + tr = PM_PlayerTraceExt( clgame.pmove, start, end, flags, clgame.pmove->numvisent, clgame.pmove->visents, -1, NULL ); + clgame.pmove->usehull = old_usehull; + + return &tr; +} + +/* +============= +CL_GetWaterEntity + +returns water brush where inside pos +============= +*/ +cl_entity_t *CL_GetWaterEntity( const float *rgflPos ) +{ + int entnum; + + entnum = CL_WaterEntity( rgflPos ); + if( entnum <= 0 ) return NULL; // world or not water + + return CL_GetEntityByIndex( entnum ); +} + +int CL_TestLine( const vec3_t start, const vec3_t end, int flags ) +{ + return PM_TestLineExt( clgame.pmove, clgame.pmove->physents, clgame.pmove->numphysent, start, end, flags ); +} + +static int pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) +{ + return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, NULL ); +} + +static void pfnStuckTouch( int hitent, pmtrace_t *tr ) +{ + int i; + + for( i = 0; i < clgame.pmove->numtouch; i++ ) + { + if( clgame.pmove->touchindex[i].ent == hitent ) + return; + } + + if( clgame.pmove->numtouch >= MAX_PHYSENTS ) + { + MsgDev( D_ERROR, "PM_StuckTouch: MAX_TOUCHENTS limit exceeded\n" ); + return; + } + + VectorCopy( clgame.pmove->velocity, tr->deltavelocity ); + tr->ent = hitent; + + clgame.pmove->touchindex[clgame.pmove->numtouch++] = *tr; +} + +static int pfnPointContents( float *p, int *truecontents ) +{ + int cont, truecont; + + truecont = cont = CL_TruePointContents( p ); + if( truecontents ) *truecontents = truecont; + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +static int pfnTruePointContents( float *p ) +{ + return CL_TruePointContents( p ); +} + +static int pfnHullPointContents( struct hull_s *hull, int num, float *p ) +{ + return PM_HullPointContents( hull, num, p ); +} + +static pmtrace_t pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe ) +{ + return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); +} + +static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL ); + break; + } + + clgame.pmove->usehull = old_usehull; + + return &tr; +} + +static hull_t *pfnHullForBsp( physent_t *pe, float *offset ) +{ + return PM_HullForBsp( pe, clgame.pmove, offset ); +} + +static float pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace ) +{ + int old_usehull; + vec3_t start_l, end_l; + vec3_t offset, temp; + qboolean rotated; + matrix4x4 matrix; + hull_t *hull; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = 2; + + hull = PM_HullForBsp( pe, clgame.pmove, offset ); + + clgame.pmove->usehull = old_usehull; + + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + rotated = true; + else rotated = false; + + if( rotated ) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + else + { + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + } + + PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace ); + trace->ent = NULL; + + if( rotated ) + { + VectorCopy( trace->plane.normal, temp ); + Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); + } + + VectorLerp( start, trace->fraction, end, trace->endpos ); + + return trace->fraction; +} + +static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= clgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &clgame.pmove->physents[ground]; + return PM_TraceTexture( pe, vstart, vend ); +} + +static void pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ) +{ + if( !clgame.pmove->runfuncs ) + return; + + S_StartSound( NULL, clgame.pmove->player_index + 1, channel, S_RegisterSound( sample ), volume, attenuation, pitch, fFlags ); +} + +static void pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + CL_PlaybackEvent( flags, NULL, eventindex, delay, origin, angles, fparam1, fparam2, iparam1, iparam2, bparam1, bparam2 ); +} + +static pmtrace_t pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter ) +{ + return PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter ); +} + +static int pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter ) +{ + return PM_TestPlayerPosition( clgame.pmove, pos, ptrace, pmFilter ); +} + +static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = clgame.pmove->usehull; + clgame.pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, -1, pmFilter ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, -1, pmFilter ); + break; + } + + clgame.pmove->usehull = old_usehull; + + return &tr; +} + +static struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= clgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &clgame.pmove->physents[ground]; + return PM_TraceSurface( pe, vstart, vend ); +} + +/* +=============== +CL_InitClientMove + +=============== +*/ +void CL_InitClientMove( void ) +{ + int i; + + Pmove_Init (); + + clgame.pmove->server = false; // running at client + clgame.pmove->movevars = &clgame.movevars; + clgame.pmove->runfuncs = false; + + // enumerate client hulls + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + if( clgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] )) + MsgDev( D_NOTE, "CL: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\n", i, + host.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2], + host.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] ); + } + + memcpy( clgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins )); + memcpy( clgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs )); + + // common utilities + clgame.pmove->PM_Info_ValueForKey = Info_ValueForKey; + clgame.pmove->PM_Particle = CL_Particle; + clgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition; + clgame.pmove->Con_NPrintf = Con_NPrintf; + clgame.pmove->Con_DPrintf = Con_DPrintf; + clgame.pmove->Con_Printf = Con_Printf; + clgame.pmove->Sys_FloatTime = Sys_DoubleTime; + clgame.pmove->PM_StuckTouch = pfnStuckTouch; + clgame.pmove->PM_PointContents = pfnPointContents; + clgame.pmove->PM_TruePointContents = pfnTruePointContents; + clgame.pmove->PM_HullPointContents = pfnHullPointContents; + clgame.pmove->PM_PlayerTrace = pfnPlayerTrace; + clgame.pmove->PM_TraceLine = pfnTraceLine; + clgame.pmove->RandomLong = COM_RandomLong; + clgame.pmove->RandomFloat = COM_RandomFloat; + clgame.pmove->PM_GetModelType = pfnGetModelType; + clgame.pmove->PM_GetModelBounds = pfnGetModelBounds; + clgame.pmove->PM_HullForBsp = pfnHullForBsp; + clgame.pmove->PM_TraceModel = pfnTraceModel; + clgame.pmove->COM_FileSize = COM_FileSize; + clgame.pmove->COM_LoadFile = COM_LoadFile; + clgame.pmove->COM_FreeFile = COM_FreeFile; + clgame.pmove->memfgets = COM_MemFgets; + clgame.pmove->PM_PlaySound = pfnPlaySound; + clgame.pmove->PM_TraceTexture = pfnTraceTexture; + clgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull; + clgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx; + clgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx; + clgame.pmove->PM_TraceLineEx = pfnTraceLineEx; + clgame.pmove->PM_TraceSurface = pfnTraceSurface; + + // initalize pmove + clgame.dllFuncs.pfnPlayerMoveInit( clgame.pmove ); +} + +static void PM_CheckMovingGround( clientdata_t *cd, entity_state_t *state, float frametime ) +{ + if(!( cd->flags & FL_BASEVELOCITY )) + { + // apply momentum (add in half of the previous frame of velocity first) + VectorMA( cd->velocity, 1.0f + (frametime * 0.5f), state->basevelocity, cd->velocity ); + VectorClear( state->basevelocity ); + } + cd->flags &= ~FL_BASEVELOCITY; +} + +void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time ) +{ + entity_state_t *ps; + clientdata_t *cd; + + ps = &from->playerstate; + cd = &from->client; + + pmove->player_index = ps->number - 1; + pmove->multiplayer = (cl.maxclients > 1); + pmove->runfuncs = runfuncs; + pmove->time = time * 1000.0f; + pmove->frametime = ucmd->msec / 1000.0f; + VectorCopy( ps->origin, pmove->origin ); + VectorCopy( ps->angles, pmove->angles ); + VectorCopy( pmove->angles, pmove->oldangles ); + VectorCopy( cd->velocity, pmove->velocity ); + VectorCopy( ps->basevelocity, pmove->basevelocity ); + VectorCopy( cd->view_ofs, pmove->view_ofs ); + VectorClear( pmove->movedir ); + pmove->flDuckTime = cd->flDuckTime; + pmove->bInDuck = cd->bInDuck; + pmove->usehull = ps->usehull; + pmove->flTimeStepSound = cd->flTimeStepSound; + pmove->iStepLeft = ps->iStepLeft; + pmove->flFallVelocity = ps->flFallVelocity; + pmove->flSwimTime = cd->flSwimTime; + VectorCopy( cd->punchangle, pmove->punchangle ); + pmove->flSwimTime = cd->flSwimTime; + pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code + pmove->effects = ps->effects; + pmove->flags = cd->flags; + pmove->gravity = ps->gravity; + pmove->friction = ps->friction; + pmove->oldbuttons = ps->oldbuttons; + pmove->waterjumptime = cd->waterjumptime; + pmove->dead = (cl.local.health <= 0); + pmove->deadflag = cd->deadflag; + pmove->spectator = (cls.spectator != 0); + pmove->movetype = ps->movetype; + pmove->onground = ps->onground; + pmove->waterlevel = cd->waterlevel; + pmove->watertype = cd->watertype; + pmove->maxspeed = clgame.movevars.maxspeed; + pmove->clientmaxspeed = cd->maxspeed; + pmove->iuser1 = cd->iuser1; + pmove->iuser2 = cd->iuser2; + pmove->iuser3 = cd->iuser3; + pmove->iuser4 = cd->iuser4; + pmove->fuser1 = cd->fuser1; + pmove->fuser2 = cd->fuser2; + pmove->fuser3 = cd->fuser3; + pmove->fuser4 = cd->fuser4; + VectorCopy( cd->vuser1, pmove->vuser1 ); + VectorCopy( cd->vuser2, pmove->vuser2 ); + VectorCopy( cd->vuser3, pmove->vuser3 ); + VectorCopy( cd->vuser4, pmove->vuser4 ); + pmove->cmd = *ucmd; // copy current cmds + + Q_strncpy( pmove->physinfo, cls.physinfo, MAX_INFO_STRING ); +} + +void CL_FinishPMove( playermove_t *pmove, local_state_t *to ) +{ + entity_state_t *ps; + clientdata_t *cd; + + ps = &to->playerstate; + cd = &to->client; + + cd->flags = pmove->flags; + cd->bInDuck = pmove->bInDuck; + cd->flTimeStepSound = pmove->flTimeStepSound; + cd->flDuckTime = pmove->flDuckTime; + cd->flSwimTime = (int)pmove->flSwimTime; + cd->waterjumptime = (int)pmove->waterjumptime; + cd->watertype = pmove->watertype; + cd->waterlevel = pmove->waterlevel; + cd->maxspeed = pmove->clientmaxspeed; + cd->deadflag = pmove->deadflag; + VectorCopy( pmove->velocity, cd->velocity ); + VectorCopy( pmove->view_ofs, cd->view_ofs ); + VectorCopy( pmove->origin, ps->origin ); + VectorCopy( pmove->angles, ps->angles ); + VectorCopy( pmove->basevelocity, ps->basevelocity ); + VectorCopy( pmove->punchangle, cd->punchangle ); + ps->oldbuttons = pmove->oldbuttons; + ps->friction = pmove->friction; + ps->movetype = pmove->movetype; + ps->onground = pmove->onground; + ps->effects = pmove->effects; + ps->usehull = pmove->usehull; + ps->iStepLeft = pmove->iStepLeft; + ps->flFallVelocity = pmove->flFallVelocity; + cd->iuser1 = pmove->iuser1; + cd->iuser2 = pmove->iuser2; + cd->iuser3 = pmove->iuser3; + cd->iuser4 = pmove->iuser4; + cd->fuser1 = pmove->fuser1; + cd->fuser2 = pmove->fuser2; + cd->fuser3 = pmove->fuser3; + cd->fuser4 = pmove->fuser4; + VectorCopy( pmove->vuser1, cd->vuser1 ); + VectorCopy( pmove->vuser2, cd->vuser2 ); + VectorCopy( pmove->vuser3, cd->vuser3 ); + VectorCopy( pmove->vuser4, cd->vuser4 ); +} + +/* +================= +CL_RunUsercmd + +Runs prediction code for user cmd +================= +*/ +void CL_RunUsercmd( local_state_t *from, local_state_t *to, usercmd_t *u, qboolean runfuncs, double *time, unsigned int random_seed ) +{ + usercmd_t cmd; + local_state_t temp; + usercmd_t split; + + memset( &temp, 0, sizeof( temp )); + + if( u->msec > 50 ) + { + split = *u; + split.msec /= 2; + CL_RunUsercmd( from, &temp, &split, runfuncs, time, random_seed ); + split.impulse = split.weaponselect = 0; + CL_RunUsercmd( &temp, to, &split, runfuncs, time, random_seed ); + return; + } + + cmd = *u; // deal with local copy + *to = *from; + + if( CL_IsPredicted( )) + { + // setup playermove state + CL_SetupPMove( clgame.pmove, from, &cmd, runfuncs, *time ); + + // motor! + clgame.dllFuncs.pfnPlayerMove( clgame.pmove, false ); + + // copy results back to client + CL_FinishPMove( clgame.pmove, to ); + + if( clgame.pmove->onground > 0 && clgame.pmove->onground < clgame.pmove->numphysent ) + cl.local.lastground = clgame.pmove->physents[clgame.pmove->onground].info; + else cl.local.lastground = clgame.pmove->onground; // world(0) or in air(-1) + } + + clgame.dllFuncs.pfnPostRunCmd( from, to, &cmd, runfuncs, *time, random_seed ); + + *time += (double)cmd.msec / 1000.0; +} + + +/* +================= +CL_MoveSpectatorCamera + +spectator movement code +================= +*/ +void CL_MoveSpectatorCamera( void ) +{ + double time = cl.time; + + if( !cls.spectator ) + return; + + CL_SetUpPlayerPrediction( false, true ); + CL_SetSolidPlayers( cl.playernum ); + CL_RunUsercmd( &cls.spectator_state, &cls.spectator_state, cl.cmd, true, &time, (uint)( time * 100.0 )); + + VectorCopy( cls.spectator_state.client.velocity, cl.simvel ); + VectorCopy( cls.spectator_state.client.origin, cl.simorg ); + VectorCopy( cls.spectator_state.client.punchangle, cl.punchangle ); + VectorCopy( cls.spectator_state.client.view_ofs, cl.viewheight ); +} + +/* +================= +CL_PredictMovement + +Sets cl.predicted.origin and cl.predicted.angles +================= +*/ +void CL_PredictMovement( qboolean repredicting ) +{ + runcmd_t *to_cmd, *from_cmd; + local_state_t *from = NULL, *to = NULL; + int current_command; + int current_command_mod; + frame_t *frame = NULL; + int i, stoppoint; + qboolean runfuncs; + double f = 1.0; + double time; + + if( cls.state != ca_active || cls.spectator ) + return; + + if( cls.demoplayback && cl.cmd != NULL && !repredicting ) + CL_DemoInterpolateAngles(); + + CL_SetUpPlayerPrediction( false, false ); + + if( cls.state != ca_active || !cl.validsequence ) + return; + + if(( cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged ) >= CL_UPDATE_MASK ) + return; + + // this is the last frame received from the server + frame = &cl.frames[cl.parsecountmod]; + + if( !CL_IsPredicted( )) + { + VectorCopy( frame->clientdata.velocity, cl.simvel ); + VectorCopy( frame->clientdata.origin, cl.simorg ); + VectorCopy( frame->clientdata.punchangle, cl.punchangle ); + VectorCopy( frame->clientdata.view_ofs, cl.viewheight ); + cl.local.usehull = frame->playerstate[cl.playernum].usehull; + cl.local.waterlevel = frame->clientdata.waterlevel; + + if( FBitSet( frame->clientdata.flags, FL_ONGROUND )) + cl.local.onground = frame->playerstate[cl.playernum].onground; + else cl.local.onground = -1; + } + + from = &cl.predicted_frames[cl.parsecountmod]; + from_cmd = &cl.commands[cls.netchan.incoming_acknowledged & CL_UPDATE_MASK]; + memcpy( from->weapondata, frame->weapondata, sizeof( from->weapondata )); + from->playerstate = frame->playerstate[cl.playernum]; + from->client = frame->clientdata; + if( !frame->valid ) return; + + time = frame->time; + stoppoint = ( repredicting ) ? 0 : 1; + cl.local.repredicting = repredicting; + cl.local.onground = -1; + + // predict forward until cl.time <= to->senttime + CL_PushPMStates(); + CL_SetSolidPlayers( cl.playernum ); + + for( i = 1; i < CL_UPDATE_MASK && cls.netchan.incoming_acknowledged + i < cls.netchan.outgoing_sequence + stoppoint; i++ ) + { + current_command = cls.netchan.incoming_acknowledged + i; + current_command_mod = current_command & CL_UPDATE_MASK; + + to = &cl.predicted_frames[(cl.parsecountmod + i) & CL_UPDATE_MASK]; + to_cmd = &cl.commands[current_command_mod]; + runfuncs = ( !repredicting && !to_cmd->processedfuncs ); + + CL_RunUsercmd( from, to, &to_cmd->cmd, runfuncs, &time, current_command ); + VectorCopy( to->playerstate.origin, cl.local.predicted_origins[current_command_mod] ); + to_cmd->processedfuncs = true; + + if( to_cmd->senttime >= host.realtime ) + break; + + // now interpolate some fraction of the final frame + if( to_cmd->senttime != from_cmd->senttime ) + f = (host.realtime - from_cmd->senttime) / (to_cmd->senttime - from_cmd->senttime) * 0.1; + + from = to; + from_cmd = to_cmd; + } + + CL_PopPMStates(); + + if(( i == CL_UPDATE_MASK ) || ( !to && !repredicting )) + { + cl.local.repredicting = false; + return; // net hasn't deliver packets in a long time... + } + + if( !to ) + { + to = from; + to_cmd = from_cmd; + } + + if( !CL_IsPredicted( )) + { + // keep onground actual + if( FBitSet( frame->clientdata.flags, FL_ONGROUND )) + cl.local.onground = frame->playerstate[cl.playernum].onground; + else cl.local.onground = -1; + + if( !repredicting || !cl_lw->value ) + cl.local.viewmodel = to->client.viewmodel; + cl.local.repredicting = false; + cl.local.moving = false; + return; + } + + f = bound( 0.0f, f, 1.0f ); + f = 0.0; // FIXME: make work, do revision + + if( CL_PlayerTeleported( from, to )) + { + VectorCopy( to->client.velocity, cl.simvel ); + VectorCopy( to->playerstate.origin, cl.simorg ); + VectorCopy( to->client.punchangle, cl.punchangle ); + VectorCopy( to->client.view_ofs, cl.viewheight ); + } + else + { + VectorLerp( from->playerstate.origin, f, to->playerstate.origin, cl.simorg ); + VectorLerp( from->client.velocity, f, to->client.velocity, cl.simvel ); + VectorLerp( from->client.punchangle, f, to->client.punchangle, cl.punchangle ); + + if( from->playerstate.usehull == to->playerstate.usehull ) + VectorLerp( from->client.view_ofs, f, to->client.view_ofs, cl.viewheight ); + else VectorCopy( to->client.view_ofs, cl.viewheight ); + } + + cl.local.waterlevel = to->client.waterlevel; + cl.local.usehull = to->playerstate.usehull; + if( !repredicting || !cl_lw->value ) + cl.local.viewmodel = to->client.viewmodel; + + if( FBitSet( to->client.flags, FL_ONGROUND )) + { + cl_entity_t *ent = CL_GetEntityByIndex( cl.local.lastground ); + + cl.local.onground = cl.local.lastground; + cl.local.moving = false; + + if( ent ) + { + vec3_t delta; + + delta[0] = ent->curstate.origin[0] - ent->prevstate.origin[0]; + delta[1] = ent->curstate.origin[1] - ent->prevstate.origin[1]; + delta[2] = 0.0f; + + if( VectorLength( delta ) > 0.0f ) + { + cls.correction_time = 0; + cl.local.moving = true; + } + } + } + else + { + cl.local.onground = -1; + cl.local.moving = 0; + } + + if( cls.correction_time > 0 && !cl_nosmooth->value && cl_smoothtime->value ) + { + vec3_t delta; + float frac; + + // only decay timer once per frame + if( !repredicting ) + cls.correction_time -= host.frametime; + + // Make sure smoothtime is postive + if( cl_smoothtime->value <= 0.0 ) + Cvar_DirectSet( cl_smoothtime, "0.1" ); + + // Clamp from 0 to cl_smoothtime.value + cls.correction_time = bound( 0.0, cls.correction_time, cl_smoothtime->value ); + + // Compute backward interpolation fraction along full correction + frac = 1.0 - cls.correction_time / cl_smoothtime->value; + + // Determine how much error we still have to make up for + VectorSubtract( cl.simorg, cl.local.lastorigin, delta ); + + // Scale the error by the backlerp fraction + VectorScale( delta, frac, delta ); + + // Go some fraction of the way + // FIXME, Probably can't do this any more + VectorAdd( cl.local.lastorigin, delta, cl.simorg ); + } + + VectorCopy( cl.simorg, cl.local.lastorigin ); + cl.local.repredicting = false; +} \ No newline at end of file diff --git a/engine/client/cl_remap.c b/engine/client/cl_remap.c new file mode 100644 index 00000000..5c79d6b7 --- /dev/null +++ b/engine/client/cl_remap.c @@ -0,0 +1,433 @@ +/* +gl_remap.c - remap model textures +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "studio.h" + +/* +==================== +CL_GetRemapInfoForEntity + +Returns remapinfo slot for specified entity +==================== +*/ +remap_info_t *CL_GetRemapInfoForEntity( cl_entity_t *e ) +{ + if( !e ) return NULL; + + if( e == &clgame.viewent ) + return clgame.remap_info[clgame.maxEntities]; + + return clgame.remap_info[e->curstate.number]; +} + +/* +==================== +CL_CmpStudioTextures + +return true if equal +==================== +*/ +qboolean CL_CmpStudioTextures( int numtexs, mstudiotexture_t *p1, mstudiotexture_t *p2 ) +{ + int i; + + if( !p1 || !p2 ) return false; + + for( i = 0; i < numtexs; i++, p1++, p2++ ) + { + if( p1->flags & STUDIO_NF_COLORMAP ) + continue; // colormaps always has different indexes + + if( p1->index != p2->index ) + return false; + } + return true; +} + +/* +==================== +CL_CreateRawTextureFromPixels + +Convert texture_t struct into mstudiotexture_t prototype +==================== +*/ +byte *CL_CreateRawTextureFromPixels( texture_t *tx, size_t *size, int topcolor, int bottomcolor ) +{ + static mstudiotexture_t pin; + byte *pal; + + Assert( size != NULL ); + + *size = sizeof( pin ) + (tx->width * tx->height) + 768; + + // fill header + if( !pin.name[0] ) Q_strncpy( pin.name, "#raw_remap_image.mdl", sizeof( pin.name )); + pin.flags = STUDIO_NF_COLORMAP; // just in case :-) + pin.index = (int)(tx + 1); // pointer to pixels + pin.width = tx->width; + pin.height = tx->height; + + // update palette + pal = (byte *)(tx + 1) + (tx->width * tx->height); + Image_PaletteHueReplace( pal, topcolor, tx->anim_min, tx->anim_max, 3 ); + Image_PaletteHueReplace( pal, bottomcolor, tx->anim_max + 1, tx->anim_total, 3 ); + + return (byte *)&pin; +} + +/* +==================== +CL_DuplicateTexture + +Dupliacte texture with remap pixels +==================== +*/ +void CL_DuplicateTexture( mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) +{ + gltexture_t *glt; + texture_t *tx = NULL; + char texname[128]; + int i, size, index; + byte paletteBackup[768]; + byte *raw, *pal; + + // save off the real texture index + index = ptexture->index; + glt = R_GetTexture( index ); + Q_snprintf( texname, sizeof( texname ), "#%i_%s", RI.currententity->curstate.number, glt->name + 1 ); + + // search for pixels + for( i = 0; i < RI.currentmodel->numtextures; i++ ) + { + tx = RI.currentmodel->textures[i]; + if( tx->gl_texturenum == index ) + break; // found + } + + Assert( tx != NULL ); + + // backup original palette + pal = (byte *)(tx + 1) + (tx->width * tx->height); + memcpy( paletteBackup, pal, 768 ); + + raw = CL_CreateRawTextureFromPixels( tx, &size, topcolor, bottomcolor ); + ptexture->index = GL_LoadTexture( texname, raw, size, TF_FORCE_COLOR, NULL ); // do copy + + // restore original palette + memcpy( pal, paletteBackup, 768 ); +} + +/* +==================== +CL_UpdateStudioTexture + +Update texture top and bottom colors +==================== +*/ +void CL_UpdateStudioTexture( mstudiotexture_t *ptexture, int topcolor, int bottomcolor ) +{ + gltexture_t *glt; + rgbdata_t *pic; + texture_t *tx = NULL; + char texname[128], name[128], mdlname[128]; + int i, size, index; + byte paletteBackup[768]; + byte *raw, *pal; + + // save off the real texture index + glt = R_GetTexture( ptexture->index ); + + // build name of original texture + Q_strncpy( mdlname, RI.currentmodel->name, sizeof( mdlname )); + COM_FileBase( ptexture->name, name ); + COM_StripExtension( mdlname ); + + Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); + index = GL_FindTexture( texname ); + if( !index ) return; // couldn't find texture + + // search for pixels + for( i = 0; i < RI.currentmodel->numtextures; i++ ) + { + tx = RI.currentmodel->textures[i]; + if( tx->gl_texturenum == index ) + break; // found + } + + Assert( tx != NULL ); + + // backup original palette + pal = (byte *)(tx + 1) + (tx->width * tx->height); + memcpy( paletteBackup, pal, 768 ); + + raw = CL_CreateRawTextureFromPixels( tx, &size, topcolor, bottomcolor ); + pic = FS_LoadImage( glt->name, raw, size ); + if( !pic ) + { + MsgDev( D_ERROR, "Couldn't update texture %s\n", glt->name ); + return; + } + + index = GL_LoadTextureInternal( glt->name, pic, 0, true ); + FS_FreeImage( pic ); + + // restore original palette + memcpy( pal, paletteBackup, 768 ); + + Assert( index == ptexture->index ); +} + +/* +==================== +CL_UpdateAliasTexture + +Update texture top and bottom colors +==================== +*/ +void CL_UpdateAliasTexture( unsigned short *texture, int skinnum, int topcolor, int bottomcolor ) +{ + char texname[MAX_QPATH]; + rgbdata_t skin, *pic; + texture_t *tx; + + if( !texture || !RI.currentmodel->textures ) + return; // no remapinfo in model + + tx = RI.currentmodel->textures[skinnum]; + if( !tx ) return; // missing texture ? + + if( *texture == 0 ) + { + Q_snprintf( texname, sizeof( texname ), "%s:remap%i", RI.currentmodel->name, skinnum ); + skin.width = tx->width; + skin.height = tx->height; + skin.depth = skin.numMips = 1; + skin.size = tx->width * tx->height; + skin.type = PF_INDEXED_24; + skin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL; + skin.encode = DXT_ENCODE_DEFAULT; + skin.buffer = (byte *)(tx + 1); + skin.palette = skin.buffer + skin.size; + pic = FS_CopyImage( &skin ); // because GL_LoadTextureInternal will freed a rgbdata_t at end + *texture = GL_LoadTextureInternal( texname, pic, TF_KEEP_SOURCE, false ); + } + + // and now we can remap with internal routines + GL_ProcessTexture( *texture, -1.0f, topcolor, bottomcolor ); +} + +/* +==================== +CL_AllocRemapInfo + +Allocate new remap info per entity +and make copy of remap textures +==================== +*/ +void CL_AllocRemapInfo( int topcolor, int bottomcolor ) +{ + remap_info_t *info; + studiohdr_t *phdr; + aliashdr_t *ahdr; + mstudiotexture_t *src, *dst; + int i, size; + + if( !RI.currententity ) return; + i = ( RI.currententity == &clgame.viewent ) ? clgame.maxEntities : RI.currententity->curstate.number; + + if( !RI.currentmodel || ( RI.currentmodel->type != mod_alias && RI.currentmodel->type != mod_studio )) + { + // entity has changed model by another type, release remap info + if( clgame.remap_info[i] ) + { + CL_FreeRemapInfo( clgame.remap_info[i] ); + clgame.remap_info[i] = NULL; + } + return; // missed or hide model, ignore it + } + + // model doesn't contains remap textures + if( RI.currentmodel->numtextures <= 0 ) + { + // entity has changed model with no remap textures + if( clgame.remap_info[i] ) + { + CL_FreeRemapInfo( clgame.remap_info[i] ); + clgame.remap_info[i] = NULL; + } + return; + } + + if( RI.currentmodel->type == mod_studio ) + { + phdr = (studiohdr_t *)Mod_StudioExtradata( RI.currentmodel ); + if( !phdr ) return; // bad model? + + src = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + dst = (clgame.remap_info[i] ? clgame.remap_info[i]->ptexture : NULL); + + // NOTE: we must copy all the structures 'mstudiotexture_t' for easy access when model is rendering + if( !CL_CmpStudioTextures( phdr->numtextures, src, dst ) || clgame.remap_info[i]->model != RI.currentmodel ) + { + // this code catches studiomodel change with another studiomodel with remap textures + // e.g. playermodel 'barney' with playermodel 'gordon' + if( clgame.remap_info[i] ) CL_FreeRemapInfo( clgame.remap_info[i] ); // free old info + size = sizeof( remap_info_t ) + ( sizeof( mstudiotexture_t ) * phdr->numtextures ); + info = clgame.remap_info[i] = Mem_Alloc( clgame.mempool, size ); + info->ptexture = (mstudiotexture_t *)(info + 1); // textures are immediately comes after remap_info + } + else + { + // studiomodel is valid, nothing to change + return; + } + + info->numtextures = phdr->numtextures; + info->topcolor = topcolor; + info->bottomcolor = bottomcolor; + + src = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + dst = info->ptexture; + + // copy unchanged first + memcpy( dst, src, sizeof( mstudiotexture_t ) * phdr->numtextures ); + + // make local copies for remap textures + for( i = 0; i < info->numtextures; i++ ) + { + if( dst[i].flags & STUDIO_NF_COLORMAP ) + CL_DuplicateTexture( &dst[i], topcolor, bottomcolor ); + } + } + else if( RI.currentmodel->type == mod_alias ) + { + ahdr = (aliashdr_t *)Mod_AliasExtradata( RI.currentmodel ); + if( !ahdr ) return; // bad model? + + // NOTE: we must copy all the structures 'mstudiotexture_t' for easy access when model is rendering + if( !clgame.remap_info[i] || clgame.remap_info[i]->model != RI.currentmodel ) + { + // this code catches studiomodel change with another studiomodel with remap textures + // e.g. playermodel 'barney' with playermodel 'gordon' + if( clgame.remap_info[i] ) CL_FreeRemapInfo( clgame.remap_info[i] ); // free old info + info = clgame.remap_info[i] = Mem_Alloc( clgame.mempool, sizeof( remap_info_t )); + } + else + { + // aliasmodel is valid, nothing to change + return; + } + + info->numtextures = RI.currentmodel->numtextures; + + // alias remapping is easy + CL_UpdateRemapInfo( topcolor, bottomcolor ); + } + else + { + // only alias & studio models are supposed for remapping + return; + } + + info->model = RI.currentmodel; +} + +/* +==================== +CL_UpdateRemapInfo + +Update all remaps per entity +==================== +*/ +void CL_UpdateRemapInfo( int topcolor, int bottomcolor ) +{ + remap_info_t *info; + int i; + + i = ( RI.currententity == &clgame.viewent ) ? clgame.maxEntities : RI.currententity->curstate.number; + info = clgame.remap_info[i]; + if( !info ) return; // no remap info + + if( info->topcolor == topcolor && info->bottomcolor == bottomcolor ) + return; // values is valid + + for( i = 0; i < info->numtextures; i++ ) + { + if( info->ptexture != NULL ) + { + if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) + CL_UpdateStudioTexture( &info->ptexture[i], topcolor, bottomcolor ); + } + else CL_UpdateAliasTexture( &info->textures[i], i, topcolor, bottomcolor ); + } + + info->topcolor = topcolor; + info->bottomcolor = bottomcolor; +} + +/* +==================== +CL_FreeRemapInfo + +Release remap info per entity +==================== +*/ +void CL_FreeRemapInfo( remap_info_t *info ) +{ + int i; + + Assert( info != NULL ); + + // release all colormap texture copies + for( i = 0; i < info->numtextures; i++ ) + { + if( info->ptexture != NULL ) + { + if( FBitSet( info->ptexture[i].flags, STUDIO_NF_COLORMAP )) + GL_FreeTexture( info->ptexture[i].index ); + } + + if( info->textures[i] != 0 ) + GL_FreeTexture( info->textures[i] ); + } + + Mem_Free( info ); // release struct +} + +/* +==================== +CL_ClearAllRemaps + +Release all remap infos +==================== +*/ +void CL_ClearAllRemaps( void ) +{ + int i; + + if( clgame.remap_info ) + { + for( i = 0; i < clgame.maxRemapInfos; i++ ) + { + if( clgame.remap_info[i] ) + CL_FreeRemapInfo( clgame.remap_info[i] ); + } + Mem_Free( clgame.remap_info ); + } + clgame.remap_info = NULL; +} \ No newline at end of file diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c new file mode 100644 index 00000000..542f50a3 --- /dev/null +++ b/engine/client/cl_scrn.c @@ -0,0 +1,757 @@ +/* +cl_scrn.c - refresh screen +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "vgui_draw.h" +#include "qfont.h" +#include "input.h" + +convar_t *scr_centertime; +convar_t *scr_loading; +convar_t *scr_download; +convar_t *scr_viewsize; +convar_t *cl_testlights; +convar_t *cl_allow_levelshots; +convar_t *cl_levelshot_name; +convar_t *cl_envshot_size; +convar_t *v_dark; + +typedef struct +{ + int x1, y1, x2, y2; +} dirty_t; + +static dirty_t scr_dirty, scr_old_dirty[2]; +static qboolean scr_init = false; + +/* +============== +SCR_DrawFPS +============== +*/ +void SCR_DrawFPS( int height ) +{ + float calc; + rgba_t color; + double newtime; + static double nexttime = 0, lasttime = 0; + static double framerate = 0; + static int framecount = 0; + static int minfps = 9999; + static int maxfps = 0; + char fpsstring[64]; + int offset; + + if( cls.state != ca_active || !cl_showfps->value || cl.background ) + return; + + switch( cls.scrshot_action ) + { + case scrshot_normal: + case scrshot_snapshot: + case scrshot_inactive: + break; + default: return; + } + + newtime = Sys_DoubleTime(); + if( newtime >= nexttime ) + { + framerate = framecount / (newtime - lasttime); + lasttime = newtime; + nexttime = Q_max( nexttime + 1.0, lasttime - 1.0 ); + framecount = 0; + } + + calc = framerate; + framecount++; + + if( calc < 1.0f ) + { + Q_snprintf( fpsstring, sizeof( fpsstring ), "%4i spf", (int)(1.0f / calc + 0.5f)); + MakeRGBA( color, 255, 0, 0, 255 ); + } + else + { + int curfps = (int)(calc + 0.5f); + + if( curfps < minfps ) minfps = curfps; + if( curfps > maxfps ) maxfps = curfps; + + if( cl_showfps->value == 2 ) + Q_snprintf( fpsstring, sizeof( fpsstring ), "fps: ^1%4i min, ^3%4i cur, ^2%4i max", minfps, curfps, maxfps ); + else Q_snprintf( fpsstring, sizeof( fpsstring ), "%4i fps", curfps ); + MakeRGBA( color, 255, 255, 255, 255 ); + } + + Con_DrawStringLen( fpsstring, &offset, NULL ); + Con_DrawString( glState.width - offset - 4, height, fpsstring, color ); +} + +/* +============== +SCR_NetSpeeds + +same as r_speeds but for network channel +============== +*/ +void SCR_NetSpeeds( void ) +{ + static char msg[MAX_SYSPATH]; + int x, y, height; + char *p, *start, *end; + float time = cl.mtime[0]; + static int min_svfps = 100; + static int max_svfps = 0; + int cur_svfps = 0; + static int min_clfps = 100; + static int max_clfps = 0; + int cur_clfps = 0; + rgba_t color; + + if( !host.allow_console ) + return; + + if( !net_speeds->value || cls.demoplayback || cls.state != ca_active ) + return; + + // prevent to get too big values at max + if( cl_serverframetime() > 0.0001f ) + { + cur_svfps = Q_rint( 1.0f / cl_serverframetime( )); + if( cur_svfps < min_svfps ) min_svfps = cur_svfps; + if( cur_svfps > max_svfps ) max_svfps = cur_svfps; + } + + // prevent to get too big values at max + if( cl_clientframetime() > 0.0001f ) + { + cur_clfps = Q_rint( 1.0f / cl_clientframetime( )); + if( cur_clfps < min_clfps ) min_clfps = cur_clfps; + if( cur_clfps > max_clfps ) max_clfps = cur_clfps; + } + + Q_snprintf( msg, sizeof( msg ), "sv fps: ^1%4i min, ^3%4i cur, ^2%4i max\ncl fps: ^1%4i min, ^3%4i cur, ^2%4i max\nGame Time: %02d:%02d\nTotal received from server: %s\nTotal sent to server: %s\n", + min_svfps, cur_svfps, max_svfps, min_clfps, cur_clfps, max_clfps, (int)(time / 60.0f ), (int)fmod( time, 60.0f ), Q_memprint( cls.netchan.total_received ), Q_memprint( cls.netchan.total_sended )); + + x = glState.width - 320; + y = 384; + + Con_DrawStringLen( NULL, NULL, &height ); + MakeRGBA( color, 255, 255, 255, 255 ); + + p = start = msg; + + do + { + end = Q_strchr( p, '\n' ); + if( end ) msg[end-start] = '\0'; + + Con_DrawString( x, y, p, color ); + y += height; + + if( end ) p = end + 1; + else break; + } while( 1 ); +} + +/* +================ +SCR_RSpeeds +================ +*/ +void SCR_RSpeeds( void ) +{ + char msg[MAX_SYSPATH]; + + if( !host.allow_console ) + return; + + if( R_SpeedsMessage( msg, sizeof( msg ))) + { + int x, y, height; + char *p, *start, *end; + rgba_t color; + + x = glState.width - 340; + y = 64; + + Con_DrawStringLen( NULL, NULL, &height ); + MakeRGBA( color, 255, 255, 255, 255 ); + + p = start = msg; + do + { + end = Q_strchr( p, '\n' ); + if( end ) msg[end-start] = '\0'; + + Con_DrawString( x, y, p, color ); + y += height; + + if( end ) p = end + 1; + else break; + } while( 1 ); + } +} + +/* +================ +SCR_MakeLevelShot + +creates levelshot at next frame +================ +*/ +void SCR_MakeLevelShot( void ) +{ + if( cls.scrshot_request != scrshot_plaque ) + return; + + // make levelshot at nextframe() + Cbuf_AddText( "levelshot\n" ); +} + +/* +================ +SCR_MakeScreenShot + +create a requested screenshot type +================ +*/ +void SCR_MakeScreenShot( void ) +{ + qboolean iRet = false; + int viewsize; + + if( cls.envshot_viewsize > 0 ) + viewsize = cls.envshot_viewsize; + else viewsize = cl_envshot_size->value; + + switch( cls.scrshot_action ) + { + case scrshot_normal: + iRet = VID_ScreenShot( cls.shotname, VID_SCREENSHOT ); + break; + case scrshot_snapshot: + iRet = VID_ScreenShot( cls.shotname, VID_SNAPSHOT ); + break; + case scrshot_plaque: + iRet = VID_ScreenShot( cls.shotname, VID_LEVELSHOT ); + break; + case scrshot_savegame: + case scrshot_demoshot: + iRet = VID_ScreenShot( cls.shotname, VID_MINISHOT ); + break; + case scrshot_envshot: + iRet = VID_CubemapShot( cls.shotname, viewsize, cls.envshot_vieworg, false ); + break; + case scrshot_skyshot: + iRet = VID_CubemapShot( cls.shotname, viewsize, cls.envshot_vieworg, true ); + break; + case scrshot_mapshot: + iRet = VID_ScreenShot( cls.shotname, VID_MAPSHOT ); + break; + case scrshot_inactive: + return; + } + + // report + if( iRet ) + { + // snapshots don't writes message about image + if( cls.scrshot_action != scrshot_snapshot ) + MsgDev( D_REPORT, "Write %s\n", cls.shotname ); + } + else MsgDev( D_ERROR, "Unable to write %s\n", cls.shotname ); + + cls.envshot_vieworg = NULL; + cls.scrshot_action = scrshot_inactive; + cls.envshot_disable_vis = false; + cls.envshot_viewsize = 0; + cls.shotname[0] = '\0'; +} + +/* +================ +SCR_DrawPlaque +================ +*/ +void SCR_DrawPlaque( void ) +{ + if(( cl_allow_levelshots->value && !cls.changelevel ) || cl.background ) + { + int levelshot = GL_LoadTexture( cl_levelshot_name->string, NULL, 0, TF_IMAGE, NULL ); + GL_SetRenderMode( kRenderNormal ); + R_DrawStretchPic( 0, 0, glState.width, glState.height, 0, 0, 1, 1, levelshot ); + if( !cl.background ) CL_DrawHUD( CL_LOADING ); + } +} + +/* +================ +SCR_BeginLoadingPlaque +================ +*/ +void SCR_BeginLoadingPlaque( qboolean is_background ) +{ + S_StopAllSounds( true ); + cl.audio_prepped = false; // don't play ambients + + if( CL_IsInMenu( ) && !cls.changedemo && !is_background ) + { + UI_SetActiveMenu( false ); + if( cls.state == ca_disconnected ) + SCR_UpdateScreen(); + } + + if( cls.state == ca_disconnected || cls.disable_screen ) + return; // already set + + if( cls.key_dest == key_console ) + return; + + if( is_background ) IN_MouseSavePos( ); + cls.draw_changelevel = !is_background; + SCR_UpdateScreen(); + cls.disable_screen = host.realtime; + cls.disable_servercount = cl.servercount; + cl.background = is_background; // set right state before svc_serverdata is came +// SNDDMA_LockSound(); +} + +/* +================ +SCR_EndLoadingPlaque +================ +*/ +void SCR_EndLoadingPlaque( void ) +{ + cls.disable_screen = 0.0f; + Con_ClearNotify(); +// SNDDMA_UnlockSound(); +} + +/* +================= +SCR_AddDirtyPoint +================= +*/ +void SCR_AddDirtyPoint( int x, int y ) +{ + if( x < scr_dirty.x1 ) scr_dirty.x1 = x; + if( x > scr_dirty.x2 ) scr_dirty.x2 = x; + if( y < scr_dirty.y1 ) scr_dirty.y1 = y; + if( y > scr_dirty.y2 ) scr_dirty.y2 = y; +} + +/* +================ +SCR_DirtyScreen +================ +*/ +void SCR_DirtyScreen( void ) +{ + SCR_AddDirtyPoint( 0, 0 ); + SCR_AddDirtyPoint( glState.width - 1, glState.height - 1 ); +} + +/* +================ +SCR_TileClear +================ +*/ +void SCR_TileClear( void ) +{ + int i, top, bottom, left, right; + dirty_t clear; + + if( scr_viewsize->value >= 120 ) + return; // full screen rendering + + // erase rect will be the union of the past three frames + // so tripple buffering works properly + clear = scr_dirty; + + for( i = 0; i < 2; i++ ) + { + if( scr_old_dirty[i].x1 < clear.x1 ) + clear.x1 = scr_old_dirty[i].x1; + if( scr_old_dirty[i].x2 > clear.x2 ) + clear.x2 = scr_old_dirty[i].x2; + if( scr_old_dirty[i].y1 < clear.y1 ) + clear.y1 = scr_old_dirty[i].y1; + if( scr_old_dirty[i].y2 > clear.y2 ) + clear.y2 = scr_old_dirty[i].y2; + } + + scr_old_dirty[1] = scr_old_dirty[0]; + scr_old_dirty[0] = scr_dirty; + + scr_dirty.x1 = 9999; + scr_dirty.x2 = -9999; + scr_dirty.y1 = 9999; + scr_dirty.y2 = -9999; + + if( clear.y2 <= clear.y1 ) + return; // nothing disturbed + + top = RI.viewport[1]; + bottom = top + RI.viewport[3] - 1; + left = RI.viewport[0]; + right = left + RI.viewport[2] - 1; + + if( clear.y1 < top ) + { + // clear above view screen + i = clear.y2 < top-1 ? clear.y2 : top - 1; + R_DrawTileClear( clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1 + 1 ); + clear.y1 = top; + } + + if( clear.y2 > bottom ) + { + // clear below view screen + i = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1; + R_DrawTileClear( clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i + 1 ); + clear.y2 = bottom; + } + + if( clear.x1 < left ) + { + // clear left of view screen + i = clear.x2 < left - 1 ? clear.x2 : left - 1; + R_DrawTileClear( clear.x1, clear.y1, i - clear.x1 + 1, clear.y2 - clear.y1 + 1 ); + clear.x1 = left; + } + + if( clear.x2 > right ) + { + // clear left of view screen + i = clear.x1 > right + 1 ? clear.x1 : right + 1; + R_DrawTileClear( i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1 + 1 ); + clear.x2 = right; + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen( void ) +{ + if( !V_PreRender( )) return; + + switch( cls.state ) + { + case ca_disconnected: + Con_RunConsole (); + break; + case ca_connecting: + case ca_connected: + case ca_validate: + SCR_DrawPlaque(); + break; + case ca_active: + Con_RunConsole (); + V_RenderView(); + break; + case ca_cinematic: + SCR_DrawCinematic(); + break; + default: + Host_Error( "SCR_UpdateScreen: bad cls.state\n" ); + break; + } + + V_PostRender(); +} + +qboolean SCR_LoadFixedWidthFont( const char *fontname ) +{ + int i, fontWidth; + + if( cls.creditsFont.valid ) + return true; // already loaded + + if( !FS_FileExists( fontname, false )) + return false; + + cls.creditsFont.hFontTexture = GL_LoadTexture( fontname, NULL, 0, TF_IMAGE|TF_KEEP_SOURCE, NULL ); + R_GetTextureParms( &fontWidth, NULL, cls.creditsFont.hFontTexture ); + cls.creditsFont.charHeight = clgame.scrInfo.iCharHeight = fontWidth / 16; + cls.creditsFont.type = FONT_FIXED; + cls.creditsFont.valid = true; + + // build fixed rectangles + for( i = 0; i < 256; i++ ) + { + cls.creditsFont.fontRc[i].left = (i * (fontWidth / 16)) % fontWidth; + cls.creditsFont.fontRc[i].right = cls.creditsFont.fontRc[i].left + fontWidth / 16; + cls.creditsFont.fontRc[i].top = (i / 16) * (fontWidth / 16); + cls.creditsFont.fontRc[i].bottom = cls.creditsFont.fontRc[i].top + fontWidth / 16; + cls.creditsFont.charWidths[i] = clgame.scrInfo.charWidths[i] = fontWidth / 16; + } + + return true; +} + +qboolean SCR_LoadVariableWidthFont( const char *fontname ) +{ + int i, fontWidth; + byte *buffer; + size_t length; + qfont_t *src; + + if( cls.creditsFont.valid ) + return true; // already loaded + + if( !FS_FileExists( fontname, false )) + return false; + + cls.creditsFont.hFontTexture = GL_LoadTexture( fontname, NULL, 0, TF_IMAGE, NULL ); + R_GetTextureParms( &fontWidth, NULL, cls.creditsFont.hFontTexture ); + + // half-life font with variable chars witdh + buffer = FS_LoadFile( fontname, &length, false ); + + // setup creditsfont + if( buffer && length >= sizeof( qfont_t )) + { + src = (qfont_t *)buffer; + cls.creditsFont.charHeight = clgame.scrInfo.iCharHeight = src->rowheight; + cls.creditsFont.type = FONT_VARIABLE; + + // build rectangles + for( i = 0; i < 256; i++ ) + { + cls.creditsFont.fontRc[i].left = (word)src->fontinfo[i].startoffset % fontWidth; + cls.creditsFont.fontRc[i].right = cls.creditsFont.fontRc[i].left + src->fontinfo[i].charwidth; + cls.creditsFont.fontRc[i].top = (word)src->fontinfo[i].startoffset / fontWidth; + cls.creditsFont.fontRc[i].bottom = cls.creditsFont.fontRc[i].top + src->rowheight; + cls.creditsFont.charWidths[i] = clgame.scrInfo.charWidths[i] = src->fontinfo[i].charwidth; + } + cls.creditsFont.valid = true; + } + if( buffer ) Mem_Free( buffer ); + + return true; +} + +/* +================ +SCR_LoadCreditsFont + +INTERNAL RESOURCE +================ +*/ +void SCR_LoadCreditsFont( void ) +{ + if( !SCR_LoadVariableWidthFont( "gfx.wad/creditsfont.fnt" )) + { + if( !SCR_LoadFixedWidthFont( "gfx/conchars" )) + MsgDev( D_ERROR, "failed to load HUD font\n" ); + } +} + +/* +================ +SCR_InstallParticlePalette + +INTERNAL RESOURCE +================ +*/ +void SCR_InstallParticlePalette( void ) +{ + rgbdata_t *pic; + int i; + + // first check 'palette.lmp' then 'palette.pal' + pic = FS_LoadImage( "gfx/palette.lmp", NULL, 0 ); + if( !pic ) pic = FS_LoadImage( "gfx/palette.pal", NULL, 0 ); + + // NOTE: imagelib required this fakebuffer for loading internal palette + if( !pic ) pic = FS_LoadImage( "#valve.pal", (byte *)&i, 768 ); + + if( pic ) + { + for( i = 0; i < 256; i++ ) + { + clgame.palette[i].r = pic->palette[i*4+0]; + clgame.palette[i].g = pic->palette[i*4+1]; + clgame.palette[i].b = pic->palette[i*4+2]; + } + FS_FreeImage( pic ); + } + else + { + for( i = 0; i < 256; i++ ) + { + clgame.palette[i].r = i; + clgame.palette[i].g = i; + clgame.palette[i].b = i; + } + MsgDev( D_WARN, "CL_InstallParticlePalette: failed. Force to grayscale\n" ); + } +} + +/* +================ +SCR_RegisterTextures + +INTERNAL RESOURCE +================ +*/ +void SCR_RegisterTextures( void ) +{ + // register gfx.wad images + + if( FS_FileExists( "gfx/paused.lmp", false )) + cls.pauseIcon = GL_LoadTexture( "gfx/paused.lmp", NULL, 0, TF_IMAGE, NULL ); + else if( FS_FileExists( "gfx/pause.lmp", false )) + cls.pauseIcon = GL_LoadTexture( "gfx/pause.lmp", NULL, 0, TF_IMAGE, NULL ); + + if( FS_FileExists( "gfx/lambda.lmp", false )) + { + if( cl_allow_levelshots->value ) + cls.loadingBar = GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE, NULL ); + else cls.loadingBar = GL_LoadTexture( "gfx/lambda.lmp", NULL, 0, TF_IMAGE, NULL ); + } + else if( FS_FileExists( "gfx/loading.lmp", false )) + { + if( cl_allow_levelshots->value ) + cls.loadingBar = GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE|TF_LUMINANCE, NULL ); + else cls.loadingBar = GL_LoadTexture( "gfx/loading.lmp", NULL, 0, TF_IMAGE, NULL ); + } + + cls.tileImage = GL_LoadTexture( "gfx/backtile.lmp", NULL, 0, TF_NOMIPMAP, NULL ); +} + +/* +================= +SCR_SizeUp_f + +Keybinding command +================= +*/ +void SCR_SizeUp_f( void ) +{ + Cvar_SetValue( "viewsize", Q_min( scr_viewsize->value + 10, 120 )); +} + + +/* +================= +SCR_SizeDown_f + +Keybinding command +================= +*/ +void SCR_SizeDown_f( void ) +{ + Cvar_SetValue( "viewsize", Q_max( scr_viewsize->value - 10, 30 )); +} + +/* +================== +SCR_VidInit +================== +*/ +void SCR_VidInit( void ) +{ + memset( &clgame.ds, 0, sizeof( clgame.ds )); // reset a draw state + memset( &gameui.ds, 0, sizeof( gameui.ds )); // reset a draw state + memset( &clgame.centerPrint, 0, sizeof( clgame.centerPrint )); + + // update screen sizes for menu + gameui.globals->scrWidth = glState.width; + gameui.globals->scrHeight = glState.height; + + VGui_Startup (); + + CL_ClearSpriteTextures(); // now all hud sprites are invalid + + // vid_state has changed + if( gameui.hInstance ) gameui.dllFuncs.pfnVidInit(); + if( clgame.hInstance ) clgame.dllFuncs.pfnVidInit(); + + // restart console size + Con_VidInit (); +} + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) +{ + if( scr_init ) return; + + MsgDev( D_NOTE, "SCR_Init()\n" ); + scr_centertime = Cvar_Get( "scr_centertime", "2.5", 0, "centerprint hold time" ); + cl_levelshot_name = Cvar_Get( "cl_levelshot_name", "*black", 0, "contains path to current levelshot" ); + cl_allow_levelshots = Cvar_Get( "allow_levelshots", "0", FCVAR_ARCHIVE, "allow engine to use indivdual levelshots instead of 'loading' image" ); + scr_loading = Cvar_Get( "scr_loading", "0", 0, "loading bar progress" ); + scr_download = Cvar_Get( "scr_download", "-1", 0, "downloading bar progress" ); + cl_testlights = Cvar_Get( "cl_testlights", "0", 0, "test dynamic lights" ); + cl_envshot_size = Cvar_Get( "cl_envshot_size", "256", FCVAR_ARCHIVE, "envshot size of cube side" ); + v_dark = Cvar_Get( "v_dark", "0", 0, "starts level from dark screen" ); + scr_viewsize = Cvar_Get( "viewsize", "120", FCVAR_ARCHIVE, "screen size" ); + + // register our commands + Cmd_AddCommand( "timerefresh", SCR_TimeRefresh_f, "turn quickly and print rendering statistcs" ); + Cmd_AddCommand( "skyname", CL_SetSky_f, "set new skybox by basename" ); + Cmd_AddCommand( "viewpos", SCR_Viewpos_f, "prints current player origin" ); + Cmd_AddCommand( "sizeup", SCR_SizeUp_f, "screen size up to 10 points" ); + Cmd_AddCommand( "sizedown", SCR_SizeDown_f, "screen size down to 10 points" ); + + if( !UI_LoadProgs( )) + { + Con_Printf( S_ERROR "can't initialize gameui.dll\n" ); // there is non fatal for us + host.allow_console = true; // we need console, because menu is missing + } + + SCR_LoadCreditsFont (); + SCR_InstallParticlePalette (); + SCR_RegisterTextures (); + SCR_InitCinematic(); + CL_InitNetgraph(); + SCR_VidInit(); + + if( host.allow_console && Sys_CheckParm( "-toconsole" )) + Cbuf_AddText( "toggleconsole\n" ); + else UI_SetActiveMenu( true ); + + scr_init = true; +} + +void SCR_Shutdown( void ) +{ + if( !scr_init ) return; + + MsgDev( D_NOTE, "SCR_Shutdown()\n" ); + Cmd_RemoveCommand( "timerefresh" ); + Cmd_RemoveCommand( "skyname" ); + Cmd_RemoveCommand( "viewpos" ); + UI_SetActiveMenu( false ); + UI_UnloadProgs(); + + scr_init = false; +} \ No newline at end of file diff --git a/engine/client/cl_tent.c b/engine/client/cl_tent.c new file mode 100644 index 00000000..118a1754 --- /dev/null +++ b/engine/client/cl_tent.c @@ -0,0 +1,3106 @@ +/* +cl_tent.c - temp entity effects management +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "r_efx.h" +#include "entity_types.h" +#include "triangleapi.h" +#include "cl_tent.h" +#include "pm_local.h" +#include "gl_local.h" +#include "studio.h" +#include "wadfile.h" // acess decal size +#include "sound.h" + +/* +============================================================== + +TEMPENTS MANAGEMENT + +============================================================== +*/ +#define FLASHLIGHT_DISTANCE 2000 // in units +#define SHARD_VOLUME 12.0f // on shard ever n^3 units +#define MAX_MUZZLEFLASH 3 + +TEMPENTITY *cl_active_tents; +TEMPENTITY *cl_free_tents; +TEMPENTITY *cl_tempents = NULL; // entities pool + +model_t *cl_sprite_muzzleflash[MAX_MUZZLEFLASH]; // muzzle flashes +model_t *cl_sprite_dot = NULL; +model_t *cl_sprite_ricochet = NULL; +model_t *cl_sprite_shell = NULL; +model_t *cl_sprite_glow = NULL; + +const char *cl_default_sprites[] = +{ + // built-in sprites + "sprites/muzzleflash1.spr", + "sprites/muzzleflash2.spr", + "sprites/muzzleflash3.spr", + "sprites/dot.spr", + "sprites/animglow01.spr", + "sprites/richo1.spr", + "sprites/shellchrome.spr", +}; + +const char *cl_player_shell_sounds[] = +{ + "player/pl_shell1.wav", + "player/pl_shell2.wav", + "player/pl_shell3.wav", +}; + +const char *cl_weapon_shell_sounds[] = +{ + "weapons/sshell1.wav", + "weapons/sshell2.wav", + "weapons/sshell3.wav", +}; + +const char *cl_ricochet_sounds[] = +{ + "weapons/ric1.wav", + "weapons/ric2.wav", + "weapons/ric3.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +}; + +const char *cl_explode_sounds[] = +{ + "weapons/explode3.wav", + "weapons/explode4.wav", + "weapons/explode5.wav", +}; + +/* +================ +CL_LoadClientSprites + +INTERNAL RESOURCE +================ +*/ +void CL_LoadClientSprites( void ) +{ + cl_sprite_muzzleflash[0] = CL_LoadClientSprite( cl_default_sprites[0] ); + cl_sprite_muzzleflash[1] = CL_LoadClientSprite( cl_default_sprites[1] ); + cl_sprite_muzzleflash[2] = CL_LoadClientSprite( cl_default_sprites[2] ); + + cl_sprite_dot = CL_LoadClientSprite( cl_default_sprites[3] ); + cl_sprite_glow = CL_LoadClientSprite( cl_default_sprites[4] ); + cl_sprite_ricochet = CL_LoadClientSprite( cl_default_sprites[5] ); + cl_sprite_shell = CL_LoadClientSprite( cl_default_sprites[6] ); +} + +/* +================ +CL_AddClientResource + +add client-side resource to list +================ +*/ +void CL_AddClientResource( const char *filename, int type ) +{ + resource_t *p, *pResource; + + for( p = cl.resourcesneeded.pNext; p != &cl.resourcesneeded; p = p->pNext ) + { + if( !Q_stricmp( p->szFileName, filename )) + break; + } + + if( p != &cl.resourcesneeded ) + return; // already in list? + + pResource = Mem_Alloc( cls.mempool, sizeof( resource_t )); + + Q_strncpy( pResource->szFileName, filename, sizeof( pResource->szFileName )); + pResource->type = type; + pResource->nIndex = -1; // client resource marker + pResource->nDownloadSize = 1; + pResource->ucFlags |= RES_WASMISSING; + + CL_AddToResourceList( pResource, &cl.resourcesneeded ); +} + +/* +================ +CL_AddClientResources + +client resources not precached by server +================ +*/ +void CL_AddClientResources( void ) +{ + char filepath[MAX_QPATH]; + int i; + + // don't request resources from localhost or in quake-compatibility mode + if( cl.maxclients <= 1 || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return; + + // check sprites first + for( i = 0; i < ARRAYSIZE( cl_default_sprites ); i++ ) + { + if( !FS_FileExists( cl_default_sprites[i], false )) + CL_AddClientResource( cl_default_sprites[i], t_model ); + } + + // then check sounds + for( i = 0; i < ARRAYSIZE( cl_player_shell_sounds ); i++ ) + { + Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, cl_player_shell_sounds[i] ); + + if( !FS_FileExists( filepath, false )) + CL_AddClientResource( cl_player_shell_sounds[i], t_sound ); + } + + for( i = 0; i < ARRAYSIZE( cl_weapon_shell_sounds ); i++ ) + { + Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, cl_weapon_shell_sounds[i] ); + + if( !FS_FileExists( filepath, false )) + CL_AddClientResource( cl_weapon_shell_sounds[i], t_sound ); + } + + for( i = 0; i < ARRAYSIZE( cl_explode_sounds ); i++ ) + { + Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, cl_explode_sounds[i] ); + + if( !FS_FileExists( filepath, false )) + CL_AddClientResource( cl_explode_sounds[i], t_sound ); + } + +#if 0 // ric sounds was precached by server-side + for( i = 0; i < ARRAYSIZE( cl_ricochet_sounds ); i++ ) + { + Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, cl_ricochet_sounds[i] ); + + if( !FS_FileExists( filepath, false )) + CL_AddClientResource( cl_ricochet_sounds[i], t_sound ); + } +#endif +} + +/* +=============== +CL_FxBlend +=============== +*/ +int CL_FxBlend( cl_entity_t *e ) +{ + int blend = 0; + float offset, dist; + vec3_t tmp; + + offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx + + switch( e->curstate.renderfx ) + { + case kRenderFxPulseSlowWide: + blend = e->curstate.renderamt + 0x40 * sin( cl.time * 2 + offset ); + break; + case kRenderFxPulseFastWide: + blend = e->curstate.renderamt + 0x40 * sin( cl.time * 8 + offset ); + break; + case kRenderFxPulseSlow: + blend = e->curstate.renderamt + 0x10 * sin( cl.time * 2 + offset ); + break; + case kRenderFxPulseFast: + blend = e->curstate.renderamt + 0x10 * sin( cl.time * 8 + offset ); + break; + case kRenderFxFadeSlow: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt > 0 ) + e->curstate.renderamt -= 1; + else e->curstate.renderamt = 0; + } + blend = e->curstate.renderamt; + break; + case kRenderFxFadeFast: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt > 3 ) + e->curstate.renderamt -= 4; + else e->curstate.renderamt = 0; + } + blend = e->curstate.renderamt; + break; + case kRenderFxSolidSlow: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt < 255 ) + e->curstate.renderamt += 1; + else e->curstate.renderamt = 255; + } + blend = e->curstate.renderamt; + break; + case kRenderFxSolidFast: + if( RP_NORMALPASS( )) + { + if( e->curstate.renderamt < 252 ) + e->curstate.renderamt += 4; + else e->curstate.renderamt = 255; + } + blend = e->curstate.renderamt; + break; + case kRenderFxStrobeSlow: + blend = 20 * sin( cl.time * 4 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxStrobeFast: + blend = 20 * sin( cl.time * 16 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxStrobeFaster: + blend = 20 * sin( cl.time * 36 + offset ); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxFlickerSlow: + blend = 20 * (sin( cl.time * 2 ) + sin( cl.time * 17 + offset )); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxFlickerFast: + blend = 20 * (sin( cl.time * 16 ) + sin( cl.time * 23 + offset )); + if( blend < 0 ) blend = 0; + else blend = e->curstate.renderamt; + break; + case kRenderFxHologram: + case kRenderFxDistort: + VectorCopy( e->origin, tmp ); + VectorSubtract( tmp, RI.vieworg, tmp ); + dist = DotProduct( tmp, RI.vforward ); + + // turn off distance fade + if( e->curstate.renderfx == kRenderFxDistort ) + dist = 1; + + if( dist <= 0 ) + { + blend = 0; + } + else + { + e->curstate.renderamt = 180; + if( dist <= 100 ) blend = e->curstate.renderamt; + else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * e->curstate.renderamt ); + blend += COM_RandomLong( -32, 31 ); + } + break; + default: + blend = e->curstate.renderamt; + break; + } + + blend = bound( 0, blend, 255 ); + + return blend; +} + +/* +================ +CL_InitTempents + +================ +*/ +void CL_InitTempEnts( void ) +{ + cl_tempents = Mem_Alloc( cls.mempool, sizeof( TEMPENTITY ) * GI->max_tents ); + CL_ClearTempEnts(); + + // load tempent sprites (glowshell, muzzleflashes etc) + CL_LoadClientSprites (); +} + +/* +================ +CL_ClearTempEnts + +================ +*/ +void CL_ClearTempEnts( void ) +{ + int i; + + if( !cl_tempents ) return; + + for( i = 0; i < GI->max_tents - 1; i++ ) + { + cl_tempents[i].next = &cl_tempents[i+1]; + cl_tempents[i].entity.trivial_accept = INVALID_HANDLE; + } + + cl_tempents[GI->max_tents-1].next = NULL; + cl_free_tents = cl_tempents; + cl_active_tents = NULL; +} + +/* +================ +CL_FreeTempEnts + +================ +*/ +void CL_FreeTempEnts( void ) +{ + if( cl_tempents ) + Mem_Free( cl_tempents ); + cl_tempents = NULL; +} + +/* +============== +CL_PrepareTEnt + +set default values +============== +*/ +void CL_PrepareTEnt( TEMPENTITY *pTemp, model_t *pmodel ) +{ + int frameCount = 0; + int modelIndex = 0; + int modelHandle = pTemp->entity.trivial_accept; + + memset( pTemp, 0, sizeof( *pTemp )); + + // use these to set per-frame and termination conditions / actions + pTemp->entity.trivial_accept = modelHandle; // keep unchanged + pTemp->flags = FTENT_NONE; + pTemp->die = cl.time + 0.75f; + + if( pmodel ) frameCount = pmodel->numframes; + else pTemp->flags |= FTENT_NOMODEL; + + pTemp->entity.curstate.modelindex = modelIndex; + pTemp->entity.curstate.rendermode = kRenderNormal; + pTemp->entity.curstate.renderfx = kRenderFxNone; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + pTemp->frameMax = Q_max( 0, frameCount - 1 ); + pTemp->entity.curstate.renderamt = 255; + pTemp->entity.curstate.body = 0; + pTemp->entity.curstate.skin = 0; + pTemp->entity.model = pmodel; + pTemp->fadeSpeed = 0.5f; + pTemp->hitSound = 0; + pTemp->clientIndex = 0; + pTemp->bounceFactor = 1; + pTemp->entity.curstate.scale = 1.0f; +} + +/* +============== +CL_TempEntPlaySound + +play collide sound +============== +*/ +void CL_TempEntPlaySound( TEMPENTITY *pTemp, float damp ) +{ + float fvol; + char soundname[32]; + qboolean isshellcasing = false; + int zvel; + + Assert( pTemp != NULL ); + + fvol = 0.8f; + + switch( pTemp->hitSound ) + { + case BOUNCE_GLASS: + Q_snprintf( soundname, sizeof( soundname ), "debris/glass%i.wav", COM_RandomLong( 1, 4 )); + break; + case BOUNCE_METAL: + Q_snprintf( soundname, sizeof( soundname ), "debris/metal%i.wav", COM_RandomLong( 1, 6 )); + break; + case BOUNCE_FLESH: + Q_snprintf( soundname, sizeof( soundname ), "debris/flesh%i.wav", COM_RandomLong( 1, 7 )); + break; + case BOUNCE_WOOD: + Q_snprintf( soundname, sizeof( soundname ), "debris/wood%i.wav", COM_RandomLong( 1, 4 )); + break; + case BOUNCE_SHRAP: + Q_snprintf( soundname, sizeof( soundname ), "%s", cl_ricochet_sounds[COM_RandomLong( 0, 4 )] ); + break; + case BOUNCE_SHOTSHELL: + Q_snprintf( soundname, sizeof( soundname ), "%s", cl_weapon_shell_sounds[COM_RandomLong( 0, 2 )] ); + isshellcasing = true; // shell casings have different playback parameters + fvol = 0.5f; + break; + case BOUNCE_SHELL: + Q_snprintf( soundname, sizeof( soundname ), "%s", cl_player_shell_sounds[COM_RandomLong( 0, 2 )] ); + isshellcasing = true; // shell casings have different playback parameters + break; + case BOUNCE_CONCRETE: + Q_snprintf( soundname, sizeof( soundname ), "debris/concrete%i.wav", COM_RandomLong( 1, 3 )); + break; + default: // null sound + return; + } + + zvel = abs( pTemp->entity.baseline.origin[2] ); + + // only play one out of every n + if( isshellcasing ) + { + // play first bounce, then 1 out of 3 + if( zvel < 200 && COM_RandomLong( 0, 3 )) + return; + } + else + { + if( COM_RandomLong( 0, 5 )) + return; + } + + if( damp > 0.0f ) + { + int pitch; + sound_t handle; + + if( isshellcasing ) + fvol *= min ( 1.0f, ((float)zvel) / 350.0f ); + else fvol *= min ( 1.0f, ((float)zvel) / 450.0f ); + + if( !COM_RandomLong( 0, 3 ) && !isshellcasing ) + pitch = COM_RandomLong( 95, 105 ); + else pitch = PITCH_NORM; + + handle = S_RegisterSound( soundname ); + S_StartSound( pTemp->entity.origin, -(pTemp - cl_tempents), CHAN_BODY, handle, fvol, ATTN_NORM, pitch, SND_STOP_LOOPING ); + } +} + +/* +============== +CL_TEntAddEntity + +add entity to renderlist +============== +*/ +int CL_TempEntAddEntity( cl_entity_t *pEntity ) +{ + vec3_t mins, maxs; + + Assert( pEntity != NULL ); + + if( !pEntity->model ) + return 0; + + VectorAdd( pEntity->origin, pEntity->model->mins, mins ); + VectorAdd( pEntity->origin, pEntity->model->maxs, maxs ); + + // g-cont. just use PVS from previous frame + if( TriBoxInPVS( mins, maxs )) + { + VectorCopy( pEntity->angles, pEntity->curstate.angles ); + VectorCopy( pEntity->origin, pEntity->curstate.origin ); + VectorCopy( pEntity->angles, pEntity->latched.prevangles ); + VectorCopy( pEntity->origin, pEntity->latched.prevorigin ); + + // add to list + if( CL_AddVisibleEntity( pEntity, ET_TEMPENTITY )) + r_stats.c_active_tents_count++; + + return 1; + } + + return 0; +} + +/* +============== +CL_AddTempEnts + +temp-entities will be added on a user-side +setup client callback +============== +*/ +void CL_TempEntUpdate( void ) +{ + double ft = cl.time - cl.oldtime; + float gravity = clgame.movevars.gravity; + + clgame.dllFuncs.pfnTempEntUpdate( ft, cl.time, gravity, &cl_free_tents, &cl_active_tents, CL_TempEntAddEntity, CL_TempEntPlaySound ); +} + +/* +============== +CL_TEntAddEntity + +free the first low priority tempent it finds. +============== +*/ +qboolean CL_FreeLowPriorityTempEnt( void ) +{ + TEMPENTITY *pActive = cl_active_tents; + TEMPENTITY *pPrev = NULL; + + while( pActive ) + { + if( pActive->priority == TENTPRIORITY_LOW ) + { + // remove from the active list. + if( pPrev ) pPrev->next = pActive->next; + else cl_active_tents = pActive->next; + + // add to the free list. + pActive->next = cl_free_tents; + cl_free_tents = pActive; + + return true; + } + + pPrev = pActive; + pActive = pActive->next; + } + + return false; +} + +/* +============== +CL_TempEntAlloc + +alloc normal\low priority tempentity +============== +*/ +TEMPENTITY *CL_TempEntAlloc( const vec3_t org, model_t *pmodel ) +{ + TEMPENTITY *pTemp; + + if( !cl_free_tents ) + { + MsgDev( D_INFO, "Overflow %d temporary ents!\n", GI->max_tents ); + return NULL; + } + + pTemp = cl_free_tents; + cl_free_tents = pTemp->next; + + CL_PrepareTEnt( pTemp, pmodel ); + + pTemp->priority = TENTPRIORITY_LOW; + if( org ) VectorCopy( org, pTemp->entity.origin ); + + pTemp->next = cl_active_tents; + cl_active_tents = pTemp; + + return pTemp; +} + +/* +============== +CL_TempEntAllocHigh + +alloc high priority tempentity +============== +*/ +TEMPENTITY *CL_TempEntAllocHigh( const vec3_t org, model_t *pmodel ) +{ + TEMPENTITY *pTemp; + + if( !cl_free_tents ) + { + // no temporary ents free, so find the first active low-priority temp ent + // and overwrite it. + CL_FreeLowPriorityTempEnt(); + } + + if( !cl_free_tents ) + { + // didn't find anything? The tent list is either full of high-priority tents + // or all tents in the list are still due to live for > 10 seconds. + MsgDev( D_INFO, "Couldn't alloc a high priority TENT!\n" ); + return NULL; + } + + // Move out of the free list and into the active list. + pTemp = cl_free_tents; + cl_free_tents = pTemp->next; + + CL_PrepareTEnt( pTemp, pmodel ); + + pTemp->priority = TENTPRIORITY_HIGH; + if( org ) VectorCopy( org, pTemp->entity.origin ); + + pTemp->next = cl_active_tents; + cl_active_tents = pTemp; + + return pTemp; +} + +/* +============== +CL_TempEntAlloc + +alloc normal priority tempentity with no model +============== +*/ +TEMPENTITY *CL_TempEntAllocNoModel( const vec3_t org ) +{ + return CL_TempEntAlloc( org, NULL ); +} + +/* +============== +CL_TempEntAlloc + +custom tempentity allocation +============== +*/ +TEMPENTITY *CL_TempEntAllocCustom( const vec3_t org, model_t *model, int high, void (*pfn)( TEMPENTITY*, float, float )) +{ + TEMPENTITY *pTemp; + + if( high ) + { + pTemp = CL_TempEntAllocHigh( org, model ); + } + else + { + pTemp = CL_TempEntAlloc( org, model ); + } + + if( pTemp && pfn ) + { + pTemp->flags |= FTENT_CLIENTCUSTOM; + pTemp->callback = pfn; + pTemp->die = cl.time; + } + + return pTemp; +} + +/* +============================================================== + + EFFECTS BASED ON TEMPENTS (presets) + +============================================================== +*/ +/* +============== +R_FizzEffect + +Create a fizz effect +============== +*/ +void R_FizzEffect( cl_entity_t *pent, int modelIndex, int density ) +{ + TEMPENTITY *pTemp; + int i, width, depth, count; + float angle, maxHeight, speed; + float xspeed, yspeed, zspeed; + vec3_t origin; + model_t *mod; + + if( !pent || pent->curstate.modelindex <= 0 ) + return; + + if(( mod = CL_ModelHandle( pent->curstate.modelindex )) == NULL ) + return; + + count = density + 1; + density = count * 3 + 6; + maxHeight = mod->maxs[2] - mod->mins[2]; + width = mod->maxs[0] - mod->mins[0]; + depth = mod->maxs[1] - mod->mins[1]; + + speed = ( pent->curstate.rendercolor.r<<8 | pent->curstate.rendercolor.g ); + if( pent->curstate.rendercolor.b ) + speed = -speed; + + angle = DEG2RAD( pent->angles[YAW] ); + SinCos( angle, &yspeed, &xspeed ); + + xspeed *= speed; + yspeed *= speed; + + for( i = 0; i < count; i++ ) + { + origin[0] = mod->mins[0] + COM_RandomLong( 0, width - 1 ); + origin[1] = mod->mins[1] + COM_RandomLong( 0, depth - 1 ); + origin[2] = mod->mins[2]; + pTemp = CL_TempEntAlloc( origin, CL_ModelHandle( modelIndex )); + + if ( !pTemp ) return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + + zspeed = COM_RandomLong( 80, 140 ); + VectorSet( pTemp->entity.baseline.origin, xspeed, yspeed, zspeed ); + pTemp->die = cl.time + ( maxHeight / zspeed ) - 0.1f; + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + // Set sprite scale + pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f ); + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderamt = 255; + } +} + +/* +============== +R_Bubbles + +Create bubbles +============== +*/ +void R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed ) +{ + TEMPENTITY *pTemp; + float sine, cosine; + float angle, zspeed; + vec3_t origin; + model_t *mod; + int i; + + if(( mod = CL_ModelHandle( modelIndex )) == NULL ) + return; + + for ( i = 0; i < count; i++ ) + { + origin[0] = COM_RandomLong( mins[0], maxs[0] ); + origin[1] = COM_RandomLong( mins[1], maxs[1] ); + origin[2] = COM_RandomLong( mins[2], maxs[2] ); + pTemp = CL_TempEntAlloc( origin, mod ); + if( !pTemp ) return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + angle = COM_RandomFloat( -M_PI, M_PI ); + SinCos( angle, &sine, &cosine ); + + zspeed = COM_RandomLong( 80, 140 ); + VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed ); + pTemp->die = cl.time + ((height - (origin[2] - mins[2])) / zspeed) - 0.1f; + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + + // Set sprite scale + pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f ); + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderamt = 255; + } +} + +/* +============== +R_BubbleTrail + +Create bubble trail +============== +*/ +void R_BubbleTrail( const vec3_t start, const vec3_t end, float height, int modelIndex, int count, float speed ) +{ + TEMPENTITY *pTemp; + float sine, cosine, zspeed; + float dist, angle; + vec3_t origin; + model_t *mod; + int i; + + if(( mod = CL_ModelHandle( modelIndex )) == NULL ) + return; + + for( i = 0; i < count; i++ ) + { + dist = COM_RandomFloat( 0, 1.0 ); + VectorLerp( start, dist, end, origin ); + pTemp = CL_TempEntAlloc( origin, mod ); + if( !pTemp ) return; + + pTemp->flags |= FTENT_SINEWAVE; + + pTemp->x = origin[0]; + pTemp->y = origin[1]; + angle = COM_RandomFloat( -M_PI, M_PI ); + SinCos( angle, &sine, &cosine ); + + zspeed = COM_RandomLong( 80, 140 ); + VectorSet( pTemp->entity.baseline.origin, speed * cosine, speed * sine, zspeed ); + pTemp->die = cl.time + ((height - (origin[2] - start[2])) / zspeed) - 0.1f; + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + + // Set sprite scale + pTemp->entity.curstate.scale = 1.0f / COM_RandomFloat( 2.0f, 5.0f ); + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderamt = 255; + } +} + +/* +============== +R_AttachTentToPlayer + +Attaches entity to player +============== +*/ +void R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life ) +{ + TEMPENTITY *pTemp; + vec3_t position; + cl_entity_t *pClient; + model_t *pModel; + + if( client <= 0 || client > cl.maxclients ) + { + MsgDev( D_ERROR, "Bad client %i in AttachTentToPlayer()!\n", client ); + return; + } + + pClient = CL_GetEntityByIndex( client ); + + if( !pClient || pClient->curstate.messagenum != cl.parsecount ) + return; + + if(( pModel = CL_ModelHandle( modelIndex )) == NULL ) + return; + + VectorCopy( pClient->origin, position ); + position[2] += zoffset; + + pTemp = CL_TempEntAllocHigh( position, pModel ); + if( !pTemp ) return; + + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.curstate.framerate = 1; + + pTemp->clientIndex = client; + pTemp->tentOffset[0] = 0; + pTemp->tentOffset[1] = 0; + pTemp->tentOffset[2] = zoffset; + pTemp->die = cl.time + life; + pTemp->flags |= FTENT_PLYRATTACHMENT|FTENT_PERSIST; + + // is the model a sprite? + if( pModel->type == mod_sprite ) + { + pTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP; + pTemp->entity.curstate.framerate = 10; + } + else + { + // no animation support for attached clientside studio models. + pTemp->frameMax = 0; + } + + pTemp->entity.curstate.frame = 0; +} + +/* +============== +R_KillAttachedTents + +Detach entity from player +============== +*/ +void R_KillAttachedTents( int client ) +{ + int i; + + if( client <= 0 || client > cl.maxclients ) + { + MsgDev( D_ERROR, "Bad client %i in KillAttachedTents()!\n", client ); + return; + } + + for( i = 0; i < GI->max_tents; i++ ) + { + TEMPENTITY *pTemp = &cl_tempents[i]; + + if( !FBitSet( pTemp->flags, FTENT_PLYRATTACHMENT )) + continue; + + // this TEMPENTITY is player attached. + // if it is attached to this client, set it to die instantly. + if( pTemp->clientIndex == client ) + { + // good enough, it will die on next tent update. + pTemp->die = cl.time; + } + } +} + +/* +============== +R_RicochetSprite + +Create ricochet sprite +============== +*/ +void R_RicochetSprite( const vec3_t pos, model_t *pmodel, float duration, float scale ) +{ + TEMPENTITY *pTemp; + + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return; + + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 200; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.curstate.scale = scale; + pTemp->die = cl.time + duration; + pTemp->flags = FTENT_FADEOUT; + pTemp->fadeSpeed = 8; + + pTemp->entity.curstate.frame = 0; + pTemp->entity.angles[ROLL] = 45.0f * COM_RandomLong( 0, 7 ); +} + +/* +============== +R_RocketFlare + +Create rocket flare +============== +*/ +void R_RocketFlare( const vec3_t pos ) +{ + TEMPENTITY *pTemp; + + if( !cl_sprite_glow ) return; + + pTemp = CL_TempEntAlloc( pos, cl_sprite_glow ); + if ( !pTemp ) return; + + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.curstate.renderamt = 200; + pTemp->entity.curstate.framerate = 1.0; + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + pTemp->entity.curstate.scale = 1.0; + pTemp->die = cl.time + 0.01f; // when 100 fps die at next frame + pTemp->entity.curstate.effects = EF_NOINTERP; +} + +/* +============== +R_MuzzleFlash + +Do muzzleflash +============== +*/ +void R_MuzzleFlash( const vec3_t pos, int type ) +{ + TEMPENTITY *pTemp; + int index; + float scale; + + index = ( type % 10 ) % MAX_MUZZLEFLASH; + scale = ( type / 10 ) * 0.1f; + if( scale == 0.0f ) scale = 0.5f; + + if( !cl_sprite_muzzleflash[index] ) + return; + + // must set position for right culling on render + pTemp = CL_TempEntAllocHigh( pos, cl_sprite_muzzleflash[index] ); + if( !pTemp ) return; + pTemp->entity.curstate.rendermode = kRenderTransAdd; + pTemp->entity.curstate.renderamt = 255; + pTemp->entity.curstate.framerate = 10; + pTemp->entity.curstate.renderfx = 0; + pTemp->die = cl.time + 0.01; // die at next frame + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + pTemp->flags |= FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP; + pTemp->entity.curstate.scale = scale; + + if( index == 0 ) pTemp->entity.angles[2] = COM_RandomLong( 0, 20 ); // rifle flash + else pTemp->entity.angles[2] = COM_RandomLong( 0, 359 ); + + CL_TempEntAddEntity( &pTemp->entity ); +} + +/* +============== +R_BloodSprite + +Create a high priority blood sprite +and some blood drops. This is high-priority tent +============== +*/ +void R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size ) +{ + model_t *pModel, *pModel2; + int impactindex; + int spatterindex; + int i, splatter; + TEMPENTITY *pTemp; + vec3_t pos; + + colorIndex += COM_RandomLong( 1, 3 ); + impactindex = colorIndex; + spatterindex = colorIndex - 1; + + // validate the model first + if(( pModel = CL_ModelHandle( modelIndex )) != NULL ) + { + VectorCopy( org, pos ); + pos[2] += COM_RandomFloat( 2.0f, 4.0f ); // make offset from ground (snarks issues) + + // large, single blood sprite is a high-priority tent + if(( pTemp = CL_TempEntAllocHigh( pos, pModel )) != NULL ) + { + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderfx = kRenderFxClampMinScale; + pTemp->entity.curstate.scale = COM_RandomFloat( size / 25.0f, size / 35.0f ); + pTemp->flags = FTENT_SPRANIMATE; + + pTemp->entity.curstate.rendercolor = clgame.palette[impactindex]; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250; + + pTemp->entity.curstate.framerate = pTemp->frameMax * 4.0f; // Finish in 0.250 seconds + pTemp->die = cl.time + (pTemp->frameMax / pTemp->entity.curstate.framerate ); // play the whole thing once + + pTemp->entity.curstate.frame = 0; + pTemp->bounceFactor = 0; + pTemp->entity.angles[2] = COM_RandomLong( 0, 360 ); + } + } + + // validate the model first + if(( pModel2 = CL_ModelHandle( modelIndex2 )) != NULL ) + { + splatter = size + ( COM_RandomLong( 1, 8 ) + COM_RandomLong( 1, 8 )); + + for( i = 0; i < splatter; i++ ) + { + // create blood drips + if(( pTemp = CL_TempEntAlloc( org, pModel2 )) != NULL ) + { + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderfx = kRenderFxClampMinScale; + pTemp->entity.curstate.scale = COM_RandomFloat( size / 15.0f, size / 25.0f ); + pTemp->flags = FTENT_ROTATE | FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD; + + pTemp->entity.curstate.rendercolor = clgame.palette[spatterindex]; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 250; + + pTemp->entity.baseline.origin[0] = COM_RandomFloat( -96.0f, 95.0f ); + pTemp->entity.baseline.origin[1] = COM_RandomFloat( -96.0f, 95.0f ); + pTemp->entity.baseline.origin[2] = COM_RandomFloat( -32.0f, 95.0f ); + pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f ); + pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f ); + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f ); + + pTemp->die = cl.time + COM_RandomFloat( 1.0f, 3.0f ); + + pTemp->entity.curstate.frame = COM_RandomLong( 1, pTemp->frameMax ); + + if( pTemp->entity.curstate.frame > 8.0f ) + pTemp->entity.curstate.frame = pTemp->frameMax; + + pTemp->entity.baseline.origin[2] += COM_RandomFloat( 4.0f, 16.0f ) * size; + pTemp->entity.angles[2] = COM_RandomFloat( 0.0f, 360.0f ); + pTemp->bounceFactor = 0.0f; + } + } + } +} + +/* +============== +R_BreakModel + +Create a shards +============== +*/ +void R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t dir, float random, float life, int count, int modelIndex, char flags ) +{ + TEMPENTITY *pTemp; + model_t *pmodel; + char type; + int i, j; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + return; + + type = flags & BREAK_TYPEMASK; + + if( count == 0 ) + { + // assume surface (not volume) + count = (size[0] * size[1] + size[1] * size[2] + size[2] * size[0]) / (3 * SHARD_VOLUME * SHARD_VOLUME); + } + + // limit to 100 pieces + if( count > 100 ) count = 100; + + for( i = 0; i < count; i++ ) + { + vec3_t vecSpot; + + for( j = 0; j < 32; j++ ) + { + // fill up the box with stuff + vecSpot[0] = pos[0] + COM_RandomFloat( -0.5f, 0.5f ) * size[0]; + vecSpot[1] = pos[1] + COM_RandomFloat( -0.5f, 0.5f ) * size[1]; + vecSpot[2] = pos[2] + COM_RandomFloat( -0.5f, 0.5f ) * size[2]; + + if( CL_PointContents( vecSpot ) != CONTENTS_SOLID ) + break; // valid spot + } + + if( j == 32 ) continue; // a piece completely stuck in the wall, ignore it + + pTemp = CL_TempEntAlloc( vecSpot, pmodel ); + if( !pTemp ) return; + + // keep track of break_type, so we know how to play sound on collision + pTemp->hitSound = type; + + if( pmodel->type == mod_sprite ) + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + else if( pmodel->type == mod_studio ) + pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax ); + + pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_FADEOUT | FTENT_SLOWGRAVITY; + + if( COM_RandomLong( 0, 255 ) < 200 ) + { + pTemp->flags |= FTENT_ROTATE; + pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256, 255 ); + pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256, 255 ); + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256, 255 ); + } + + if (( COM_RandomLong( 0, 255 ) < 100 ) && FBitSet( flags, BREAK_SMOKE )) + pTemp->flags |= FTENT_SMOKETRAIL; + + if(( type == BREAK_GLASS ) || FBitSet( flags, BREAK_TRANS )) + { + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 128; + } + else + { + pTemp->entity.curstate.rendermode = kRenderNormal; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = 255; // set this for fadeout + } + + pTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -random, random ); + pTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -random, random ); + pTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, random ); + + pTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 1.0f ); // Add an extra 0-1 secs of life + } +} + +/* +============== +R_TempModel + +Create a temp model with gravity, sounds and fadeout +============== +*/ +TEMPENTITY *R_TempModel( const vec3_t pos, const vec3_t dir, const vec3_t angles, float life, int modelIndex, int soundtype ) +{ + // alloc a new tempent + TEMPENTITY *pTemp; + model_t *pmodel; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + return NULL; + + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return NULL; + + pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_GRAVITY); + VectorCopy( dir, pTemp->entity.baseline.origin ); + VectorCopy( angles, pTemp->entity.angles ); + + // keep track of shell type + switch( soundtype ) + { + case TE_BOUNCE_SHELL: + pTemp->hitSound = BOUNCE_SHELL; + pTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 ); + pTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 ); + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 ); + pTemp->flags |= FTENT_ROTATE; + break; + case TE_BOUNCE_SHOTSHELL: + pTemp->hitSound = BOUNCE_SHOTSHELL; + pTemp->entity.baseline.angles[0] = COM_RandomFloat( -512, 511 ); + pTemp->entity.baseline.angles[1] = COM_RandomFloat( -255, 255 ); + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -255, 255 ); + pTemp->flags |= FTENT_ROTATE|FTENT_SLOWGRAVITY; + break; + } + + if( pmodel->type == mod_sprite ) + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + else pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax ); + + pTemp->die = cl.time + life; + + return pTemp; +} + +/* +============== +R_DefaultSprite + +Create an animated sprite +============== +*/ +TEMPENTITY *R_DefaultSprite( const vec3_t pos, int spriteIndex, float framerate ) +{ + TEMPENTITY *pTemp; + model_t *psprite; + + // don't spawn while paused + if( cl.time == cl.oldtime ) + return NULL; + + if(( psprite = CL_ModelHandle( spriteIndex )) == NULL || psprite->type != mod_sprite ) + { + MsgDev( D_INFO, "No Sprite %d!\n", spriteIndex ); + return NULL; + } + + pTemp = CL_TempEntAlloc( pos, psprite ); + if( !pTemp ) return NULL; + + pTemp->entity.curstate.scale = 1.0f; + pTemp->flags |= FTENT_SPRANIMATE; + if( framerate == 0 ) framerate = 10; + + pTemp->entity.curstate.framerate = framerate; + pTemp->die = cl.time + (float)pTemp->frameMax / framerate; + pTemp->entity.curstate.frame = 0; + + return pTemp; +} + +/* +=============== +R_SparkShower + +Create an animated moving sprite +=============== +*/ +void R_SparkShower( const vec3_t pos ) +{ + TEMPENTITY *pTemp; + + pTemp = CL_TempEntAllocNoModel( pos ); + if( !pTemp ) return; + + pTemp->entity.baseline.origin[0] = COM_RandomFloat( -300.0f, 300.0f ); + pTemp->entity.baseline.origin[1] = COM_RandomFloat( -300.0f, 300.0f ); + pTemp->entity.baseline.origin[2] = COM_RandomFloat( -200.0f, 200.0f ); + + pTemp->flags |= FTENT_SLOWGRAVITY | FTENT_COLLIDEWORLD | FTENT_SPARKSHOWER; + + pTemp->entity.curstate.framerate = COM_RandomFloat( 0.5f, 1.5f ); + pTemp->entity.curstate.scale = cl.time; + pTemp->die = cl.time + 0.5; +} + +/* +=============== +R_TempSprite + +Create an animated moving sprite +=============== +*/ +TEMPENTITY *R_TempSprite( vec3_t pos, const vec3_t dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ) +{ + TEMPENTITY *pTemp; + model_t *pmodel; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + { + MsgDev( D_ERROR, "No model %d!\n", modelIndex ); + return NULL; + } + + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return NULL; + + pTemp->entity.curstate.framerate = 10; + pTemp->entity.curstate.rendermode = rendermode; + pTemp->entity.curstate.renderfx = renderfx; + pTemp->entity.curstate.scale = scale; + pTemp->entity.baseline.renderamt = a * 255; + pTemp->entity.curstate.renderamt = a * 255; + pTemp->flags |= flags; + + VectorCopy( dir, pTemp->entity.baseline.origin ); + + if( life ) pTemp->die = cl.time + life; + else pTemp->die = cl.time + ( pTemp->frameMax * 0.1f ) + 1.0f; + pTemp->entity.curstate.frame = 0; + + return pTemp; +} + +/* +=============== +R_Sprite_Explode + +apply params for exploding sprite +=============== +*/ +void R_Sprite_Explode( TEMPENTITY *pTemp, float scale, int flags ) +{ + if( !pTemp ) return; + + if( FBitSet( flags, TE_EXPLFLAG_NOADDITIVE )) + { + // solid sprite + pTemp->entity.curstate.rendermode = kRenderNormal; + pTemp->entity.curstate.renderamt = 255; + } + else if( FBitSet( flags, TE_EXPLFLAG_DRAWALPHA )) + { + // alpha sprite (came from hl2) + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderamt = 180; + } + else + { + // additive sprite + pTemp->entity.curstate.rendermode = kRenderTransAdd; + pTemp->entity.curstate.renderamt = 180; + } + + if( FBitSet( flags, TE_EXPLFLAG_ROTATE )) + { + // came from hl2 + pTemp->entity.angles[2] = COM_RandomLong( 0, 360 ); + } + + pTemp->entity.curstate.renderfx = kRenderFxNone; + pTemp->entity.baseline.origin[2] = 8; + pTemp->entity.origin[2] += 10; + pTemp->entity.curstate.scale = scale; +} + +/* +=============== +R_Sprite_Smoke + +apply params for smoke sprite +=============== +*/ +void R_Sprite_Smoke( TEMPENTITY *pTemp, float scale ) +{ + int iColor; + + if( !pTemp ) return; + + iColor = COM_RandomLong( 20, 35 ); + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderfx = kRenderFxNone; + pTemp->entity.baseline.origin[2] = 30; + pTemp->entity.curstate.rendercolor.r = iColor; + pTemp->entity.curstate.rendercolor.g = iColor; + pTemp->entity.curstate.rendercolor.b = iColor; + pTemp->entity.origin[2] += 20; + pTemp->entity.curstate.scale = scale; +} + +/* +=============== +R_Spray + +Throws a shower of sprites or models +=============== +*/ +void R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread, int rendermode ) +{ + TEMPENTITY *pTemp; + float noise; + float znoise; + model_t *pmodel; + int i; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + { + MsgDev( D_INFO, "No model %d!\n", modelIndex ); + return; + } + + noise = (float)spread / 100.0f; + + // more vertical displacement + znoise = Q_min( 1.0f, noise * 1.5f ); + + for( i = 0; i < count; i++ ) + { + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return; + + pTemp->entity.curstate.rendermode = rendermode; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + + if( rendermode != kRenderGlow ) + { + // spray + pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY; + + if( pTemp->frameMax > 1 ) + { + pTemp->flags |= FTENT_COLLIDEWORLD | FTENT_SLOWGRAVITY | FTENT_SPRANIMATE; + pTemp->die = cl.time + (pTemp->frameMax * 0.1f); + pTemp->entity.curstate.framerate = 10; + } + else pTemp->die = cl.time + 0.35f; + } + else + { + // sprite spray + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + pTemp->flags |= FTENT_FADEOUT | FTENT_SLOWGRAVITY; + pTemp->entity.curstate.framerate = 0.5; + pTemp->die = cl.time + 0.35f; + pTemp->fadeSpeed = 2.0; + } + + // make the spittle fly the direction indicated, but mix in some noise. + pTemp->entity.baseline.origin[0] = dir[0] + COM_RandomFloat( -noise, noise ); + pTemp->entity.baseline.origin[1] = dir[1] + COM_RandomFloat( -noise, noise ); + pTemp->entity.baseline.origin[2] = dir[2] + COM_RandomFloat( 0, znoise ); + VectorScale( pTemp->entity.baseline.origin, COM_RandomFloat(( speed * 0.8f ), ( speed * 1.2f )), pTemp->entity.baseline.origin ); + } +} + +/* +=============== +R_Sprite_Spray + +Spray of alpha sprites +=============== +*/ +void R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int spread ) +{ + R_Spray( pos, dir, modelIndex, count, speed, spread, kRenderGlow ); +} + +/* +=============== +R_Sprite_Trail + +Line of moving glow sprites with gravity, +fadeout, and collisions +=============== +*/ +void R_Sprite_Trail( int type, vec3_t start, vec3_t end, int modelIndex, int count, float life, float size, float amp, int renderamt, float speed ) +{ + TEMPENTITY *pTemp; + vec3_t delta, dir; + model_t *pmodel; + int i; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + return; + + VectorSubtract( end, start, delta ); + VectorNormalize2( delta, dir ); + + amp /= 256.0f; + + for( i = 0; i < count; i++ ) + { + vec3_t pos, vel; + + // Be careful of divide by 0 when using 'count' here... + if( i == 0 ) VectorCopy( start, pos ); + else VectorMA( start, ( i / ( count - 1.0f )), delta, pos ); + + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return; + + pTemp->flags = (FTENT_COLLIDEWORLD|FTENT_SPRCYCLE|FTENT_FADEOUT|FTENT_SLOWGRAVITY); + + VectorScale( dir, speed, vel ); + vel[0] += COM_RandomFloat( -127.0f, 128.0f ) * amp; + vel[1] += COM_RandomFloat( -127.0f, 128.0f ) * amp; + vel[2] += COM_RandomFloat( -127.0f, 128.0f ) * amp; + VectorCopy( vel, pTemp->entity.baseline.origin ); + VectorCopy( pos, pTemp->entity.origin ); + + pTemp->entity.curstate.scale = size; + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt = renderamt; + + pTemp->entity.curstate.frame = COM_RandomLong( 0, pTemp->frameMax ); + pTemp->die = cl.time + life + COM_RandomFloat( 0.0f, 4.0f ); + } +} + +/* +=============== +R_FunnelSprite + +Create a funnel effect with custom sprite +=============== +*/ +void R_FunnelSprite( const vec3_t org, int modelIndex, int reverse ) +{ + TEMPENTITY *pTemp; + vec3_t dir, dest; + float dist, vel; + model_t *pmodel; + int i, j; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + { + MsgDev( D_ERROR, "no model %d!\n", modelIndex ); + return; + } + + for( i = -8; i < 8; i++ ) + { + for( j = -8; j < 8; j++ ) + { + pTemp = CL_TempEntAlloc( org, pmodel ); + if( !pTemp ) return; + + dest[0] = (i * 32.0f) + org[0]; + dest[1] = (j * 32.0f) + org[1]; + dest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f ); + + if( reverse ) + { + VectorCopy( org, pTemp->entity.origin ); + VectorSubtract( dest, pTemp->entity.origin, dir ); + } + else + { + VectorCopy( dest, pTemp->entity.origin ); + VectorSubtract( org, pTemp->entity.origin, dir ); + } + + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 200; + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -100.0f, 100.0f ); + pTemp->entity.curstate.framerate = COM_RandomFloat( 0.1f, 0.4f ); + pTemp->flags = FTENT_ROTATE|FTENT_FADEOUT; + pTemp->entity.curstate.framerate = 10; + + vel = dest[2] / 8.0f; + if( vel < 64.0f ) vel = 64.0f; + dist = VectorNormalizeLength( dir ); + vel += COM_RandomFloat( 64.0f, 128.0f ); + VectorScale( dir, vel, pTemp->entity.baseline.origin ); + pTemp->die = cl.time + (dist / vel) - 0.5f; + pTemp->fadeSpeed = 2.0f; + } + } +} + +/* +=============== +R_SparkEffect + +Create a streaks + richochet sprite +=============== +*/ +void R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax ) +{ + R_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, COM_RandomFloat( 0.5f, 1.0f )); + R_SparkStreaks( pos, count, velocityMin, velocityMax ); +} + +/* +============== +R_RicochetSound + +Make a random ricochet sound +============== +*/ +void R_RicochetSound( const vec3_t pos ) +{ + int iPitch = COM_RandomLong( 90, 105 ); + float fvol = COM_RandomFloat( 0.7f, 0.9f ); + char soundpath[32]; + sound_t handle; + + Q_snprintf( soundpath, sizeof( soundpath ), "%s", cl_ricochet_sounds[COM_RandomLong( 0, 4 )] ); + handle = S_RegisterSound( soundpath ); + + S_StartSound( pos, 0, CHAN_AUTO, handle, fvol, ATTN_NORM, iPitch, 0 ); +} + +/* +============== +R_Projectile + +Create an projectile entity +============== +*/ +void R_Projectile( const vec3_t origin, const vec3_t velocity, int modelIndex, int life, int owner, void (*hitcallback)( TEMPENTITY*, pmtrace_t* )) +{ + TEMPENTITY *pTemp; + model_t *pmodel; + vec3_t dir; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + return; + + pTemp = CL_TempEntAllocHigh( origin, pmodel ); + if( !pTemp ) return; + + VectorCopy( velocity, pTemp->entity.baseline.origin ); + + if( pmodel->type == mod_sprite ) + { + SetBits( pTemp->flags, FTENT_SPRANIMATE ); + + if( pTemp->frameMax < 10 ) + { + SetBits( pTemp->flags, FTENT_SPRANIMATE|FTENT_SPRANIMATELOOP ); + pTemp->entity.curstate.framerate = 10; + } + else + { + pTemp->entity.curstate.framerate = pTemp->frameMax / life; + } + } + else + { + pTemp->frameMax = 0; + VectorNormalize2( velocity, dir ); + VectorAngles( dir, pTemp->entity.angles ); + } + + pTemp->flags |= FTENT_COLLIDEALL|FTENT_PERSIST|FTENT_COLLIDEKILL; + pTemp->clientIndex = bound( 1, owner, cl.maxclients ); + pTemp->entity.baseline.renderamt = 255; + pTemp->hitcallback = hitcallback; + pTemp->die = cl.time + life; +} + +/* +============== +R_TempSphereModel + +Spherical shower of models, picks from set +============== +*/ +void R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex ) +{ + TEMPENTITY *pTemp; + int i; + + // create temp models + for( i = 0; i < count; i++ ) + { + pTemp = CL_TempEntAlloc( pos, CL_ModelHandle( modelIndex )); + if( !pTemp ) return; + + pTemp->entity.curstate.body = COM_RandomLong( 0, pTemp->frameMax ); + + if( COM_RandomLong( 0, 255 ) < 10 ) + pTemp->flags |= FTENT_SLOWGRAVITY; + else pTemp->flags |= FTENT_GRAVITY; + + if( COM_RandomLong( 0, 255 ) < 200 ) + { + pTemp->flags |= FTENT_ROTATE; + pTemp->entity.baseline.angles[0] = COM_RandomFloat( -256.0f, -255.0f ); + pTemp->entity.baseline.angles[1] = COM_RandomFloat( -256.0f, -255.0f ); + pTemp->entity.baseline.angles[2] = COM_RandomFloat( -256.0f, -255.0f ); + } + + if( COM_RandomLong( 0, 255 ) < 100 ) + pTemp->flags |= FTENT_SMOKETRAIL; + + pTemp->flags |= FTENT_FLICKER | FTENT_COLLIDEWORLD; + pTemp->entity.curstate.rendermode = kRenderNormal; + pTemp->entity.curstate.effects = i & 31; + pTemp->entity.baseline.origin[0] = COM_RandomFloat( -1.0f, 1.0f ); + pTemp->entity.baseline.origin[1] = COM_RandomFloat( -1.0f, 1.0f ); + pTemp->entity.baseline.origin[2] = COM_RandomFloat( -1.0f, 1.0f ); + + VectorNormalize( pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.baseline.origin, speed, pTemp->entity.baseline.origin ); + pTemp->die = cl.time + life; + } +} + +/* +============== +R_Explosion + +Create an explosion (scale is magnitude) +============== +*/ +void R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags ) +{ + sound_t hSound; + + if( scale != 0.0f ) + { + // create explosion sprite + R_Sprite_Explode( R_DefaultSprite( pos, model, framerate ), scale, flags ); + + if( !FBitSet( flags, TE_EXPLFLAG_NOPARTICLES )) + R_FlickerParticles( pos ); + + if( !FBitSet( flags, TE_EXPLFLAG_NODLIGHTS )) + { + dlight_t *dl; + + // big flash + dl = CL_AllocDlight( 0 ); + VectorCopy( pos, dl->origin ); + dl->radius = 200; + dl->color.r = 250; + dl->color.g = 250; + dl->color.b = 150; + dl->die = cl.time + 0.01f; + dl->decay = 80; + + // red glow + dl = CL_AllocDlight( 0 ); + VectorCopy( pos, dl->origin ); + dl->radius = 150; + dl->color.r = 255; + dl->color.g = 190; + dl->color.b = 40; + dl->die = cl.time + 1.0f; + dl->decay = 200; + } + } + + if( !FBitSet( flags, TE_EXPLFLAG_NOSOUND )) + { + hSound = S_RegisterSound( va( "%s", cl_explode_sounds[COM_RandomLong( 0, 2 )] )); + S_StartSound( pos, 0, CHAN_STATIC, hSound, VOL_NORM, 0.3f, PITCH_NORM, 0 ); + } +} + +/* +============== +R_PlayerSprites + +Create a particle smoke around player +============== +*/ +void R_PlayerSprites( int client, int modelIndex, int count, int size ) +{ + TEMPENTITY *pTemp; + cl_entity_t *pEnt; + vec3_t position; + vec3_t dir; + float vel; + int i; + + pEnt = CL_GetEntityByIndex( client ); + + if( !pEnt || !pEnt->player ) + { + MsgDev( D_INFO, "Bad ent %i in R_PlayerSprites()!\n", client ); + return; + } + + vel = 128; + + for( i = 0; i < count; i++ ) + { + VectorCopy( pEnt->origin, position ); + position[0] += COM_RandomFloat( -10.0f, 10.0f ); + position[1] += COM_RandomFloat( -10.0f, 10.0f ); + position[2] += COM_RandomFloat( -20.0f, 36.0f ); + + pTemp = CL_TempEntAlloc( position, CL_ModelHandle( modelIndex )); + if( !pTemp ) return; + + VectorSubtract( pTemp->entity.origin, pEnt->origin, pTemp->tentOffset ); + + if ( i != 0 ) + { + pTemp->flags |= FTENT_PLYRATTACHMENT; + pTemp->clientIndex = client; + } + else + { + VectorSubtract( position, pEnt->origin, dir ); + VectorNormalize( dir ); + VectorScale( dir, 60, dir ); + VectorCopy( dir, pTemp->entity.baseline.origin ); + pTemp->entity.baseline.origin[1] = COM_RandomFloat( 20.0f, 60.0f ); + } + + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.curstate.framerate = COM_RandomFloat( 1.0f - (size / 100.0f ), 1.0f ); + + if( pTemp->frameMax > 1 ) + { + pTemp->flags |= FTENT_SPRANIMATE; + pTemp->entity.curstate.framerate = 20.0f; + pTemp->die = cl.time + (pTemp->frameMax * 0.05f); + } + else + { + pTemp->die = cl.time + 0.35f; + } + } +} + +/* +============== +R_FireField + +Makes a field of fire +============== +*/ +void R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life ) +{ + TEMPENTITY *pTemp; + model_t *pmodel; + float time; + vec3_t pos; + int i; + + if(( pmodel = CL_ModelHandle( modelIndex )) == NULL ) + return; + + for( i = 0; i < count; i++ ) + { + VectorCopy( org, pos ); + pos[0] += COM_RandomFloat( -radius, radius ); + pos[1] += COM_RandomFloat( -radius, radius ); + + if( !FBitSet( flags, TEFIRE_FLAG_PLANAR )) + pos[2] += COM_RandomFloat( -radius, radius ); + + pTemp = CL_TempEntAlloc( pos, pmodel ); + if( !pTemp ) return; + + if( FBitSet( flags, TEFIRE_FLAG_ALPHA )) + { + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 128; + } + else if( FBitSet( flags, TEFIRE_FLAG_ADDITIVE )) + { + pTemp->entity.curstate.rendermode = kRenderTransAdd; + pTemp->entity.curstate.renderamt = 80; + } + else + { + pTemp->entity.curstate.rendermode = kRenderNormal; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.baseline.renderamt = pTemp->entity.curstate.renderamt = 255; + } + + pTemp->entity.curstate.framerate = COM_RandomFloat( 0.75f, 1.25f ); + time = life + COM_RandomFloat( -0.25f, 0.5f ); + pTemp->die = cl.time + time; + + if( pTemp->frameMax > 1 ) + { + pTemp->flags |= FTENT_SPRANIMATE; + + if( FBitSet( flags, TEFIRE_FLAG_LOOP )) + { + pTemp->entity.curstate.framerate = 15.0f; + pTemp->flags |= FTENT_SPRANIMATELOOP; + } + else + { + pTemp->entity.curstate.framerate = pTemp->frameMax / time; + } + } + + if( FBitSet( flags, TEFIRE_FLAG_ALLFLOAT ) || ( FBitSet( flags, TEFIRE_FLAG_SOMEFLOAT ) && !COM_RandomLong( 0, 1 ))) + { + // drift sprite upward + pTemp->entity.baseline.origin[2] = COM_RandomFloat( 10.0f, 30.0f ); + } + } +} + +/* +============== +R_MultiGunshot + +Client version of shotgun shot +============== +*/ +void R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int count, int decalCount, int *decalIndices ) +{ + pmtrace_t trace; + vec3_t right, up; + vec3_t vecSrc, vecDir, vecEnd; + int i, j, decalIndex; + + VectorVectors( dir, right, up ); + VectorCopy( org, vecSrc ); + + for( i = 0; i < count; i++ ) + { + // get circular gaussian spread + float x, y, z; + do { + x = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f ); + y = COM_RandomFloat( -0.5f, 0.5f ) + COM_RandomFloat( -0.5f, 0.5f ); + z = x * x + y * y; + } while( z > 1.0f ); + + for( j = 0; j < 3; j++ ) + { + vecDir[j] = dir[j] + x * noise[0] * right[j] + y * noise[1] * up[j]; + vecEnd[j] = vecSrc[j] + 4096.0f * vecDir[j]; + } + + trace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE ); + + // paint decals + if( trace.fraction != 1.0f ) + { + physent_t *pe = NULL; + + if( i & 2 ) R_RicochetSound( trace.endpos ); + R_BulletImpactParticles( trace.endpos ); + + if( trace.ent >= 0 && trace.ent < clgame.pmove->numphysent ) + pe = &clgame.pmove->physents[trace.ent]; + + if( pe && ( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP )) + { + cl_entity_t *e = CL_GetEntityByIndex( pe->info ); + decalIndex = CL_DecalIndex( decalIndices[COM_RandomLong( 0, decalCount-1 )] ); + CL_DecalShoot( decalIndex, e->index, 0, trace.endpos, 0 ); + } + } + } +} + +/* +============== +R_Sprite_WallPuff + +Create a wallpuff +============== +*/ +void R_Sprite_WallPuff( TEMPENTITY *pTemp, float scale ) +{ + if( !pTemp ) return; + + pTemp->entity.curstate.renderamt = 255; + pTemp->entity.curstate.rendermode = kRenderTransAlpha; + pTemp->entity.angles[ROLL] = COM_RandomLong( 0, 359 ); + pTemp->entity.baseline.origin[2] = 30; + pTemp->entity.curstate.scale = scale; + pTemp->die = cl.time + 0.01f; +} + +/* +============== +CL_ParseTempEntity + +handle temp-entity messages +============== +*/ +void CL_ParseTempEntity( sizebuf_t *msg ) +{ + sizebuf_t buf; + byte pbuf[256]; + int iSize = MSG_ReadWord( msg ); + int type, color, count, flags; + int decalIndex, modelIndex, entityIndex; + float scale, life, frameRate, vel, random; + float brightness, r, g, b; + vec3_t pos, pos2, ang; + int decalIndices[1]; // just stub + TEMPENTITY *pTemp; + cl_entity_t *pEnt; + dlight_t *dl; + + decalIndex = modelIndex = entityIndex = 0; + + // parse user message into buffer + MSG_ReadBytes( msg, pbuf, iSize ); + + // init a safe tempbuffer + MSG_Init( &buf, "TempEntity", pbuf, iSize ); + + type = MSG_ReadByte( &buf ); + + switch( type ) + { + case TE_BEAMPOINTS: + case TE_BEAMENTPOINT: + case TE_LIGHTNING: + case TE_BEAMENTS: + case TE_BEAM: + case TE_BEAMSPRITE: + case TE_BEAMTORUS: + case TE_BEAMDISK: + case TE_BEAMCYLINDER: + case TE_BEAMFOLLOW: + case TE_BEAMRING: + case TE_BEAMHOSE: + case TE_KILLBEAM: + CL_ParseViewBeam( &buf, type ); + break; + case TE_GUNSHOT: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + R_RicochetSound( pos ); + R_RunParticleEffect( pos, vec3_origin, 0, 20 ); + break; + case TE_EXPLOSION: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + frameRate = MSG_ReadByte( &buf ); + flags = MSG_ReadByte( &buf ); + R_Explosion( pos, modelIndex, scale, frameRate, flags ); + break; + case TE_TAREXPLOSION: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + R_BlobExplosion( pos ); + break; + case TE_SMOKE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + frameRate = MSG_ReadByte( &buf ); + pTemp = R_DefaultSprite( pos, modelIndex, frameRate ); + R_Sprite_Smoke( pTemp, scale ); + break; + case TE_TRACER: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + R_TracerEffect( pos, pos2 ); + break; + case TE_SPARKS: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + R_SparkShower( pos ); + break; + case TE_LAVASPLASH: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + R_LavaSplash( pos ); + break; + case TE_TELEPORT: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + R_TeleportSplash( pos ); + break; + case TE_EXPLOSION2: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + color = MSG_ReadByte( &buf ); + count = MSG_ReadByte( &buf ); + R_ParticleExplosion2( pos, color, count ); + break; + case TE_BSPDECAL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + decalIndex = MSG_ReadShort( &buf ); + entityIndex = MSG_ReadShort( &buf ); + if( entityIndex ) modelIndex = MSG_ReadShort( &buf ); + CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, FDECAL_PERMANENT ); + break; + case TE_IMPLOSION: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + scale = MSG_ReadByte( &buf ); + count = MSG_ReadByte( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_Implosion( pos, scale, count, life ); + break; + case TE_SPRITETRAIL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + vel = (float)MSG_ReadByte( &buf ); + random = (float)MSG_ReadByte( &buf ); + R_Sprite_Trail( type, pos, pos2, modelIndex, count, life, scale, random, 255, vel ); + break; + case TE_SPRITE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + brightness = (float)MSG_ReadByte( &buf ); + + if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) + { + pTemp->entity.curstate.scale = scale; + pTemp->entity.baseline.renderamt = brightness; + pTemp->entity.curstate.renderamt = brightness; + pTemp->entity.curstate.rendermode = kRenderTransAdd; + } + break; + case TE_GLOWSPRITE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + brightness = (float)MSG_ReadByte( &buf ); + + if(( pTemp = R_DefaultSprite( pos, modelIndex, 0 )) != NULL ) + { + pTemp->entity.curstate.scale = scale; + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderfx = kRenderFxNoDissipation; + pTemp->entity.baseline.renderamt = brightness; + pTemp->entity.curstate.renderamt = brightness; + pTemp->flags = FTENT_FADEOUT; + pTemp->die = cl.time + life; + } + break; + case TE_STREAK_SPLASH: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + color = MSG_ReadByte( &buf ); + count = MSG_ReadShort( &buf ); + vel = (float)MSG_ReadShort( &buf ); + random = (float)MSG_ReadShort( &buf ); + R_StreakSplash( pos, pos2, color, count, vel, -random, random ); + break; + case TE_DLIGHT: + dl = CL_AllocDlight( 0 ); + dl->origin[0] = MSG_ReadCoord( &buf ); + dl->origin[1] = MSG_ReadCoord( &buf ); + dl->origin[2] = MSG_ReadCoord( &buf ); + dl->radius = (float)(MSG_ReadByte( &buf ) * 10.0f); + dl->color.r = MSG_ReadByte( &buf ); + dl->color.g = MSG_ReadByte( &buf ); + dl->color.b = MSG_ReadByte( &buf ); + dl->die = cl.time + (float)(MSG_ReadByte( &buf ) * 0.1f); + dl->decay = (float)(MSG_ReadByte( &buf ) * 10.0f); + break; + case TE_ELIGHT: + dl = CL_AllocElight( MSG_ReadShort( &buf )); + dl->origin[0] = MSG_ReadCoord( &buf ); + dl->origin[1] = MSG_ReadCoord( &buf ); + dl->origin[2] = MSG_ReadCoord( &buf ); + dl->radius = MSG_ReadCoord( &buf ); + dl->color.r = MSG_ReadByte( &buf ); + dl->color.g = MSG_ReadByte( &buf ); + dl->color.b = MSG_ReadByte( &buf ); + life = (float)MSG_ReadByte( &buf ) * 0.1f; + dl->die = cl.time + life; + dl->decay = MSG_ReadCoord( &buf ); + if( life != 0 ) dl->decay /= life; + break; + case TE_TEXTMESSAGE: + CL_ParseTextMessage( &buf ); + break; + case TE_LINE: + case TE_BOX: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + life = (float)(MSG_ReadShort( &buf ) * 0.1f); + r = MSG_ReadByte( &buf ); + g = MSG_ReadByte( &buf ); + b = MSG_ReadByte( &buf ); + if( type == TE_LINE ) R_ParticleLine( pos, pos2, r, g, b, life ); + else R_ParticleBox( pos, pos2, r, g, b, life ); + break; + case TE_LARGEFUNNEL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + flags = MSG_ReadShort( &buf ); + R_LargeFunnel( pos, flags ); + R_FunnelSprite( pos, modelIndex, flags ); + break; + case TE_BLOODSTREAM: + case TE_BLOOD: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + color = MSG_ReadByte( &buf ); + count = MSG_ReadByte( &buf ); + if( type == TE_BLOOD ) R_Blood( pos, pos2, color, count ); + else R_BloodStream( pos, pos2, color, count ); + break; + case TE_SHOWLINE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + R_ShowLine( pos, pos2 ); + break; + case TE_DECAL: + case TE_DECALHIGH: + case TE_WORLDDECAL: + case TE_WORLDDECALHIGH: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + decalIndex = MSG_ReadByte( &buf ); + if( type == TE_DECAL || type == TE_DECALHIGH ) + entityIndex = MSG_ReadShort( &buf ); + else entityIndex = 0; + if( type == TE_DECALHIGH || type == TE_WORLDDECALHIGH ) + decalIndex += 256; + pEnt = CL_GetEntityByIndex( entityIndex ); + if( pEnt ) modelIndex = pEnt->curstate.modelindex; + CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, modelIndex, pos, 0 ); + break; + case TE_FIZZ: + entityIndex = MSG_ReadShort( &buf ); + modelIndex = MSG_ReadShort( &buf ); + scale = MSG_ReadByte( &buf ); // same as density + pEnt = CL_GetEntityByIndex( entityIndex ); + R_FizzEffect( pEnt, modelIndex, scale ); + break; + case TE_MODEL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + ang[0] = 0.0f; + ang[1] = MSG_ReadAngle( &buf ); // yaw angle + ang[2] = 0.0f; + modelIndex = MSG_ReadShort( &buf ); + flags = MSG_ReadByte( &buf ); // sound flags + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_TempModel( pos, pos2, ang, life, modelIndex, flags ); + break; + case TE_EXPLODEMODEL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + vel = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadShort( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_TempSphereModel( pos, vel, life, count, modelIndex ); + break; + case TE_BREAKMODEL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + ang[0] = MSG_ReadCoord( &buf ); + ang[1] = MSG_ReadCoord( &buf ); + ang[2] = MSG_ReadCoord( &buf ); + random = (float)MSG_ReadByte( &buf ) * 10.0f; + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + flags = MSG_ReadByte( &buf ); + R_BreakModel( pos, pos2, ang, random, life, count, modelIndex, (char)flags ); + break; + case TE_GUNSHOTDECAL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + entityIndex = MSG_ReadShort( &buf ); + decalIndex = MSG_ReadByte( &buf ); + pEnt = CL_GetEntityByIndex( entityIndex ); + CL_DecalShoot( CL_DecalIndex( decalIndex ), entityIndex, 0, pos, 0 ); + R_BulletImpactParticles( pos ); + R_RicochetSound( pos ); + break; + case TE_SPRAY: + case TE_SPRITE_SPRAY: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + vel = (float)MSG_ReadByte( &buf ); + random = (float)MSG_ReadByte( &buf ); + if( type == TE_SPRAY ) + { + flags = MSG_ReadByte( &buf ); // rendermode + R_Spray( pos, pos2, modelIndex, count, vel, random, flags ); + } + else R_Sprite_Spray( pos, pos2, modelIndex, count, vel * 2.0f, random ); + break; + case TE_ARMOR_RICOCHET: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_RicochetSprite( pos, cl_sprite_ricochet, 0.1f, scale ); + R_RicochetSound( pos ); + break; + case TE_PLAYERDECAL: + color = MSG_ReadByte( &buf ) - 1; // playernum + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + entityIndex = MSG_ReadShort( &buf ); + decalIndex = MSG_ReadByte( &buf ); + CL_PlayerDecal( color, decalIndex, entityIndex, pos ); + break; + case TE_BUBBLES: + case TE_BUBBLETRAIL: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + scale = MSG_ReadCoord( &buf ); // water height + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + vel = MSG_ReadCoord( &buf ); + if( type == TE_BUBBLES ) R_Bubbles( pos, pos2, scale, modelIndex, count, vel ); + else R_BubbleTrail( pos, pos2, scale, modelIndex, count, vel ); + break; + case TE_BLOODSPRITE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); // sprite #1 + decalIndex = MSG_ReadShort( &buf ); // sprite #2 + color = MSG_ReadByte( &buf ); + scale = (float)MSG_ReadByte( &buf ); + R_BloodSprite( pos, color, modelIndex, decalIndex, scale ); + break; + case TE_PROJECTILE: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + modelIndex = MSG_ReadShort( &buf ); + life = MSG_ReadByte( &buf ); + color = MSG_ReadByte( &buf ); // playernum + R_Projectile( pos, pos2, modelIndex, life, color, NULL ); + break; + case TE_PLAYERSPRITES: + color = MSG_ReadShort( &buf ); // entitynum + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + random = (float)MSG_ReadByte( &buf ); + R_PlayerSprites( color, modelIndex, count, random ); + break; + case TE_PARTICLEBURST: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + scale = (float)MSG_ReadShort( &buf ); + color = MSG_ReadByte( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_ParticleBurst( pos, scale, color, life ); + break; + case TE_FIREFIELD: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + scale = (float)MSG_ReadShort( &buf ); + modelIndex = MSG_ReadShort( &buf ); + count = MSG_ReadByte( &buf ); + flags = MSG_ReadByte( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_FireField( pos, scale, modelIndex, count, flags, life ); + break; + case TE_PLAYERATTACHMENT: + color = MSG_ReadByte( &buf ); // playernum + scale = MSG_ReadCoord( &buf ); // height + modelIndex = MSG_ReadShort( &buf ); + life = (float)(MSG_ReadShort( &buf ) * 0.1f); + R_AttachTentToPlayer( color, modelIndex, scale, life ); + break; + case TE_KILLPLAYERATTACHMENTS: + color = MSG_ReadByte( &buf ); // playernum + R_KillAttachedTents( color ); + break; + case TE_MULTIGUNSHOT: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ) * 0.1f; + pos2[1] = MSG_ReadCoord( &buf ) * 0.1f; + pos2[2] = MSG_ReadCoord( &buf ) * 0.1f; + ang[0] = MSG_ReadCoord( &buf ) * 0.01f; + ang[1] = MSG_ReadCoord( &buf ) * 0.01f; + ang[2] = 0.0f; + count = MSG_ReadByte( &buf ); + decalIndices[0] = MSG_ReadByte( &buf ); + R_MultiGunshot( pos, pos2, ang, count, 1, decalIndices ); + break; + case TE_USERTRACER: + pos[0] = MSG_ReadCoord( &buf ); + pos[1] = MSG_ReadCoord( &buf ); + pos[2] = MSG_ReadCoord( &buf ); + pos2[0] = MSG_ReadCoord( &buf ); + pos2[1] = MSG_ReadCoord( &buf ); + pos2[2] = MSG_ReadCoord( &buf ); + life = (float)(MSG_ReadByte( &buf ) * 0.1f); + color = MSG_ReadByte( &buf ); + scale = (float)(MSG_ReadByte( &buf ) * 0.1f); + R_UserTracerParticle( pos, pos2, life, color, scale, 0, NULL ); + break; + default: + MsgDev( D_ERROR, "ParseTempEntity: illegible TE message %i\n", type ); + break; + } + + // throw warning + if( MSG_CheckOverflow( &buf )) MsgDev( D_WARN, "ParseTempEntity: overflow TE message\n" ); +} + + +/* +============================================================== + +LIGHT STYLE MANAGEMENT + +============================================================== +*/ +#define STYLE_LERPING_THRESHOLD 3.0f // because we wan't interpolate fast sequences (like on\off) + +/* +================ +CL_ClearLightStyles +================ +*/ +void CL_ClearLightStyles( void ) +{ + memset( cl.lightstyles, 0, sizeof( cl.lightstyles )); +} + +void CL_SetLightstyle( int style, const char *s, float f ) +{ + int i, k; + lightstyle_t *ls; + float val1, val2; + + Assert( s != NULL ); + Assert( style >= 0 && style < MAX_LIGHTSTYLES ); + + ls = &cl.lightstyles[style]; + + Q_strncpy( ls->pattern, s, sizeof( ls->pattern )); + + ls->length = Q_strlen( s ); + ls->time = f; // set local time + + for( i = 0; i < ls->length; i++ ) + ls->map[i] = (float)(s[i] - 'a'); + + ls->interp = (ls->length <= 1) ? false : true; + + // check for allow interpolate + // NOTE: fast flickering styles looks ugly when interpolation is running + for( k = 0; k < (ls->length - 1); k++ ) + { + val1 = ls->map[(k+0) % ls->length]; + val2 = ls->map[(k+1) % ls->length]; + + if( fabs( val1 - val2 ) > STYLE_LERPING_THRESHOLD ) + { + ls->interp = false; + break; + } + } + MsgDev( D_REPORT, "Lightstyle %i (%s), interp %s\n", style, ls->pattern, ls->interp ? "Yes" : "No" ); +} + +/* +============================================================== + +DLIGHT MANAGEMENT + +============================================================== +*/ +dlight_t cl_dlights[MAX_DLIGHTS]; +dlight_t cl_elights[MAX_ELIGHTS]; + +/* +================ +CL_ClearDlights +================ +*/ +void CL_ClearDlights( void ) +{ + memset( cl_dlights, 0, sizeof( cl_dlights )); + memset( cl_elights, 0, sizeof( cl_elights )); +} + +/* +=============== +CL_AllocDlight + +=============== +*/ +dlight_t *CL_AllocDlight( int key ) +{ + dlight_t *dl; + int i; + + // first look for an exact key match + if( key ) + { + for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ ) + { + if( dl->key == key ) + { + // reuse this light + memset( dl, 0, sizeof( *dl )); + dl->key = key; + return dl; + } + } + } + + // then look for anything else + for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ ) + { + if( dl->die < cl.time && dl->key == 0 ) + { + memset( dl, 0, sizeof( *dl )); + dl->key = key; + return dl; + } + } + + // otherwise grab first dlight + dl = &cl_dlights[0]; + memset( dl, 0, sizeof( *dl )); + dl->key = key; + + return dl; +} + +/* +=============== +CL_AllocElight + +=============== +*/ +dlight_t *CL_AllocElight( int key ) +{ + dlight_t *dl; + int i; + + // first look for an exact key match + if( key ) + { + for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ ) + { + if( dl->key == key ) + { + // reuse this light + memset( dl, 0, sizeof( *dl )); + dl->key = key; + return dl; + } + } + } + + // then look for anything else + for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ ) + { + if( dl->die < cl.time && dl->key == 0 ) + { + memset( dl, 0, sizeof( *dl )); + dl->key = key; + return dl; + } + } + + // otherwise grab first dlight + dl = &cl_elights[0]; + memset( dl, 0, sizeof( *dl )); + dl->key = key; + + return dl; +} + +/* +=============== +CL_DecayLights + +=============== +*/ +void CL_DecayLights( void ) +{ + dlight_t *dl; + float time; + int i; + + time = cl.time - cl.oldtime; + + for( i = 0, dl = cl_dlights; i < MAX_DLIGHTS; i++, dl++ ) + { + if( !dl->radius ) continue; + + dl->radius -= time * dl->decay; + if( dl->radius < 0 ) dl->radius = 0; + + if( dl->die < cl.time || !dl->radius ) + memset( dl, 0, sizeof( *dl )); + } + + for( i = 0, dl = cl_elights; i < MAX_ELIGHTS; i++, dl++ ) + { + if( !dl->radius ) continue; + + dl->radius -= time * dl->decay; + if( dl->radius < 0 ) dl->radius = 0; + + if( dl->die < cl.time || !dl->radius ) + memset( dl, 0, sizeof( *dl )); + } +} + +/* +================ +CL_UpdateFlashlight + +update client flashlight +================ +*/ +void CL_UpdateFlashlight( cl_entity_t *ent ) +{ + vec3_t forward, view_ofs; + vec3_t vecSrc, vecEnd; + float falloff; + pmtrace_t *trace; + cl_entity_t *hit; + dlight_t *dl; + + if( ent->index == ( cl.playernum + 1 )) + { + // local player case + AngleVectors( cl.viewangles, forward, NULL, NULL ); + VectorCopy( cl.viewheight, view_ofs ); + } + else // non-local player case + { + vec3_t v_angle; + + // NOTE: pitch divided by 3.0 twice. So we need apply 3^2 = 9 + v_angle[PITCH] = ent->curstate.angles[PITCH] * 9.0f; + v_angle[YAW] = ent->angles[YAW]; + v_angle[ROLL] = 0.0f; // roll not used + + AngleVectors( v_angle, forward, NULL, NULL ); + view_ofs[0] = view_ofs[1] = 0.0f; + + // FIXME: these values are hardcoded ... + if( ent->curstate.usehull == 1 ) + view_ofs[2] = 12.0f; // VEC_DUCK_VIEW; + else view_ofs[2] = 28.0f; // DEFAULT_VIEWHEIGHT + } + + VectorAdd( ent->origin, view_ofs, vecSrc ); + VectorMA( vecSrc, FLASHLIGHT_DISTANCE, forward, vecEnd ); + + trace = CL_VisTraceLine( vecSrc, vecEnd, PM_STUDIO_BOX ); + + // update flashlight endpos + dl = CL_AllocDlight( ent->index ); +#if 1 + hit = CL_GetEntityByIndex( clgame.pmove->visents[trace->ent].info ); + if( hit && hit->model && ( hit->model->type == mod_alias || hit->model->type == mod_studio )) + VectorCopy( hit->origin, dl->origin ); + else VectorCopy( trace->endpos, dl->origin ); +#else + VectorCopy( trace->endpos, dl->origin ); +#endif + // compute falloff + falloff = trace->fraction * FLASHLIGHT_DISTANCE; + if( falloff < 500.0f ) falloff = 1.0f; + else falloff = 500.0f / falloff; + falloff *= falloff; + + // apply brigthness to dlight + dl->color.r = bound( 0, falloff * 255, 255 ); + dl->color.g = bound( 0, falloff * 255, 255 ); + dl->color.b = bound( 0, falloff * 255, 255 ); + dl->die = cl.time + 0.01f; // die on next frame + dl->radius = 80; +} + +/* +================ +CL_AddEntityEffects + +apply various effects to entity origin or attachment +================ +*/ +void CL_AddEntityEffects( cl_entity_t *ent ) +{ + // yellow flies effect 'monster stuck in the wall' + if( FBitSet( ent->curstate.effects, EF_BRIGHTFIELD )) + R_EntityParticles( ent ); + + if( FBitSet( ent->curstate.effects, EF_DIMLIGHT )) + { + if( ent->player && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + CL_UpdateFlashlight( ent ); + } + else + { + dlight_t *dl = CL_AllocDlight( ent->index ); + dl->color.r = dl->color.g = dl->color.b = 100; + dl->radius = COM_RandomFloat( 200, 231 ); + VectorCopy( ent->origin, dl->origin ); + dl->die = cl.time + 0.001; + } + } + + if( FBitSet( ent->curstate.effects, EF_BRIGHTLIGHT )) + { + dlight_t *dl = CL_AllocDlight( ent->index ); + dl->color.r = dl->color.g = dl->color.b = 250; + if( ent->player ) dl->radius = 400; // don't flickering + else dl->radius = COM_RandomFloat( 400, 431 ); + VectorCopy( ent->origin, dl->origin ); + dl->die = cl.time + 0.001; + dl->origin[2] += 16.0f; + } + + // add light effect + if( FBitSet( ent->curstate.effects, EF_LIGHT )) + { + dlight_t *dl = CL_AllocDlight( ent->index ); + dl->color.r = dl->color.g = dl->color.b = 100; + VectorCopy( ent->origin, dl->origin ); + R_RocketFlare( ent->origin ); + dl->die = cl.time + 0.001; + dl->radius = 200; + } +} + +/* +================ +CL_AddModelEffects + +these effects will be enable by flag in model header +================ +*/ +void CL_AddModelEffects( cl_entity_t *ent ) +{ + vec3_t oldorigin; + + if( !ent->model ) return; + + switch( ent->model->type ) + { + case mod_alias: + case mod_studio: + break; + default: return; + } + + VectorCopy( ent->prevstate.origin, oldorigin ); + + // NOTE: this completely over control about angles and don't broke interpolation + if( FBitSet( ent->model->flags, STUDIO_ROTATE )) + ent->angles[1] = anglemod( 100.0f * cl.time ); + + if( FBitSet( ent->model->flags, STUDIO_GIB )) + R_RocketTrail( oldorigin, ent->curstate.origin, 2 ); + + if( FBitSet( ent->model->flags, STUDIO_ZOMGIB )) + R_RocketTrail( oldorigin, ent->curstate.origin, 4 ); + + if( FBitSet( ent->model->flags, STUDIO_TRACER )) + R_RocketTrail( oldorigin, ent->curstate.origin, 3 ); + + if( FBitSet( ent->model->flags, STUDIO_TRACER2 )) + R_RocketTrail( oldorigin, ent->curstate.origin, 5 ); + + if( FBitSet( ent->model->flags, STUDIO_ROCKET )) + { + dlight_t *dl = CL_AllocDlight( ent->index ); + + dl->color.r = dl->color.g = dl->color.b = 200; + VectorCopy( ent->origin, dl->origin ); + + // XASH SPECIFIC: get radius from head entity + if( ent->curstate.rendermode != kRenderNormal ) + dl->radius = Q_max( 0, ent->curstate.renderamt - 55 ); + else dl->radius = 200; + + dl->die = cl.time + 0.01f; + + R_RocketTrail( oldorigin, ent->curstate.origin, 0 ); + } + + if( FBitSet( ent->model->flags, STUDIO_GRENADE )) + R_RocketTrail( oldorigin, ent->curstate.origin, 1 ); + + if( FBitSet( ent->model->flags, STUDIO_TRACER3 )) + R_RocketTrail( oldorigin, ent->curstate.origin, 6 ); +} + +/* +================ +CL_TestLights + +if cl_testlights is set, create 32 lights models +================ +*/ +void CL_TestLights( void ) +{ + int i, j; + float f, r; + int numLights; + dlight_t *dl; + + if( !cl_testlights->value ) return; + + numLights = bound( 1, cl_testlights->value, MAX_DLIGHTS ); + + for( i = 0; i < numLights; i++ ) + { + dl = &cl_dlights[i]; + + r = 64 * ((i % 4) - 1.5f ); + f = 64 * ( i / 4) + 128; + + for( j = 0; j < 3; j++ ) + dl->origin[j] = RI.vieworg[j] + RI.vforward[j] * f + RI.vright[j] * r; + + dl->color.r = ((((i % 6) + 1) & 1)>>0) * 255; + dl->color.g = ((((i % 6) + 1) & 2)>>1) * 255; + dl->color.b = ((((i % 6) + 1) & 4)>>2) * 255; + dl->radius = Q_max( 64, 200 - 5 * numLights ); + dl->die = cl.time + host.frametime; + } +} + +/* +============================================================== + +DECAL MANAGEMENT + +============================================================== +*/ +/* +=============== +CL_DecalShoot + +normal temporary decal +=============== +*/ +void CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags ) +{ + R_DecalShoot( textureIndex, entityIndex, modelIndex, pos, flags, 1.0f ); +} + +/* +=============== +CL_FireCustomDecal + +custom temporary decal +=============== +*/ +void CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale ) +{ + R_DecalShoot( textureIndex, entityIndex, modelIndex, pos, flags, scale ); +} + +/* +=============== +CL_PlayerDecal + +spray custom colored decal (clan logo etc) +=============== +*/ +void CL_PlayerDecal( int playernum, int customIndex, int entityIndex, float *pos ) +{ + int textureIndex = 0; + customization_t *pCust = NULL; + + if( playernum < MAX_CLIENTS ) + pCust = cl.players[playernum].customdata.pNext; + + if( pCust != NULL && pCust->pBuffer != NULL && pCust->pInfo != NULL ) + { + if( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal && pCust->bTranslated ) + { + if( !pCust->nUserData1 && pCust->pInfo != NULL ) + { + const char *decalname = va( "player%dlogo%d", playernum, customIndex ); + pCust->nUserData1 = GL_LoadTextureInternal( decalname, pCust->pInfo, TF_DECAL, false ); + } + textureIndex = pCust->nUserData1; + } + } + + R_DecalShoot( textureIndex, entityIndex, 0, pos, FDECAL_CUSTOM, 1.0f ); +} + +/* +=============== +CL_DecalIndexFromName + +get decal global index from decalname +=============== +*/ +int CL_DecalIndexFromName( const char *name ) +{ + int i; + + if( !COM_CheckString( name )) + return 0; + + // look through the loaded sprite name list for SpriteName + for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) + { + if( !Q_stricmp( name, host.draw_decals[i] )) + return i; + } + return 0; // invalid decal +} + +/* +=============== +CL_DecalIndex + +get texture index from decal global index +=============== +*/ +int CL_DecalIndex( int id ) +{ + id = bound( 0, id, MAX_DECALS - 1 ); + + if( cl.decal_index[id] == 0 ) + { + Image_SetForceFlags( IL_LOAD_DECAL ); + cl.decal_index[id] = GL_LoadTexture( host.draw_decals[id], NULL, 0, TF_DECAL, NULL ); + Image_ClearForceFlags(); + } + + return cl.decal_index[id]; +} + +/* +=============== +CL_DecalRemoveAll + +remove all decals with specified texture +=============== +*/ +void CL_DecalRemoveAll( int textureIndex ) +{ + int id = bound( 0, textureIndex, MAX_DECALS - 1 ); + R_DecalRemoveAll( cl.decal_index[id] ); +} + +/* +============================================================== + +EFRAGS MANAGEMENT + +============================================================== +*/ +efrag_t cl_efrags[MAX_EFRAGS]; + +/* +============== +CL_ClearEfrags +============== +*/ +void CL_ClearEfrags( void ) +{ + int i; + + memset( cl_efrags, 0, sizeof( cl_efrags )); + + // allocate the efrags and chain together into a free list + clgame.free_efrags = cl_efrags; + for( i = 0; i < MAX_EFRAGS - 1; i++ ) + clgame.free_efrags[i].entnext = &clgame.free_efrags[i+1]; + clgame.free_efrags[i].entnext = NULL; +} + +/* +============== +CL_ClearEffects +============== +*/ +void CL_ClearEffects( void ) +{ + CL_ClearEfrags (); + CL_ClearDlights (); + CL_ClearTempEnts (); + CL_ClearViewBeams (); + CL_ClearParticles (); + CL_ClearLightStyles (); +} \ No newline at end of file diff --git a/engine/client/cl_tent.h b/engine/client/cl_tent.h new file mode 100644 index 00000000..b4c49835 --- /dev/null +++ b/engine/client/cl_tent.h @@ -0,0 +1,120 @@ +/* +cl_tent.h - efx api set +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef CL_TENT_H +#define CL_TENT_H + +// EfxAPI +struct particle_s *R_AllocParticle( void (*callback)( struct particle_s*, float )); +void R_Explosion( vec3_t pos, int model, float scale, float framerate, int flags ); +void R_ParticleExplosion( const vec3_t org ); +void R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength ); +void R_Implosion( const vec3_t end, float radius, int count, float life ); +void R_Blood( const vec3_t org, const vec3_t dir, int pcolor, int speed ); +void R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed ); +void R_BlobExplosion( const vec3_t org ); +void R_EntityParticles( cl_entity_t *ent ); +void R_FlickerParticles( const vec3_t org ); +void R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count ); +void R_ParticleBurst( const vec3_t org, int size, int color, float life ); +void R_LavaSplash( const vec3_t org ); +void R_TeleportSplash( const vec3_t org ); +void R_RocketTrail( vec3_t start, vec3_t end, int type ); +short R_LookupColor( byte r, byte g, byte b ); +void R_GetPackedColor( short *packed, short color ); +void R_TracerEffect( const vec3_t start, const vec3_t end ); +void R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( struct particle_s* )); +struct particle_s *R_TracerParticles( float *org, float *vel, float life ); +void R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life ); +void R_ParticleBox( const vec3_t mins, const vec3_t maxs, byte r, byte g, byte b, float life ); +void R_ShowLine( const vec3_t start, const vec3_t end ); +void R_BulletImpactParticles( const vec3_t pos ); +void R_SparkShower( const vec3_t org ); +struct tempent_s *CL_TempEntAlloc( const vec3_t org, model_t *pmodel ); +struct tempent_s *CL_TempEntAllocHigh( const vec3_t org, model_t *pmodel ); +struct tempent_s *CL_TempEntAllocNoModel( const vec3_t org ); +struct tempent_s *CL_TempEntAllocCustom( const vec3_t org, model_t *model, int high, void (*callback)( struct tempent_s*, float, float )); +void R_FizzEffect( cl_entity_t *pent, int modelIndex, int density ); +void R_Bubbles( const vec3_t mins, const vec3_t maxs, float height, int modelIndex, int count, float speed ); +void R_BubbleTrail( const vec3_t start, const vec3_t end, float flWaterZ, int modelIndex, int count, float speed ); +void R_AttachTentToPlayer( int client, int modelIndex, float zoffset, float life ); +void R_KillAttachedTents( int client ); +void R_RicochetSprite( const vec3_t pos, model_t *pmodel, float duration, float scale ); +void R_RocketFlare( const vec3_t pos ); +void R_MuzzleFlash( const vec3_t pos, int type ); +void R_BloodSprite( const vec3_t org, int colorIndex, int modelIndex, int modelIndex2, float size ); +void R_BreakModel( const vec3_t pos, const vec3_t size, const vec3_t dir, float random, float life, int count, int modelIndex, char flags ); +struct tempent_s *R_TempModel( const vec3_t pos, const vec3_t dir, const vec3_t angles, float life, int modelIndex, int soundtype ); +struct tempent_s *R_TempSprite( vec3_t pos, const vec3_t dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); +struct tempent_s *R_DefaultSprite( const vec3_t pos, int spriteIndex, float framerate ); +void R_Sprite_Explode( struct tempent_s *pTemp, float scale, int flags ); +void R_Sprite_Smoke( struct tempent_s *pTemp, float scale ); +void R_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int iRand, int renderMode ); +void R_Sprite_Spray( const vec3_t pos, const vec3_t dir, int modelIndex, int count, int speed, int iRand ); +void R_Sprite_Trail( int type, vec3_t vecStart, vec3_t vecEnd, int modelIndex, int nCount, float flLife, float flSize, float flAmplitude, int nRenderamt, float flSpeed ); +void R_FunnelSprite( const vec3_t pos, int spriteIndex, int flags ); +void R_LargeFunnel( const vec3_t pos, int reverse ); +void R_SparkEffect( const vec3_t pos, int count, int velocityMin, int velocityMax ); +void R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velMin, int velMax ); +void R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax ); +void R_Projectile( const vec3_t origin, const vec3_t velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s*, struct pmtrace_s* )); +void R_TempSphereModel( const vec3_t pos, float speed, float life, int count, int modelIndex ); +void R_MultiGunshot( const vec3_t org, const vec3_t dir, const vec3_t noise, int count, int decalCount, int *decalIndices ); +void R_FireField( float *org, int radius, int modelIndex, int count, int flags, float life ); +void R_PlayerSprites( int client, int modelIndex, int count, int size ); +void R_Sprite_WallPuff( struct tempent_s *pTemp, float scale ); +void R_DebugParticle( const vec3_t pos, byte r, byte g, byte b ); +void R_RicochetSound( const vec3_t pos ); +struct dlight_s *CL_AllocDlight( int key ); +struct dlight_s *CL_AllocElight( int key ); +void CL_UpdateFlashlight( cl_entity_t *pEnt ); +void CL_AddEntityEffects( cl_entity_t *ent ); +void CL_AddModelEffects( cl_entity_t *ent ); +void CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags ); +void CL_DecalRemoveAll( int textureIndex ); +int CL_DecalIndexFromName( const char *name ); +int CL_DecalIndex( int id ); + +// Beams +struct beam_s *R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); +struct beam_s *R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); +struct beam_s *R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); +struct beam_s *R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); +struct beam_s *R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); +struct beam_s *R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); +struct beam_s *R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); +void R_BeamKill( int deadEntity ); + + +// TriAPI +void TriBegin( int mode ); +void TriTexCoord2f( float u, float v ); +void TriVertex3fv( const float *v ); +void TriVertex3f( float x, float y, float z ); +int TriBoxInPVS( float *mins, float *maxs ); +void TriColor4f( float r, float g, float b, float a ); +int TriSpriteTexture( model_t *pSpriteModel, int frame ); +void TriColor4fRendermode( float r, float g, float b, float a, int rendermode ); +int TriWorldToScreen( float *world, float *screen ); +void TriColor4ub( byte r, byte g, byte b, byte a ); +void TriBrightness( float brightness ); +void TriRenderMode( int mode ); +void TriCullFace( int mode ); +void TriEnd( void ); + +extern model_t *cl_sprite_dot; +extern model_t *cl_sprite_shell; + +#endif//CL_TENT_H \ No newline at end of file diff --git a/engine/client/cl_video.c b/engine/client/cl_video.c new file mode 100644 index 00000000..55ef8975 --- /dev/null +++ b/engine/client/cl_video.c @@ -0,0 +1,306 @@ +/* +cl_video.c - avi video player +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" + +/* +================================================================= + +AVI PLAYING + +================================================================= +*/ + +static long xres, yres; +static float video_duration; +static float cin_time; +static int cin_frame; +static wavdata_t cin_audio; +static movie_state_t *cin_state; + +/* +================== +SCR_NextMovie + +Called when a demo or cinematic finishes +If the "nextmovie" cvar is set, that command will be issued +================== +*/ +qboolean SCR_NextMovie( void ) +{ + string str; + + if( cls.movienum == -1 ) + { + S_StopAllSounds( true ); + SCR_StopCinematic(); + return false; // don't play movies + } + + if( !cls.movies[cls.movienum][0] || cls.movienum == MAX_MOVIES ) + { + S_StopAllSounds( true ); + SCR_StopCinematic(); + cls.movienum = -1; + return false; + } + + Q_snprintf( str, MAX_STRING, "movie %s full\n", cls.movies[cls.movienum] ); + + Cbuf_InsertText( str ); + cls.movienum++; + + return true; +} + +void SCR_CreateStartupVids( void ) +{ + file_t *f; + + f = FS_Open( DEFAULT_VIDEOLIST_PATH, "w", false ); + if( !f ) return; + + // make standard video playlist: sierra, valve + FS_Print( f, "media/sierra.avi\n" ); + FS_Print( f, "media/valve.avi\n" ); + FS_Close( f ); +} + +void SCR_CheckStartupVids( void ) +{ + int c = 0; + char *afile, *pfile; + string token; + + if( Sys_CheckParm( "-nointro" ) || host_developer.value || cls.demonum != -1 || GameState->nextstate != STATE_RUNFRAME ) + { + // don't run movies where we in developer-mode + cls.movienum = -1; + return; + } + + if( !FS_FileExists( DEFAULT_VIDEOLIST_PATH, false )) + SCR_CreateStartupVids(); + + afile = FS_LoadFile( DEFAULT_VIDEOLIST_PATH, NULL, false ); + if( !afile ) return; // something bad happens + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + Q_strncpy( cls.movies[c], token, sizeof( cls.movies[0] )); + + if( ++c > MAX_MOVIES - 1 ) + { + Con_Printf( S_WARN "too many movies (%d) specified in %s\n", MAX_MOVIES, DEFAULT_VIDEOLIST_PATH ); + break; + } + } + + Mem_Free( afile ); + + // run cinematic + cls.movienum = 0; + SCR_NextMovie (); + Cbuf_Execute(); +} + +/* +================== +SCR_RunCinematic +================== +*/ +void SCR_RunCinematic( void ) +{ + if( cls.state != ca_cinematic ) + return; + + if( !AVI_IsActive( cin_state )) + { + SCR_NextMovie( ); + return; + } + + if( UI_IsVisible( )) + { + // these can happens when user set +menu_ option to cmdline + AVI_CloseVideo( cin_state ); + cls.state = ca_disconnected; + Key_SetKeyDest( key_menu ); + S_StopStreaming(); + cls.movienum = -1; + cin_time = 0.0f; + cls.signon = 0; + return; + } + + // advances cinematic time (ignores maxfps and host_framerate settings) + cin_time += host.realframetime; + + // stop the video after it finishes + if( cin_time > video_duration + 0.1f ) + { + SCR_NextMovie( ); + return; + } + + // read the next frame + cin_frame = AVI_GetVideoFrameNumber( cin_state, cin_time ); +} + +/* +================== +SCR_DrawCinematic + +Returns true if a cinematic is active, meaning the view rendering +should be skipped +================== +*/ +qboolean SCR_DrawCinematic( void ) +{ + static int last_frame = -1; + qboolean redraw = false; + byte *frame = NULL; + + if( !glw_state.initialized || cin_time <= 0.0f ) + return false; + + if( cin_frame != last_frame ) + { + frame = AVI_GetVideoFrame( cin_state, cin_frame ); + last_frame = cin_frame; + redraw = true; + } + + R_DrawStretchRaw( 0, 0, glState.width, glState.height, xres, yres, frame, redraw ); + + return true; +} + +/* +================== +SCR_PlayCinematic +================== +*/ +qboolean SCR_PlayCinematic( const char *arg ) +{ + string path; + const char *fullpath; + + fullpath = FS_GetDiskPath( arg, false ); + + if( FS_FileExists( arg, false ) && !fullpath ) + { + MsgDev( D_ERROR, "Couldn't load %s from packfile. Please extract it\n", path ); + return false; + } + + AVI_OpenVideo( cin_state, fullpath, true, false ); + if( !AVI_IsActive( cin_state )) + { + AVI_CloseVideo( cin_state ); + return false; + } + + if( !( AVI_GetVideoInfo( cin_state, &xres, &yres, &video_duration ))) // couldn't open this at all. + { + AVI_CloseVideo( cin_state ); + return false; + } + + if( AVI_GetAudioInfo( cin_state, &cin_audio )) + { + // begin streaming + S_StopAllSounds( true ); + S_StartStreaming(); + } + + UI_SetActiveMenu( false ); + cls.state = ca_cinematic; + cin_time = 0.0f; + cls.signon = 0; + + return true; +} + +long SCR_GetAudioChunk( char *rawdata, long length ) +{ + int r; + + r = AVI_GetAudioChunk( cin_state, rawdata, cin_audio.loopStart, length ); + cin_audio.loopStart += r; // advance play position + + return r; +} + +wavdata_t *SCR_GetMovieInfo( void ) +{ + if( AVI_IsActive( cin_state )) + return &cin_audio; + return NULL; +} + +/* +================== +SCR_StopCinematic +================== +*/ +void SCR_StopCinematic( void ) +{ + if( cls.state != ca_cinematic ) + return; + + AVI_CloseVideo( cin_state ); + S_StopStreaming(); + cin_time = 0.0f; + + cls.state = ca_disconnected; + cls.signon = 0; + + UI_SetActiveMenu( true ); +} + +/* +================== +SCR_InitCinematic +================== +*/ +void SCR_InitCinematic( void ) +{ + AVI_Initailize (); + cin_state = AVI_GetState( CIN_MAIN ); +} + +/* +================== +SCR_FreeCinematic +================== +*/ +void SCR_FreeCinematic( void ) +{ + movie_state_t *cin_state; + + // release videos + cin_state = AVI_GetState( CIN_LOGO ); + AVI_CloseVideo( cin_state ); + + cin_state = AVI_GetState( CIN_MAIN ); + AVI_CloseVideo( cin_state ); + + AVI_Shutdown(); +} \ No newline at end of file diff --git a/engine/client/cl_view.c b/engine/client/cl_view.c new file mode 100644 index 00000000..28bffe78 --- /dev/null +++ b/engine/client/cl_view.c @@ -0,0 +1,396 @@ +/* +cl_view.c - player rendering positioning +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "entity_types.h" +#include "gl_local.h" +#include "vgui_draw.h" + +/* +=============== +V_CalcViewRect + +calc frame rectangle (Quake1 style) +=============== +*/ +void V_CalcViewRect( void ) +{ + qboolean full = false; + int sb_lines; + float size; + + // intermission is always full screen + if( cl.intermission ) size = 120.0f; + else size = scr_viewsize->value; + + if( size >= 120.0f ) + sb_lines = 0; // no status bar at all + else if( size >= 110.0f ) + sb_lines = 24; // no inventory + else sb_lines = 48; + + if( scr_viewsize->value >= 100.0 ) + { + full = true; + size = 100.0f; + } + else size = scr_viewsize->value; + + if( cl.intermission ) + { + size = 100.0f; + sb_lines = 0; + full = true; + } + size /= 100.0; + + clgame.viewport[2] = glState.width * size; + clgame.viewport[3] = glState.height * size; + + if( clgame.viewport[3] > glState.height - sb_lines ) + clgame.viewport[3] = glState.height - sb_lines; + if( clgame.viewport[3] > glState.height ) + clgame.viewport[3] = glState.height; + + clgame.viewport[0] = ( glState.width - clgame.viewport[2] ) / 2; + if( full ) clgame.viewport[1] = 0; + else clgame.viewport[1] = ( glState.height - sb_lines - clgame.viewport[3] ) / 2; + +} + +/* +=============== +V_SetupViewModel +=============== +*/ +void V_SetupViewModel( void ) +{ + cl_entity_t *view = &clgame.viewent; + player_info_t *info = &cl.players[cl.playernum]; + + if( !cl.local.weaponstarttime ) + cl.local.weaponstarttime = cl.time; + + // setup the viewent variables + view->curstate.colormap = (info->topcolor & 0xFFFF)|((info->bottomcolor << 8) & 0xFFFF); + view->curstate.number = cl.playernum + 1; + view->index = cl.playernum + 1; + view->model = CL_ModelHandle( cl.local.viewmodel ); + view->curstate.modelindex = cl.local.viewmodel; + view->curstate.sequence = cl.local.weaponsequence; + view->curstate.rendermode = kRenderNormal; + + // alias models has another animation methods + if( view->model && view->model->type == mod_studio ) + { + view->curstate.animtime = cl.local.weaponstarttime; + view->curstate.frame = 0.0f; + } +} + +/* +=============== +V_SetRefParams +=============== +*/ +void V_SetRefParams( ref_params_t *fd ) +{ + memset( fd, 0, sizeof( ref_params_t )); + + // probably this is not needs + VectorCopy( RI.vieworg, fd->vieworg ); + VectorCopy( RI.viewangles, fd->viewangles ); + + fd->frametime = host.frametime; + fd->time = cl.time; + + fd->intermission = cl.intermission; + fd->paused = (cl.paused != 0); + fd->spectator = (cls.spectator != 0); + fd->onground = (cl.local.onground != -1); + fd->waterlevel = cl.local.waterlevel; + + VectorCopy( cl.simvel, fd->simvel ); + VectorCopy( cl.simorg, fd->simorg ); + + VectorCopy( cl.viewheight, fd->viewheight ); + fd->idealpitch = cl.local.idealpitch; + + VectorCopy( cl.viewangles, fd->cl_viewangles ); + fd->health = cl.local.health; + VectorCopy( cl.crosshairangle, fd->crosshairangle ); + fd->viewsize = scr_viewsize->value; + + VectorCopy( cl.punchangle, fd->punchangle ); + fd->maxclients = cl.maxclients; + fd->viewentity = cl.viewentity; + fd->playernum = cl.playernum; + fd->max_entities = clgame.maxEntities; + fd->demoplayback = cls.demoplayback; + fd->hardware = 1; // OpenGL + + if( cl.first_frame ) + { + cl.first_frame = false; // now can be unlocked + fd->smoothing = true; // NOTE: currently this used to prevent ugly un-duck effect while level is changed + } + else fd->smoothing = cl.local.pushmsec; // enable smoothing in multiplayer by server request (AMX uses) + + // get pointers to movement vars and user cmd + fd->movevars = &clgame.movevars; + fd->cmd = cl.cmd; + + // setup viewport + fd->viewport[0] = clgame.viewport[0]; + fd->viewport[1] = clgame.viewport[1]; + fd->viewport[2] = clgame.viewport[2]; + fd->viewport[3] = clgame.viewport[3]; + + fd->onlyClientDraw = 0; // reset clientdraw + fd->nextView = 0; // reset nextview +} + +/* +=============== +V_MergeOverviewRefdef + +merge refdef with overview settings +=============== +*/ +void V_RefApplyOverview( ref_viewpass_t *rvp ) +{ + ref_overview_t *ov = &clgame.overView; + float aspect; + float size_x, size_y; + vec2_t mins, maxs; + + if( !CL_IsDevOverviewMode( )) + return; + + // NOTE: Xash3D may use 16:9 or 16:10 aspects + aspect = (float)glState.width / (float)glState.height; + + size_x = fabs( 8192.0f / ov->flZoom ); + size_y = fabs( 8192.0f / (ov->flZoom * aspect )); + + // compute rectangle + ov->xLeft = -(size_x / 2); + ov->xRight = (size_x / 2); + ov->yTop = -(size_y / 2); + ov->yBottom = (size_y / 2); + + if( CL_IsDevOverviewMode() == 1 ) + { + Con_NPrintf( 0, " Overview: Zoom %.2f, Map Origin (%.2f, %.2f, %.2f), Z Min %.2f, Z Max %.2f, Rotated %i\n", + ov->flZoom, ov->origin[0], ov->origin[1], ov->origin[2], ov->zNear, ov->zFar, ov->rotated ); + } + + VectorCopy( ov->origin, rvp->vieworigin ); + rvp->vieworigin[2] = ov->zFar + ov->zNear; + Vector2Copy( rvp->vieworigin, mins ); + Vector2Copy( rvp->vieworigin, maxs ); + + mins[!ov->rotated] += ov->xLeft; + maxs[!ov->rotated] += ov->xRight; + mins[ov->rotated] += ov->yTop; + maxs[ov->rotated] += ov->yBottom; + + rvp->viewangles[0] = 90.0f; + rvp->viewangles[1] = 90.0f; + rvp->viewangles[2] = (ov->rotated) ? (ov->flZoom < 0.0f) ? 180.0f : 0.0f : (ov->flZoom < 0.0f) ? -90.0f : 90.0f; + + SetBits( rvp->flags, RF_DRAW_OVERVIEW ); + + Mod_SetOrthoBounds( mins, maxs ); +} + +/* +============= +V_GetRefParams +============= +*/ +void V_GetRefParams( ref_params_t *fd, ref_viewpass_t *rvp ) +{ + // part1: deniable updates + VectorCopy( fd->simvel, cl.simvel ); + VectorCopy( fd->simorg, cl.simorg ); + VectorCopy( fd->punchangle, cl.punchangle ); + VectorCopy( fd->viewheight, cl.viewheight ); + + // part2: really used updates + VectorCopy( fd->crosshairangle, cl.crosshairangle ); + VectorCopy( fd->cl_viewangles, cl.viewangles ); + + // setup ref_viewpass + rvp->viewport[0] = fd->viewport[0]; + rvp->viewport[1] = fd->viewport[1]; + rvp->viewport[2] = fd->viewport[2]; + rvp->viewport[3] = fd->viewport[3]; + + VectorCopy( fd->vieworg, rvp->vieworigin ); + VectorCopy( fd->viewangles, rvp->viewangles ); + + rvp->viewentity = fd->viewentity; + + // calc FOV + rvp->fov_x = bound( 10.0f, cl.local.scr_fov, 150.0f ); // this is a final fov value + + // first we need to compute FOV and other things that needs for frustum properly work + rvp->fov_y = V_CalcFov( &rvp->fov_x, clgame.viewport[2], clgame.viewport[3] ); + + // adjust FOV for widescreen + if( glState.wideScreen && r_adjust_fov->value ) + V_AdjustFov( &rvp->fov_x, &rvp->fov_y, clgame.viewport[2], clgame.viewport[3], false ); + + rvp->flags = 0; + + if( fd->onlyClientDraw ) + SetBits( rvp->flags, RF_ONLY_CLIENTDRAW ); + SetBits( rvp->flags, RF_DRAW_WORLD ); +} + +/* +================== +V_PreRender + +================== +*/ +qboolean V_PreRender( void ) +{ + // too early + if( !glw_state.initialized ) + return false; + + if( host.status == HOST_NOFOCUS ) + return false; + + if( host.status == HOST_SLEEP ) + return false; + + // if the screen is disabled (loading plaque is up) + if( cls.disable_screen ) + { + if(( host.realtime - cls.disable_screen ) > cl_timeout->value ) + { + MsgDev( D_ERROR, "V_PreRender: loading plaque timed out\n" ); + cls.disable_screen = 0.0f; + } + return false; + } + + R_BeginFrame( !cl.paused ); + + return true; +} + +//============================================================================ + +/* +================== +V_RenderView + +================== +*/ +void V_RenderView( void ) +{ + ref_params_t rp; + ref_viewpass_t rvp; + int viewnum = 0; + + if( !cl.video_prepped || ( UI_IsVisible() && !cl.background )) + return; // still loading + + V_CalcViewRect (); // compute viewport rectangle + V_SetRefParams( &rp ); + V_SetupViewModel (); + R_Set2DMode( false ); + SCR_DirtyScreen(); + GL_BackendStartFrame (); + + do + { + clgame.dllFuncs.pfnCalcRefdef( &rp ); + V_GetRefParams( &rp, &rvp ); + V_RefApplyOverview( &rvp ); + + if( viewnum == 0 && FBitSet( rvp.flags, RF_ONLY_CLIENTDRAW )) + { + pglClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + pglClear( GL_COLOR_BUFFER_BIT ); + } + + R_RenderFrame( &rvp ); + viewnum++; + + } while( rp.nextView ); + + // draw debug triangles on a server + SV_DrawDebugTriangles (); + GL_BackendEndFrame (); +} + +/* +================== +V_PostRender + +================== +*/ +void V_PostRender( void ) +{ + static double oldtime; + qboolean draw_2d = false; + + R_AllowFog( false ); + R_Set2DMode( true ); + + if( cls.state == ca_active && cls.signon == SIGNONS && cls.scrshot_action != scrshot_mapshot ) + { + SCR_TileClear(); + CL_DrawHUD( CL_ACTIVE ); + VGui_Paint( true ); + } + + switch( cls.scrshot_action ) + { + case scrshot_inactive: + case scrshot_normal: + case scrshot_snapshot: + draw_2d = true; + break; + } + + if( draw_2d ) + { + SCR_RSpeeds(); + SCR_NetSpeeds(); + SCR_DrawNetGraph(); + SV_DrawOrthoTriangles(); + CL_DrawDemoRecording(); + CL_DrawHUD( CL_CHANGELEVEL ); + R_ShowTextures(); + Con_DrawConsole(); + UI_UpdateMenu( host.realtime ); + Con_DrawVersion(); + Con_DrawDebug(); // must be last + + S_ExtraUpdate(); + } + + SCR_MakeScreenShot(); + R_AllowFog( true ); + R_EndFrame(); +} \ No newline at end of file diff --git a/engine/client/client.h b/engine/client/client.h new file mode 100644 index 00000000..55baf787 --- /dev/null +++ b/engine/client/client.h @@ -0,0 +1,1050 @@ +/* +client.h - primary header for client +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef CLIENT_H +#define CLIENT_H + +#include "mathlib.h" +#include "cdll_int.h" +#include "menu_int.h" +#include "cl_entity.h" +#include "mod_local.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "ref_params.h" +#include "render_api.h" +#include "cdll_exp.h" +#include "screenfade.h" +#include "protocol.h" +#include "netchan.h" +#include "net_api.h" +#include "world.h" + +#define MAX_DEMOS 32 +#define MAX_MOVIES 8 +#define MAX_CDTRACKS 32 +#define MAX_CLIENT_SPRITES 256 // SpriteTextures +#define MAX_EFRAGS 8192 // Arcane Dimensions required +#define MAX_REQUESTS 64 + +// screenshot types +#define VID_SCREENSHOT 0 +#define VID_LEVELSHOT 1 +#define VID_MINISHOT 2 +#define VID_MAPSHOT 3 // special case for overview layer +#define VID_SNAPSHOT 4 // save screenshot into root dir and no gamma correction + +// client sprite types +#define SPR_CLIENT 0 // client sprite for temp-entities or user-textures +#define SPR_HUDSPRITE 1 // hud sprite +#define SPR_MAPSPRITE 2 // contain overview.bmp that diced into frames 128x128 + +typedef int sound_t; + +//============================================================================= +typedef struct netbandwithgraph_s +{ + word client; + word players; + word entities; // entities bytes, except for players + word tentities; // temp entities + word sound; + word event; + word usr; + word msgbytes; + word voicebytes; +} netbandwidthgraph_t; + +typedef struct frame_s +{ + // received from server + double receivedtime; // time message was received, or -1 + double latency; + double time; // server timestamp + qboolean valid; // cleared if delta parsing was invalid + qboolean choked; + + clientdata_t clientdata; // local client private data + entity_state_t playerstate[MAX_CLIENTS]; + weapon_data_t weapondata[MAX_LOCAL_WEAPONS]; + netbandwidthgraph_t graphdata; + byte flags[MAX_VISIBLE_PACKET_VIS_BYTES]; + int num_entities; + int first_entity; // into the circular cl_packet_entities[] +} frame_t; + +typedef struct runcmd_s +{ + double senttime; + double receivedtime; + float frame_lerp; + + usercmd_t cmd; + + qboolean processedfuncs; + qboolean heldback; + int sendsize; +} runcmd_t; + +// add angles +typedef struct +{ + float starttime; + float total; +} pred_viewangle_t; + +#define ANGLE_BACKUP 16 +#define ANGLE_MASK (ANGLE_BACKUP - 1) + +#define CL_UPDATE_MASK (CL_UPDATE_BACKUP - 1) +extern int CL_UPDATE_BACKUP; + +#define SIGNONS 2 // signon messages to receive before connected +#define INVALID_HANDLE 0xFFFF // for XashXT cache system + +#define MIN_UPDATERATE 10.0f +#define MAX_UPDATERATE 102.0f + +#define MIN_EX_INTERP 50.0f +#define MAX_EX_INTERP 100.0f + +#define CL_MIN_RESEND_TIME 1.5f // mininum time gap (in seconds) before a subsequent connection request is sent. +#define CL_MAX_RESEND_TIME 20.0f // max time. The cvar cl_resend is bounded by these. + +#define cl_serverframetime() (cl.mtime[0] - cl.mtime[1]) +#define cl_clientframetime() (cl.time - cl.oldtime) + +typedef struct +{ + // got from prediction system + vec3_t predicted_origins[CMD_BACKUP]; + vec3_t prediction_error; + vec3_t lastorigin; + int lastground; + + // interp info + float interp_amount; + + // misc local info + qboolean repredicting; // repredicting in progress + qboolean thirdperson; + qboolean apply_effects; // local player will not added but we should apply their effects: flashlight etc + float idealpitch; + int viewmodel; + int health; // client health + int onground; + int light_level; + int waterlevel; + int usehull; + int moving; + int pushmsec; + int weapons; + float maxspeed; + float scr_fov; + + // weapon predict stuff + int weaponsequence; + float weaponstarttime; +} cl_local_data_t; + +typedef struct +{ + char name[MAX_OSPATH]; + char modelname[MAX_OSPATH]; + model_t *model; +} player_model_t; + +typedef struct +{ + qboolean bUsed; + float fTime; + int nBytesRemaining; +} downloadtime_t; + +typedef struct +{ + qboolean doneregistering; + int percent; + qboolean downloadrequested; + downloadtime_t rgStats[8]; + int nCurStat; + int nTotalSize; + int nTotalToTransfer; + int nRemainingToTransfer; + float fLastStatusUpdate; + qboolean custom; +} incomingtransfer_t; + +// the client_t structure is wiped completely +// at every server map change +typedef struct +{ + int servercount; // server identification for prespawns + int validsequence; // this is the sequence number of the last good + // world snapshot/update we got. If this is 0, we can't + // render a frame yet + int parsecount; // server message counter + int parsecountmod; // modulo with network window + + qboolean video_prepped; // false if on new level or new ref dll + qboolean audio_prepped; // false if on new level or new snd dll + qboolean paused; + + int delta_sequence; // acknowledged sequence number + + double mtime[2]; // the timestamp of the last two messages + float lerpFrac; + + int last_command_ack; + int last_incoming_sequence; + + qboolean send_reply; + qboolean background; // not real game, just a background + qboolean first_frame; // first rendering frame + qboolean proxy_redirect; // spectator stuff + + uint checksum; // for catching cheater maps + + frame_t frames[MULTIPLAYER_BACKUP]; // alloced on svc_serverdata + runcmd_t commands[MULTIPLAYER_BACKUP]; // each mesage will send several old cmds + local_state_t predicted_frames[MULTIPLAYER_BACKUP]; // local client state + + double time; // this is the time value that the client + // is rendering at. always <= cls.realtime + // a lerp point for other data + double oldtime; // previous cl.time, time-oldtime is used + // to decay light values and smooth step ups + float timedelta; // floating delta between two updates + + char serverinfo[MAX_SERVERINFO_STRING]; + player_model_t player_models[MAX_CLIENTS]; // cache of player models + player_info_t players[MAX_CLIENTS]; // collected info about all other players include himself + double lastresourcecheck; + string downloadUrl; + event_state_t events; + + // predicting stuff but not only... + cl_local_data_t local; + + // player final info + usercmd_t *cmd; // cl.commands[outgoing_sequence].cmd + int viewentity; + vec3_t viewangles; + vec3_t viewheight; + vec3_t punchangle; + + int intermission; // don't change view angle, full screen, et + vec3_t crosshairangle; + + pred_viewangle_t predicted_angle[ANGLE_BACKUP];// accumulate angles from server + int angle_position; + float addangletotal; + float prevaddangletotal; + + // predicted origin and velocity + vec3_t simorg; + vec3_t simvel; + + // server state information + int playernum; + int maxclients; + + entity_state_t instanced_baseline[MAX_CUSTOM_BASELINES]; + int instanced_baseline_count; + + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + char event_precache[MAX_EVENTS][MAX_QPATH]; + char files_precache[MAX_CUSTOM][MAX_QPATH]; + lightstyle_t lightstyles[MAX_LIGHTSTYLES]; + model_t *models[MAX_MODELS+1]; // precached models (plus sentinel slot) + int nummodels; + int numfiles; + + consistency_t consistency_list[MAX_MODELS]; + int num_consistency; + + qboolean need_force_consistency_response; + resource_t resourcesonhand; + resource_t resourcesneeded; + resource_t resourcelist[MAX_RESOURCES]; + int num_resources; + + short sound_index[MAX_SOUNDS]; + short decal_index[MAX_DECALS]; + + model_t *worldmodel; // pointer to world +} client_t; + +/* +================================================================== + +the client_static_t structure is persistant through an arbitrary number +of server connections + +================================================================== +*/ +typedef enum +{ + ca_disconnected = 0,// not talking to a server + ca_connecting, // sending request packets to the server + ca_connected, // netchan_t established, waiting for svc_serverdata + ca_validate, // download resources, validating, auth on server + ca_active, // game views should be displayed + ca_cinematic, // playing a cinematic, not connected to a server +} connstate_t; + +typedef enum +{ + scrshot_inactive, + scrshot_normal, // in-game screenshot + scrshot_snapshot, // in-game snapshot + scrshot_plaque, // levelshot + scrshot_savegame, // saveshot + scrshot_demoshot, // for demos preview + scrshot_envshot, // cubemap view + scrshot_skyshot, // skybox view + scrshot_mapshot // overview layer +} scrshot_t; + +// client screen state +typedef enum +{ + CL_LOADING = 1, // draw loading progress-bar + CL_ACTIVE, // draw normal hud + CL_PAUSED, // pause when active + CL_CHANGELEVEL, // draw 'loading' during changelevel +} scrstate_t; + +typedef struct +{ + char name[32]; + int number; // svc_ number + int size; // if size == -1, size come from first byte after svcnum + pfnUserMsgHook func; // user-defined function +} cl_user_message_t; + +typedef void (*pfnEventHook)( event_args_t *args ); + +typedef struct +{ + char name[MAX_QPATH]; + word index; // event index + pfnEventHook func; // user-defined function +} cl_user_event_t; + +#define FONT_FIXED 0 +#define FONT_VARIABLE 1 + +typedef struct +{ + int hFontTexture; // handle to texture + wrect_t fontRc[256]; // rectangles + byte charWidths[256]; + int charHeight; + int type; + qboolean valid; // all rectangles are valid +} cl_font_t; + +typedef struct +{ + // temp handle + const model_t *pSprite; // pointer to current SpriteTexture + + // scissor test + int scissor_x; + int scissor_y; + int scissor_width; + int scissor_height; + qboolean scissor_test; + qboolean adjust_size; // allow to adjust scale for fonts + + int renderMode; // override kRenderMode from TriAPI + int cullMode; // override CULL FACE from TriAPI + + // holds text color + rgba_t textColor; + rgba_t spriteColor; + vec4_t triRGBA; + + // crosshair members + const model_t *pCrosshair; + wrect_t rcCrosshair; + rgba_t rgbaCrosshair; +} client_draw_t; + +typedef struct cl_predicted_player_s +{ + int movetype; + int solid; + int usehull; + qboolean active; + vec3_t origin; // interpolated origin + vec3_t angles; +} predicted_player_t; + +typedef struct +{ + int gl_texturenum; // this is a real texnum + + // scissor test + int scissor_x; + int scissor_y; + int scissor_width; + int scissor_height; + qboolean scissor_test; + + // holds text color + rgba_t textColor; +} gameui_draw_t; + +typedef struct +{ + // centerprint stuff + float time; + int y, lines; + char message[2048]; + int totalWidth; + int totalHeight; +} center_print_t; + +typedef struct +{ + float time; + float duration; + float amplitude; + float frequency; + float next_shake; + vec3_t offset; + float angle; + vec3_t applied_offset; + float applied_angle; +} screen_shake_t; + +typedef struct +{ + unsigned short textures[MAX_SKINS];// alias textures + struct mstudiotex_s *ptexture; // array of textures with local copy of remapped textures + short numtextures; // textures count + short topcolor; // cached value + short bottomcolor; // cached value + model_t *model; // for catch model changes +} remap_info_t; + +typedef enum +{ + NET_REQUEST_CANCEL = 0, // request was cancelled for some reasons + NET_REQUEST_GAMEUI, // called from GameUI + NET_REQUEST_CLIENT, // called from Client +} net_request_type_t; + +typedef struct +{ + net_response_t resp; + net_api_response_func_t pfnFunc; + double timeout; + double timesend; // time when request was sended + int flags; // FNETAPI_MULTIPLE_RESPONSE etc +} net_request_t; + +// new versions of client dlls have a single export with all callbacks +typedef void (*CL_EXPORT_FUNCS)( void *pv ); + +typedef struct +{ + void *hInstance; // pointer to client.dll + cldll_func_t dllFuncs; // dll exported funcs + render_interface_t drawFuncs; // custom renderer support + byte *mempool; // client edicts pool + string mapname; // map name + string maptitle; // display map title + string itemspath; // path to items description for auto-complete func + + cl_entity_t *entities; // dynamically allocated entity array + cl_entity_t *static_entities; // dynamically allocated static entity array + remap_info_t **remap_info; // store local copy of all remap textures for each entity + + int maxEntities; + int maxRemapInfos; // maxEntities + cl.viewEnt; also used for catch entcount + int numStatics; // actual static entity count + int maxModels; + + // movement values from server + movevars_t movevars; + movevars_t oldmovevars; + playermove_t *pmove; // pmove state + + qboolean pushed; // used by PM_Push\Pop state + int oldviscount; // used by PM_Push\Pop state + int oldphyscount; // used by PM_Push\Pop state + + cl_user_message_t msg[MAX_USER_MESSAGES]; // keep static to avoid fragment memory + cl_user_event_t *events[MAX_EVENTS]; + + string cdtracks[MAX_CDTRACKS]; // 32 cd-tracks read from cdaudio.txt + + model_t sprites[MAX_CLIENT_SPRITES]; // client spritetextures + int viewport[4]; // viewport sizes + + client_draw_t ds; // draw2d stuff (hud, weaponmenu etc) + screenfade_t fade; // screen fade + screen_shake_t shake; // screen shake + center_print_t centerPrint; // centerprint variables + SCREENINFO scrInfo; // actual screen info + ref_overview_t overView; // overView params + color24 palette[256]; // palette used for particle colors + + client_textmessage_t *titles; // title messages, not network messages + int numTitles; + + net_request_type_t request_type; // filter the requests + net_request_t net_requests[MAX_REQUESTS]; // no reason to keep more + net_request_t *master_request; // queued master request + + efrag_t *free_efrags; // linked efrags + cl_entity_t viewent; // viewmodel +} clgame_static_t; + +typedef struct +{ + void *hInstance; // pointer to client.dll + UI_FUNCTIONS dllFuncs; // dll exported funcs + byte *mempool; // client edicts pool + + cl_entity_t playermodel; // uiPlayerSetup drawing model + player_info_t playerinfo; // local playerinfo + + gameui_draw_t ds; // draw2d stuff (menu images) + GAMEINFO gameInfo; // current gameInfo + GAMEINFO *modsInfo[MAX_MODS]; // simplified gameInfo for MainUI + + ui_globalvars_t *globals; + + qboolean drawLogo; // set to TRUE if logo.avi missed or corrupted + long logo_xres; + long logo_yres; + float logo_length; +} gameui_static_t; + +typedef struct +{ + connstate_t state; + qboolean initialized; + qboolean changelevel; // during changelevel + qboolean changedemo; // during changedemo + double timestart; // just for profiling + + // screen rendering information + float disable_screen; // showing loading plaque between levels + // or changing rendering dlls + // if time gets > 30 seconds ahead, break it + int disable_servercount; // when we receive a frame and cl.servercount + // > cls.disable_servercount, clear disable_screen + + qboolean draw_changelevel; // draw changelevel image 'Loading...' + + keydest_t key_dest; + + byte *mempool; // client premamnent pool: edicts etc + + netadr_t hltv_listen_address; + + int signon; // 0 to SIGNONS, for the signon sequence. + int quakePort; // a 16 bit value that allows quake servers + // to work around address translating routers + // g-cont. this port allow many copies of engine in multiplayer game + // connection information + char servername[MAX_QPATH]; // name of server from original connect + double connect_time; // for connection retransmits + int max_fragment_size; // we needs to test a real network bandwidth + int connect_retry; // how many times we send a connect packet to the server + qboolean spectator; // not a real player, just spectator + + local_state_t spectator_state; // init as client startup + + char userinfo[MAX_INFO_STRING]; + char physinfo[MAX_INFO_STRING]; // read-only + + sizebuf_t datagram; // unreliable stuff. gets sent in CL_Move about cl_cmdrate times per second. + byte datagram_buf[MAX_DATAGRAM]; + + netchan_t netchan; + int challenge; // from the server to use for connecting + + float packet_loss; + double packet_loss_recalc_time; + + float nextcmdtime; // when can we send the next command packet? + int lastoutgoingcommand; // sequence number of last outgoing command + int lastupdate_sequence; // prediction stuff + + int td_lastframe; // to meter out one message a frame + int td_startframe; // host_framecount at start + double td_starttime; // realtime at second frame of timedemo + + // game images + int pauseIcon; // draw 'paused' when game in-pause + int tileImage; // for draw any areas not covered by the refresh + int loadingBar; // 'loading' progress bar + cl_font_t creditsFont; // shared creditsfont + + float latency; // rolling average of frame latencey (receivedtime - senttime) values. + + int num_client_entities; // cl.maxclients * CL_UPDATE_BACKUP * MAX_PACKET_ENTITIES + int next_client_entities; // next client_entity to use + entity_state_t *packet_entities; // [num_client_entities] + + predicted_player_t predicted_players[MAX_CLIENTS]; + double correction_time; + + scrshot_t scrshot_request; // request for screen shot + scrshot_t scrshot_action; // in-action + const float *envshot_vieworg; // envshot position + int envshot_viewsize; // override cvar + qboolean envshot_disable_vis; // disable VIS on server while makes an envshots + string shotname; + + // download info + incomingtransfer_t dl; + + // demo loop control + int demonum; // -1 = don't play demos + int olddemonum; // restore playing + string demos[MAX_DEMOS]; // when not playing + + // movie playlist + int movienum; + string movies[MAX_MOVIES]; + + // demo recording info must be here, so it isn't clearing on level change + qboolean demorecording; + qboolean demoplayback; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean timedemo; + string demoname; // for demo looping + double demotime; // recording time + + file_t *demofile; + file_t *demoheader; // contain demo startup info in case we record a demo on this level +} client_static_t; + +#ifdef __cplusplus +extern "C" { +#endif + +extern client_t cl; +extern client_static_t cls; +extern clgame_static_t clgame; +extern gameui_static_t gameui; + +#ifdef __cplusplus +} +#endif + +// +// cvars +// +extern convar_t mp_decals; +extern convar_t cl_logofile; +extern convar_t cl_logocolor; +extern convar_t cl_allow_download; +extern convar_t cl_allow_upload; +extern convar_t cl_download_ingame; +extern convar_t *cl_nopred; +extern convar_t *cl_showfps; +extern convar_t *cl_envshot_size; +extern convar_t *cl_timeout; +extern convar_t *cl_nodelta; +extern convar_t *cl_interp; +extern convar_t *cl_showerror; +extern convar_t *cl_nosmooth; +extern convar_t *cl_smoothtime; +extern convar_t *cl_crosshair; +extern convar_t *cl_testlights; +extern convar_t *cl_cmdrate; +extern convar_t *cl_updaterate; +extern convar_t *cl_solid_players; +extern convar_t *cl_idealpitchscale; +extern convar_t *cl_allow_levelshots; +extern convar_t *cl_lightstyle_lerping; +extern convar_t *cl_draw_particles; +extern convar_t *cl_draw_tracers; +extern convar_t *cl_levelshot_name; +extern convar_t *cl_draw_beams; +extern convar_t *cl_clockreset; +extern convar_t *cl_fixtimerate; +extern convar_t *gl_showtextures; +extern convar_t *cl_bmodelinterp; +extern convar_t *cl_righthand; +extern convar_t *cl_lw; // local weapons +extern convar_t *cl_showevents; +extern convar_t *scr_centertime; +extern convar_t *scr_viewsize; +extern convar_t *scr_loading; +extern convar_t *v_dark; // start from dark +extern convar_t *net_graph; +extern convar_t *rate; + +//============================================================================= + +void CL_SetLightstyle( int style, const char* s, float f ); +void CL_RunLightStyles( void ); +void CL_DecayLights( void ); + +//================================================= + +// +// cl_cmds.c +// +void CL_Quit_f( void ); +void CL_ScreenShot_f( void ); +void CL_SnapShot_f( void ); +void CL_PlayCDTrack_f( void ); +void CL_EnvShot_f( void ); +void CL_SkyShot_f( void ); +void CL_SaveShot_f( void ); +void CL_DemoShot_f( void ); +void CL_LevelShot_f( void ); +void CL_SetSky_f( void ); +void SCR_Viewpos_f( void ); +void SCR_TimeRefresh_f( void ); + +// +// cl_custom.c +// +qboolean CL_CheckFile( sizebuf_t *msg, resource_t *pResource ); +void CL_AddToResourceList( resource_t *pResource, resource_t *pList ); +void CL_RemoveFromResourceList( resource_t *pResource ); +void CL_MoveToOnHandList( resource_t *pResource ); +void CL_ClearResourceLists( void ); + +// +// cl_main.c +// +void CL_Init( void ); +void CL_SendCommand( void ); +void CL_Disconnect_f( void ); +void CL_ProcessFile( qboolean successfully_received, const char *filename ); +void CL_WriteUsercmd( sizebuf_t *msg, int from, int to ); +int CL_GetFragmentSize( void *unused ); +qboolean CL_PrecacheResources( void ); +void CL_SetupOverviewParams( void ); +void CL_UpdateFrameLerp( void ); +int CL_IsDevOverviewMode( void ); +void CL_PingServers_f( void ); +void CL_SignonReply( void ); +void CL_ClearState( void ); + +// +// cl_demo.c +// +void CL_StartupDemoHeader( void ); +void CL_DrawDemoRecording( void ); +void CL_WriteDemoUserCmd( int cmdnumber ); +void CL_WriteDemoMessage( qboolean startup, int start, sizebuf_t *msg ); +void CL_WriteDemoUserMessage( const byte *buffer, size_t size ); +qboolean CL_DemoReadMessage( byte *buffer, size_t *length ); +void CL_DemoInterpolateAngles( void ); +void CL_WriteDemoJumpTime( void ); +void CL_CloseDemoHeader( void ); +void CL_StopPlayback( void ); +void CL_StopRecord( void ); +void CL_PlayDemo_f( void ); +void CL_TimeDemo_f( void ); +void CL_StartDemos_f( void ); +void CL_Demos_f( void ); +void CL_DeleteDemo_f( void ); +void CL_Record_f( void ); +void CL_Stop_f( void ); +void CL_FreeDemo( void ); + +// +// cl_events.c +// +void CL_ParseEvent( sizebuf_t *msg ); +void CL_ParseReliableEvent( sizebuf_t *msg ); +void CL_SetEventIndex( const char *szEvName, int ev_index ); +void CL_QueueEvent( int flags, int index, float delay, event_args_t *args ); +void CL_PlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func ); +void CL_BatchResourceRequest( qboolean initialize ); +int CL_EstimateNeededResources( void ); +void CL_ResetEvent( event_info_t *ei ); +word CL_EventIndex( const char *name ); +void CL_FireEvents( void ); + +// +// cl_game.c +// +void CL_UnloadProgs( void ); +qboolean CL_LoadProgs( const char *name ); +void CL_ParseUserMessage( sizebuf_t *msg, int svc_num ); +void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize ); +void CL_ParseFinaleCutscene( sizebuf_t *msg, int level ); +void CL_ParseTextMessage( sizebuf_t *msg ); +void CL_DrawHUD( int state ); +void CL_InitEdicts( void ); +void CL_FreeEdicts( void ); +void CL_ClearWorld( void ); +void CL_DrawCenterPrint( void ); +void CL_ClearSpriteTextures( void ); +void CL_FreeEntity( cl_entity_t *pEdict ); +void CL_CenterPrint( const char *text, float y ); +void CL_TextMessageParse( byte *pMemFile, int fileSize ); +client_textmessage_t *CL_TextMessageGet( const char *pName ); +int pfnDecalIndexFromName( const char *szDecalName ); +int pfnIndexFromTrace( struct pmtrace_s *pTrace ); +model_t *CL_ModelHandle( int modelindex ); +void NetAPI_CancelAllRequests( void ); +int CL_FindModelIndex( const char *m ); +cl_entity_t *CL_GetLocalPlayer( void ); +model_t *CL_LoadClientSprite( const char *filename ); +model_t *CL_LoadModel( const char *modelname, int *index ); +HSPRITE pfnSPR_Load( const char *szPicName ); +HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags ); +void PicAdjustSize( float *x, float *y, float *w, float *h ); +void CL_FillRGBA( int x, int y, int width, int height, int r, int g, int b, int a ); +void CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr ); +void CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr ); +void CL_SetTraceHull( int hull ); + +_inline cl_entity_t *CL_EDICT_NUM( int n ) +{ + if(( n >= 0 ) && ( n < clgame.maxEntities )) + return clgame.entities + n; + + Host_Error( "CL_EDICT_NUM: bad number %i\n", n ); + return NULL; +} + +// +// cl_parse.c +// +void CL_ParseServerMessage( sizebuf_t *msg, qboolean normal_message ); +void CL_ParseTempEntity( sizebuf_t *msg ); +void CL_StartResourceDownloading( const char *pszMessage, qboolean bCustom ); +qboolean CL_DispatchUserMessage( const char *pszName, int iSize, void *pbuf ); +qboolean CL_RequestMissingResources( void ); +void CL_RegisterResources ( sizebuf_t *msg ); + +// +// cl_scrn.c +// +void SCR_VidInit( void ); +void SCR_TileClear( void ); +void SCR_DirtyScreen( void ); +void SCR_AddDirtyPoint( int x, int y ); +void SCR_InstallParticlePalette( void ); +void SCR_EndLoadingPlaque( void ); +void SCR_RegisterTextures( void ); +void SCR_LoadCreditsFont( void ); +void SCR_MakeScreenShot( void ); +void SCR_MakeLevelShot( void ); +void SCR_NetSpeeds( void ); +void SCR_RSpeeds( void ); +void SCR_DrawFPS( int height ); + +// +// cl_netgraph.c +// +void CL_InitNetgraph( void ); +void SCR_DrawNetGraph( void ); + +// +// cl_view.c +// + +void V_Init (void); +void V_Shutdown( void ); +qboolean V_PreRender( void ); +void V_PostRender( void ); +void V_RenderView( void ); + +// +// cl_pmove.c +// +void CL_SetSolidEntities( void ); +void CL_SetSolidPlayers( int playernum ); +void CL_InitClientMove( void ); +void CL_PredictMovement( qboolean repredicting ); +void CL_CheckPredictionError( void ); +qboolean CL_IsPredicted( void ); +int CL_TruePointContents( const vec3_t p ); +int CL_PointContents( const vec3_t p ); +int CL_WaterEntity( const float *rgflPos ); +cl_entity_t *CL_GetWaterEntity( const float *rgflPos ); +void CL_SetupPMove( playermove_t *pmove, local_state_t *from, usercmd_t *ucmd, qboolean runfuncs, double time ); +int CL_TestLine( const vec3_t start, const vec3_t end, int flags ); +pmtrace_t *CL_VisTraceLine( vec3_t start, vec3_t end, int flags ); +pmtrace_t CL_TraceLine( vec3_t start, vec3_t end, int flags ); +void CL_MoveSpectatorCamera( void ); +void CL_SetLastUpdate( void ); +void CL_RedoPrediction( void ); +void CL_ClearPhysEnts( void ); +void CL_PushPMStates( void ); +void CL_PopPMStates( void ); +void CL_SetUpPlayerPrediction( int dopred, int bIncludeLocalClient ); + +// +// cl_studio.c +// +void CL_InitStudioAPI( void ); + +// +// cl_frame.c +// +int CL_ParsePacketEntities( sizebuf_t *msg, qboolean delta ); +qboolean CL_AddVisibleEntity( cl_entity_t *ent, int entityType ); +void CL_ResetLatchedVars( cl_entity_t *ent, qboolean full_reset ); +qboolean CL_GetEntitySpatialization( struct channel_s *ch ); +qboolean CL_GetMovieSpatialization( struct rawchan_s *ch ); +void CL_ComputePlayerOrigin( cl_entity_t *clent ); +void CL_UpdateEntityFields( cl_entity_t *ent ); +void CL_MoveThirdpersonCamera( void ); +qboolean CL_IsPlayerIndex( int idx ); +void CL_SetIdealPitch( void ); +void CL_EmitEntities( void ); + +// +// cl_remap.c +// +remap_info_t *CL_GetRemapInfoForEntity( cl_entity_t *e ); +void CL_AllocRemapInfo( int topcolor, int bottomcolor ); +void CL_FreeRemapInfo( remap_info_t *info ); +void R_StudioSetRemapColors( int top, int bottom ); +void CL_UpdateRemapInfo( int topcolor, int bottomcolor ); +void CL_ClearAllRemaps( void ); + +// +// cl_tent.c +// +int CL_AddEntity( int entityType, cl_entity_t *pEnt ); +void CL_WeaponAnim( int iAnim, int body ); +void CL_ClearEffects( void ); +void CL_ClearEfrags( void ); +void CL_TestLights( void ); +void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime ); +void CL_FireCustomDecal( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags, float scale ); +void CL_DecalShoot( int textureIndex, int entityIndex, int modelIndex, float *pos, int flags ); +void CL_PlayerDecal( int playerIndex, int textureIndex, int entityIndex, float *pos ); +void R_FreeDeadParticles( struct particle_s **ppparticles ); +void CL_AddClientResource( const char *filename, int type ); +void CL_AddClientResources( void ); +int CL_FxBlend( cl_entity_t *e ); +void CL_InitParticles( void ); +void CL_ClearParticles( void ); +void CL_FreeParticles( void ); +void CL_DrawParticles( double frametime ); +void CL_DrawTracers( double frametime ); +void CL_InitTempEnts( void ); +void CL_ClearTempEnts( void ); +void CL_FreeTempEnts( void ); +void CL_TempEntUpdate( void ); +void CL_InitViewBeams( void ); +void CL_ClearViewBeams( void ); +void CL_FreeViewBeams( void ); +void CL_DrawBeams( int fTrans ); +void CL_AddCustomBeam( cl_entity_t *pEnvBeam ); +void CL_KillDeadBeams( cl_entity_t *pDeadEntity ); +void CL_ParseViewBeam( sizebuf_t *msg, int beamType ); +void CL_LoadClientSprites( void ); +void CL_ReadPointFile_f( void ); +void CL_ReadLineFile_f( void ); +void CL_RunLightStyles( void ); + +// +// console.c +// +extern convar_t *con_fontsize; +qboolean Con_Visible( void ); +qboolean Con_FixedFont( void ); +void Con_VidInit( void ); +void Con_Shutdown( void ); +void Con_ToggleConsole_f( void ); +void Con_ClearNotify( void ); +void Con_DrawDebug( void ); +void Con_RunConsole( void ); +void Con_DrawConsole( void ); +void Con_DrawVersion( void ); +void Con_DrawStringLen( const char *pText, int *length, int *height ); +int Con_DrawString( int x, int y, const char *string, rgba_t setColor ); +int Con_DrawCharacter( int x, int y, int number, rgba_t color ); +void Con_DrawCharacterLen( int number, int *width, int *height ); +void Con_DefaultColor( int r, int g, int b ); +void Con_InvalidateFonts( void ); +void Con_SetFont( int fontNum ); +void Con_CharEvent( int key ); +void Con_RestoreFont( void ); +void Key_Console( int key ); +void Key_Message( int key ); +void Con_FastClose( void ); + +// +// s_main.c +// +void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data ); +void S_StartBackgroundTrack( const char *intro, const char *loop, long position, qboolean fullpath ); +void S_StopBackgroundTrack( void ); +void S_StreamSetPause( int pause ); +void S_StartStreaming( void ); +void S_StopStreaming( void ); +void S_BeginRegistration( void ); +sound_t S_RegisterSound( const char *sample ); +void S_EndRegistration( void ); +void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex ); +void S_StartSound( const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, int pitch, int flags ); +void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags ); +void S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds ); +void S_FadeMusicVolume( float fadePercent ); +void S_StartLocalSound( const char *name, float volume, qboolean reliable ); +void SND_UpdateSound( void ); +void S_ExtraUpdate( void ); + +// +// cl_gameui.c +// +void UI_UnloadProgs( void ); +qboolean UI_LoadProgs( void ); +void UI_UpdateMenu( float realtime ); +void UI_KeyEvent( int key, qboolean down ); +void UI_MouseMove( int x, int y ); +void UI_SetActiveMenu( qboolean fActive ); +void UI_AddServerToList( netadr_t adr, const char *info ); +void UI_GetCursorPos( int *pos_x, int *pos_y ); +void UI_SetCursorPos( int pos_x, int pos_y ); +void UI_ShowCursor( qboolean show ); +qboolean UI_CreditsActive( void ); +void UI_CharEvent( int key ); +qboolean UI_MouseInRect( void ); +qboolean UI_IsVisible( void ); +void pfnPIC_Set( HIMAGE hPic, int r, int g, int b, int a ); +void pfnPIC_Draw( int x, int y, int width, int height, const wrect_t *prc ); +void pfnPIC_DrawTrans( int x, int y, int width, int height, const wrect_t *prc ); +void pfnPIC_DrawHoles( int x, int y, int width, int height, const wrect_t *prc ); +void pfnPIC_DrawAdditive( int x, int y, int width, int height, const wrect_t *prc ); + +// +// cl_video.c +// +void SCR_InitCinematic( void ); +void SCR_FreeCinematic( void ); +qboolean SCR_PlayCinematic( const char *name ); +qboolean SCR_DrawCinematic( void ); +qboolean SCR_NextMovie( void ); +void SCR_RunCinematic( void ); +void SCR_StopCinematic( void ); +void CL_PlayVideo_f( void ); + +#endif//CLIENT_H \ No newline at end of file diff --git a/engine/client/gl_alias.c b/engine/client/gl_alias.c new file mode 100644 index 00000000..cefd8e78 --- /dev/null +++ b/engine/client/gl_alias.c @@ -0,0 +1,1497 @@ +/* +gl_alias.c - alias model renderer +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "alias.h" +#include "pm_local.h" +#include "gl_local.h" +#include "cl_tent.h" + +extern cvar_t r_shadows; + +typedef struct +{ + double time; + double frametime; + int framecount; // alias framecount + qboolean interpolate; + + float ambientlight; + float shadelight; + vec3_t lightvec; // averaging light direction + vec3_t lightvec_local; // light direction in local space + vec3_t lightspot; // shadow spot + vec3_t lightcolor; // averaging lightcolor + int oldpose; // shadow used + int newpose; // shadow used + float lerpfrac; // lerp frames +} alias_draw_state_t; + +static alias_draw_state_t g_alias; // global alias state + +/* +================================================================= + +ALIAS MODEL DISPLAY LIST GENERATION + +================================================================= +*/ +static qboolean m_fDoRemap; +static aliashdr_t *m_pAliasHeader; +static trivertex_t *g_poseverts[MAXALIASFRAMES]; +static dtriangle_t g_triangles[MAXALIASTRIS]; +static stvert_t g_stverts[MAXALIASVERTS]; +static qboolean g_used[8192]; + +// a pose is a single set of vertexes. a frame may be +// an animating sequence of poses +int g_posenum; + +// the command list holds counts and s/t values that are valid for +// every frame +static int g_commands[8192]; +static int g_numcommands; + +// all frames will have their vertexes rearranged and expanded +// so they are in the order expected by the command list +static int g_vertexorder[8192]; +static int g_numorder; + +static int g_stripverts[128]; +static int g_striptris[128]; +static int g_stripcount; + +/* +==================== +R_StudioInit + +==================== +*/ +void R_AliasInit( void ) +{ + g_alias.interpolate = true; + m_fDoRemap = false; +} + +/* +================ +StripLength +================ +*/ +static int StripLength( int starttri, int startv ) +{ + int m1, m2, j, k; + dtriangle_t *last, *check; + + g_used[starttri] = 2; + + last = &g_triangles[starttri]; + + g_stripverts[0] = last->vertindex[(startv+0) % 3]; + g_stripverts[1] = last->vertindex[(startv+1) % 3]; + g_stripverts[2] = last->vertindex[(startv+2) % 3]; + + g_striptris[0] = starttri; + g_stripcount = 1; + + m1 = last->vertindex[(startv+2)%3]; + m2 = last->vertindex[(startv+1)%3]; +nexttri: + // look for a matching triangle + for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) + { + if( check->facesfront != last->facesfront ) + continue; + + for( k = 0; k < 3; k++ ) + { + if( check->vertindex[k] != m1 ) + continue; + if( check->vertindex[(k+1) % 3] != m2 ) + continue; + + // this is the next part of the fan + + // if we can't use this triangle, this tristrip is done + if( g_used[j] ) goto done; + + // the new edge + if( g_stripcount & 1 ) + m2 = check->vertindex[(k+2) % 3]; + else m1 = check->vertindex[(k+2) % 3]; + + g_stripverts[g_stripcount+2] = check->vertindex[(k+2) % 3]; + g_striptris[g_stripcount] = j; + g_stripcount++; + + g_used[j] = 2; + goto nexttri; + } + } +done: + // clear the temp used flags + for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) + { + if( g_used[j] == 2 ) + g_used[j] = 0; + } + + return g_stripcount; +} + +/* +=========== +FanLength +=========== +*/ +static int FanLength( int starttri, int startv ) +{ + int m1, m2, j, k; + dtriangle_t *last, *check; + + g_used[starttri] = 2; + + last = &g_triangles[starttri]; + + g_stripverts[0] = last->vertindex[(startv+0) % 3]; + g_stripverts[1] = last->vertindex[(startv+1) % 3]; + g_stripverts[2] = last->vertindex[(startv+2) % 3]; + + g_striptris[0] = starttri; + g_stripcount = 1; + + m1 = last->vertindex[(startv+0) % 3]; + m2 = last->vertindex[(startv+2) % 3]; + +nexttri: + // look for a matching triangle + for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) + { + if( check->facesfront != last->facesfront ) + continue; + + for( k = 0; k < 3; k++ ) + { + if( check->vertindex[k] != m1 ) + continue; + if( check->vertindex[(k+1) % 3] != m2 ) + continue; + + // this is the next part of the fan + // if we can't use this triangle, this tristrip is done + if( g_used[j] ) goto done; + + // the new edge + m2 = check->vertindex[(k+2) % 3]; + + g_stripverts[g_stripcount + 2] = m2; + g_striptris[g_stripcount] = j; + g_stripcount++; + + g_used[j] = 2; + goto nexttri; + } + } +done: + // clear the temp used flags + for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) + { + if( g_used[j] == 2 ) + g_used[j] = 0; + } + + return g_stripcount; +} + +/* +================ +BuildTris + +Generate a list of trifans or strips +for the model, which holds for all frames +================ +*/ +void BuildTris( void ) +{ + int len, bestlen, besttype; + int bestverts[1024]; + int besttris[1024]; + int type, startv; + int i, j, k; + float s, t; + + // + // build tristrips + // + memset( g_used, 0, sizeof( g_used )); + g_numcommands = 0; + g_numorder = 0; + + for( i = 0; i < m_pAliasHeader->numtris; i++ ) + { + // pick an unused triangle and start the trifan + if( g_used[i] ) continue; + + bestlen = 0; + for( type = 0; type < 2; type++ ) + { + for( startv = 0; startv < 3; startv++ ) + { + if( type == 1 ) len = StripLength( i, startv ); + else len = FanLength( i, startv ); + + if( len > bestlen ) + { + besttype = type; + bestlen = len; + + for( j = 0; j < bestlen + 2; j++ ) + bestverts[j] = g_stripverts[j]; + + for( j = 0; j < bestlen; j++ ) + besttris[j] = g_striptris[j]; + } + } + } + + // mark the tris on the best strip as used + for( j = 0; j < bestlen; j++ ) + g_used[besttris[j]] = 1; + + if( besttype == 1 ) + g_commands[g_numcommands++] = (bestlen + 2); + else g_commands[g_numcommands++] = -(bestlen + 2); + + for( j = 0; j < bestlen + 2; j++ ) + { + // emit a vertex into the reorder buffer + k = bestverts[j]; + g_vertexorder[g_numorder++] = k; + + // emit s/t coords into the commands stream + s = g_stverts[k].s; + t = g_stverts[k].t; + + if( !g_triangles[besttris[0]].facesfront && g_stverts[k].onseam ) + s += m_pAliasHeader->skinwidth / 2; // on back side + s = (s + 0.5f) / m_pAliasHeader->skinwidth; + t = (t + 0.5f) / m_pAliasHeader->skinheight; + + // Carmack use floats and Valve use shorts here... + *(float *)&g_commands[g_numcommands++] = s; + *(float *)&g_commands[g_numcommands++] = t; + } + } + + g_commands[g_numcommands++] = 0; // end of list marker +} + +/* +================ +GL_MakeAliasModelDisplayLists +================ +*/ +void GL_MakeAliasModelDisplayLists( model_t *m ) +{ + trivertex_t *verts; + int i, j; + + BuildTris( ); + + // save the data out + m_pAliasHeader->poseverts = g_numorder; + + m_pAliasHeader->commands = Mem_Alloc( m->mempool, g_numcommands * 4 ); + memcpy( m_pAliasHeader->commands, g_commands, g_numcommands * 4 ); + + m_pAliasHeader->posedata = Mem_Alloc( m->mempool, m_pAliasHeader->numposes * m_pAliasHeader->poseverts * sizeof( trivertex_t )); + verts = m_pAliasHeader->posedata; + + for( i = 0; i < m_pAliasHeader->numposes; i++ ) + { + for( j = 0; j < g_numorder; j++ ) + *verts++ = g_poseverts[i][g_vertexorder[j]]; + } +} + +/* +============================================================================== + +ALIAS MODELS + +============================================================================== +*/ +/* +================= +Mod_LoadAliasFrame +================= +*/ +void *Mod_LoadAliasFrame( void *pin, maliasframedesc_t *frame ) +{ + daliasframe_t *pdaliasframe; + trivertex_t *pinframe; + int i; + + pdaliasframe = (daliasframe_t *)pin; + + Q_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name )); + frame->firstpose = g_posenum; + frame->numposes = 1; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; + frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; + } + + pinframe = (trivertex_t *)(pdaliasframe + 1); + + g_poseverts[g_posenum] = (trivertex_t *)pinframe; + pinframe += m_pAliasHeader->numverts; + g_posenum++; + + return (void *)pinframe; +} + +/* +================= +Mod_LoadAliasGroup +================= +*/ +void *Mod_LoadAliasGroup( void *pin, maliasframedesc_t *frame ) +{ + daliasgroup_t *pingroup; + int i, numframes; + daliasinterval_t *pin_intervals; + void *ptemp; + + pingroup = (daliasgroup_t *)pin; + numframes = pingroup->numframes; + + frame->firstpose = g_posenum; + frame->numposes = numframes; + + for( i = 0; i < 3; i++ ) + { + frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; + frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; + } + + pin_intervals = (daliasinterval_t *)(pingroup + 1); + + // all the intervals are always equal 0.1 so we don't care about them + frame->interval = pin_intervals->interval; + pin_intervals += numframes; + ptemp = (void *)pin_intervals; + + for( i = 0; i < numframes; i++ ) + { + g_poseverts[g_posenum] = (trivertex_t *)((daliasframe_t *)ptemp + 1); + ptemp = (trivertex_t *)((daliasframe_t *)ptemp + 1) + m_pAliasHeader->numverts; + g_posenum++; + } + + return ptemp; +} + +/* +=============== +Mod_CreateSkinData +=============== +*/ +rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) +{ + static rgbdata_t skin; + char name[MAX_QPATH]; + int i; + + skin.width = width; + skin.height = height; + skin.depth = 1; + skin.type = PF_INDEXED_24; + skin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL; + skin.encode = DXT_ENCODE_DEFAULT; + skin.numMips = 1; + skin.buffer = data; + skin.palette = (byte *)&clgame.palette; + skin.size = width * height; + + for( i = 0; i < skin.width * skin.height; i++ ) + { + if( data[i] > 224 && data[i] != 255 ) + { + SetBits( skin.flags, IMAGE_HAS_LUMA ); + break; + } + } + + COM_FileBase( loadmodel->name, name ); + + // for alias models only player can have remap textures + if( mod != NULL && !Q_stricmp( name, "player" )) + { + texture_t *tx = NULL; + int i, size; + + i = mod->numtextures; + mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); + size = width * height + 768; + tx = Mem_Alloc( mod->mempool, sizeof( *tx ) + size ); + mod->textures[i] = tx; + + Q_strncpy( tx->name, "DM_Skin", sizeof( tx->name )); + tx->anim_min = SHIRT_HUE_START; // topcolor start + tx->anim_max = SHIRT_HUE_END; // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + tx->anim_total = PANTS_HUE_END;// bottomcolor end + + tx->width = width; + tx->height = height; + + // the pixels immediately follow the structures + memcpy( (tx+1), data, width * height ); + memcpy( ((byte *)(tx+1)+(width * height)), skin.palette, 768 ); + mod->numtextures++; // done + } + + // make an copy + return FS_CopyImage( &skin ); +} + +void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) +{ + string name, lumaname; + rgbdata_t *pic; + + Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); + Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); + pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + + m_pAliasHeader->gl_texturenum[skinnum][0] = + m_pAliasHeader->gl_texturenum[skinnum][1] = + m_pAliasHeader->gl_texturenum[skinnum][2] = + m_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0, false ); + FS_FreeImage( pic ); + + if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags & TF_HAS_LUMA ) + { + pic = Mod_CreateSkinData( NULL, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->fb_texturenum[skinnum][0] = + m_pAliasHeader->fb_texturenum[skinnum][1] = + m_pAliasHeader->fb_texturenum[skinnum][2] = + m_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA, false ); + FS_FreeImage( pic ); + } + + return ((byte *)(pskintype + 1) + size); +} + +void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) +{ + daliasskininterval_t *pinskinintervals; + daliasskingroup_t *pinskingroup; + string name, lumaname; + rgbdata_t *pic; + int i, j; + + // animating skin group. yuck. + pskintype++; + pinskingroup = (daliasskingroup_t *)pskintype; + pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); + pskintype = (void *)(pinskinintervals + pinskingroup->numskins); + + for( i = 0; i < pinskingroup->numskins; i++ ) + { + Q_snprintf( name, sizeof( name ), "%s_%i_%i", loadmodel->name, skinnum, i ); + pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->gl_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( name, pic, 0, false ); + FS_FreeImage( pic ); + + if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags & TF_HAS_LUMA ) + { + Q_snprintf( lumaname, sizeof( lumaname ), "%s_%i_%i_luma", loadmodel->name, skinnum, i ); + pic = Mod_CreateSkinData( NULL, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); + m_pAliasHeader->fb_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA, false ); + FS_FreeImage( pic ); + } + + pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + } + + for( j = i; i < 4; i++ ) + { + m_pAliasHeader->gl_texturenum[skinnum][i & 3] = m_pAliasHeader->gl_texturenum[skinnum][i - j]; + m_pAliasHeader->fb_texturenum[skinnum][i & 3] = m_pAliasHeader->fb_texturenum[skinnum][i - j]; + } + + return pskintype; +} + +/* +=============== +Mod_LoadAllSkins +=============== +*/ +void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) +{ + int i, size; + + if( numskins < 1 || numskins > MAX_SKINS ) + Host_Error( "Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins ); + + size = m_pAliasHeader->skinwidth * m_pAliasHeader->skinheight; + + for( i = 0; i < numskins; i++ ) + { + if( pskintype->type == ALIAS_SKIN_SINGLE ) + { + pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( pskintype, i, size ); + } + else + { + pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( pskintype, i, size ); + } + } + + return (void *)pskintype; +} + +//========================================================================= +/* +================= +Mod_CalcAliasBounds +================= +*/ +void Mod_CalcAliasBounds( model_t *mod ) +{ + int i, j, k; + float radius; + float dist; + vec3_t v; + + ClearBounds( mod->mins, mod->maxs ); + radius = 0.0f; + + // process verts + for( i = 0; i < m_pAliasHeader->numposes; i++ ) + { + for( j = 0; j < m_pAliasHeader->numverts; j++ ) + { + for( k = 0; k < 3; k++ ) + v[k] = g_poseverts[i][j].v[k] * m_pAliasHeader->scale[k] + m_pAliasHeader->scale_origin[k]; + + AddPointToBounds( v, mod->mins, mod->maxs ); + dist = DotProduct( v, v ); + + if( radius < dist ) + radius = dist; + } + } + + mod->radius = sqrt( radius ); +} + +/* +================= +Mod_LoadAliasModel +================= +*/ +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) +{ + daliashdr_t *pinmodel; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + int numframes, size; + daliasframetype_t *pframetype; + daliasskintype_t *pskintype; + int i, j; + + if( loaded ) *loaded = false; + pinmodel = (daliashdr_t *)buffer; + i = pinmodel->version; + + if( i != ALIAS_VERSION ) + { + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", mod->name, i, ALIAS_VERSION ); + return; + } + + mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + + // allocate space for a working header, plus all the data except the frames, + // skin and group info + size = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t ); + + m_pAliasHeader = Mem_Alloc( mod->mempool, size ); + mod->flags = pinmodel->flags; // share effects flags + + // endian-adjust and copy the data, starting with the alias model header + m_pAliasHeader->boundingradius = pinmodel->boundingradius; + m_pAliasHeader->numskins = pinmodel->numskins; + m_pAliasHeader->skinwidth = pinmodel->skinwidth; + m_pAliasHeader->skinheight = pinmodel->skinheight; + m_pAliasHeader->numverts = pinmodel->numverts; + + if( m_pAliasHeader->numverts <= 0 ) + { + MsgDev( D_ERROR, "model %s has no vertices\n", mod->name ); + return; + } + + if( m_pAliasHeader->numverts > MAXALIASVERTS ) + { + MsgDev( D_ERROR, "model %s has too many vertices\n", mod->name ); + return; + } + + m_pAliasHeader->numtris = pinmodel->numtris; + + if( m_pAliasHeader->numtris <= 0 ) + { + MsgDev( D_ERROR, "model %s has no triangles\n", mod->name ); + return; + } + + m_pAliasHeader->numframes = pinmodel->numframes; + numframes = m_pAliasHeader->numframes; + + if( numframes < 1 ) + { + MsgDev( D_ERROR, "Mod_LoadAliasModel: Invalid # of frames: %d\n", numframes ); + return; + } + + m_pAliasHeader->size = pinmodel->size; +// mod->synctype = pinmodel->synctype; + mod->numframes = m_pAliasHeader->numframes; + + for( i = 0; i < 3; i++ ) + { + m_pAliasHeader->scale[i] = pinmodel->scale[i]; + m_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i]; + m_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i]; + } + + // load the skins + pskintype = (daliasskintype_t *)&pinmodel[1]; + pskintype = Mod_LoadAllSkins( m_pAliasHeader->numskins, pskintype ); + + // load base s and t vertices + pinstverts = (stvert_t *)pskintype; + + for( i = 0; i < m_pAliasHeader->numverts; i++ ) + { + g_stverts[i].onseam = pinstverts[i].onseam; + g_stverts[i].s = pinstverts[i].s; + g_stverts[i].t = pinstverts[i].t; + } + + // load triangle lists + pintriangles = (dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; + + for( i = 0; i < m_pAliasHeader->numtris; i++ ) + { + g_triangles[i].facesfront = pintriangles[i].facesfront; + + for( j = 0; j < 3; j++ ) + g_triangles[i].vertindex[j] = pintriangles[i].vertindex[j]; + } + + // load the frames + pframetype = (daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris]; + g_posenum = 0; + + for( i = 0; i < numframes; i++ ) + { + aliasframetype_t frametype = pframetype->type; + + if( frametype == ALIAS_SINGLE ) + pframetype = (daliasframetype_t *)Mod_LoadAliasFrame( pframetype + 1, &m_pAliasHeader->frames[i] ); + else pframetype = (daliasframetype_t *)Mod_LoadAliasGroup( pframetype + 1, &m_pAliasHeader->frames[i] ); + } + + m_pAliasHeader->numposes = g_posenum; + + Mod_CalcAliasBounds( mod ); + mod->type = mod_alias; + + // build the draw lists + GL_MakeAliasModelDisplayLists( mod ); + + // move the complete, relocatable alias model to the cache + loadmodel->cache.data = m_pAliasHeader; + + if( loaded ) *loaded = true; // done +} + +/* +================= +Mod_UnloadAliasModel +================= +*/ +void Mod_UnloadAliasModel( model_t *mod ) +{ + aliashdr_t *palias; + int i, j; + + Assert( mod != NULL ); + + if( mod->type != mod_alias ) + return; // not an alias + + palias = mod->cache.data; + if( !palias ) return; // already freed + + for( i = 0; i < MAX_SKINS; i++ ) + { + if( !palias->gl_texturenum[i][0] ) + break; + + for( j = 0; j < 4; j++ ) + { + GL_FreeTexture( palias->gl_texturenum[i][j] ); + GL_FreeTexture( palias->fb_texturenum[i][j] ); + } + } + + Mem_FreePool( &mod->mempool ); + memset( mod, 0, sizeof( *mod )); +} + +/* +============================================================= + + ALIAS MODELS + +============================================================= +*/ + +/* +=============== +R_AliasDynamicLight + +similar to R_StudioDynamicLight +=============== +*/ +void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) +{ + movevars_t *mv = &clgame.movevars; + vec3_t lightDir, vecSrc, vecEnd; + vec3_t origin, dist, finalLight; + float add, radius, total; + colorVec light; + uint lnum; + dlight_t *dl; + + if( !plight || !ent ) + return; + + if( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT )) + { + plight->shadelight = 0; + plight->ambientlight = 192; + + VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f ); + VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + return; + } + + // determine plane to get lightvalues from: ceil or floor + if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) + VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); + else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); + + VectorCopy( ent->origin, origin ); + + VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f ); + light.r = light.g = light.b = light.a = 0; + + if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) + { + msurface_t *psurf = NULL; + pmtrace_t trace; + + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) + { + vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f; + } + else + { + vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f; + } + + trace = CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE ); + if( trace.ent > 0 ) psurf = PM_TraceSurface( &clgame.pmove->physents[trace.ent], vecSrc, vecEnd ); + else psurf = PM_TraceSurface( clgame.pmove->physents, vecSrc, vecEnd ); + + if( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )) + { + VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); + + light.r = LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); + light.g = LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); + light.b = LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + } + } + + if(( light.r + light.g + light.b ) == 0 ) + { + colorVec gcolor; + float grad[4]; + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + light = R_LightVec( vecSrc, vecEnd, g_alias.lightspot ); + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + vecSrc[0] -= 16.0f; + vecSrc[1] -= 16.0f; + vecEnd[0] -= 16.0f; + vecEnd[1] -= 16.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] += 32.0f; + vecEnd[0] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[1] += 32.0f; + vecEnd[1] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] -= 32.0f; + vecEnd[0] -= 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3]; + lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3]; + VectorNormalize( lightDir ); + } + + VectorSet( finalLight, light.r, light.g, light.b ); + ent->cvFloorColor = light; + + total = Q_max( Q_max( light.r, light.g ), light.b ); + if( total == 0.0f ) total = 1.0f; + + // scale lightdir by light intentsity + VectorScale( lightDir, total, lightDir ); + + for( lnum = 0, dl = cl_dlights; lnum < MAX_DLIGHTS; lnum++, dl++ ) + { + if( dl->die < g_alias.time || !r_dynamic->value ) + continue; + + VectorSubtract( origin, dl->origin, dist ); + + radius = VectorLength( dist ); + add = dl->radius - radius; + + if( add > 0.0f ) + { + total += add; + + if( radius > 1.0f ) + VectorScale( dist, ( add / radius ), dist ); + else VectorScale( dist, add, dist ); + + VectorAdd( lightDir, dist, lightDir ); + + finalLight[0] += LightToTexGamma( dl->color.r ) * ( add * 512.0f ); + finalLight[1] += LightToTexGamma( dl->color.g ) * ( add * 512.0f ); + finalLight[2] += LightToTexGamma( dl->color.b ) * ( add * 512.0f ); + } + } + + VectorScale( lightDir, 0.9f, lightDir ); + + plight->shadelight = VectorLength( lightDir ); + plight->ambientlight = total - plight->shadelight; + + total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] ); + + if( total > 0.0f ) + { + plight->color[0] = finalLight[0] * ( 1.0f / total ); + plight->color[1] = finalLight[1] * ( 1.0f / total ); + plight->color[2] = finalLight[2] * ( 1.0f / total ); + } + else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + + if( plight->ambientlight > 128 ) + plight->ambientlight = 128; + + if( plight->ambientlight + plight->shadelight > 255 ) + plight->shadelight = 255 - plight->ambientlight; + + VectorNormalize2( lightDir, plight->plightvec ); +} + +/* +=============== +R_AliasSetupLighting + +=============== +*/ +void R_AliasSetupLighting( alight_t *plight ) +{ + if( !m_pAliasHeader || !plight ) + return; + + g_alias.ambientlight = plight->ambientlight; + g_alias.shadelight = plight->shadelight; + VectorCopy( plight->plightvec, g_alias.lightvec ); + VectorCopy( plight->color, g_alias.lightcolor ); + + // transform back to local space + Matrix4x4_VectorIRotate( RI.objectMatrix, g_alias.lightvec, g_alias.lightvec_local ); + VectorNormalize( g_alias.lightvec_local ); +} + +/* +=============== +R_AliasLighting + +=============== +*/ +void R_AliasLighting( float *lv, const vec3_t normal ) +{ + float illum = g_alias.ambientlight; + float r, lightcos; + + lightcos = DotProduct( normal, g_alias.lightvec_local ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += g_alias.shadelight; + + r = SHADE_LAMBERT; + + // do modified hemispherical lighting + if( r <= 1.0f ) + { + r += 1.0f; + lightcos = (( r - 1.0f ) - lightcos) / r; + if( lightcos > 0.0f ) + illum += g_alias.shadelight * lightcos; + } + else + { + lightcos = (lightcos + ( r - 1.0f )) / r; + if( lightcos > 0.0f ) + illum -= g_alias.shadelight * lightcos; + } + + illum = Q_max( illum, 0.0f ); + illum = Q_min( illum, 255.0f ); + *lv = illum * (1.0f / 255.0f); +} + +/* +=============== +R_AliasSetRemapColors + +=============== +*/ +void R_AliasSetRemapColors( int newTop, int newBottom ) +{ + CL_AllocRemapInfo( newTop, newBottom ); + + if( CL_GetRemapInfoForEntity( RI.currententity )) + { + CL_UpdateRemapInfo( newTop, newBottom ); + m_fDoRemap = true; + } +} + +/* +============= +GL_DrawAliasFrame +============= +*/ +void GL_DrawAliasFrame( aliashdr_t *paliashdr ) +{ + float lv_tmp; + trivertex_t *verts0; + trivertex_t *verts1; + vec3_t vert, norm; + int *order; + int count; + + verts0 = verts1 = paliashdr->posedata; + verts0 += g_alias.oldpose * paliashdr->poseverts; + verts1 += g_alias.newpose * paliashdr->poseverts; + order = paliashdr->commands; + + while( 1 ) + { + // get the vertex count and primitive type + count = *order++; + if( !count ) break; // done + + if( count < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + count = -count; + } + else + { + pglBegin( GL_TRIANGLE_STRIP ); + } + + do + { + // texture coordinates come from the draw list + if( GL_Support( GL_ARB_MULTITEXTURE ) && glState.activeTMU > 0 ) + { + GL_MultiTexCoord2f( GL_TEXTURE0, ((float *)order)[0], ((float *)order)[1] ); + GL_MultiTexCoord2f( GL_TEXTURE1, ((float *)order)[0], ((float *)order)[1] ); + } + else + { + pglTexCoord2f( ((float *)order)[0], ((float *)order)[1] ); + } + order += 2; + + VectorLerp( m_bytenormals[verts0->lightnormalindex], g_alias.lerpfrac, m_bytenormals[verts1->lightnormalindex], norm ); + VectorNormalize( norm ); + R_AliasLighting( &lv_tmp, norm ); + pglColor4f( g_alias.lightcolor[0] * lv_tmp, g_alias.lightcolor[1] * lv_tmp, g_alias.lightcolor[2] * lv_tmp, tr.blend ); + VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, vert ); + pglVertex3fv( vert ); + verts0++, verts1++; + } while( --count ); + + pglEnd(); + } +} + +/* +============= +GL_DrawAliasShadow +============= +*/ +void GL_DrawAliasShadow( aliashdr_t *paliashdr ) +{ + trivertex_t *verts0; + trivertex_t *verts1; + float vec_x, vec_y; + vec3_t av, point; + int *order; + float height; + int count; + + if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) + return; + + if( glState.stencilEnabled ) + pglEnable( GL_STENCIL_TEST ); + + height = g_alias.lightspot[2] + 1.0f; + vec_x = -g_alias.lightvec[0] * 8.0f; + vec_y = -g_alias.lightvec[1] * 8.0f; + + r_stats.c_alias_polys += paliashdr->numtris; + + verts0 = verts1 = paliashdr->posedata; + verts0 += g_alias.oldpose * paliashdr->poseverts; + verts1 += g_alias.newpose * paliashdr->poseverts; + order = paliashdr->commands; + + while( 1 ) + { + // get the vertex count and primitive type + count = *order++; + if( !count ) break; // done + + if( count < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + count = -count; + } + else + { + pglBegin( GL_TRIANGLE_STRIP ); + } + + do + { + // texture coordinates come from the draw list + // (skipped for shadows) pglTexCoord2fv ((float *)order); + order += 2; + + // normals and vertexes come from the frame list + VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, av ); + point[0] = av[0] * paliashdr->scale[0] + paliashdr->scale_origin[0]; + point[1] = av[1] * paliashdr->scale[1] + paliashdr->scale_origin[1]; + point[2] = av[2] * paliashdr->scale[2] + paliashdr->scale_origin[2]; + Matrix3x4_VectorTransform( RI.objectMatrix, point, av ); + + point[0] = av[0] - (vec_x * ( av[2] - g_alias.lightspot[2] )); + point[1] = av[1] - (vec_y * ( av[2] - g_alias.lightspot[2] )); + point[2] = g_alias.lightspot[2] + 1.0f; + + pglVertex3fv( point ); + verts0++, verts1++; + + } while( --count ); + + pglEnd(); + } + + if( glState.stencilEnabled ) + pglDisable( GL_STENCIL_TEST ); +} + +/* +==================== +R_AliasLerpMovement + +==================== +*/ +void R_AliasLerpMovement( cl_entity_t *e ) +{ + float f = 1.0f; + + // don't do it if the goalstarttime hasn't updated in a while. + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + if( g_alias.interpolate && ( g_alias.time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) + f = ( g_alias.time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); + + g_alias.lerpfrac = bound( 0.0f, f, 1.0f ); + + if( e->player || e->curstate.movetype != MOVETYPE_STEP ) + return; // monsters only + + // Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_studio.time ); + VectorLerp( e->latched.prevorigin, f, e->curstate.origin, e->origin ); + + if( !VectorCompare( e->curstate.angles, e->latched.prevangles )) + { + vec4_t q, q1, q2; + + AngleQuaternion( e->curstate.angles, q1, false ); + AngleQuaternion( e->latched.prevangles, q2, false ); + QuaternionSlerp( q2, q1, f, q ); + QuaternionAngle( q, e->angles ); + } + else VectorCopy( e->curstate.angles, e->angles ); + + // NOTE: this completely over control about angles and don't broke interpolation + if( FBitSet( e->model->flags, ALIAS_ROTATE )) + e->angles[1] = anglemod( 100.0f * g_alias.time ); +} + +/* +================= +R_SetupAliasFrame + +================= +*/ +void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) +{ + int newpose, oldpose; + int newframe, oldframe; + int numposes, cycle; + float interval; + + oldframe = e->latched.prevframe; + newframe = e->curstate.frame; + + if( newframe < 0 ) + { + newframe = 0; + } + else if( newframe >= paliashdr->numframes ) + { + if( newframe > paliashdr->numframes ) + MsgDev( D_WARN, "R_GetAliasFrame: no such frame %d (%s)\n", newframe, e->model->name ); + newframe = paliashdr->numframes - 1; + } + + if(( oldframe >= paliashdr->numframes ) || ( oldframe < 0 )) + oldframe = newframe; + + numposes = paliashdr->frames[newframe].numposes; + + if( numposes > 1 ) + { + oldpose = newpose = paliashdr->frames[newframe].firstpose; + interval = 1.0f / paliashdr->frames[newframe].interval; + cycle = (int)(g_alias.time * interval); + oldpose += (cycle + 0) % numposes; // lerpframe from + newpose += (cycle + 1) % numposes; // lerpframe to + g_alias.lerpfrac = ( g_alias.time * interval ); + g_alias.lerpfrac -= (int)g_alias.lerpfrac; + } + else + { + oldpose = paliashdr->frames[oldframe].firstpose; + newpose = paliashdr->frames[newframe].firstpose; + } + + g_alias.oldpose = oldpose; + g_alias.newpose = newpose; + + GL_DrawAliasFrame( paliashdr ); +} + +/* +=============== +R_StudioDrawAbsBBox + +=============== +*/ +static void R_AliasDrawAbsBBox( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) +{ + vec3_t p[8]; + int i; + + // looks ugly, skip + if( r_drawentities->value != 5 || e == &clgame.viewent ) + return; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; + p[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; + p[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; + } + + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + TriColor4f( 0.5f, 0.5f, 1.0f, 0.5f ); + TriRenderMode( kRenderTransAdd ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + TriBegin( TRI_QUADS ); + for( i = 0; i < 6; i++ ) + { + TriBrightness( g_alias.shadelight / 255.0f ); + TriVertex3fv( p[boxpnt[i][0]] ); + TriVertex3fv( p[boxpnt[i][1]] ); + TriVertex3fv( p[boxpnt[i][2]] ); + TriVertex3fv( p[boxpnt[i][3]] ); + } + TriEnd(); + + TriRenderMode( kRenderNormal ); +} + +static void R_AliasDrawLightTrace( cl_entity_t *e ) +{ + if( r_drawentities->value == 7 ) + { + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0.5, 0 ); + pglVertex3fv( e->origin ); + pglVertex3fv( g_alias.lightspot ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 1, 0, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( g_alias.lightspot ); + pglEnd(); + pglPointSize( 1.0f ); + + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); + } +} + +/* +================ +R_AliasSetupTimings + +init current time for a given model +================ +*/ +static void R_AliasSetupTimings( void ) +{ + if( RI.drawWorld ) + { + // synchronize with server time + g_alias.time = cl.time; + } + else + { + // menu stuff + g_alias.time = host.realtime; + } + + m_fDoRemap = false; +} + +/* +================= +R_DrawAliasModel + +================= +*/ +void R_DrawAliasModel( cl_entity_t *e ) +{ + model_t *clmodel; + vec3_t absmin, absmax; + remap_info_t *pinfo = NULL; + int anim, skin; + alight_t lighting; + player_info_t *playerinfo; + vec3_t dir, angles; + + clmodel = RI.currententity->model; + + VectorAdd( e->origin, clmodel->mins, absmin ); + VectorAdd( e->origin, clmodel->maxs, absmax ); + + if( R_CullModel( e, absmin, absmax )) + return; + + // + // locate the proper data + // + m_pAliasHeader = (aliashdr_t *)Mod_AliasExtradata( RI.currententity->model ); + if( !m_pAliasHeader ) return; + + // init time + R_AliasSetupTimings(); + + // angles will be modify below keep original + VectorCopy( e->angles, angles ); + + R_AliasLerpMovement( e ); + + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + e->angles[PITCH] = -e->angles[PITCH]; // stupid quake bug + + // don't rotate clients, only aim + if( e->player ) e->angles[PITCH] = 0.0f; + + // + // get lighting information + // + lighting.plightvec = dir; + R_AliasDynamicLight( e, &lighting ); + + r_stats.c_alias_polys += m_pAliasHeader->numtris; + r_stats.c_alias_models_drawn++; + + // + // draw all the triangles + // + + R_RotateForEntity( e ); + + // model and frame independant + R_AliasSetupLighting( &lighting ); + GL_SetRenderMode( e->curstate.rendermode ); + + // setup remapping only for players + if( e->player && ( playerinfo = pfnPlayerInfo( e->curstate.number - 1 )) != NULL ) + { + // get remap colors + int topcolor = bound( 0, playerinfo->topcolor, 13 ); + int bottomcolor = bound( 0, playerinfo->bottomcolor, 13 ); + R_AliasSetRemapColors( topcolor, bottomcolor ); + } + + pglTranslatef( m_pAliasHeader->scale_origin[0], m_pAliasHeader->scale_origin[1], m_pAliasHeader->scale_origin[2] ); + + if( tr.fFlipViewModel ) + pglScalef( m_pAliasHeader->scale[0], -m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] ); + else pglScalef( m_pAliasHeader->scale[0], m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] ); + + anim = (int)(g_alias.time * 10) & 3; + skin = bound( 0, RI.currententity->curstate.skin, m_pAliasHeader->numskins - 1 ); + if( m_fDoRemap ) pinfo = CL_GetRemapInfoForEntity( e ); + + if( r_lightmap->value && !r_fullbright->value ) + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + else if( pinfo != NULL && pinfo->textures[skin] != 0 ) + GL_Bind( GL_TEXTURE0, pinfo->textures[skin] ); // FIXME: allow remapping for skingroups someday + else GL_Bind( GL_TEXTURE0, m_pAliasHeader->gl_texturenum[skin][anim] ); + + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + if( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] ) + { + GL_Bind( GL_TEXTURE1, m_pAliasHeader->fb_texturenum[skin][anim] ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); + } + + pglShadeModel( GL_SMOOTH ); + R_SetupAliasFrame( e, m_pAliasHeader ); + + if( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] ) + GL_CleanUpTextureUnits( 1 ); + + pglShadeModel( GL_FLAT ); + R_LoadIdentity(); + + // get lerped origin + VectorAdd( e->origin, clmodel->mins, absmin ); + VectorAdd( e->origin, clmodel->maxs, absmax ); + + R_AliasDrawAbsBBox( e, absmin, absmax ); + R_AliasDrawLightTrace( e ); + + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + + if( r_shadows.value ) + { + // need to compute transformation matrix + Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, 1.0f ); + pglDisable( GL_TEXTURE_2D ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglEnable( GL_BLEND ); + pglColor4f( 0.0f, 0.0f, 0.0f, 0.5f ); + pglDepthFunc( GL_LESS ); + + GL_DrawAliasShadow( m_pAliasHeader ); + + pglDepthFunc( GL_LEQUAL ); + pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_BLEND ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + R_LoadIdentity(); + } + + // restore original angles + VectorCopy( angles, e->angles ); +} + +//================================================================================== \ No newline at end of file diff --git a/engine/client/gl_backend.c b/engine/client/gl_backend.c new file mode 100644 index 00000000..aa510248 --- /dev/null +++ b/engine/client/gl_backend.c @@ -0,0 +1,721 @@ +/* +gl_backend.c - rendering backend +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mathlib.h" + +char r_speeds_msg[MAX_SYSPATH]; +ref_speeds_t r_stats; // r_speeds counters + +/* +=============== +R_SpeedsMessage +=============== +*/ +qboolean R_SpeedsMessage( char *out, size_t size ) +{ + if( clgame.drawFuncs.R_SpeedsMessage != NULL ) + { + if( clgame.drawFuncs.R_SpeedsMessage( out, size )) + return true; + // otherwise pass to default handler + } + + if( r_speeds->value <= 0 ) return false; + if( !out || !size ) return false; + + Q_strncpy( out, r_speeds_msg, size ); + + return true; +} + +/* +============== +GL_BackendStartFrame +============== +*/ +void GL_BackendStartFrame( void ) +{ + r_speeds_msg[0] = '\0'; +} + +/* +============== +GL_BackendEndFrame +============== +*/ +void GL_BackendEndFrame( void ) +{ + if( r_speeds->value <= 0 || !RI.drawWorld ) + return; + + switch( (int)r_speeds->value ) + { + case 1: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i wpoly, %3i apoly\n%3i epoly, %3i spoly", + r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys ); + break; + case 2: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "visible leafs:\n%3i leafs\ncurrent leaf %3i", + r_stats.c_world_leafs, Mod_PointInLeaf( RI.pvsorigin, cl.worldmodel->nodes ) - cl.worldmodel->leafs ); + break; + case 3: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i alias models drawn\n%3i studio models drawn\n%3i sprites drawn", + r_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn ); + break; + case 4: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i static entities\n%3i normal entities\n%3i server entities", + r_numStatics, r_numEntities - r_numStatics, pfnNumberOfEntities( )); + break; + case 5: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i tempents\n%3i viewbeams\n%3i particles", + r_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count ); + break; + } + + memset( &r_stats, 0, sizeof( r_stats )); +} + +/* +================= +GL_LoadTexMatrix +================= +*/ +void GL_LoadTexMatrix( const matrix4x4 m ) +{ + pglMatrixMode( GL_TEXTURE ); + GL_LoadMatrix( m ); + glState.texIdentityMatrix[glState.activeTMU] = false; +} + +/* +================= +GL_LoadTexMatrixExt +================= +*/ +void GL_LoadTexMatrixExt( const float *glmatrix ) +{ + Assert( glmatrix != NULL ); + pglMatrixMode( GL_TEXTURE ); + pglLoadMatrixf( glmatrix ); + glState.texIdentityMatrix[glState.activeTMU] = false; +} + +/* +================= +GL_LoadMatrix +================= +*/ +void GL_LoadMatrix( const matrix4x4 source ) +{ + GLfloat dest[16]; + + Matrix4x4_ToArrayFloatGL( source, dest ); + pglLoadMatrixf( dest ); +} + +/* +================= +GL_LoadIdentityTexMatrix +================= +*/ +void GL_LoadIdentityTexMatrix( void ) +{ + if( glState.texIdentityMatrix[glState.activeTMU] ) + return; + + pglMatrixMode( GL_TEXTURE ); + pglLoadIdentity(); + glState.texIdentityMatrix[glState.activeTMU] = true; +} + +/* +================= +GL_SelectTexture +================= +*/ +void GL_SelectTexture( GLint tmu ) +{ + if( !GL_Support( GL_ARB_MULTITEXTURE )) + return; + + // don't allow negative texture units + if( tmu < 0 ) return; + + if( tmu >= GL_MaxTextureUnits( )) + { + MsgDev( D_ERROR, "GL_SelectTexture: bad tmu state %i\n", tmu ); + return; + } + + if( glState.activeTMU == tmu ) + return; + + glState.activeTMU = tmu; + + if( pglActiveTextureARB ) + { + pglActiveTextureARB( tmu + GL_TEXTURE0_ARB ); + + if( tmu < glConfig.max_texture_coords ) + pglClientActiveTextureARB( tmu + GL_TEXTURE0_ARB ); + } +} + +/* +============== +GL_DisableAllTexGens +============== +*/ +void GL_DisableAllTexGens( void ) +{ + GL_TexGen( GL_S, 0 ); + GL_TexGen( GL_T, 0 ); + GL_TexGen( GL_R, 0 ); + GL_TexGen( GL_Q, 0 ); +} + +/* +============== +GL_CleanUpTextureUnits +============== +*/ +void GL_CleanUpTextureUnits( int last ) +{ + int i; + + for( i = glState.activeTMU; i > (last - 1); i-- ) + { + // disable upper units + if( glState.currentTextureTargets[i] != GL_NONE ) + { + pglDisable( glState.currentTextureTargets[i] ); + glState.currentTextureTargets[i] = GL_NONE; + glState.currentTextures[i] = -1; // unbind texture + } + + GL_SetTexCoordArrayMode( GL_NONE ); + GL_LoadIdentityTexMatrix(); + GL_DisableAllTexGens(); + GL_SelectTexture( i - 1 ); + } +} + +/* +============== +GL_CleanupAllTextureUnits +============== +*/ +void GL_CleanupAllTextureUnits( void ) +{ + // force to cleanup all the units + GL_SelectTexture( GL_MaxTextureUnits() - 1 ); + GL_CleanUpTextureUnits( 0 ); +} + +/* +================= +GL_MultiTexCoord2f +================= +*/ +void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ) +{ + if( !GL_Support( GL_ARB_MULTITEXTURE )) + return; + + if( pglMultiTexCoord2f != NULL ) + pglMultiTexCoord2f( texture + GL_TEXTURE0_ARB, s, t ); +} + +/* +================= +GL_TextureTarget +================= +*/ +void GL_TextureTarget( uint target ) +{ + if( glState.activeTMU < 0 || glState.activeTMU >= GL_MaxTextureUnits( )) + { + MsgDev( D_ERROR, "GL_TextureTarget: bad tmu state %i\n", glState.activeTMU ); + return; + } + + if( glState.currentTextureTargets[glState.activeTMU] != target ) + { + if( glState.currentTextureTargets[glState.activeTMU] != GL_NONE ) + pglDisable( glState.currentTextureTargets[glState.activeTMU] ); + glState.currentTextureTargets[glState.activeTMU] = target; + if( target != GL_NONE ) + pglEnable( glState.currentTextureTargets[glState.activeTMU] ); + } +} + +/* +================= +GL_TexGen +================= +*/ +void GL_TexGen( GLenum coord, GLenum mode ) +{ + int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int bit, gen; + + switch( coord ) + { + case GL_S: + bit = 1; + gen = GL_TEXTURE_GEN_S; + break; + case GL_T: + bit = 2; + gen = GL_TEXTURE_GEN_T; + break; + case GL_R: + bit = 4; + gen = GL_TEXTURE_GEN_R; + break; + case GL_Q: + bit = 8; + gen = GL_TEXTURE_GEN_Q; + break; + default: return; + } + + if( mode ) + { + if( !( glState.genSTEnabled[tmu] & bit )) + { + pglEnable( gen ); + glState.genSTEnabled[tmu] |= bit; + } + pglTexGeni( coord, GL_TEXTURE_GEN_MODE, mode ); + } + else + { + if( glState.genSTEnabled[tmu] & bit ) + { + pglDisable( gen ); + glState.genSTEnabled[tmu] &= ~bit; + } + } +} + +/* +================= +GL_SetTexCoordArrayMode +================= +*/ +void GL_SetTexCoordArrayMode( GLenum mode ) +{ + int tmu = min( glConfig.max_texture_coords, glState.activeTMU ); + int bit, cmode = glState.texCoordArrayMode[tmu]; + + if( mode == GL_TEXTURE_COORD_ARRAY ) + bit = 1; + else if( mode == GL_TEXTURE_CUBE_MAP_ARB ) + bit = 2; + else bit = 0; + + if( cmode != bit ) + { + if( cmode == 1 ) pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + else if( cmode == 2 ) pglDisable( GL_TEXTURE_CUBE_MAP_ARB ); + + if( bit == 1 ) pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + else if( bit == 2 ) pglEnable( GL_TEXTURE_CUBE_MAP_ARB ); + + glState.texCoordArrayMode[tmu] = bit; + } +} + +/* +================= +GL_Cull +================= +*/ +void GL_Cull( GLenum cull ) +{ + if( !cull ) + { + pglDisable( GL_CULL_FACE ); + glState.faceCull = 0; + return; + } + + pglEnable( GL_CULL_FACE ); + pglCullFace( cull ); + glState.faceCull = cull; +} + +void GL_SetRenderMode( int mode ) +{ + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + switch( mode ) + { + case kRenderNormal: + default: + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + break; + case kRenderTransColor: + case kRenderTransTexture: + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + break; + case kRenderTransAlpha: + pglDisable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + break; + case kRenderGlow: + case kRenderTransAdd: + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + break; + } +} + +/* +============================================================================== + +SCREEN SHOTS + +============================================================================== +*/ +// used for 'env' and 'sky' shots +typedef struct envmap_s +{ + vec3_t angles; + int flags; +} envmap_t; + +const envmap_t r_skyBoxInfo[6] = +{ +{{ 0, 270, 180}, IMAGE_FLIP_X }, +{{ 0, 90, 180}, IMAGE_FLIP_X }, +{{ -90, 0, 180}, IMAGE_FLIP_X }, +{{ 90, 0, 180}, IMAGE_FLIP_X }, +{{ 0, 0, 180}, IMAGE_FLIP_X }, +{{ 0, 180, 180}, IMAGE_FLIP_X }, +}; + +const envmap_t r_envMapInfo[6] = +{ +{{ 0, 0, 90}, 0 }, +{{ 0, 180, -90}, 0 }, +{{ 0, 90, 0}, 0 }, +{{ 0, 270, 180}, 0 }, +{{-90, 180, -90}, 0 }, +{{ 90, 0, 90}, 0 } +}; + +/* +=============== +VID_WriteOverviewScript + +Create overview script file +=============== +*/ +void VID_WriteOverviewScript( void ) +{ + ref_overview_t *ov = &clgame.overView; + string filename; + file_t *f; + + Q_snprintf( filename, sizeof( filename ), "overviews/%s.txt", clgame.mapname ); + + f = FS_Open( filename, "w", false ); + if( !f ) return; + + FS_Printf( f, "// overview description file for %s.bsp\n\n", clgame.mapname ); + FS_Print( f, "global\n{\n" ); + FS_Printf( f, "\tZOOM\t%.2f\n", ov->flZoom ); + FS_Printf( f, "\tORIGIN\t%.2f\t%.2f\t%.2f\n", ov->origin[0], ov->origin[1], ov->origin[2] ); + FS_Printf( f, "\tROTATED\t%i\n", ov->rotated ? 1 : 0 ); + FS_Print( f, "}\n\nlayer\n{\n" ); + FS_Printf( f, "\tIMAGE\t\"overviews/%s.bmp\"\n", clgame.mapname ); + FS_Printf( f, "\tHEIGHT\t%.2f\n", ov->zFar ); // ??? + FS_Print( f, "}\n" ); + + FS_Close( f ); +} + +qboolean VID_ScreenShot( const char *filename, int shot_type ) +{ + rgbdata_t *r_shot; + uint flags = IMAGE_FLIP_Y; + int width = 0, height = 0; + qboolean result; + + r_shot = Mem_Alloc( r_temppool, sizeof( rgbdata_t )); + r_shot->width = (glState.width + 3) & ~3; + r_shot->height = (glState.height + 3) & ~3; + r_shot->flags = IMAGE_HAS_COLOR; + r_shot->type = PF_RGB_24; + r_shot->size = r_shot->width * r_shot->height * PFDesc[r_shot->type].bpp; + r_shot->palette = NULL; + r_shot->buffer = Mem_Alloc( r_temppool, r_shot->size ); + + // get screen frame + pglReadPixels( 0, 0, r_shot->width, r_shot->height, GL_RGB, GL_UNSIGNED_BYTE, r_shot->buffer ); + + switch( shot_type ) + { + case VID_SCREENSHOT: + break; + case VID_SNAPSHOT: + FS_AllowDirectPaths( true ); + break; + case VID_LEVELSHOT: + flags |= IMAGE_RESAMPLE; + if( glState.wideScreen ) + { + height = 480; + width = 800; + } + else + { + height = 480; + width = 640; + } + break; + case VID_MINISHOT: + flags |= IMAGE_RESAMPLE; + height = 200; + width = 320; + break; + case VID_MAPSHOT: + VID_WriteOverviewScript(); // store overview script too + flags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format + height = 768; + width = 1024; + break; + } + + Image_Process( &r_shot, width, height, flags, NULL ); + + // write image + result = FS_SaveImage( filename, r_shot ); + host.write_to_clipboard = false; // disable write to clipboard + FS_AllowDirectPaths( false ); // always reset after store screenshot + FS_FreeImage( r_shot ); + + return result; +} + +/* +================= +VID_CubemapShot +================= +*/ +qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ) +{ + rgbdata_t *r_shot, *r_side; + byte *temp = NULL; + byte *buffer = NULL; + string basename; + int i = 1, flags, result; + + if( !RI.drawWorld || !cl.worldmodel ) + return false; + + // make sure the specified size is valid + while( i < size ) i<<=1; + + if( i != size ) return false; + if( size > glState.width || size > glState.height ) + return false; + + // setup refdef + RI.params |= RP_ENVVIEW; // do not render non-bmodel entities + + // alloc space + temp = Mem_Alloc( r_temppool, size * size * 3 ); + buffer = Mem_Alloc( r_temppool, size * size * 3 * 6 ); + r_shot = Mem_Alloc( r_temppool, sizeof( rgbdata_t )); + r_side = Mem_Alloc( r_temppool, sizeof( rgbdata_t )); + + // use client vieworg + if( !vieworg ) vieworg = RI.vieworg; + + for( i = 0; i < 6; i++ ) + { + // go into 3d mode + R_Set2DMode( false ); + + if( skyshot ) + { + R_DrawCubemapView( vieworg, r_skyBoxInfo[i].angles, size ); + flags = r_skyBoxInfo[i].flags; + } + else + { + R_DrawCubemapView( vieworg, r_envMapInfo[i].angles, size ); + flags = r_envMapInfo[i].flags; + } + + pglReadPixels( 0, 0, size, size, GL_RGB, GL_UNSIGNED_BYTE, temp ); + r_side->flags = IMAGE_HAS_COLOR; + r_side->width = r_side->height = size; + r_side->type = PF_RGB_24; + r_side->size = r_side->width * r_side->height * 3; + r_side->buffer = temp; + + if( flags ) Image_Process( &r_side, 0, 0, flags, NULL ); + memcpy( buffer + (size * size * 3 * i), r_side->buffer, size * size * 3 ); + } + + RI.params &= ~RP_ENVVIEW; + + r_shot->flags = IMAGE_HAS_COLOR; + r_shot->flags |= (skyshot) ? IMAGE_SKYBOX : IMAGE_CUBEMAP; + r_shot->width = size; + r_shot->height = size; + r_shot->type = PF_RGB_24; + r_shot->size = r_shot->width * r_shot->height * 3 * 6; + r_shot->palette = NULL; + r_shot->buffer = buffer; + + // make sure what we have right extension + Q_strncpy( basename, base, MAX_STRING ); + COM_StripExtension( basename ); + COM_DefaultExtension( basename, ".tga" ); + + // write image as 6 sides + result = FS_SaveImage( basename, r_shot ); + FS_FreeImage( r_shot ); + FS_FreeImage( r_side ); + + return result; +} + +//======================================================= + +/* +=============== +R_ShowTextures + +Draw all the images to the screen, on top of whatever +was there. This is used to test for texture thrashing. +=============== +*/ +void R_ShowTextures( void ) +{ + gltexture_t *image; + float x, y, w, h; + int total, start, end; + int i, j, k, base_w, base_h; + rgba_t color = { 192, 192, 192, 255 }; + int charHeight, numTries = 0; + static qboolean showHelp = true; + string shortname; + + if( !gl_showtextures->value ) + return; + + if( showHelp ) + { + CL_CenterPrint( "use '<-' and '->' keys to change atlas page, ESC to quit", 0.25f ); + showHelp = false; + } + + GL_SetRenderMode( kRenderNormal ); + pglClear( GL_COLOR_BUFFER_BIT ); + pglFinish(); + + base_w = 8; // textures view by horizontal + base_h = 6; // textures view by vertical + +rebuild_page: + total = base_w * base_h; + start = total * (gl_showtextures->value - 1); + end = total * gl_showtextures->value; + if( end > MAX_TEXTURES ) end = MAX_TEXTURES; + + w = glState.width / base_w; + h = glState.height / base_h; + + Con_DrawStringLen( NULL, NULL, &charHeight ); + + for( i = j = 0; i < MAX_TEXTURES; i++ ) + { + image = R_GetTexture( i ); + if( j == start ) break; // found start + if( pglIsTexture( image->texnum )) j++; + } + + if( i == MAX_TEXTURES && gl_showtextures->value != 1 ) + { + // bad case, rewind to one and try again + Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 )); + if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop + } + + for( k = 0; i < MAX_TEXTURES; i++ ) + { + if( j == end ) break; // page is full + + image = R_GetTexture( i ); + if( !pglIsTexture( image->texnum )) + continue; + + x = k % base_w * w; + y = k / base_w * h; + + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_Bind( GL_TEXTURE0, i ); // NOTE: don't use image->texnum here, because skybox has a 'wrong' indexes + + if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) + pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0, 0 ); + pglVertex2f( x, y ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( image->width, 0 ); + else pglTexCoord2f( 1, 0 ); + pglVertex2f( x + w, y ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( image->width, image->height ); + else pglTexCoord2f( 1, 1 ); + pglVertex2f( x + w, y + h ); + if( image->target == GL_TEXTURE_RECTANGLE_EXT ) + pglTexCoord2f( 0, image->height ); + else pglTexCoord2f( 0, 1 ); + pglVertex2f( x, y + h ); + pglEnd(); + + if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE )) + pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); + + COM_FileBase( image->name, shortname ); + if( Q_strlen( shortname ) > 18 ) + { + // cutoff too long names, it looks ugly + shortname[16] = '.'; + shortname[17] = '.'; + shortname[18] = '\0'; + } + Con_DrawString( x + 1, y + h - charHeight, shortname, color ); + j++, k++; + } + + CL_DrawCenterPrint (); + pglFinish(); +} \ No newline at end of file diff --git a/engine/client/gl_beams.c b/engine/client/gl_beams.c new file mode 100644 index 00000000..056f9684 --- /dev/null +++ b/engine/client/gl_beams.c @@ -0,0 +1,2032 @@ +/* +gl_beams.c - beams rendering +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "r_efx.h" +#include "event_flags.h" +#include "entity_types.h" +#include "triangleapi.h" +#include "customentity.h" +#include "cl_tent.h" +#include "pm_local.h" +#include "gl_local.h" +#include "studio.h" + +#define NOISE_DIVISIONS 64 // don't touch - many tripmines cause the crash when it equal 128 + +typedef struct +{ + vec3_t pos; + float texcoord; // Y texture coordinate + float width; +} beamseg_t; + +/* +============================================================== + +FRACTAL NOISE + +============================================================== +*/ +static float rgNoise[NOISE_DIVISIONS+1]; // global noise array + +// freq2 += step * 0.1; +// Fractal noise generator, power of 2 wavelength +static void FracNoise( float *noise, int divs ) +{ + int div2; + + div2 = divs >> 1; + if( divs < 2 ) return; + + // noise is normalized to +/- scale + noise[div2] = ( noise[0] + noise[divs] ) * 0.5f + divs * COM_RandomFloat( -0.125f, 0.125f ); + + if( div2 > 1 ) + { + FracNoise( &noise[div2], div2 ); + FracNoise( noise, div2 ); + } +} + +static void SineNoise( float *noise, int divs ) +{ + float freq = 0; + float step = M_PI / (float)divs; + int i; + + for( i = 0; i < divs; i++ ) + { + noise[i] = sin( freq ); + freq += step; + } +} + +/* +============================================================== + +BEAM MATHLIB + +============================================================== +*/ +static void R_BeamComputePerpendicular( const vec3_t vecBeamDelta, vec3_t pPerp ) +{ + // direction in worldspace of the center of the beam + vec3_t vecBeamCenter; + + VectorNormalize2( vecBeamDelta, vecBeamCenter ); + CrossProduct( RI.vforward, vecBeamCenter, pPerp ); + VectorNormalize( pPerp ); +} + +static void R_BeamComputeNormal( const vec3_t vStartPos, const vec3_t vNextPos, vec3_t pNormal ) +{ + // vTangentY = line vector for beam + vec3_t vTangentY, vDirToBeam; + + VectorSubtract( vStartPos, vNextPos, vTangentY ); + + // vDirToBeam = vector from viewer origin to beam + VectorSubtract( vStartPos, RI.vieworg, vDirToBeam ); + + // get a vector that is perpendicular to us and perpendicular to the beam. + // this is used to fatten the beam. + CrossProduct( vTangentY, vDirToBeam, pNormal ); + VectorNormalizeFast( pNormal ); +} + +/* +============================================================== + +BEAM ALLOCATE & PROCESSING + +============================================================== +*/ +/* +============== +R_BeamAlloc + +============== +*/ +BEAM *R_BeamAlloc( void ) +{ + BEAM *pBeam; + + if( !cl_free_beams ) + return NULL; + + pBeam = cl_free_beams; + cl_free_beams = pBeam->next; + memset( pBeam, 0, sizeof( *pBeam )); + pBeam->next = cl_active_beams; + cl_active_beams = pBeam; + pBeam->die = cl.time; + + return pBeam; +} + +/* +============== +R_BeamFree + +============== +*/ +void R_BeamFree( BEAM *pBeam ) +{ + // free particles that have died off. + R_FreeDeadParticles( &pBeam->particles ); + + // now link into free list; + pBeam->next = cl_free_beams; + cl_free_beams = pBeam; +} + +/* +============== +R_BeamSetup + +generic function. all beams must be +passed through this +============== +*/ +void R_BeamSetup( BEAM *pbeam, vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) +{ + model_t *sprite = CL_ModelHandle( modelIndex ); + + if( !sprite ) return; + + pbeam->type = BEAM_POINTS; + pbeam->modelIndex = modelIndex; + pbeam->frame = 0; + pbeam->frameRate = 0; + pbeam->frameCount = sprite->numframes; + + VectorCopy( start, pbeam->source ); + VectorCopy( end, pbeam->target ); + VectorSubtract( end, start, pbeam->delta ); + + pbeam->freq = speed * cl.time; + pbeam->die = life + cl.time; + pbeam->amplitude = amplitude; + pbeam->brightness = brightness; + pbeam->width = width; + pbeam->speed = speed; + + if( amplitude >= 0.50f ) + pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels + else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + + pbeam->pFollowModel = NULL; + pbeam->flags = 0; +} + +/* +============== +R_BeamSetAttributes + +set beam attributes +============== +*/ +void R_BeamSetAttributes( BEAM *pbeam, float r, float g, float b, float framerate, int startFrame ) +{ + pbeam->frame = (float)startFrame; + pbeam->frameRate = framerate; + pbeam->r = r; + pbeam->g = g; + pbeam->b = b; +} + +/* +============== +R_BeamLightning + +template for new beams +============== +*/ +BEAM *R_BeamLightning( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ) +{ + BEAM *pbeam = R_BeamAlloc(); + + if( !pbeam ) return NULL; + pbeam->die = cl.time; + + if( modelIndex < 0 ) + return NULL; + + R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); + + return pbeam; +} + +/* +============== +R_BeamGetEntity + +extract entity number from index +handle user entities +============== +*/ +static cl_entity_t *R_BeamGetEntity( int index ) +{ + if( index < 0 ) + return clgame.dllFuncs.pfnGetUserEntity( BEAMENT_ENTITY( -index )); + return CL_GetEntityByIndex( BEAMENT_ENTITY( index )); +} + +/* +============== +R_BeamComputePoint + +compute attachment point for beam +============== +*/ +static qboolean R_BeamComputePoint( int beamEnt, vec3_t pt ) +{ + cl_entity_t *ent; + int attach; + + ent = R_BeamGetEntity( beamEnt ); + + if( beamEnt < 0 ) + attach = BEAMENT_ATTACHMENT( -beamEnt ); + else attach = BEAMENT_ATTACHMENT( beamEnt ); + + if( !ent ) + { + MsgDev( D_ERROR, "R_BeamComputePoint: invalid entity %i\n", BEAMENT_ENTITY( beamEnt )); + VectorClear( pt ); + return false; + } + + // get attachment + if( attach > 0 ) + VectorCopy( ent->attachment[attach - 1], pt ); + else if(( ent->index - 1 ) == cl.playernum ) + VectorCopy( cl.simorg, pt ); + else VectorCopy( ent->origin, pt ); + + return true; +} + +/* +============== +R_BeamRecomputeEndpoints + +Recomputes beam endpoints.. +============== +*/ +qboolean R_BeamRecomputeEndpoints( BEAM *pbeam ) +{ + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) + { + cl_entity_t *start = R_BeamGetEntity( pbeam->startEntity ); + + if( R_BeamComputePoint( pbeam->startEntity, pbeam->source )) + { + if( !pbeam->pFollowModel ) + pbeam->pFollowModel = start->model; + SetBits( pbeam->flags, FBEAM_STARTVISIBLE ); + } + else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) + { + ClearBits( pbeam->flags, FBEAM_STARTENTITY ); + } + } + + if( FBitSet( pbeam->flags, FBEAM_ENDENTITY )) + { + cl_entity_t *end = R_BeamGetEntity( pbeam->endEntity ); + + if( R_BeamComputePoint( pbeam->endEntity, pbeam->target )) + { + if( !pbeam->pFollowModel ) + pbeam->pFollowModel = end->model; + SetBits( pbeam->flags, FBEAM_ENDVISIBLE ); + } + else if( !FBitSet( pbeam->flags, FBEAM_FOREVER )) + { + ClearBits( pbeam->flags, FBEAM_ENDENTITY ); + pbeam->die = cl.time; + return false; + } + else + { + return false; + } + } + + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY ) && !FBitSet( pbeam->flags, FBEAM_STARTVISIBLE )) + return false; + return true; +} + +/* +============== +R_BeamCull + +Cull the beam by bbox +============== +*/ +qboolean R_BeamCull( const vec3_t start, const vec3_t end, qboolean pvsOnly ) +{ + vec3_t mins, maxs; + int i; + + for( i = 0; i < 3; i++ ) + { + if( start[i] < end[i] ) + { + mins[i] = start[i]; + maxs[i] = end[i]; + } + else + { + mins[i] = end[i]; + maxs[i] = start[i]; + } + + // don't let it be zero sized + if( mins[i] == maxs[i] ) + maxs[i] += 1.0f; + } + + // check bbox + if( Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( ))) + { + if( pvsOnly || !R_CullBox( mins, maxs )) + { + // beam is visible + return false; + } + } + + // beam is culled + return true; +} + +/* +============================================================== + +BEAM DRAW METHODS + +============================================================== +*/ +/* +================ +R_DrawSegs + +general code for drawing beams +================ +*/ +static void R_DrawSegs( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments, int flags ) +{ + int noiseIndex, noiseStep; + int i, total_segs, segs_drawn; + float div, length, fraction, factor; + float flMaxWidth, vLast, vStep, brightness; + vec3_t perp1, vLastNormal; + beamseg_t curSeg; + + if( segments < 2 ) return; + + length = VectorLength( delta ); + flMaxWidth = width * 0.5f; + div = 1.0f / ( segments - 1 ); + + if( length * div < flMaxWidth * 1.414f ) + { + // here, we have too many segments; we could get overlap... so lets have less segments + segments = (int)( length / ( flMaxWidth * 1.414f )) + 1.0f; + if( segments < 2 ) segments = 2; + } + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + div = 1.0f / (segments - 1); + length *= 0.01f; + + // UNDONE: Expose texture length scale factor to control "fuzziness" + vStep = length * div; // Texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + + if( flags & FBEAM_SINENOISE ) + { + if( segments < 16 ) + { + segments = 16; + div = 1.0f / ( segments - 1 ); + } + + scale *= 100.0f; + length = segments * 0.1f; + } + else + { + scale *= length; + } + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ); + noiseIndex = 0; + + if( FBitSet( flags, FBEAM_SINENOISE )) + { + noiseIndex = 0; + } + + brightness = 1.0f; + + if( FBitSet( flags, FBEAM_SHADEIN )) + { + brightness = 0; + } + + // Choose two vectors that are perpendicular to the beam + R_BeamComputePerpendicular( delta, perp1 ); + + total_segs = segments; + segs_drawn = 0; + + // specify all the segments. + for( i = 0; i < segments; i++ ) + { + beamseg_t nextSeg; + vec3_t vPoint1, vPoint2; + + Assert( noiseIndex < ( NOISE_DIVISIONS << 16 )); + + fraction = i * div; + + if( FBitSet( flags, FBEAM_SHADEIN ) && FBitSet( flags, FBEAM_SHADEOUT )) + { + if( fraction < 0.5f ) brightness = 2.0f * fraction; + else brightness = 2.0f * ( 1.0f - fraction ); + } + else if( FBitSet( flags, FBEAM_SHADEIN )) + { + brightness = fraction; + } + else if( FBitSet( flags, FBEAM_SHADEOUT )) + { + brightness = 1.0f - fraction; + } + + VectorMA( source, fraction, delta, nextSeg.pos ); + + // distort using noise + if( scale != 0 ) + { + factor = rgNoise[noiseIndex>>16] * scale; + + if( FBitSet( flags, FBEAM_SINENOISE )) + { + float s, c; + + SinCos( fraction * M_PI * length + freq, &s, &c ); + VectorMA( nextSeg.pos, (factor * s), RI.vup, nextSeg.pos ); + + // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + VectorMA( nextSeg.pos, (factor * c), RI.vright, nextSeg.pos ); + } + else + { + VectorMA( nextSeg.pos, factor, perp1, nextSeg.pos ); + } + } + + // specify the next segment. + nextSeg.width = width * 2.0f; + nextSeg.texcoord = vLast; + + if( segs_drawn > 0 ) + { + // Get a vector that is perpendicular to us and perpendicular to the beam. + // This is used to fatten the beam. + vec3_t vNormal, vAveNormal; + + R_BeamComputeNormal( curSeg.pos, nextSeg.pos, vNormal ); + + if( segs_drawn > 1 ) + { + // Average this with the previous normal + VectorAdd( vNormal, vLastNormal, vAveNormal ); + VectorScale( vAveNormal, 0.5f, vAveNormal ); + VectorNormalizeFast( vAveNormal ); + } + else + { + VectorCopy( vNormal, vAveNormal ); + } + + VectorCopy( vNormal, vLastNormal ); + + // draw regular segment + VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vAveNormal, vPoint1 ); + VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vAveNormal, vPoint2 ); + + pglTexCoord2f( 0.0f, curSeg.texcoord ); + TriBrightness( brightness ); + pglNormal3fv( vAveNormal ); + pglVertex3fv( vPoint1 ); + + pglTexCoord2f( 1.0f, curSeg.texcoord ); + TriBrightness( brightness ); + pglNormal3fv( vAveNormal ); + pglVertex3fv( vPoint2 ); + } + + curSeg = nextSeg; + segs_drawn++; + + if( segs_drawn == total_segs ) + { + // draw the last segment + VectorMA( curSeg.pos, ( curSeg.width * 0.5f ), vLastNormal, vPoint1 ); + VectorMA( curSeg.pos, (-curSeg.width * 0.5f ), vLastNormal, vPoint2 ); + + // specify the points. + pglTexCoord2f( 0.0f, curSeg.texcoord ); + TriBrightness( brightness ); + pglNormal3fv( vLastNormal ); + pglVertex3fv( vPoint1 ); + + pglTexCoord2f( 1.0f, curSeg.texcoord ); + TriBrightness( brightness ); + pglNormal3fv( vLastNormal ); + pglVertex3fv( vPoint2 ); + } + + vLast += vStep; // Advance texture scroll (v axis only) + noiseIndex += noiseStep; + } +} + +/* +================ +R_DrawTorus + +Draw beamtours +================ +*/ +void R_DrawTorus( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + int i, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast, tmp, normal; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01; + if( length < 0.5 ) length = 0.5; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + + vStep = length * div; // Texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ); + noiseIndex = 0; + + for( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + SinCos( fraction * M_PI2, &s, &c ); + + point[0] = s * freq * delta[2] + source[0]; + point[1] = c * freq * delta[2] + source[1]; + point[2] = source[2]; + + // distort using noise + if( scale != 0 ) + { + if(( noiseIndex >> 16 ) < NOISE_DIVISIONS ) + { + factor = rgNoise[noiseIndex>>16] * scale; + VectorMA( point, factor, RI.vup, point ); + + // rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = rgNoise[noiseIndex>>16] * scale * cos( fraction * M_PI * 3 + freq ); + VectorMA( point, factor, RI.vright, point ); + } + } + + // Transform point into screen space + TriWorldToScreen( point, screen ); + + if( i != 0 ) + { + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( RI.vup, -tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( point, width, normal, last1 ); + VectorMA( point, -width, normal, last2 ); + + vLast += vStep; // advance texture scroll (v axis only) + TriTexCoord2f( 1, vLast ); + TriVertex3fv( last2 ); + TriTexCoord2f( 0, vLast ); + TriVertex3fv( last1 ); + } + + VectorCopy( screen, screenLast ); + noiseIndex += noiseStep; + } +} + +/* +================ +R_DrawDisk + +Draw beamdisk +================ +*/ +void R_DrawDisk( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + float div, length, fraction; + float w, vLast, vStep; + vec3_t point; + int i; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) // UNDONE: Allow more segments? + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01f; + if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + vStep = length * div; // Texture length texels per space pixel + + // scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + // clamp the beam width + w = fmod( freq, width ) * delta[2]; + + // NOTE: we must force the degenerate triangles to be on the edge + for( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + VectorCopy( source, point ); + + TriBrightness( 1.0f ); + TriTexCoord2f( 1.0f, vLast ); + TriVertex3fv( point ); + + SinCos( fraction * M_PI2, &s, &c ); + point[0] = s * w + source[0]; + point[1] = c * w + source[1]; + point[2] = source[2]; + + TriBrightness( 1.0f ); + TriTexCoord2f( 0.0f, vLast ); + TriVertex3fv( point ); + + vLast += vStep; // advance texture scroll (v axis only) + } +} + +/* +================ +R_DrawCylinder + +Draw beam cylinder +================ +*/ +void R_DrawCylinder( vec3_t source, vec3_t delta, float width, float scale, float freq, float speed, int segments ) +{ + float div, length, fraction; + float vLast, vStep; + vec3_t point; + int i; + + if( segments < 2 ) + return; + + if( segments > NOISE_DIVISIONS ) + segments = NOISE_DIVISIONS; + + length = VectorLength( delta ) * 0.01f; + if( length < 0.5f ) length = 0.5f; // don't lose all of the noise/texture on short beams + + div = 1.0f / (segments - 1); + vStep = length * div; // texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1 ); + scale = scale * length; + + for ( i = 0; i < segments; i++ ) + { + float s, c; + + fraction = i * div; + SinCos( fraction * M_PI2, &s, &c ); + + point[0] = s * freq * delta[2] + source[0]; + point[1] = c * freq * delta[2] + source[1]; + point[2] = source[2] + width; + + TriBrightness( 0 ); + TriTexCoord2f( 1, vLast ); + TriVertex3fv( point ); + + point[0] = s * freq * ( delta[2] + width ) + source[0]; + point[1] = c * freq * ( delta[2] + width ) + source[1]; + point[2] = source[2] - width; + + TriBrightness( 1 ); + TriTexCoord2f( 0, vLast ); + TriVertex3fv( point ); + + vLast += vStep; // Advance texture scroll (v axis only) + } +} + +/* +============== +R_DrawBeamFollow + +drawi followed beam +============== +*/ +void R_DrawBeamFollow( BEAM *pbeam, float frametime ) +{ + particle_t *pnew, *particles; + float fraction, div, vLast, vStep; + vec3_t last1, last2, tmp, screen; + vec3_t delta, screenLast, normal; + + R_FreeDeadParticles( &pbeam->particles ); + + particles = pbeam->particles; + pnew = NULL; + + div = 0; + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY )) + { + if( particles ) + { + VectorSubtract( particles->org, pbeam->source, delta ); + div = VectorLength( delta ); + + if( div >= 32 && cl_free_particles ) + { + pnew = cl_free_particles; + cl_free_particles = pnew->next; + } + } + else if( cl_free_particles ) + { + pnew = cl_free_particles; + cl_free_particles = pnew->next; + div = 0; + } + } + + if( pnew ) + { + VectorCopy( pbeam->source, pnew->org ); + pnew->die = cl.time + pbeam->amplitude; + VectorClear( pnew->vel ); + + pnew->next = particles; + pbeam->particles = pnew; + particles = pnew; + } + + // nothing to draw + if( !particles ) return; + + if( !pnew && div != 0 ) + { + VectorCopy( pbeam->source, delta ); + TriWorldToScreen( pbeam->source, screenLast ); + TriWorldToScreen( particles->org, screen ); + } + else if( particles && particles->next ) + { + VectorCopy( particles->org, delta ); + TriWorldToScreen( particles->org, screenLast ); + TriWorldToScreen( particles->next->org, screen ); + particles = particles->next; + } + else + { + return; + } + + // UNDONE: This won't work, screen and screenLast must be extrapolated here to fix the + // first beam segment for this trail + + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // Build point along noraml line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); // Build point along normal line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( delta, pbeam->width, normal, last1 ); + VectorMA( delta, -pbeam->width, normal, last2 ); + + div = 1.0 / pbeam->amplitude; + fraction = ( pbeam->die - cl.time ) * div; + + vLast = 0.0; + vStep = 1.0; + + while( particles ) + { + TriBrightness( fraction ); + TriTexCoord2f( 1, 1 ); + TriVertex3fv( last2 ); + TriBrightness( fraction ); + TriTexCoord2f( 0, 1 ); + TriVertex3fv( last1 ); + + // Transform point into screen space + TriWorldToScreen( particles->org, screen ); + // Build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + VectorScale( RI.vup, tmp[0], normal ); // Build point along noraml line (normal is -y, x) + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( particles->org, pbeam->width, normal, last1 ); + VectorMA( particles->org, -pbeam->width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + + if( particles->next != NULL ) + { + fraction = (particles->die - cl.time) * div; + } + else + { + fraction = 0.0; + } + + TriBrightness( fraction ); + TriTexCoord2f( 0, 0 ); + TriVertex3fv( last1 ); + TriBrightness( fraction ); + TriTexCoord2f( 1, 0 ); + TriVertex3fv( last2 ); + + VectorCopy( screen, screenLast ); + + particles = particles->next; + } + + // drift popcorn trail if there is a velocity + particles = pbeam->particles; + + while( particles ) + { + VectorMA( particles->org, frametime, particles->vel, particles->org ); + particles = particles->next; + } +} + +/* +================ +R_DrawRing + +Draw beamring +================ +*/ +void R_DrawRing( vec3_t source, vec3_t delta, float width, float amplitude, float freq, float speed, int segments ) +{ + int i, j, noiseIndex, noiseStep; + float div, length, fraction, factor, vLast, vStep; + vec3_t last1, last2, point, screen, screenLast; + vec3_t tmp, normal, center, xaxis, yaxis; + float radius, x, y, scale; + + if( segments < 2 ) + return; + + VectorClear( screenLast ); + segments = segments * M_PI; + + if( segments > NOISE_DIVISIONS * 8 ) + segments = NOISE_DIVISIONS * 8; + + length = VectorLength( delta ) * 0.01f * M_PI; + if( length < 0.5f ) length = 0.5f; // Don't lose all of the noise/texture on short beams + + div = 1.0f / ( segments - 1 ); + + vStep = length * div / 8.0f; // texture length texels per space pixel + + // Scroll speed 3.5 -- initial texture position, scrolls 3.5/sec (1.0 is entire texture) + vLast = fmod( freq * speed, 1.0f ); + scale = amplitude * length / 8.0f; + + // Iterator to resample noise waveform (it needs to be generated in powers of 2) + noiseStep = (int)((float)( NOISE_DIVISIONS - 1 ) * div * 65536.0f ) * 8; + noiseIndex = 0; + + VectorScale( delta, 0.5f, delta ); + VectorAdd( source, delta, center ); + + VectorCopy( delta, xaxis ); + radius = VectorLength( xaxis ); + + // cull beamring + // -------------------------------- + // Compute box center +/- radius + VectorSet( last1, radius, radius, scale ); + VectorAdd( center, last1, tmp ); // maxs + VectorSubtract( center, last1, screen ); // mins + + if( !cl.worldmodel ) + return; + + // is that box in PVS && frustum? + if( !Mod_BoxVisible( screen, tmp, Mod_GetCurrentVis( )) || R_CullBox( screen, tmp )) + { + return; + } + + VectorSet( yaxis, xaxis[1], -xaxis[0], 0.0f ); + VectorNormalize( yaxis ); + VectorScale( yaxis, radius, yaxis ); + + j = segments / 8; + + for( i = 0; i < segments + 1; i++ ) + { + fraction = i * div; + SinCos( fraction * M_PI2, &x, &y ); + + VectorMAMAM( x, xaxis, y, yaxis, 1.0f, center, point ); + + // distort using noise + factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + VectorMA( point, factor, RI.vup, point ); + + // Rotate the noise along the perpendicluar axis a bit to keep the bolt from looking diagonal + factor = rgNoise[(noiseIndex >> 16) & (NOISE_DIVISIONS - 1)] * scale; + factor *= cos( fraction * M_PI * 24 + freq ); + VectorMA( point, factor, RI.vright, point ); + + // Transform point into screen space + TriWorldToScreen( point, screen ); + + if( i != 0 ) + { + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // Build point along normal line (normal is -y, x) + VectorScale( RI.vup, tmp[0], normal ); + VectorMA( normal, tmp[1], RI.vright, normal ); + + // Make a wide line + VectorMA( point, width, normal, last1 ); + VectorMA( point, -width, normal, last2 ); + + vLast += vStep; // Advance texture scroll (v axis only) + TriTexCoord2f( 1.0f, vLast ); + TriVertex3fv( last2 ); + TriTexCoord2f( 0.0f, vLast ); + TriVertex3fv( last1 ); + } + + VectorCopy( screen, screenLast ); + noiseIndex += noiseStep; + j--; + + if( j == 0 && amplitude != 0 ) + { + j = segments / 8; + FracNoise( rgNoise, NOISE_DIVISIONS ); + } + } +} + +/* +============== +R_BeamDraw + +Update beam vars and draw it +============== +*/ +void R_BeamDraw( BEAM *pbeam, float frametime ) +{ + model_t *model; + vec3_t delta; + + model = CL_ModelHandle( pbeam->modelIndex ); + SetBits( pbeam->flags, FBEAM_ISACTIVE ); + + if( !model || model->type != mod_sprite ) + { + pbeam->flags &= ~FBEAM_ISACTIVE; // force to ignore + pbeam->die = cl.time; + return; + } + + // update frequency + pbeam->freq += frametime; + + // generate fractal noise + if( frametime != 0.0f ) + { + rgNoise[0] = 0; + rgNoise[NOISE_DIVISIONS] = 0; + } + + if( pbeam->amplitude != 0 && frametime != 0.0f ) + { + if( FBitSet( pbeam->flags, FBEAM_SINENOISE )) + SineNoise( rgNoise, NOISE_DIVISIONS ); + else FracNoise( rgNoise, NOISE_DIVISIONS ); + } + + // update end points + if( FBitSet( pbeam->flags, FBEAM_STARTENTITY|FBEAM_ENDENTITY )) + { + // makes sure attachment[0] + attachment[1] are valid + if( !R_BeamRecomputeEndpoints( pbeam )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); // force to ignore + return; + } + + // compute segments from the new endpoints + VectorSubtract( pbeam->target, pbeam->source, delta ); + VectorClear( pbeam->delta ); + + if( VectorLength( delta ) > 0.0000001f ) + VectorCopy( delta, pbeam->delta ); + + if( pbeam->amplitude >= 0.50f ) + pbeam->segments = VectorLength( pbeam->delta ) * 0.25f + 3.0f; // one per 4 pixels + else pbeam->segments = VectorLength( pbeam->delta ) * 0.075f + 3.0f; // one per 16 pixels + } + + if( pbeam->type == TE_BEAMPOINTS && R_BeamCull( pbeam->source, pbeam->target, 0 )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); + return; + } + + // don't draw really short or inactive beams + if( !FBitSet( pbeam->flags, FBEAM_ISACTIVE ) || VectorLength( pbeam->delta ) < 0.1f ) + { + return; + } + + if( pbeam->flags & ( FBEAM_FADEIN|FBEAM_FADEOUT )) + { + // update life cycle + pbeam->t = pbeam->freq + ( pbeam->die - cl.time ); + if( pbeam->t != 0.0f ) pbeam->t = 1.0f - pbeam->freq / pbeam->t; + } + + if( pbeam->type == TE_BEAMHOSE ) + { + float flDot; + + VectorSubtract( pbeam->target, pbeam->source, delta ); + VectorNormalize( delta ); + + flDot = DotProduct( delta, RI.vforward ); + + // abort if the player's looking along it away from the source + if( flDot > 0 ) + { + return; + } + else + { + float flFade = pow( flDot, 10 ); + vec3_t localDir, vecProjection, tmp; + float flDistance; + + // fade the beam if the player's not looking at the source + VectorSubtract( RI.vieworg, pbeam->source, localDir ); + flDot = DotProduct( delta, localDir ); + VectorScale( delta, flDot, vecProjection ); + VectorSubtract( localDir, vecProjection, tmp ); + flDistance = VectorLength( tmp ); + + if( flDistance > 30 ) + { + flDistance = 1.0f - (( flDistance - 30.0f ) / 64.0f ); + if( flDistance <= 0 ) flFade = 0; + else flFade *= pow( flDistance, 3 ); + } + + if( flFade < ( 1.0f / 255.0f )) + return; + + // FIXME: needs to be testing + pbeam->brightness *= flFade; + } + } + + TriRenderMode( FBitSet( pbeam->flags, FBEAM_SOLID ) ? kRenderNormal : kRenderTransAdd ); + + if( !TriSpriteTexture( model, (int)(pbeam->frame + pbeam->frameRate * cl.time) % pbeam->frameCount )) + { + ClearBits( pbeam->flags, FBEAM_ISACTIVE ); + return; + } + + if( pbeam->type == TE_BEAMFOLLOW ) + { + cl_entity_t *pStart; + + // XASH SPECIFIC: get brightness from head entity + pStart = R_BeamGetEntity( pbeam->startEntity ); + if( pStart && pStart->curstate.rendermode != kRenderNormal ) + pbeam->brightness = CL_FxBlend( pStart ) / 255.0f; + } + + if( FBitSet( pbeam->flags, FBEAM_FADEIN )) + TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->t * pbeam->brightness ); + else if( FBitSet( pbeam->flags, FBEAM_FADEOUT )) + TriColor4f( pbeam->r, pbeam->g, pbeam->b, ( 1.0f - pbeam->t ) * pbeam->brightness ); + else TriColor4f( pbeam->r, pbeam->g, pbeam->b, pbeam->brightness ); + + switch( pbeam->type ) + { + case TE_BEAMTORUS: + GL_Cull( GL_NONE ); + TriBegin( TRI_TRIANGLE_STRIP ); + R_DrawTorus( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + TriEnd(); + break; + case TE_BEAMDISK: + GL_Cull( GL_NONE ); + TriBegin( TRI_TRIANGLE_STRIP ); + R_DrawDisk( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + TriEnd(); + break; + case TE_BEAMCYLINDER: + GL_Cull( GL_NONE ); + TriBegin( TRI_TRIANGLE_STRIP ); + R_DrawCylinder( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + TriEnd(); + break; + case TE_BEAMPOINTS: + case TE_BEAMHOSE: + TriBegin( TRI_TRIANGLE_STRIP ); + R_DrawSegs( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments, pbeam->flags ); + TriEnd(); + break; + case TE_BEAMFOLLOW: + TriBegin( TRI_QUADS ); + R_DrawBeamFollow( pbeam, frametime ); + TriEnd(); + break; + case TE_BEAMRING: + GL_Cull( GL_NONE ); + TriBegin( TRI_TRIANGLE_STRIP ); + R_DrawRing( pbeam->source, pbeam->delta, pbeam->width, pbeam->amplitude, pbeam->freq, pbeam->speed, pbeam->segments ); + TriEnd(); + break; + } + + GL_Cull( GL_FRONT ); +} + +/* +============== +R_BeamDrawCustomEntity + +initialize beam from server entity +============== +*/ +void R_BeamDrawCustomEntity( cl_entity_t *ent ) +{ + BEAM beam; + float amp = ent->curstate.body / 100.0f; + float blend = CL_FxBlend( ent ) / 255.0f; + float r, g, b; + int beamFlags; + + r = ent->curstate.rendercolor.r / 255.0f; + g = ent->curstate.rendercolor.g / 255.0f; + b = ent->curstate.rendercolor.b / 255.0f; + + R_BeamSetup( &beam, ent->origin, ent->angles, ent->curstate.modelindex, 0, ent->curstate.scale, amp, blend, ent->curstate.animtime ); + R_BeamSetAttributes( &beam, r, g, b, ent->curstate.framerate, ent->curstate.frame ); + beam.pFollowModel = NULL; + + switch( ent->curstate.rendermode & 0x0F ) + { + case BEAM_ENTPOINT: + beam.type = TE_BEAMPOINTS; + if( ent->curstate.sequence ) + { + SetBits( beam.flags, FBEAM_STARTENTITY ); + beam.startEntity = ent->curstate.sequence; + } + if( ent->curstate.skin ) + { + SetBits( beam.flags, FBEAM_ENDENTITY ); + beam.endEntity = ent->curstate.skin; + } + break; + case BEAM_ENTS: + beam.type = TE_BEAMPOINTS; + SetBits( beam.flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); + beam.startEntity = ent->curstate.sequence; + beam.endEntity = ent->curstate.skin; + break; + case BEAM_HOSE: + beam.type = TE_BEAMHOSE; + break; + case BEAM_POINTS: + // already set up + break; + } + + beamFlags = ( ent->curstate.rendermode & 0xF0 ); + + if( FBitSet( beamFlags, BEAM_FSINE )) + SetBits( beam.flags, FBEAM_SINENOISE ); + + if( FBitSet( beamFlags, BEAM_FSOLID )) + SetBits( beam.flags, FBEAM_SOLID ); + + if( FBitSet( beamFlags, BEAM_FSHADEIN )) + SetBits( beam.flags, FBEAM_SHADEIN ); + + if( FBitSet( beamFlags, BEAM_FSHADEOUT )) + SetBits( beam.flags, FBEAM_SHADEOUT ); + + // draw it + R_BeamDraw( &beam, tr.frametime ); +} + +/* +============================================================== + +VIEWBEAMS MANAGEMENT + +============================================================== +*/ +BEAM *cl_active_beams; +BEAM *cl_free_beams; +BEAM *cl_viewbeams = NULL; // beams pool + +/* +================ +CL_InitViewBeams + +================ +*/ +void CL_InitViewBeams( void ) +{ + cl_viewbeams = Mem_Alloc( cls.mempool, sizeof( BEAM ) * GI->max_beams ); + CL_ClearViewBeams(); +} + +/* +================ +CL_ClearViewBeams + +================ +*/ +void CL_ClearViewBeams( void ) +{ + int i; + + if( !cl_viewbeams ) return; + + // clear beams + cl_free_beams = cl_viewbeams; + cl_active_beams = NULL; + + for( i = 0; i < GI->max_beams - 1; i++ ) + cl_viewbeams[i].next = &cl_viewbeams[i+1]; + cl_viewbeams[GI->max_beams - 1].next = NULL; +} + +/* +================ +CL_FreeViewBeams + +================ +*/ +void CL_FreeViewBeams( void ) +{ + if( cl_viewbeams ) + Mem_Free( cl_viewbeams ); + cl_viewbeams = NULL; +} + +/* +================ +CL_AddCustomBeam + +Add the beam that encoded as custom entity +================ +*/ +void CL_AddCustomBeam( cl_entity_t *pEnvBeam ) +{ + if( tr.draw_list->num_beam_entities >= MAX_VISIBLE_PACKET ) + { + MsgDev( D_ERROR, "Too many custom beams %d!\n", tr.draw_list->num_beam_entities ); + return; + } + + if( pEnvBeam ) + { + tr.draw_list->beam_entities[tr.draw_list->num_beam_entities] = pEnvBeam; + tr.draw_list->num_beam_entities++; + } +} + + +/* +============== +CL_KillDeadBeams + +============== +*/ +void CL_KillDeadBeams( cl_entity_t *pDeadEntity ) +{ + BEAM *pbeam; + BEAM *pnewlist; + BEAM *pnext; + particle_t *pHead; // build a new list to replace cl_active_beams. + + pbeam = cl_active_beams; // old list. + pnewlist = NULL; // new list. + + while( pbeam ) + { + pnext = pbeam->next; + + // link into new list. + if( R_BeamGetEntity( pbeam->startEntity ) != pDeadEntity ) + { + pbeam->next = pnewlist; + pnewlist = pbeam; + + pbeam = pnext; + continue; + } + + pbeam->flags &= ~(FBEAM_STARTENTITY | FBEAM_ENDENTITY); + + if( pbeam->type != TE_BEAMFOLLOW ) + { + // remove beam + pbeam->die = cl.time - 0.1f; + + // kill off particles + pHead = pbeam->particles; + while( pHead ) + { + pHead->die = cl.time - 0.1f; + pHead = pHead->next; + } + + // free the beam + R_BeamFree( pbeam ); + } + else + { + // stay active + pbeam->next = pnewlist; + pnewlist = pbeam; + } + + pbeam = pnext; + } + + // We now have a new list with the bogus stuff released. + cl_active_beams = pnewlist; +} + +/* +============== +CL_BeamAttemptToDie + +Check for expired beams +============== +*/ +qboolean CL_BeamAttemptToDie( BEAM *pBeam ) +{ + Assert( pBeam != NULL ); + + // premanent beams never die automatically + if( FBitSet( pBeam->flags, FBEAM_FOREVER )) + return false; + + if( pBeam->type == TE_BEAMFOLLOW && pBeam->particles ) + { + // wait for all trails are dead + return false; + } + + // other beams + if( pBeam->die > cl.time ) + return false; + + return true; +} + +/* +============== +CL_DrawBeams + +draw beam loop +============== +*/ +void CL_DrawBeams( int fTrans ) +{ + BEAM *pBeam, *pNext; + BEAM *pPrev = NULL; + int i, flags; + + if( !cl_draw_beams->value ) + return; + + pglShadeModel( GL_SMOOTH ); + pglDepthMask( fTrans ? GL_FALSE : GL_TRUE ); + + // server beams don't allocate beam chains + // all params are stored in cl_entity_t + for( i = 0; i < tr.draw_list->num_beam_entities; i++ ) + { + RI.currentbeam = tr.draw_list->beam_entities[i]; + flags = RI.currentbeam->curstate.rendermode & 0xF0; + + if( fTrans && FBitSet( flags, FBEAM_SOLID )) + continue; + + if( !fTrans && !FBitSet( flags, FBEAM_SOLID )) + continue; + + R_BeamDrawCustomEntity( RI.currentbeam ); + r_stats.c_view_beams_count++; + } + + RI.currentbeam = NULL; + + // draw temporary entity beams + for( pBeam = cl_active_beams; pBeam; pBeam = pNext ) + { + // need to store the next one since we may delete this one + pNext = pBeam->next; + + if( fTrans && FBitSet( pBeam->flags, FBEAM_SOLID )) + continue; + + if( !fTrans && !FBitSet( pBeam->flags, FBEAM_SOLID )) + continue; + + // retire old beams + if( CL_BeamAttemptToDie( pBeam )) + { + // reset links + if( pPrev ) pPrev->next = pNext; + else cl_active_beams = pNext; + + // free the beam + R_BeamFree( pBeam ); + + pBeam = NULL; + continue; + } + + R_BeamDraw( pBeam, cl.time - cl.oldtime ); + r_stats.c_view_beams_count++; + pPrev = pBeam; + } + + pglShadeModel( GL_FLAT ); + pglDepthMask( GL_TRUE ); +} + +/* +============== +R_BeamKill + +Remove beam attached to specified entity +and all particle trails (if this is a beamfollow) +============== +*/ +void R_BeamKill( int deadEntity ) +{ + cl_entity_t *pDeadEntity; + + pDeadEntity = R_BeamGetEntity( deadEntity ); + if( !pDeadEntity ) return; + + CL_KillDeadBeams( pDeadEntity ); +} + +/* +============== +R_BeamEnts + +Create beam between two ents +============== +*/ +BEAM *R_BeamEnts( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, + float speed, int startFrame, float framerate, float r, float g, float b ) +{ + cl_entity_t *start, *end; + BEAM *pbeam; + model_t *mod; + + mod = CL_ModelHandle( modelIndex ); + + // need a valid model. + if( !mod || mod->type != mod_sprite ) + return NULL; + + start = R_BeamGetEntity( startEnt ); + end = R_BeamGetEntity( endEnt ); + + if( !start || !end ) + return NULL; + + // don't start temporary beams out of the PVS + if( life != 0 && ( !start->model || !end->model )) + return NULL; + + pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); + if( !pbeam ) return NULL; + + pbeam->type = TE_BEAMPOINTS; + SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); + if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); + + pbeam->startEntity = startEnt; + pbeam->endEntity = endEnt; + + R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); + + return pbeam; +} + +/* +============== +R_BeamPoints + +Create beam between two points +============== +*/ +BEAM *R_BeamPoints( vec3_t start, vec3_t end, int modelIndex, float life, float width, float amplitude, + float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) +{ + BEAM *pbeam; + + if( life != 0 && R_BeamCull( start, end, true )) + return NULL; + + pbeam = R_BeamAlloc(); + if( !pbeam ) return NULL; + + pbeam->die = cl.time; + + if( modelIndex < 0 ) + return NULL; + + R_BeamSetup( pbeam, start, end, modelIndex, life, width, amplitude, brightness, speed ); + if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); + + R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); + + return pbeam; +} + +/* +============== +R_BeamCirclePoints + +Create beam cicrle +============== +*/ +BEAM *R_BeamCirclePoints( int type, vec3_t start, vec3_t end, int modelIndex, float life, float width, + float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) +{ + BEAM *pbeam = R_BeamLightning( start, end, modelIndex, life, width, amplitude, brightness, speed ); + + if( !pbeam ) return NULL; + pbeam->type = type; + if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); + R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); + + return pbeam; +} + + +/* +============== +R_BeamEntPoint + +Create beam between entity and point +============== +*/ +BEAM *R_BeamEntPoint( int startEnt, vec3_t end, int modelIndex, float life, float width, float amplitude, + float brightness, float speed, int startFrame, float framerate, float r, float g, float b ) +{ + BEAM *pbeam; + cl_entity_t *start; + + start = R_BeamGetEntity( startEnt ); + + if( !start ) return NULL; + + if( life == 0 && !start->model ) + return NULL; + + pbeam = R_BeamAlloc(); + if ( !pbeam ) return NULL; + + pbeam->die = cl.time; + if( modelIndex < 0 ) + return NULL; + + R_BeamSetup( pbeam, vec3_origin, end, modelIndex, life, width, amplitude, brightness, speed ); + + pbeam->type = TE_BEAMPOINTS; + SetBits( pbeam->flags, FBEAM_STARTENTITY ); + if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); + pbeam->startEntity = startEnt; + pbeam->endEntity = 0; + + R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); + + return pbeam; +} + +/* +============== +R_BeamRing + +Create beam between two ents +============== +*/ +BEAM *R_BeamRing( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, + float speed, int startFrame, float framerate, float r, float g, float b ) +{ + BEAM *pbeam; + cl_entity_t *start, *end; + + start = R_BeamGetEntity( startEnt ); + end = R_BeamGetEntity( endEnt ); + + if( !start || !end ) + return NULL; + + if( life != 0 && ( !start->model || !end->model )) + return NULL; + + pbeam = R_BeamLightning( vec3_origin, vec3_origin, modelIndex, life, width, amplitude, brightness, speed ); + if( !pbeam ) return NULL; + + pbeam->type = TE_BEAMRING; + SetBits( pbeam->flags, FBEAM_STARTENTITY | FBEAM_ENDENTITY ); + if( life == 0 ) SetBits( pbeam->flags, FBEAM_FOREVER ); + pbeam->startEntity = startEnt; + pbeam->endEntity = endEnt; + + R_BeamSetAttributes( pbeam, r, g, b, framerate, startFrame ); + + return pbeam; +} + +/* +============== +R_BeamFollow + +Create beam following with entity +============== +*/ +BEAM *R_BeamFollow( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ) +{ + BEAM *pbeam = R_BeamAlloc(); + + if( !pbeam ) return NULL; + pbeam->die = cl.time; + + if( modelIndex < 0 ) + return NULL; + + R_BeamSetup( pbeam, vec3_origin, vec3_origin, modelIndex, life, width, life, brightness, 1.0f ); + + pbeam->type = TE_BEAMFOLLOW; + SetBits( pbeam->flags, FBEAM_STARTENTITY ); + pbeam->startEntity = startEnt; + + R_BeamSetAttributes( pbeam, r, g, b, 1.0f, 0 ); + + return pbeam; +} + +/* +============== +R_BeamSprite + +Create a beam with sprite at the end +Valve legacy +============== +*/ +void R_BeamSprite( vec3_t start, vec3_t end, int beamIndex, int spriteIndex ) +{ + R_BeamPoints( start, end, beamIndex, 0.01f, 0.4f, 0, COM_RandomFloat( 0.5f, 0.655f ), 5.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f ); + R_TempSprite( end, vec3_origin, 0.1f, spriteIndex, kRenderTransAdd, kRenderFxNone, 0.35f, 0.01f, 0.0f ); +} + +/* +============== +CL_ParseViewBeam + +handle beam messages +============== +*/ +void CL_ParseViewBeam( sizebuf_t *msg, int beamType ) +{ + vec3_t start, end; + int modelIndex, startFrame; + float frameRate, life, width; + int startEnt, endEnt; + float noise, speed; + float r, g, b, a; + + switch( beamType ) + { + case TE_BEAMPOINTS: + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + modelIndex = MSG_ReadShort( msg ); + startFrame = MSG_ReadByte( msg ); + frameRate = (float)MSG_ReadByte( msg ); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + speed = (float)(MSG_ReadByte( msg ) * 0.1f); + R_BeamPoints( start, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + break; + case TE_BEAMENTPOINT: + startEnt = MSG_ReadShort( msg ); + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + modelIndex = MSG_ReadShort( msg ); + startFrame = MSG_ReadByte( msg ); + frameRate = (float)MSG_ReadByte( msg ); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + speed = (float)(MSG_ReadByte( msg ) * 0.1f); + R_BeamEntPoint( startEnt, end, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + break; + case TE_LIGHTNING: + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + modelIndex = MSG_ReadShort( msg ); + R_BeamLightning( start, end, modelIndex, life, width, noise, 0.6F, 3.5f ); + break; + case TE_BEAMENTS: + startEnt = MSG_ReadShort( msg ); + endEnt = MSG_ReadShort( msg ); + modelIndex = MSG_ReadShort( msg ); + startFrame = MSG_ReadByte( msg ); + frameRate = (float)(MSG_ReadByte( msg ) * 0.1f); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + speed = (float)(MSG_ReadByte( msg ) * 0.1f); + R_BeamEnts( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + break; + case TE_BEAM: + MsgDev( D_ERROR, "TE_BEAM is obsolete\n" ); + break; + case TE_BEAMSPRITE: + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + modelIndex = MSG_ReadShort( msg ); // beam model + startFrame = MSG_ReadShort( msg ); // sprite model + R_BeamSprite( start, end, modelIndex, startFrame ); + break; + case TE_BEAMTORUS: + case TE_BEAMDISK: + case TE_BEAMCYLINDER: + start[0] = MSG_ReadCoord( msg ); + start[1] = MSG_ReadCoord( msg ); + start[2] = MSG_ReadCoord( msg ); + end[0] = MSG_ReadCoord( msg ); + end[1] = MSG_ReadCoord( msg ); + end[2] = MSG_ReadCoord( msg ); + modelIndex = MSG_ReadShort( msg ); + startFrame = MSG_ReadByte( msg ); + frameRate = (float)(MSG_ReadByte( msg )); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + speed = (float)MSG_ReadByte( msg ); + R_BeamCirclePoints( beamType, start, end, modelIndex, life, width, noise, a, speed / 10.0f, startFrame, frameRate, r, g, b ); + break; + case TE_BEAMFOLLOW: + startEnt = MSG_ReadShort( msg ); + modelIndex = MSG_ReadShort( msg ); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)MSG_ReadByte( msg ); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + R_BeamFollow( startEnt, modelIndex, life, width, r, g, b, a ); + break; + case TE_BEAMRING: + startEnt = MSG_ReadShort( msg ); + endEnt = MSG_ReadShort( msg ); + modelIndex = MSG_ReadShort( msg ); + startFrame = MSG_ReadByte( msg ); + frameRate = (float)MSG_ReadByte( msg ); + life = (float)(MSG_ReadByte( msg ) * 0.1f); + width = (float)(MSG_ReadByte( msg ) * 0.1f); + noise = (float)(MSG_ReadByte( msg ) * 0.01f); + r = (float)MSG_ReadByte( msg ) / 255.0f; + g = (float)MSG_ReadByte( msg ) / 255.0f; + b = (float)MSG_ReadByte( msg ) / 255.0f; + a = (float)MSG_ReadByte( msg ) / 255.0f; + speed = (float)(MSG_ReadByte( msg ) * 0.1f); + R_BeamRing( startEnt, endEnt, modelIndex, life, width, noise, a, speed, startFrame, frameRate, r, g, b ); + break; + case TE_BEAMHOSE: + MsgDev( D_ERROR, "TE_BEAMHOSE is obsolete\n" ); + break; + case TE_KILLBEAM: + startEnt = MSG_ReadShort( msg ); + R_BeamKill( startEnt ); + break; + } +} + +/* +=============== +CL_ReadLineFile_f + +Optimized version of pointfile - use beams instead of particles +=============== +*/ +void CL_ReadLineFile_f( void ) +{ + char *afile, *pfile; + vec3_t p1, p2; + int count, modelIndex; + char filename[MAX_QPATH]; + model_t *model; + string token; + + Q_snprintf( filename, sizeof( filename ), "maps/%s.lin", clgame.mapname ); + afile = FS_LoadFile( filename, NULL, false ); + + if( !afile ) + { + Con_Printf( S_ERROR "couldn't open %s\n", filename ); + return; + } + + Con_Printf( "Reading %s...\n", filename ); + + count = 0; + pfile = afile; + model = CL_LoadModel( DEFAULT_LASERBEAM_PATH, &modelIndex ); + + while( 1 ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p1[0] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p1[1] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p1[2] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '-' ) + { + Con_Printf( S_ERROR "%s is corrupted\n", filename ); + break; + } + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p2[0] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p2[1] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + p2[2] = Q_atof( token ); + + count++; + + if( !R_BeamPoints( p1, p2, modelIndex, 99999, 2, 0, 255, 0, 0, 0, 255.0f, 0.0f, 0.0f )) + { + if( !model || model->type != mod_sprite ) + Con_Printf( S_ERROR "failed to load \"%s\"!\n", DEFAULT_LASERBEAM_PATH ); + else Con_Printf( S_ERROR "not enough free beams!\n" ); + break; + } + } + + Mem_Free( afile ); + + if( count ) Con_Printf( "%i lines read\n", count ); + else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); +} \ No newline at end of file diff --git a/engine/client/gl_cull.c b/engine/client/gl_cull.c new file mode 100644 index 00000000..d786651e --- /dev/null +++ b/engine/client/gl_cull.c @@ -0,0 +1,151 @@ +/* +gl_cull.c - render culling routines +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "entity_types.h" + +/* +============================================================= + +FRUSTUM AND PVS CULLING + +============================================================= +*/ +/* +================= +R_CullBox + +Returns true if the box is completely outside the frustum +================= +*/ +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ) +{ + return GL_FrustumCullBox( &RI.frustum, mins, maxs, 0 ); +} + +/* +================= +R_CullSphere + +Returns true if the sphere is completely outside the frustum +================= +*/ +qboolean R_CullSphere( const vec3_t centre, const float radius ) +{ + return GL_FrustumCullSphere( &RI.frustum, centre, radius, 0 ); +} + +/* +============= +R_CullModel +============= +*/ +int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) +{ + if( e == &clgame.viewent ) + { + if( CL_IsDevOverviewMode( )) + return 1; + + if( RP_NORMALPASS() && !cl.local.thirdperson && cl.viewentity == ( cl.playernum + 1 )) + return 0; + + return 1; + } + + // local client can't view himself if camera or thirdperson is not active + if( RP_LOCALCLIENT( e ) && !cl.local.thirdperson && cl.viewentity == ( cl.playernum + 1 )) + return 1; + + if( R_CullBox( absmin, absmax )) + return 1; + + return 0; +} + +/* +================= +R_CullSurface + +cull invisible surfaces +================= +*/ +int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ) +{ + cl_entity_t *e = RI.currententity; + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return CULL_OTHER; + + if( r_nocull->value ) + return CULL_VISIBLE; + + // world surfaces can be culled by vis frame too + if( RI.currententity == clgame.entities && surf->visframe != tr.framecount ) + return CULL_VISFRAME; + + // only static ents can be culled by frustum + if( !R_StaticEntity( e )) frustum = NULL; + + if( !VectorIsNull( surf->plane->normal )) + { + float dist; + + // can use normal.z for world (optimisation) + if( RI.drawOrtho ) + { + vec3_t orthonormal; + + if( e == clgame.entities ) orthonormal[2] = surf->plane->normal[2]; + else Matrix4x4_VectorRotate( RI.objectMatrix, surf->plane->normal, orthonormal ); + dist = orthonormal[2]; + } + else dist = PlaneDiff( tr.modelorg, surf->plane ); + + if( glState.faceCull == GL_FRONT ) + { + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + else if( glState.faceCull == GL_BACK ) + { + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + } + + if( frustum && GL_FrustumCullBox( frustum, surf->info->mins, surf->info->maxs, clipflags )) + return CULL_FRUSTUM; + + return CULL_VISIBLE; +} \ No newline at end of file diff --git a/engine/client/gl_decals.c b/engine/client/gl_decals.c new file mode 100644 index 00000000..d65ef267 --- /dev/null +++ b/engine/client/gl_decals.c @@ -0,0 +1,1293 @@ +/* +gl_decals.c - decal paste and rendering +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "cl_tent.h" + +#define DECAL_OVERLAP_DISTANCE 2 +#define DECAL_DISTANCE 4 // too big values produce more clipped polygons +#define MAX_DECALCLIPVERT 32 // produced vertexes of fragmented decal +#define DECAL_CACHEENTRY 256 // MUST BE POWER OF 2 or code below needs to change! +#define DECAL_TRANSPARENT_THRESHOLD 230 // transparent decals draw with GL_MODULATE + +// empirically determined constants for minimizing overalpping decals +#define MAX_OVERLAP_DECALS 6 +#define DECAL_OVERLAP_DIST 8 +#define MIN_DECAL_SCALE 0.01f +#define MAX_DECAL_SCALE 16.0f + +// clip edges +#define LEFT_EDGE 0 +#define RIGHT_EDGE 1 +#define TOP_EDGE 2 +#define BOTTOM_EDGE 3 + +// This structure contains the information used to create new decals +typedef struct +{ + vec3_t m_Position; // world coordinates of the decal center + model_t *m_pModel; // the model the decal is going to be applied in + int m_iTexture; // The decal material + int m_Size; // Size of the decal (in world coords) + int m_Flags; + int m_Entity; // Entity the decal is applied to. + float m_scale; + int m_decalWidth; + int m_decalHeight; + vec3_t m_Basis[3]; +} decalinfo_t; + +static float g_DecalClipVerts[MAX_DECALCLIPVERT][VERTEXSIZE]; +static float g_DecalClipVerts2[MAX_DECALCLIPVERT][VERTEXSIZE]; + +static decal_t gDecalPool[MAX_RENDER_DECALS]; +static int gDecalCount; + +void R_ClearDecals( void ) +{ + memset( gDecalPool, 0, sizeof( gDecalPool )); + gDecalCount = 0; +} + +// unlink pdecal from any surface it's attached to +static void R_DecalUnlink( decal_t *pdecal ) +{ + decal_t *tmp; + + if( pdecal->psurface ) + { + if( pdecal->psurface->pdecals == pdecal ) + { + pdecal->psurface->pdecals = pdecal->pnext; + } + else + { + tmp = pdecal->psurface->pdecals; + if( !tmp ) Host_Error( "D_DecalUnlink: bad decal list\n" ); + + while( tmp->pnext ) + { + if( tmp->pnext == pdecal ) + { + tmp->pnext = pdecal->pnext; + break; + } + tmp = tmp->pnext; + } + } + } + + if( pdecal->polys ) + Mem_Free( pdecal->polys ); + + pdecal->psurface = NULL; + pdecal->polys = NULL; +} + +// Just reuse next decal in list +// A decal that spans multiple surfaces will use multiple decal_t pool entries, +// as each surface needs it's own. +static decal_t *R_DecalAlloc( decal_t *pdecal ) +{ + int limit = MAX_RENDER_DECALS; + + if( r_decals->value < limit ) + limit = r_decals->value; + + if( !limit ) return NULL; + + if( !pdecal ) + { + int count = 0; + + // check for the odd possiblity of infinte loop + do + { + if( gDecalCount >= limit ) + gDecalCount = 0; + + pdecal = &gDecalPool[gDecalCount]; // reuse next decal + gDecalCount++; + count++; + } while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit ); + } + + // if decal is already linked to a surface, unlink it. + R_DecalUnlink( pdecal ); + + return pdecal; +} + +//----------------------------------------------------------------------------- +// find decal image and grab size from it +//----------------------------------------------------------------------------- +static void R_GetDecalDimensions( int texture, int *width, int *height ) +{ + if( width ) *width = 1; // to avoid divide by zero + if( height ) *height = 1; + + R_GetTextureParms( width, height, texture ); +} + +//----------------------------------------------------------------------------- +// compute the decal basis based on surface normal +//----------------------------------------------------------------------------- +void R_DecalComputeBasis( msurface_t *surf, int flags, vec3_t textureSpaceBasis[3] ) +{ + vec3_t surfaceNormal; + + // setup normal + if( surf->flags & SURF_PLANEBACK ) + VectorNegate( surf->plane->normal, surfaceNormal ); + else VectorCopy( surf->plane->normal, surfaceNormal ); + + VectorNormalize2( surfaceNormal, textureSpaceBasis[2] ); +#if 0 + if( FBitSet( flags, FDECAL_CUSTOM )) + { + vec3_t pSAxis = { 1, 0, 0 }; + + // T = S cross N + CrossProduct( pSAxis, textureSpaceBasis[2], textureSpaceBasis[1] ); + + // Name sure they aren't parallel or antiparallel + // In that case, fall back to the normal algorithm. + if( DotProduct( textureSpaceBasis[1], textureSpaceBasis[1] ) > 1e-6 ) + { + // S = N cross T + CrossProduct( textureSpaceBasis[2], textureSpaceBasis[1], textureSpaceBasis[0] ); + + VectorNormalizeFast( textureSpaceBasis[0] ); + VectorNormalizeFast( textureSpaceBasis[1] ); + return; + } + // Fall through to the standard algorithm for parallel or antiparallel + } +#endif + VectorNormalize2( surf->texinfo->vecs[0], textureSpaceBasis[0] ); + VectorNormalize2( surf->texinfo->vecs[1], textureSpaceBasis[1] ); +} + +void R_SetupDecalTextureSpaceBasis( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +{ + int width, height; + + // Compute the non-scaled decal basis + R_DecalComputeBasis( surf, pDecal->flags, textureSpaceBasis ); + R_GetDecalDimensions( texture, &width, &height ); + + // world width of decal = ptexture->width / pDecal->scale + // world height of decal = ptexture->height / pDecal->scale + // scale is inverse, scales world space to decal u/v space [0,1] + // OPTIMIZE: Get rid of these divides + decalWorldScale[0] = (float)pDecal->scale / width; + decalWorldScale[1] = (float)pDecal->scale / height; + + VectorScale( textureSpaceBasis[0], decalWorldScale[0], textureSpaceBasis[0] ); + VectorScale( textureSpaceBasis[1], decalWorldScale[1], textureSpaceBasis[1] ); +} + +// Build the initial list of vertices from the surface verts into the global array, 'verts'. +void R_SetupDecalVertsForMSurface( decal_t *pDecal, msurface_t *surf, vec3_t textureSpaceBasis[3], float *verts ) +{ + float *v; + int i; + + for( i = 0, v = surf->polys->verts[0]; i < surf->polys->numverts; i++, v += VERTEXSIZE, verts += VERTEXSIZE ) + { + VectorCopy( v, verts ); // copy model space coordinates + verts[3] = DotProduct( verts, textureSpaceBasis[0] ) - pDecal->dx + 0.5f; + verts[4] = DotProduct( verts, textureSpaceBasis[1] ) - pDecal->dy + 0.5f; + verts[5] = verts[6] = 0.0f; + } +} + +// Figure out where the decal maps onto the surface. +void R_SetupDecalClip( decal_t *pDecal, msurface_t *surf, int texture, vec3_t textureSpaceBasis[3], float decalWorldScale[2] ) +{ + R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); + + // Generate texture coordinates for each vertex in decal s,t space + // probably should pre-generate this, store it and use it for decal-decal collisions + // as in R_DecalsIntersect() + pDecal->dx = DotProduct( pDecal->position, textureSpaceBasis[0] ); + pDecal->dy = DotProduct( pDecal->position, textureSpaceBasis[1] ); +} + +// Quick and dirty sutherland Hodgman clipper +// Clip polygon to decal in texture space +// JAY: This code is lame, change it later. It does way too much work per frame +// It can be made to recursively call the clipping code and only copy the vertex list once +int R_ClipInside( float *vert, int edge ) +{ + switch( edge ) + { + case LEFT_EDGE: + if( vert[3] > 0.0f ) + return 1; + return 0; + case RIGHT_EDGE: + if( vert[3] < 1.0f ) + return 1; + return 0; + case TOP_EDGE: + if( vert[4] > 0.0f ) + return 1; + return 0; + case BOTTOM_EDGE: + if( vert[4] < 1.0f ) + return 1; + return 0; + } + return 0; +} + +void R_ClipIntersect( float *one, float *two, float *out, int edge ) +{ + float t; + + // t is the parameter of the line between one and two clipped to the edge + // or the fraction of the clipped point between one & two + // vert[0], vert[1], vert[2] is X, Y, Z + // vert[3] is u + // vert[4] is v + // vert[5] is lightmap u + // vert[6] is lightmap v + + if( edge < TOP_EDGE ) + { + if( edge == LEFT_EDGE ) + { + // left + t = ((one[3] - 0.0f) / (one[3] - two[3])); + out[3] = out[5] = 0.0f; + } + else + { + // right + t = ((one[3] - 1.0f) / (one[3] - two[3])); + out[3] = out[5] = 1.0f; + } + + out[4] = one[4] + (two[4] - one[4]) * t; + out[6] = one[6] + (two[6] - one[6]) * t; + } + else + { + if( edge == TOP_EDGE ) + { + // top + t = ((one[4] - 0.0f) / (one[4] - two[4])); + out[4] = out[6] = 0.0f; + } + else + { + // bottom + t = ((one[4] - 1.0f) / (one[4] - two[4])); + out[4] = out[6] = 1.0f; + } + + out[3] = one[3] + (two[3] - one[3]) * t; + out[5] = one[5] + (two[4] - one[5]) * t; + } + + VectorLerp( one, t, two, out ); +} + +static int SHClip( float *vert, int vertCount, float *out, int edge ) +{ + int j, outCount; + float *s, *p; + + outCount = 0; + + s = &vert[(vertCount - 1) * VERTEXSIZE]; + + for( j = 0; j < vertCount; j++ ) + { + p = &vert[j * VERTEXSIZE]; + + if( R_ClipInside( p, edge )) + { + if( R_ClipInside( s, edge )) + { + // Add a vertex and advance out to next vertex + memcpy( out, p, sizeof( float ) * VERTEXSIZE ); + out += VERTEXSIZE; + outCount++; + } + else + { + R_ClipIntersect( s, p, out, edge ); + out += VERTEXSIZE; + outCount++; + + memcpy( out, p, sizeof( float ) * VERTEXSIZE ); + out += VERTEXSIZE; + outCount++; + } + } + else + { + if( R_ClipInside( s, edge )) + { + R_ClipIntersect( p, s, out, edge ); + out += VERTEXSIZE; + outCount++; + } + } + + s = p; + } + + return outCount; +} + +float *R_DoDecalSHClip( float *pInVerts, decal_t *pDecal, int nStartVerts, int *pVertCount ) +{ + float *pOutVerts = g_DecalClipVerts[0]; + int outCount; + + // clip the polygon to the decal texture space + outCount = SHClip( pInVerts, nStartVerts, g_DecalClipVerts2[0], LEFT_EDGE ); + outCount = SHClip( g_DecalClipVerts2[0], outCount, g_DecalClipVerts[0], RIGHT_EDGE ); + outCount = SHClip( g_DecalClipVerts[0], outCount, g_DecalClipVerts2[0], TOP_EDGE ); + outCount = SHClip( g_DecalClipVerts2[0], outCount, pOutVerts, BOTTOM_EDGE ); + + if( pVertCount ) + *pVertCount = outCount; + + return pOutVerts; +} + +//----------------------------------------------------------------------------- +// Generate clipped vertex list for decal pdecal projected onto polygon psurf +//----------------------------------------------------------------------------- +float *R_DecalVertsClip( decal_t *pDecal, msurface_t *surf, int texture, int *pVertCount ) +{ + float decalWorldScale[2]; + vec3_t textureSpaceBasis[3]; + + // figure out where the decal maps onto the surface. + R_SetupDecalClip( pDecal, surf, texture, textureSpaceBasis, decalWorldScale ); + + // build the initial list of vertices from the surface verts. + R_SetupDecalVertsForMSurface( pDecal, surf, textureSpaceBasis, g_DecalClipVerts[0] ); + + return R_DoDecalSHClip( g_DecalClipVerts[0], pDecal, surf->polys->numverts, pVertCount ); +} + +// Generate lighting coordinates at each vertex for decal vertices v[] on surface psurf +static void R_DecalVertsLight( float *v, msurface_t *surf, int vertCount ) +{ + float s, t; + mtexinfo_t *tex; + mextrasurf_t *info = surf->info; + float sample_size; + int j; + + sample_size = Mod_SampleSizeForFace( surf ); + tex = surf->texinfo; + + for( j = 0; j < vertCount; j++, v += VERTEXSIZE ) + { + // lightmap texture coordinates + s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + s += surf->light_s * sample_size; + s += sample_size * 0.5; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + t += surf->light_t * sample_size; + t += sample_size * 0.5; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + v[5] = s; + v[6] = t; + } +} + +// Check for intersecting decals on this surface +static decal_t *R_DecalIntersect( decalinfo_t *decalinfo, msurface_t *surf, int *pcount ) +{ + int texture; + decal_t *plast, *pDecal; + vec3_t decalExtents[2]; + float lastArea = 2; + int mapSize[2]; + + plast = NULL; + *pcount = 0; + + // (Same as R_SetupDecalClip). + texture = decalinfo->m_iTexture; + + // precalculate the extents of decalinfo's decal in world space. + R_GetDecalDimensions( texture, &mapSize[0], &mapSize[1] ); + VectorScale( decalinfo->m_Basis[0], ((mapSize[0] / decalinfo->m_scale) * 0.5f), decalExtents[0] ); + VectorScale( decalinfo->m_Basis[1], ((mapSize[1] / decalinfo->m_scale) * 0.5f), decalExtents[1] ); + + pDecal = surf->pdecals; + + while( pDecal ) + { + texture = pDecal->texture; + + // Don't steal bigger decals and replace them with smaller decals + // Don't steal permanent decals + if( !FBitSet( pDecal->flags, FDECAL_PERMANENT )) + { + vec3_t testBasis[3]; + vec3_t testPosition[2]; + float testWorldScale[2]; + vec2_t vDecalMin, vDecalMax; + vec2_t vUnionMin, vUnionMax; + + R_SetupDecalTextureSpaceBasis( pDecal, surf, texture, testBasis, testWorldScale ); + + VectorSubtract( decalinfo->m_Position, decalExtents[0], testPosition[0] ); + VectorSubtract( decalinfo->m_Position, decalExtents[1], testPosition[1] ); + + // Here, we project the min and max extents of the decal that got passed in into + // this decal's (pDecal's) [0,0,1,1] clip space, just like we would if we were + // clipping a triangle into pDecal's clip space. + Vector2Set( vDecalMin, + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + + VectorAdd( decalinfo->m_Position, decalExtents[0], testPosition[0] ); + VectorAdd( decalinfo->m_Position, decalExtents[1], testPosition[1] ); + + Vector2Set( vDecalMax, + DotProduct( testPosition[0], testBasis[0] ) - pDecal->dx + 0.5f, + DotProduct( testPosition[1], testBasis[1] ) - pDecal->dy + 0.5f ); + + // Now figure out the part of the projection that intersects pDecal's + // clip box [0,0,1,1]. + Vector2Set( vUnionMin, max( vDecalMin[0], 0 ), max( vDecalMin[1], 0 )); + Vector2Set( vUnionMax, min( vDecalMax[0], 1 ), min( vDecalMax[1], 1 )); + + if( vUnionMin[0] < 1 && vUnionMin[1] < 1 && vUnionMax[0] > 0 && vUnionMax[1] > 0 ) + { + // Figure out how much of this intersects the (0,0) - (1,1) bbox. + float flArea = (vUnionMax[0] - vUnionMin[1]) * (vUnionMax[1] - vUnionMin[1]); + + if( flArea > 0.6f ) + { + *pcount += 1; + + if( !plast || flArea <= lastArea ) + { + plast = pDecal; + lastArea = flArea; + } + } + } + } + pDecal = pDecal->pnext; + } + return plast; +} + +/* +==================== +R_DecalCreatePoly + +creates mesh for decal on first rendering +==================== +*/ +glpoly_t *R_DecalCreatePoly( decalinfo_t *decalinfo, decal_t *pdecal, msurface_t *surf ) +{ + int lnumverts; + glpoly_t *poly; + float *v; + int i; + + if( pdecal->polys ) // already created? + return pdecal->polys; + + v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &lnumverts ); + if( !lnumverts ) return NULL; // probably this never happens + + // allocate glpoly + poly = Mem_Alloc( com_studiocache, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); + poly->next = pdecal->polys; + poly->flags = surf->flags; + pdecal->polys = poly; + poly->numverts = lnumverts; + + for( i = 0; i < lnumverts; i++, v += VERTEXSIZE ) + { + VectorCopy( v, poly->verts[i] ); + poly->verts[i][3] = v[3]; + poly->verts[i][4] = v[4]; + poly->verts[i][5] = v[5]; + poly->verts[i][6] = v[6]; + } + + return poly; +} + +// Add the decal to the surface's list of decals. +static void R_AddDecalToSurface( decal_t *pdecal, msurface_t *surf, decalinfo_t *decalinfo ) +{ + decal_t *pold; + + pdecal->pnext = NULL; + pold = surf->pdecals; + + if( pold ) + { + while( pold->pnext ) + pold = pold->pnext; + pold->pnext = pdecal; + } + else + { + surf->pdecals = pdecal; + } + + // tag surface + pdecal->psurface = surf; + + // at this point decal are linked with surface + // and will be culled, drawing and sorting + // together with surface + + // alloc clipped poly for decal + R_DecalCreatePoly( decalinfo, pdecal, surf ); +} + +static void R_DecalCreate( decalinfo_t *decalinfo, msurface_t *surf, float x, float y ) +{ + decal_t *pdecal, *pold; + int count, vertCount; + + if( !surf ) + { + MsgDev( D_ERROR, "psurface NULL in R_DecalCreate!\n" ); + return; + } + + pold = R_DecalIntersect( decalinfo, surf, &count ); + if( count < MAX_OVERLAP_DECALS ) pold = NULL; + + pdecal = R_DecalAlloc( pold ); + if( !pdecal ) return; // r_decals == 0 ??? + + pdecal->flags = decalinfo->m_Flags; + + VectorCopy( decalinfo->m_Position, pdecal->position ); + + pdecal->dx = x; + pdecal->dy = y; + + // set scaling + pdecal->scale = decalinfo->m_scale; + pdecal->entityIndex = decalinfo->m_Entity; + pdecal->texture = decalinfo->m_iTexture; + + // check to see if the decal actually intersects the surface + // if not, then remove the decal + R_DecalVertsClip( pdecal, surf, decalinfo->m_iTexture, &vertCount ); + + if( !vertCount ) + { + R_DecalUnlink( pdecal ); + return; + } + + // add to the surface's list + R_AddDecalToSurface( pdecal, surf, decalinfo ); +} + +void R_DecalSurface( msurface_t *surf, decalinfo_t *decalinfo ) +{ + // get the texture associated with this surface + mtexinfo_t *tex = surf->texinfo; + decal_t *decal = surf->pdecals; + vec4_t textureU, textureV; + float s, t, w, h; + + // we in restore mode + if( cls.state == ca_connected || cls.state == ca_validate ) + { + // NOTE: we may have the decal on this surface that come from another level. + // check duplicate with same position and texture + while( decal != NULL ) + { + if( VectorCompare( decal->position, decalinfo->m_Position ) && decal->texture == decalinfo->m_iTexture ) + return; // decal already exists, don't place it again + decal = decal->pnext; + } + } + + Vector4Copy( tex->vecs[0], textureU ); + Vector4Copy( tex->vecs[1], textureV ); + + // project decal center into the texture space of the surface + s = DotProduct( decalinfo->m_Position, textureU ) + textureU[3] - surf->texturemins[0]; + t = DotProduct( decalinfo->m_Position, textureV ) + textureV[3] - surf->texturemins[1]; + + // Determine the decal basis (measured in world space) + // Note that the decal basis vectors 0 and 1 will always lie in the same + // plane as the texture space basis vectorstextureVecsTexelsPerWorldUnits. + R_DecalComputeBasis( surf, decalinfo->m_Flags, decalinfo->m_Basis ); + + // Compute an effective width and height (axis aligned) in the parent texture space + // How does this work? decalBasis[0] represents the u-direction (width) + // of the decal measured in world space, decalBasis[1] represents the + // v-direction (height) measured in world space. + // textureVecsTexelsPerWorldUnits[0] represents the u direction of + // the surface's texture space measured in world space (with the appropriate + // scale factor folded in), and textureVecsTexelsPerWorldUnits[1] + // represents the texture space v direction. We want to find the dimensions (w,h) + // of a square measured in texture space, axis aligned to that coordinate system. + // All we need to do is to find the components of the decal edge vectors + // (decalWidth * decalBasis[0], decalHeight * decalBasis[1]) + // in texture coordinates: + + w = fabs( decalinfo->m_decalWidth * DotProduct( textureU, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureU, decalinfo->m_Basis[1] )); + + h = fabs( decalinfo->m_decalWidth * DotProduct( textureV, decalinfo->m_Basis[0] )) + + fabs( decalinfo->m_decalHeight * DotProduct( textureV, decalinfo->m_Basis[1] )); + + // move s,t to upper left corner + s -= ( w * 0.5f ); + t -= ( h * 0.5f ); + + // Is this rect within the surface? -- tex width & height are unsigned + if( s <= -w || t <= -h || s > (surf->extents[0] + w) || t > (surf->extents[1] + h)) + { + return; // nope + } + + // stamp it + R_DecalCreate( decalinfo, surf, s, t ); +} + +//----------------------------------------------------------------------------- +// iterate over all surfaces on a node, looking for surfaces to decal +//----------------------------------------------------------------------------- +static void R_DecalNodeSurfaces( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) +{ + // iterate over all surfaces in the node + msurface_t *surf; + int i; + + surf = model->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + // never apply decals on the water or sky surfaces + if( surf->flags & (SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR)) + continue; + + if( surf->flags & SURF_TRANSPARENT && !glState.stencilEnabled ) + continue; + + R_DecalSurface( surf, decalinfo ); + } +} + +//----------------------------------------------------------------------------- +// Recursive routine to find surface to apply a decal to. World coordinates of +// the decal are passed in r_recalpos like the rest of the engine. This should +// be called through R_DecalShoot() +//----------------------------------------------------------------------------- +static void R_DecalNode( model_t *model, mnode_t *node, decalinfo_t *decalinfo ) +{ + mplane_t *splitplane; + float dist; + + Assert( node != NULL ); + + if( node->contents < 0 ) + { + // hit a leaf + return; + } + + splitplane = node->plane; + dist = DotProduct( decalinfo->m_Position, splitplane->normal ) - splitplane->dist; + + // This is arbitrarily set to 10 right now. In an ideal world we'd have the + // exact surface but we don't so, this tells me which planes are "sort of + // close" to the gunshot -- the gunshot is actually 4 units in front of the + // wall (see dlls\weapons.cpp). We also need to check to see if the decal + // actually intersects the texture space of the surface, as this method tags + // parallel surfaces in the same node always. + // JAY: This still tags faces that aren't correct at edges because we don't + // have a surface normal + if( dist > decalinfo->m_Size ) + { + R_DecalNode( model, node->children[0], decalinfo ); + } + else if( dist < -decalinfo->m_Size ) + { + R_DecalNode( model, node->children[1], decalinfo ); + } + else + { + if( dist < DECAL_DISTANCE && dist > -DECAL_DISTANCE ) + R_DecalNodeSurfaces( model, node, decalinfo ); + + R_DecalNode( model, node->children[0], decalinfo ); + R_DecalNode( model, node->children[1], decalinfo ); + } +} + +// Shoots a decal onto the surface of the BSP. position is the center of the decal in world coords +void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ) +{ + decalinfo_t decalInfo; + cl_entity_t *ent = NULL; + model_t *model = NULL; + int width, height; + hull_t *hull; + + if( textureIndex <= 0 || textureIndex >= MAX_TEXTURES ) + { + MsgDev( D_ERROR, "Decal has invalid texture!\n" ); + return; + } + + if( entityIndex > 0 ) + { + ent = CL_GetEntityByIndex( entityIndex ); + + if( modelIndex > 0 ) model = CL_ModelHandle( modelIndex ); + else if( ent != NULL ) model = CL_ModelHandle( ent->curstate.modelindex ); + else return; + } + else if( modelIndex > 0 ) + model = CL_ModelHandle( modelIndex ); + else model = cl.worldmodel; + + if( !model ) return; + + if( model->type != mod_brush ) + { + MsgDev( D_ERROR, "Decals must hit mod_brush!\n" ); + return; + } + + decalInfo.m_pModel = model; + hull = &model->hulls[0]; // always use #0 hull + + // NOTE: all the decals at 'first shoot' placed into local space of parent entity + // and won't transform again on a next restore, levelchange etc + if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE )) + { + vec3_t pos_l; + + // transform decal position in local bmodel space + if( !VectorIsNull( ent->angles )) + { + matrix4x4 matrix; + + Matrix4x4_CreateFromEntity( matrix, ent->angles, ent->origin, 1.0f ); + Matrix4x4_VectorITransform( matrix, pos, pos_l ); + } + else + { + VectorSubtract( pos, ent->origin, pos_l ); + } + + VectorCopy( pos_l, decalInfo.m_Position ); + // decal position moved into local space + SetBits( flags, FDECAL_LOCAL_SPACE ); + } + else + { + // already in local space + VectorCopy( pos, decalInfo.m_Position ); + } + + // this decal must use landmark for correct transition + // because their model exist only in world-space + if( !FBitSet( model->flags, MODEL_HAS_ORIGIN )) + SetBits( flags, FDECAL_USE_LANDMARK ); + + // more state used by R_DecalNode() + decalInfo.m_iTexture = textureIndex; + decalInfo.m_Entity = entityIndex; + decalInfo.m_Flags = flags; + + R_GetDecalDimensions( textureIndex, &width, &height ); + decalInfo.m_Size = width >> 1; + if(( height >> 1 ) > decalInfo.m_Size ) + decalInfo.m_Size = height >> 1; + + decalInfo.m_scale = bound( MIN_DECAL_SCALE, scale, MAX_DECAL_SCALE ); + + // compute the decal dimensions in world space + decalInfo.m_decalWidth = width / decalInfo.m_scale; + decalInfo.m_decalHeight = height / decalInfo.m_scale; + + R_DecalNode( model, &model->nodes[hull->firstclipnode], &decalInfo ); +} + +// Build the vertex list for a decal on a surface and clip it to the surface. +// This is a template so it can work on world surfaces and dynamic displacement +// triangles the same way. +float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ) +{ + glpoly_t *p = pDecal->polys; + int i, count; + float *v, *v2; + + if( p ) + { + v = g_DecalClipVerts[0]; + count = p->numverts; + v2 = p->verts[0]; + + // if we have mesh so skip clipping and just copy vertexes out (perf) + for( i = 0; i < count; i++, v += VERTEXSIZE, v2 += VERTEXSIZE ) + { + VectorCopy( v2, v ); + v[3] = v2[3]; + v[4] = v2[4]; + v[5] = v2[5]; + v[6] = v2[6]; + } + + // restore pointer + v = g_DecalClipVerts[0]; + } + else + { + v = R_DecalVertsClip( pDecal, surf, texture, &count ); + R_DecalVertsLight( v, surf, count ); + } + + if( outCount ) + *outCount = count; + + return v; +} + +void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ) +{ + float *v; + int i, numVerts; + + v = R_DecalSetupVerts( pDecal, fa, pDecal->texture, &numVerts ); + if( !numVerts ) return; + + GL_Bind( GL_TEXTURE0, pDecal->texture ); + + pglBegin( GL_POLYGON ); + + for( i = 0; i < numVerts; i++, v += VERTEXSIZE ) + { + pglTexCoord2f( v[3], v[4] ); + pglVertex3fv( v ); + } + + pglEnd(); +} + +void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ) +{ + decal_t *p; + cl_entity_t *e; + + if( !fa->pdecals ) return; + + e = RI.currententity; + Assert( e != NULL ); + + if( single ) + { + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + { + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + + if( e->curstate.rendermode == kRenderTransAlpha ) + pglDisable( GL_ALPHA_TEST ); + } + + if( e->curstate.rendermode == kRenderTransColor ) + pglEnable( GL_TEXTURE_2D ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_NONE ); + + if( gl_polyoffset->value ) + { + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1.0f, -gl_polyoffset->value ); + } + } + + if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) + { + mtexinfo_t *tex = fa->texinfo; + + for( p = fa->pdecals; p; p = p->pnext ) + { + if( p->texture ) + { + float *o, *v; + int i, numVerts; + o = R_DecalSetupVerts( p, fa, p->texture, &numVerts ); + + pglEnable( GL_STENCIL_TEST ); + pglStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFF ); + pglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + pglStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE ); + pglBegin( GL_POLYGON ); + + for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE ) + { + v[5] = ( DotProduct( v, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width; + v[6] = ( DotProduct( v, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height; + + pglTexCoord2f( v[5], v[6] ); + pglVertex3fv( v ); + } + + pglEnd(); + pglStencilOp( GL_KEEP, GL_KEEP, GL_DECR ); + + pglEnable( GL_ALPHA_TEST ); + pglBegin( GL_POLYGON ); + + for( i = 0, v = o; i < numVerts; i++, v += VERTEXSIZE ) + { + pglTexCoord2f( v[5], v[6] ); + pglVertex3fv( v ); + } + + pglEnd(); + pglDisable( GL_ALPHA_TEST ); + + pglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + pglStencilFunc( GL_EQUAL, 0, 0xFFFFFFFF ); + pglStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + } + } + } + + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + if( reverse && e->curstate.rendermode == kRenderTransTexture ) + { + decal_t *list[1024]; + int i, count; + + for( p = fa->pdecals, count = 0; p && count < 1024; p = p->pnext ) + if( p->texture ) list[count++] = p; + + for( i = count - 1; i >= 0; i-- ) + DrawSingleDecal( list[i], fa ); + } + else + { + for( p = fa->pdecals; p; p = p->pnext ) + { + if( !p->texture ) continue; + DrawSingleDecal( p, fa ); + } + } + + if( FBitSet( fa->flags, SURF_TRANSPARENT ) && glState.stencilEnabled ) + pglDisable( GL_STENCIL_TEST ); + + if( single ) + { + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + { + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + + if( e->curstate.rendermode == kRenderTransAlpha ) + pglEnable( GL_ALPHA_TEST ); + } + + if( gl_polyoffset->value ) + pglDisable( GL_POLYGON_OFFSET_FILL ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_FRONT ); + + if( e->curstate.rendermode == kRenderTransColor ) + pglDisable( GL_TEXTURE_2D ); + + // restore blendfunc here + if( e->curstate.rendermode == kRenderTransAdd || e->curstate.rendermode == kRenderGlow ) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + } +} + +void DrawDecalsBatch( void ) +{ + cl_entity_t *e; + int i; + + if( !tr.num_draw_decals ) + return; + + e = RI.currententity; + Assert( e != NULL ); + + if( e->curstate.rendermode != kRenderTransTexture ) + { + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglDepthMask( GL_FALSE ); + } + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_NONE ); + + if( gl_polyoffset->value ) + { + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1.0f, -gl_polyoffset->value ); + } + + for( i = 0; i < tr.num_draw_decals; i++ ) + { + DrawSurfaceDecals( tr.draw_decals[i], false, false ); + } + + if( e->curstate.rendermode != kRenderTransTexture ) + { + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + } + + if( gl_polyoffset->value ) + pglDisable( GL_POLYGON_OFFSET_FILL ); + + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + GL_Cull( GL_FRONT ); + + tr.num_draw_decals = 0; +} + +/* +============================================================= + + DECALS SERIALIZATION + +============================================================= +*/ +static qboolean R_DecalUnProject( decal_t *pdecal, decallist_t *entry ) +{ + if( !pdecal || !( pdecal->psurface )) + return false; + + VectorCopy( pdecal->position, entry->position ); + entry->entityIndex = pdecal->entityIndex; + + // Grab surface plane equation + if( pdecal->psurface->flags & SURF_PLANEBACK ) + VectorNegate( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); + else VectorCopy( pdecal->psurface->plane->normal, entry->impactPlaneNormal ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pList - +// count - +// Output : static int +//----------------------------------------------------------------------------- +static int DecalListAdd( decallist_t *pList, int count ) +{ + vec3_t tmp; + decallist_t *pdecal; + int i; + + pdecal = pList + count; + + for( i = 0; i < count; i++ ) + { + if( !Q_strcmp( pdecal->name, pList[i].name ) && pdecal->entityIndex == pList[i].entityIndex ) + { + VectorSubtract( pdecal->position, pList[i].position, tmp ); // Merge + + if( VectorLength( tmp ) < DECAL_OVERLAP_DISTANCE ) + return count; + } + } + + // this is a new decal + return count + 1; +} + +static int DecalDepthCompare( const void *a, const void *b ) +{ + const decallist_t *elem1, *elem2; + + elem1 = (const decallist_t *)a; + elem2 = (const decallist_t *)b; + + if( elem1->depth > elem2->depth ) + return 1; + if( elem1->depth < elem2->depth ) + return -1; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Called by CSaveRestore::SaveClientState +// Input : *pList - +// Output : int +//----------------------------------------------------------------------------- +int R_CreateDecalList( decallist_t *pList ) +{ + int total = 0; + int i, depth; + + if( cl.worldmodel ) + { + for( i = 0; i < MAX_RENDER_DECALS; i++ ) + { + decal_t *decal = &gDecalPool[i]; + decal_t *pdecals; + + // decal is in use and is not a custom decal + if( decal->psurface == NULL || FBitSet( decal->flags, FDECAL_DONTSAVE )) + continue; + + // compute depth + depth = 0; + pdecals = decal->psurface->pdecals; + + while( pdecals && pdecals != decal ) + { + depth++; + pdecals = pdecals->pnext; + } + + pList[total].depth = depth; + pList[total].flags = decal->flags; + pList[total].scale = decal->scale; + + R_DecalUnProject( decal, &pList[total] ); + COM_FileBase( R_GetTexture( decal->texture )->name, pList[total].name ); + + // check to see if the decal should be added + total = DecalListAdd( pList, total ); + } + + if( clgame.drawFuncs.R_CreateStudioDecalList ) + { + total += clgame.drawFuncs.R_CreateStudioDecalList( pList, total ); + } + } + + // sort the decals lowest depth first, so they can be re-applied in order + qsort( pList, total, sizeof( decallist_t ), DecalDepthCompare ); + + return total; +} + +/* +=============== +R_DecalRemoveAll + +remove all decals with specified texture +=============== +*/ +void R_DecalRemoveAll( int textureIndex ) +{ + decal_t *pdecal; + int i; + + if( textureIndex < 0 || textureIndex >= MAX_TEXTURES ) + { + MsgDev( D_ERROR, "Decal has invalid texture!\n" ); + return; + } + + for( i = 0; i < gDecalCount; i++ ) + { + pdecal = &gDecalPool[i]; + + // don't remove permanent decals + if( !textureIndex && FBitSet( pdecal->flags, FDECAL_PERMANENT )) + continue; + + if( !textureIndex || ( pdecal->texture == textureIndex )) + R_DecalUnlink( pdecal ); + } +} + +/* +=============== +R_EntityRemoveDecals + +remove all decals from specified entity +=============== +*/ +void R_EntityRemoveDecals( model_t *mod ) +{ + msurface_t *psurf; + decal_t *p; + int i; + + if( !mod || mod->type != mod_brush ) + return; + + psurf = &mod->surfaces[mod->firstmodelsurface]; + for( i = 0; i < mod->nummodelsurfaces; i++, psurf++ ) + { + for( p = psurf->pdecals; p; p = p->pnext ) + R_DecalUnlink( p ); + } +} + +/* +=============== +R_ClearAllDecals + +remove all decals from anything +used for full decals restart +=============== +*/ +void R_ClearAllDecals( void ) +{ + decal_t *pdecal; + int i; + + // because gDecalCount may be zeroed after recach the decal limit + for( i = 0; i < MAX_RENDER_DECALS; i++ ) + { + pdecal = &gDecalPool[i]; + R_DecalUnlink( pdecal ); + } + + if( clgame.drawFuncs.R_ClearStudioDecals ) + { + clgame.drawFuncs.R_ClearStudioDecals(); + } +} \ No newline at end of file diff --git a/engine/client/gl_draw.c b/engine/client/gl_draw.c new file mode 100644 index 00000000..2b93d6de --- /dev/null +++ b/engine/client/gl_draw.c @@ -0,0 +1,282 @@ +/* +gl_draw.c - orthogonal drawing stuff +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" + +/* +============= +R_GetImageParms +============= +*/ +void R_GetTextureParms( int *w, int *h, int texnum ) +{ + gltexture_t *glt; + + glt = R_GetTexture( texnum ); + if( w ) *w = glt->srcWidth; + if( h ) *h = glt->srcHeight; +} + +/* +============= +R_GetSpriteParms + +same as GetImageParms but used +for sprite models +============= +*/ +void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int currentFrame, const model_t *pSprite ) +{ + mspriteframe_t *pFrame; + + if( !pSprite || pSprite->type != mod_sprite ) return; // bad model ? + pFrame = R_GetSpriteFrame( pSprite, currentFrame, 0.0f ); + + if( frameWidth ) *frameWidth = pFrame->width; + if( frameHeight ) *frameHeight = pFrame->height; + if( numFrames ) *numFrames = pSprite->numframes; +} + +int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) +{ + if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data ) + return 0; + + return R_GetSpriteFrame( m_pSpriteModel, frame, 0.0f )->gl_texturenum; +} + +/* +============= +R_DrawStretchPic +============= +*/ +void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ) +{ + GL_Bind( GL_TEXTURE0, texnum ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( s1, t1 ); + pglVertex2f( x, y ); + + pglTexCoord2f( s2, t1 ); + pglVertex2f( x + w, y ); + + pglTexCoord2f( s2, t2 ); + pglVertex2f( x + w, y + h ); + + pglTexCoord2f( s1, t2 ); + pglVertex2f( x, y + h ); + pglEnd(); +} + +/* +============= +Draw_TileClear + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +void R_DrawTileClear( int x, int y, int w, int h ) +{ + float tw, th; + gltexture_t *glt; + + GL_SetRenderMode( kRenderNormal ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + GL_Bind( GL_TEXTURE0, cls.tileImage ); + + glt = R_GetTexture( cls.tileImage ); + tw = glt->srcWidth; + th = glt->srcHeight; + + pglBegin( GL_QUADS ); + pglTexCoord2f( x / tw, y / th ); + pglVertex2f( x, y ); + pglTexCoord2f((x + w) / tw, y / th ); + pglVertex2f( x + w, y ); + pglTexCoord2f((x + w) / tw, (y + h) / th ); + pglVertex2f( x + w, y + h ); + pglTexCoord2f( x / tw, (y + h) / th ); + pglVertex2f( x, y + h ); + pglEnd (); +} + +/* +============= +R_DrawStretchRaw +============= +*/ +void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ) +{ + byte *raw = NULL; + gltexture_t *tex; + + if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) + { + int width = 1, height = 1; + + // check the dimensions + width = NearestPOW( cols, true ); + height = NearestPOW( rows, false ); + + if( cols != width || rows != height ) + { + raw = GL_ResampleTexture( data, cols, rows, width, height, false ); + cols = width; + rows = height; + } + } + else + { + raw = (byte *)data; + } + + if( cols > glConfig.max_2d_texture_size ) + Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", cols ); + if( rows > glConfig.max_2d_texture_size ) + Host_Error( "R_DrawStretchRaw: size %i exceeds hardware limits\n", rows ); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + + tex = R_GetTexture( tr.cinTexture ); + GL_Bind( GL_TEXTURE0, tr.cinTexture ); + + if( cols == tex->width && rows == tex->height ) + { + if( dirty ) + { + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + } + } + else + { + tex->width = cols; + tex->height = rows; + if( dirty ) + { + pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + } + } + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0, 0 ); + pglVertex2f( x, y ); + pglTexCoord2f( 1, 0 ); + pglVertex2f( x + w, y ); + pglTexCoord2f( 1, 1 ); + pglVertex2f( x + w, y + h ); + pglTexCoord2f( 0, 1 ); + pglVertex2f( x, y + h ); + pglEnd(); +} + +/* +============= +R_UploadStretchRaw +============= +*/ +void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ) +{ + byte *raw = NULL; + gltexture_t *tex; + + if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) + { + // check the dimensions + width = NearestPOW( width, true ); + height = NearestPOW( height, false ); + } + else + { + width = bound( 128, width, glConfig.max_2d_texture_size ); + height = bound( 128, height, glConfig.max_2d_texture_size ); + } + + if( cols != width || rows != height ) + { + raw = GL_ResampleTexture( data, cols, rows, width, height, false ); + cols = width; + rows = height; + } + else + { + raw = (byte *)data; + } + + if( cols > glConfig.max_2d_texture_size ) + Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", cols ); + if( rows > glConfig.max_2d_texture_size ) + Host_Error( "R_UploadStretchRaw: size %i exceeds hardware limits\n", rows ); + + tex = R_GetTexture( texture ); + GL_Bind( GL_KEEP_UNIT, texture ); + tex->width = cols; + tex->height = rows; + + pglTexImage2D( GL_TEXTURE_2D, 0, tex->format, cols, rows, 0, GL_BGRA, GL_UNSIGNED_BYTE, raw ); + GL_ApplyTextureParams( tex ); +} + +/* +=============== +R_Set2DMode +=============== +*/ +void R_Set2DMode( qboolean enable ) +{ + if( enable ) + { + if( glState.in2DMode ) + return; + + // set 2D virtual screen size + pglViewport( 0, 0, glState.width, glState.height ); + pglMatrixMode( GL_PROJECTION ); + pglLoadIdentity(); + pglOrtho( 0, glState.width, glState.height, 0, -99999, 99999 ); + pglMatrixMode( GL_MODELVIEW ); + pglLoadIdentity(); + + GL_Cull( GL_NONE ); + + pglDepthMask( GL_FALSE ); + pglDisable( GL_DEPTH_TEST ); + pglEnable( GL_ALPHA_TEST ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + + glState.in2DMode = true; + RI.currententity = NULL; + RI.currentmodel = NULL; + } + else + { + pglDepthMask( GL_TRUE ); + pglEnable( GL_DEPTH_TEST ); + glState.in2DMode = false; + + pglMatrixMode( GL_PROJECTION ); + GL_LoadMatrix( RI.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.worldviewMatrix ); + + GL_Cull( GL_FRONT ); + } +} \ No newline at end of file diff --git a/engine/client/gl_export.h b/engine/client/gl_export.h new file mode 100644 index 00000000..12c4f2a6 --- /dev/null +++ b/engine/client/gl_export.h @@ -0,0 +1,1302 @@ +/* +gl_export.h - opengl definition +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef GL_EXPORT_H +#define GL_EXPORT_H +#ifndef APIENTRY +#define APIENTRY +#endif + +typedef uint GLenum; +typedef byte GLboolean; +typedef uint GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef byte GLubyte; +typedef word GLushort; +typedef uint GLuint; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef int GLintptrARB; +typedef int GLsizeiptrARB; +typedef char GLcharARB; +typedef uint GLhandleARB; +typedef float GLmatrix[16]; + +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 + +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +#define GL_DEPTH_TEST 0x0B71 +#define GL_DEPTH_WRITEMASK 0x0B72 +#define GL_CULL_FACE 0x0B44 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_BLEND 0x0BE2 +#define GL_ALPHA_TEST 0x0BC0 + +// shading model +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +#define GL_ZERO 0x0 +#define GL_ONE 0x1 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 + +#define GL_CLAMP_TO_BORDER_ARB 0x812D + +#define GL_ADD 0x0104 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 + +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +#define GL_FALSE 0x0 +#define GL_TRUE 0x1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 + +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +#define GL_NO_ERROR 0x0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +#define GL_DITHER 0x0BD0 +#define GL_ALPHA 0x1906 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +#define GL_RG 0x8227 +#define GL_RG_INTEGER 0x8228 +#define GL_R8 0x8229 +#define GL_R16 0x822A +#define GL_RG8 0x822B +#define GL_RG16 0x822C +#define GL_R16F 0x822D +#define GL_R32F 0x822E +#define GL_RG16F 0x822F +#define GL_RG32F 0x8230 +#define GL_R8I 0x8231 +#define GL_R8UI 0x8232 +#define GL_R16I 0x8233 +#define GL_R16UI 0x8234 +#define GL_R32I 0x8235 +#define GL_R32UI 0x8236 +#define GL_RG8I 0x8237 +#define GL_RG8UI 0x8238 +#define GL_RG16I 0x8239 +#define GL_RG16UI 0x823A +#define GL_RG32I 0x823B +#define GL_RG32UI 0x823C + +// texture coord name +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +// texture gen mode +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +// texture gen parameter +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 + +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 + +#define GL_RED_SCALE 0x0D14 +#define GL_GREEN_SCALE 0x0D18 +#define GL_BLUE_SCALE 0x0D1A +#define GL_ALPHA_SCALE 0x0D1C + +/* AttribMask */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff + +#define GL_STENCIL_TEST 0x0B90 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +// fog stuff +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE0_SGIS 0x835E +#define GL_TEXTURE1_SGIS 0x835F +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_EXT 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_EXT 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT 0x84F8 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_DEPTH_COMPONENT32F 0x8CAC +#define GL_DEPTH32F_STENCIL8 0x8CAD +#define GL_FLOAT_32_UNSIGNED_INT_24_8_REV 0x8DAD + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_ADD_SIGNED 0x8574 + +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 + +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 + +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 + +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF + +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 + +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 + +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C + +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E + +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B + +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F + +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F + +//GL_ARB_vertex_buffer_object +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D + +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C + +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 + +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF + +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E + +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 + +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 + +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 /*WGL_CONTEXT_ES2_PROFILE_BIT_EXT*/ +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 + +// helper opengl functions +GLenum ( APIENTRY *pglGetError )(void); +const GLubyte * ( APIENTRY *pglGetString )(GLenum name); + +// base gl functions +void ( APIENTRY *pglAccum )(GLenum op, GLfloat value); +void ( APIENTRY *pglAlphaFunc )(GLenum func, GLclampf ref); +void ( APIENTRY *pglArrayElement )(GLint i); +void ( APIENTRY *pglBegin )(GLenum mode); +void ( APIENTRY *pglBindTexture )(GLenum target, GLuint texture); +void ( APIENTRY *pglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +void ( APIENTRY *pglBlendFunc )(GLenum sfactor, GLenum dfactor); +void ( APIENTRY *pglCallList )(GLuint list); +void ( APIENTRY *pglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +void ( APIENTRY *pglClear )(GLbitfield mask); +void ( APIENTRY *pglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY *pglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +void ( APIENTRY *pglClearDepth )(GLclampd depth); +void ( APIENTRY *pglClearIndex )(GLfloat c); +void ( APIENTRY *pglClearStencil )(GLint s); +GLboolean ( APIENTRY *pglIsEnabled )( GLenum cap ); +GLboolean ( APIENTRY *pglIsList )( GLuint list ); +GLboolean ( APIENTRY *pglIsTexture )( GLuint texture ); +void ( APIENTRY *pglClipPlane )(GLenum plane, const GLdouble *equation); +void ( APIENTRY *pglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +void ( APIENTRY *pglColor3bv )(const GLbyte *v); +void ( APIENTRY *pglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +void ( APIENTRY *pglColor3dv )(const GLdouble *v); +void ( APIENTRY *pglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +void ( APIENTRY *pglColor3fv )(const GLfloat *v); +void ( APIENTRY *pglColor3i )(GLint red, GLint green, GLint blue); +void ( APIENTRY *pglColor3iv )(const GLint *v); +void ( APIENTRY *pglColor3s )(GLshort red, GLshort green, GLshort blue); +void ( APIENTRY *pglColor3sv )(const GLshort *v); +void ( APIENTRY *pglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +void ( APIENTRY *pglColor3ubv )(const GLubyte *v); +void ( APIENTRY *pglColor3ui )(GLuint red, GLuint green, GLuint blue); +void ( APIENTRY *pglColor3uiv )(const GLuint *v); +void ( APIENTRY *pglColor3us )(GLushort red, GLushort green, GLushort blue); +void ( APIENTRY *pglColor3usv )(const GLushort *v); +void ( APIENTRY *pglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +void ( APIENTRY *pglColor4bv )(const GLbyte *v); +void ( APIENTRY *pglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +void ( APIENTRY *pglColor4dv )(const GLdouble *v); +void ( APIENTRY *pglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +void ( APIENTRY *pglColor4fv )(const GLfloat *v); +void ( APIENTRY *pglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +void ( APIENTRY *pglColor4iv )(const GLint *v); +void ( APIENTRY *pglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +void ( APIENTRY *pglColor4sv )(const GLshort *v); +void ( APIENTRY *pglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +void ( APIENTRY *pglColor4ubv )(const GLubyte *v); +void ( APIENTRY *pglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +void ( APIENTRY *pglColor4uiv )(const GLuint *v); +void ( APIENTRY *pglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +void ( APIENTRY *pglColor4usv )(const GLushort *v); +void ( APIENTRY *pglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +void ( APIENTRY *pglColorMaterial )(GLenum face, GLenum mode); +void ( APIENTRY *pglColorPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +void ( APIENTRY *pglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +void ( APIENTRY *pglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +void ( APIENTRY *pglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +void ( APIENTRY *pglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY *pglCullFace )(GLenum mode); +void ( APIENTRY *pglDeleteLists )(GLuint list, GLsizei range); +void ( APIENTRY *pglDeleteTextures )(GLsizei n, const GLuint *textures); +void ( APIENTRY *pglDepthFunc )(GLenum func); +void ( APIENTRY *pglDepthMask )(GLboolean flag); +void ( APIENTRY *pglDepthRange )(GLclampd zNear, GLclampd zFar); +void ( APIENTRY *pglDisable )(GLenum cap); +void ( APIENTRY *pglDisableClientState )(GLenum array); +void ( APIENTRY *pglDrawArrays )(GLenum mode, GLint first, GLsizei count); +void ( APIENTRY *pglDrawBuffer )(GLenum mode); +void ( APIENTRY *pglDrawElements )(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY *pglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY *pglEdgeFlag )(GLboolean flag); +void ( APIENTRY *pglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglEdgeFlagv )(const GLboolean *flag); +void ( APIENTRY *pglEnable )(GLenum cap); +void ( APIENTRY *pglEnableClientState )(GLenum array); +void ( APIENTRY *pglEnd )(void); +void ( APIENTRY *pglEndList )(void); +void ( APIENTRY *pglEvalCoord1d )(GLdouble u); +void ( APIENTRY *pglEvalCoord1dv )(const GLdouble *u); +void ( APIENTRY *pglEvalCoord1f )(GLfloat u); +void ( APIENTRY *pglEvalCoord1fv )(const GLfloat *u); +void ( APIENTRY *pglEvalCoord2d )(GLdouble u, GLdouble v); +void ( APIENTRY *pglEvalCoord2dv )(const GLdouble *u); +void ( APIENTRY *pglEvalCoord2f )(GLfloat u, GLfloat v); +void ( APIENTRY *pglEvalCoord2fv )(const GLfloat *u); +void ( APIENTRY *pglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +void ( APIENTRY *pglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +void ( APIENTRY *pglEvalPoint1 )(GLint i); +void ( APIENTRY *pglEvalPoint2 )(GLint i, GLint j); +void ( APIENTRY *pglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +void ( APIENTRY *pglFinish )(void); +void ( APIENTRY *pglFlush )(void); +void ( APIENTRY *pglFogf )(GLenum pname, GLfloat param); +void ( APIENTRY *pglFogfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY *pglFogi )(GLenum pname, GLint param); +void ( APIENTRY *pglFogiv )(GLenum pname, const GLint *params); +void ( APIENTRY *pglFrontFace )(GLenum mode); +void ( APIENTRY *pglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY *pglGenTextures )(GLsizei n, GLuint *textures); +void ( APIENTRY *pglGetBooleanv )(GLenum pname, GLboolean *params); +void ( APIENTRY *pglGetClipPlane )(GLenum plane, GLdouble *equation); +void ( APIENTRY *pglGetDoublev )(GLenum pname, GLdouble *params); +void ( APIENTRY *pglGetFloatv )(GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetIntegerv )(GLenum pname, GLint *params); +void ( APIENTRY *pglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetLightiv )(GLenum light, GLenum pname, GLint *params); +void ( APIENTRY *pglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +void ( APIENTRY *pglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +void ( APIENTRY *pglGetMapiv )(GLenum target, GLenum query, GLint *v); +void ( APIENTRY *pglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +void ( APIENTRY *pglGetPixelMapfv )(GLenum map, GLfloat *values); +void ( APIENTRY *pglGetPixelMapuiv )(GLenum map, GLuint *values); +void ( APIENTRY *pglGetPixelMapusv )(GLenum map, GLushort *values); +void ( APIENTRY *pglGetPointerv )(GLenum pname, GLvoid* *params); +void ( APIENTRY *pglGetPolygonStipple )(GLubyte *mask); +void ( APIENTRY *pglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY *pglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +void ( APIENTRY *pglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +void ( APIENTRY *pglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY *pglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +void ( APIENTRY *pglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +void ( APIENTRY *pglHint )(GLenum target, GLenum mode); +void ( APIENTRY *pglIndexMask )(GLuint mask); +void ( APIENTRY *pglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglIndexd )(GLdouble c); +void ( APIENTRY *pglIndexdv )(const GLdouble *c); +void ( APIENTRY *pglIndexf )(GLfloat c); +void ( APIENTRY *pglIndexfv )(const GLfloat *c); +void ( APIENTRY *pglIndexi )(GLint c); +void ( APIENTRY *pglIndexiv )(const GLint *c); +void ( APIENTRY *pglIndexs )(GLshort c); +void ( APIENTRY *pglIndexsv )(const GLshort *c); +void ( APIENTRY *pglIndexub )(GLubyte c); +void ( APIENTRY *pglIndexubv )(const GLubyte *c); +void ( APIENTRY *pglInitNames )(void); +void ( APIENTRY *pglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglLightModelf )(GLenum pname, GLfloat param); +void ( APIENTRY *pglLightModelfv )(GLenum pname, const GLfloat *params); +void ( APIENTRY *pglLightModeli )(GLenum pname, GLint param); +void ( APIENTRY *pglLightModeliv )(GLenum pname, const GLint *params); +void ( APIENTRY *pglLightf )(GLenum light, GLenum pname, GLfloat param); +void ( APIENTRY *pglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +void ( APIENTRY *pglLighti )(GLenum light, GLenum pname, GLint param); +void ( APIENTRY *pglLightiv )(GLenum light, GLenum pname, const GLint *params); +void ( APIENTRY *pglLineStipple )(GLint factor, GLushort pattern); +void ( APIENTRY *pglLineWidth )(GLfloat width); +void ( APIENTRY *pglListBase )(GLuint base); +void ( APIENTRY *pglLoadIdentity )(void); +void ( APIENTRY *pglLoadMatrixd )(const GLdouble *m); +void ( APIENTRY *pglLoadMatrixf )(const GLfloat *m); +void ( APIENTRY *pglLoadName )(GLuint name); +void ( APIENTRY *pglLogicOp )(GLenum opcode); +void ( APIENTRY *pglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +void ( APIENTRY *pglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +void ( APIENTRY *pglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +void ( APIENTRY *pglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +void ( APIENTRY *pglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +void ( APIENTRY *pglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +void ( APIENTRY *pglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +void ( APIENTRY *pglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +void ( APIENTRY *pglMaterialf )(GLenum face, GLenum pname, GLfloat param); +void ( APIENTRY *pglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +void ( APIENTRY *pglMateriali )(GLenum face, GLenum pname, GLint param); +void ( APIENTRY *pglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +void ( APIENTRY *pglMatrixMode )(GLenum mode); +void ( APIENTRY *pglMultMatrixd )(const GLdouble *m); +void ( APIENTRY *pglMultMatrixf )(const GLfloat *m); +void ( APIENTRY *pglNewList )(GLuint list, GLenum mode); +void ( APIENTRY *pglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +void ( APIENTRY *pglNormal3bv )(const GLbyte *v); +void ( APIENTRY *pglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +void ( APIENTRY *pglNormal3dv )(const GLdouble *v); +void ( APIENTRY *pglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +void ( APIENTRY *pglNormal3fv )(const GLfloat *v); +void ( APIENTRY *pglNormal3i )(GLint nx, GLint ny, GLint nz); +void ( APIENTRY *pglNormal3iv )(const GLint *v); +void ( APIENTRY *pglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +void ( APIENTRY *pglNormal3sv )(const GLshort *v); +void ( APIENTRY *pglNormalPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +void ( APIENTRY *pglPassThrough )(GLfloat token); +void ( APIENTRY *pglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +void ( APIENTRY *pglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +void ( APIENTRY *pglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +void ( APIENTRY *pglPixelStoref )(GLenum pname, GLfloat param); +void ( APIENTRY *pglPixelStorei )(GLenum pname, GLint param); +void ( APIENTRY *pglPixelTransferf )(GLenum pname, GLfloat param); +void ( APIENTRY *pglPixelTransferi )(GLenum pname, GLint param); +void ( APIENTRY *pglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +void ( APIENTRY *pglPointSize )(GLfloat size); +void ( APIENTRY *pglPolygonMode )(GLenum face, GLenum mode); +void ( APIENTRY *pglPolygonOffset )(GLfloat factor, GLfloat units); +void ( APIENTRY *pglPolygonStipple )(const GLubyte *mask); +void ( APIENTRY *pglPopAttrib )(void); +void ( APIENTRY *pglPopClientAttrib )(void); +void ( APIENTRY *pglPopMatrix )(void); +void ( APIENTRY *pglPopName )(void); +void ( APIENTRY *pglPushAttrib )(GLbitfield mask); +void ( APIENTRY *pglPushClientAttrib )(GLbitfield mask); +void ( APIENTRY *pglPushMatrix )(void); +void ( APIENTRY *pglPushName )(GLuint name); +void ( APIENTRY *pglRasterPos2d )(GLdouble x, GLdouble y); +void ( APIENTRY *pglRasterPos2dv )(const GLdouble *v); +void ( APIENTRY *pglRasterPos2f )(GLfloat x, GLfloat y); +void ( APIENTRY *pglRasterPos2fv )(const GLfloat *v); +void ( APIENTRY *pglRasterPos2i )(GLint x, GLint y); +void ( APIENTRY *pglRasterPos2iv )(const GLint *v); +void ( APIENTRY *pglRasterPos2s )(GLshort x, GLshort y); +void ( APIENTRY *pglRasterPos2sv )(const GLshort *v); +void ( APIENTRY *pglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY *pglRasterPos3dv )(const GLdouble *v); +void ( APIENTRY *pglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY *pglRasterPos3fv )(const GLfloat *v); +void ( APIENTRY *pglRasterPos3i )(GLint x, GLint y, GLint z); +void ( APIENTRY *pglRasterPos3iv )(const GLint *v); +void ( APIENTRY *pglRasterPos3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY *pglRasterPos3sv )(const GLshort *v); +void ( APIENTRY *pglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY *pglRasterPos4dv )(const GLdouble *v); +void ( APIENTRY *pglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY *pglRasterPos4fv )(const GLfloat *v); +void ( APIENTRY *pglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY *pglRasterPos4iv )(const GLint *v); +void ( APIENTRY *pglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY *pglRasterPos4sv )(const GLshort *v); +void ( APIENTRY *pglReadBuffer )(GLenum mode); +void ( APIENTRY *pglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +void ( APIENTRY *pglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +void ( APIENTRY *pglRectdv )(const GLdouble *v1, const GLdouble *v2); +void ( APIENTRY *pglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +void ( APIENTRY *pglRectfv )(const GLfloat *v1, const GLfloat *v2); +void ( APIENTRY *pglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +void ( APIENTRY *pglRectiv )(const GLint *v1, const GLint *v2); +void ( APIENTRY *pglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +void ( APIENTRY *pglRectsv )(const GLshort *v1, const GLshort *v2); +void ( APIENTRY *pglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY *pglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY *pglScaled )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY *pglScalef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY *pglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY *pglSelectBuffer )(GLsizei size, GLuint *buffer); +void ( APIENTRY *pglShadeModel )(GLenum mode); +void ( APIENTRY *pglStencilFunc )(GLenum func, GLint ref, GLuint mask); +void ( APIENTRY *pglStencilMask )(GLuint mask); +void ( APIENTRY *pglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +void ( APIENTRY *pglTexCoord1d )(GLdouble s); +void ( APIENTRY *pglTexCoord1dv )(const GLdouble *v); +void ( APIENTRY *pglTexCoord1f )(GLfloat s); +void ( APIENTRY *pglTexCoord1fv )(const GLfloat *v); +void ( APIENTRY *pglTexCoord1i )(GLint s); +void ( APIENTRY *pglTexCoord1iv )(const GLint *v); +void ( APIENTRY *pglTexCoord1s )(GLshort s); +void ( APIENTRY *pglTexCoord1sv )(const GLshort *v); +void ( APIENTRY *pglTexCoord2d )(GLdouble s, GLdouble t); +void ( APIENTRY *pglTexCoord2dv )(const GLdouble *v); +void ( APIENTRY *pglTexCoord2f )(GLfloat s, GLfloat t); +void ( APIENTRY *pglTexCoord2fv )(const GLfloat *v); +void ( APIENTRY *pglTexCoord2i )(GLint s, GLint t); +void ( APIENTRY *pglTexCoord2iv )(const GLint *v); +void ( APIENTRY *pglTexCoord2s )(GLshort s, GLshort t); +void ( APIENTRY *pglTexCoord2sv )(const GLshort *v); +void ( APIENTRY *pglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +void ( APIENTRY *pglTexCoord3dv )(const GLdouble *v); +void ( APIENTRY *pglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +void ( APIENTRY *pglTexCoord3fv )(const GLfloat *v); +void ( APIENTRY *pglTexCoord3i )(GLint s, GLint t, GLint r); +void ( APIENTRY *pglTexCoord3iv )(const GLint *v); +void ( APIENTRY *pglTexCoord3s )(GLshort s, GLshort t, GLshort r); +void ( APIENTRY *pglTexCoord3sv )(const GLshort *v); +void ( APIENTRY *pglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +void ( APIENTRY *pglTexCoord4dv )(const GLdouble *v); +void ( APIENTRY *pglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +void ( APIENTRY *pglTexCoord4fv )(const GLfloat *v); +void ( APIENTRY *pglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +void ( APIENTRY *pglTexCoord4iv )(const GLint *v); +void ( APIENTRY *pglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +void ( APIENTRY *pglTexCoord4sv )(const GLshort *v); +void ( APIENTRY *pglTexCoordPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY *pglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY *pglTexEnvi )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY *pglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY *pglTexGend )(GLenum coord, GLenum pname, GLdouble param); +void ( APIENTRY *pglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +void ( APIENTRY *pglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +void ( APIENTRY *pglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +void ( APIENTRY *pglTexGeni )(GLenum coord, GLenum pname, GLint param); +void ( APIENTRY *pglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +void ( APIENTRY *pglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY *pglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY *pglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +void ( APIENTRY *pglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +void ( APIENTRY *pglTexParameteri )(GLenum target, GLenum pname, GLint param); +void ( APIENTRY *pglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +void ( APIENTRY *pglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY *pglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +void ( APIENTRY *pglTranslated )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY *pglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY *pglVertex2d )(GLdouble x, GLdouble y); +void ( APIENTRY *pglVertex2dv )(const GLdouble *v); +void ( APIENTRY *pglVertex2f )(GLfloat x, GLfloat y); +void ( APIENTRY *pglVertex2fv )(const GLfloat *v); +void ( APIENTRY *pglVertex2i )(GLint x, GLint y); +void ( APIENTRY *pglVertex2iv )(const GLint *v); +void ( APIENTRY *pglVertex2s )(GLshort x, GLshort y); +void ( APIENTRY *pglVertex2sv )(const GLshort *v); +void ( APIENTRY *pglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +void ( APIENTRY *pglVertex3dv )(const GLdouble *v); +void ( APIENTRY *pglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +void ( APIENTRY *pglVertex3fv )(const GLfloat *v); +void ( APIENTRY *pglVertex3i )(GLint x, GLint y, GLint z); +void ( APIENTRY *pglVertex3iv )(const GLint *v); +void ( APIENTRY *pglVertex3s )(GLshort x, GLshort y, GLshort z); +void ( APIENTRY *pglVertex3sv )(const GLshort *v); +void ( APIENTRY *pglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +void ( APIENTRY *pglVertex4dv )(const GLdouble *v); +void ( APIENTRY *pglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY *pglVertex4fv )(const GLfloat *v); +void ( APIENTRY *pglVertex4i )(GLint x, GLint y, GLint z, GLint w); +void ( APIENTRY *pglVertex4iv )(const GLint *v); +void ( APIENTRY *pglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +void ( APIENTRY *pglVertex4sv )(const GLshort *v); +void ( APIENTRY *pglVertexPointer )(GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); +void ( APIENTRY *pglPointParameterfEXT)( GLenum param, GLfloat value ); +void ( APIENTRY *pglPointParameterfvEXT)( GLenum param, const GLfloat *value ); +void ( APIENTRY *pglLockArraysEXT) (int , int); +void ( APIENTRY *pglUnlockArraysEXT) (void); +void ( APIENTRY *pglActiveTextureARB)( GLenum ); +void ( APIENTRY *pglClientActiveTextureARB)( GLenum ); +void ( APIENTRY *pglGetCompressedTexImage)( GLenum target, GLint lod, const GLvoid* data ); +void ( APIENTRY *pglDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); +void ( APIENTRY *pglDrawRangeElementsEXT)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices ); +void ( APIENTRY *pglDrawElements)(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices); +void ( APIENTRY *pglVertexPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void ( APIENTRY *pglNormalPointer)(GLenum type, GLsizei stride, const GLvoid *ptr); +void ( APIENTRY *pglColorPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void ( APIENTRY *pglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr); +void ( APIENTRY *pglArrayElement)(GLint i); +void ( APIENTRY *pglMultiTexCoord1f) (GLenum, GLfloat); +void ( APIENTRY *pglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +void ( APIENTRY *pglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +void ( APIENTRY *pglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +void ( APIENTRY *pglActiveTexture) (GLenum); +void ( APIENTRY *pglClientActiveTexture) (GLenum); +void ( APIENTRY *pglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +void ( APIENTRY *pglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +void ( APIENTRY *pglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +void ( APIENTRY *pglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +void ( APIENTRY *pglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +void ( APIENTRY *pglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +void ( APIENTRY *pglDeleteObjectARB)(GLhandleARB obj); +GLhandleARB ( APIENTRY *pglGetHandleARB)(GLenum pname); +void ( APIENTRY *pglDetachObjectARB)(GLhandleARB containerObj, GLhandleARB attachedObj); +GLhandleARB ( APIENTRY *pglCreateShaderObjectARB)(GLenum shaderType); +void ( APIENTRY *pglShaderSourceARB)(GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +void ( APIENTRY *pglCompileShaderARB)(GLhandleARB shaderObj); +GLhandleARB ( APIENTRY *pglCreateProgramObjectARB)(void); +void ( APIENTRY *pglAttachObjectARB)(GLhandleARB containerObj, GLhandleARB obj); +void ( APIENTRY *pglLinkProgramARB)(GLhandleARB programObj); +void ( APIENTRY *pglUseProgramObjectARB)(GLhandleARB programObj); +void ( APIENTRY *pglValidateProgramARB)(GLhandleARB programObj); +void ( APIENTRY *pglBindProgramARB)(GLenum target, GLuint program); +void ( APIENTRY *pglDeleteProgramsARB)(GLsizei n, const GLuint *programs); +void ( APIENTRY *pglGenProgramsARB)(GLsizei n, GLuint *programs); +void ( APIENTRY *pglProgramStringARB)(GLenum target, GLenum format, GLsizei len, const GLvoid *string); +void ( APIENTRY *pglProgramEnvParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY *pglProgramLocalParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +void ( APIENTRY *pglUniform1fARB)(GLint location, GLfloat v0); +void ( APIENTRY *pglUniform2fARB)(GLint location, GLfloat v0, GLfloat v1); +void ( APIENTRY *pglUniform3fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +void ( APIENTRY *pglUniform4fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +void ( APIENTRY *pglUniform1iARB)(GLint location, GLint v0); +void ( APIENTRY *pglUniform2iARB)(GLint location, GLint v0, GLint v1); +void ( APIENTRY *pglUniform3iARB)(GLint location, GLint v0, GLint v1, GLint v2); +void ( APIENTRY *pglUniform4iARB)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +void ( APIENTRY *pglUniform1fvARB)(GLint location, GLsizei count, const GLfloat *value); +void ( APIENTRY *pglUniform2fvARB)(GLint location, GLsizei count, const GLfloat *value); +void ( APIENTRY *pglUniform3fvARB)(GLint location, GLsizei count, const GLfloat *value); +void ( APIENTRY *pglUniform4fvARB)(GLint location, GLsizei count, const GLfloat *value); +void ( APIENTRY *pglUniform1ivARB)(GLint location, GLsizei count, const GLint *value); +void ( APIENTRY *pglUniform2ivARB)(GLint location, GLsizei count, const GLint *value); +void ( APIENTRY *pglUniform3ivARB)(GLint location, GLsizei count, const GLint *value); +void ( APIENTRY *pglUniform4ivARB)(GLint location, GLsizei count, const GLint *value); +void ( APIENTRY *pglUniformMatrix2fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void ( APIENTRY *pglUniformMatrix3fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void ( APIENTRY *pglUniformMatrix4fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +void ( APIENTRY *pglGetObjectParameterfvARB)(GLhandleARB obj, GLenum pname, GLfloat *params); +void ( APIENTRY *pglGetObjectParameterivARB)(GLhandleARB obj, GLenum pname, GLint *params); +void ( APIENTRY *pglGetInfoLogARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +void ( APIENTRY *pglGetAttachedObjectsARB)(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +GLint ( APIENTRY *pglGetUniformLocationARB)(GLhandleARB programObj, const GLcharARB *name); +void ( APIENTRY *pglGetActiveUniformARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +void ( APIENTRY *pglGetUniformfvARB)(GLhandleARB programObj, GLint location, GLfloat *params); +void ( APIENTRY *pglGetUniformivARB)(GLhandleARB programObj, GLint location, GLint *params); +void ( APIENTRY *pglGetShaderSourceARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +void ( APIENTRY *pglPolygonStipple)(const GLubyte *mask); +void ( APIENTRY *pglTexImage3D)( GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels ); +void ( APIENTRY *pglTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels ); +void ( APIENTRY *pglCopyTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height ); +void ( APIENTRY *pglBlendEquationEXT)(GLenum); +void ( APIENTRY *pglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +void ( APIENTRY *pglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); +void ( APIENTRY *pglActiveStencilFaceEXT)(GLenum); +void ( APIENTRY *pglVertexAttribPointerARB)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +void ( APIENTRY *pglEnableVertexAttribArrayARB)(GLuint index); +void ( APIENTRY *pglDisableVertexAttribArrayARB)(GLuint index); +void ( APIENTRY *pglBindAttribLocationARB)(GLhandleARB programObj, GLuint index, const GLcharARB *name); +void ( APIENTRY *pglGetActiveAttribARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +GLint ( APIENTRY *pglGetAttribLocationARB)(GLhandleARB programObj, const GLcharARB *name); +void ( APIENTRY *pglBindFragDataLocation)(GLuint programObj, GLuint index, const GLcharARB *name); +void ( APIENTRY *pglVertexAttrib2fARB)( GLuint index, GLfloat x, GLfloat y ); +void ( APIENTRY *pglVertexAttrib2fvARB)( GLuint index, const GLfloat *v ); +void ( APIENTRY *pglVertexAttrib3fvARB)( GLuint index, const GLfloat *v ); +void ( APIENTRY *pglBindBufferARB) (GLenum target, GLuint buffer); +void ( APIENTRY *pglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +void ( APIENTRY *pglGenBuffersARB) (GLsizei n, GLuint *buffers); +GLboolean ( APIENTRY *pglIsBufferARB) (GLuint buffer); +GLvoid* ( APIENTRY *pglMapBufferARB) (GLenum target, GLenum access); +GLboolean ( APIENTRY *pglUnmapBufferARB) (GLenum target); +void ( APIENTRY *pglBufferDataARB) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +void ( APIENTRY *pglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +void ( APIENTRY *pglGenQueriesARB) (GLsizei n, GLuint *ids); +void ( APIENTRY *pglDeleteQueriesARB) (GLsizei n, const GLuint *ids); +GLboolean ( APIENTRY *pglIsQueryARB) (GLuint id); +void ( APIENTRY *pglBeginQueryARB) (GLenum target, GLuint id); +void ( APIENTRY *pglEndQueryARB) (GLenum target); +void ( APIENTRY *pglGetQueryivARB) (GLenum target, GLenum pname, GLint *params); +void ( APIENTRY *pglGetQueryObjectivARB) (GLuint id, GLenum pname, GLint *params); +void ( APIENTRY *pglGetQueryObjectuivARB) (GLuint id, GLenum pname, GLuint *params); +typedef void ( APIENTRY *pglDebugProcARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLcharARB* message, GLvoid* userParam ); +void ( APIENTRY *pglDebugMessageControlARB)( GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled ); +void ( APIENTRY *pglDebugMessageInsertARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* buf ); +void ( APIENTRY *pglDebugMessageCallbackARB)( pglDebugProcARB callback, void* userParam ); +GLuint ( APIENTRY *pglGetDebugMessageLogARB)( GLuint count, GLsizei bufsize, GLenum* sources, GLenum* types, GLuint* ids, GLuint* severities, GLsizei* lengths, char* messageLog ); +GLboolean ( APIENTRY *pglIsRenderbuffer )(GLuint renderbuffer); +void ( APIENTRY *pglBindRenderbuffer )(GLenum target, GLuint renderbuffer); +void ( APIENTRY *pglDeleteRenderbuffers )(GLsizei n, const GLuint *renderbuffers); +void ( APIENTRY *pglGenRenderbuffers )(GLsizei n, GLuint *renderbuffers); +void ( APIENTRY *pglRenderbufferStorage )(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +void ( APIENTRY *pglRenderbufferStorageMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +void ( APIENTRY *pglGetRenderbufferParameteriv )(GLenum target, GLenum pname, GLint *params); +GLboolean (APIENTRY *pglIsFramebuffer )(GLuint framebuffer); +void ( APIENTRY *pglBindFramebuffer )(GLenum target, GLuint framebuffer); +void ( APIENTRY *pglDeleteFramebuffers )(GLsizei n, const GLuint *framebuffers); +void ( APIENTRY *pglGenFramebuffers )(GLsizei n, GLuint *framebuffers); +GLenum ( APIENTRY *pglCheckFramebufferStatus )(GLenum target); +void ( APIENTRY *pglFramebufferTexture1D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +void ( APIENTRY *pglFramebufferTexture2D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +void ( APIENTRY *pglFramebufferTexture3D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +void ( APIENTRY *pglFramebufferTextureLayer )(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +void ( APIENTRY *pglFramebufferRenderbuffer )(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +void ( APIENTRY *pglGetFramebufferAttachmentParameteriv )(GLenum target, GLenum attachment, GLenum pname, GLint *params); +void ( APIENTRY *pglBlitFramebuffer )(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +void ( APIENTRY *pglDrawBuffersARB)( GLsizei n, const GLenum *bufs ); +void ( APIENTRY *pglGenerateMipmap )( GLenum target ); +void ( APIENTRY *pglBindVertexArray )( GLuint array ); +void ( APIENTRY *pglDeleteVertexArrays )( GLsizei n, const GLuint *arrays ); +void ( APIENTRY *pglGenVertexArrays )( GLsizei n, const GLuint *arrays ); +GLboolean ( APIENTRY *pglIsVertexArray )( GLuint array ); +void ( APIENTRY * pglSwapInterval) ( int interval ); +extern void *pglGetProcAddress( const GLubyte * ); +BOOL ( WINAPI * pwglSwapBuffers)(HDC); +BOOL ( WINAPI * pwglCopyContext)(HGLRC, HGLRC, UINT); +HGLRC ( WINAPI * pwglCreateContext)(HDC); +HGLRC ( WINAPI * pwglCreateLayerContext)(HDC, int); +BOOL ( WINAPI * pwglDeleteContext)(HGLRC); +HGLRC ( WINAPI * pwglGetCurrentContext)(VOID); +PROC ( WINAPI * pwglGetProcAddress)(LPCSTR); +BOOL ( WINAPI * pwglMakeCurrent)(HDC, HGLRC); +BOOL ( WINAPI * pwglShareLists)(HGLRC, HGLRC); +BOOL ( WINAPI * pwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); +BOOL ( WINAPI * pwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); +BOOL ( WINAPI * pwglDescribeLayerPlane)(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); +int ( WINAPI * pwglSetLayerPaletteEntries)(HDC, int, int, int, CONST COLORREF *); +int ( WINAPI * pwglGetLayerPaletteEntries)(HDC, int, int, int, COLORREF *); +BOOL ( WINAPI * pwglRealizeLayerPalette)(HDC, int, BOOL); +BOOL ( WINAPI * pwglSwapLayerBuffers)(HDC, UINT); +BOOL ( WINAPI * pwglSwapIntervalEXT)( int interval ); +HGLRC ( WINAPI * pwglCreateContextAttribsARB)( HDC hDC, HGLRC hShareContext, const int *attribList ); +const char *( WINAPI * pwglGetExtensionsStringEXT)( void ); + +#endif//GL_EXPORT_H \ No newline at end of file diff --git a/engine/client/gl_frustum.c b/engine/client/gl_frustum.c new file mode 100644 index 00000000..c17432a5 --- /dev/null +++ b/engine/client/gl_frustum.c @@ -0,0 +1,355 @@ +/* +gl_frustum.cpp - frustum test implementation +Copyright (C) 2016 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "gl_local.h" +#include "mathlib.h" + +void GL_FrustumEnablePlane( gl_frustum_t *out, int side ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + // make sure what plane is ready + if( !VectorIsNull( out->planes[side].normal )) + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumDisablePlane( gl_frustum_t *out, int side ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + ClearBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ) +{ + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + out->planes[side].type = PlaneTypeForNormal( vecNormal ); + out->planes[side].signbits = SignbitsForPlane( vecNormal ); + VectorCopy( vecNormal, out->planes[side].normal ); + out->planes[side].dist = flDist; + + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ) +{ + float length; + + Assert( side >= 0 && side < FRUSTUM_PLANES ); + + // normalize + length = VectorLength( out->planes[side].normal ); + + if( length ) + { + float ilength = (1.0f / length); + out->planes[side].normal[0] *= ilength; + out->planes[side].normal[1] *= ilength; + out->planes[side].normal[2] *= ilength; + out->planes[side].dist *= ilength; + } + + out->planes[side].type = PlaneTypeForNormal( out->planes[side].normal ); + out->planes[side].signbits = SignbitsForPlane( out->planes[side].normal ); + + SetBits( out->clipFlags, BIT( side )); +} + +void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ) +{ + float xs, xc; + vec3_t farpoint, nearpoint; + vec3_t normal, iforward; + + // horizontal fov used for left and right planes + SinCos( DEG2RAD( flFovX ) * 0.5f, &xs, &xc ); + + // setup left plane + VectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vright, normal ); + GL_FrustumSetPlane( out, FRUSTUM_LEFT, normal, DotProduct( RI.cullorigin, normal )); + + // setup right plane + VectorMAM( xs, RI.cull_vforward, xc, RI.cull_vright, normal ); + GL_FrustumSetPlane( out, FRUSTUM_RIGHT, normal, DotProduct( RI.cullorigin, normal )); + + // vertical fov used for top and bottom planes + SinCos( DEG2RAD( flFovY ) * 0.5f, &xs, &xc ); + VectorNegate( RI.cull_vforward, iforward ); + + // setup bottom plane + VectorMAM( xs, RI.cull_vforward, -xc, RI.cull_vup, normal ); + GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, normal, DotProduct( RI.cullorigin, normal )); + + // setup top plane + VectorMAM( xs, RI.cull_vforward, xc, RI.cull_vup, normal ); + GL_FrustumSetPlane( out, FRUSTUM_TOP, normal, DotProduct( RI.cullorigin, normal )); + + // setup far plane + VectorMA( RI.cullorigin, flZFar, RI.cull_vforward, farpoint ); + GL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, DotProduct( iforward, farpoint )); + + // no need to setup backplane for general view. + if( flZNear == 0.0f ) return; + + // setup near plane + VectorMA( RI.cullorigin, flZNear, RI.cull_vforward, nearpoint ); + GL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, DotProduct( RI.cull_vforward, nearpoint )); +} + +void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ) +{ + vec3_t iforward, iright, iup; + + // setup the near and far planes + float orgOffset = DotProduct( RI.cullorigin, RI.cull_vforward ); + VectorNegate( RI.cull_vforward, iforward ); + + // because quake ortho is inverted and far and near should be swaped + GL_FrustumSetPlane( out, FRUSTUM_FAR, iforward, -flZNear - orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_NEAR, RI.cull_vforward, flZFar + orgOffset ); + + // setup left and right planes + orgOffset = DotProduct( RI.cullorigin, RI.cull_vright ); + VectorNegate( RI.cull_vright, iright ); + + GL_FrustumSetPlane( out, FRUSTUM_LEFT, RI.cull_vright, xLeft + orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_RIGHT, iright, -xRight - orgOffset ); + + // setup top and buttom planes + orgOffset = DotProduct( RI.cullorigin, RI.cull_vup ); + VectorNegate( RI.cull_vup, iup ); + + GL_FrustumSetPlane( out, FRUSTUM_TOP, RI.cull_vup, yTop + orgOffset ); + GL_FrustumSetPlane( out, FRUSTUM_BOTTOM, iup, -yBottom - orgOffset ); +} + +void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ) +{ + vec3_t normal; + int i; + + for( i = 0; i < FRUSTUM_PLANES; i++ ) + { + // setup normal for each direction + VectorClear( normal ); + normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f; + GL_FrustumSetPlane( out, i, normal, DotProduct( org, normal ) - radius ); + } +} + +void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ) +{ + int i; + + // left + out->planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0]; + out->planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0]; + out->planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0]; + out->planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]); + + // right + out->planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0]; + out->planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0]; + out->planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0]; + out->planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]); + + // bottom + out->planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1]; + out->planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1]; + out->planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1]; + out->planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]); + + // top + out->planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1]; + out->planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1]; + out->planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1]; + out->planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]); + + // near + out->planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2]; + out->planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2]; + out->planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2]; + out->planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]); + + // far + out->planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2]; + out->planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2]; + out->planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2]; + out->planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]); + + for( i = 0; i < FRUSTUM_PLANES; i++ ) + { + GL_FrustumNormalizePlane( out, i ); + } +} + +void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t corners[8] ) +{ + memset( corners, 0, sizeof( corners )); + + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[0] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_FAR], corners[1] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[2] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_FAR], corners[3] ); + + if( FBitSet( out->clipFlags, BIT( FRUSTUM_NEAR ))) + { + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[4] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], &out->planes[FRUSTUM_NEAR], corners[5] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[6] ); + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_BOTTOM], &out->planes[FRUSTUM_NEAR], corners[7] ); + } + else + { + PlanesGetIntersectionPoint( &out->planes[FRUSTUM_LEFT], &out->planes[FRUSTUM_RIGHT], &out->planes[FRUSTUM_TOP], corners[4] ); + VectorCopy( corners[4], corners[5] ); + VectorCopy( corners[4], corners[6] ); + VectorCopy( corners[4], corners[7] ); + } +} + +void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ) +{ + vec3_t corners[8]; + int i; + + GL_FrustumComputeCorners( out, corners ); + + ClearBounds( mins, maxs ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( corners[i], mins, maxs ); +} + +void GL_FrustumDrawDebug( gl_frustum_t *out ) +{ + vec3_t bbox[8]; + int i; + + GL_FrustumComputeCorners( out, bbox ); + + // g-cont. frustum must be yellow :-) + pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f ); + pglDisable( GL_TEXTURE_2D ); + pglBegin( GL_LINES ); + + for( i = 0; i < 2; i += 1 ) + { + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i*2+0] ); + pglVertex3fv( bbox[i*2+1] ); + pglVertex3fv( bbox[i*2+4] ); + pglVertex3fv( bbox[i*2+5] ); + } + + pglEnd(); + pglEnable( GL_TEXTURE_2D ); +} + +// cull methods +qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ) +{ + int iClipFlags; + int i, bit; + + if( r_nocull->value ) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = out->clipFlags; + + for( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 ) + { + const mplane_t *p = &out->planes[FRUSTUM_PLANES - i]; + + if( !FBitSet( iClipFlags, bit )) + continue; + + switch( p->signbits ) + { + case 0: + if( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 1: + if( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 2: + if( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 3: + if( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * maxs[2] < p->dist ) + return true; + break; + case 4: + if( p->normal[0] * maxs[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 5: + if( p->normal[0] * mins[0] + p->normal[1] * maxs[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 6: + if( p->normal[0] * maxs[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + case 7: + if( p->normal[0] * mins[0] + p->normal[1] * mins[1] + p->normal[2] * mins[2] < p->dist ) + return true; + break; + default: + return false; + } + } + + return false; +} + +qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t center, float radius, int userClipFlags ) +{ + int iClipFlags; + int i, bit; + + if( r_nocull->value ) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = out->clipFlags; + + for( i = FRUSTUM_PLANES, bit = 1; i > 0; i--, bit <<= 1 ) + { + const mplane_t *p = &out->planes[FRUSTUM_PLANES - i]; + + if( !FBitSet( iClipFlags, bit )) + continue; + + if( DotProduct( center, p->normal ) - p->dist <= -radius ) + return true; + } + + return false; +} \ No newline at end of file diff --git a/engine/client/gl_frustum.h b/engine/client/gl_frustum.h new file mode 100644 index 00000000..61052434 --- /dev/null +++ b/engine/client/gl_frustum.h @@ -0,0 +1,52 @@ +/* +gl_frustum.cpp - frustum test implementation +Copyright (C) 2016 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef GL_FRUSTUM_H +#define GL_FRUSTUM_H + +// don't change this order +#define FRUSTUM_LEFT 0 +#define FRUSTUM_RIGHT 1 +#define FRUSTUM_BOTTOM 2 +#define FRUSTUM_TOP 3 +#define FRUSTUM_FAR 4 +#define FRUSTUM_NEAR 5 +#define FRUSTUM_PLANES 6 + +typedef struct gl_frustum_s +{ + mplane_t planes[FRUSTUM_PLANES]; + unsigned int clipFlags; +} gl_frustum_t; + +void GL_FrustumInitProj( gl_frustum_t *out, float flZNear, float flZFar, float flFovX, float flFovY ); +void GL_FrustumInitOrtho( gl_frustum_t *out, float xLeft, float xRight, float yTop, float yBottom, float flZNear, float flZFar ); +void GL_FrustumInitBox( gl_frustum_t *out, const vec3_t org, float radius ); // used for pointlights +void GL_FrustumInitProjFromMatrix( gl_frustum_t *out, const matrix4x4 projection ); +void GL_FrustumSetPlane( gl_frustum_t *out, int side, const vec3_t vecNormal, float flDist ); +void GL_FrustumNormalizePlane( gl_frustum_t *out, int side ); +void GL_FrustumComputeBounds( gl_frustum_t *out, vec3_t mins, vec3_t maxs ); +void GL_FrustumComputeCorners( gl_frustum_t *out, vec3_t bbox[8] ); +void GL_FrustumDrawDebug( gl_frustum_t *out ); + +// cull methods +qboolean GL_FrustumCullBox( gl_frustum_t *out, const vec3_t mins, const vec3_t maxs, int userClipFlags ); +qboolean GL_FrustumCullSphere( gl_frustum_t *out, const vec3_t centre, float radius, int userClipFlags ); + +// plane manipulating +void GL_FrustumEnablePlane( gl_frustum_t *out, int side ); +void GL_FrustumDisablePlane( gl_frustum_t *out, int side ); + +#endif//GL_FRUSTUM_H \ No newline at end of file diff --git a/engine/client/gl_image.c b/engine/client/gl_image.c new file mode 100644 index 00000000..f1161d90 --- /dev/null +++ b/engine/client/gl_image.c @@ -0,0 +1,2775 @@ +/* +gl_image.c - texture uploading and processing +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "studio.h" + +#define TEXTURES_HASH_SIZE (MAX_TEXTURES >> 2) + +static gltexture_t r_textures[MAX_TEXTURES]; +static gltexture_t *r_texturesHashTable[TEXTURES_HASH_SIZE]; +static byte data2D[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4]; // intermediate texbuffer +static int r_numTextures; +static rgbdata_t r_image; // generic pixelbuffer used for internal textures + +// internal tables +static vec3_t r_luminanceTable[256]; // RGB to luminance + +#define IsLightMap( tex ) ( FBitSet(( tex )->flags, TF_ATLAS_PAGE )) +/* +================= +R_GetTexture + +acess to array elem +================= +*/ +gltexture_t *R_GetTexture( GLenum texnum ) +{ + ASSERT( texnum >= 0 && texnum < MAX_TEXTURES ); + return &r_textures[texnum]; +} + +/* +================= +GL_TargetToString +================= +*/ +static const char *GL_TargetToString( GLenum target ) +{ + switch( target ) + { + case GL_TEXTURE_1D: + return "1D"; + case GL_TEXTURE_2D: + return "2D"; + case GL_TEXTURE_3D: + return "3D"; + case GL_TEXTURE_CUBE_MAP_ARB: + return "Cube"; + case GL_TEXTURE_2D_ARRAY_EXT: + return "Array"; + case GL_TEXTURE_RECTANGLE_EXT: + return "Rect"; + } + return "??"; +} + +/* +================= +GL_Bind +================= +*/ +void GL_Bind( GLint tmu, GLenum texnum ) +{ + gltexture_t *texture; + GLuint glTarget; + + // missed texture ? + if( texnum <= 0 ) texnum = tr.defaultTexture; + Assert( texnum > 0 && texnum < MAX_TEXTURES ); + + if( tmu != GL_KEEP_UNIT ) + GL_SelectTexture( tmu ); + else tmu = glState.activeTMU; + + texture = &r_textures[texnum]; + glTarget = texture->target; + + if( glTarget == GL_TEXTURE_2D_ARRAY_EXT ) + glTarget = GL_TEXTURE_2D; + + if( glState.currentTextureTargets[tmu] != glTarget ) + { + if( glState.currentTextureTargets[tmu] != GL_NONE ) + pglDisable( glState.currentTextureTargets[tmu] ); + glState.currentTextureTargets[tmu] = glTarget; + pglEnable( glState.currentTextureTargets[tmu] ); + } + + if( glState.currentTextures[tmu] == texture->texnum ) + return; + + pglBindTexture( texture->target, texture->texnum ); + glState.currentTextures[tmu] = texture->texnum; +} + +/* +================= +GL_ApplyTextureParams +================= +*/ +void GL_ApplyTextureParams( gltexture_t *tex ) +{ + vec4_t border = { 0.0f, 0.0f, 0.0f, 1.0f }; + + Assert( tex != NULL ); + + // set texture filter + if( FBitSet( tex->flags, TF_DEPTHMAP )) + { + if( !FBitSet( tex->flags, TF_NOCOMPARE )) + { + pglTexParameteri( tex->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB ); + pglTexParameteri( tex->target, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL ); + } + + if( FBitSet( tex->flags, TF_LUMINANCE )) + pglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE ); + else pglTexParameteri( tex->target, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY ); + + if( FBitSet( tex->flags, TF_NEAREST )) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + // allow max anisotropy as 1.0f on depth textures + if( GL_Support( GL_ANISOTROPY_EXT )) + pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f ); + } + else if( FBitSet( tex->flags, TF_NOMIPMAP ) || tex->numMips <= 1 ) + { + if( FBitSet( tex->flags, TF_NEAREST ) || ( IsLightMap( tex ) && gl_lightmap_nearest->value )) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + } + else + { + if( FBitSet( tex->flags, TF_NEAREST ) || gl_texture_nearest->value ) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + + // set texture anisotropy if available + if( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 )) + pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy->value ); + + // set texture LOD bias if available + if( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 )) + pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias->value ); + } + + // check if border is not supported + if( FBitSet( tex->flags, TF_BORDER ) && !GL_Support( GL_CLAMP_TEXBORDER_EXT )) + { + ClearBits( tex->flags, TF_BORDER ); + SetBits( tex->flags, TF_CLAMP ); + } + + // only seamless cubemaps allows wrap 'clamp_to_border" + if( tex->target == GL_TEXTURE_CUBE_MAP_ARB && !GL_Support( GL_ARB_SEAMLESS_CUBEMAP ) && FBitSet( tex->flags, TF_BORDER )) + ClearBits( tex->flags, TF_BORDER ); + + // set texture wrap + if( FBitSet( tex->flags, TF_BORDER )) + { + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); + + if( tex->target != GL_TEXTURE_1D ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); + + if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER ); + + pglTexParameterfv( tex->target, GL_TEXTURE_BORDER_COLOR, border ); + } + else if( FBitSet( tex->flags, TF_CLAMP )) + { + if( GL_Support( GL_CLAMPTOEDGE_EXT )) + { + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); + + if( tex->target != GL_TEXTURE_1D ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); + + if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_CLAMP ); + + if( tex->target != GL_TEXTURE_1D ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_CLAMP ); + + if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_CLAMP ); + } + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_S, GL_REPEAT ); + + if( tex->target != GL_TEXTURE_1D ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_T, GL_REPEAT ); + + if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + pglTexParameteri( tex->target, GL_TEXTURE_WRAP_R, GL_REPEAT ); + } +} + +/* +================= +GL_UpdateTextureParams +================= +*/ +static void GL_UpdateTextureParams( int iTexture ) +{ + gltexture_t *tex = &r_textures[iTexture]; + + Assert( tex != NULL ); + + if( !tex->texnum ) return; // free slot + + GL_Bind( GL_TEXTURE0, iTexture ); + + // set texture anisotropy if available + if( GL_Support( GL_ANISOTROPY_EXT ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP )) + pglTexParameterf( tex->target, GL_TEXTURE_MAX_ANISOTROPY_EXT, gl_texture_anisotropy->value ); + + // set texture LOD bias if available + if( GL_Support( GL_TEXTURE_LOD_BIAS ) && ( tex->numMips > 1 ) && !FBitSet( tex->flags, TF_DEPTHMAP )) + pglTexParameterf( tex->target, GL_TEXTURE_LOD_BIAS_EXT, gl_texture_lodbias->value ); + + if( IsLightMap( tex )) + { + if( gl_lightmap_nearest->value ) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } + } + + if( tex->numMips <= 1 ) return; + + if( FBitSet( tex->flags, TF_NEAREST ) || gl_texture_nearest->value ) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } +} + +/* +================= +R_SetTextureParameters +================= +*/ +void R_SetTextureParameters( void ) +{ + int i; + + if( GL_Support( GL_ANISOTROPY_EXT )) + { + if( gl_texture_anisotropy->value > glConfig.max_texture_anisotropy ) + Cvar_SetValue( "gl_anisotropy", glConfig.max_texture_anisotropy ); + else if( gl_texture_anisotropy->value < 1.0f ) + Cvar_SetValue( "gl_anisotropy", 1.0f ); + } + + if( GL_Support( GL_TEXTURE_LOD_BIAS )) + { + if( gl_texture_lodbias->value < -glConfig.max_texture_lod_bias ) + Cvar_SetValue( "gl_mipmap_bias", -glConfig.max_texture_lod_bias ); + else if( gl_texture_lodbias->value > glConfig.max_texture_lod_bias ) + Cvar_SetValue( "gl_mipmap_bias", glConfig.max_texture_lod_bias ); + } + + ClearBits( gl_texture_anisotropy->flags, FCVAR_CHANGED ); + ClearBits( gl_texture_lodbias->flags, FCVAR_CHANGED ); + ClearBits( gl_texture_nearest->flags, FCVAR_CHANGED ); + ClearBits( gl_lightmap_nearest->flags, FCVAR_CHANGED ); + + // change all the existing mipmapped texture objects + for( i = 0; i < r_numTextures; i++ ) + GL_UpdateTextureParams( i ); +} + +/* +================ +GL_CalcTextureSamples +================ +*/ +static int GL_CalcTextureSamples( int flags ) +{ + if( FBitSet( flags, IMAGE_HAS_COLOR )) + return FBitSet( flags, IMAGE_HAS_ALPHA ) ? 4 : 3; + return FBitSet( flags, IMAGE_HAS_ALPHA ) ? 2 : 1; +} + +/* +================== +GL_CalcImageSize +================== +*/ +static size_t GL_CalcImageSize( pixformat_t format, int width, int height, int depth ) +{ + size_t size = 0; + + // check the depth error + depth = Q_max( 1, depth ); + + switch( format ) + { + case PF_RGB_24: + case PF_BGR_24: + size = width * height * depth * 3; + break; + case PF_BGRA_32: + case PF_RGBA_32: + size = width * height * depth * 4; + break; + case PF_DXT1: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; + break; + case PF_DXT3: + case PF_DXT5: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; + break; + } + + return size; +} + +/* +================== +GL_CalcTextureSize +================== +*/ +static size_t GL_CalcTextureSize( GLenum format, int width, int height, int depth ) +{ + size_t size = 0; + + // check the depth error + depth = Q_max( 1, depth ); + + switch( format ) + { + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 8) * depth; + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + size = (((width + 3) >> 2) * ((height + 3) >> 2) * 16) * depth; + break; + case GL_RGBA8: + case GL_RGBA: + size = width * height * depth * 4; + break; + case GL_RGB8: + case GL_RGB: + size = width * height * depth * 4; + break; + case GL_INTENSITY: + case GL_LUMINANCE: + case GL_INTENSITY8: + case GL_LUMINANCE8: + size = (width * height * depth); + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + size = width * height * depth * 2; + break; + case GL_R8: + size = width * height * depth; + break; + case GL_RG8: + size = width * height * depth * 2; + break; + case GL_R16: + size = width * height * depth * 2; + break; + case GL_RG16: + size = width * height * depth * 4; + break; + case GL_R16F: + case GL_LUMINANCE16F_ARB: + size = width * height * depth * 2; // half-floats + break; + case GL_R32F: + case GL_LUMINANCE32F_ARB: + size = width * height * depth * 4; + break; + case GL_RG16F: + case GL_LUMINANCE_ALPHA16F_ARB: + size = width * height * depth * 4; + break; + case GL_RG32F: + case GL_LUMINANCE_ALPHA32F_ARB: + size = width * height * depth * 8; + break; + case GL_RGB16F_ARB: + case GL_RGBA16F_ARB: + size = width * height * depth * 8; + break; + case GL_RGB32F_ARB: + case GL_RGBA32F_ARB: + size = width * height * depth * 16; + break; + case GL_DEPTH_COMPONENT16: + size = width * height * depth * 2; + break; + case GL_DEPTH_COMPONENT24: + size = width * height * depth * 4; + break; + case GL_DEPTH_COMPONENT32F: + size = width * height * depth * 4; + break; + default: + Host_Error( "GL_CalcTextureSize: bad texture internal format (%u)\n", format ); + break; + } + + return size; +} + +static int GL_CalcMipmapCount( gltexture_t *tex, qboolean haveBuffer ) +{ + int width, height; + int mipcount; + + Assert( tex != NULL ); + + if( !haveBuffer || tex->target == GL_TEXTURE_3D ) + return 1; + + // generate mip-levels by user request + if( FBitSet( tex->flags, TF_NOMIPMAP )) + return 1; + + // mip-maps can't exceeds 16 + for( mipcount = 0; mipcount < 16; mipcount++ ) + { + width = Q_max( 1, ( tex->width >> mipcount )); + height = Q_max( 1, ( tex->height >> mipcount )); + if( width == 1 && height == 1 ) + break; + } + + return mipcount + 1; +} + +/* +================ +GL_SetTextureDimensions +================ +*/ +static void GL_SetTextureDimensions( gltexture_t *tex, int width, int height, int depth ) +{ + int maxTextureSize; + int maxDepthSize = 1; + + Assert( tex != NULL ); + + switch( tex->target ) + { + case GL_TEXTURE_1D: + case GL_TEXTURE_2D: + maxTextureSize = glConfig.max_2d_texture_size; + break; + case GL_TEXTURE_2D_ARRAY_EXT: + maxDepthSize = glConfig.max_2d_texture_layers; + maxTextureSize = glConfig.max_2d_texture_size; + break; + case GL_TEXTURE_RECTANGLE_EXT: + maxTextureSize = glConfig.max_2d_rectangle_size; + break; + case GL_TEXTURE_CUBE_MAP_ARB: + maxTextureSize = glConfig.max_cubemap_size; + break; + case GL_TEXTURE_3D: + maxDepthSize = glConfig.max_3d_texture_size; + maxTextureSize = glConfig.max_3d_texture_size; + break; + } + + // store original sizes + tex->srcWidth = width; + tex->srcHeight = height; + + if( !GL_Support( GL_ARB_TEXTURE_NPOT_EXT )) + { + width = (width + 3) & ~3; + height = (height + 3) & ~3; + } + + if( width > maxTextureSize || height > maxTextureSize || depth > maxDepthSize ) + { + if( tex->target == GL_TEXTURE_1D ) + { + while( width > maxTextureSize ) + width >>= 1; + } + else if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT ) + { + while( width > maxTextureSize || height > maxTextureSize || depth > maxDepthSize ) + { + width >>= 1; + height >>= 1; + depth >>= 1; + } + } + else // all remaining cases + { + while( width > maxTextureSize || height > maxTextureSize ) + { + width >>= 1; + height >>= 1; + } + } + } + + // set the texture dimensions + tex->width = Q_max( 1, width ); + tex->height = Q_max( 1, height ); + tex->depth = Q_max( 1, depth ); +} + +/* +=============== +GL_SetTextureTarget +=============== +*/ +static void GL_SetTextureTarget( gltexture_t *tex, rgbdata_t *pic ) +{ + Assert( pic != NULL ); + Assert( tex != NULL ); + + // correct depth size + pic->depth = Q_max( 1, pic->depth ); + tex->numMips = 0; // begin counting + + // correct mip count + pic->numMips = Q_max( 1, pic->numMips ); + + // trying to determine texture type + if( pic->width > 1 && pic->height <= 1 ) + tex->target = GL_TEXTURE_1D; + else if( FBitSet( pic->flags, IMAGE_CUBEMAP )) + tex->target = GL_TEXTURE_CUBE_MAP_ARB; + else if( FBitSet( pic->flags, IMAGE_MULTILAYER ) && pic->depth >= 1 ) + tex->target = GL_TEXTURE_2D_ARRAY_EXT; + else if( pic->width > 1 && pic->height > 1 && pic->depth > 1 ) + tex->target = GL_TEXTURE_3D; + else if( FBitSet( tex->flags, TF_TEXTURE_RECTANGLE ) && pic->width == glState.width && pic->height == glState.height ) + tex->target = GL_TEXTURE_RECTANGLE_EXT; + else tex->target = GL_TEXTURE_2D; // default case + + // check for hardware support + if(( tex->target == GL_TEXTURE_CUBE_MAP_ARB ) && !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + tex->target = GL_NONE; + + if(( tex->target == GL_TEXTURE_RECTANGLE_EXT ) && !GL_Support( GL_TEXTURE_2D_RECT_EXT )) + tex->target = GL_TEXTURE_2D; // fallback + + if(( tex->target == GL_TEXTURE_2D_ARRAY_EXT ) && !GL_Support( GL_TEXTURE_ARRAY_EXT )) + tex->target = GL_NONE; + + if(( tex->target == GL_TEXTURE_3D ) && !GL_Support( GL_TEXTURE_3D_EXT )) + tex->target = GL_NONE; + + // check if depth textures are not supported + if( FBitSet( tex->flags, TF_DEPTHMAP ) && !GL_Support( GL_DEPTH_TEXTURE )) + tex->target = GL_NONE; + + // depth cubemaps only allowed when GL_EXT_gpu_shader4 is supported + if( tex->target == GL_TEXTURE_CUBE_MAP_ARB && !GL_Support( GL_EXT_GPU_SHADER4 ) && FBitSet( tex->flags, TF_DEPTHMAP )) + tex->target = GL_NONE; + + if( tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + tex->flags |= TF_CUBEMAP; // it's cubemap! +} + +/* +=============== +GL_SetTextureFormat +=============== +*/ +static void GL_SetTextureFormat( gltexture_t *tex, pixformat_t format, int channelMask ) +{ + qboolean haveColor = ( channelMask & IMAGE_HAS_COLOR ); + qboolean haveAlpha = ( channelMask & IMAGE_HAS_ALPHA ); + + Assert( tex != NULL ); + + if( ImageDXT( format )) + { + switch( format ) + { + case PF_DXT1: tex->format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; // never use DXT1 with 1-bit alpha + case PF_DXT3: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case PF_DXT5: tex->format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + } + return; + } + else if( FBitSet( tex->flags, TF_DEPTHMAP )) + { + if( FBitSet( tex->flags, TF_ARB_16BIT )) + tex->format = GL_DEPTH_COMPONENT16; + else if( FBitSet( tex->flags, TF_ARB_FLOAT ) && GL_Support( GL_ARB_DEPTH_FLOAT_EXT )) + tex->format = GL_DEPTH_COMPONENT32F; + else tex->format = GL_DEPTH_COMPONENT24; + } + else if( FBitSet( tex->flags, TF_ARB_FLOAT ) && GL_Support( GL_ARB_TEXTURE_FLOAT_EXT )) + { + if( haveColor && haveAlpha ) + { + if( FBitSet( tex->flags, TF_ARB_16BIT ) || glw_state.desktopBitsPixel == 16 ) + tex->format = GL_RGBA16F_ARB; + else tex->format = GL_RGBA32F_ARB; + } + else if( haveColor ) + { + if( FBitSet( tex->flags, TF_ARB_16BIT ) || glw_state.desktopBitsPixel == 16 ) + tex->format = GL_RGB16F_ARB; + else tex->format = GL_RGB32F_ARB; + } + else if( haveAlpha ) + { + if( FBitSet( tex->flags, TF_ARB_16BIT ) || glw_state.desktopBitsPixel == 16 ) + tex->format = GL_LUMINANCE_ALPHA16F_ARB; + else tex->format = GL_LUMINANCE_ALPHA32F_ARB; + } + else + { + if( FBitSet( tex->flags, TF_ARB_16BIT ) || glw_state.desktopBitsPixel == 16 ) + tex->format = GL_LUMINANCE16F_ARB; + else tex->format = GL_LUMINANCE32F_ARB; + } + } + else + { + // NOTE: not all the types will be compressed + int bits = glw_state.desktopBitsPixel; + + switch( GL_CalcTextureSamples( channelMask ) ) + { + case 1: tex->format = GL_LUMINANCE8; break; + case 2: tex->format = GL_LUMINANCE8_ALPHA8; break; + case 3: + switch( bits ) + { + case 16: tex->format = GL_RGB5; break; + case 32: tex->format = GL_RGB8; break; + default: tex->format = GL_RGB; break; + } + break; + case 4: + default: + switch( bits ) + { + case 16: tex->format = GL_RGBA4; break; + case 32: tex->format = GL_RGBA8; break; + default: tex->format = GL_RGBA; break; + } + break; + } + } +} + +/* +================= +GL_ResampleTexture + +Assume input buffer is RGBA +================= +*/ +byte *GL_ResampleTexture( const byte *source, int inWidth, int inHeight, int outWidth, int outHeight, qboolean isNormalMap ) +{ + uint frac, fracStep; + uint *in = (uint *)source; + uint p1[0x1000], p2[0x1000]; + byte *pix1, *pix2, *pix3, *pix4; + uint *out, *inRow1, *inRow2; + static byte *scaledImage = NULL; // pointer to a scaled image + vec3_t normal; + int i, x, y; + + if( !source ) return NULL; + + scaledImage = Mem_Realloc( r_temppool, scaledImage, outWidth * outHeight * 4 ); + fracStep = inWidth * 0x10000 / outWidth; + out = (uint *)scaledImage; + + frac = fracStep >> 2; + for( i = 0; i < outWidth; i++ ) + { + p1[i] = 4 * (frac >> 16); + frac += fracStep; + } + + frac = (fracStep >> 2) * 3; + for( i = 0; i < outWidth; i++ ) + { + p2[i] = 4 * (frac >> 16); + frac += fracStep; + } + + if( isNormalMap ) + { + for( y = 0; y < outHeight; y++, out += outWidth ) + { + inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); + inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + + for( x = 0; x < outWidth; x++ ) + { + pix1 = (byte *)inRow1 + p1[x]; + pix2 = (byte *)inRow1 + p2[x]; + pix3 = (byte *)inRow2 + p1[x]; + pix4 = (byte *)inRow2 + p2[x]; + + normal[0] = MAKE_SIGNED( pix1[0] ) + MAKE_SIGNED( pix2[0] ) + MAKE_SIGNED( pix3[0] ) + MAKE_SIGNED( pix4[0] ); + normal[1] = MAKE_SIGNED( pix1[1] ) + MAKE_SIGNED( pix2[1] ) + MAKE_SIGNED( pix3[1] ) + MAKE_SIGNED( pix4[1] ); + normal[2] = MAKE_SIGNED( pix1[2] ) + MAKE_SIGNED( pix2[2] ) + MAKE_SIGNED( pix3[2] ) + MAKE_SIGNED( pix4[2] ); + + if( !VectorNormalizeLength( normal )) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + ((byte *)(out+x))[0] = 128 + (byte)(127.0f * normal[0]); + ((byte *)(out+x))[1] = 128 + (byte)(127.0f * normal[1]); + ((byte *)(out+x))[2] = 128 + (byte)(127.0f * normal[2]); + ((byte *)(out+x))[3] = 255; + } + } + } + else + { + for( y = 0; y < outHeight; y++, out += outWidth ) + { + inRow1 = in + inWidth * (int)(((float)y + 0.25f) * inHeight / outHeight); + inRow2 = in + inWidth * (int)(((float)y + 0.75f) * inHeight / outHeight); + + for( x = 0; x < outWidth; x++ ) + { + pix1 = (byte *)inRow1 + p1[x]; + pix2 = (byte *)inRow1 + p2[x]; + pix3 = (byte *)inRow2 + p1[x]; + pix4 = (byte *)inRow2 + p2[x]; + + ((byte *)(out+x))[0] = (pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2; + ((byte *)(out+x))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2; + ((byte *)(out+x))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2; + ((byte *)(out+x))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2; + } + } + } + + return scaledImage; +} + +/* +================= +GL_BoxFilter3x3 + +box filter 3x3 +================= +*/ +void GL_BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) +{ + int r = 0, g = 0, b = 0, a = 0; + int count = 0, acount = 0; + int i, j, u, v; + const byte *pixel; + + for( i = 0; i < 3; i++ ) + { + u = ( i - 1 ) + x; + + for( j = 0; j < 3; j++ ) + { + v = ( j - 1 ) + y; + + if( u >= 0 && u < w && v >= 0 && v < h ) + { + pixel = &in[( u + v * w ) * 4]; + + if( pixel[3] != 0 ) + { + r += pixel[0]; + g += pixel[1]; + b += pixel[2]; + a += pixel[3]; + acount++; + } + } + } + } + + if( acount == 0 ) + acount = 1; + + out[0] = r / acount; + out[1] = g / acount; + out[2] = b / acount; +// out[3] = (int)( SimpleSpline( ( a / 12.0f ) / 255.0f ) * 255 ); +} + +/* +================= +GL_ApplyFilter + +Apply box-filter to 1-bit alpha +================= +*/ +byte *GL_ApplyFilter( const byte *source, int width, int height ) +{ + byte *in = (byte *)source; + byte *out = (byte *)source; + int i; + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return in; + + for( i = 0; source && i < width * height; i++, in += 4 ) + { + if( in[0] == 0 && in[1] == 0 && in[2] == 0 && in[3] == 0 ) + GL_BoxFilter3x3( in, source, width, height, i % width, i / width ); + } + + return out; +} + +/* +================= +GL_ApplyGamma + +Assume input buffer is RGBA +================= +*/ +byte *GL_ApplyGamma( const byte *source, int pixels, qboolean isNormalMap ) +{ + byte *in = (byte *)source; + byte *out = (byte *)source; + int i; + + if( source && !isNormalMap ) + { + for( i = 0; i < pixels; i++, in += 4 ) + { + in[0] = TextureToGamma( in[0] ); + in[1] = TextureToGamma( in[1] ); + in[2] = TextureToGamma( in[2] ); + } + } + return out; +} + +/* +================= +GL_BuildMipMap + +Operates in place, quartering the size of the texture +================= +*/ +static void GL_BuildMipMap( byte *in, int srcWidth, int srcHeight, int srcDepth, qboolean isNormalMap ) +{ + byte *out = in; + int instride = ALIGN( srcWidth * 4, 1 ); + int mipWidth, mipHeight, outpadding; + int row, x, y, z; + vec3_t normal; + + if( !in ) return; + + mipWidth = max( 1, ( srcWidth >> 1 )); + mipHeight = max( 1, ( srcHeight >> 1 )); + outpadding = ALIGN( mipWidth * 4, 1 ) - mipWidth * 4; + row = srcWidth << 2; + + // move through all layers + for( z = 0; z < srcDepth; z++ ) + { + if( isNormalMap ) + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( in[row+4] ) + + MAKE_SIGNED( next[row+0] ) + MAKE_SIGNED( next[row+4] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( in[row+5] ) + + MAKE_SIGNED( next[row+1] ) + MAKE_SIGNED( next[row+5] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( in[row+6] ) + + MAKE_SIGNED( next[row+2] ) + MAKE_SIGNED( next[row+6] ); + } + else + { + normal[0] = MAKE_SIGNED( in[row+0] ) + MAKE_SIGNED( next[row+0] ); + normal[1] = MAKE_SIGNED( in[row+1] ) + MAKE_SIGNED( next[row+1] ); + normal[2] = MAKE_SIGNED( in[row+2] ) + MAKE_SIGNED( next[row+2] ); + } + + + if( !VectorNormalizeLength( normal )) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + out[0] = 128 + (byte)(127.0f * normal[0]); + out[1] = 128 + (byte)(127.0f * normal[1]); + out[2] = 128 + (byte)(127.0f * normal[2]); + out[3] = 255; + } + } + } + else + { + for( y = 0; y < mipHeight; y++, in += instride * 2, out += outpadding ) + { + byte *next = ((( y << 1 ) + 1 ) < srcHeight ) ? ( in + instride ) : in; + for( x = 0, row = 0; x < mipWidth; x++, row += 8, out += 4 ) + { + if((( x << 1 ) + 1 ) < srcWidth ) + { + out[0] = (in[row+0] + in[row+4] + next[row+0] + next[row+4]) >> 2; + out[1] = (in[row+1] + in[row+5] + next[row+1] + next[row+5]) >> 2; + out[2] = (in[row+2] + in[row+6] + next[row+2] + next[row+6]) >> 2; + out[3] = (in[row+3] + in[row+7] + next[row+3] + next[row+7]) >> 2; + } + else + { + out[0] = (in[row+0] + next[row+0]) >> 1; + out[1] = (in[row+1] + next[row+1]) >> 1; + out[2] = (in[row+2] + next[row+2]) >> 1; + out[3] = (in[row+3] + next[row+3]) >> 1; + } + } + } + } + } +} + +/* +================= +GL_MakeLuminance + +Converts the given image to luminance +================= +*/ +void GL_MakeLuminance( rgbdata_t *in ) +{ + byte luminance; + float r, g, b; + int x, y; + + for( y = 0; y < in->height; y++ ) + { + for( x = 0; x < in->width; x++ ) + { + r = r_luminanceTable[in->buffer[4*(y*in->width+x)+0]][0]; + g = r_luminanceTable[in->buffer[4*(y*in->width+x)+1]][1]; + b = r_luminanceTable[in->buffer[4*(y*in->width+x)+2]][2]; + + luminance = (byte)(r + g + b); + + in->buffer[4*(y*in->width+x)+0] = luminance; + in->buffer[4*(y*in->width+x)+1] = luminance; + in->buffer[4*(y*in->width+x)+2] = luminance; + } + } +} + +static void GL_TextureImageRAW( gltexture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, GLint type, const void *data ) +{ + GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; + qboolean subImage = ( tex->flags & TF_IMG_UPLOADED ); + GLenum inFormat = PFDesc[type].glFormat; + GLint dataType = GL_UNSIGNED_BYTE; + + Assert( tex != NULL ); + + if( tex->flags & TF_DEPTHMAP ) + inFormat = GL_DEPTH_COMPONENT; + + if( tex->target == GL_TEXTURE_1D ) + { + if( subImage ) pglTexSubImage1D( tex->target, level, 0, width, inFormat, dataType, data ); + else pglTexImage1D( tex->target, level, tex->format, width, 0, inFormat, dataType, data ); + } + else if( tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + { + if( subImage ) pglTexSubImage2D( cubeTarget + side, level, 0, 0, width, height, inFormat, dataType, data ); + else pglTexImage2D( cubeTarget + side, level, tex->format, width, height, 0, inFormat, dataType, data ); + } + else if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT ) + { + if( subImage ) pglTexSubImage3D( tex->target, level, 0, 0, 0, width, height, depth, inFormat, dataType, data ); + else pglTexImage3D( tex->target, level, tex->format, width, height, depth, 0, inFormat, dataType, data ); + } + else // 2D or RECT + { + if( subImage ) pglTexSubImage2D( tex->target, level, 0, 0, width, height, inFormat, dataType, data ); + else pglTexImage2D( tex->target, level, tex->format, width, height, 0, inFormat, dataType, data ); + } +} + +static void GL_TextureImageDXT( gltexture_t *tex, GLint side, GLint level, GLint width, GLint height, GLint depth, size_t size, const void *data ) +{ + GLuint cubeTarget = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; + qboolean subImage = ( tex->flags & TF_IMG_UPLOADED ); + + Assert( tex != NULL ); + + if( tex->target == GL_TEXTURE_1D ) + { + if( subImage ) pglCompressedTexSubImage1DARB( tex->target, level, 0, width, tex->format, size, data ); + else pglCompressedTexImage1DARB( tex->target, level, tex->format, width, 0, size, data ); + } + else if( tex->target == GL_TEXTURE_CUBE_MAP_ARB ) + { + if( subImage ) pglCompressedTexSubImage2DARB( cubeTarget + side, level, 0, 0, width, height, tex->format, size, data ); + else pglCompressedTexImage2DARB( cubeTarget + side, level, tex->format, width, height, 0, size, data ); + } + else if( tex->target == GL_TEXTURE_3D || tex->target == GL_TEXTURE_2D_ARRAY_EXT ) + { + if( subImage ) pglCompressedTexSubImage3DARB( tex->target, level, 0, 0, 0, width, height, depth, tex->format, size, data ); + else pglCompressedTexImage3DARB( tex->target, level, tex->format, width, height, depth, 0, size, data ); + } + else // 2D or RECT + { + if( subImage ) pglCompressedTexSubImage2DARB( tex->target, level, 0, 0, width, height, tex->format, size, data ); + else pglCompressedTexImage2DARB( tex->target, level, tex->format, width, height, 0, size, data ); + } +} + +/* +=============== +GL_CheckTexImageError + +show GL-errors on load images +=============== +*/ +static void GL_CheckTexImageError( gltexture_t *tex ) +{ + int err; + + Assert( tex != NULL ); + + // catch possible errors + if(( err = pglGetError()) != GL_NO_ERROR ) + MsgDev( D_ERROR, "GL_UploadTexture: error %x while uploading %s [%s]\n", err, tex->name, GL_TargetToString( tex->target )); +} + +/* +=============== +GL_UploadTexture + +upload texture into video memory +=============== +*/ +static qboolean GL_UploadTexture( gltexture_t *tex, rgbdata_t *pic ) +{ + byte *buf, *data; + size_t texsize, size; + uint width, height; + uint i, j, numSides; + uint offset = 0; + qboolean normalMap; + const byte *bufend; + + Assert( pic != NULL ); + Assert( tex != NULL ); + + GL_SetTextureTarget( tex, pic ); // must be first + + // make sure what target is correct + if( tex->target == GL_NONE ) + { + MsgDev( D_ERROR, "GL_UploadTexture: %s is not supported by your hardware\n", tex->name ); + return false; + } + + GL_SetTextureDimensions( tex, pic->width, pic->height, pic->depth ); + GL_SetTextureFormat( tex, pic->type, pic->flags ); + + tex->fogParams[0] = pic->fogParams[0]; + tex->fogParams[1] = pic->fogParams[1]; + tex->fogParams[2] = pic->fogParams[2]; + tex->fogParams[3] = pic->fogParams[3]; + + if(( pic->width * pic->height ) & 3 ) + { + // will be resampled, just tell me for debug targets + MsgDev( D_NOTE, "GL_UploadTexture: %s s&3 [%d x %d]\n", tex->name, pic->width, pic->height ); + } + + buf = pic->buffer; + bufend = pic->buffer + pic->size; // total image size include all the layers, cube sides, mipmaps + offset = GL_CalcImageSize( pic->type, pic->width, pic->height, pic->depth ); + texsize = GL_CalcTextureSize( tex->format, tex->width, tex->height, tex->depth ); + normalMap = FBitSet( tex->flags, TF_NORMALMAP ) ? true : false; + numSides = FBitSet( pic->flags, IMAGE_CUBEMAP ) ? 6 : 1; + + // uploading texture into video memory, change the binding + glState.currentTextures[glState.activeTMU] = tex->texnum; + pglBindTexture( tex->target, tex->texnum ); + + for( i = 0; i < numSides; i++ ) + { + // track the buffer bounds + if( buf != NULL && buf >= bufend ) + Host_Error( "GL_UploadTexture: %s image buffer overflow\n", tex->name ); + + if( ImageDXT( pic->type )) + { + for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) + { + width = Q_max( 1, ( tex->width >> j )); + height = Q_max( 1, ( tex->height >> j )); + texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); + size = GL_CalcImageSize( pic->type, width, height, tex->depth ); + GL_TextureImageDXT( tex, i, j, width, height, tex->depth, size, buf ); + tex->size += texsize; + buf += size; // move pointer + tex->numMips++; + + GL_CheckTexImageError( tex ); + } + } + else if( Q_max( 1, pic->numMips ) > 1 ) // not-compressed DDS + { + for( j = 0; j < Q_max( 1, pic->numMips ); j++ ) + { + width = Q_max( 1, ( tex->width >> j )); + height = Q_max( 1, ( tex->height >> j )); + texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); + size = GL_CalcImageSize( pic->type, width, height, tex->depth ); + GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, buf ); + tex->size += texsize; + buf += size; // move pointer + tex->numMips++; + + GL_CheckTexImageError( tex ); + + } + } + else // RGBA32 + { + int mipCount = GL_CalcMipmapCount( tex, ( buf != NULL )); + + // NOTE: only single uncompressed textures can be resamples, no mips, no layers, no sides + if(( tex->depth == 1 ) && ( pic->width != tex->width ) || ( pic->height != tex->height )) + data = GL_ResampleTexture( buf, pic->width, pic->height, tex->width, tex->height, normalMap ); + else data = buf; + + if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP|TF_SKYSIDE )) + data = GL_ApplyGamma( data, tex->width * tex->height * tex->depth, FBitSet( tex->flags, TF_NORMALMAP )); + + if( !ImageDXT( pic->type ) && !FBitSet( tex->flags, TF_NOMIPMAP ) && FBitSet( pic->flags, IMAGE_ONEBIT_ALPHA )) + data = GL_ApplyFilter( data, tex->width, tex->height ); + + // mips will be auto-generated if desired + for( j = 0; j < mipCount; j++ ) + { + width = Q_max( 1, ( tex->width >> j )); + height = Q_max( 1, ( tex->height >> j )); + texsize = GL_CalcTextureSize( tex->format, width, height, tex->depth ); + size = GL_CalcImageSize( pic->type, width, height, tex->depth ); + GL_TextureImageRAW( tex, i, j, width, height, tex->depth, pic->type, data ); + if( mipCount > 1 ) + GL_BuildMipMap( data, width, height, tex->depth, normalMap ); + tex->size += texsize; + tex->numMips++; + + GL_CheckTexImageError( tex ); + } + + // move to next side + if( numSides > 1 && ( buf != NULL )) + buf += GL_CalcImageSize( pic->type, pic->width, pic->height, 1 ); + } + } + + tex->flags |= TF_IMG_UPLOADED; // done + tex->numMips /= numSides; + + return true; +} + +/* +=============== +GL_ProcessImage + +do specified actions on pixels +=============== +*/ +static void GL_ProcessImage( gltexture_t *tex, rgbdata_t *pic, imgfilter_t *filter ) +{ + uint img_flags = 0; + + // force upload texture as RGB or RGBA (detail textures requires this) + if( tex->flags & TF_FORCE_COLOR ) pic->flags |= IMAGE_HAS_COLOR; + if( pic->flags & IMAGE_HAS_ALPHA ) tex->flags |= TF_HAS_ALPHA; + + tex->encode = pic->encode; // share encode method + + if( ImageDXT( pic->type )) + { + if( !pic->numMips ) + tex->flags |= TF_NOMIPMAP; // disable mipmapping by user request + + // clear all the unsupported flags + tex->flags &= ~TF_KEEP_SOURCE; + } + else + { + // copy flag about luma pixels + if( pic->flags & IMAGE_HAS_LUMA ) + tex->flags |= TF_HAS_LUMA; + + if( pic->flags & IMAGE_QUAKEPAL ) + tex->flags |= TF_QUAKEPAL; + + // create luma texture from quake texture + if( tex->flags & TF_MAKELUMA ) + { + img_flags |= IMAGE_MAKE_LUMA; + tex->flags &= ~TF_MAKELUMA; + } + + if( !FBitSet( tex->flags, TF_IMG_UPLOADED ) && FBitSet( tex->flags, TF_KEEP_SOURCE )) + tex->original = FS_CopyImage( pic ); // because current pic will be expanded to rgba + + // we need to expand image into RGBA buffer + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + img_flags |= IMAGE_FORCE_RGBA; + + // processing image before uploading (force to rgba, make luma etc) + if( pic->buffer ) Image_Process( &pic, 0, 0, img_flags, filter ); + + if( tex->flags & TF_LUMINANCE ) + { + if( !( tex->flags & TF_DEPTHMAP )) + { + GL_MakeLuminance( pic ); + tex->flags &= ~TF_LUMINANCE; + } + pic->flags &= ~IMAGE_HAS_COLOR; + } + } +} + +/* +================ +GL_LoadTexture +================ +*/ +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags, imgfilter_t *filter ) +{ + gltexture_t *tex; + rgbdata_t *pic; + uint i, hash; + uint picFlags = 0; + + if( !COM_CheckString( name ) || !glw_state.initialized ) + return 0; + + if( Q_strlen( name ) >= sizeof( r_textures->name )) + { + Con_Printf( S_ERROR "LoadTexture: too long name %s (%d)\n", name, Q_strlen( name )); + return 0; + } + + // see if already loaded + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = r_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + return (tex - r_textures); + } + + if( flags & TF_NOFLIP_TGA ) + picFlags |= IL_DONTFLIP_TGA; + + if( FBitSet( flags, TF_KEEP_SOURCE ) && !FBitSet( flags, TF_EXPAND_SOURCE )) + picFlags |= IL_KEEP_8BIT; + + // set some image flags + Image_SetForceFlags( picFlags ); + + pic = FS_LoadImage( name, buf, size ); + if( !pic ) return 0; // couldn't loading image + + // find a free texture slot + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + + // find a free texture_t slot + for( i = 0, tex = r_textures; i < r_numTextures; i++, tex++ ) + if( !tex->name[0] ) break; + + if( i == r_numTextures ) + { + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + r_numTextures++; + } + + tex = &r_textures[i]; + Q_strncpy( tex->name, name, sizeof( tex->name )); + tex->flags = flags; + + if( flags & TF_SKYSIDE ) + tex->texnum = tr.skyboxbasenum++; + else tex->texnum = i; // texnum is used for fast acess into r_textures array too + + GL_ProcessImage( tex, pic, filter ); + + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gltexture_t )); + FS_FreeImage( pic ); // release source texture + return 0; + } + + GL_ApplyTextureParams( tex ); // update texture filter, wrap etc + FS_FreeImage( pic ); // release source texture + + // add to hash table + tex->hashValue = COM_HashKey( tex->name, TEXTURES_HASH_SIZE ); + tex->nextHash = r_texturesHashTable[tex->hashValue]; + r_texturesHashTable[tex->hashValue] = tex; + + // NOTE: always return texnum as index in array or engine will stop work !!! + return i; +} + +/* +================ +GL_LoadTextureArray +================ +*/ +int GL_LoadTextureArray( const char **names, int flags, imgfilter_t *filter ) +{ + gltexture_t *tex; + rgbdata_t *pic, *src; + char basename[256]; + uint numLayers = 0; + uint picFlags = 0; + char name[256]; + uint i, j, hash; + + if( !names || !names[0] || !glw_state.initialized ) + return 0; + + // count layers (g-cont. this is pontentially unsafe loop) + for( i = 0; i < glConfig.max_2d_texture_layers && ( *names[i] != '\0' ); i++ ) + numLayers++; + name[0] = '\0'; + + if( numLayers <= 0 ) return 0; + + // create complexname from layer names + for( i = 0; i < numLayers; i++ ) + { + COM_FileBase( names[i], basename ); + Q_strncat( name, va( "%s", basename ), sizeof( name )); + if( i != ( numLayers - 1 )) Q_strncat( name, "|", sizeof( name )); + } + + Q_strncat( name, va( "[%i]", numLayers ), sizeof( name )); + + if( Q_strlen( name ) >= sizeof( r_textures->name )) + { + Con_Printf( S_ERROR "LoadTextureArray: too long name %s (%d)\n", name, Q_strlen( name )); + return 0; + } + + // see if already loaded + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = r_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + return (tex - r_textures); + } + + // load all the images and pack it into single image + for( i = 0, pic = NULL; i < numLayers; i++ ) + { + size_t srcsize, dstsize, mipsize; + + src = FS_LoadImage( names[i], NULL, 0 ); + if( !src ) break; // coldn't find layer + + if( pic ) + { + // mixed mode: DXT + RGB + if( pic->type != src->type ) + { + MsgDev( D_ERROR, "GL_LoadTextureArray: mismatch image format for %s and %s\n", names[0], names[i] ); + break; + } + + // different mipcount + if( pic->numMips != src->numMips ) + { + MsgDev( D_ERROR, "GL_LoadTextureArray: mismatch mip count for %s and %s\n", names[0], names[i] ); + break; + } + + if( pic->encode != src->encode ) + { + MsgDev( D_ERROR, "GL_LoadTextureArray: mismatch custom encoding for %s and %s\n", names[0], names[i] ); + break; + } + + // but allow to rescale raw images + if( ImageRAW( pic->type ) && ImageRAW( src->type ) && ( pic->width != src->width || pic->height != src->height )) + Image_Process( &src, pic->width, pic->height, IMAGE_RESAMPLE, NULL ); + + if( pic->size != src->size ) + { + MsgDev( D_ERROR, "GL_LoadTextureArray: mismatch image size for %s and %s\n", names[0], names[i] ); + break; + } + } + else + { + // create new image + pic = Mem_Alloc( host.imagepool, sizeof( rgbdata_t )); + memcpy( pic, src, sizeof( rgbdata_t )); + + // expand pic buffer for all layers + pic->buffer = Mem_Alloc( host.imagepool, pic->size * numLayers ); + pic->depth = 0; + } + + mipsize = srcsize = dstsize = 0; + + for( j = 0; j < max( 1, pic->numMips ); j++ ) + { + int width = max( 1, ( pic->width >> j )); + int height = max( 1, ( pic->height >> j )); + mipsize = GL_CalcImageSize( pic->type, width, height, 1 ); + memcpy( pic->buffer + dstsize + mipsize * i, src->buffer + srcsize, mipsize ); + dstsize += mipsize * numLayers; + srcsize += mipsize; + } + + FS_FreeImage( src ); + + // increase layers + pic->depth++; + } + + // there were errors + if( !pic || ( pic->depth != numLayers )) + { + MsgDev( D_ERROR, "GL_LoadTextureArray: not all layers were loaded. Texture array is not created\n" ); + if( pic ) FS_FreeImage( pic ); + return 0; + } + + // it's multilayer image! + pic->flags |= IMAGE_MULTILAYER; + pic->size *= numLayers; + + // find a free texture slot + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + + // find a free texture_t slot + for( i = 0, tex = r_textures; i < r_numTextures; i++, tex++ ) + if( !tex->name[0] ) break; + + if( i == r_numTextures ) + { + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + r_numTextures++; + } + + tex = &r_textures[i]; + Q_strncpy( tex->name, name, sizeof( tex->name )); + tex->flags = flags; + tex->texnum = i; // texnum is used for fast acess into r_textures array too + + GL_ProcessImage( tex, pic, filter ); + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gltexture_t )); + FS_FreeImage( pic ); // release source texture + return 0; + } + + GL_ApplyTextureParams( tex ); // update texture filter, wrap etc + FS_FreeImage( pic ); // release source texture + + // add to hash table + tex->hashValue = COM_HashKey( tex->name, TEXTURES_HASH_SIZE ); + tex->nextHash = r_texturesHashTable[tex->hashValue]; + r_texturesHashTable[tex->hashValue] = tex; + + // NOTE: always return texnum as index in array or engine will stop work !!! + return i; +} + +/* +================ +GL_LoadTextureInternal +================ +*/ +int GL_LoadTextureInternal( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ) +{ + gltexture_t *tex; + uint i, hash; + + if( !COM_CheckString( name ) || !glw_state.initialized ) + return 0; + + if( Q_strlen( name ) >= sizeof( r_textures->name )) + { + Con_Printf( S_ERROR "LoadTexture: too long name %s (%d)\n", name, Q_strlen( name )); + return 0; + } + + // see if already loaded + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = r_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + { + if( update ) break; + return (tex - r_textures); + } + } + + if( !pic ) return 0; // couldn't loading image + if( update && !tex ) + { + Host_Error( "Couldn't find texture %s for update\n", name ); + } + + // find a free texture slot + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + + if( !update ) + { + // find a free texture_t slot + for( i = 0, tex = r_textures; i < r_numTextures; i++, tex++ ) + if( !tex->name[0] ) break; + + if( i == r_numTextures ) + { + if( r_numTextures == MAX_TEXTURES ) + Host_Error( "GL_LoadTexture: MAX_TEXTURES limit exceeds\n" ); + r_numTextures++; + } + + tex = &r_textures[i]; + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + Q_strncpy( tex->name, name, sizeof( tex->name )); + tex->texnum = i; // texnum is used for fast acess into r_textures array too + tex->flags = flags; + } + else + { + tex->flags |= flags; + } + + GL_ProcessImage( tex, pic, NULL ); + if( !GL_UploadTexture( tex, pic )) + { + memset( tex, 0, sizeof( gltexture_t )); + return 0; + } + + GL_ApplyTextureParams( tex ); // update texture filter, wrap etc + + if( !update ) + { + // add to hash table + tex->hashValue = COM_HashKey( tex->name, TEXTURES_HASH_SIZE ); + tex->nextHash = r_texturesHashTable[tex->hashValue]; + r_texturesHashTable[tex->hashValue] = tex; + } + + return (tex - r_textures); +} + +/* +================ +GL_CreateTexture + +creates texture from buffer +================ +*/ +int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ) +{ + rgbdata_t r_empty; + int texture; + + memset( &r_empty, 0, sizeof( r_empty )); + r_empty.width = width; + r_empty.height = height; + r_empty.type = PF_RGBA_32; + r_empty.size = r_empty.width * r_empty.height * 4; + r_empty.flags = IMAGE_HAS_COLOR | (( flags & TF_HAS_ALPHA ) ? IMAGE_HAS_ALPHA : 0 ); + r_empty.buffer = (byte *)buffer; + + if( FBitSet( flags, TF_TEXTURE_1D )) + { + r_empty.height = 1; + r_empty.size = r_empty.width * 4; + } + else if( FBitSet( flags, TF_TEXTURE_3D )) + { + if( !GL_Support( GL_TEXTURE_3D_EXT )) + return 0; + + r_empty.depth = r_empty.width; // assume 3D texture as cube + r_empty.size = r_empty.width * r_empty.height * r_empty.depth * 4; + } + else if( FBitSet( flags, TF_CUBEMAP )) + { + SetBits( r_empty.flags, IMAGE_CUBEMAP ); + ClearBits( flags, TF_CUBEMAP ); // will be set later + r_empty.size *= 6; + } + + texture = GL_LoadTextureInternal( name, &r_empty, flags, false ); + + return texture; +} + +/* +================ +GL_CreateTextureArray + +creates texture array from buffer +================ +*/ +int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ) +{ + rgbdata_t r_empty; + int texture; + + memset( &r_empty, 0, sizeof( r_empty )); + r_empty.width = width; + r_empty.height = height; + r_empty.depth = depth; + r_empty.type = PF_RGBA_32; + r_empty.size = r_empty.width * r_empty.height * r_empty.depth * 4; + r_empty.flags = IMAGE_HAS_COLOR | (( flags & TF_HAS_ALPHA ) ? IMAGE_HAS_ALPHA : 0 ); + r_empty.buffer = (byte *)buffer; + + if( FBitSet( flags, TF_TEXTURE_3D )) + { + if( !GL_Support( GL_TEXTURE_3D_EXT )) + return 0; + } + else + { + if( !GL_Support( GL_TEXTURE_ARRAY_EXT )) + return 0; + SetBits( r_empty.flags, IMAGE_MULTILAYER ); + } + + texture = GL_LoadTextureInternal( name, &r_empty, flags, false ); + + return texture; +} + +/* +================ +GL_ProcessTexture +================ +*/ +void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ) +{ + gltexture_t *image; + rgbdata_t *pic; + int flags = 0; + + if( texnum <= 0 ) return; // missed image + Assert( texnum > 0 && texnum < MAX_TEXTURES ); + image = &r_textures[texnum]; + + // select mode + if( gamma != -1.0f ) + { + flags = IMAGE_LIGHTGAMMA; + } + else if( topColor != -1 && bottomColor != -1 ) + { + flags = IMAGE_REMAP; + } + else + { + MsgDev( D_ERROR, "GL_ProcessTexture: bad operation for %s\n", image->name ); + return; + } + + if( !image->original ) + { + MsgDev( D_ERROR, "GL_ProcessTexture: no input data for %s\n", image->name ); + return; + } + + if( ImageDXT( image->original->type )) + { + MsgDev( D_ERROR, "GL_ProcessTexture: can't process compressed texture %s\n", image->name ); + return; + } + + // all the operations makes over the image copy not an original + pic = FS_CopyImage( image->original ); + Image_Process( &pic, topColor, bottomColor, flags, NULL ); + + GL_UploadTexture( image, pic ); + GL_ApplyTextureParams( image ); // update texture filter, wrap etc + + FS_FreeImage( pic ); +} + +/* +================ +GL_LoadTexture +================ +*/ +int GL_FindTexture( const char *name ) +{ + gltexture_t *tex; + uint hash; + + if( !COM_CheckString( name ) || !glw_state.initialized ) + return 0; + + if( Q_strlen( name ) >= sizeof( r_textures->name )) + { + Con_Printf( S_ERROR "FindTexture: too long name %s (%d)\n", name, Q_strlen( name )); + return 0; + } + + // see if already loaded + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = r_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + return (tex - r_textures); + } + + return 0; +} + +/* +================ +GL_FreeImage + +Frees image by name +================ +*/ +void GL_FreeImage( const char *name ) +{ + gltexture_t *tex; + uint hash; + + if( !COM_CheckString( name ) || !glw_state.initialized ) + return; + + if( Q_strlen( name ) >= sizeof( r_textures->name )) + { + Con_Printf( S_ERROR "FreeTexture: too long name %s (%d)\n", name, Q_strlen( name )); + return; + } + + // see if already loaded + hash = COM_HashKey( name, TEXTURES_HASH_SIZE ); + + for( tex = r_texturesHashTable[hash]; tex != NULL; tex = tex->nextHash ) + { + if( !Q_stricmp( tex->name, name )) + { + R_FreeImage( tex ); + return; + } + } +} + +/* +================ +GL_FreeTexture +================ +*/ +void GL_FreeTexture( GLenum texnum ) +{ + // number 0 it's already freed + if( texnum <= 0 || !glw_state.initialized ) + return; + + Assert( texnum > 0 && texnum < MAX_TEXTURES ); + R_FreeImage( &r_textures[texnum] ); +} + +/* +================ +R_FreeImage +================ +*/ +void R_FreeImage( gltexture_t *image ) +{ + gltexture_t *cur; + gltexture_t **prev; + + Assert( image != NULL ); + + if( !image->name[0] ) + { + if( image->texnum != 0 ) + MsgDev( D_ERROR, "trying to free unnamed texture with texnum %i\n", image->texnum ); + return; + } + + // remove from hash table + prev = &r_texturesHashTable[image->hashValue]; + + while( 1 ) + { + cur = *prev; + if( !cur ) break; + + if( cur == image ) + { + *prev = cur->nextHash; + break; + } + prev = &cur->nextHash; + } + + // release source + if( image->original ) + FS_FreeImage( image->original ); + + pglDeleteTextures( 1, &image->texnum ); + memset( image, 0, sizeof( *image )); +} + +/* +============================================================================== + +INTERNAL TEXTURES + +============================================================================== +*/ +/* +================== +R_InitDefaultTexture +================== +*/ +static rgbdata_t *R_InitDefaultTexture( texFlags_t *flags ) +{ + int x, y; + + // also use this for bad textures, but without alpha + r_image.width = r_image.height = 16; + r_image.buffer = data2D; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + + *flags = 0; + + // emo-texture from quake1 + for( y = 0; y < 16; y++ ) + { + for( x = 0; x < 16; x++ ) + { + if(( y < 8 ) ^ ( x < 8 )) + ((uint *)&data2D)[y*16+x] = 0xFFFF00FF; + else ((uint *)&data2D)[y*16+x] = 0xFF000000; + } + } + return &r_image; +} + +/* +================== +R_InitParticleTexture +================== +*/ +static rgbdata_t *R_InitParticleTexture( texFlags_t *flags ) +{ + int x, y; + int dx2, dy, d; + + // particle texture + r_image.width = r_image.height = 16; + r_image.buffer = data2D; + r_image.flags = (IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA); + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + + *flags = TF_CLAMP; + + for( x = 0; x < 16; x++ ) + { + dx2 = x - 8; + dx2 = dx2 * dx2; + + for( y = 0; y < 16; y++ ) + { + dy = y - 8; + d = 255 - 35 * sqrt( dx2 + dy * dy ); + data2D[( y*16 + x ) * 4 + 3] = bound( 0, d, 255 ); + } + } + return &r_image; +} + +/* +================== +R_InitSkyTexture +================== +*/ +static rgbdata_t *R_InitSkyTexture( texFlags_t *flags ) +{ + int i; + + // skybox texture + for( i = 0; i < 256; i++ ) + ((uint *)&data2D)[i] = 0xFFFFDEB5; + + *flags = 0; + + r_image.buffer = data2D; + r_image.width = r_image.height = 16; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitCinematicTexture +================== +*/ +static rgbdata_t *R_InitCinematicTexture( texFlags_t *flags ) +{ + r_image.buffer = data2D; + r_image.type = PF_RGBA_32; + r_image.flags = IMAGE_HAS_COLOR; + r_image.width = r_image.height = 256; + r_image.size = r_image.width * r_image.height * 4; + + *flags = TF_NOMIPMAP|TF_CLAMP; + + return &r_image; +} + +/* +================== +R_InitSolidColorTexture +================== +*/ +static rgbdata_t *R_InitSolidColorTexture( texFlags_t *flags, int color ) +{ + // solid color texture + r_image.width = r_image.height = 1; + r_image.buffer = data2D; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGB_24; + r_image.size = r_image.width * r_image.height * 3; + + *flags = 0; + + data2D[0] = data2D[1] = data2D[2] = color; + return &r_image; +} + +/* +================== +R_InitWhiteTexture +================== +*/ +static rgbdata_t *R_InitWhiteTexture( texFlags_t *flags ) +{ + return R_InitSolidColorTexture( flags, 255 ); +} + +/* +================== +R_InitGrayTexture +================== +*/ +static rgbdata_t *R_InitGrayTexture( texFlags_t *flags ) +{ + return R_InitSolidColorTexture( flags, 127 ); +} + +/* +================== +R_InitBlackTexture +================== +*/ +static rgbdata_t *R_InitBlackTexture( texFlags_t *flags ) +{ + return R_InitSolidColorTexture( flags, 0 ); +} + +/* +================== +R_InitBlankBumpTexture +================== +*/ +static rgbdata_t *R_InitBlankBumpTexture( texFlags_t *flags ) +{ + int i; + + // default normalmap texture + for( i = 0; i < 256; i++ ) + { + data2D[i*4+0] = 127; + data2D[i*4+1] = 127; + data2D[i*4+2] = 255; + } + + *flags = TF_NORMALMAP; + + r_image.buffer = data2D; + r_image.width = r_image.height = 16; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitBlankDeluxeTexture +================== +*/ +static rgbdata_t *R_InitBlankDeluxeTexture( texFlags_t *flags ) +{ + int i; + + // default normalmap texture + for( i = 0; i < 256; i++ ) + { + data2D[i*4+0] = 127; + data2D[i*4+1] = 127; + data2D[i*4+2] = 0; // light from ceiling + } + + *flags = TF_NORMALMAP; + + r_image.buffer = data2D; + r_image.width = r_image.height = 16; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitAttenuationTexture +================== +*/ +static rgbdata_t *R_InitAttenTextureGamma( texFlags_t *flags, float gamma ) +{ + int i; + + // 1d attenuation texture + r_image.width = 256; + r_image.height = 1; + r_image.buffer = data2D; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + + for( i = 0; i < r_image.width; i++ ) + { + float atten = 255 - bound( 0, 255 * pow((i + 0.5f) / r_image.width, gamma ) + 0.5f, 255 ); + + // clear attenuation at ends to prevent light go outside + if( i == (r_image.width - 1) || i == 0 ) + atten = 0.0f; + + data2D[(i * 4) + 0] = (byte)atten; + data2D[(i * 4) + 1] = (byte)atten; + data2D[(i * 4) + 2] = (byte)atten; + data2D[(i * 4) + 3] = (byte)atten; + } + + *flags = TF_NOMIPMAP|TF_CLAMP|TF_TEXTURE_1D; + + return &r_image; +} + +static rgbdata_t *R_InitAttenuationTexture( texFlags_t *flags ) +{ + return R_InitAttenTextureGamma( flags, 1.5f ); +} + +static rgbdata_t *R_InitAttenuationTexture2( texFlags_t *flags ) +{ + return R_InitAttenTextureGamma( flags, 0.5f ); +} + +static rgbdata_t *R_InitAttenuationTexture3( texFlags_t *flags ) +{ + return R_InitAttenTextureGamma( flags, 3.5f ); +} + +static rgbdata_t *R_InitAttenuationTextureNoAtten( texFlags_t *flags ) +{ + // 1d attenuation texture + r_image.width = 256; + r_image.height = 1; + r_image.buffer = data2D; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + + memset( data2D, 0xFF, r_image.size ); + *flags = TF_NOMIPMAP|TF_CLAMP|TF_TEXTURE_1D; + + return &r_image; +} + +/* +================== +R_InitAttenuationTexture3D +================== +*/ +static rgbdata_t *R_InitAttenTexture3D( texFlags_t *flags ) +{ + vec3_t v = { 0, 0, 0 }; + int x, y, z, d, size, size2, halfsize; + float intensity; + + if( !GL_Support( GL_TEXTURE_3D_EXT )) + return NULL; + + // 3d attenuation texture + r_image.width = 32; + r_image.height = 32; + r_image.depth = 32; + r_image.buffer = data2D; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * r_image.depth * 4; + + size = 32; + halfsize = size / 2; + intensity = halfsize * halfsize; + size2 = size * size; + + for( x = 0; x < r_image.width; x++ ) + { + for( y = 0; y < r_image.height; y++ ) + { + for( z = 0; z < r_image.depth; z++ ) + { + v[0] = (( x + 0.5f ) * ( 2.0f / (float)size ) - 1.0f ); + v[1] = (( y + 0.5f ) * ( 2.0f / (float)size ) - 1.0f ); + if( r_image.depth > 1 ) v[2] = (( z + 0.5f ) * ( 2.0f / (float)size ) - 1.0f ); + + intensity = 1.0f - sqrt( DotProduct( v, v ) ); + if( intensity > 0 ) intensity = intensity * intensity * 215.5f; + d = bound( 0, intensity, 255 ); + + data2D[((z * size + y) * size + x) * 4 + 0] = d; + data2D[((z * size + y) * size + x) * 4 + 1] = d; + data2D[((z * size + y) * size + x) * 4 + 2] = d; + } + } + } + + *flags = TF_NOMIPMAP|TF_CLAMP|TF_TEXTURE_3D; + + return &r_image; +} + +static rgbdata_t *R_InitDlightTexture( texFlags_t *flags ) +{ + // solid color texture + r_image.width = BLOCK_SIZE_DEFAULT; + r_image.height = BLOCK_SIZE_DEFAULT; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + r_image.buffer = data2D; + + memset( data2D, 0x00, r_image.size ); + + *flags = TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE; + + return &r_image; +} + +static rgbdata_t *R_InitDlightTexture2( texFlags_t *flags ) +{ + // solid color texture + r_image.width = BLOCK_SIZE_MAX; + r_image.height = BLOCK_SIZE_MAX; + r_image.flags = IMAGE_HAS_COLOR; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + r_image.buffer = data2D; + + memset( data2D, 0x00, r_image.size ); + + *flags = TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE; + + return &r_image; +} + +/* +================== +R_InitNormalizeCubemap +================== +*/ +static rgbdata_t *R_InitNormalizeCubemap( texFlags_t *flags ) +{ + int i, x, y, size = 32; + byte *dataCM = data2D; + float s, t; + vec3_t normal; + + if( !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + return NULL; + + // normal cube map texture + for( i = 0; i < 6; i++ ) + { + for( y = 0; y < size; y++ ) + { + for( x = 0; x < size; x++ ) + { + s = (((float)x + 0.5f) * (2.0f / size )) - 1.0f; + t = (((float)y + 0.5f) * (2.0f / size )) - 1.0f; + + switch( i ) + { + case 0: VectorSet( normal, 1.0f, -t, -s ); break; + case 1: VectorSet( normal, -1.0f, -t, s ); break; + case 2: VectorSet( normal, s, 1.0f, t ); break; + case 3: VectorSet( normal, s, -1.0f, -t ); break; + case 4: VectorSet( normal, s, -t, 1.0f ); break; + case 5: VectorSet( normal, -s, -t, -1.0f); break; + } + + VectorNormalize( normal ); + + dataCM[4*(y*size+x)+0] = (byte)(128 + 127 * normal[0]); + dataCM[4*(y*size+x)+1] = (byte)(128 + 127 * normal[1]); + dataCM[4*(y*size+x)+2] = (byte)(128 + 127 * normal[2]); + dataCM[4*(y*size+x)+3] = 255; + } + } + dataCM += (size*size*4); // move pointer + } + + *flags = (TF_NOMIPMAP|TF_CUBEMAP|TF_CLAMP); + + r_image.width = r_image.height = size; + r_image.size = r_image.width * r_image.height * 4 * 6; + r_image.flags |= (IMAGE_CUBEMAP|IMAGE_HAS_COLOR); // yes it's cubemap + r_image.buffer = data2D; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitDlightCubemap +================== +*/ +static rgbdata_t *R_InitDlightCubemap( texFlags_t *flags ) +{ + int i, x, y, size = 4; + byte *dataCM = data2D; + int dx2, dy, d; + + if( !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + return NULL; + + // normal cube map texture + for( i = 0; i < 6; i++ ) + { + for( x = 0; x < size; x++ ) + { + dx2 = x - size / 2; + dx2 = dx2 * dx2; + + for( y = 0; y < size; y++ ) + { + dy = y - size / 2; + d = 255 - 35 * sqrt( dx2 + dy * dy ); + dataCM[( y * size + x ) * 4 + 0] = bound( 0, d, 255 ); + dataCM[( y * size + x ) * 4 + 1] = bound( 0, d, 255 ); + dataCM[( y * size + x ) * 4 + 2] = bound( 0, d, 255 ); + } + } + dataCM += (size * size * 4); // move pointer + } + + *flags = (TF_NOMIPMAP|TF_CUBEMAP|TF_CLAMP); + + r_image.width = r_image.height = size; + r_image.size = r_image.width * r_image.height * 4 * 6; + r_image.flags |= (IMAGE_CUBEMAP|IMAGE_HAS_COLOR); // yes it's cubemap + r_image.buffer = data2D; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitGrayCubemap +================== +*/ +static rgbdata_t *R_InitGrayCubemap( texFlags_t *flags ) +{ + int size = 4; + byte *dataCM = data2D; + + if( !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + return NULL; + + // gray cubemap - just stub for pointlights + memset( dataCM, 0x7F, size * size * 6 * 4 ); + + *flags = (TF_NOMIPMAP|TF_CUBEMAP|TF_CLAMP); + + r_image.width = r_image.height = size; + r_image.size = r_image.width * r_image.height * 4 * 6; + r_image.flags |= (IMAGE_CUBEMAP|IMAGE_HAS_COLOR); // yes it's cubemap + r_image.buffer = data2D; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitWhiteCubemap +================== +*/ +static rgbdata_t *R_InitWhiteCubemap( texFlags_t *flags ) +{ + int size = 4; + byte *dataCM = data2D; + + if( !GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + return NULL; + + // white cubemap - just stub for pointlights + memset( dataCM, 0xFF, size * size * 6 * 4 ); + + *flags = (TF_NOMIPMAP|TF_CUBEMAP|TF_CLAMP); + + r_image.width = r_image.height = size; + r_image.size = r_image.width * r_image.height * 4 * 6; + r_image.flags |= (IMAGE_CUBEMAP|IMAGE_HAS_COLOR); // yes it's cubemap + r_image.buffer = data2D; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitVSDCTCubemap +================== +*/ +static rgbdata_t *R_InitVSDCTCubemap( texFlags_t *flags ) +{ + // maps to a 2x3 texture rectangle with normalized coordinates + // +- + // XX + // YY + // ZZ + // stores abs(dir.xy), offset.xy/2.5 + static byte data[4*6] = + { + 0xFF, 0x00, 0x33, 0x33, // +X: <1, 0>, <0.5, 0.5> + 0xFF, 0x00, 0x99, 0x33, // -X: <1, 0>, <1.5, 0.5> + 0x00, 0xFF, 0x33, 0x99, // +Y: <0, 1>, <0.5, 1.5> + 0x00, 0xFF, 0x99, 0x99, // -Y: <0, 1>, <1.5, 1.5> + 0x00, 0x00, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> + 0x00, 0x00, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> + }; + + *flags = (TF_NEAREST|TF_CUBEMAP|TF_CLAMP); + + r_image.width = r_image.height = 1; + r_image.size = r_image.width * r_image.height * 4 * 6; + r_image.flags |= (IMAGE_CUBEMAP|IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA); // yes it's cubemap + r_image.buffer = data; + r_image.type = PF_RGBA_32; + + return &r_image; +} + +/* +================== +R_InitBuiltinTextures +================== +*/ +static void R_InitBuiltinTextures( void ) +{ + rgbdata_t *pic; + texFlags_t flags; + + const struct + { + char *name; + int *texnum; + rgbdata_t *(*init)( texFlags_t *flags ); + } + + textures[] = + { + { "*default", &tr.defaultTexture, R_InitDefaultTexture }, + { "*white", &tr.whiteTexture, R_InitWhiteTexture }, + { "*gray", &tr.grayTexture, R_InitGrayTexture }, + { "*black", &tr.blackTexture, R_InitBlackTexture }, + { "*particle", &tr.particleTexture, R_InitParticleTexture }, + { "*cintexture", &tr.cinTexture, R_InitCinematicTexture }, // force linear filter + { "*dlight", &tr.dlightTexture, R_InitDlightTexture }, + { "*dlight2", &tr.dlightTexture2, R_InitDlightTexture2 }, + { "*atten", &tr.attenuationTexture, R_InitAttenuationTexture }, + { "*atten2", &tr.attenuationTexture2, R_InitAttenuationTexture2 }, + { "*atten3", &tr.attenuationTexture3, R_InitAttenuationTexture3 }, + { "*attnno", &tr.attenuationStubTexture, R_InitAttenuationTextureNoAtten }, + { "*normalize", &tr.normalizeTexture, R_InitNormalizeCubemap }, + { "*blankbump", &tr.blankbumpTexture, R_InitBlankBumpTexture }, + { "*blankdeluxe", &tr.blankdeluxeTexture, R_InitBlankDeluxeTexture }, + { "*lightCube", &tr.dlightCubeTexture, R_InitDlightCubemap }, + { "*grayCube", &tr.grayCubeTexture, R_InitGrayCubemap }, + { "*whiteCube", &tr.whiteCubeTexture, R_InitWhiteCubemap }, + { "*atten3D", &tr.attenuationTexture3D, R_InitAttenTexture3D }, + { "*sky", &tr.skyTexture, R_InitSkyTexture }, + { "*vsdct", &tr.vsdctCubeTexture, R_InitVSDCTCubemap }, + { NULL, NULL, NULL } + }; + size_t i, num_builtin_textures = sizeof( textures ) / sizeof( textures[0] ) - 1; + + for( i = 0; i < num_builtin_textures; i++ ) + { + memset( &r_image, 0, sizeof( rgbdata_t )); + memset( data2D, 0xFF, sizeof( data2D )); + + pic = textures[i].init( &flags ); + if( pic == NULL ) continue; + *textures[i].texnum = GL_LoadTextureInternal( textures[i].name, pic, flags, false ); + } +} + +/* +=============== +R_TextureList_f +=============== +*/ +void R_TextureList_f( void ) +{ + gltexture_t *image; + int i, texCount, bytes = 0; + + Con_Printf( "\n" ); + Con_Printf( " -id- -w- -h- -size- -fmt- -type- -data- -encode- -wrap- -depth- -name--------\n" ); + + for( i = texCount = 0, image = r_textures; i < r_numTextures; i++, image++ ) + { + if( !image->texnum ) continue; + + bytes += image->size; + texCount++; + + Con_Printf( "%4i: ", i ); + Con_Printf( "%4i %4i ", image->width, image->height ); + Con_Printf( "%12s ", Q_memprint( image->size )); + + switch( image->format ) + { + case GL_COMPRESSED_RGBA_ARB: + Con_Printf( "CRGBA " ); + break; + case GL_COMPRESSED_RGB_ARB: + Con_Printf( "CRGB " ); + break; + case GL_COMPRESSED_LUMINANCE_ALPHA_ARB: + Con_Printf( "CLA " ); + break; + case GL_COMPRESSED_LUMINANCE_ARB: + Con_Printf( "CL " ); + break; + case GL_COMPRESSED_ALPHA_ARB: + Con_Printf( "CA " ); + break; + case GL_COMPRESSED_INTENSITY_ARB: + Con_Printf( "CI " ); + break; + case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: + Con_Printf( "DXT1c " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + Con_Printf( "DXT1a " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + Con_Printf( "DXT3 " ); + break; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + Con_Printf( "DXT5 " ); + break; + case GL_RGBA: + Con_Printf( "RGBA " ); + break; + case GL_RGBA8: + Con_Printf( "RGBA8 " ); + break; + case GL_RGBA4: + Con_Printf( "RGBA4 " ); + break; + case GL_RGB: + Con_Printf( "RGB " ); + break; + case GL_RGB8: + Con_Printf( "RGB8 " ); + break; + case GL_RGB5: + Con_Printf( "RGB5 " ); + break; + case GL_LUMINANCE4_ALPHA4: + Con_Printf( "L4A4 " ); + break; + case GL_LUMINANCE_ALPHA: + case GL_LUMINANCE8_ALPHA8: + Con_Printf( "L8A8 " ); + break; + case GL_LUMINANCE4: + Con_Printf( "L4 " ); + break; + case GL_LUMINANCE: + case GL_LUMINANCE8: + Con_Printf( "L8 " ); + break; + case GL_ALPHA8: + Con_Printf( "A8 " ); + break; + case GL_INTENSITY8: + Con_Printf( "I8 " ); + break; + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT24: + Con_Printf( "DPTH24" ); + break; + case GL_DEPTH_COMPONENT32F: + Con_Printf( "DPTH32" ); + break; + case GL_LUMINANCE16F_ARB: + Con_Printf( "L16F " ); + break; + case GL_LUMINANCE32F_ARB: + Con_Printf( "L32F " ); + break; + case GL_LUMINANCE_ALPHA16F_ARB: + Con_Printf( "LA16F " ); + break; + case GL_LUMINANCE_ALPHA32F_ARB: + Con_Printf( "LA32F " ); + break; + case GL_RGB16F_ARB: + Con_Printf( "RGB16F" ); + break; + case GL_RGB32F_ARB: + Con_Printf( "RGB32F" ); + break; + case GL_RGBA16F_ARB: + Con_Printf( "RGBA16F" ); + break; + case GL_RGBA32F_ARB: + Con_Printf( "RGBA32F" ); + break; + default: + Con_Printf( " ^1ERROR^7 " ); + break; + } + + switch( image->target ) + { + case GL_TEXTURE_1D: + Con_Printf( " 1D " ); + break; + case GL_TEXTURE_2D: + Con_Printf( " 2D " ); + break; + case GL_TEXTURE_3D: + Con_Printf( " 3D " ); + break; + case GL_TEXTURE_CUBE_MAP_ARB: + Con_Printf( "CUBE " ); + break; + case GL_TEXTURE_RECTANGLE_EXT: + Con_Printf( "RECT " ); + break; + case GL_TEXTURE_2D_ARRAY_EXT: + Con_Printf( "ARRAY " ); + break; + default: + Con_Printf( "???? " ); + break; + } + + if( image->flags & TF_NORMALMAP ) + Con_Printf( "normal " ); + else Con_Printf( "diffuse " ); + + switch( image->encode ) + { + case DXT_ENCODE_COLOR_YCoCg: + Con_Printf( "YCoCg " ); + break; + case DXT_ENCODE_NORMAL_AG_ORTHO: + Con_Printf( "ortho " ); + break; + case DXT_ENCODE_NORMAL_AG_STEREO: + Con_Printf( "stereo " ); + break; + case DXT_ENCODE_NORMAL_AG_PARABOLOID: + Con_Printf( "parabolic " ); + break; + case DXT_ENCODE_NORMAL_AG_QUARTIC: + Con_Printf( "quartic " ); + break; + case DXT_ENCODE_NORMAL_AG_AZIMUTHAL: + Con_Printf( "azimuthal " ); + break; + default: + Con_Printf( "default " ); + break; + } + + if( image->flags & TF_CLAMP ) + Con_Printf( "clamp " ); + else if( image->flags & TF_BORDER ) + Con_Printf( "border " ); + else Con_Printf( "repeat " ); + Con_Printf( " %d ", image->depth ); + Con_Printf( " %s\n", image->name ); + } + + Con_Printf( "---------------------------------------------------------\n" ); + Con_Printf( "%i total textures\n", texCount ); + Con_Printf( "%s total memory used\n", Q_memprint( bytes )); + Con_Printf( "\n" ); +} + +/* +=============== +R_InitImages +=============== +*/ +void R_InitImages( void ) +{ + float f; + uint i; + + memset( r_textures, 0, sizeof( r_textures )); + memset( r_texturesHashTable, 0, sizeof( r_texturesHashTable )); + r_numTextures = 0; + + // create unused 0-entry + Q_strncpy( r_textures->name, "*unused*", sizeof( r_textures->name )); + r_textures->hashValue = COM_HashKey( r_textures->name, TEXTURES_HASH_SIZE ); + r_textures->nextHash = r_texturesHashTable[r_textures->hashValue]; + r_texturesHashTable[r_textures->hashValue] = r_textures; + r_numTextures = 1; + + // build luminance table + for( i = 0; i < 256; i++ ) + { + f = (float)i; + r_luminanceTable[i][0] = f * 0.299f; + r_luminanceTable[i][1] = f * 0.587f; + r_luminanceTable[i][2] = f * 0.114f; + } + + // set texture parameters + R_SetTextureParameters(); + R_InitBuiltinTextures(); + + R_ParseTexFilters( "scripts/texfilter.txt" ); + Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); +} + +/* +=============== +R_ShutdownImages +=============== +*/ +void R_ShutdownImages( void ) +{ + gltexture_t *image; + int i; + + if( !glw_state.initialized ) return; + + Cmd_RemoveCommand( "texturelist" ); + GL_CleanupAllTextureUnits(); + + for( i = 0, image = r_textures; i < r_numTextures; i++, image++ ) + R_FreeImage( image ); + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); + memset( r_texturesHashTable, 0, sizeof( r_texturesHashTable )); + memset( r_textures, 0, sizeof( r_textures )); + r_numTextures = 0; +} \ No newline at end of file diff --git a/engine/client/gl_local.h b/engine/client/gl_local.h new file mode 100644 index 00000000..2c78105a --- /dev/null +++ b/engine/client/gl_local.h @@ -0,0 +1,677 @@ +/* +gl_local.h - renderer local declarations +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef GL_LOCAL_H +#define GL_LOCAL_H + +#include "gl_export.h" +#include "cl_entity.h" +#include "render_api.h" +#include "protocol.h" +#include "dlight.h" +#include "gl_frustum.h" + +extern byte *r_temppool; + +#define BLOCK_SIZE tr.block_size // lightmap blocksize +#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility +#define BLOCK_SIZE_MAX 1024 + +#define MAX_TEXTURES 4096 +#define MAX_DETAIL_TEXTURES 256 +#define MAX_LIGHTMAPS 256 +#define SUBDIVIDE_SIZE 64 +#define MAX_DECAL_SURFS 4096 +#define MAX_DRAW_STACK 2 // normal view and menu view + +#define SHADEDOT_QUANT 16 // precalculated dot products for quantized angles +#define SHADE_LAMBERT 1.495f + +// refparams +#define RP_NONE 0 +#define RP_ENVVIEW BIT( 0 ) // used for cubemapshot +#define RP_OLDVIEWLEAF BIT( 1 ) +#define RP_CLIPPLANE BIT( 2 ) + +#define RP_NONVIEWERREF (RP_ENVVIEW) +#define R_ModelOpaque( rm ) ( rm == kRenderNormal ) +#define R_StaticEntity( ent ) ( VectorIsNull( ent->origin ) && VectorIsNull( ent->angles )) +#define RP_LOCALCLIENT( e ) ((e) != NULL && (e)->index == ( cl.playernum + 1 ) && e->player ) +#define RP_NORMALPASS() ( FBitSet( RI.params, RP_NONVIEWERREF ) == 0 ) + +#define TF_SKY (TF_SKYSIDE|TF_NOMIPMAP) +#define TF_FONT (TF_NOMIPMAP|TF_CLAMP) +#define TF_IMAGE (TF_NOMIPMAP|TF_CLAMP) +#define TF_DECAL (TF_CLAMP) + +#define CULL_VISIBLE 0 // not culled +#define CULL_BACKSIDE 1 // backside of transparent wall +#define CULL_FRUSTUM 2 // culled by frustum +#define CULL_VISFRAME 3 // culled by PVS +#define CULL_OTHER 4 // culled by other reason + +typedef struct gltexture_s +{ + char name[256]; // game path, including extension (can be store image programs) + word srcWidth; // keep unscaled sizes + word srcHeight; + word width; // upload width\height + word height; + word depth; // texture depth or count of layers for 2D_ARRAY + byte numMips; // mipmap count + + GLuint target; // glTarget + GLuint texnum; // gl texture binding + GLint format; // uploaded format + GLint encode; // using GLSL decoder + texFlags_t flags; + + rgba_t fogParams; // some water textures + // contain info about underwater fog + rgbdata_t *original; // keep original image + + // debug info + size_t size; // upload size for debug targets + + // detail textures stuff + float xscale; + float yscale; + + int servercount; + uint hashValue; + struct gltexture_s *nextHash; +} gltexture_t; + +typedef struct +{ + int params; // rendering parameters + + qboolean drawWorld; // ignore world for drawing PlayerModel + qboolean isSkyVisible; // sky is visible + qboolean onlyClientDraw; // disabled by client request + qboolean drawOrtho; // draw world as orthogonal projection + + float fov_x, fov_y; // current view fov + + cl_entity_t *currententity; + model_t *currentmodel; + cl_entity_t *currentbeam; // same as above but for beams + + int viewport[4]; + gl_frustum_t frustum; + + mleaf_t *viewleaf; + mleaf_t *oldviewleaf; + vec3_t pvsorigin; + vec3_t vieworg; // locked vieworigin + vec3_t viewangles; + vec3_t vforward; + vec3_t vright; + vec3_t vup; + + vec3_t cullorigin; + vec3_t cull_vforward; + vec3_t cull_vright; + vec3_t cull_vup; + + float farClip; + + qboolean fogCustom; + qboolean fogEnabled; + qboolean fogSkybox; + vec4_t fogColor; + float fogDensity; + float fogStart; + float fogEnd; + int cached_contents; // in water + int cached_waterlevel; // was in water + + float skyMins[2][6]; + float skyMaxs[2][6]; + + matrix4x4 objectMatrix; // currententity matrix + matrix4x4 worldviewMatrix; // modelview for world + matrix4x4 modelviewMatrix; // worldviewMatrix * objectMatrix + + matrix4x4 projectionMatrix; + matrix4x4 worldviewProjectionMatrix; // worldviewMatrix * projectionMatrix + byte visbytes[(MAX_MAP_LEAFS+7)/8];// actual PVS for current frame + + float viewplanedist; + mplane_t clipPlane; +} ref_instance_t; + +typedef struct +{ + cl_entity_t *solid_entities[MAX_VISIBLE_PACKET]; // opaque moving or alpha brushes + cl_entity_t *trans_entities[MAX_VISIBLE_PACKET]; // translucent brushes + cl_entity_t *beam_entities[MAX_VISIBLE_PACKET]; + uint num_solid_entities; + uint num_trans_entities; + uint num_beam_entities; +} draw_list_t; + +typedef struct +{ + int cinTexture; // cinematic texture + int skyTexture; // default sky texture + int whiteTexture; + int grayTexture; + int blackTexture; + int particleTexture; + int defaultTexture; // use for bad textures + int solidskyTexture; // quake1 solid-sky layer + int alphaskyTexture; // quake1 alpha-sky layer + int lightmapTextures[MAX_LIGHTMAPS]; + int dlightTexture; // custom dlight texture + int dlightTexture2; // big dlight texture (for big lightmaps) + int attenuationTexture; // normal attenuation + int attenuationTexture2;// dark attenuation + int attenuationTexture3;// bright attenuation + int attenuationTexture3D;// 3D attenuation + int attenuationStubTexture; + int blankbumpTexture; + int blankdeluxeTexture; + int normalizeTexture; + int dlightCubeTexture; // dynamic cubemap + int vsdctCubeTexture; // Virtual Shadow Depth Cubemap Texture + int grayCubeTexture; + int whiteCubeTexture; + int skyboxTextures[6]; // skybox sides + + int skytexturenum; // this not a gl_texturenum! + int skyboxbasenum; // start with 5800 + + // entity lists + draw_list_t draw_stack[MAX_DRAW_STACK]; + int draw_stack_pos; + draw_list_t *draw_list; + + msurface_t *draw_decals[MAX_DECAL_SURFS]; + int num_draw_decals; + + // OpenGL matrix states + qboolean modelviewIdentity; + + int visframecount; // PVS frame + int dlightframecount; // dynamic light frame + int realframecount; // not including viewpasses + int framecount; + + qboolean ignore_lightgamma; + qboolean fCustomRendering; + qboolean fResetVis; + qboolean fFlipViewModel; + + byte visbytes[(MAX_MAP_LEAFS+7)/8]; // member custom PVS + int lightstylevalue[MAX_LIGHTSTYLES]; // value 0 - 65536 + int block_size; // lightmap blocksize + + double frametime; // special frametime for multipass rendering (will set to 0 on a nextview) + float blend; // global blend value + + // cull info + vec3_t modelorg; // relative to viewpoint +} ref_globals_t; + +typedef struct +{ + uint c_world_polys; + uint c_studio_polys; + uint c_sprite_polys; + uint c_alias_polys; + uint c_world_leafs; + + uint c_view_beams_count; + uint c_active_tents_count; + uint c_alias_models_drawn; + uint c_studio_models_drawn; + uint c_sprite_models_drawn; + uint c_particle_count; + + uint c_client_ents; // entities that moved to client +} ref_speeds_t; + +extern ref_speeds_t r_stats; +extern ref_instance_t RI; +extern ref_globals_t tr; + +extern float gldepthmin, gldepthmax; +extern mleaf_t *r_viewleaf, *r_oldviewleaf; +extern mleaf_t *r_viewleaf2, *r_oldviewleaf2; +extern dlight_t cl_dlights[MAX_DLIGHTS]; +extern dlight_t cl_elights[MAX_ELIGHTS]; +#define r_numEntities (tr.draw_list->num_solid_entities + tr.draw_list->num_trans_entities) +#define r_numStatics (r_stats.c_client_ents) + +extern struct beam_s *cl_active_beams; +extern struct beam_s *cl_free_beams; +extern struct particle_s *cl_free_particles; + +// +// gl_backend.c +// +void GL_BackendStartFrame( void ); +void GL_BackendEndFrame( void ); +void GL_CleanUpTextureUnits( int last ); +void GL_Bind( GLint tmu, GLenum texnum ); +void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t ); +void GL_SetTexCoordArrayMode( GLenum mode ); +void GL_LoadTexMatrix( const matrix4x4 m ); +void GL_LoadTexMatrixExt( const float *glmatrix ); +void GL_LoadMatrix( const matrix4x4 source ); +void GL_TexGen( GLenum coord, GLenum mode ); +void GL_SelectTexture( GLint texture ); +void GL_CleanupAllTextureUnits( void ); +void GL_LoadIdentityTexMatrix( void ); +void GL_DisableAllTexGens( void ); +void GL_SetRenderMode( int mode ); +void GL_TextureTarget( uint target ); +void GL_Cull( GLenum cull ); +void R_ShowTextures( void ); + +// +// gl_cull.c +// +int R_CullModel( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ); +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); +qboolean R_CullSphere( const vec3_t centre, const float radius ); +int R_CullSurface( msurface_t *surf, gl_frustum_t *frustum, uint clipflags ); + +// +// gl_decals.c +// +void DrawSurfaceDecals( msurface_t *fa, qboolean single, qboolean reverse ); +float *R_DecalSetupVerts( decal_t *pDecal, msurface_t *surf, int texture, int *outCount ); +void DrawSingleDecal( decal_t *pDecal, msurface_t *fa ); +void R_EntityRemoveDecals( model_t *mod ); +void DrawDecalsBatch( void ); +void R_ClearDecals( void ); + +// +// gl_draw.c +// +void R_Set2DMode( qboolean enable ); +void R_DrawTileClear( int x, int y, int w, int h ); +void R_UploadStretchRaw( int texture, int cols, int rows, int width, int height, const byte *data ); + +// +// gl_image.c +// +void R_SetTextureParameters( void ); +gltexture_t *R_GetTexture( GLenum texnum ); +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags, imgfilter_t *filter ); +int GL_LoadTextureArray( const char **names, int flags, imgfilter_t *filter ); +int GL_LoadTextureInternal( const char *name, rgbdata_t *pic, texFlags_t flags, qboolean update ); +byte *GL_ResampleTexture( const byte *source, int in_w, int in_h, int out_w, int out_h, qboolean isNormalMap ); +int GL_CreateTexture( const char *name, int width, int height, const void *buffer, texFlags_t flags ); +int GL_CreateTextureArray( const char *name, int width, int height, int depth, const void *buffer, texFlags_t flags ); +void GL_ProcessTexture( int texnum, float gamma, int topColor, int bottomColor ); +void GL_ApplyTextureParams( gltexture_t *tex ); +void R_FreeImage( gltexture_t *image ); +int GL_FindTexture( const char *name ); +void GL_FreeTexture( GLenum texnum ); +void GL_FreeImage( const char *name ); +const char *GL_Target( GLenum target ); +void R_TextureList_f( void ); +void R_InitImages( void ); +void R_ShutdownImages( void ); + +// +// gl_refrag.c +// +void R_StoreEfrags( efrag_t **ppefrag, int framecount ); + +// +// gl_rlight.c +// +void R_PushDlights( void ); +void R_AnimateLight( void ); +void R_GetLightSpot( vec3_t lightspot ); +void R_MarkLights( dlight_t *light, int bit, mnode_t *node ); +void R_LightForPoint( const vec3_t point, color24 *ambientLight, qboolean invLight, qboolean useAmbient, float radius ); +colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lightspot ); +int R_CountSurfaceDlights( msurface_t *surf ); +colorVec R_LightPoint( const vec3_t p0 ); +int R_CountDlights( void ); + +// +// gl_rmain.c +// +void R_ClearScene( void ); +void R_LoadIdentity( void ); +void R_RenderScene( void ); +void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ); +void R_SetupRefParams( const struct ref_viewpass_s *rvp ); +void R_TranslateForEntity( cl_entity_t *e ); +void R_RotateForEntity( cl_entity_t *e ); +void R_SetupGL( qboolean set_gl_state ); +qboolean R_InitRenderAPI( void ); +void R_AllowFog( int allowed ); +void R_SetupFrustum( void ); +void R_FindViewLeaf( void ); +void R_PushScene( void ); +void R_PopScene( void ); +void R_DrawFog( void ); + +// +// gl_rmath.c +// +float V_CalcFov( float *fov_x, float width, float height ); +void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ); +void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ); +void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ); +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); +void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ); +void Matrix4x4_ConcatScale( matrix4x4 out, float x ); +void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ); +void Matrix4x4_CreateScale( matrix4x4 out, float x ); +void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_CreateProjection(matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar); +void Matrix4x4_CreateOrtho(matrix4x4 m, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar); +void Matrix4x4_CreateModelview( matrix4x4 out ); + +// +// gl_rmisc. +// +void R_ParseTexFilters( const char *filename ); +imgfilter_t *R_FindTexFilter( const char *texname ); + +// +// gl_rsurf.c +// +void R_MarkLeaves( void ); +void R_DrawWorld( void ); +void R_DrawWaterSurfaces( void ); +void R_DrawBrushModel( cl_entity_t *e ); +void GL_SubdivideSurface( msurface_t *fa ); +void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ); +void DrawGLPoly( glpoly_t *p, float xScale, float yScale ); +texture_t *R_TextureAnimation( msurface_t *s ); +void GL_SetupFogColorForSurfaces( void ); +void R_DrawAlphaTextureChains( void ); +void GL_RebuildLightmaps( void ); +void GL_InitRandomTable( void ); +void GL_BuildLightmaps( void ); +void GL_ResetFogColor( void ); + +// +// gl_sprite.c +// +void R_SpriteInit( void ); +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ); +mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ); +void R_DrawSpriteModel( cl_entity_t *e ); + +// +// gl_studio.c +// +void R_StudioInit( void ); +void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ); +void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ); +float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ); +struct mstudiotex_s *R_StudioGetTexture( cl_entity_t *e ); +float CL_GetStudioEstimatedFrame( cl_entity_t *ent ); +int R_GetEntityRenderMode( cl_entity_t *ent ); +void R_DrawStudioModel( cl_entity_t *e ); +player_info_t *pfnPlayerInfo( int index ); + +// +// gl_alias.c +// +void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ); +void R_DrawAliasModel( cl_entity_t *e ); +void R_AliasInit( void ); + +// +// gl_warp.c +// +void R_InitSkyClouds( struct mip_s *mt, struct texture_s *tx, qboolean custom_palette ); +void R_AddSkyBoxSurface( msurface_t *fa ); +void R_ClearSkyBox( void ); +void R_DrawSkyBox( void ); +void R_DrawClouds( void ); +void EmitWaterPolys( msurface_t *warp, qboolean reverse ); + +// +// gl_vidnt.c +// +#define GL_CheckForErrors() GL_CheckForErrors_( __FILE__, __LINE__ ) +void GL_CheckForErrors_( const char *filename, const int fileline ); +const char *VID_GetModeString( int vid_mode ); +void *GL_GetProcAddress( const char *name ); +void GL_UpdateSwapInterval( void ); +qboolean GL_DeleteContext( void ); +qboolean GL_Support( int r_ext ); +void VID_CheckChanges( void ); +int GL_MaxTextureUnits( void ); +qboolean R_Init( void ); +void R_Shutdown( void ); + +// +// renderer exports +// +qboolean R_Init( void ); +void R_Shutdown( void ); +void VID_CheckChanges( void ); +int GL_LoadTexture( const char *name, const byte *buf, size_t size, int flags, imgfilter_t *filter ); +void GL_FreeImage( const char *name ); +qboolean VID_ScreenShot( const char *filename, int shot_type ); +qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot ); +void R_BeginFrame( qboolean clearScene ); +void R_RenderFrame( const struct ref_viewpass_s *vp ); +void R_EndFrame( void ); +void R_ClearScene( void ); +void R_GetTextureParms( int *w, int *h, int texnum ); +void R_GetSpriteParms( int *frameWidth, int *frameHeight, int *numFrames, int curFrame, const struct model_s *pSprite ); +void R_DrawStretchRaw( float x, float y, float w, float h, int cols, int rows, const byte *data, qboolean dirty ); +void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, int texnum ); +qboolean R_SpeedsMessage( char *out, size_t size ); +void R_SetupSky( const char *skyboxname ); +qboolean R_CullBox( const vec3_t mins, const vec3_t maxs ); +qboolean R_WorldToScreen( const vec3_t point, vec3_t screen ); +void R_ScreenToWorld( const vec3_t screen, vec3_t point ); +qboolean R_AddEntity( struct cl_entity_s *pRefEntity, int entityType ); +void Mod_LoadMapSprite( struct model_s *mod, const void *buffer, size_t size, qboolean *loaded ); +void Mod_UnloadSpriteModel( struct model_s *mod ); +void Mod_UnloadStudioModel( struct model_s *mod ); +void Mod_UnloadBrushModel( struct model_s *mod ); +void Mod_UnloadAliasModel( struct model_s *mod ); +void GL_SetRenderMode( int mode ); +void R_RunViewmodelEvents( void ); +void R_DrawViewModel( void ); +int R_GetSpriteTexture( const struct model_s *m_pSpriteModel, int frame ); +void R_DecalShoot( int textureIndex, int entityIndex, int modelIndex, vec3_t pos, int flags, float scale ); +void R_RemoveEfrags( struct cl_entity_s *ent ); +void R_AddEfrags( struct cl_entity_s *ent ); +void R_DecalRemoveAll( int texture ); +byte *Mod_GetCurrentVis( void ); +void Mod_SetOrthoBounds( float *mins, float *maxs ); +void R_NewMap( void ); + +/* +======================================================================= + + GL STATE MACHINE + +======================================================================= +*/ +enum +{ + GL_OPENGL_110 = 0, // base + GL_WGL_EXTENSIONS, + GL_WGL_SWAPCONTROL, + GL_WGL_PROCADDRESS, + GL_ARB_MULTITEXTURE, + GL_TEXTURE_CUBEMAP_EXT, + GL_ANISOTROPY_EXT, + GL_TEXTURE_LOD_BIAS, + GL_TEXTURE_COMPRESSION_EXT, + GL_SHADER_GLSL100_EXT, + GL_TEXTURE_2D_RECT_EXT, + GL_TEXTURE_ARRAY_EXT, + GL_TEXTURE_3D_EXT, + GL_CLAMPTOEDGE_EXT, + GL_ARB_TEXTURE_NPOT_EXT, + GL_CLAMP_TEXBORDER_EXT, + GL_ARB_TEXTURE_FLOAT_EXT, + GL_ARB_DEPTH_FLOAT_EXT, + GL_ARB_SEAMLESS_CUBEMAP, + GL_EXT_GPU_SHADER4, // shaders only + GL_ARB_TEXTURE_RG, + GL_DEPTH_TEXTURE, + GL_DEBUG_OUTPUT, + GL_EXTCOUNT, // must be last +}; + +enum +{ + GL_KEEP_UNIT = -1, + GL_TEXTURE0 = 0, + GL_TEXTURE1, // used in some cases + MAX_TEXTURE_UNITS = 32 // can't acess to all over units without GLSL or cg +}; + +typedef enum +{ + GLHW_GENERIC, // where everthing works the way it should + GLHW_RADEON, // where you don't have proper GLSL support + GLHW_NVIDIA, // Geforce 8/9 class DX10 hardware + GLHW_INTEL // Intel Mobile Graphics +} glHWType_t; + +typedef struct +{ + const char *renderer_string; // ptrs to OpenGL32.dll, use with caution + const char *vendor_string; + const char *version_string; + + glHWType_t hardware_type; + + // list of supported extensions + const char *extensions_string; + const char *wgl_extensions_string; + byte extension[GL_EXTCOUNT]; + + int max_texture_units; + int max_texture_coords; + int max_teximage_units; + GLint max_2d_texture_size; + GLint max_2d_rectangle_size; + GLint max_2d_texture_layers; + GLint max_3d_texture_size; + GLint max_cubemap_size; + + GLfloat max_texture_anisotropy; + GLfloat max_texture_lod_bias; + + GLint max_vertex_uniforms; + GLint max_vertex_attribs; + + int color_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + + gl_context_type_t context; + gles_wrapper_t wrapper; + + qboolean softwareGammaUpdate; + int prev_mode; +} glconfig_t; + +typedef struct +{ + int width, height; + qboolean fullScreen; + qboolean wideScreen; + + int activeTMU; + GLint currentTextures[MAX_TEXTURE_UNITS]; + GLuint currentTextureTargets[MAX_TEXTURE_UNITS]; + GLboolean texIdentityMatrix[MAX_TEXTURE_UNITS]; + GLint genSTEnabled[MAX_TEXTURE_UNITS]; // 0 - disabled, OR 1 - S, OR 2 - T, OR 4 - R + GLint texCoordArrayMode[MAX_TEXTURE_UNITS]; // 0 - disabled, 1 - enabled, 2 - cubemap + + int faceCull; + + qboolean stencilEnabled; + qboolean in2DMode; +} glstate_t; + +typedef struct +{ + HDC hDC; // handle to device context + HGLRC hGLRC; // handle to GL rendering context + + int desktopBitsPixel; + int desktopWidth; + int desktopHeight; + + qboolean initialized; // OpenGL subsystem started + qboolean extended; // extended context allows to GL_Debug +} glwstate_t; + +extern glconfig_t glConfig; +extern glstate_t glState; +extern glwstate_t glw_state; + +// +// renderer cvars +// +extern convar_t *gl_texture_anisotropy; +extern convar_t *gl_extensions; +extern convar_t *gl_check_errors; +extern convar_t *gl_texture_lodbias; +extern convar_t *gl_texture_nearest; +extern convar_t *gl_lightmap_nearest; +extern convar_t *gl_keeptjunctions; +extern convar_t *gl_detailscale; +extern convar_t *gl_wireframe; +extern convar_t *gl_polyoffset; +extern convar_t *gl_finish; +extern convar_t *gl_nosort; +extern convar_t *gl_clear; +extern convar_t *gl_test; // cvar to testify new effects + +extern convar_t *r_speeds; +extern convar_t *r_fullbright; +extern convar_t *r_norefresh; +extern convar_t *r_lighting_extended; +extern convar_t *r_lighting_modulate; +extern convar_t *r_lighting_ambient; +extern convar_t *r_studio_lambert; +extern convar_t *r_detailtextures; +extern convar_t *r_drawentities; +extern convar_t *r_adjust_fov; +extern convar_t *r_decals; +extern convar_t *r_novis; +extern convar_t *r_nocull; +extern convar_t *r_lockpvs; +extern convar_t *r_lockfrustum; +extern convar_t *r_traceglow; +extern convar_t *r_dynamic; +extern convar_t *r_lightmap; + +extern convar_t *vid_displayfrequency; +extern convar_t *vid_fullscreen; +extern convar_t *vid_brightness; +extern convar_t *vid_gamma; +extern convar_t *vid_mode; + +#endif//GL_LOCAL_H \ No newline at end of file diff --git a/engine/client/gl_refrag.c b/engine/client/gl_refrag.c new file mode 100644 index 00000000..66071ed1 --- /dev/null +++ b/engine/client/gl_refrag.c @@ -0,0 +1,205 @@ +/* +gl_refrag.c - store entity fragments +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mod_local.h" +#include "entity_types.h" +#include "studio.h" + +/* +=============================================================================== + + ENTITY FRAGMENT FUNCTIONS + +=============================================================================== +*/ + +static efrag_t **lastlink; +static mnode_t *r_pefragtopnode; +static vec3_t r_emins, r_emaxs; +static cl_entity_t *r_addent; + +/* +================ +R_RemoveEfrags + +Call when removing an object from the world or moving it to another position +================ +*/ +void R_RemoveEfrags( cl_entity_t *ent ) +{ + efrag_t *ef, *old, *walk, **prev; + + ef = ent->efrag; + + while( ef ) + { + prev = &ef->leaf->efrags; + while( 1 ) + { + walk = *prev; + if( !walk ) break; + + if( walk == ef ) + { + // remove this fragment + *prev = ef->leafnext; + break; + } + else prev = &walk->leafnext; + } + + old = ef; + ef = ef->entnext; + + // put it on the free list + old->entnext = clgame.free_efrags; + clgame.free_efrags = old; + } + ent->efrag = NULL; +} + +/* +=================== +R_SplitEntityOnNode +=================== +*/ +static void R_SplitEntityOnNode( mnode_t *node ) +{ + efrag_t *ef; + mplane_t *splitplane; + mleaf_t *leaf; + int sides; + + if( node->contents == CONTENTS_SOLID ) + return; + + // add an efrag if the node is a leaf + if( node->contents < 0 ) + { + if( !r_pefragtopnode ) + r_pefragtopnode = node; + + leaf = (mleaf_t *)node; + + // grab an efrag off the free list + ef = clgame.free_efrags; + if( !ef ) + { + MsgDev( D_ERROR, "too many efrags!\n" ); + return; // no free fragments... + } + + clgame.free_efrags = clgame.free_efrags->entnext; + ef->entity = r_addent; + + // add the entity link + *lastlink = ef; + lastlink = &ef->entnext; + ef->entnext = NULL; + + // set the leaf links + ef->leaf = leaf; + ef->leafnext = leaf->efrags; + leaf->efrags = ef; + return; + } + + // NODE_MIXED + splitplane = node->plane; + sides = BOX_ON_PLANE_SIDE( r_emins, r_emaxs, splitplane ); + + if( sides == 3 ) + { + // split on this plane + // if this is the first splitter of this bmodel, remember it + if( !r_pefragtopnode ) r_pefragtopnode = node; + } + + // recurse down the contacted sides + if( sides & 1 ) R_SplitEntityOnNode( node->children[0] ); + if( sides & 2 ) R_SplitEntityOnNode( node->children[1] ); +} + +/* +=========== +R_AddEfrags +=========== +*/ +void R_AddEfrags( cl_entity_t *ent ) +{ + int i; + + if( !ent->model ) + return; + + r_addent = ent; + lastlink = &ent->efrag; + r_pefragtopnode = NULL; + + for( i = 0; i < 3; i++ ) + { + r_emins[i] = ent->origin[i] + ent->model->mins[i]; + r_emaxs[i] = ent->origin[i] + ent->model->maxs[i]; + } + + R_SplitEntityOnNode( cl.worldmodel->nodes ); + ent->topnode = r_pefragtopnode; +} + +/* +================ +R_StoreEfrags + +================ +*/ +void R_StoreEfrags( efrag_t **ppefrag, int framecount ) +{ + cl_entity_t *pent; + model_t *clmodel; + efrag_t *pefrag; + + while(( pefrag = *ppefrag ) != NULL ) + { + pent = pefrag->entity; + clmodel = pent->model; + + switch( clmodel->type ) + { + case mod_alias: + case mod_brush: + case mod_studio: + case mod_sprite: + pent = pefrag->entity; + + if( pent->visframe != framecount ) + { + if( CL_AddVisibleEntity( pent, ET_FRAGMENTED )) + { + // mark that we've recorded this entity for this frame + pent->visframe = framecount; + } + } + + ppefrag = &pefrag->leafnext; + break; + default: + Host_Error( "R_StoreEfrags: bad entity type %d\n", clmodel->type ); + break; + } + } +} \ No newline at end of file diff --git a/engine/client/gl_rlight.c b/engine/client/gl_rlight.c new file mode 100644 index 00000000..b8142376 --- /dev/null +++ b/engine/client/gl_rlight.c @@ -0,0 +1,430 @@ +/* +gl_rlight.c - dynamic and static lights +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "mathlib.h" +#include "gl_local.h" +#include "pm_local.h" +#include "studio.h" + +/* +============================================================================= + +DYNAMIC LIGHTS + +============================================================================= +*/ +/* +================== +CL_RunLightStyles + +================== +*/ +void CL_RunLightStyles( void ) +{ + int i, k, flight, clight; + float l, lerpfrac, backlerp; + float frametime = (cl.time - cl.oldtime); + float scale; + lightstyle_t *ls; + + if( !cl.worldmodel ) return; + + scale = r_lighting_modulate->value; + + // light animations + // 'm' is normal light, 'a' is no light, 'z' is double bright + for( i = 0, ls = cl.lightstyles; i < MAX_LIGHTSTYLES; i++, ls++ ) + { + if( r_fullbright->value || !cl.worldmodel->lightdata ) + { + tr.lightstylevalue[i] = 256 * 256; + continue; + } + + if( !cl.paused && frametime <= 0.1f ) + ls->time += frametime; // evaluate local time + + flight = (int)Q_floor( ls->time * 10 ); + clight = (int)Q_ceil( ls->time * 10 ); + lerpfrac = ( ls->time * 10 ) - flight; + backlerp = 1.0f - lerpfrac; + + if( !ls->length ) + { + tr.lightstylevalue[i] = 256 * scale; + continue; + } + else if( ls->length == 1 ) + { + // single length style so don't bother interpolating + tr.lightstylevalue[i] = ls->map[0] * 22 * scale; + continue; + } + else if( !ls->interp || !cl_lightstyle_lerping->value ) + { + tr.lightstylevalue[i] = ls->map[flight%ls->length] * 22 * scale; + continue; + } + + // interpolate animating light + // frame just gone + k = ls->map[flight % ls->length]; + l = (float)( k * 22.0f ) * backlerp; + + // upcoming frame + k = ls->map[clight % ls->length]; + l += (float)( k * 22.0f ) * lerpfrac; + + tr.lightstylevalue[i] = (int)l * scale; + } +} + +/* +============= +R_MarkLights +============= +*/ +void R_MarkLights( dlight_t *light, int bit, mnode_t *node ) +{ + float dist; + msurface_t *surf; + int i; + + if( node->contents < 0 ) + return; + + dist = PlaneDiff( light->origin, node->plane ); + + if( dist > light->radius ) + { + R_MarkLights( light, bit, node->children[0] ); + return; + } + if( dist < -light->radius ) + { + R_MarkLights( light, bit, node->children[1] ); + return; + } + + // mark the polygons + surf = RI.currentmodel->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + if( !BoundsAndSphereIntersect( surf->info->mins, surf->info->maxs, light->origin, light->radius )) + continue; // no intersection + + if( surf->dlightframe != tr.dlightframecount ) + { + surf->dlightbits = 0; + surf->dlightframe = tr.dlightframecount; + } + surf->dlightbits |= bit; + } + + R_MarkLights( light, bit, node->children[0] ); + R_MarkLights( light, bit, node->children[1] ); +} + +/* +============= +R_PushDlights +============= +*/ +void R_PushDlights( void ) +{ + dlight_t *l; + int i; + + tr.dlightframecount = tr.framecount; + l = cl_dlights; + + RI.currententity = clgame.entities; + RI.currentmodel = RI.currententity->model; + + for( i = 0; i < MAX_DLIGHTS; i++, l++ ) + { + if( l->die < cl.time || !l->radius ) + continue; + + if( GL_FrustumCullSphere( &RI.frustum, l->origin, l->radius, 15 )) + continue; + + R_MarkLights( l, 1<nodes ); + } +} + +/* +============= +R_CountDlights +============= +*/ +int R_CountDlights( void ) +{ + dlight_t *l; + int i, numDlights = 0; + + for( i = 0, l = cl_dlights; i < MAX_DLIGHTS; i++, l++ ) + { + if( l->die < cl.time || !l->radius ) + continue; + + numDlights++; + } + + return numDlights; +} + +/* +============= +R_CountSurfaceDlights +============= +*/ +int R_CountSurfaceDlights( msurface_t *surf ) +{ + int i, numDlights = 0; + + for( i = 0; i < MAX_DLIGHTS; i++ ) + { + if(!( surf->dlightbits & BIT( i ))) + continue; // not lit by this light + + numDlights++; + } + + return numDlights; +} + +/* +======================================================================= + + AMBIENT LIGHTING + +======================================================================= +*/ +static float g_trace_fraction; +static vec3_t g_trace_lightspot; + +/* +================= +R_RecursiveLightPoint +================= +*/ +static qboolean R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, colorVec *cv, const vec3_t start, const vec3_t end, qboolean debug ) +{ + float front, back, frac, midf; + int i, map, side, size; + float ds, dt, s, t; + int sample_size; + mextrasurf_t *info; + msurface_t *surf; + mtexinfo_t *tex; + color24 *lm; + vec3_t mid; + + // didn't hit anything + if( !node || node->contents < 0 ) + { + cv->r = cv->g = cv->b = cv->a = 0; + return false; + } + + // calculate mid point + front = PlaneDiff( start, node->plane ); + back = PlaneDiff( end, node->plane ); + + side = front < 0; + if(( back < 0 ) == side ) + return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, cv, start, end, debug ); + + frac = front / ( front - back ); + + VectorLerp( start, frac, end, mid ); + midf = p1f + ( p2f - p1f ) * frac; + + // co down front side + if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, cv, start, mid, debug )) + return true; // hit something + + if(( back < 0 ) == side ) + { + cv->r = cv->g = cv->b = cv->a = 0; + return false; // didn't hit anything + } + + // check for impact on this node + surf = model->surfaces + node->firstsurface; + VectorCopy( mid, g_trace_lightspot ); + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + int smax, tmax; + + tex = surf->texinfo; + info = surf->info; + + if( FBitSet( surf->flags, SURF_DRAWTILED )) + continue; // no lightmaps + + s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3]; + t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3]; + + if( s < info->lightmapmins[0] || t < info->lightmapmins[1] ) + continue; + + ds = s - info->lightmapmins[0]; + dt = t - info->lightmapmins[1]; + + if ( ds > info->lightextents[0] || dt > info->lightextents[1] ) + continue; + + cv->r = cv->g = cv->b = cv->a = 0; + + if( !surf->samples ) + return true; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = (info->lightextents[0] / sample_size) + 1; + tmax = (info->lightextents[1] / sample_size) + 1; + ds /= sample_size; + dt /= sample_size; + + lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); + g_trace_fraction = midf; + size = smax * tmax; + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + uint scale = tr.lightstylevalue[surf->styles[map]]; + + if( tr.ignore_lightgamma ) + { + cv->r += lm->r * scale; + cv->g += lm->g * scale; + cv->b += lm->b * scale; + } + else + { + cv->r += LightToTexGamma( lm->r ) * scale; + cv->g += LightToTexGamma( lm->g ) * scale; + cv->b += LightToTexGamma( lm->b ) * scale; + } + lm += size; // skip to next lightmap + } + + return true; + } + + // go down back side + return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, cv, mid, end, debug ); +} + +int R_LightTraceFilter( physent_t *pe ) +{ + if( !pe || pe->solid != SOLID_BSP || pe->info == 0 ) + return 1; + + return 0; +} + +/* +================= +R_LightVec + +check bspmodels to get light from +================= +*/ +colorVec R_LightVec( const vec3_t start, const vec3_t end, vec3_t lspot ) +{ + float last_fraction; + int i, maxEnts = 1; + colorVec light, cv; + + if( cl.worldmodel && cl.worldmodel->lightdata ) + { + light.r = light.g = light.b = light.a = 0; + last_fraction = 1.0f; + + // get light from bmodels too + if( r_lighting_extended->value ) + maxEnts = clgame.pmove->numphysent; + + // check all the bsp-models + for( i = 0; i < maxEnts; i++ ) + { + physent_t *pe = &clgame.pmove->physents[i]; + vec3_t offset, start_l, end_l; + mnode_t *pnodes; + matrix4x4 matrix; + + if( !pe->model || pe->model->type != mod_brush ) + continue; // skip non-bsp models + + pnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode]; + VectorSubtract( pe->model->hulls[0].clip_mins, vec3_origin, offset ); + VectorAdd( offset, pe->origin, offset ); + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + + // rotate start and end into the models frame of reference + if( !VectorIsNull( pe->angles )) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + + VectorClear( g_trace_lightspot ); + g_trace_fraction = 1.0f; + + if( !R_RecursiveLightPoint( pe->model, pnodes, 0.0f, 1.0f, &cv, start_l, end_l, lspot != NULL )) + continue; // didn't hit anything + + if( g_trace_fraction < last_fraction ) + { + if( lspot ) VectorCopy( g_trace_lightspot, lspot ); + light.r = Q_min(( cv.r >> 7 ), 255 ); + light.g = Q_min(( cv.g >> 7 ), 255 ); + light.b = Q_min(( cv.b >> 7 ), 255 ); + last_fraction = g_trace_fraction; + } + } + } + else + { + light.r = light.g = light.b = 255; + light.a = 0; + } + + return light; +} + +/* +================= +R_LightPoint + +light from floor +================= +*/ +colorVec R_LightPoint( const vec3_t p0 ) +{ + vec3_t p1; + + VectorSet( p1, p0[0], p0[1], p0[2] - 2048.0f ); + + return R_LightVec( p0, p1, NULL ); +} \ No newline at end of file diff --git a/engine/client/gl_rmain.c b/engine/client/gl_rmain.c new file mode 100644 index 00000000..6798128a --- /dev/null +++ b/engine/client/gl_rmain.c @@ -0,0 +1,1541 @@ +/* +gl_rmain.c - renderer main loop +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mathlib.h" +#include "library.h" +#include "beamdef.h" +#include "particledef.h" +#include "entity_types.h" + +#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) + +msurface_t *r_debug_surface; +const char *r_debug_hitbox; +float gldepthmin, gldepthmax; +ref_instance_t RI; + +static int R_RankForRenderMode( int rendermode ) +{ + switch( rendermode ) + { + case kRenderTransTexture: + return 1; // draw second + case kRenderTransAdd: + return 2; // draw third + case kRenderGlow: + return 3; // must be last! + } + return 0; +} + +void R_AllowFog( int allowed ) +{ + static int isFogEnabled; + + if( allowed ) + { + if( isFogEnabled ) + pglEnable( GL_FOG ); + } + else + { + isFogEnabled = pglIsEnabled( GL_FOG ); + + if( isFogEnabled ) + pglDisable( GL_FOG ); + } +} + +/* +=============== +R_OpaqueEntity + +Opaque entity can be brush or studio model but sprite +=============== +*/ +static qboolean R_OpaqueEntity( cl_entity_t *ent ) +{ + if( R_GetEntityRenderMode( ent ) == kRenderNormal ) + return true; + return false; +} + +/* +=============== +R_TransEntityCompare + +Sorting translucent entities by rendermode then by distance +=============== +*/ +static int R_TransEntityCompare( const cl_entity_t **a, const cl_entity_t **b ) +{ + cl_entity_t *ent1, *ent2; + vec3_t vecLen, org; + float dist1, dist2; + int rendermode1; + int rendermode2; + + ent1 = (cl_entity_t *)*a; + ent2 = (cl_entity_t *)*b; + rendermode1 = R_GetEntityRenderMode( ent1 ); + rendermode2 = R_GetEntityRenderMode( ent2 ); + + // sort by distance + if( ent1->model->type != mod_brush || rendermode1 != kRenderTransAlpha ) + { + VectorAverage( ent1->model->mins, ent1->model->maxs, org ); + VectorAdd( ent1->origin, org, org ); + VectorSubtract( RI.vieworg, org, vecLen ); + dist1 = DotProduct( vecLen, vecLen ); + } + else dist1 = 1000000000; + + if( ent2->model->type != mod_brush || rendermode2 != kRenderTransAlpha ) + { + VectorAverage( ent2->model->mins, ent2->model->maxs, org ); + VectorAdd( ent2->origin, org, org ); + VectorSubtract( RI.vieworg, org, vecLen ); + dist2 = DotProduct( vecLen, vecLen ); + } + else dist2 = 1000000000; + + if( dist1 > dist2 ) + return -1; + if( dist1 < dist2 ) + return 1; + + // then sort by rendermode + if( R_RankForRenderMode( rendermode1 ) > R_RankForRenderMode( rendermode2 )) + return 1; + if( R_RankForRenderMode( rendermode1 ) < R_RankForRenderMode( rendermode2 )) + return -1; + + return 0; +} + +/* +=============== +R_WorldToScreen + +Convert a given point from world into screen space +Returns true if we behind to screen +=============== +*/ +qboolean R_WorldToScreen( const vec3_t point, vec3_t screen ) +{ + matrix4x4 worldToScreen; + qboolean behind; + float w; + + if( !point || !screen ) + return true; + + Matrix4x4_Copy( worldToScreen, RI.worldviewProjectionMatrix ); + screen[0] = worldToScreen[0][0] * point[0] + worldToScreen[0][1] * point[1] + worldToScreen[0][2] * point[2] + worldToScreen[0][3]; + screen[1] = worldToScreen[1][0] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[1][2] * point[2] + worldToScreen[1][3]; + w = worldToScreen[3][0] * point[0] + worldToScreen[3][1] * point[1] + worldToScreen[3][2] * point[2] + worldToScreen[3][3]; + screen[2] = 0.0f; // just so we have something valid here + + if( w < 0.001f ) + { + screen[0] *= 100000; + screen[1] *= 100000; + behind = true; + } + else + { + float invw = 1.0f / w; + screen[0] *= invw; + screen[1] *= invw; + behind = false; + } + + return behind; +} + +/* +=============== +R_ScreenToWorld + +Convert a given point from screen into world space +=============== +*/ +void R_ScreenToWorld( const vec3_t screen, vec3_t point ) +{ + matrix4x4 screenToWorld; + float w; + + if( !point || !screen ) + return; + + Matrix4x4_Invert_Full( screenToWorld, RI.worldviewProjectionMatrix ); + + point[0] = screen[0] * screenToWorld[0][0] + screen[1] * screenToWorld[0][1] + screen[2] * screenToWorld[0][2] + screenToWorld[0][3]; + point[1] = screen[0] * screenToWorld[1][0] + screen[1] * screenToWorld[1][1] + screen[2] * screenToWorld[1][2] + screenToWorld[1][3]; + point[2] = screen[0] * screenToWorld[2][0] + screen[1] * screenToWorld[2][1] + screen[2] * screenToWorld[2][2] + screenToWorld[2][3]; + w = screen[0] * screenToWorld[3][0] + screen[1] * screenToWorld[3][1] + screen[2] * screenToWorld[3][2] + screenToWorld[3][3]; + if( w != 0.0f ) VectorScale( point, ( 1.0f / w ), point ); +} + +/* +=============== +R_PushScene +=============== +*/ +void R_PushScene( void ) +{ + if( ++tr.draw_stack_pos >= MAX_DRAW_STACK ) + Host_Error( "draw stack overflow\n" ); + + tr.draw_list = &tr.draw_stack[tr.draw_stack_pos]; +} + +/* +=============== +R_PushScene +=============== +*/ +void R_PopScene( void ) +{ + if( --tr.draw_stack_pos < 0 ) + Host_Error( "draw stack underflow\n" ); + tr.draw_list = &tr.draw_stack[tr.draw_stack_pos]; +} + +/* +=============== +R_ClearScene +=============== +*/ +void R_ClearScene( void ) +{ + tr.draw_list->num_solid_entities = 0; + tr.draw_list->num_trans_entities = 0; + tr.draw_list->num_beam_entities = 0; +} + +/* +=============== +R_AddEntity +=============== +*/ +qboolean R_AddEntity( struct cl_entity_s *clent, int type ) +{ + if( !r_drawentities->value ) + return false; // not allow to drawing + + if( !clent || !clent->model ) + return false; // if set to invisible, skip + + if( clent->curstate.effects & EF_NODRAW ) + return false; // done + + if( clent->curstate.rendermode != kRenderNormal && CL_FxBlend( clent ) <= 0 ) + return true; // invisible + + if( type == ET_FRAGMENTED ) + r_stats.c_client_ents++; + + if( R_OpaqueEntity( clent )) + { + // opaque + if( tr.draw_list->num_solid_entities >= MAX_VISIBLE_PACKET ) + return false; + + tr.draw_list->solid_entities[tr.draw_list->num_solid_entities] = clent; + tr.draw_list->num_solid_entities++; + } + else + { + // translucent + if( tr.draw_list->num_trans_entities >= MAX_VISIBLE_PACKET ) + return false; + + tr.draw_list->trans_entities[tr.draw_list->num_trans_entities] = clent; + tr.draw_list->num_trans_entities++; + } + + return true; +} + +/* +============= +R_Clear +============= +*/ +static void R_Clear( int bitMask ) +{ + int bits; + + if( CL_IsDevOverviewMode( )) + pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules) + else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); + + bits = GL_DEPTH_BUFFER_BIT; + + if( glState.stencilEnabled ) + bits |= GL_STENCIL_BUFFER_BIT; + + bits &= bitMask; + + pglClear( bits ); + + // change ordering for overview + if( RI.drawOrtho ) + { + gldepthmin = 1.0f; + gldepthmax = 0.0f; + } + else + { + gldepthmin = 0.0f; + gldepthmax = 1.0f; + } + + pglDepthFunc( GL_LEQUAL ); + pglDepthRange( gldepthmin, gldepthmax ); +} + +//============================================================================= +/* +=============== +R_GetFarClip +=============== +*/ +static float R_GetFarClip( void ) +{ + if( cl.worldmodel && RI.drawWorld ) + return clgame.movevars.zmax * 1.73f; + return 2048.0f; +} + +/* +=============== +R_SetupFrustum +=============== +*/ +void R_SetupFrustum( void ) +{ + ref_overview_t *ov = &clgame.overView; + + if( RP_NORMALPASS() && ( cl.local.waterlevel >= 3 )) + { + RI.fov_x = atan( tan( DEG2RAD( RI.fov_x ) / 2 ) * ( 0.97 + sin( cl.time * 1.5 ) * 0.03 )) * 2 / (M_PI / 180.0); + RI.fov_y = atan( tan( DEG2RAD( RI.fov_y ) / 2 ) * ( 1.03 - sin( cl.time * 1.5 ) * 0.03 )) * 2 / (M_PI / 180.0); + } + + // build the transformation matrix for the given view angles + AngleVectors( RI.viewangles, RI.vforward, RI.vright, RI.vup ); + + if( !r_lockfrustum->value ) + { + VectorCopy( RI.vieworg, RI.cullorigin ); + VectorCopy( RI.vforward, RI.cull_vforward ); + VectorCopy( RI.vright, RI.cull_vright ); + VectorCopy( RI.vup, RI.cull_vup ); + } + + if( RI.drawOrtho ) + GL_FrustumInitOrtho( &RI.frustum, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); + else GL_FrustumInitProj( &RI.frustum, 0.0f, R_GetFarClip(), RI.fov_x, RI.fov_y ); // NOTE: we ignore nearplane here (mirrors only) +} + +/* +============= +R_SetupProjectionMatrix +============= +*/ +static void R_SetupProjectionMatrix( matrix4x4 m ) +{ + GLdouble xMin, xMax, yMin, yMax, zNear, zFar; + + if( RI.drawOrtho ) + { + ref_overview_t *ov = &clgame.overView; + Matrix4x4_CreateOrtho( m, ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); + return; + } + + RI.farClip = R_GetFarClip(); + + zNear = 4.0f; + zFar = max( 256.0f, RI.farClip ); + + yMax = zNear * tan( RI.fov_y * M_PI / 360.0 ); + yMin = -yMax; + + xMax = zNear * tan( RI.fov_x * M_PI / 360.0 ); + xMin = -xMax; + + Matrix4x4_CreateProjection( m, xMax, xMin, yMax, yMin, zNear, zFar ); +} + +/* +============= +R_SetupModelviewMatrix +============= +*/ +static void R_SetupModelviewMatrix( matrix4x4 m ) +{ + Matrix4x4_CreateModelview( m ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[2], 1, 0, 0 ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[0], 0, 1, 0 ); + Matrix4x4_ConcatRotate( m, -RI.viewangles[1], 0, 0, 1 ); + Matrix4x4_ConcatTranslate( m, -RI.vieworg[0], -RI.vieworg[1], -RI.vieworg[2] ); +} + +/* +============= +R_LoadIdentity +============= +*/ +void R_LoadIdentity( void ) +{ + if( tr.modelviewIdentity ) return; + + Matrix4x4_LoadIdentity( RI.objectMatrix ); + Matrix4x4_Copy( RI.modelviewMatrix, RI.worldviewMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); + tr.modelviewIdentity = true; +} + +/* +============= +R_RotateForEntity +============= +*/ +void R_RotateForEntity( cl_entity_t *e ) +{ + float scale = 1.0f; + + if( e == clgame.entities ) + { + R_LoadIdentity(); + return; + } + + if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, scale ); + Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); + tr.modelviewIdentity = false; +} + +/* +============= +R_TranslateForEntity +============= +*/ +void R_TranslateForEntity( cl_entity_t *e ) +{ + float scale = 1.0f; + + if( e == clgame.entities ) + { + R_LoadIdentity(); + return; + } + + if( e->model->type != mod_brush && e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + Matrix4x4_CreateFromEntity( RI.objectMatrix, vec3_origin, e->origin, scale ); + Matrix4x4_ConcatTransforms( RI.modelviewMatrix, RI.worldviewMatrix, RI.objectMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.modelviewMatrix ); + tr.modelviewIdentity = false; +} + +/* +=============== +R_FindViewLeaf +=============== +*/ +void R_FindViewLeaf( void ) +{ + RI.oldviewleaf = RI.viewleaf; + RI.viewleaf = Mod_PointInLeaf( RI.pvsorigin, cl.worldmodel->nodes ); +} + +/* +=============== +R_SetupFrame +=============== +*/ +static void R_SetupFrame( void ) +{ + // setup viewplane dist + RI.viewplanedist = DotProduct( RI.vieworg, RI.vforward ); + + if( !gl_nosort->value ) + { + // sort translucents entities by rendermode and distance + qsort( tr.draw_list->trans_entities, tr.draw_list->num_trans_entities, sizeof( cl_entity_t* ), R_TransEntityCompare ); + } + + // current viewleaf + if( RI.drawWorld ) + { + RI.isSkyVisible = false; // unknown at this moment + R_FindViewLeaf(); + } +} + +/* +============= +R_SetupGL +============= +*/ +void R_SetupGL( qboolean set_gl_state ) +{ + R_SetupModelviewMatrix( RI.worldviewMatrix ); + R_SetupProjectionMatrix( RI.projectionMatrix ); + + Matrix4x4_Concat( RI.worldviewProjectionMatrix, RI.projectionMatrix, RI.worldviewMatrix ); + + if( !set_gl_state ) return; + + if( RP_NORMALPASS( )) + { + int x, x2, y, y2; + + // set up viewport (main, playersetup) + x = floor( RI.viewport[0] * glState.width / glState.width ); + x2 = ceil(( RI.viewport[0] + RI.viewport[2] ) * glState.width / glState.width ); + y = floor( glState.height - RI.viewport[1] * glState.height / glState.height ); + y2 = ceil( glState.height - ( RI.viewport[1] + RI.viewport[3] ) * glState.height / glState.height ); + + pglViewport( x, y2, x2 - x, y - y2 ); + } + else + { + // envpass, mirrorpass + pglViewport( RI.viewport[0], RI.viewport[1], RI.viewport[2], RI.viewport[3] ); + } + + pglMatrixMode( GL_PROJECTION ); + GL_LoadMatrix( RI.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI.worldviewMatrix ); + + if( RI.params & RP_CLIPPLANE ) + { + GLdouble clip[4]; + mplane_t *p = &RI.clipPlane; + + clip[0] = p->normal[0]; + clip[1] = p->normal[1]; + clip[2] = p->normal[2]; + clip[3] = -p->dist; + + pglClipPlane( GL_CLIP_PLANE0, clip ); + pglEnable( GL_CLIP_PLANE0 ); + } + + GL_Cull( GL_FRONT ); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); +} + +/* +============= +R_EndGL +============= +*/ +static void R_EndGL( void ) +{ + if( RI.params & RP_CLIPPLANE ) + pglDisable( GL_CLIP_PLANE0 ); +} + +/* +============= +R_RecursiveFindWaterTexture + +using to find source waterleaf with +watertexture to grab fog values from it +============= +*/ +static gltexture_t *R_RecursiveFindWaterTexture( const mnode_t *node, const mnode_t *ignore, qboolean down ) +{ + gltexture_t *tex = NULL; + + // assure the initial node is not null + // we could check it here, but we would rather check it + // outside the call to get rid of one additional recursion level + Assert( node != NULL ); + + // ignore solid nodes + if( node->contents == CONTENTS_SOLID ) + return NULL; + + if( node->contents < 0 ) + { + mleaf_t *pleaf; + msurface_t **mark; + int i, c; + + // ignore non-liquid leaves + if( node->contents != CONTENTS_WATER && node->contents != CONTENTS_LAVA && node->contents != CONTENTS_SLIME ) + return NULL; + + // find texture + pleaf = (mleaf_t *)node; + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + for( i = 0; i < c; i++, mark++ ) + { + if( (*mark)->flags & SURF_DRAWTURB && (*mark)->texinfo && (*mark)->texinfo->texture ) + return R_GetTexture( (*mark)->texinfo->texture->gl_texturenum ); + } + + // texture not found + return NULL; + } + + // this is a regular node + // traverse children + if( node->children[0] && ( node->children[0] != ignore )) + { + tex = R_RecursiveFindWaterTexture( node->children[0], node, true ); + if( tex ) return tex; + } + + if( node->children[1] && ( node->children[1] != ignore )) + { + tex = R_RecursiveFindWaterTexture( node->children[1], node, true ); + if( tex ) return tex; + } + + // for down recursion, return immediately + if( down ) return NULL; + + // texture not found, step up if any + if( node->parent ) + return R_RecursiveFindWaterTexture( node->parent, node, false ); + + // top-level node, bail out + return NULL; +} + +/* +============= +R_CheckFog + +check for underwater fog +Using backward recursion to find waterline leaf +from underwater leaf (idea: XaeroX) +============= +*/ +static void R_CheckFog( void ) +{ + cl_entity_t *ent; + gltexture_t *tex; + int i, cnt, count; + + // quake global fog + if( clgame.movevars.fog_settings != 0 && FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + // quake-style global fog + RI.fogColor[0] = ((clgame.movevars.fog_settings & 0xFF000000) >> 24) / 255.0f; + RI.fogColor[1] = ((clgame.movevars.fog_settings & 0xFF0000) >> 16) / 255.0f; + RI.fogColor[2] = ((clgame.movevars.fog_settings & 0xFF00) >> 8) / 255.0f; + RI.fogDensity = ((clgame.movevars.fog_settings & 0xFF) / 255.0f) * 0.015625f; + RI.fogStart = RI.fogEnd = 0.0f; + RI.fogColor[3] = 1.0f; + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + return; + } + +#ifdef HACKS_RELATED_HLMODS + // special condition for Spirit 1.9 that used direct calls of glFog-functions + if(( !RI.fogEnabled && !RI.fogCustom ) && pglIsEnabled( GL_FOG ) && VectorIsNull( RI.fogColor )) + { + // fill the fog color from GL-state machine + pglGetFloatv( GL_FOG_COLOR, RI.fogColor ); + RI.fogSkybox = true; + } +#endif + RI.fogEnabled = false; + + if( RI.onlyClientDraw || cl.local.waterlevel < 3 || !RI.drawWorld || !RI.viewleaf ) + { + if( RI.cached_waterlevel == 3 ) + { + // in some cases waterlevel jumps from 3 to 1. Catch it + RI.cached_waterlevel = cl.local.waterlevel; + RI.cached_contents = CONTENTS_EMPTY; + if( !RI.fogCustom ) pglDisable( GL_FOG ); + } + return; + } + + ent = CL_GetWaterEntity( RI.vieworg ); + if( ent && ent->model && ent->model->type == mod_brush && ent->curstate.skin < 0 ) + cnt = ent->curstate.skin; + else cnt = RI.viewleaf->contents; + + RI.cached_waterlevel = cl.local.waterlevel; + + if( !IsLiquidContents( RI.cached_contents ) && IsLiquidContents( cnt )) + { + tex = NULL; + + // check for water texture + if( ent && ent->model && ent->model->type == mod_brush ) + { + msurface_t *surf; + + count = ent->model->nummodelsurfaces; + + for( i = 0, surf = &ent->model->surfaces[ent->model->firstmodelsurface]; i < count; i++, surf++ ) + { + if( surf->flags & SURF_DRAWTURB && surf->texinfo && surf->texinfo->texture ) + { + tex = R_GetTexture( surf->texinfo->texture->gl_texturenum ); + RI.cached_contents = ent->curstate.skin; + break; + } + } + } + else + { + tex = R_RecursiveFindWaterTexture( RI.viewleaf->parent, NULL, false ); + if( tex ) RI.cached_contents = RI.viewleaf->contents; + } + + if( !tex ) return; // no valid fogs + + // copy fog params + RI.fogColor[0] = tex->fogParams[0] / 255.0f; + RI.fogColor[1] = tex->fogParams[1] / 255.0f; + RI.fogColor[2] = tex->fogParams[2] / 255.0f; + RI.fogDensity = tex->fogParams[3] * 0.000025f; + RI.fogStart = RI.fogEnd = 0.0f; + RI.fogColor[3] = 1.0f; + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + } + else + { + RI.fogCustom = false; + RI.fogEnabled = true; + RI.fogSkybox = true; + } +} + +/* +============= +R_DrawFog + +============= +*/ +void R_DrawFog( void ) +{ + if( !RI.fogEnabled ) return; + + pglEnable( GL_FOG ); + pglFogi( GL_FOG_MODE, GL_EXP ); + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); + pglFogfv( GL_FOG_COLOR, RI.fogColor ); + pglHint( GL_FOG_HINT, GL_NICEST ); +} + +/* +============= +R_DrawEntitiesOnList +============= +*/ +void R_DrawEntitiesOnList( void ) +{ + int i; + + tr.blend = 1.0f; + GL_CheckForErrors(); + + // first draw solid entities + for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->solid_entities[i]; + RI.currentmodel = RI.currententity->model; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_brush: + R_DrawBrushModel( RI.currententity ); + break; + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_DrawStudioModel( RI.currententity ); + break; + default: + break; + } + } + + GL_CheckForErrors(); + + // quake-specific feature + R_DrawAlphaTextureChains(); + + GL_CheckForErrors(); + + // draw sprites seperately, because of alpha blending + for( i = 0; i < tr.draw_list->num_solid_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->solid_entities[i]; + RI.currentmodel = RI.currententity->model; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_sprite: + R_DrawSpriteModel( RI.currententity ); + break; + } + } + + GL_CheckForErrors(); + + if( !RI.onlyClientDraw ) + { + CL_DrawBeams( false ); + } + + GL_CheckForErrors(); + + if( RI.drawWorld ) + clgame.dllFuncs.pfnDrawNormalTriangles(); + + GL_CheckForErrors(); + + // then draw translucent entities + for( i = 0; i < tr.draw_list->num_trans_entities && !RI.onlyClientDraw; i++ ) + { + RI.currententity = tr.draw_list->trans_entities[i]; + RI.currentmodel = RI.currententity->model; + + // handle studiomodels with custom rendermodes on texture + if( RI.currententity->curstate.rendermode != kRenderNormal ) + tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; + else tr.blend = 1.0f; // draw as solid but sorted by distance + + if( tr.blend <= 0.0f ) continue; + + Assert( RI.currententity != NULL ); + Assert( RI.currentmodel != NULL ); + + switch( RI.currentmodel->type ) + { + case mod_brush: + R_DrawBrushModel( RI.currententity ); + break; + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_DrawStudioModel( RI.currententity ); + break; + case mod_sprite: + R_DrawSpriteModel( RI.currententity ); + break; + default: + break; + } + } + + GL_CheckForErrors(); + + if( RI.drawWorld ) + { + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + clgame.dllFuncs.pfnDrawTransparentTriangles (); + } + + GL_CheckForErrors(); + + if( !RI.onlyClientDraw ) + { + R_AllowFog( false ); + CL_DrawBeams( true ); + CL_DrawParticles( tr.frametime ); + CL_DrawTracers( tr.frametime ); + R_AllowFog( true ); + } + + GL_CheckForErrors(); + + pglDisable( GL_BLEND ); // Trinity Render issues + + if( !RI.onlyClientDraw ) + R_DrawViewModel(); + CL_ExtraUpdate(); + + GL_CheckForErrors(); +} + +/* +================ +R_RenderScene + +R_SetupRefParams must be called right before +================ +*/ +void R_RenderScene( void ) +{ + if( !cl.worldmodel && RI.drawWorld ) + Host_Error( "R_RenderView: NULL worldmodel\n" ); + + // frametime is valid only for normal pass + if( RP_NORMALPASS( )) + tr.frametime = cl.time - cl.oldtime; + else tr.frametime = 0.0; + + // begin a new frame + tr.framecount++; + + R_PushDlights(); + + R_SetupFrustum(); + R_SetupFrame(); + R_SetupGL( true ); + R_Clear( ~0 ); + + R_MarkLeaves(); + R_DrawFog (); + + R_DrawWorld(); + R_CheckFog(); + + CL_ExtraUpdate (); // don't let sound get messed up if going slow + + R_DrawEntitiesOnList(); + + R_DrawWaterSurfaces(); + + R_EndGL(); +} + +/* +=============== +R_DoResetGamma + +gamma will be reset for +some type of screenshots +=============== +*/ +qboolean R_DoResetGamma( void ) +{ + // FIXME: this looks ugly. apply the backward gamma changes to the output image + return false; + + switch( cls.scrshot_action ) + { + case scrshot_normal: + if( CL_IsDevOverviewMode( )) + return true; + return false; + case scrshot_snapshot: + if( CL_IsDevOverviewMode( )) + return true; + return false; + case scrshot_plaque: + case scrshot_savegame: + case scrshot_demoshot: + case scrshot_envshot: + case scrshot_skyshot: + case scrshot_mapshot: + return true; + default: + return false; + } +} + +/* +=============== +R_BeginFrame +=============== +*/ +void R_BeginFrame( qboolean clearScene ) +{ + glConfig.softwareGammaUpdate = false; // in case of possible fails + + if(( gl_clear->value || CL_IsDevOverviewMode( )) && clearScene && cls.state != ca_cinematic ) + { + pglClear( GL_COLOR_BUFFER_BIT ); + } + + if( R_DoResetGamma( )) + { + BuildGammaTable( 1.8f, 0.0f ); + glConfig.softwareGammaUpdate = true; + GL_RebuildLightmaps(); + glConfig.softwareGammaUpdate = false; + + // next frame will be restored gamma + SetBits( vid_brightness->flags, FCVAR_CHANGED ); + SetBits( vid_gamma->flags, FCVAR_CHANGED ); + } + else if( FBitSet( vid_gamma->flags, FCVAR_CHANGED ) || FBitSet( vid_brightness->flags, FCVAR_CHANGED )) + { + BuildGammaTable( vid_gamma->value, vid_brightness->value ); + glConfig.softwareGammaUpdate = true; + GL_RebuildLightmaps(); + glConfig.softwareGammaUpdate = false; + } + + R_Set2DMode( true ); + + // draw buffer stuff + pglDrawBuffer( GL_BACK ); + + // update texture parameters + if( FBitSet( gl_texture_nearest->flags|gl_lightmap_nearest->flags|gl_texture_anisotropy->flags|gl_texture_lodbias->flags, FCVAR_CHANGED )) + R_SetTextureParameters(); + + // swapinterval stuff + GL_UpdateSwapInterval(); + + CL_ExtraUpdate (); +} + +/* +=============== +R_SetupRefParams + +set initial params for renderer +=============== +*/ +void R_SetupRefParams( const ref_viewpass_t *rvp ) +{ + RI.params = RP_NONE; + RI.drawWorld = FBitSet( rvp->flags, RF_DRAW_WORLD ); + RI.onlyClientDraw = FBitSet( rvp->flags, RF_ONLY_CLIENTDRAW ); + RI.farClip = 0; + + if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP )) + RI.drawOrtho = FBitSet( rvp->flags, RF_DRAW_OVERVIEW ); + else RI.drawOrtho = false; + + // setup viewport + RI.viewport[0] = rvp->viewport[0]; + RI.viewport[1] = rvp->viewport[1]; + RI.viewport[2] = rvp->viewport[2]; + RI.viewport[3] = rvp->viewport[3]; + + // calc FOV + RI.fov_x = rvp->fov_x; + RI.fov_y = rvp->fov_y; + + VectorCopy( rvp->vieworigin, RI.vieworg ); + VectorCopy( rvp->viewangles, RI.viewangles ); + VectorCopy( rvp->vieworigin, RI.pvsorigin ); +} + +/* +=============== +R_RenderFrame +=============== +*/ +void R_RenderFrame( const ref_viewpass_t *rvp ) +{ + if( r_norefresh->value ) + return; + + // setup the initial render params + R_SetupRefParams( rvp ); + + if( gl_finish->value && RI.drawWorld ) + pglFinish(); + + // completely override rendering + if( clgame.drawFuncs.GL_RenderFrame != NULL ) + { + tr.fCustomRendering = true; + + if( clgame.drawFuncs.GL_RenderFrame( rvp )) + { + tr.realframecount++; + tr.fResetVis = true; + return; + } + } + + tr.fCustomRendering = false; + if( !RI.onlyClientDraw ) + R_RunViewmodelEvents(); + + tr.realframecount++; // right called after viewmodel events + R_RenderScene(); +} + +/* +=============== +R_EndFrame +=============== +*/ +void R_EndFrame( void ) +{ + // flush any remaining 2D bits + R_Set2DMode( false ); + + if( !pwglSwapBuffers( glw_state.hDC )) + { + Con_Printf( S_ERROR "failed to swap buffers\n" ); + Host_NewInstance( va("#%s", GI->gamefolder ), "stopped" ); + } +} + +/* +=============== +R_DrawCubemapView +=============== +*/ +void R_DrawCubemapView( const vec3_t origin, const vec3_t angles, int size ) +{ + ref_viewpass_t rvp; + + // basic params + rvp.flags = rvp.viewentity = 0; + SetBits( rvp.flags, RF_DRAW_WORLD ); + SetBits( rvp.flags, RF_DRAW_CUBEMAP ); + + rvp.viewport[0] = rvp.viewport[1] = 0; + rvp.viewport[2] = rvp.viewport[3] = size; + rvp.fov_x = rvp.fov_y = 90.0f; // this is a final fov value + + // setup origin & angles + VectorCopy( origin, rvp.vieworigin ); + VectorCopy( angles, rvp.viewangles ); + + R_RenderFrame( &rvp ); + + RI.viewleaf = NULL; // force markleafs next frame +} + +static int GL_RenderGetParm( int parm, int arg ) +{ + gltexture_t *glt; + + switch( parm ) + { + case PARM_TEX_WIDTH: + glt = R_GetTexture( arg ); + return glt->width; + case PARM_TEX_HEIGHT: + glt = R_GetTexture( arg ); + return glt->height; + case PARM_TEX_SRC_WIDTH: + glt = R_GetTexture( arg ); + return glt->srcWidth; + case PARM_TEX_SRC_HEIGHT: + glt = R_GetTexture( arg ); + return glt->srcHeight; + case PARM_TEX_GLFORMAT: + glt = R_GetTexture( arg ); + return glt->format; + case PARM_TEX_ENCODE: + glt = R_GetTexture( arg ); + return glt->encode; + case PARM_TEX_MIPCOUNT: + glt = R_GetTexture( arg ); + return glt->numMips; + case PARM_TEX_DEPTH: + glt = R_GetTexture( arg ); + return glt->depth; + case PARM_BSP2_SUPPORTED: +#ifdef SUPPORT_BSP2_FORMAT + return 1; +#endif + return 0; + case PARM_TEX_SKYBOX: + Assert( arg >= 0 && arg < 6 ); + return tr.skyboxTextures[arg]; + case PARM_TEX_SKYTEXNUM: + return tr.skytexturenum; + case PARM_TEX_LIGHTMAP: + arg = bound( 0, arg, MAX_LIGHTMAPS - 1 ); + return tr.lightmapTextures[arg]; + case PARM_SKY_SPHERE: + return FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX ); + case PARM_WIDESCREEN: + return glState.wideScreen; + case PARM_FULLSCREEN: + return glState.fullScreen; + case PARM_SCREEN_WIDTH: + return glState.width; + case PARM_SCREEN_HEIGHT: + return glState.height; + case PARM_CLIENT_INGAME: + return CL_IsInGame(); + case PARM_MAX_ENTITIES: + return clgame.maxEntities; + case PARM_TEX_TARGET: + glt = R_GetTexture( arg ); + return glt->target; + case PARM_TEX_TEXNUM: + glt = R_GetTexture( arg ); + return glt->texnum; + case PARM_TEX_FLAGS: + glt = R_GetTexture( arg ); + return glt->flags; + case PARM_FEATURES: + return host.features; + case PARM_ACTIVE_TMU: + return glState.activeTMU; + case PARM_LIGHTSTYLEVALUE: + arg = bound( 0, arg, MAX_LIGHTSTYLES - 1 ); + return tr.lightstylevalue[arg]; + case PARM_MAP_HAS_DELUXE: + return FBitSet( world.flags, FWORLD_HAS_DELUXEMAP ); + case PARM_MAX_IMAGE_UNITS: + return GL_MaxTextureUnits(); + case PARM_CLIENT_ACTIVE: + return (cls.state == ca_active); + case PARM_REBUILD_GAMMA: + return glConfig.softwareGammaUpdate; + case PARM_DEDICATED_SERVER: + return (host.type == HOST_DEDICATED); + case PARM_SURF_SAMPLESIZE: + if( arg >= 0 && arg < cl.worldmodel->numsurfaces ) + return Mod_SampleSizeForFace( &cl.worldmodel->surfaces[arg] ); + return LM_SAMPLE_SIZE; + case PARM_GL_CONTEXT_TYPE: + return glConfig.context; + case PARM_GLES_WRAPPER: + return glConfig.wrapper; + case PARM_STENCIL_ACTIVE: + return glState.stencilEnabled; + case PARM_WATER_ALPHA: + return FBitSet( world.flags, FWORLD_WATERALPHA ); + } + return 0; +} + +static void R_GetDetailScaleForTexture( int texture, float *xScale, float *yScale ) +{ + gltexture_t *glt = R_GetTexture( texture ); + + if( xScale ) *xScale = glt->xscale; + if( yScale ) *yScale = glt->yscale; +} + +static void R_GetExtraParmsForTexture( int texture, byte *red, byte *green, byte *blue, byte *density ) +{ + gltexture_t *glt = R_GetTexture( texture ); + + if( red ) *red = glt->fogParams[0]; + if( green ) *green = glt->fogParams[1]; + if( blue ) *blue = glt->fogParams[2]; + if( density ) *density = glt->fogParams[3]; +} + +/* +================= +R_EnvShot + +================= +*/ +static void R_EnvShot( const float *vieworg, const char *name, int skyshot, int shotsize ) +{ + static vec3_t viewPoint; + + if( !name ) + { + MsgDev( D_ERROR, "R_%sShot: bad name\n", skyshot ? "Sky" : "Env" ); + return; + } + + if( cls.scrshot_action != scrshot_inactive ) + { + if( cls.scrshot_action != scrshot_skyshot && cls.scrshot_action != scrshot_envshot ) + MsgDev( D_ERROR, "R_%sShot: subsystem is busy, try later.\n", skyshot ? "Sky" : "Env" ); + return; + } + + cls.envshot_vieworg = NULL; // use client view + Q_strncpy( cls.shotname, name, sizeof( cls.shotname )); + + if( vieworg ) + { + // make sure what viewpoint don't temporare + VectorCopy( vieworg, viewPoint ); + cls.envshot_vieworg = viewPoint; + cls.envshot_disable_vis = true; + } + + // make request for envshot + if( skyshot ) cls.scrshot_action = scrshot_skyshot; + else cls.scrshot_action = scrshot_envshot; + + // catch negative values + cls.envshot_viewsize = max( 0, shotsize ); +} + +static void R_SetCurrentEntity( cl_entity_t *ent ) +{ + RI.currententity = ent; + + // set model also + if( RI.currententity != NULL ) + { + RI.currentmodel = RI.currententity->model; + } +} + +static void R_SetCurrentModel( model_t *mod ) +{ + RI.currentmodel = mod; +} + +static int R_FatPVS( const vec3_t org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ) +{ + return Mod_FatPVS( org, radius, visbuffer, world.visbytes, merge, fullvis ); +} + +static lightstyle_t *CL_GetLightStyle( int number ) +{ + Assert( number >= 0 && number < MAX_LIGHTSTYLES ); + return &cl.lightstyles[number]; +} + +static dlight_t *CL_GetDynamicLight( int number ) +{ + Assert( number >= 0 && number < MAX_DLIGHTS ); + return &cl_dlights[number]; +} + +static dlight_t *CL_GetEntityLight( int number ) +{ + Assert( number >= 0 && number < MAX_ELIGHTS ); + return &cl_elights[number]; +} + +static float R_GetFrameTime( void ) +{ + return tr.frametime; +} + +static const char *GL_TextureName( unsigned int texnum ) +{ + return R_GetTexture( texnum )->name; +} + +const byte *GL_TextureData( unsigned int texnum ) +{ + rgbdata_t *pic = R_GetTexture( texnum )->original; + + if( pic != NULL ) + return pic->buffer; + return NULL; +} + +static int GL_LoadTextureNoFilter( const char *name, const byte *buf, size_t size, int flags ) +{ + return GL_LoadTexture( name, buf, size, flags, NULL ); +} + +static int GL_LoadTextureArrayNoFilter( const char **names, int flags ) +{ + return GL_LoadTextureArray( names, flags, NULL ); +} + +static const ref_overview_t *GL_GetOverviewParms( void ) +{ + return &clgame.overView; +} + +static void *R_Mem_Alloc( size_t cb, const char *filename, const int fileline ) +{ + return _Mem_Alloc( cls.mempool, cb, filename, fileline ); +} + +static void R_Mem_Free( void *mem, const char *filename, const int fileline ) +{ + if( !mem ) return; + _Mem_Free( mem, filename, fileline ); +} + +/* +========= +pfnGetFilesList + +========= +*/ +static char **pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) +{ + static search_t *t = NULL; + + if( t ) Mem_Free( t ); // release prev search + + t = FS_Search( pattern, true, gamedironly ); + + if( !t ) + { + if( numFiles ) *numFiles = 0; + return NULL; + } + + if( numFiles ) *numFiles = t->numfilenames; + return t->filenames; +} + +static uint pfnFileBufferCRC32( const void *buffer, const int length ) +{ + uint modelCRC = 0; + + if( !buffer || length <= 0 ) + return modelCRC; + + CRC32_Init( &modelCRC ); + CRC32_ProcessBuffer( &modelCRC, buffer, length ); + return CRC32_Final( modelCRC ); +} + +/* +============= +CL_GenericHandle + +============= +*/ +const char *CL_GenericHandle( int fileindex ) +{ + if( fileindex < 0 || fileindex >= MAX_CUSTOM ) + return 0; + return cl.files_precache[fileindex]; +} + +static render_api_t gRenderAPI = +{ + GL_RenderGetParm, + R_GetDetailScaleForTexture, + R_GetExtraParmsForTexture, + CL_GetLightStyle, + CL_GetDynamicLight, + CL_GetEntityLight, + LightToTexGamma, + R_GetFrameTime, + R_SetCurrentEntity, + R_SetCurrentModel, + R_FatPVS, + R_StoreEfrags, + GL_FindTexture, + GL_TextureName, + GL_TextureData, + GL_LoadTextureNoFilter, + GL_CreateTexture, + GL_LoadTextureArrayNoFilter, + GL_CreateTextureArray, + GL_FreeTexture, + DrawSingleDecal, + R_DecalSetupVerts, + R_EntityRemoveDecals, + AVI_LoadVideoNoSound, + AVI_GetVideoInfo, + AVI_GetVideoFrameNumber, + AVI_GetVideoFrame, + R_UploadStretchRaw, + AVI_FreeVideo, + AVI_IsActive, + NULL, + NULL, + NULL, + GL_Bind, + GL_SelectTexture, + GL_LoadTexMatrixExt, + GL_LoadIdentityTexMatrix, + GL_CleanUpTextureUnits, + GL_TexGen, + GL_TextureTarget, + GL_SetTexCoordArrayMode, + GL_GetProcAddress, + NULL, + NULL, + NULL, + CL_DrawParticlesExternal, + R_EnvShot, + pfnSPR_LoadExt, + R_LightVec, + R_StudioGetTexture, + GL_GetOverviewParms, + CL_GenericHandle, + NULL, + NULL, + R_Mem_Alloc, + R_Mem_Free, + pfnGetFilesList, + pfnFileBufferCRC32, + COM_CompareFileTime, + Host_Error, + CL_ModelHandle, + pfnTime, + Cvar_Set, + S_FadeMusicVolume, + COM_SetRandomSeed, +}; + +/* +=============== +R_InitRenderAPI + +Initialize client external rendering +=============== +*/ +qboolean R_InitRenderAPI( void ) +{ + // make sure what render functions is cleared + memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs )); + + if( clgame.dllFuncs.pfnGetRenderInterface ) + { + if( clgame.dllFuncs.pfnGetRenderInterface( CL_RENDER_INTERFACE_VERSION, &gRenderAPI, &clgame.drawFuncs )) + { + MsgDev( D_REPORT, "CL_LoadProgs: ^2initailized extended RenderAPI ^7ver. %i\n", CL_RENDER_INTERFACE_VERSION ); + return true; + } + + // make sure what render functions is cleared + memset( &clgame.drawFuncs, 0, sizeof( clgame.drawFuncs )); + + return false; // just tell user about problems + } + + // render interface is missed + return true; +} \ No newline at end of file diff --git a/engine/client/gl_rmath.c b/engine/client/gl_rmath.c new file mode 100644 index 00000000..d21adfa3 --- /dev/null +++ b/engine/client/gl_rmath.c @@ -0,0 +1,320 @@ +/* +gl_rmath.c - renderer mathlib +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "gl_local.h" +#include "mathlib.h" +#include "client.h" + +/* +==================== +V_CalcFov +==================== +*/ +float V_CalcFov( float *fov_x, float width, float height ) +{ + float x, half_fov_y; + + if( *fov_x < 1.0f || *fov_x > 170.0f ) + { + if( !cls.demoplayback ) + MsgDev( D_ERROR, "V_CalcFov: bad fov %g!\n", *fov_x ); + *fov_x = 90.0f; + } + + x = width / tan( DEG2RAD( *fov_x ) * 0.5f ); + half_fov_y = atan( height / x ); + + return RAD2DEG( half_fov_y ) * 2; +} + +/* +==================== +V_AdjustFov +==================== +*/ +void V_AdjustFov( float *fov_x, float *fov_y, float width, float height, qboolean lock_x ) +{ + float x, y; + + if( width * 3 == 4 * height || width * 4 == height * 5 ) + { + // 4:3 or 5:4 ratio + return; + } + + if( lock_x ) + { + *fov_y = 2 * atan((width * 3) / (height * 4) * tan( *fov_y * M_PI / 360.0 * 0.5 )) * 360 / M_PI; + return; + } + + y = V_CalcFov( fov_x, 640, 480 ); + x = *fov_x; + + *fov_x = V_CalcFov( &y, height, width ); + if( *fov_x < x ) *fov_x = x; + else *fov_y = y; +} + +/* +======================================================================== + + Matrix4x4 operations (private to renderer) + +======================================================================== +*/ +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0] + in1[0][3] * in2[3][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1] + in1[0][3] * in2[3][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2] + in1[0][3] * in2[3][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3] * in2[3][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0] + in1[1][3] * in2[3][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1] + in1[1][3] * in2[3][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2] + in1[1][3] * in2[3][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3] * in2[3][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0] + in1[2][3] * in2[3][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1] + in1[2][3] * in2[3][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2] + in1[2][3] * in2[3][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3] * in2[3][3]; + out[3][0] = in1[3][0] * in2[0][0] + in1[3][1] * in2[1][0] + in1[3][2] * in2[2][0] + in1[3][3] * in2[3][0]; + out[3][1] = in1[3][0] * in2[0][1] + in1[3][1] * in2[1][1] + in1[3][2] * in2[2][1] + in1[3][3] * in2[3][1]; + out[3][2] = in1[3][0] * in2[0][2] + in1[3][1] * in2[1][2] + in1[3][2] * in2[2][2] + in1[3][3] * in2[3][2]; + out[3][3] = in1[3][0] * in2[0][3] + in1[3][1] * in2[1][3] + in1[3][2] * in2[2][3] + in1[3][3] * in2[3][3]; +} + +/* +================ +Matrix4x4_CreateProjection + +NOTE: produce quake style world orientation +================ +*/ +void Matrix4x4_CreateProjection( matrix4x4 out, float xMax, float xMin, float yMax, float yMin, float zNear, float zFar ) +{ + out[0][0] = ( 2.0f * zNear ) / ( xMax - xMin ); + out[1][1] = ( 2.0f * zNear ) / ( yMax - yMin ); + out[2][2] = -( zFar + zNear ) / ( zFar - zNear ); + out[3][3] = out[0][1] = out[1][0] = out[3][0] = out[0][3] = out[3][1] = out[1][3] = 0.0f; + + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[0][2] = ( xMax + xMin ) / ( xMax - xMin ); + out[1][2] = ( yMax + yMin ) / ( yMax - yMin ); + out[3][2] = -1.0f; + out[2][3] = -( 2.0f * zFar * zNear ) / ( zFar - zNear ); +} + +void Matrix4x4_CreateOrtho( matrix4x4 out, float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ) +{ + out[0][0] = 2.0f / (xRight - xLeft); + out[1][1] = 2.0f / (yTop - yBottom); + out[2][2] = -2.0f / (zFar - zNear); + out[3][3] = 1.0f; + out[0][1] = out[0][2] = out[1][0] = out[1][2] = out[3][0] = out[3][1] = out[3][2] = 0.0f; + + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[0][3] = -(xRight + xLeft) / (xRight - xLeft); + out[1][3] = -(yTop + yBottom) / (yTop - yBottom); + out[2][3] = -(zFar + zNear) / (zFar - zNear); +} + +/* +================ +Matrix4x4_CreateModelview + +NOTE: produce quake style world orientation +================ +*/ +void Matrix4x4_CreateModelview( matrix4x4 out ) +{ + out[0][0] = out[1][1] = out[2][2] = 0.0f; + out[3][0] = out[0][3] = 0.0f; + out[3][1] = out[1][3] = 0.0f; + out[3][2] = out[2][3] = 0.0f; + out[3][3] = 1.0f; + out[1][0] = out[0][2] = out[2][1] = 0.0f; + out[2][0] = out[0][1] = -1.0f; + out[1][2] = 1.0f; +} + +void Matrix4x4_ToArrayFloatGL( const matrix4x4 in, float out[16] ) +{ + out[ 0] = in[0][0]; + out[ 1] = in[1][0]; + out[ 2] = in[2][0]; + out[ 3] = in[3][0]; + out[ 4] = in[0][1]; + out[ 5] = in[1][1]; + out[ 6] = in[2][1]; + out[ 7] = in[3][1]; + out[ 8] = in[0][2]; + out[ 9] = in[1][2]; + out[10] = in[2][2]; + out[11] = in[3][2]; + out[12] = in[0][3]; + out[13] = in[1][3]; + out[14] = in[2][3]; + out[15] = in[3][3]; +} + +void Matrix4x4_FromArrayFloatGL( matrix4x4 out, const float in[16] ) +{ + out[0][0] = in[0]; + out[1][0] = in[1]; + out[2][0] = in[2]; + out[3][0] = in[3]; + out[0][1] = in[4]; + out[1][1] = in[5]; + out[2][1] = in[6]; + out[3][1] = in[7]; + out[0][2] = in[8]; + out[1][2] = in[9]; + out[2][2] = in[10]; + out[3][2] = in[11]; + out[0][3] = in[12]; + out[1][3] = in[13]; + out[2][3] = in[14]; + out[3][3] = in[15]; +} + +void Matrix4x4_CreateTranslate( matrix4x4 out, float x, float y, float z ) +{ + out[0][0] = 1.0f; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = x; + out[1][0] = 0.0f; + out[1][1] = 1.0f; + out[1][2] = 0.0f; + out[1][3] = y; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = 1.0f; + out[2][3] = z; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_CreateRotate( matrix4x4 out, float angle, float x, float y, float z ) +{ + float len, c, s; + + len = x * x + y * y + z * z; + if( len != 0.0f ) len = 1.0f / sqrt( len ); + x *= len; + y *= len; + z *= len; + + angle *= (-M_PI / 180.0f); + SinCos( angle, &s, &c ); + + out[0][0]=x * x + c * (1 - x * x); + out[0][1]=x * y * (1 - c) + z * s; + out[0][2]=z * x * (1 - c) - y * s; + out[0][3]=0.0f; + out[1][0]=x * y * (1 - c) - z * s; + out[1][1]=y * y + c * (1 - y * y); + out[1][2]=y * z * (1 - c) + x * s; + out[1][3]=0.0f; + out[2][0]=z * x * (1 - c) + y * s; + out[2][1]=y * z * (1 - c) - x * s; + out[2][2]=z * z + c * (1 - z * z); + out[2][3]=0.0f; + out[3][0]=0.0f; + out[3][1]=0.0f; + out[3][2]=0.0f; + out[3][3]=1.0f; +} + +void Matrix4x4_CreateScale( matrix4x4 out, float x ) +{ + out[0][0] = x; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = 0.0f; + out[1][0] = 0.0f; + out[1][1] = x; + out[1][2] = 0.0f; + out[1][3] = 0.0f; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = x; + out[2][3] = 0.0f; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_CreateScale3( matrix4x4 out, float x, float y, float z ) +{ + out[0][0] = x; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = 0.0f; + out[1][0] = 0.0f; + out[1][1] = y; + out[1][2] = 0.0f; + out[1][3] = 0.0f; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = z; + out[2][3] = 0.0f; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_ConcatTranslate( matrix4x4 out, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateTranslate( temp, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatRotate( matrix4x4 out, float angle, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateRotate( temp, angle, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatScale( matrix4x4 out, float x ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateScale( temp, x ); + Matrix4x4_Concat( out, base, temp ); +} + +void Matrix4x4_ConcatScale3( matrix4x4 out, float x, float y, float z ) +{ + matrix4x4 base, temp; + + Matrix4x4_Copy( base, out ); + Matrix4x4_CreateScale3( temp, x, y, z ); + Matrix4x4_Concat( out, base, temp ); +} \ No newline at end of file diff --git a/engine/client/gl_rmisc.c b/engine/client/gl_rmisc.c new file mode 100644 index 00000000..9868867f --- /dev/null +++ b/engine/client/gl_rmisc.c @@ -0,0 +1,514 @@ +/* +gl_rmisc.c - renderer misceallaneous +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mod_local.h" +#include "shake.h" + +typedef struct +{ + const char *texname; + const char *detail; + const char material; + int lMin; + int lMax; +} dmaterial_t; + +typedef struct +{ + char texname[64]; // shortname + imgfilter_t filter; +} dfilter_t; + +dfilter_t *tex_filters[MAX_TEXTURES]; +int num_texfilters; + +// default rules for apply detail textures. +// maybe move this to external script? +static const dmaterial_t detail_table[] = +{ +{ "crt", "dt_conc", 'C', 0, 0 }, // concrete +{ "rock", "dt_rock1", 'C', 0, 0 }, +{ "conc", "dt_conc", 'C', 0, 0 }, +{ "brick", "dt_brick", 'C', 0, 0 }, +{ "wall", "dt_brick", 'C', 0, 0 }, +{ "city", "dt_conc", 'C', 0, 0 }, +{ "crete", "dt_conc", 'C', 0, 0 }, +{ "generic", "dt_brick", 'C', 0, 0 }, +{ "floor", "dt_conc", 'C', 0, 0 }, +{ "metal", "dt_metal%i", 'M', 1, 2 }, // metal +{ "mtl", "dt_metal%i", 'M', 1, 2 }, +{ "pipe", "dt_metal%i", 'M', 1, 2 }, +{ "elev", "dt_metal%i", 'M', 1, 2 }, +{ "sign", "dt_metal%i", 'M', 1, 2 }, +{ "barrel", "dt_metal%i", 'M', 1, 2 }, +{ "bath", "dt_ssteel1", 'M', 1, 2 }, +{ "tech", "dt_ssteel1", 'M', 1, 2 }, +{ "refbridge", "dt_metal%i", 'M', 1, 2 }, +{ "panel", "dt_ssteel1", 'M', 0, 0 }, +{ "brass", "dt_ssteel1", 'M', 0, 0 }, +{ "rune", "dt_metal%i", 'M', 1, 2 }, +{ "car", "dt_metal%i", 'M', 1, 2 }, +{ "circuit", "dt_metal%i", 'M', 1, 2 }, +{ "steel", "dt_ssteel1", 'M', 0, 0 }, +{ "dirt", "dt_ground%i", 'D', 1, 5 }, // dirt +{ "drt", "dt_ground%i", 'D', 1, 5 }, +{ "out", "dt_ground%i", 'D', 1, 5 }, +{ "grass", "dt_grass1", 'D', 0, 0 }, +{ "mud", "dt_carpet1", 'D', 0, 0 }, +{ "vent", "dt_ssteel1", 'V', 1, 4 }, // vent +{ "duct", "dt_ssteel1", 'V', 1, 4 }, +{ "tile", "dt_smooth%i", 'T', 1, 2 }, +{ "labflr", "dt_smooth%i", 'T', 1, 2 }, +{ "bath", "dt_smooth%i", 'T', 1, 2 }, +{ "grate", "dt_stone%i", 'G', 1, 4 }, // vent +{ "stone", "dt_stone%i", 'G', 1, 4 }, +{ "grt", "dt_stone%i", 'G', 1, 4 }, +{ "wiz", "dt_wood%i", 'W', 1, 3 }, +{ "wood", "dt_wood%i", 'W', 1, 3 }, +{ "wizwood", "dt_wood%i", 'W', 1, 3 }, +{ "wd", "dt_wood%i", 'W', 1, 3 }, +{ "table", "dt_wood%i", 'W', 1, 3 }, +{ "board", "dt_wood%i", 'W', 1, 3 }, +{ "chair", "dt_wood%i", 'W', 1, 3 }, +{ "brd", "dt_wood%i", 'W', 1, 3 }, +{ "carp", "dt_carpet1", 'W', 1, 3 }, +{ "book", "dt_wood%i", 'W', 1, 3 }, +{ "box", "dt_wood%i", 'W', 1, 3 }, +{ "cab", "dt_wood%i", 'W', 1, 3 }, +{ "couch", "dt_wood%i", 'W', 1, 3 }, +{ "crate", "dt_wood%i", 'W', 1, 3 }, +{ "poster", "dt_plaster%i", 'W', 1, 2 }, +{ "sheet", "dt_plaster%i", 'W', 1, 2 }, +{ "stucco", "dt_plaster%i", 'W', 1, 2 }, +{ "comp", "dt_smooth1", 'P', 0, 0 }, +{ "cmp", "dt_smooth1", 'P', 0, 0 }, +{ "elec", "dt_smooth1", 'P', 0, 0 }, +{ "vend", "dt_smooth1", 'P', 0, 0 }, +{ "monitor", "dt_smooth1", 'P', 0, 0 }, +{ "phone", "dt_smooth1", 'P', 0, 0 }, +{ "glass", "dt_ssteel1", 'Y', 0, 0 }, +{ "window", "dt_ssteel1", 'Y', 0, 0 }, +{ "flesh", "dt_rough1", 'F', 0, 0 }, +{ "meat", "dt_rough1", 'F', 0, 0 }, +{ "fls", "dt_rough1", 'F', 0, 0 }, +{ "ground", "dt_ground%i", 'D', 1, 5 }, +{ "gnd", "dt_ground%i", 'D', 1, 5 }, +{ "snow", "dt_snow%i", 'O', 1, 2 }, // snow +{ "wswamp", "dt_smooth1", 'W', 0, 0 }, +{ NULL, NULL, 0, 0, 0 } +}; + +static const char *R_DetailTextureForName( const char *name ) +{ + const dmaterial_t *table; + + if( !name || !*name ) return NULL; + if( !Q_strnicmp( name, "sky", 3 )) + return NULL; // never details for sky + + // never apply details for liquids + if( !Q_strnicmp( name + 1, "!lava", 5 )) + return NULL; + if( !Q_strnicmp( name + 1, "!slime", 6 )) + return NULL; + if( !Q_strnicmp( name, "!cur_90", 7 )) + return NULL; + if( !Q_strnicmp( name, "!cur_0", 6 )) + return NULL; + if( !Q_strnicmp( name, "!cur_270", 8 )) + return NULL; + if( !Q_strnicmp( name, "!cur_180", 8 )) + return NULL; + if( !Q_strnicmp( name, "!cur_up", 7 )) + return NULL; + if( !Q_strnicmp( name, "!cur_dwn", 8 )) + return NULL; + if( name[0] == '!' ) + return NULL; + + // never apply details to the special textures + if( !Q_strnicmp( name, "origin", 6 )) + return NULL; + if( !Q_strnicmp( name, "clip", 4 )) + return NULL; + if( !Q_strnicmp( name, "hint", 4 )) + return NULL; + if( !Q_strnicmp( name, "skip", 4 )) + return NULL; + if( !Q_strnicmp( name, "translucent", 11 )) + return NULL; + if( !Q_strnicmp( name, "3dsky", 5 )) // xash-mod support :-) + return NULL; + if( !Q_strnicmp( name, "scroll", 6 )) + return NULL; + if( name[0] == '@' ) + return NULL; + + // last check ... + if( !Q_strnicmp( name, "null", 4 )) + return NULL; + + for( table = detail_table; table && table->texname; table++ ) + { + if( Q_stristr( name, table->texname )) + { + if(( table->lMin + table->lMax ) > 0 ) + return va( table->detail, COM_RandomLong( table->lMin, table->lMax )); + return table->detail; + } + } + + return NULL; +} + +void R_CreateDetailTexturesList( const char *filename ) +{ + file_t *detail_txt = NULL; + float xScale, yScale; + const char *detail_name; + texture_t *tex; + rgbdata_t *pic; + int i; + + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + tex = cl.worldmodel->textures[i]; + detail_name = R_DetailTextureForName( tex->name ); + if( !detail_name ) continue; + + // detailtexture detected + if( detail_name ) + { + if( !detail_txt ) detail_txt = FS_Open( filename, "w", false ); + if( !detail_txt ) + { + MsgDev( D_ERROR, "Can't write %s\n", filename ); + break; + } + + pic = FS_LoadImage( va( "gfx/detail/%s", detail_name ), NULL, 0 ); + + if( pic ) + { + xScale = (pic->width / (float)tex->width) * gl_detailscale->value; + yScale = (pic->height / (float)tex->height) * gl_detailscale->value; + FS_FreeImage( pic ); + } + else xScale = yScale = 10.0f; + + // store detailtexture description + FS_Printf( detail_txt, "%s detail/%s %.2f %.2f\n", tex->name, detail_name, xScale, yScale ); + } + } + + if( detail_txt ) FS_Close( detail_txt ); +} + +void R_ParseDetailTextures( const char *filename ) +{ + char *afile, *pfile; + string token, texname; + string detail_texname; + string detail_path; + float xScale, yScale; + texture_t *tex; + int i; + + if( r_detailtextures->value >= 2 && !FS_FileExists( filename, false )) + { + // use built-in generator for detail textures + R_CreateDetailTexturesList( filename ); + } + + afile = FS_LoadFile( filename, NULL, false ); + if( !afile ) return; + + pfile = afile; + + // format: 'texturename' 'detailtexture' 'xScale' 'yScale' + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + texname[0] = '\0'; + detail_texname[0] = '\0'; + + // read texname + if( token[0] == '{' ) + { + // NOTE: COM_ParseFile handled some symbols seperately + // this code will be fix it + pfile = COM_ParseFile( pfile, token ); + Q_strncat( texname, "{", sizeof( texname )); + Q_strncat( texname, token, sizeof( texname )); + } + else Q_strncpy( texname, token, sizeof( texname )); + + // read detailtexture name + pfile = COM_ParseFile( pfile, token ); + Q_strncat( detail_texname, token, sizeof( detail_texname )); + + // trying the scales or '{' + pfile = COM_ParseFile( pfile, token ); + + // read second part of detailtexture name + if( token[0] == '{' ) + { + Q_strncat( detail_texname, token, sizeof( detail_texname )); + pfile = COM_ParseFile( pfile, token ); // read scales + Q_strncat( detail_texname, token, sizeof( detail_texname )); + pfile = COM_ParseFile( pfile, token ); // parse scales + } + + Q_snprintf( detail_path, sizeof( detail_path ), "gfx/%s", detail_texname ); + + // read scales + xScale = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + yScale = Q_atof( token ); + + if( xScale <= 0.0f || yScale <= 0.0f ) + continue; + + // search for existing texture and uploading detail texture + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + tex = cl.worldmodel->textures[i]; + + if( Q_stricmp( tex->name, texname )) + continue; + + tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR, NULL ); + + // texture is loaded + if( tex->dt_texturenum ) + { + gltexture_t *glt; + + glt = R_GetTexture( tex->gl_texturenum ); + glt->xscale = xScale; + glt->yscale = yScale; + } + break; + } + } + + Mem_Free( afile ); +} + +void R_ParseTexFilters( const char *filename ) +{ + char *afile, *pfile; + string token, texname; + dfilter_t *tf; + int i; + + afile = FS_LoadFile( filename, NULL, false ); + if( !afile ) return; + + pfile = afile; + + // format: 'texturename' 'filtername' 'factor' 'bias' 'blendmode' 'grayscale' + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + imgfilter_t filter; + + memset( &filter, 0, sizeof( filter )); + Q_strncpy( texname, token, sizeof( texname )); + + // parse filter + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "blur" )) + filter.filter = BLUR_FILTER; + else if( !Q_stricmp( token, "blur2" )) + filter.filter = BLUR_FILTER2; + else if( !Q_stricmp( token, "edge" )) + filter.filter = EDGE_FILTER; + else if( !Q_stricmp( token, "emboss" )) + filter.filter = EMBOSS_FILTER; + + // reading factor + pfile = COM_ParseFile( pfile, token ); + filter.factor = Q_atof( token ); + + // reading bias + pfile = COM_ParseFile( pfile, token ); + filter.bias = Q_atof( token ); + + // reading blendFunc + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "modulate" ) || !Q_stricmp( token, "GL_MODULATE" )) + filter.blendFunc = GL_MODULATE; + else if( !Q_stricmp( token, "replace" ) || !Q_stricmp( token, "GL_REPLACE" )) + filter.blendFunc = GL_REPLACE; + else if( !Q_stricmp( token, "add" ) || !Q_stricmp( token, "GL_ADD" )) + filter.blendFunc = GL_ADD; + else if( !Q_stricmp( token, "decal" ) || !Q_stricmp( token, "GL_DECAL" )) + filter.blendFunc = GL_DECAL; + else if( !Q_stricmp( token, "blend" ) || !Q_stricmp( token, "GL_BLEND" )) + filter.blendFunc = GL_BLEND; + else if( !Q_stricmp( token, "add_signed" ) || !Q_stricmp( token, "GL_ADD_SIGNED" )) + filter.blendFunc = GL_ADD_SIGNED; + else MsgDev( D_WARN, "unknown blendFunc '%s' specified for texture '%s'\n", texname, token ); + + // reading flags + pfile = COM_ParseFile( pfile, token ); + filter.flags = Q_atoi( token ); + + // make sure what factor is not zeroed + if( filter.factor == 0.0f ) + { + MsgDev( D_WARN, "texfilter for texture %s has factor 0! Ignored\n", texname ); + continue; + } + + // check if already existed + for( i = 0; i < num_texfilters; i++ ) + { + tf = tex_filters[i]; + + if( !Q_stricmp( tf->texname, texname )) + { + MsgDev( D_WARN, "texture %s has specified multiple filters! Ignored\n", texname ); + break; + } + } + + if( i != num_texfilters ) + continue; // already specified + + // allocate new texfilter + tf = Z_Malloc( sizeof( dfilter_t )); + tex_filters[num_texfilters++] = tf; + + Q_strncpy( tf->texname, texname, sizeof( tf->texname )); + tf->filter = filter; + } + + MsgDev( D_INFO, "%i texture filters parsed\n", num_texfilters ); + + Mem_Free( afile ); +} + +imgfilter_t *R_FindTexFilter( const char *texname ) +{ + dfilter_t *tf; + int i; + + for( i = 0; i < num_texfilters; i++ ) + { + tf = tex_filters[i]; + + if( !Q_stricmp( tf->texname, texname )) + return &tf->filter; + } + + return NULL; +} + +/* +======================= +R_ClearStaticEntities + +e.g. by demo request +======================= +*/ +void R_ClearStaticEntities( void ) +{ + int i; + + if( host.type == HOST_DEDICATED ) + return; + + // clear out efrags in case the level hasn't been reloaded + for( i = 0; i < cl.worldmodel->numleafs; i++ ) + cl.worldmodel->leafs[i+1].efrags = NULL; + + clgame.numStatics = 0; + + CL_ClearEfrags (); +} + +void R_NewMap( void ) +{ + texture_t *tx; + int i; + + R_ClearDecals(); // clear all level decals + + // upload detailtextures + if( r_detailtextures->value ) + { + string mapname, filepath; + + Q_strncpy( mapname, cl.worldmodel->name, sizeof( mapname )); + COM_StripExtension( mapname ); + Q_sprintf( filepath, "%s_detail.txt", mapname ); + + R_ParseDetailTextures( filepath ); + } + + if( v_dark->value ) + { + screenfade_t *sf = &clgame.fade; + client_textmessage_t *title; + + title = CL_TextMessageGet( "GAMETITLE" ); + + if( title ) + { + // get settings from titles.txt + sf->fadeEnd = title->holdtime + title->fadeout; + sf->fadeReset = title->fadeout; + } + else sf->fadeEnd = sf->fadeReset = 5.0f; + + sf->fadeFlags = FFADE_IN; + sf->fader = sf->fadeg = sf->fadeb = 0; + sf->fadealpha = 255; + sf->fadeSpeed = (float)sf->fadealpha / sf->fadeReset; + sf->fadeReset += cl.time; + sf->fadeEnd += sf->fadeReset; + + Cvar_SetValue( "v_dark", 0.0f ); + } + + // clear out efrags in case the level hasn't been reloaded + for( i = 0; i < cl.worldmodel->numleafs; i++ ) + cl.worldmodel->leafs[i+1].efrags = NULL; + + tr.skytexturenum = -1; + pglDisable( GL_FOG ); + + // clearing texture chains + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + if( !cl.worldmodel->textures[i] ) + continue; + + tx = cl.worldmodel->textures[i]; + + if( !Q_strncmp( tx->name, "sky", 3 ) && tx->width == 256 && tx->height == 128 ) + tr.skytexturenum = i; + + tx->texturechain = NULL; + } + + R_SetupSky( clgame.movevars.skyName ); + + GL_BuildLightmaps (); +} \ No newline at end of file diff --git a/engine/client/gl_rpart.c b/engine/client/gl_rpart.c new file mode 100644 index 00000000..44552b80 --- /dev/null +++ b/engine/client/gl_rpart.c @@ -0,0 +1,1667 @@ +/* +cl_part.c - particles and tracers +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "r_efx.h" +#include "event_flags.h" +#include "entity_types.h" +#include "triangleapi.h" +#include "pm_local.h" +#include "cl_tent.h" +#include "studio.h" + +#define PART_SIZE Q_max( 0.5f, cl_draw_particles->value ) + +/* +============================================================== + +PARTICLES MANAGEMENT + +============================================================== +*/ +// particle ramps +static int ramp1[8] = { 0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61 }; +static int ramp2[8] = { 0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66 }; +static int ramp3[6] = { 0x6d, 0x6b, 6, 5, 4, 3 }; +static float gTracerSize[11] = { 1.5f, 0.5f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f }; +static int gSparkRamp[9] = { 0xfe, 0xfd, 0xfc, 0x6f, 0x6e, 0x6d, 0x6c, 0x67, 0x60 }; + +static color24 gTracerColors[] = +{ +{ 255, 255, 255 }, // White +{ 255, 0, 0 }, // Red +{ 0, 255, 0 }, // Green +{ 0, 0, 255 }, // Blue +{ 0, 0, 0 }, // Tracer default, filled in from cvars, etc. +{ 255, 167, 17 }, // Yellow-orange sparks +{ 255, 130, 90 }, // Yellowish streaks (garg) +{ 55, 60, 144 }, // Blue egon streak +{ 255, 130, 90 }, // More Yellowish streaks (garg) +{ 255, 140, 90 }, // More Yellowish streaks (garg) +{ 200, 130, 90 }, // More red streaks (garg) +{ 255, 120, 70 }, // Darker red streaks (garg) +}; + +convar_t *tracerred; +convar_t *tracergreen; +convar_t *tracerblue; +convar_t *traceralpha; +convar_t *tracerspeed; +convar_t *tracerlength; +convar_t *traceroffset; + +particle_t *cl_active_particles; +particle_t *cl_active_tracers; +particle_t *cl_free_particles; +particle_t *cl_particles = NULL; // particle pool +static vec3_t cl_avelocities[NUMVERTEXNORMALS]; +static float cl_lasttimewarn = 0.0f; + +/* +================ +R_LookupColor + +find nearest color in particle palette +================ +*/ +short R_LookupColor( byte r, byte g, byte b ) +{ + int i, best; + float diff, bestdiff; + float rf, gf, bf; + + bestdiff = 999999; + best = 65535; + + for( i = 0; i < 256; i++ ) + { + rf = r - clgame.palette[i].r; + gf = g - clgame.palette[i].g; + bf = b - clgame.palette[i].b; + + // convert color to monochrome + diff = rf * (rf * 0.2) + gf * (gf * 0.5) + bf * (bf * 0.3); + + if ( diff < bestdiff ) + { + bestdiff = diff; + best = i; + } + } + + return best; +} + +/* +================ +R_GetPackedColor + +in hardware mode does nothing +================ +*/ +void R_GetPackedColor( short *packed, short color ) +{ + if( packed ) *packed = 0; +} + +/* +================ +CL_InitParticles + +================ +*/ +void CL_InitParticles( void ) +{ + int i; + + cl_particles = Mem_Alloc( cls.mempool, sizeof( particle_t ) * GI->max_particles ); + CL_ClearParticles (); + + // this is used for EF_BRIGHTFIELD + for( i = 0; i < NUMVERTEXNORMALS; i++ ) + { + cl_avelocities[i][0] = COM_RandomFloat( 0.0f, 2.55f ); + cl_avelocities[i][1] = COM_RandomFloat( 0.0f, 2.55f ); + cl_avelocities[i][2] = COM_RandomFloat( 0.0f, 2.55f ); + } + + tracerred = Cvar_Get( "tracerred", "0.8", 0, "tracer red component weight ( 0 - 1.0 )" ); + tracergreen = Cvar_Get( "tracergreen", "0.8", 0, "tracer green component weight ( 0 - 1.0 )" ); + tracerblue = Cvar_Get( "tracerblue", "0.4", 0, "tracer blue component weight ( 0 - 1.0 )" ); + traceralpha = Cvar_Get( "traceralpha", "0.5", 0, "tracer alpha amount ( 0 - 1.0 )" ); + tracerspeed = Cvar_Get( "tracerspeed", "6000", 0, "tracer speed" ); + tracerlength = Cvar_Get( "tracerlength", "0.8", 0, "tracer length factor" ); + traceroffset = Cvar_Get( "traceroffset", "30", 0, "tracer starting offset" ); +} + +/* +================ +CL_ClearParticles + +================ +*/ +void CL_ClearParticles( void ) +{ + int i; + + if( !cl_particles ) return; + + cl_free_particles = cl_particles; + cl_active_particles = NULL; + cl_active_tracers = NULL; + + for( i = 0; i < GI->max_particles - 1; i++ ) + cl_particles[i].next = &cl_particles[i+1]; + + cl_particles[GI->max_particles-1].next = NULL; +} + +/* +================ +CL_FreeParticles + +================ +*/ +void CL_FreeParticles( void ) +{ + if( cl_particles ) + Mem_Free( cl_particles ); + cl_particles = NULL; +} + +/* +================ +CL_FreeParticle + +move particle to freelist +================ +*/ +void CL_FreeParticle( particle_t *p ) +{ + if( p->deathfunc ) + { + // call right the deathfunc before die + p->deathfunc( p ); + p->deathfunc = NULL; + } + + p->next = cl_free_particles; + cl_free_particles = p; +} + +/* +================ +R_AllocParticle + +can return NULL if particles is out +================ +*/ +particle_t *R_AllocParticle( void (*callback)( particle_t*, float )) +{ + particle_t *p; + + if( !cl_draw_particles->value ) + return NULL; + + // never alloc particles when we not in game + if( tr.frametime == 0.0 ) return NULL; + + if( !cl_free_particles ) + { + if( cl_lasttimewarn < host.realtime ) + { + // don't spam about overflow + MsgDev( D_ERROR, "Overflow %d particles\n", GI->max_particles ); + cl_lasttimewarn = host.realtime + 1.0f; + } + return NULL; + } + + p = cl_free_particles; + cl_free_particles = p->next; + p->next = cl_active_particles; + cl_active_particles = p; + + // clear old particle + p->type = pt_static; + VectorClear( p->vel ); + VectorClear( p->org ); + p->packedColor = 0; + p->die = cl.time; + p->color = 0; + p->ramp = 0; + + if( callback ) + { + p->type = pt_clientcustom; + p->callback = callback; + } + + return p; +} + +/* +================ +R_AllocTracer + +can return NULL if particles is out +================ +*/ +particle_t *R_AllocTracer( const vec3_t org, const vec3_t vel, float life ) +{ + particle_t *p; + + if( !cl_draw_tracers->value ) + return NULL; + + // never alloc particles when we not in game + if( tr.frametime == 0.0 ) return NULL; + + if( !cl_free_particles ) + { + if( cl_lasttimewarn < host.realtime ) + { + // don't spam about overflow + MsgDev( D_ERROR, "Overflow %d tracers\n", GI->max_particles ); + cl_lasttimewarn = host.realtime + 1.0f; + } + return NULL; + } + + p = cl_free_particles; + cl_free_particles = p->next; + p->next = cl_active_tracers; + cl_active_tracers = p; + + // clear old particle + p->type = pt_static; + VectorCopy( org, p->org ); + VectorCopy( vel, p->vel ); + p->die = cl.time + life; + p->ramp = tracerlength->value; + p->color = 4; // select custom color + p->packedColor = 255; // alpha + + return p; +} + +/* +============== +R_FreeDeadParticles + +Free particles that time has expired +============== +*/ +void R_FreeDeadParticles( particle_t **ppparticles ) +{ + particle_t *p, *kill; + + // kill all the ones hanging direcly off the base pointer + while( 1 ) + { + kill = *ppparticles; + if( kill && kill->die < cl.time ) + { + if( kill->deathfunc ) + kill->deathfunc( kill ); + kill->deathfunc = NULL; + *ppparticles = kill->next; + kill->next = cl_free_particles; + cl_free_particles = kill; + continue; + } + break; + } + + // kill off all the others + for( p = *ppparticles; p; p = p->next ) + { + while( 1 ) + { + kill = p->next; + if( kill && kill->die < cl.time ) + { + if( kill->deathfunc ) + kill->deathfunc( kill ); + kill->deathfunc = NULL; + p->next = kill->next; + kill->next = cl_free_particles; + cl_free_particles = kill; + continue; + } + break; + } + } +} + +/* +================ +CL_DrawParticles + +update particle color, position, free expired and draw it +================ +*/ +void CL_DrawParticles( double frametime ) +{ + particle_t *p; + float time3 = 15.0f * frametime; + float time2 = 10.0f * frametime; + float time1 = 5.0f * frametime; + float dvel = 4.0f * frametime; + float grav = frametime * clgame.movevars.gravity * 0.05f; + vec3_t right, up; + color24 *pColor; + int alpha; + float size; + + if( !cl_draw_particles->value ) + return; + + R_FreeDeadParticles( &cl_active_particles ); + + if( !cl_active_particles ) + return; // nothing to draw? + + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + GL_Bind( GL_TEXTURE0, tr.particleTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDepthMask( GL_FALSE ); + + pglBegin( GL_QUADS ); + + for( p = cl_active_particles; p; p = p->next ) + { + if(( p->type != pt_blob ) || ( p->packedColor == 255 )) + { + size = PART_SIZE; // get initial size of particle + + // scale up to keep particles from disappearing + size += (p->org[0] - RI.vieworg[0]) * RI.cull_vforward[0]; + size += (p->org[1] - RI.vieworg[1]) * RI.cull_vforward[1]; + size += (p->org[2] - RI.vieworg[2]) * RI.cull_vforward[2]; + + if( size < 20.0f ) size = PART_SIZE; + else size = PART_SIZE + size * 0.002f; + + // scale the axes by radius + VectorScale( RI.cull_vright, size, right ); + VectorScale( RI.cull_vup, size, up ); + + p->color = bound( 0, p->color, 255 ); + pColor = &clgame.palette[p->color]; + + alpha = 255 * (p->die - cl.time) * 16.0f; + if( alpha > 255 || p->type == pt_static ) + alpha = 255; + + pglColor4ub( LightToTexGamma( pColor->r ), LightToTexGamma( pColor->g ), LightToTexGamma( pColor->b ), alpha ); + + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex3f( p->org[0] - right[0] + up[0], p->org[1] - right[1] + up[1], p->org[2] - right[2] + up[2] ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3f( p->org[0] + right[0] + up[0], p->org[1] + right[1] + up[1], p->org[2] + right[2] + up[2] ); + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3f( p->org[0] + right[0] - up[0], p->org[1] + right[1] - up[1], p->org[2] + right[2] - up[2] ); + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3f( p->org[0] - right[0] - up[0], p->org[1] - right[1] - up[1], p->org[2] - right[2] - up[2] ); + r_stats.c_particle_count++; + } + + if( p->type != pt_clientcustom ) + { + // update position. + VectorMA( p->org, frametime, p->vel, p->org ); + } + + switch( p->type ) + { + case pt_static: + break; + case pt_fire: + p->ramp += time1; + if( p->ramp >= 6.0f ) p->die = -1.0f; + else p->color = ramp3[(int)p->ramp]; + p->vel[2] += grav; + break; + case pt_explode: + p->ramp += time2; + if( p->ramp >= 8.0f ) p->die = -1.0f; + else p->color = ramp1[(int)p->ramp]; + VectorMA( p->vel, dvel, p->vel, p->vel ); + p->vel[2] -= grav; + break; + case pt_explode2: + p->ramp += time3; + if( p->ramp >= 8.0f ) p->die = -1.0f; + else p->color = ramp2[(int)p->ramp]; + VectorMA( p->vel,-frametime, p->vel, p->vel ); + p->vel[2] -= grav; + break; + case pt_blob: + if( p->packedColor == 255 ) + { + // normal blob explosion + VectorMA( p->vel, dvel, p->vel, p->vel ); + p->vel[2] -= grav; + break; + } + case pt_blob2: + if( p->packedColor == 255 ) + { + // normal blob explosion + p->vel[0] -= p->vel[0] * dvel; + p->vel[1] -= p->vel[1] * dvel; + p->vel[2] -= grav; + } + else + { + p->ramp += time2; + if( p->ramp >= 9.0f ) p->ramp = 0.0f; + p->color = gSparkRamp[(int)p->ramp]; + VectorMA( p->vel, -frametime * 0.5f, p->vel, p->vel ); + p->type = COM_RandomLong( 0, 3 ) ? pt_blob : pt_blob2; + p->vel[2] -= grav * 5.0f; + } + break; + case pt_grav: + p->vel[2] -= grav * 20.0f; + break; + case pt_slowgrav: + p->vel[2] -= grav; + break; + case pt_vox_grav: + p->vel[2] -= grav * 8.0f; + break; + case pt_vox_slowgrav: + p->vel[2] -= grav * 4.0f; + break; + case pt_clientcustom: + if( p->callback ) + p->callback( p, frametime ); + break; + } + } + + pglEnd(); + pglDepthMask( GL_TRUE ); +} + +/* +================ +CL_CullTracer + +check tracer bbox +================ +*/ +static qboolean CL_CullTracer( particle_t *p, const vec3_t start, const vec3_t end ) +{ + vec3_t mins, maxs; + int i; + + // compute the bounding box + for( i = 0; i < 3; i++ ) + { + if( start[i] < end[i] ) + { + mins[i] = start[i]; + maxs[i] = end[i]; + } + else + { + mins[i] = end[i]; + maxs[i] = start[i]; + } + + // don't let it be zero sized + if( mins[i] == maxs[i] ) + { + maxs[i] += gTracerSize[p->type] * 2.0f; + } + } + + // check bbox + return R_CullBox( mins, maxs ); +} + +/* +================ +CL_DrawTracers + +update tracer color, position, free expired and draw it +================ +*/ +void CL_DrawTracers( double frametime ) +{ + float scale, atten, gravity; + vec3_t screenLast, screen; + vec3_t start, end, delta; + particle_t *p; + + if( !cl_draw_tracers->value ) + return; + + // update tracer color if this is changed + if( FBitSet( tracerred->flags|tracergreen->flags|tracerblue->flags|traceralpha->flags, FCVAR_CHANGED )) + { + gTracerColors[4].r = (byte)(tracerred->value * traceralpha->value * 255); + gTracerColors[4].g = (byte)(tracergreen->value * traceralpha->value * 255); + gTracerColors[4].b = (byte)(tracerblue->value * traceralpha->value * 255); + ClearBits( tracerred->flags, FCVAR_CHANGED ); + ClearBits( tracergreen->flags, FCVAR_CHANGED ); + ClearBits( tracerblue->flags, FCVAR_CHANGED ); + ClearBits( traceralpha->flags, FCVAR_CHANGED ); + } + + R_FreeDeadParticles( &cl_active_tracers ); + + if( !cl_active_tracers ) + return; // nothing to draw? + + if( !TriSpriteTexture( cl_sprite_dot, 0 )) + return; + + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + pglDisable( GL_ALPHA_TEST ); + pglDepthMask( GL_FALSE ); + + gravity = frametime * clgame.movevars.gravity; + scale = 1.0 - (frametime * 0.9); + if( scale < 0.0f ) scale = 0.0f; + + for( p = cl_active_tracers; p; p = p->next ) + { + atten = (p->die - cl.time); + if( atten > 0.1f ) atten = 0.1f; + + VectorScale( p->vel, ( p->ramp * atten ), delta ); + VectorAdd( p->org, delta, end ); + VectorCopy( p->org, start ); + + if( !CL_CullTracer( p, start, end )) + { + vec3_t verts[4], tmp2; + vec3_t tmp, normal; + color24 *pColor; + + // Transform point into screen space + TriWorldToScreen( start, screen ); + TriWorldToScreen( end, screenLast ); + + // build world-space normal to screen-space direction vector + VectorSubtract( screen, screenLast, tmp ); + + // we don't need Z, we're in screen space + tmp[2] = 0; + VectorNormalize( tmp ); + + // build point along noraml line (normal is -y, x) + VectorScale( RI.cull_vup, tmp[0] * gTracerSize[p->type], normal ); + VectorScale( RI.cull_vright, -tmp[1] * gTracerSize[p->type], tmp2 ); + VectorSubtract( normal, tmp2, normal ); + + // compute four vertexes + VectorSubtract( start, normal, verts[0] ); + VectorAdd( start, normal, verts[1] ); + VectorAdd( verts[0], delta, verts[2] ); + VectorAdd( verts[1], delta, verts[3] ); + + pColor = &gTracerColors[p->color]; + pglColor4ub( pColor->r, pColor->g, pColor->b, p->packedColor ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 0.8f ); + pglVertex3fv( verts[2] ); + pglTexCoord2f( 1.0f, 0.8f ); + pglVertex3fv( verts[3] ); + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3fv( verts[1] ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3fv( verts[0] ); + pglEnd(); + } + + // evaluate position + VectorMA( p->org, frametime, p->vel, p->org ); + + if( p->type == pt_grav ) + { + p->vel[0] *= scale; + p->vel[1] *= scale; + p->vel[2] -= gravity; + + p->packedColor = 255 * (p->die - cl.time) * 2; + if( p->packedColor > 255 ) p->packedColor = 255; + } + else if( p->type == pt_slowgrav ) + { + p->vel[2] = gravity * 0.05; + } + } + + pglDepthMask( GL_TRUE ); +} + +/* +=============== +CL_DrawParticlesExternal + +allow to draw effects from custom renderer +=============== +*/ +void CL_DrawParticlesExternal( const ref_viewpass_t *rvp, qboolean trans_pass, float frametime ) +{ + ref_instance_t oldRI = RI; + + memcpy( &oldRI, &RI, sizeof( ref_instance_t )); + R_SetupRefParams( rvp ); + R_SetupFrustum(); + R_SetupGL( false ); // don't touch GL-states + + // setup PVS for frame + memcpy( RI.visbytes, tr.visbytes, world.visbytes ); + tr.frametime = frametime; + + if( trans_pass == false ) + { + CL_DrawBeams( false ); + } + else + { + CL_DrawBeams( true ); + CL_DrawParticles( tr.frametime ); + CL_DrawTracers( tr.frametime ); + } + + // restore internal state + memcpy( &RI, &oldRI, sizeof( ref_instance_t )); +} + +/* +=============== +R_EntityParticles + +set EF_BRIGHTFIELD effect +=============== +*/ +void R_EntityParticles( cl_entity_t *ent ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + vec3_t forward; + particle_t *p; + int i; + + for( i = 0; i < NUMVERTEXNORMALS; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + angle = cl.time * cl_avelocities[i][0]; + SinCos( angle, &sy, &cy ); + angle = cl.time * cl_avelocities[i][1]; + SinCos( angle, &sp, &cp ); + angle = cl.time * cl_avelocities[i][2]; + SinCos( angle, &sr, &cr ); + + VectorSet( forward, cp * cy, cp * sy, -sp ); + + p->die = cl.time + 0.001f; + p->color = 111; // yellow + + VectorMAMAM( 1.0f, ent->origin, 64.0f, m_bytenormals[i], 16.0f, forward, p->org ); + } +} + +/* +=============== +R_ParticleExplosion + +=============== +*/ +void R_ParticleExplosion( const vec3_t org ) +{ + particle_t *p; + int i, j; + + for( i = 0; i < 1024; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 5.0f; + p->ramp = COM_RandomLong( 0, 3 ); + p->color = ramp1[0]; + + for( j = 0; j < 3; j++ ) + { + p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); + p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); + } + + if( i & 1 ) p->type = pt_explode; + else p->type = pt_explode2; + } +} + +/* +=============== +R_ParticleExplosion2 + +=============== +*/ +void R_ParticleExplosion2( const vec3_t org, int colorStart, int colorLength ) +{ + int i, j; + int colorMod = 0; + particle_t *p; + + for( i = 0; i < 512; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 0.3f; + p->color = colorStart + ( colorMod % colorLength ); + p->packedColor = 255; // use old code for blob particles + colorMod++; + + p->type = pt_blob; + + for( j = 0; j < 3; j++ ) + { + p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); + p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); + } + } +} + +/* +=============== +R_BlobExplosion + +=============== +*/ +void R_BlobExplosion( const vec3_t org ) +{ + particle_t *p; + int i, j; + + for( i = 0; i < 1024; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + COM_RandomFloat( 2.0f, 2.4f ); + p->packedColor = 255; // use old code for blob particles + + if( i & 1 ) + { + p->type = pt_blob; + p->color = COM_RandomLong( 66, 71 ); + } + else + { + p->type = pt_blob2; + p->color = COM_RandomLong( 150, 155 ); + } + + for( j = 0; j < 3; j++ ) + { + p->org[j] = org[j] + COM_RandomFloat( -16.0f, 16.0f ); + p->vel[j] = COM_RandomFloat( -256.0f, 256.0f ); + } + } +} + +/* +=============== +ParticleEffect + +PARTICLE_EFFECT on server +=============== +*/ +void R_RunParticleEffect( const vec3_t org, const vec3_t dir, int color, int count ) +{ + particle_t *p; + int i; + + if( count == 1024 ) + { + // rocket explosion + R_ParticleExplosion( org ); + return; + } + + for( i = 0; i < count; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->color = (color & ~7) + COM_RandomLong( 0, 7 ); + p->die = cl.time + COM_RandomFloat( 0.1f, 0.4f ); + p->type = pt_slowgrav; + + VectorAddScalar( org, COM_RandomFloat( -8.0f, 8.0f ), p->org ); + VectorScale( dir, 15.0f, p->vel ); + } +} + +/* +=============== +R_Blood + +particle spray +=============== +*/ +void R_Blood( const vec3_t org, const vec3_t ndir, int pcolor, int speed ) +{ + vec3_t pos, dir, vec; + float pspeed = speed * 3.0f; + int i, j; + particle_t *p; + + VectorNormalize2( ndir, dir ); + + for( i = 0; i < (speed / 2); i++ ) + { + VectorAddScalar( org, COM_RandomFloat( -3.0f, 3.0f ), pos ); + VectorAddScalar( dir, COM_RandomFloat( -0.06f, 0.06f ), vec ); + + for( j = 0; j < 7; j++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 1.5f; + p->color = pcolor + COM_RandomLong( 0, 9 ); + p->type = pt_vox_grav; + + VectorAddScalar( pos, COM_RandomFloat( -1.0f, 1.0f ), p->org ); + VectorScale( vec, pspeed, p->vel ); + } + } +} + +/* +=============== +R_BloodStream + +particle spray 2 +=============== +*/ +void R_BloodStream( const vec3_t org, const vec3_t dir, int pcolor, int speed ) +{ + particle_t *p; + int i, j; + float arc; + float accel = speed; + + for( arc = 0.05f, i = 0; i < 100; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 2.0f; + p->type = pt_vox_grav; + p->color = pcolor + COM_RandomLong( 0, 9 ); + + VectorCopy( org, p->org ); + VectorCopy( dir, p->vel ); + + p->vel[2] -= arc; + arc -= 0.005f; + VectorScale( p->vel, accel, p->vel ); + accel -= 0.00001f; // so last few will drip + } + + for( arc = 0.075f, i = 0; i < ( speed / 5 ); i++ ) + { + float num; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 3.0f; + p->color = pcolor + COM_RandomLong( 0, 9 ); + p->type = pt_vox_slowgrav; + + VectorCopy( org, p->org ); + VectorCopy( dir, p->vel ); + + p->vel[2] -= arc; + arc -= 0.005f; + + num = COM_RandomFloat( 0.0f, 1.0f ); + accel = speed * num; + num *= 1.7f; + + VectorScale( p->vel, num, p->vel ); + VectorScale( p->vel, accel, p->vel ); + + for( j = 0; j < 2; j++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 3.0f; + p->color = pcolor + COM_RandomLong( 0, 9 ); + p->type = pt_vox_slowgrav; + + p->org[0] = org[0] + COM_RandomFloat( -1.0f, 1.0f ); + p->org[1] = org[1] + COM_RandomFloat( -1.0f, 1.0f ); + p->org[2] = org[2] + COM_RandomFloat( -1.0f, 1.0f ); + + VectorCopy( dir, p->vel ); + p->vel[2] -= arc; + + VectorScale( p->vel, num, p->vel ); + VectorScale( p->vel, accel, p->vel ); + } + } +} + +/* +=============== +R_LavaSplash + +=============== +*/ +void R_LavaSplash( const vec3_t org ) +{ + particle_t *p; + float vel; + vec3_t dir; + int i, j, k; + + for( i = -16; i < 16; i++ ) + { + for( j = -16; j <16; j++ ) + { + for( k = 0; k < 1; k++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + COM_RandomFloat( 2.0f, 2.62f ); + p->color = COM_RandomLong( 224, 231 ); + p->type = pt_slowgrav; + + dir[0] = j * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); + dir[1] = i * 8.0f + COM_RandomFloat( 0.0f, 7.0f ); + dir[2] = 256.0f; + + p->org[0] = org[0] + dir[0]; + p->org[1] = org[1] + dir[1]; + p->org[2] = org[2] + COM_RandomFloat( 0.0f, 63.0f ); + + VectorNormalize( dir ); + vel = COM_RandomFloat( 50.0f, 113.0f ); + VectorScale( dir, vel, p->vel ); + } + } + } +} + +/* +=============== +R_ParticleBurst + +=============== +*/ +void R_ParticleBurst( const vec3_t org, int size, int color, float life ) +{ + particle_t *p; + vec3_t dir, dest; + int i, j; + float dist; + + for( i = 0; i < 32; i++ ) + { + for( j = 0; j < 32; j++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + life + COM_RandomFloat( -0.5f, 0.5f ); + p->color = color + COM_RandomLong( 0, 10 ); + p->ramp = 1.0f; + + VectorCopy( org, p->org ); + VectorAddScalar( org, COM_RandomFloat( -size, size ), dest ); + VectorSubtract( dest, p->org, dir ); + dist = VectorNormalizeLength( dir ); + VectorScale( dir, ( dist / life ), p->vel ); + } + } +} + +/* +=============== +R_LargeFunnel + +=============== +*/ +void R_LargeFunnel( const vec3_t org, int reverse ) +{ + particle_t *p; + float vel, dist; + vec3_t dir, dest; + int i, j; + + for( i = -8; i < 8; i++ ) + { + for( j = -8; j < 8; j++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + dest[0] = (i * 32.0f) + org[0]; + dest[1] = (j * 32.0f) + org[1]; + dest[2] = org[2] + COM_RandomFloat( 100.0f, 800.0f ); + + if( reverse ) + { + VectorCopy( org, p->org ); + VectorSubtract( dest, p->org, dir ); + } + else + { + VectorCopy( dest, p->org ); + VectorSubtract( org, p->org, dir ); + } + + vel = dest[2] / 8.0f; + if( vel < 64.0f ) vel = 64.0f; + + dist = VectorNormalizeLength( dir ); + vel += COM_RandomFloat( 64.0f, 128.0f ); + VectorScale( dir, vel, p->vel ); + p->die = cl.time + (dist / vel ); + p->color = 244; // green color + } + } +} + +/* +=============== +R_TeleportSplash + +=============== +*/ +void R_TeleportSplash( const vec3_t org ) +{ + particle_t *p; + vec3_t dir; + float vel; + int i, j, k; + + for( i = -16; i < 16; i += 4 ) + { + for( j = -16; j < 16; j += 4 ) + { + for( k = -24; k < 32; k += 4 ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + COM_RandomFloat( 0.2f, 0.34f ); + p->color = COM_RandomLong( 7, 14 ); + p->type = pt_slowgrav; + + dir[0] = j * 8.0f; + dir[1] = i * 8.0f; + dir[2] = k * 8.0f; + + p->org[0] = org[0] + i + COM_RandomFloat( 0.0f, 3.0f ); + p->org[1] = org[1] + j + COM_RandomFloat( 0.0f, 3.0f ); + p->org[2] = org[2] + k + COM_RandomFloat( 0.0f, 3.0f ); + + VectorNormalize( dir ); + vel = COM_RandomFloat( 50.0f, 113.0f ); + VectorScale( dir, vel, p->vel ); + } + } + } +} + +/* +=============== +R_RocketTrail + +=============== +*/ +void R_RocketTrail( vec3_t start, vec3_t end, int type ) +{ + vec3_t vec, right, up; + static int tracercount; + float s, c, x, y; + float len, dec; + particle_t *p; + + VectorSubtract( end, start, vec ); + len = VectorNormalizeLength( vec ); + + if( type == 7 ) + { + VectorVectors( vec, right, up ); + } + + if( type < 128 ) + { + dec = 3.0f; + } + else + { + dec = 1.0f; + type -= 128; + } + + VectorScale( vec, dec, vec ); + + while( len > 0 ) + { + len -= dec; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 2.0f; + + switch( type ) + { + case 0: // rocket trail + p->ramp = COM_RandomLong( 0, 3 ); + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); + break; + case 1: // smoke smoke + p->ramp = COM_RandomLong( 2, 5 ); + p->color = ramp3[(int)p->ramp]; + p->type = pt_fire; + VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); + break; + case 2: // blood + p->type = pt_grav; + p->color = COM_RandomLong( 67, 74 ); + VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); + break; + case 3: + case 5: // tracer + p->die = cl.time + 0.5f; + + if( type == 3 ) p->color = 52 + (( tracercount & 4 )<<1 ); + else p->color = 230 + (( tracercount & 4 )<<1 ); + + VectorCopy( start, p->org ); + tracercount++; + + if( FBitSet( tracercount, 1 )) + { + p->vel[0] = 30.0f * vec[1]; + p->vel[1] = 30.0f * -vec[0]; + } + else + { + p->vel[0] = 30.0f * -vec[1]; + p->vel[1] = 30.0f * vec[0]; + } + break; + case 4: // slight blood + p->type = pt_grav; + p->color = COM_RandomLong( 67, 70 ); + VectorAddScalar( start, COM_RandomFloat( -3.0f, 3.0f ), p->org ); + len -= 3.0f; + break; + case 6: // voor trail + p->color = COM_RandomLong( 152, 155 ); + p->die += 0.3f; + VectorAddScalar( start, COM_RandomFloat( -8.0f, 8.0f ), p->org ); + break; + case 7: // explosion tracer + x = COM_RandomLong( 0, 65535 ); + y = COM_RandomLong( 8, 16 ); + SinCos( x, &s, &c ); + s *= y; + c *= y; + + VectorMAMAM( 1.0f, start, s, right, c, up, p->org ); + VectorSubtract( start, p->org, p->vel ); + VectorScale( p->vel, 2.0f, p->vel ); + VectorMA( p->vel, COM_RandomFloat( 96.0f, 111.0f ), vec, p->vel ); + p->ramp = COM_RandomLong( 0, 3 ); + p->color = ramp3[(int)p->ramp]; + p->type = pt_explode2; + break; + default: + // just build line to show error + VectorCopy( start, p->org ); + break; + } + + VectorAdd( start, vec, start ); + } +} + +/* +================ +R_ParticleLine + +================ +*/ +void R_ParticleLine( const vec3_t start, const vec3_t end, byte r, byte g, byte b, float life ) +{ + int pcolor; + + pcolor = R_LookupColor( r, g, b ); + PM_ParticleLine( start, end, pcolor, life, 0 ); +} + +/* +================ +R_ParticleBox + +================ +*/ +void R_ParticleBox( const vec3_t absmin, const vec3_t absmax, byte r, byte g, byte b, float life ) +{ + vec3_t mins, maxs; + vec3_t origin; + int pcolor; + + pcolor = R_LookupColor( r, g, b ); + + VectorAverage( absmax, absmin, origin ); + VectorSubtract( absmax, origin, maxs ); + VectorSubtract( absmin, origin, mins ); + + PM_DrawBBox( mins, maxs, origin, pcolor, life ); +} + +/* +================ +R_ShowLine + +================ +*/ +void R_ShowLine( const vec3_t start, const vec3_t end ) +{ + vec3_t dir, org; + float len; + particle_t *p; + + VectorSubtract( end, start, dir ); + len = VectorNormalizeLength( dir ); + VectorScale( dir, 5.0f, dir ); + VectorCopy( start, org ); + + while( len > 0 ) + { + len -= 5.0f; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + p->die = cl.time + 30; + p->color = 75; + + VectorCopy( org, p->org ); + VectorAdd( org, dir, org ); + } +} + +/* +=============== +R_BulletImpactParticles + +=============== +*/ +void R_BulletImpactParticles( const vec3_t pos ) +{ + int i, quantity; + int color; + float dist; + vec3_t dir; + particle_t *p; + + VectorSubtract( pos, RI.vieworg, dir ); + dist = VectorLength( dir ); + if( dist > 1000.0f ) dist = 1000.0f; + + quantity = (1000.0f - dist) / 100.0f; + if( quantity == 0 ) quantity = 1; + + color = 3 - ((30 * quantity) / 100 ); + R_SparkStreaks( pos, 2, -200, 200 ); + + for( i = 0; i < quantity * 4; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + VectorCopy( pos, p->org); + + p->vel[0] = COM_RandomFloat( -1.0f, 1.0f ); + p->vel[1] = COM_RandomFloat( -1.0f, 1.0f ); + p->vel[2] = COM_RandomFloat( -1.0f, 1.0f ); + VectorScale( p->vel, COM_RandomFloat( 50.0f, 100.0f ), p->vel ); + + p->die = cl.time + 0.5; + p->color = 3 - color; + p->type = pt_grav; + } +} + +/* +=============== +R_FlickerParticles + +=============== +*/ +void R_FlickerParticles( const vec3_t org ) +{ + particle_t *p; + int i; + + for( i = 0; i < 15; i++ ) + { + p = R_AllocParticle( NULL ); + if( !p ) return; + + VectorCopy( org, p->org ); + p->vel[0] = COM_RandomFloat( -32.0f, 32.0f ); + p->vel[1] = COM_RandomFloat( -32.0f, 32.0f ); + p->vel[2] = COM_RandomFloat( 80.0f, 143.0f ); + + p->die = cl.time + 2.0f; + p->type = pt_blob2; + p->color = 254; + } +} + +/* +=============== +R_StreakSplash + +create a splash of streaks +=============== +*/ +void R_StreakSplash( const vec3_t pos, const vec3_t dir, int color, int count, float speed, int velocityMin, int velocityMax ) +{ + vec3_t vel, vel2; + particle_t *p; + int i; + + VectorScale( dir, speed, vel ); + + for( i = 0; i < count; i++ ) + { + VectorAddScalar( vel, COM_RandomFloat( velocityMin, velocityMax ), vel2 ); + p = R_AllocTracer( pos, vel2, COM_RandomFloat( 0.1f, 0.5f )); + if( !p ) return; + + p->type = pt_grav; + p->color = color; + p->ramp = 1.0f; + } +} + +/* +=============== +R_DebugParticle + +just for debug purposes +=============== +*/ +void R_DebugParticle( const vec3_t pos, byte r, byte g, byte b ) +{ + particle_t *p; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + VectorCopy( pos, p->org ); + p->color = R_LookupColor( r, g, b ); + p->die = cl.time + 0.01f; +} + +/* +=============== +CL_Particle + +pmove debugging particle +=============== +*/ +void CL_Particle( const vec3_t org, int color, float life, int zpos, int zvel ) +{ + particle_t *p; + + p = R_AllocParticle( NULL ); + if( !p ) return; + + if( org ) VectorCopy( org, p->org ); + p->die = cl.time + life; + p->vel[2] += zvel; // ??? + p->color = color; +} + +/* +=============== +R_TracerEffect + +=============== +*/ +void R_TracerEffect( const vec3_t start, const vec3_t end ) +{ + vec3_t pos, vel, dir; + float len, speed; + float offset; + + speed = Q_max( tracerspeed->value, 3.0f ); + + VectorSubtract( end, start, dir ); + len = VectorLength( dir ); + if( len == 0.0f ) return; + + VectorScale( dir, 1.0f / len, dir ); // normalize + offset = COM_RandomFloat( -10.0f, 9.0f ) + traceroffset->value; + VectorScale( dir, offset, vel ); + VectorAdd( start, vel, pos ); + VectorScale( dir, speed, vel ); + + R_AllocTracer( pos, vel, len / speed ); +} + +/* +=============== +R_UserTracerParticle + +=============== +*/ +void R_UserTracerParticle( float *org, float *vel, float life, int colorIndex, float length, byte deathcontext, void (*deathfunc)( particle_t *p )) +{ + particle_t *p; + + if( colorIndex < 0 ) + { + MsgDev( D_ERROR, "UserTracer with color < 0\n" ); + return; + } + + if( colorIndex > ARRAYSIZE( gTracerColors )) + { + MsgDev( D_ERROR, "UserTracer with color > %d\n", ARRAYSIZE( gTracerColors )); + return; + } + + if(( p = R_AllocTracer( org, vel, life )) != NULL ) + { + p->context = deathcontext; + p->deathfunc = deathfunc; + p->color = colorIndex; + p->ramp = length; + } +} + +/* +=============== +R_TracerParticles + +allow more customization +=============== +*/ +particle_t *R_TracerParticles( float *org, float *vel, float life ) +{ + return R_AllocTracer( org, vel, life ); +} + +/* +=============== +R_SparkStreaks + +create a streak tracers +=============== +*/ +void R_SparkStreaks( const vec3_t pos, int count, int velocityMin, int velocityMax ) +{ + particle_t *p; + vec3_t vel; + int i; + + for( i = 0; icolor = 5; + p->type = pt_grav; + p->ramp = 0.5f; + } +} + +/* +=============== +R_Implosion + +make implosion tracers +=============== +*/ +void R_Implosion( const vec3_t end, float radius, int count, float life ) +{ + float dist = ( radius / 100.0f ); + vec3_t start, temp, vel; + float factor; + particle_t *p; + int i; + + if( life <= 0.0f ) life = 0.1f; // to avoid divide by zero + factor = -1.0 / life; + + for ( i = 0; i < count; i++ ) + { + temp[0] = dist * COM_RandomFloat( -100.0f, 100.0f ); + temp[1] = dist * COM_RandomFloat( -100.0f, 100.0f ); + temp[2] = dist * COM_RandomFloat( 0.0f, 100.0f ); + VectorScale( temp, factor, vel ); + VectorAdd( temp, end, start ); + + if(( p = R_AllocTracer( start, vel, life )) == NULL ) + return; + + p->type = pt_explode; + } +} + +/* +=============== +CL_ReadPointFile_f + +=============== +*/ +void CL_ReadPointFile_f( void ) +{ + char *afile, *pfile; + vec3_t org; + int count; + particle_t *p; + char filename[64]; + string token; + + Q_snprintf( filename, sizeof( filename ), "maps/%s.pts", clgame.mapname ); + afile = FS_LoadFile( filename, NULL, false ); + + if( !afile ) + { + Con_Printf( S_ERROR "couldn't open %s\n", filename ); + return; + } + + Con_Printf( "Reading %s...\n", filename ); + + count = 0; + pfile = afile; + + while( 1 ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + org[0] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + org[1] = Q_atof( token ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + org[2] = Q_atof( token ); + + count++; + + if( !cl_free_particles ) + { + Con_Printf( S_ERROR "not enough free particles!\n" ); + break; + } + + // NOTE: can't use R_AllocParticle because this command + // may be executed from the console, while frametime is 0 + p = cl_free_particles; + cl_free_particles = p->next; + p->next = cl_active_particles; + cl_active_particles = p; + + p->ramp = 0; + p->type = pt_static; + p->die = cl.time + 99999; + p->color = (-count) & 15; + VectorCopy( org, p->org ); + VectorClear( p->vel ); + } + + Mem_Free( afile ); + + if( count ) Con_Printf( "%i points read\n", count ); + else Con_Printf( "map %s has no leaks!\n", clgame.mapname ); +} \ No newline at end of file diff --git a/engine/client/gl_rsurf.c b/engine/client/gl_rsurf.c new file mode 100644 index 00000000..7f02ee62 --- /dev/null +++ b/engine/client/gl_rsurf.c @@ -0,0 +1,2190 @@ +/* +gl_rsurf.c - surface-related refresh code +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mod_local.h" +#include "mathlib.h" + +typedef struct +{ + int allocated[BLOCK_SIZE_MAX]; + int current_lightmap_texture; + msurface_t *dynamic_surfaces; + msurface_t *lightmap_surfaces[MAX_LIGHTMAPS]; + byte lightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4]; +} gllightmapstate_t; + +static int nColinElim; // stats +static vec2_t world_orthocenter; +static vec2_t world_orthohalf; +static uint r_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3]; +static mextrasurf_t *fullbright_surfaces[MAX_TEXTURES]; +static mextrasurf_t *detail_surfaces[MAX_TEXTURES]; +static int rtable[MOD_FRAMES][MOD_FRAMES]; +static qboolean draw_alpha_surfaces = false; +static qboolean draw_fullbrights = false; +static qboolean draw_details = false; +static msurface_t *skychain = NULL; +static gllightmapstate_t gl_lms; + +static void LM_UploadBlock( int lightmapnum ); + +byte *Mod_GetCurrentVis( void ) +{ + if( clgame.drawFuncs.Mod_GetCurrentVis && tr.fCustomRendering ) + return clgame.drawFuncs.Mod_GetCurrentVis(); + return RI.visbytes; +} + +void Mod_SetOrthoBounds( float *mins, float *maxs ) +{ + if( clgame.drawFuncs.GL_OrthoBounds ) + { + clgame.drawFuncs.GL_OrthoBounds( mins, maxs ); + } + + Vector2Average( maxs, mins, world_orthocenter ); + Vector2Subtract( maxs, world_orthocenter, world_orthohalf ); +} + +static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) +{ + int i, j; + float *v; + + ClearBounds( mins, maxs ); + + for( i = 0, v = verts; i < numverts; i++ ) + { + for( j = 0; j < 3; j++, v++ ) + { + if( *v < mins[j] ) mins[j] = *v; + if( *v > maxs[j] ) maxs[j] = *v; + } + } +} + +static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ) +{ + vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; + mextrasurf_t *warpinfo = warpface->info; + float dist[SUBDIVIDE_SIZE]; + float m, frac, s, t, *v; + int i, j, k, f, b; + float sample_size; + vec3_t mins, maxs; + glpoly_t *poly; + + if( numverts > ( SUBDIVIDE_SIZE - 4 )) + Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts ); + + sample_size = Mod_SampleSizeForFace( warpface ); + BoundPoly( numverts, verts, mins, maxs ); + + for( i = 0; i < 3; i++ ) + { + m = ( mins[i] + maxs[i] ) * 0.5f; + m = SUBDIVIDE_SIZE * floor( m / SUBDIVIDE_SIZE + 0.5f ); + if( maxs[i] - m < 8 ) continue; + if( m - mins[i] < 8 ) continue; + + // cut it + v = verts + i; + for( j = 0; j < numverts; j++, v += 3 ) + dist[j] = *v - m; + + // wrap cases + dist[j] = dist[0]; + v -= i; + VectorCopy( verts, v ); + + f = b = 0; + v = verts; + for( j = 0; j < numverts; j++, v += 3 ) + { + if( dist[j] >= 0 ) + { + VectorCopy( v, front[f] ); + f++; + } + + if( dist[j] <= 0 ) + { + VectorCopy (v, back[b]); + b++; + } + + if( dist[j] == 0 || dist[j+1] == 0 ) + continue; + + if(( dist[j] > 0 ) != ( dist[j+1] > 0 )) + { + // clip point + frac = dist[j] / ( dist[j] - dist[j+1] ); + for( k = 0; k < 3; k++ ) + front[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]); + f++; + b++; + } + } + + SubdividePolygon_r( warpface, f, front[0] ); + SubdividePolygon_r( warpface, b, back[0] ); + return; + } + + if( numverts != 4 ) + ClearBits( warpface->flags, SURF_DRAWTURB_QUADS ); + + // add a point in the center to help keep warp valid + poly = Mem_Alloc( loadmodel->mempool, sizeof( glpoly_t ) + (numverts - 4) * VERTEXSIZE * sizeof( float )); + poly->next = warpface->polys; + poly->flags = warpface->flags; + warpface->polys = poly; + poly->numverts = numverts; + + for( i = 0; i < numverts; i++, verts += 3 ) + { + VectorCopy( verts, poly->verts[i] ); + + if( FBitSet( warpface->flags, SURF_DRAWTURB )) + { + s = DotProduct( verts, warpface->texinfo->vecs[0] ); + t = DotProduct( verts, warpface->texinfo->vecs[1] ); + } + else + { + s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; + t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; + s /= warpface->texinfo->texture->width; + t /= warpface->texinfo->texture->height; + } + + poly->verts[i][3] = s; + poly->verts[i][4] = t; + + // for speed reasons + if( !FBitSet( warpface->flags, SURF_DRAWTURB )) + { + // lightmap texture coordinates + s = DotProduct( verts, warpinfo->lmvecs[0] ) + warpinfo->lmvecs[0][3]; + s -= warpinfo->lightmapmins[0]; + s += warpface->light_s * sample_size; + s += sample_size * 0.5; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( verts, warpinfo->lmvecs[1] ) + warpinfo->lmvecs[1][3]; + t -= warpinfo->lightmapmins[1]; + t += warpface->light_t * sample_size; + t += sample_size * 0.5; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + poly->verts[i][5] = s; + poly->verts[i][6] = t; + } + } +} + +void GL_SetupFogColorForSurfaces( void ) +{ + vec3_t fogColor; + float factor, div; + + if( !pglIsEnabled( GL_FOG )) + return; + + if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ) + { + pglFogfv( GL_FOG_COLOR, RI.fogColor ); + return; + } + + div = (r_detailtextures->value) ? 2.0f : 1.0f; + factor = (r_detailtextures->value) ? 3.0f : 2.0f; + fogColor[0] = pow( RI.fogColor[0] / div, ( 1.0f / factor )); + fogColor[1] = pow( RI.fogColor[1] / div, ( 1.0f / factor )); + fogColor[2] = pow( RI.fogColor[2] / div, ( 1.0f / factor )); + pglFogfv( GL_FOG_COLOR, fogColor ); +} + +void GL_ResetFogColor( void ) +{ + // restore fog here + if( pglIsEnabled( GL_FOG )) + pglFogfv( GL_FOG_COLOR, RI.fogColor ); +} + +/* +================ +GL_SubdivideSurface + +Breaks a polygon up along axial 64 unit +boundaries so that turbulent and sky warps +can be done reasonably. +================ +*/ +void GL_SubdivideSurface( msurface_t *fa ) +{ + vec3_t verts[SUBDIVIDE_SIZE]; + int numverts; + int i, lindex; + float *vec; + + // convert edges back to a normal polygon + numverts = 0; + for( i = 0; i < fa->numedges; i++ ) + { + lindex = loadmodel->surfedges[fa->firstedge + i]; + + if( lindex > 0 ) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position; + else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position; + VectorCopy( vec, verts[numverts] ); + numverts++; + } + + SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state + + // do subdivide + SubdividePolygon_r( fa, numverts, verts[0] ); +} + +/* +================ +GL_BuildPolygonFromSurface +================ +*/ +void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) +{ + int i, lindex, lnumverts; + medge_t *pedges, *r_pedge; + mextrasurf_t *info = fa->info; + float sample_size; + int vertpage; + texture_t *tex; + gltexture_t *glt; + float *vec; + float s, t; + glpoly_t *poly; + + // already created + if( !mod || fa->polys ) return; + + if( !fa->texinfo || !fa->texinfo->texture ) + return; // bad polygon ? + + if( fa->flags & SURF_CONVEYOR && fa->texinfo->texture->gl_texturenum != 0 ) + { + glt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); + tex = fa->texinfo->texture; + Assert( glt != NULL && tex != NULL ); + + // update conveyor widths for keep properly speed of scrolling + glt->srcWidth = tex->width; + glt->srcHeight = tex->height; + } + + sample_size = Mod_SampleSizeForFace( fa ); + + // reconstruct the polygon + pedges = mod->edges; + lnumverts = fa->numedges; + vertpage = 0; + + // draw texture + poly = Mem_Alloc( mod->mempool, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float )); + poly->next = fa->polys; + poly->flags = fa->flags; + fa->polys = poly; + poly->numverts = lnumverts; + + for( i = 0; i < lnumverts; i++ ) + { + lindex = mod->surfedges[fa->firstedge + i]; + + if( lindex > 0 ) + { + r_pedge = &pedges[lindex]; + vec = mod->vertexes[r_pedge->v[0]].position; + } + else + { + r_pedge = &pedges[-lindex]; + vec = mod->vertexes[r_pedge->v[1]].position; + } + + s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3]; + s /= fa->texinfo->texture->width; + + t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3]; + t /= fa->texinfo->texture->height; + + VectorCopy( vec, poly->verts[i] ); + poly->verts[i][3] = s; + poly->verts[i][4] = t; + + // lightmap texture coordinates + s = DotProduct( vec, info->lmvecs[0] ) + info->lmvecs[0][3]; + s -= info->lightmapmins[0]; + s += fa->light_s * sample_size; + s += sample_size / 2.0; + s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; + + t = DotProduct( vec, info->lmvecs[1] ) + info->lmvecs[1][3]; + t -= info->lightmapmins[1]; + t += fa->light_t * sample_size; + t += sample_size / 2.0; + t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height; + + poly->verts[i][5] = s; + poly->verts[i][6] = t; + } + + // remove co-linear points - Ed + if( !gl_keeptjunctions->value && !( fa->flags & SURF_UNDERWATER )) + { + for( i = 0; i < lnumverts; i++ ) + { + vec3_t v1, v2; + float *prev, *this, *next; + + prev = poly->verts[(i + lnumverts - 1) % lnumverts]; + next = poly->verts[(i + 1) % lnumverts]; + this = poly->verts[i]; + + VectorSubtract( this, prev, v1 ); + VectorNormalize( v1 ); + VectorSubtract( next, prev, v2 ); + VectorNormalize( v2 ); + + // skip co-linear points + if(( fabs( v1[0] - v2[0] ) <= 0.001f) && (fabs( v1[1] - v2[1] ) <= 0.001f) && (fabs( v1[2] - v2[2] ) <= 0.001f)) + { + int j, k; + + for( j = i + 1; j < lnumverts; j++ ) + { + for( k = 0; k < VERTEXSIZE; k++ ) + poly->verts[j-1][k] = poly->verts[j][k]; + } + + // retry next vertex next time, which is now current vertex + lnumverts--; + nColinElim++; + i--; + } + } + } + + poly->numverts = lnumverts; +} + +/* +=============== +R_TextureAnimation + +Returns the proper texture for a given time and base texture +=============== +*/ +texture_t *R_TextureAnimation( msurface_t *s ) +{ + texture_t *base = s->texinfo->texture; + int count, reletive; + + if( RI.currententity->curstate.frame ) + { + if( base->alternate_anims ) + base = base->alternate_anims; + } + + if( !base->anim_total ) + return base; + + if( base->name[0] == '-' ) + { + int tx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES; + int ty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES; + + reletive = rtable[tx][ty] % base->anim_total; + } + else + { + int speed; + + // Quake1 textures uses 10 frames per second + if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL )) + speed = 10; + else speed = 20; + + reletive = (int)(cl.time * speed) % base->anim_total; + } + + count = 0; + + while( base->anim_min > reletive || base->anim_max <= reletive ) + { + base = base->anim_next; + + if( !base ) + { + MsgDev( D_ERROR, "R_TextureAnimation: broken loop\n" ); + return s->texinfo->texture; + } + + if( ++count > MOD_FRAMES ) + { + MsgDev( D_ERROR, "R_TextureAnimation: infinite loop\n" ); + return s->texinfo->texture; + } + } + + return base; +} + +/* +=============== +R_AddDynamicLights +=============== +*/ +void R_AddDynamicLights( msurface_t *surf ) +{ + float dist, rad, minlight; + int lnum, s, t, sd, td, smax, tmax; + float sl, tl, sacc, tacc; + vec3_t impact, origin_l; + mextrasurf_t *info = surf->info; + int sample_frac = 1.0; + float sample_size; + mtexinfo_t *tex; + dlight_t *dl; + uint *bl; + + // no dlighted surfaces here + if( !R_CountSurfaceDlights( surf )) return; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = (info->lightextents[0] / sample_size) + 1; + tmax = (info->lightextents[1] / sample_size) + 1; + tex = surf->texinfo; + + if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) + { + if( surf->texinfo->faceinfo ) + sample_frac = surf->texinfo->faceinfo->texture_step; + else if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) + sample_frac = LM_SAMPLE_EXTRASIZE; + else sample_frac = LM_SAMPLE_SIZE; + } + + for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) + { + if( !FBitSet( surf->dlightbits, BIT( lnum ))) + continue; // not lit by this light + + dl = &cl_dlights[lnum]; + + // transform light origin to local bmodel space + if( !tr.modelviewIdentity ) + Matrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l ); + else VectorCopy( dl->origin, origin_l ); + + rad = dl->radius; + dist = PlaneDiff( origin_l, surf->plane ); + rad -= fabs( dist ); + + // rad is now the highest intensity on the plane + minlight = dl->minlight; + if( rad < minlight ) + continue; + + minlight = rad - minlight; + + if( surf->plane->type < 3 ) + { + VectorCopy( origin_l, impact ); + impact[surf->plane->type] -= dist; + } + else VectorMA( origin_l, -dist, surf->plane->normal, impact ); + + sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; + tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; + bl = r_blocklights; + + for( t = 0, tacc = 0; t < tmax; t++, tacc += sample_size ) + { + td = (tl - tacc) * sample_frac; + if( td < 0 ) td = -td; + + for( s = 0, sacc = 0; s < smax; s++, sacc += sample_size, bl += 3 ) + { + sd = (sl - sacc) * sample_frac; + if( sd < 0 ) sd = -sd; + + if( sd > td ) dist = sd + (td >> 1); + else dist = td + (sd >> 1); + + if( dist < minlight ) + { + bl[0] += ((int)((rad - dist) * 256) * LightToTexGamma( dl->color.r )) / 256; + bl[1] += ((int)((rad - dist) * 256) * LightToTexGamma( dl->color.g )) / 256; + bl[2] += ((int)((rad - dist) * 256) * LightToTexGamma( dl->color.b )) / 256; + } + } + } + } +} + +/* +================ +R_SetCacheState +================ +*/ +void R_SetCacheState( msurface_t *surf ) +{ + int maps; + + for( maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++ ) + { + surf->cached_light[maps] = tr.lightstylevalue[surf->styles[maps]]; + } +} + +/* +============================================================================= + + LIGHTMAP ALLOCATION + +============================================================================= +*/ +static void LM_InitBlock( void ) +{ + memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated )); +} + +static int LM_AllocBlock( int w, int h, int *x, int *y ) +{ + int i, j; + int best, best2; + + best = BLOCK_SIZE; + + for( i = 0; i < BLOCK_SIZE - w; i++ ) + { + best2 = 0; + + for( j = 0; j < w; j++ ) + { + if( gl_lms.allocated[i+j] >= best ) + break; + if( gl_lms.allocated[i+j] > best2 ) + best2 = gl_lms.allocated[i+j]; + } + + if( j == w ) + { + // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if( best + h > BLOCK_SIZE ) + return false; + + for( i = 0; i < w; i++ ) + gl_lms.allocated[*x + i] = best + h; + + return true; +} + +static void LM_UploadBlock( qboolean dynamic ) +{ + int i; + + if( dynamic ) + { + int height = 0; + + for( i = 0; i < BLOCK_SIZE; i++ ) + { + if( gl_lms.allocated[i] > height ) + height = gl_lms.allocated[i]; + } + + if( host.features & ENGINE_LARGE_LIGHTMAPS ) + GL_Bind( GL_TEXTURE0, tr.dlightTexture2 ); + else GL_Bind( GL_TEXTURE0, tr.dlightTexture ); + + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer ); + } + else + { + rgbdata_t r_lightmap; + char lmName[16]; + + i = gl_lms.current_lightmap_texture; + + // upload static lightmaps only during loading + memset( &r_lightmap, 0, sizeof( r_lightmap )); + Q_snprintf( lmName, sizeof( lmName ), "*lightmap%i", i ); + + r_lightmap.width = BLOCK_SIZE; + r_lightmap.height = BLOCK_SIZE; + r_lightmap.type = PF_RGBA_32; + r_lightmap.size = r_lightmap.width * r_lightmap.height * 4; + r_lightmap.flags = IMAGE_HAS_COLOR; + r_lightmap.buffer = gl_lms.lightmap_buffer; + tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_ATLAS_PAGE, false ); + + if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS ) + Host_Error( "AllocBlock: full\n" ); + } +} + +/* +================= +R_BuildLightmap + +Combine and scale multiple lightmaps into the floating +format in r_blocklights +================= +*/ +static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean dynamic ) +{ + int smax, tmax; + uint *bl, scale; + int i, map, size, s, t; + int sample_size; + mextrasurf_t *info = surf->info; + color24 *lm; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + size = smax * tmax; + + lm = surf->samples; + + memset( r_blocklights, 0, sizeof( uint ) * size * 3 ); + + // add all the lightmaps + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ ) + { + scale = tr.lightstylevalue[surf->styles[map]]; + + for( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ ) + { + bl[0] += LightToTexGamma( lm->r ) * scale; + bl[1] += LightToTexGamma( lm->g ) * scale; + bl[2] += LightToTexGamma( lm->b ) * scale; + } + } + + // add all the dynamic lights + if( surf->dlightframe == tr.framecount && dynamic ) + R_AddDynamicLights( surf ); + + // Put into texture format + stride -= (smax << 2); + bl = r_blocklights; + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = min((bl[0] >> 7), 255 ); + dest[1] = min((bl[1] >> 7), 255 ); + dest[2] = min((bl[2] >> 7), 255 ); + dest[3] = 255; + + bl += 3; + dest += 4; + } + } +} + +/* +================ +DrawGLPoly +================ +*/ +void DrawGLPoly( glpoly_t *p, float xScale, float yScale ) +{ + float *v; + float sOffset, sy; + float tOffset, cy; + cl_entity_t *e = RI.currententity; + int i, hasScale = false; + + if( !p ) return; + + if( FBitSet( p->flags, SURF_DRAWTILED )) + GL_ResetFogColor(); + + if( p->flags & SURF_CONVEYOR ) + { + gltexture_t *texture; + float flConveyorSpeed; + float flRate, flAngle; + + flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f; + if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed; + texture = R_GetTexture( glState.currentTextures[glState.activeTMU] ); + + flRate = abs( flConveyorSpeed ) / (float)texture->srcWidth; + flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0; + + SinCos( flAngle * ( M_PI / 180.0f ), &sy, &cy ); + sOffset = cl.time * cy * flRate; + tOffset = cl.time * sy * flRate; + + // make sure that we are positive + if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset; + if( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset; + + // make sure that we are in a [0,1] range + sOffset = sOffset - (int)sOffset; + tOffset = tOffset - (int)tOffset; + } + else + { + sOffset = tOffset = 0.0f; + } + + if( xScale != 0.0f && yScale != 0.0f ) + hasScale = true; + + pglBegin( GL_POLYGON ); + + for( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE ) + { + if( hasScale ) + pglTexCoord2f(( v[3] + sOffset ) * xScale, ( v[4] + tOffset ) * yScale ); + else pglTexCoord2f( v[3] + sOffset, v[4] + tOffset ); + + pglVertex3fv( v ); + } + + pglEnd(); + + if( FBitSet( p->flags, SURF_DRAWTILED )) + GL_SetupFogColorForSurfaces(); +} + +/* +================ +DrawGLPolyChain + +Render lightmaps +================ +*/ +void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) +{ + qboolean dynamic = true; + + if( soffset == 0.0f && toffset == 0.0f ) + dynamic = false; + + for( ; p != NULL; p = p->chain ) + { + float *v; + int i; + + pglBegin( GL_POLYGON ); + + v = p->verts[0]; + for( i = 0; i < p->numverts; i++, v += VERTEXSIZE ) + { + if( !dynamic ) pglTexCoord2f( v[5], v[6] ); + else pglTexCoord2f( v[5] - soffset, v[6] - toffset ); + pglVertex3fv( v ); + } + pglEnd (); + } +} + +/* +================ +R_BlendLightmaps +================ +*/ +void R_BlendLightmaps( void ) +{ + msurface_t *surf, *newsurf = NULL; + int i; + + if( r_fullbright->value || !cl.worldmodel->lightdata ) + return; + + if( RI.currententity ) + { + if( RI.currententity->curstate.effects & EF_FULLBRIGHT ) + return; // disabled by user + + // check for rendermode + switch( RI.currententity->curstate.rendermode ) + { + case kRenderTransTexture: + case kRenderTransColor: + case kRenderTransAdd: + case kRenderGlow: + return; // no lightmaps + } + } + + GL_SetupFogColorForSurfaces (); + + if( !r_lightmap->value ) + pglEnable( GL_BLEND ); + else pglDisable( GL_BLEND ); + + // lightmapped solid surfaces + pglDepthMask( GL_FALSE ); + pglDepthFunc( GL_EQUAL ); + + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + // render static lightmaps first + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( gl_lms.lightmap_surfaces[i] ) + { + GL_Bind( GL_TEXTURE0, tr.lightmapTextures[i] ); + + for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain ) + { + if( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f ); + } + } + } + + // render dynamic lightmaps + if( r_dynamic->value ) + { + LM_InitBlock(); + + if( host.features & ENGINE_LARGE_LIGHTMAPS ) + GL_Bind( GL_TEXTURE0, tr.dlightTexture2 ); + else GL_Bind( GL_TEXTURE0, tr.dlightTexture ); + + newsurf = gl_lms.dynamic_surfaces; + + for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain ) + { + int smax, tmax; + int sample_size; + mextrasurf_t *info = surf->info; + byte *base; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + if( LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t )) + { + base = gl_lms.lightmap_buffer; + base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4; + + R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true ); + } + else + { + msurface_t *drawsurf; + + // upload what we have so far + LM_UploadBlock( true ); + + // draw all surfaces that use this lightmap + for( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->info->lightmapchain ) + { + if( drawsurf->polys ) + { + DrawGLPolyChain( drawsurf->polys, + ( drawsurf->light_s - drawsurf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), + ( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); + } + } + + newsurf = drawsurf; + + // clear the block + LM_InitBlock(); + + // try uploading the block now + if( !LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t )) + Host_Error( "AllocBlock: full\n" ); + + base = gl_lms.lightmap_buffer; + base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4; + + R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true ); + } + } + + // draw remainder of dynamic lightmaps that haven't been uploaded yet + if( newsurf ) LM_UploadBlock( true ); + + for( surf = newsurf; surf != NULL; surf = surf->info->lightmapchain ) + { + if( surf->polys ) + { + DrawGLPolyChain( surf->polys, + ( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ), + ( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE )); + } + } + } + + pglDisable( GL_BLEND ); + pglDepthMask( GL_TRUE ); + pglDepthFunc( GL_LEQUAL ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + + // restore fog here + GL_ResetFogColor(); +} + +/* +================ +R_RenderFullbrights +================ +*/ +void R_RenderFullbrights( void ) +{ + mextrasurf_t *es, *p; + int i; + + if( !draw_fullbrights ) + return; + + R_AllowFog( false ); + pglEnable( GL_BLEND ); + pglDepthMask( GL_FALSE ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_ONE, GL_ONE ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( i = 1; i < MAX_TEXTURES; i++ ) + { + es = fullbright_surfaces[i]; + if( !es ) continue; + + GL_Bind( GL_TEXTURE0, i ); + + for( p = es; p; p = p->lumachain ) + DrawGLPoly( p->surf->polys, 0.0f, 0.0f ); + + fullbright_surfaces[i] = NULL; + es->lumachain = NULL; + } + + pglDisable( GL_BLEND ); + pglDepthMask( GL_TRUE ); + pglDisable( GL_ALPHA_TEST ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + + draw_fullbrights = false; + R_AllowFog( true ); +} + +/* +================ +R_RenderDetails +================ +*/ +void R_RenderDetails( void ) +{ + gltexture_t *glt; + mextrasurf_t *es, *p; + msurface_t *fa; + int i; + + if( !draw_details ) + return; + + GL_SetupFogColorForSurfaces(); + + pglEnable( GL_BLEND ); + pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); + pglDepthFunc( GL_EQUAL ); + + for( i = 1; i < MAX_TEXTURES; i++ ) + { + es = detail_surfaces[i]; + if( !es ) continue; + + GL_Bind( GL_TEXTURE0, i ); + + for( p = es; p; p = p->detailchain ) + { + fa = p->surf; + glt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); // get texture scale + DrawGLPoly( fa->polys, glt->xscale, glt->yscale ); + } + + detail_surfaces[i] = NULL; + es->detailchain = NULL; + } + + pglDisable( GL_BLEND ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglDepthFunc( GL_LEQUAL ); + + draw_details = false; + + // restore fog here + GL_ResetFogColor(); +} + +/* +================ +R_RenderBrushPoly +================ +*/ +void R_RenderBrushPoly( msurface_t *fa, int cull_type ) +{ + qboolean is_dynamic = false; + int maps; + texture_t *t; + + r_stats.c_world_polys++; + + if( fa->flags & SURF_DRAWSKY ) + return; // already handled + + t = R_TextureAnimation( fa ); + + GL_Bind( GL_TEXTURE0, t->gl_texturenum ); + + if( FBitSet( fa->flags, SURF_DRAWTURB )) + { + // warp texture, no lightmaps + EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); + return; + } + + if( t->fb_texturenum ) + { + fa->info->lumachain = fullbright_surfaces[t->fb_texturenum]; + fullbright_surfaces[t->fb_texturenum] = fa->info; + draw_fullbrights = true; + } + + if( r_detailtextures->value ) + { + if( pglIsEnabled( GL_FOG )) + { + // don't apply detail textures for windows in the fog + if( RI.currententity->curstate.rendermode != kRenderTransTexture ) + { + if( t->dt_texturenum ) + { + fa->info->detailchain = detail_surfaces[t->dt_texturenum]; + detail_surfaces[t->dt_texturenum] = fa->info; + } + else + { + // draw stub detail texture for underwater surfaces + fa->info->detailchain = detail_surfaces[tr.grayTexture]; + detail_surfaces[tr.grayTexture] = fa->info; + } + draw_details = true; + } + } + else if( t->dt_texturenum ) + { + fa->info->detailchain = detail_surfaces[t->dt_texturenum]; + detail_surfaces[t->dt_texturenum] = fa->info; + draw_details = true; + } + } + + DrawGLPoly( fa->polys, 0.0f, 0.0f ); + + if( RI.currententity->curstate.rendermode == kRenderNormal ) + { + // batch decals to draw later + if( tr.num_draw_decals < MAX_DECAL_SURFS && fa->pdecals ) + tr.draw_decals[tr.num_draw_decals++] = fa; + } + else + { + // if rendermode != kRenderNormal draw decals sequentially + DrawSurfaceDecals( fa, true, (cull_type == CULL_BACKSIDE)); + } + + if( fa->flags & SURF_DRAWTILED ) + return; // no lightmaps anyway + + // check for lightmap modification + for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ ) + { + if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] ) + goto dynamic; + } + + // dynamic this frame or dynamic previously + if(( fa->dlightframe == tr.framecount )) + { +dynamic: + // NOTE: at this point we have only valid textures + if( r_dynamic->value ) is_dynamic = true; + } + + if( is_dynamic ) + { + if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 || fa->styles[maps] == 20 ) && ( fa->dlightframe != tr.framecount )) + { + byte temp[132*132*4]; + mextrasurf_t *info = fa->info; + int sample_size; + int smax, tmax; + + sample_size = Mod_SampleSizeForFace( fa ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + R_BuildLightMap( fa, temp, smax * 4, true ); + R_SetCacheState( fa ); + + GL_Bind( GL_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] ); + + pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax, + GL_RGBA, GL_UNSIGNED_BYTE, temp ); + + fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; + gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; + } + else + { + fa->info->lightmapchain = gl_lms.dynamic_surfaces; + gl_lms.dynamic_surfaces = fa; + } + } + else + { + fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum]; + gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa; + } +} + +/* +================ +R_DrawTextureChains +================ +*/ +void R_DrawTextureChains( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + // make sure what color is reset + pglColor4ub( 255, 255, 255, 255 ); + R_LoadIdentity(); // set identity matrix + + GL_SetupFogColorForSurfaces(); + + // restore worldmodel + RI.currententity = clgame.entities; + RI.currentmodel = RI.currententity->model; + + if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX )) + { + pglDisable( GL_TEXTURE_2D ); + pglColor3f( 1.0f, 1.0f, 1.0f ); + } + + // clip skybox surfaces + for( s = skychain; s != NULL; s = s->texturechain ) + R_AddSkyBoxSurface( s ); + + if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX )) + { + pglEnable( GL_TEXTURE_2D ); + if( skychain ) + R_DrawClouds(); + skychain = NULL; + } + + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + t = cl.worldmodel->textures[i]; + if( !t ) continue; + + s = t->texturechain; + + if( !s || ( i == tr.skytexturenum )) + continue; + + if(( s->flags & SURF_DRAWTURB ) && clgame.movevars.wateralpha < 1.0f ) + continue; // draw translucent water later + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT )) + { + draw_alpha_surfaces = true; + continue; // draw transparent surfaces later + } + + for( ; s != NULL; s = s->texturechain ) + R_RenderBrushPoly( s, CULL_VISIBLE ); + t->texturechain = NULL; + } +} + +/* +================ +R_DrawAlphaTextureChains +================ +*/ +void R_DrawAlphaTextureChains( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + if( !draw_alpha_surfaces ) + return; + + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + gl_lms.dynamic_surfaces = NULL; + + // make sure what color is reset + pglColor4ub( 255, 255, 255, 255 ); + R_LoadIdentity(); // set identity matrix + + pglDisable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.25f ); + + GL_SetupFogColorForSurfaces(); + + // restore worldmodel + RI.currententity = clgame.entities; + RI.currentmodel = RI.currententity->model; + RI.currententity->curstate.rendermode = kRenderTransAlpha; + draw_alpha_surfaces = false; + + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + t = cl.worldmodel->textures[i]; + if( !t ) continue; + + s = t->texturechain; + + if( !s || !FBitSet( s->flags, SURF_TRANSPARENT )) + continue; + + for( ; s != NULL; s = s->texturechain ) + R_RenderBrushPoly( s, CULL_VISIBLE ); + t->texturechain = NULL; + } + + GL_ResetFogColor(); + R_BlendLightmaps(); + RI.currententity->curstate.rendermode = kRenderNormal; // restore world rendermode + pglAlphaFunc( GL_GREATER, 0.0f ); +} + +/* +================ +R_DrawWaterSurfaces +================ +*/ +void R_DrawWaterSurfaces( void ) +{ + int i; + msurface_t *s; + texture_t *t; + + if( !RI.drawWorld || RI.onlyClientDraw ) + return; + + // non-transparent water is already drawed + if( clgame.movevars.wateralpha >= 1.0f ) + return; + + // restore worldmodel + RI.currententity = clgame.entities; + RI.currentmodel = RI.currententity->model; + + // go back to the world matrix + R_LoadIdentity(); + + pglEnable( GL_BLEND ); + pglDepthMask( GL_FALSE ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4f( 1.0f, 1.0f, 1.0f, clgame.movevars.wateralpha ); + + for( i = 0; i < cl.worldmodel->numtextures; i++ ) + { + t = cl.worldmodel->textures[i]; + if( !t ) continue; + + s = t->texturechain; + if( !s ) continue; + + if( !FBitSet( s->flags, SURF_DRAWTURB )) + continue; + + // set modulate mode explicitly + GL_Bind( GL_TEXTURE0, t->gl_texturenum ); + + for( ; s; s = s->texturechain ) + EmitWaterPolys( s, false ); + + t->texturechain = NULL; + } + + pglDisable( GL_BLEND ); + pglDepthMask( GL_TRUE ); + pglDisable( GL_ALPHA_TEST ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +================= +R_SurfaceCompare + +compare translucent surfaces +================= +*/ +static int R_SurfaceCompare( const sortedface_t *a, const sortedface_t *b ) +{ + msurface_t *surf1, *surf2; + vec3_t org1, org2; + float len1, len2; + + surf1 = (msurface_t *)a->surf; + surf2 = (msurface_t *)b->surf; + + VectorAdd( RI.currententity->origin, surf1->info->origin, org1 ); + VectorAdd( RI.currententity->origin, surf2->info->origin, org2 ); + + // compare by plane dists + len1 = DotProduct( org1, RI.vforward ) - RI.viewplanedist; + len2 = DotProduct( org2, RI.vforward ) - RI.viewplanedist; + + if( len1 > len2 ) + return -1; + if( len1 < len2 ) + return 1; + + return 0; +} + +void R_SetRenderMode( cl_entity_t *e ) +{ + switch( e->curstate.rendermode ) + { + case kRenderNormal: + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + break; + case kRenderTransColor: + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, e->curstate.renderamt ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_TEXTURE_2D ); + pglEnable( GL_BLEND ); + break; + case kRenderTransAdd: + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4f( tr.blend, tr.blend, tr.blend, 1.0f ); + pglBlendFunc( GL_ONE, GL_ONE ); + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + break; + case kRenderTransAlpha: + pglEnable( GL_ALPHA_TEST ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend ); + pglEnable( GL_BLEND ); + } + else + { + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglDisable( GL_BLEND ); + } + pglAlphaFunc( GL_GREATER, 0.25f ); + break; + default: + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend ); + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + break; + } +} + +/* +================= +R_DrawBrushModel +================= +*/ +void R_DrawBrushModel( cl_entity_t *e ) +{ + int i, k, num_sorted; + vec3_t origin_l, oldorigin; + int old_rendermode; + vec3_t mins, maxs; + int cull_type; + msurface_t *psurf; + model_t *clmodel; + qboolean rotated; + dlight_t *l; + + if( !RI.drawWorld ) return; + + clmodel = e->model; + + if( !VectorIsNull( e->angles )) + { + for( i = 0; i < 3; i++ ) + { + mins[i] = e->origin[i] - clmodel->radius; + maxs[i] = e->origin[i] + clmodel->radius; + } + rotated = true; + } + else + { + VectorAdd( e->origin, clmodel->mins, mins ); + VectorAdd( e->origin, clmodel->maxs, maxs ); + rotated = false; + } + + if( R_CullBox( mins, maxs )) + return; + + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + old_rendermode = e->curstate.rendermode; + gl_lms.dynamic_surfaces = NULL; + + if( rotated ) R_RotateForEntity( e ); + else R_TranslateForEntity( e ); + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FBitSet( clmodel->flags, MODEL_TRANSPARENT )) + e->curstate.rendermode = kRenderTransAlpha; + + e->visframe = tr.realframecount; // visible + + if( rotated ) Matrix4x4_VectorITransform( RI.objectMatrix, RI.cullorigin, tr.modelorg ); + else VectorSubtract( RI.cullorigin, e->origin, tr.modelorg ); + + // calculate dynamic lighting for bmodel + for( k = 0, l = cl_dlights; k < MAX_DLIGHTS; k++, l++ ) + { + if( l->die < cl.time || !l->radius ) + continue; + + VectorCopy( l->origin, oldorigin ); // save lightorigin + Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l ); + VectorCopy( origin_l, l->origin ); // move light in bmodel space + R_MarkLights( l, 1<nodes + clmodel->hulls[0].firstclipnode ); + VectorCopy( oldorigin, l->origin ); // restore lightorigin + } + + // setup the rendermode + R_SetRenderMode( e ); + GL_SetupFogColorForSurfaces (); + + if( e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( false ); + + psurf = &clmodel->surfaces[clmodel->firstmodelsurface]; + num_sorted = 0; + + for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ ) + { + if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + if( psurf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES )) + continue; + if( mins[2] + 1.0 >= psurf->plane->dist ) + continue; + } + + cull_type = R_CullSurface( psurf, &RI.frustum, RI.frustum.clipFlags ); + + if( cull_type >= CULL_FRUSTUM ) + continue; + + if( cull_type == CULL_BACKSIDE ) + { + if( !FBitSet( psurf->flags, SURF_DRAWTURB ) && !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture )) + continue; + } + + if( num_sorted < world.max_surfaces ) + { + world.draw_surfaces[num_sorted].surf = psurf; + world.draw_surfaces[num_sorted].cull = cull_type; + num_sorted++; + } + } + + // sort faces if needs + if( !FBitSet( clmodel->flags, MODEL_LIQUID ) && e->curstate.rendermode == kRenderTransTexture && !gl_nosort->value ) + qsort( world.draw_surfaces, num_sorted, sizeof( sortedface_t ), R_SurfaceCompare ); + + // draw sorted translucent surfaces + for( i = 0; i < num_sorted; i++ ) + R_RenderBrushPoly( world.draw_surfaces[i].surf, world.draw_surfaces[i].cull ); + + if( e->curstate.rendermode == kRenderTransColor ) + pglEnable( GL_TEXTURE_2D ); + + DrawDecalsBatch(); + GL_ResetFogColor(); + R_BlendLightmaps(); + R_RenderFullbrights(); + R_RenderDetails(); + + // restore fog here + if( e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( true ); + + e->curstate.rendermode = old_rendermode; + pglDisable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglDisable( GL_BLEND ); + pglDepthMask( GL_TRUE ); + R_LoadIdentity(); // restore worldmatrix +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ +/* +================ +R_RecursiveWorldNode +================ +*/ +void R_RecursiveWorldNode( mnode_t *node, uint clipflags ) +{ + int i, clipped; + msurface_t *surf, **mark; + mleaf_t *pleaf; + int c, side; + float dot; + + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags && !r_nocull->value ) + { + for( i = 0; i < 6; i++ ) + { + const mplane_t *p = &RI.frustum.planes[i]; + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + pleaf = (mleaf_t *)node; + + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + if( c ) + { + do + { + (*mark)->visframe = tr.framecount; + mark++; + } while( --c ); + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; + return; + } + + // node is just a decision point, so go down the apropriate sides + + // find which side of the node we are on + dot = PlaneDiff( tr.modelorg, node->plane ); + side = (dot >= 0.0f) ? 0 : 1; + + // recurse down the children, front side first + R_RecursiveWorldNode( node->children[side], clipflags ); + + // draw stuff + for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) + { + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if( surf->flags & SURF_DRAWSKY ) + { + // make sky chain to right clip the skybox + surf->texturechain = skychain; + skychain = surf; + } + else + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // recurse down the back side + R_RecursiveWorldNode( node->children[!side], clipflags ); +} + +/* +================ +R_CullNodeTopView + +cull node by user rectangle (simple scissor) +================ +*/ +qboolean R_CullNodeTopView( mnode_t *node ) +{ + vec2_t delta, size; + vec3_t center, half; + + // build the node center and half-diagonal + VectorAverage( node->minmaxs, node->minmaxs + 3, center ); + VectorSubtract( node->minmaxs + 3, center, half ); + + // cull against the screen frustum or the appropriate area's frustum. + Vector2Subtract( center, world_orthocenter, delta ); + Vector2Add( half, world_orthohalf, size ); + + return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] ); +} + +/* +================ +R_DrawTopViewLeaf +================ +*/ +static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags ) +{ + msurface_t **mark, *surf; + int i; + + for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ ) + { + surf = *mark; + + // don't process the same surface twice + if( surf->visframe == tr.framecount ) + continue; + + surf->visframe = tr.framecount; + + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if(!( surf->flags & SURF_DRAWSKY )) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + R_StoreEfrags( &pleaf->efrags, tr.realframecount ); + + r_stats.c_world_leafs++; +} + +/* +================ +R_DrawWorldTopView +================ +*/ +void R_DrawWorldTopView( mnode_t *node, uint clipflags ) +{ + int i, c, clipped; + msurface_t *surf; + + do + { + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags && !r_nocull->value ) + { + for( i = 0; i < 6; i++ ) + { + const mplane_t *p = &RI.frustum.planes[i]; + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // cull against the screen frustum or the appropriate area's frustum. + if( R_CullNodeTopView( node )) + return; + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + R_DrawTopViewLeaf( (mleaf_t *)node, clipflags ); + return; + } + + // draw stuff + for( c = node->numsurfaces, surf = cl.worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) + { + // don't process the same surface twice + if( surf->visframe == tr.framecount ) + continue; + + surf->visframe = tr.framecount; + + if( R_CullSurface( surf, &RI.frustum, clipflags )) + continue; + + if(!( surf->flags & SURF_DRAWSKY )) + { + surf->texturechain = surf->texinfo->texture->texturechain; + surf->texinfo->texture->texturechain = surf; + } + } + + // recurse down both children, we don't care the order... + R_DrawWorldTopView( node->children[0], clipflags ); + node = node->children[1]; + + } while( node ); +} + +/* +============= +R_DrawTriangleOutlines +============= +*/ +void R_DrawTriangleOutlines( void ) +{ + int i, j; + msurface_t *surf; + glpoly_t *p; + float *v; + + if( !gl_wireframe->value ) + return; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + // render static surfaces first + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain ) + { + p = surf->polys; + for( ; p != NULL; p = p->chain ) + { + pglBegin( GL_POLYGON ); + v = p->verts[0]; + for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) + pglVertex3fv( v ); + pglEnd (); + } + } + } + + // render surfaces with dynamic lightmaps + for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain ) + { + p = surf->polys; + + for( ; p != NULL; p = p->chain ) + { + pglBegin( GL_POLYGON ); + v = p->verts[0]; + for( j = 0; j < p->numverts; j++, v += VERTEXSIZE ) + pglVertex3fv( v ); + pglEnd (); + } + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); +} + +/* +============= +R_DrawWorld +============= +*/ +void R_DrawWorld( void ) +{ + // paranoia issues: when gl_renderer is "0" we need have something valid for currententity + // to prevent crashing until HeadShield drawing. + RI.currententity = clgame.entities; + RI.currentmodel = RI.currententity->model; + + if( !RI.drawWorld || RI.onlyClientDraw ) + return; + + VectorCopy( RI.cullorigin, tr.modelorg ); + memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces )); + memset( fullbright_surfaces, 0, sizeof( fullbright_surfaces )); + memset( detail_surfaces, 0, sizeof( detail_surfaces )); + + gl_lms.dynamic_surfaces = NULL; + pglDisable( GL_ALPHA_TEST ); + pglDisable( GL_BLEND ); + tr.blend = 1.0f; + + R_ClearSkyBox (); + + if( RI.drawOrtho ) + R_DrawWorldTopView( cl.worldmodel->nodes, RI.frustum.clipFlags ); + else R_RecursiveWorldNode( cl.worldmodel->nodes, RI.frustum.clipFlags ); + + R_DrawTextureChains(); + + if( !CL_IsDevOverviewMode( )) + { + DrawDecalsBatch(); + GL_ResetFogColor(); + R_BlendLightmaps(); + R_RenderFullbrights(); + R_RenderDetails(); + + if( skychain ) + R_DrawSkyBox(); + } + + tr.num_draw_decals = 0; + skychain = NULL; + + R_DrawTriangleOutlines (); +} + +/* +=============== +R_MarkLeaves + +Mark the leaves and nodes that are in the PVS for the current leaf +=============== +*/ +void R_MarkLeaves( void ) +{ + qboolean novis = false; + qboolean force = false; + mleaf_t *leaf = NULL; + mnode_t *node; + vec3_t test; + int i; + + if( !RI.drawWorld ) return; + + if( FBitSet( r_novis->flags, FCVAR_CHANGED ) || tr.fResetVis ) + { + // force recalc viewleaf + ClearBits( r_novis->flags, FCVAR_CHANGED ); + tr.fResetVis = false; + RI.viewleaf = NULL; + } + + VectorCopy( RI.pvsorigin, test ); + + if( RI.viewleaf != NULL ) + { + // merge two leafs that can be a crossed-line contents + if( RI.viewleaf->contents == CONTENTS_EMPTY ) + { + VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] - 16.0f ); + leaf = Mod_PointInLeaf( test, cl.worldmodel->nodes ); + } + else + { + VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] + 16.0f ); + leaf = Mod_PointInLeaf( test, cl.worldmodel->nodes ); + } + + if(( leaf->contents != CONTENTS_SOLID ) && ( RI.viewleaf != leaf )) + force = true; + } + + if( RI.viewleaf == RI.oldviewleaf && RI.viewleaf != NULL && !force ) + return; + + // development aid to let you run around + // and see exactly where the pvs ends + if( r_lockpvs->value ) return; + + RI.oldviewleaf = RI.viewleaf; + tr.visframecount++; + + if( r_novis->value || RI.drawOrtho || !RI.viewleaf || !cl.worldmodel->visdata ) + novis = true; + + Mod_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, world.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis ); + if( force && !novis ) Mod_FatPVS( test, REFPVS_RADIUS, RI.visbytes, world.visbytes, true, novis ); + + for( i = 0; i < cl.worldmodel->numleafs; i++ ) + { + if( CHECKVISBIT( RI.visbytes, i )) + { + node = (mnode_t *)&cl.worldmodel->leafs[i+1]; + do + { + if( node->visframe == tr.visframecount ) + break; + node->visframe = tr.visframecount; + node = node->parent; + } while( node ); + } + } +} + +/* +======================== +GL_CreateSurfaceLightmap +======================== +*/ +void GL_CreateSurfaceLightmap( msurface_t *surf ) +{ + int smax, tmax; + int sample_size; + mextrasurf_t *info = surf->info; + byte *base; + + if( !cl.worldmodel->lightdata ) return; + if( surf->flags & SURF_DRAWTILED ) + return; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = ( info->lightextents[0] / sample_size ) + 1; + tmax = ( info->lightextents[1] / sample_size ) + 1; + + if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t )) + { + LM_UploadBlock( false ); + LM_InitBlock(); + + if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t )) + Host_Error( "AllocBlock: full\n" ); + } + + surf->lightmaptexturenum = gl_lms.current_lightmap_texture; + + base = gl_lms.lightmap_buffer; + base += ( surf->light_t * BLOCK_SIZE + surf->light_s ) * 4; + + R_SetCacheState( surf ); + R_BuildLightMap( surf, base, BLOCK_SIZE * 4, false ); +} + +/* +================== +GL_RebuildLightmaps + +Rebuilds the lightmap texture +when gamma is changed +================== +*/ +void GL_RebuildLightmaps( void ) +{ + int i, j; + model_t *m; + + if( !cl.video_prepped ) + return; // wait for worldmodel + + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); + + // release old lightmaps + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( !tr.lightmapTextures[i] ) break; + GL_FreeTexture( tr.lightmapTextures[i] ); + } + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); + gl_lms.current_lightmap_texture = 0; + + // setup all the lightstyles + CL_RunLightStyles(); + + LM_InitBlock(); + + for( i = 0; i < cl.nummodels; i++ ) + { + if(( m = CL_ModelHandle( i + 1 )) == NULL ) + continue; + + if( m->name[0] == '*' || m->type != mod_brush ) + continue; + + loadmodel = m; + + for( j = 0; j < m->numsurfaces; j++ ) + GL_CreateSurfaceLightmap( m->surfaces + j ); + } + LM_UploadBlock( false ); + + if( clgame.drawFuncs.GL_BuildLightmaps ) + { + // build lightmaps on the client-side + clgame.drawFuncs.GL_BuildLightmaps( ); + } +} + +/* +================== +GL_BuildLightmaps + +Builds the lightmap texture +with all the surfaces from all brush models +================== +*/ +void GL_BuildLightmaps( void ) +{ + int i, j; + model_t *m; + + // release old lightmaps + for( i = 0; i < MAX_LIGHTMAPS; i++ ) + { + if( !tr.lightmapTextures[i] ) break; + GL_FreeTexture( tr.lightmapTextures[i] ); + } + + memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures )); + memset( &RI, 0, sizeof( RI )); + + // update the lightmap blocksize + if( FBitSet( host.features, ENGINE_LARGE_LIGHTMAPS )) + tr.block_size = BLOCK_SIZE_MAX; + else tr.block_size = BLOCK_SIZE_DEFAULT; + + skychain = NULL; + + tr.framecount = tr.visframecount = 1; // no dlight cache + gl_lms.current_lightmap_texture = 0; + tr.modelviewIdentity = false; + tr.realframecount = 1; + nColinElim = 0; + + // setup all the lightstyles + CL_RunLightStyles(); + + LM_InitBlock(); + + for( i = 0; i < cl.nummodels; i++ ) + { + if(( m = CL_ModelHandle( i + 1 )) == NULL ) + continue; + + if( m->name[0] == '*' || m->type != mod_brush ) + continue; + + for( j = 0; j < m->numsurfaces; j++ ) + { + // clearing all decal chains + m->surfaces[j].pdecals = NULL; + m->surfaces[j].visframe = 0; + loadmodel = m; + + GL_CreateSurfaceLightmap( m->surfaces + j ); + + if( m->surfaces[j].flags & SURF_DRAWTURB ) + continue; + + GL_BuildPolygonFromSurface( m, m->surfaces + j ); + } + + // clearing visframe + for( j = 0; j < m->numleafs; j++ ) + m->leafs[j+1].visframe = 0; + for( j = 0; j < m->numnodes; j++ ) + m->nodes[j].visframe = 0; + } + + LM_UploadBlock( false ); + + if( clgame.drawFuncs.GL_BuildLightmaps ) + { + // build lightmaps on the client-side + clgame.drawFuncs.GL_BuildLightmaps( ); + } + + // now gamma and brightness are valid + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); + + if( !gl_keeptjunctions->value ) + MsgDev( D_INFO, "Eliminate %i vertexes\n", nColinElim ); +} + +void GL_InitRandomTable( void ) +{ + int tu, tv; + + // make random predictable + COM_SetRandomSeed( 255 ); + + for( tu = 0; tu < MOD_FRAMES; tu++ ) + { + for( tv = 0; tv < MOD_FRAMES; tv++ ) + { + rtable[tu][tv] = COM_RandomLong( 0, 0x7FFF ); + } + } + + COM_SetRandomSeed( 0 ); +} \ No newline at end of file diff --git a/engine/client/gl_sprite.c b/engine/client/gl_sprite.c new file mode 100644 index 00000000..b52c9033 --- /dev/null +++ b/engine/client/gl_sprite.c @@ -0,0 +1,1091 @@ +/* +gl_sprite.c - sprite rendering +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "pm_local.h" +#include "sprite.h" +#include "studio.h" +#include "entity_types.h" +#include "cl_tent.h" + +// it's a Valve default value for LoadMapSprite (probably must be power of two) +#define MAPSPRITE_SIZE 128 +#define GLARE_FALLOFF 19000.0f + +convar_t *r_sprite_lerping; +convar_t *r_sprite_lighting; +char group_suffix[8]; +static uint r_texFlags = 0; +static int sprite_version; +float sprite_radius; + +/* +==================== +R_SpriteInit + +==================== +*/ +void R_SpriteInit( void ) +{ + r_sprite_lerping = Cvar_Get( "r_sprite_lerping", "1", FCVAR_ARCHIVE, "enables sprite animation lerping" ); + r_sprite_lighting = Cvar_Get( "r_sprite_lighting", "1", FCVAR_ARCHIVE, "enables sprite lighting (blood etc)" ); +} + +/* +==================== +R_SpriteLoadFrame + +upload a single frame +==================== +*/ +static dframetype_t *R_SpriteLoadFrame( model_t *mod, void *pin, mspriteframe_t **ppframe, int num ) +{ + dspriteframe_t *pinframe; + mspriteframe_t *pspriteframe; + int gl_texturenum = 0; + char texname[128]; + int bytes = 1; + + pinframe = (dspriteframe_t *)pin; + if( sprite_version == SPRITE_VERSION_32 ) + bytes = 4; + + // build uinque frame name + if( FBitSet( mod->flags, MODEL_CLIENT )) // it's a HUD sprite + { + Q_snprintf( texname, sizeof( texname ), "#HUD/%s_%s_%i%i.spr", mod->name, group_suffix, num / 10, num % 10 ); + gl_texturenum = GL_LoadTexture( texname, pin, pinframe->width * pinframe->height * bytes, r_texFlags, NULL ); + } + else + { + Q_snprintf( texname, sizeof( texname ), "#%s_%s_%i%i.spr", mod->name, group_suffix, num / 10, num % 10 ); + gl_texturenum = GL_LoadTexture( texname, pin, pinframe->width * pinframe->height * bytes, r_texFlags, NULL ); + } + + // setup frame description + pspriteframe = Mem_Alloc( mod->mempool, sizeof( mspriteframe_t )); + pspriteframe->width = pinframe->width; + pspriteframe->height = pinframe->height; + pspriteframe->up = pinframe->origin[1]; + pspriteframe->left = pinframe->origin[0]; + pspriteframe->down = pinframe->origin[1] - pinframe->height; + pspriteframe->right = pinframe->width + pinframe->origin[0]; + pspriteframe->gl_texturenum = gl_texturenum; + *ppframe = pspriteframe; + + return (dframetype_t *)((byte *)(pinframe + 1) + pinframe->width * pinframe->height * bytes ); +} + +/* +==================== +R_SpriteLoadGroup + +upload a group frames +==================== +*/ +static dframetype_t *R_SpriteLoadGroup( model_t *mod, void *pin, mspriteframe_t **ppframe, int framenum ) +{ + dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + dspriteinterval_t *pin_intervals; + float *poutintervals; + int i, groupsize, numframes; + void *ptemp; + + pingroup = (dspritegroup_t *)pin; + numframes = pingroup->numframes; + + groupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] ); + pspritegroup = Mem_Alloc( mod->mempool, groupsize ); + pspritegroup->numframes = numframes; + + *ppframe = (mspriteframe_t *)pspritegroup; + pin_intervals = (dspriteinterval_t *)(pingroup + 1); + poutintervals = Mem_Alloc( mod->mempool, numframes * sizeof( float )); + pspritegroup->intervals = poutintervals; + + for( i = 0; i < numframes; i++ ) + { + *poutintervals = pin_intervals->interval; + if( *poutintervals <= 0.0f ) + *poutintervals = 1.0f; // set error value + poutintervals++; + pin_intervals++; + } + + ptemp = (void *)pin_intervals; + for( i = 0; i < numframes; i++ ) + { + ptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); + } + + return (dframetype_t *)ptemp; +} + +/* +==================== +Mod_LoadSpriteModel + +load sprite model +==================== +*/ +void Mod_LoadSpriteModel( model_t *mod, const void *buffer, qboolean *loaded, uint texFlags ) +{ + dsprite_q1_t *pinq1; + dsprite_hl_t *pinhl; + dsprite_t *pin; + short *numi = NULL; + dframetype_t *pframetype; + msprite_t *psprite; + int i, size; + + if( loaded ) *loaded = false; + pin = (dsprite_t *)buffer; + mod->type = mod_sprite; + r_texFlags = texFlags; + i = pin->version; + + if( pin->ident != IDSPRITEHEADER ) + { + MsgDev( D_ERROR, "%s has wrong id (%x should be %x)\n", mod->name, pin->ident, IDSPRITEHEADER ); + return; + } + + if( i != SPRITE_VERSION_Q1 && i != SPRITE_VERSION_HL && i != SPRITE_VERSION_32 ) + { + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i or %i)\n", mod->name, i, SPRITE_VERSION_Q1, SPRITE_VERSION_HL ); + return; + } + + mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + sprite_version = i; + + if( i == SPRITE_VERSION_Q1 || i == SPRITE_VERSION_32 ) + { + pinq1 = (dsprite_q1_t *)buffer; + size = sizeof( msprite_t ) + ( pinq1->numframes - 1 ) * sizeof( psprite->frames ); + psprite = Mem_Alloc( mod->mempool, size ); + mod->cache.data = psprite; // make link to extradata + + psprite->type = pinq1->type; + psprite->texFormat = SPR_ADDITIVE; //SPR_ALPHTEST; + psprite->numframes = mod->numframes = pinq1->numframes; + psprite->facecull = SPR_CULL_FRONT; + psprite->radius = pinq1->boundingradius; + psprite->synctype = pinq1->synctype; + + mod->mins[0] = mod->mins[1] = -pinq1->bounds[0] * 0.5f; + mod->maxs[0] = mod->maxs[1] = pinq1->bounds[0] * 0.5f; + mod->mins[2] = -pinq1->bounds[1] * 0.5f; + mod->maxs[2] = pinq1->bounds[1] * 0.5f; + numi = NULL; + } + else if( i == SPRITE_VERSION_HL ) + { + pinhl = (dsprite_hl_t *)buffer; + size = sizeof( msprite_t ) + ( pinhl->numframes - 1 ) * sizeof( psprite->frames ); + psprite = Mem_Alloc( mod->mempool, size ); + mod->cache.data = psprite; // make link to extradata + + psprite->type = pinhl->type; + psprite->texFormat = pinhl->texFormat; + psprite->numframes = mod->numframes = pinhl->numframes; + psprite->facecull = pinhl->facetype; + psprite->radius = pinhl->boundingradius; + psprite->synctype = pinhl->synctype; + + mod->mins[0] = mod->mins[1] = -pinhl->bounds[0] * 0.5f; + mod->maxs[0] = mod->maxs[1] = pinhl->bounds[0] * 0.5f; + mod->mins[2] = -pinhl->bounds[1] * 0.5f; + mod->maxs[2] = pinhl->bounds[1] * 0.5f; + numi = (short *)(pinhl + 1); + } + + if( host.type == HOST_DEDICATED ) + { + // skip frames loading + if( loaded ) *loaded = true; // done + psprite->numframes = 0; + return; + } + + if( numi == NULL ) + { + rgbdata_t *pal; + + pal = FS_LoadImage( "#id.pal", (byte *)&i, 768 ); + pframetype = (dframetype_t *)(pinq1 + 1); + FS_FreeImage( pal ); // palette installed, no reason to keep this data + } + else if( *numi == 256 ) + { + byte *src = (byte *)(numi+1); + rgbdata_t *pal; + + // install palette + switch( psprite->texFormat ) + { + case SPR_INDEXALPHA: + pal = FS_LoadImage( "#gradient.pal", src, 768 ); + break; + case SPR_ALPHTEST: + pal = FS_LoadImage( "#masked.pal", src, 768 ); + break; + default: + pal = FS_LoadImage( "#normal.pal", src, 768 ); + break; + } + + pframetype = (dframetype_t *)(src + 768); + FS_FreeImage( pal ); // palette installed, no reason to keep this data + } + else + { + MsgDev( D_ERROR, "%s has wrong number of palette colors %i (should be 256)\n", mod->name, *numi ); + return; + } + + if( mod->numframes < 1 ) + { + MsgDev( D_ERROR, "%s has invalid # of frames: %d\n", mod->name, mod->numframes ); + return; + } + + for( i = 0; i < mod->numframes; i++ ) + { + frametype_t frametype = pframetype->type; + psprite->frames[i].type = frametype; + + switch( frametype ) + { + case FRAME_SINGLE: + Q_strncpy( group_suffix, "one", sizeof( group_suffix )); + pframetype = R_SpriteLoadFrame( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + case FRAME_GROUP: + Q_strncpy( group_suffix, "grp", sizeof( group_suffix )); + pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + case FRAME_ANGLED: + Q_strncpy( group_suffix, "ang", sizeof( group_suffix )); + pframetype = R_SpriteLoadGroup( mod, pframetype + 1, &psprite->frames[i].frameptr, i ); + break; + } + if( pframetype == NULL ) break; // technically an error + } + + if( loaded ) *loaded = true; // done +} + +/* +==================== +Mod_LoadMapSprite + +Loading a bitmap image as sprite with multiple frames +as pieces of input image +==================== +*/ +void Mod_LoadMapSprite( model_t *mod, const void *buffer, size_t size, qboolean *loaded ) +{ + byte *src, *dst; + rgbdata_t *pix, temp; + char texname[128]; + int i, j, x, y, w, h; + int xl, yl, xh, yh; + int linedelta, numframes; + mspriteframe_t *pspriteframe; + msprite_t *psprite; + + if( loaded ) *loaded = false; + Q_snprintf( texname, sizeof( texname ), "#%s", mod->name ); + Image_SetForceFlags( IL_OVERVIEW ); + pix = FS_LoadImage( texname, buffer, size ); + Image_ClearForceFlags(); + if( !pix ) return; // bad image or something else + + mod->type = mod_sprite; + r_texFlags = 0; // no custom flags for map sprites + + if( pix->width % MAPSPRITE_SIZE ) + w = pix->width - ( pix->width % MAPSPRITE_SIZE ); + else w = pix->width; + + if( pix->height % MAPSPRITE_SIZE ) + h = pix->height - ( pix->height % MAPSPRITE_SIZE ); + else h = pix->height; + + if( w < MAPSPRITE_SIZE ) w = MAPSPRITE_SIZE; + if( h < MAPSPRITE_SIZE ) h = MAPSPRITE_SIZE; + + // resample image if needed + Image_Process( &pix, w, h, IMAGE_FORCE_RGBA|IMAGE_RESAMPLE, NULL ); + + w = h = MAPSPRITE_SIZE; + + // check range + if( w > pix->width ) w = pix->width; + if( h > pix->height ) h = pix->height; + + // determine how many frames we needs + numframes = (pix->width * pix->height) / (w * h); + mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); + psprite = Mem_Alloc( mod->mempool, sizeof( msprite_t ) + ( numframes - 1 ) * sizeof( psprite->frames )); + mod->cache.data = psprite; // make link to extradata + + psprite->type = SPR_FWD_PARALLEL_ORIENTED; + psprite->texFormat = SPR_ALPHTEST; + psprite->numframes = mod->numframes = numframes; + psprite->radius = sqrt(((w >> 1) * (w >> 1)) + ((h >> 1) * (h >> 1))); + + mod->mins[0] = mod->mins[1] = -w / 2; + mod->maxs[0] = mod->maxs[1] = w / 2; + mod->mins[2] = -h / 2; + mod->maxs[2] = h / 2; + + // create a temporary pic + memset( &temp, 0, sizeof( temp )); + temp.width = w; + temp.height = h; + temp.type = pix->type; + temp.flags = pix->flags; + temp.size = w * h * PFDesc[temp.type].bpp; + temp.buffer = Mem_Alloc( r_temppool, temp.size ); + temp.palette = NULL; + + // chop the image and upload into video memory + for( i = xl = yl = 0; i < numframes; i++ ) + { + xh = xl + w; + yh = yl + h; + + src = pix->buffer + ( yl * pix->width + xl ) * 4; + linedelta = ( pix->width - w ) * 4; + dst = temp.buffer; + + // cut block from source + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + for( j = 0; j < 4; j++ ) + *dst++ = *src++; + src += linedelta; + } + + // build uinque frame name + Q_snprintf( texname, sizeof( texname ), "#MAP/%s_%i%i.spr", mod->name, i / 10, i % 10 ); + + psprite->frames[i].frameptr = Mem_Alloc( mod->mempool, sizeof( mspriteframe_t )); + pspriteframe = psprite->frames[i].frameptr; + pspriteframe->width = w; + pspriteframe->height = h; + pspriteframe->up = ( h >> 1 ); + pspriteframe->left = -( w >> 1 ); + pspriteframe->down = ( h >> 1 ) - h; + pspriteframe->right = w + -( w >> 1 ); + pspriteframe->gl_texturenum = GL_LoadTextureInternal( texname, &temp, TF_IMAGE, false ); + + xl += w; + if( xl >= pix->width ) + { + xl = 0; + yl += h; + } + } + + FS_FreeImage( pix ); + Mem_Free( temp.buffer ); + + if( loaded ) *loaded = true; +} + +/* +==================== +Mod_UnloadSpriteModel + +release sprite model and frames +==================== +*/ +void Mod_UnloadSpriteModel( model_t *mod ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe; + int i, j; + + Assert( mod != NULL ); + + if( mod->type == mod_sprite ) + { + if( host.type != HOST_DEDICATED ) + { + psprite = mod->cache.data; + + if( psprite ) + { + // release all textures + for( i = 0; i < psprite->numframes; i++ ) + { + if( psprite->frames[i].type == SPR_SINGLE ) + { + pspriteframe = psprite->frames[i].frameptr; + GL_FreeTexture( pspriteframe->gl_texturenum ); + } + else + { + pspritegroup = (mspritegroup_t *)psprite->frames[i].frameptr; + + for( j = 0; j < pspritegroup->numframes; j++ ) + { + pspriteframe = pspritegroup->frames[i]; + GL_FreeTexture( pspriteframe->gl_texturenum ); + } + } + } + } + } + } + + Mem_FreePool( &mod->mempool ); + memset( mod, 0, sizeof( *mod )); +} + +/* +================ +R_GetSpriteFrame + +assume pModel is valid +================ +*/ +mspriteframe_t *R_GetSpriteFrame( const model_t *pModel, int frame, float yaw ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe = NULL; + float *pintervals, fullinterval; + int i, numframes; + float targettime; + + Assert( pModel != NULL ); + psprite = pModel->cache.data; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + if( frame > psprite->numframes ) + MsgDev( D_WARN, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, pModel->name ); + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == SPR_SINGLE ) + { + pspriteframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == SPR_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = cl.time - ((int)( cl.time / fullinterval )) * fullinterval; + + for( i = 0; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + } + pspriteframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + + // e.g. doom-style sprite monsters + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pspriteframe = pspritegroup->frames[angleframe]; + } + + return pspriteframe; +} + +/* +================ +R_GetSpriteFrameInterpolant + +NOTE: we using prevblending[0] and [1] for holds interval +between frames where are we lerping +================ +*/ +float R_GetSpriteFrameInterpolant( cl_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) +{ + msprite_t *psprite; + mspritegroup_t *pspritegroup; + int i, j, numframes, frame; + float lerpFrac, time, jtime, jinterval; + float *pintervals, fullinterval, targettime; + int m_fDoInterp; + + psprite = ent->model->cache.data; + frame = (int)ent->curstate.frame; + lerpFrac = 1.0f; + + // misc info + m_fDoInterp = (ent->curstate.effects & EF_NOINTERP) ? false : true; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + MsgDev( D_WARN, "R_GetSpriteFrameInterpolant: no such frame %d (%s)\n", frame, ent->model->name ); + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == FRAME_SINGLE ) + { + if( m_fDoInterp ) + { + if( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_SINGLE ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 1.0f; + } + + if( ent->latched.sequencetime < cl.time ) + { + if( frame != ent->latched.prevblending[1] ) + { + ent->latched.prevblending[0] = ent->latched.prevblending[1]; + ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 0.0f; + } + else lerpFrac = (cl.time - ent->latched.sequencetime) * 11.0f; + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 0.0f; + } + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + lerpFrac = 1.0f; + } + + if( ent->latched.prevblending[0] >= psprite->numframes ) + { + // reset interpolation on change model + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 0.0f; + } + + // get the interpolated frames + if( oldframe ) *oldframe = psprite->frames[ent->latched.prevblending[0]].frameptr; + if( curframe ) *curframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == FRAME_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + jinterval = pintervals[1] - pintervals[0]; + time = cl.time; + jtime = 0.0f; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = time - ((int)(time / fullinterval)) * fullinterval; + + // LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, + // i instead measure the time of the first frame, hoping it is consistent + for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + j = i; + jinterval = pintervals[i] - jtime; + jtime = pintervals[i]; + } + + if( m_fDoInterp ) + lerpFrac = (targettime - jtime) / jinterval; + else j = i; // no lerping + + // get the interpolated frames + if( oldframe ) *oldframe = pspritegroup->frames[j]; + if( curframe ) *curframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + // e.g. doom-style sprite monsters + float yaw = ent->angles[YAW]; + int angleframe = (int)(Q_rint(( RI.viewangles[1] - yaw + 45.0f ) / 360 * 8) - 4) & 7; + + if( m_fDoInterp ) + { + if( ent->latched.prevblending[0] >= psprite->numframes || psprite->frames[ent->latched.prevblending[0]].type != FRAME_ANGLED ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 1.0f; + } + + if( ent->latched.sequencetime < cl.time ) + { + if( frame != ent->latched.prevblending[1] ) + { + ent->latched.prevblending[0] = ent->latched.prevblending[1]; + ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 0.0f; + } + else lerpFrac = (cl.time - ent->latched.sequencetime) * ent->curstate.framerate; + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + ent->latched.sequencetime = cl.time; + lerpFrac = 0.0f; + } + } + else + { + ent->latched.prevblending[0] = ent->latched.prevblending[1] = frame; + lerpFrac = 1.0f; + } + + pspritegroup = (mspritegroup_t *)psprite->frames[ent->latched.prevblending[0]].frameptr; + if( oldframe ) *oldframe = pspritegroup->frames[angleframe]; + + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + if( curframe ) *curframe = pspritegroup->frames[angleframe]; + } + + return lerpFrac; +} + +/* +================ +R_CullSpriteModel + +Cull sprite model by bbox +================ +*/ +qboolean R_CullSpriteModel( cl_entity_t *e, vec3_t origin ) +{ + vec3_t sprite_mins, sprite_maxs; + float scale = 1.0f; + + if( !e->model->cache.data ) + return true; + + if( e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + // scale original bbox (no rotation for sprites) + VectorScale( e->model->mins, scale, sprite_mins ); + VectorScale( e->model->maxs, scale, sprite_maxs ); + + sprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs ); + + VectorAdd( sprite_mins, origin, sprite_mins ); + VectorAdd( sprite_maxs, origin, sprite_maxs ); + + return R_CullModel( e, sprite_mins, sprite_maxs ); +} + +/* +================ +R_GlowSightDistance + +Set sprite brightness factor +================ +*/ +static float R_SpriteGlowBlend( vec3_t origin, int rendermode, int renderfx, float *pscale ) +{ + float dist, brightness; + vec3_t glowDist; + pmtrace_t *tr; + + VectorSubtract( origin, RI.vieworg, glowDist ); + dist = VectorLength( glowDist ); + + if( RP_NORMALPASS( )) + { + tr = CL_VisTraceLine( RI.vieworg, origin, r_traceglow->value ? PM_GLASS_IGNORE : (PM_GLASS_IGNORE|PM_STUDIO_IGNORE)); + + if(( 1.0f - tr->fraction ) * dist > 8.0f ) + return 0.0f; + } + + if( renderfx == kRenderFxNoDissipation ) + return 1.0f; + + brightness = GLARE_FALLOFF / ( dist * dist ); + brightness = bound( 0.05f, brightness, 1.0f ); + *pscale *= dist * ( 1.0f / 200.0f ); + + return brightness; +} + +/* +================ +R_SpriteOccluded + +Do occlusion test for glow-sprites +================ +*/ +qboolean R_SpriteOccluded( cl_entity_t *e, vec3_t origin, float *pscale ) +{ + if( e->curstate.rendermode == kRenderGlow ) + { + float blend; + vec3_t v; + + TriWorldToScreen( origin, v ); + + if( v[0] < RI.viewport[0] || v[0] > RI.viewport[0] + RI.viewport[2] ) + return true; // do scissor + if( v[1] < RI.viewport[1] || v[1] > RI.viewport[1] + RI.viewport[3] ) + return true; // do scissor + + blend = R_SpriteGlowBlend( origin, e->curstate.rendermode, e->curstate.renderfx, pscale ); + tr.blend *= blend; + + if( blend <= 0.01f ) + return true; // faded + } + else + { + if( R_CullSpriteModel( e, origin )) + return true; + } + + return false; +} + +/* +================= +R_DrawSpriteQuad +================= +*/ +static void R_DrawSpriteQuad( mspriteframe_t *frame, vec3_t org, vec3_t v_right, vec3_t v_up, float scale ) +{ + vec3_t point; + + r_stats.c_sprite_polys++; + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + VectorMA( org, frame->down * scale, v_up, point ); + VectorMA( point, frame->left * scale, v_right, point ); + pglVertex3fv( point ); + pglTexCoord2f( 0.0f, 0.0f ); + VectorMA( org, frame->up * scale, v_up, point ); + VectorMA( point, frame->left * scale, v_right, point ); + pglVertex3fv( point ); + pglTexCoord2f( 1.0f, 0.0f ); + VectorMA( org, frame->up * scale, v_up, point ); + VectorMA( point, frame->right * scale, v_right, point ); + pglVertex3fv( point ); + pglTexCoord2f( 1.0f, 1.0f ); + VectorMA( org, frame->down * scale, v_up, point ); + VectorMA( point, frame->right * scale, v_right, point ); + pglVertex3fv( point ); + pglEnd(); +} + +static qboolean R_SpriteHasLightmap( cl_entity_t *e, int texFormat ) +{ + if( !r_sprite_lighting->value ) + return false; + + if( texFormat != SPR_ALPHTEST ) + return false; + + if( e->curstate.effects & EF_FULLBRIGHT ) + return false; + + if( e->curstate.renderamt <= 127 ) + return false; + + switch( e->curstate.rendermode ) + { + case kRenderNormal: + case kRenderTransAlpha: + case kRenderTransTexture: + break; + default: + return false; + } + + return true; +} + +/* +================= +R_SpriteAllowLerping +================= +*/ +static qboolean R_SpriteAllowLerping( cl_entity_t *e, msprite_t *psprite ) +{ + if( !r_sprite_lerping->value ) + return false; + + if( psprite->numframes <= 1 ) + return false; + + if( psprite->texFormat != SPR_ADDITIVE ) + return false; + + if( e->curstate.rendermode == kRenderNormal || e->curstate.rendermode == kRenderTransAlpha ) + return false; + + return true; +} + +/* +================= +R_DrawSpriteModel +================= +*/ +void R_DrawSpriteModel( cl_entity_t *e ) +{ + mspriteframe_t *frame, *oldframe; + msprite_t *psprite; + model_t *model; + int i, type; + float angle, dot, sr, cr; + float lerp = 1.0f, ilerp, scale; + vec3_t v_forward, v_right, v_up; + vec3_t origin, color, color2; + + if( RI.params & RP_ENVVIEW ) + return; + + model = e->model; + psprite = (msprite_t * )model->cache.data; + VectorCopy( e->origin, origin ); // set render origin + + // do movewith + if( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *parent; + + parent = CL_GetEntityByIndex( e->curstate.aiment ); + + if( parent && parent->model ) + { + if( parent->model->type == mod_studio && e->curstate.body > 0 ) + { + int num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS ); + VectorCopy( parent->attachment[num-1], origin ); + } + else VectorCopy( parent->origin, origin ); + } + } + + scale = e->curstate.scale; + if( !scale ) scale = 1.0f; + + if( R_SpriteOccluded( e, origin, &scale )) + return; // sprite culled + + r_stats.c_sprite_models_drawn++; + + if( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( false ); + + // select properly rendermode + switch( e->curstate.rendermode ) + { + case kRenderTransAlpha: + pglDepthMask( GL_FALSE ); + case kRenderTransColor: + case kRenderTransTexture: + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + break; + case kRenderGlow: + pglDisable( GL_DEPTH_TEST ); + case kRenderTransAdd: + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + pglDepthMask( GL_FALSE ); + break; + case kRenderNormal: + default: + pglDisable( GL_BLEND ); + break; + } + + // all sprites can have color + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglEnable( GL_ALPHA_TEST ); + + // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug + if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) + { + color[0] = (float)e->curstate.rendercolor.r * ( 1.0f / 255.0f ); + color[1] = (float)e->curstate.rendercolor.g * ( 1.0f / 255.0f ); + color[2] = (float)e->curstate.rendercolor.b * ( 1.0f / 255.0f ); + } + else + { + color[0] = 1.0f; + color[1] = 1.0f; + color[2] = 1.0f; + } + + if( R_SpriteHasLightmap( e, psprite->texFormat )) + { + colorVec lightColor = R_LightPoint( origin ); + // FIXME: collect light from dlights? + color2[0] = (float)lightColor.r * ( 1.0f / 255.0f ); + color2[1] = (float)lightColor.g * ( 1.0f / 255.0f ); + color2[2] = (float)lightColor.b * ( 1.0f / 255.0f ); + // NOTE: sprites with 'lightmap' looks ugly when alpha func is GL_GREATER 0.0 + pglAlphaFunc( GL_GREATER, 0.25f ); + } + + if( R_SpriteAllowLerping( e, psprite )) + lerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame ); + else frame = oldframe = R_GetSpriteFrame( model, e->curstate.frame, e->angles[YAW] ); + + type = psprite->type; + + // automatically roll parallel sprites if requested + if( e->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL ) + type = SPR_FWD_PARALLEL_ORIENTED; + + switch( type ) + { + case SPR_ORIENTED: + AngleVectors( e->angles, v_forward, v_right, v_up ); + VectorScale( v_forward, 0.01f, v_forward ); // to avoid z-fighting + VectorSubtract( origin, v_forward, origin ); + break; + case SPR_FACING_UPRIGHT: + VectorSet( v_right, origin[1] - RI.vieworg[1], -(origin[0] - RI.vieworg[0]), 0.0f ); + VectorSet( v_up, 0.0f, 0.0f, 1.0f ); + VectorNormalize( v_right ); + break; + case SPR_FWD_PARALLEL_UPRIGHT: + dot = RI.vforward[2]; + if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 + return; // invisible + VectorSet( v_up, 0.0f, 0.0f, 1.0f ); + VectorSet( v_right, RI.vforward[1], -RI.vforward[0], 0.0f ); + VectorNormalize( v_right ); + break; + case SPR_FWD_PARALLEL_ORIENTED: + angle = e->angles[ROLL] * (M_PI2 / 360.0f); + SinCos( angle, &sr, &cr ); + for( i = 0; i < 3; i++ ) + { + v_right[i] = (RI.vright[i] * cr + RI.vup[i] * sr); + v_up[i] = RI.vright[i] * -sr + RI.vup[i] * cr; + } + break; + case SPR_FWD_PARALLEL: // normal sprite + default: + VectorCopy( RI.vright, v_right ); + VectorCopy( RI.vup, v_up ); + break; + } + + if( psprite->facecull == SPR_CULL_NONE ) + GL_Cull( GL_NONE ); + + if( oldframe == frame ) + { + // draw the single non-lerped frame + pglColor4f( color[0], color[1], color[2], tr.blend ); + GL_Bind( GL_TEXTURE0, frame->gl_texturenum ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + } + else + { + // draw two combined lerped frames + lerp = bound( 0.0f, lerp, 1.0f ); + ilerp = 1.0f - lerp; + + if( ilerp != 0.0f ) + { + pglColor4f( color[0], color[1], color[2], tr.blend * ilerp ); + GL_Bind( GL_TEXTURE0, oldframe->gl_texturenum ); + R_DrawSpriteQuad( oldframe, origin, v_right, v_up, scale ); + } + + if( lerp != 0.0f ) + { + pglColor4f( color[0], color[1], color[2], tr.blend * lerp ); + GL_Bind( GL_TEXTURE0, frame->gl_texturenum ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + } + } + + // draw the sprite 'lightmap' :-) + if( R_SpriteHasLightmap( e, psprite->texFormat )) + { + if( !r_lightmap->value ) + pglEnable( GL_BLEND ); + else pglDisable( GL_BLEND ); + pglDepthFunc( GL_EQUAL ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_ZERO, GL_SRC_COLOR ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + pglColor4f( color2[0], color2[1], color2[2], tr.blend ); + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + R_DrawSpriteQuad( frame, origin, v_right, v_up, scale ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglDepthFunc( GL_LEQUAL ); + } + + if( psprite->facecull == SPR_CULL_NONE ) + GL_Cull( GL_FRONT ); + + pglDisable( GL_ALPHA_TEST ); + pglDepthMask( GL_TRUE ); + + if( e->curstate.rendermode == kRenderGlow || e->curstate.rendermode == kRenderTransAdd ) + R_AllowFog( true ); + + if( e->curstate.rendermode != kRenderNormal ) + { + pglDisable( GL_BLEND ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglEnable( GL_DEPTH_TEST ); + } +} \ No newline at end of file diff --git a/engine/client/gl_studio.c b/engine/client/gl_studio.c new file mode 100644 index 00000000..0f164033 --- /dev/null +++ b/engine/client/gl_studio.c @@ -0,0 +1,3939 @@ +/* +gl_studio.c - studio model renderer +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "mathlib.h" +#include "const.h" +#include "r_studioint.h" +#include "triangleapi.h" +#include "studio.h" +#include "pm_local.h" +#include "gl_local.h" +#include "cl_tent.h" + +#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events +#define MAX_LOCALLIGHTS 4 + +CVAR_DEFINE_AUTO( r_glowshellfreq, "2.2", 0, "glowing shell frequency update" ); +CVAR_DEFINE_AUTO( r_shadows, "0", 0, "cast shadows from models" ); + +static vec3_t hullcolor[8] = +{ +{ 1.0f, 1.0f, 1.0f }, +{ 1.0f, 0.5f, 0.5f }, +{ 0.5f, 1.0f, 0.5f }, +{ 1.0f, 1.0f, 0.5f }, +{ 0.5f, 0.5f, 1.0f }, +{ 1.0f, 0.5f, 1.0f }, +{ 0.5f, 1.0f, 1.0f }, +{ 1.0f, 1.0f, 1.0f }, +}; + +typedef struct sortedmesh_s +{ + mstudiomesh_t *mesh; + int flags; // face flags +} sortedmesh_t; + +typedef struct +{ + double time; + double frametime; + int framecount; // studio framecount + qboolean interpolate; + int rendermode; + float blend; // blend value + + // bones + matrix3x4 rotationmatrix; + matrix3x4 bonestransform[MAXSTUDIOBONES]; + matrix3x4 lighttransform[MAXSTUDIOBONES]; + + // boneweighting stuff + matrix3x4 worldtransform[MAXSTUDIOBONES]; + + // cached bones + matrix3x4 cached_bonestransform[MAXSTUDIOBONES]; + matrix3x4 cached_lighttransform[MAXSTUDIOBONES]; + char cached_bonenames[MAXSTUDIOBONES][32]; + int cached_numbones; // number of bones in cache + + sortedmesh_t meshes[MAXSTUDIOMESHES]; // sorted meshes + vec3_t verts[MAXSTUDIOVERTS]; + vec3_t norms[MAXSTUDIOVERTS]; + + // lighting state + float ambientlight; + float shadelight; + vec3_t lightvec; // averaging light direction + vec3_t lightspot; // shadow spot + vec3_t lightcolor; // averaging lightcolor + vec3_t blightvec[MAXSTUDIOBONES]; // bone light vecs + vec3_t lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel + + // chrome stuff + vec3_t chrome_origin; + vec2_t chrome[MAXSTUDIOVERTS]; // texture coords for surface normals + vec3_t chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames + vec3_t chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames + int chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated + + // glowshell stuff + int normaltable[MAXSTUDIOVERTS]; // glowshell uses this + + // elights cache + int numlocallights; + int lightage[MAXSTUDIOBONES]; + dlight_t *locallight[MAX_LOCALLIGHTS]; + color24 locallightcolor[MAX_LOCALLIGHTS]; + vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS]; + vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS]; + float locallightR2[MAX_LOCALLIGHTS]; +} studio_draw_state_t; + +// studio-related cvars +convar_t *r_studio_sort_textures; +convar_t *r_drawviewmodel; +convar_t *cl_righthand = NULL; +convar_t *cl_himodels; + +static r_studio_interface_t *pStudioDraw; +static studio_draw_state_t g_studio; // global studio state + +// global variables +static qboolean m_fDoRemap; +mstudiomodel_t *m_pSubModel; +mstudiobodyparts_t *m_pBodyPart; +player_info_t *m_pPlayerInfo; +studiohdr_t *m_pStudioHeader; +float m_flGaitMovement; +int g_iBackFaceCull; +int g_nTopColor, g_nBottomColor; // remap colors +int g_nFaceFlags, g_nForceFaceFlags; + +/* +==================== +R_StudioInit + +==================== +*/ +void R_StudioInit( void ) +{ + cl_himodels = Cvar_Get( "cl_himodels", "1", FCVAR_ARCHIVE, "draw high-resolution player models in multiplayer" ); + r_studio_sort_textures = Cvar_Get( "r_studio_sort_textures", "0", FCVAR_ARCHIVE, "change draw order for additive meshes" ); + r_drawviewmodel = Cvar_Get( "r_drawviewmodel", "1", 0, "draw firstperson weapon model" ); + + Matrix3x4_LoadIdentity( g_studio.rotationmatrix ); + Cvar_RegisterVariable( &r_glowshellfreq ); + +// g-cont. especially not registered +// Cvar_RegisterVariable( &r_shadows ); + + g_studio.interpolate = true; + g_studio.framecount = 0; + m_fDoRemap = false; +} + +/* +================ +R_StudioSetupTimings + +init current time for a given model +================ +*/ +static void R_StudioSetupTimings( void ) +{ + if( RI.drawWorld ) + { + // synchronize with server time + g_studio.time = cl.time; + g_studio.frametime = cl.time - cl.oldtime; + } + else + { + // menu stuff + g_studio.time = host.realtime; + g_studio.frametime = host.frametime; + } +} + +/* +================ +R_AllowFlipViewModel + +should a flip the viewmodel if cl_righthand is set to 1 +================ +*/ +static qboolean R_AllowFlipViewModel( cl_entity_t *e ) +{ + if( cl_righthand && cl_righthand->value > 0 ) + { + if( e == &clgame.viewent ) + return true; + } + + return false; +} + +/* +================ +R_StudioComputeBBox + +Compute a full bounding box for current sequence +================ +*/ +static qboolean R_StudioComputeBBox( vec3_t bbox[8] ) +{ + vec3_t studio_mins, studio_maxs; + vec3_t mins, maxs, p1, p2; + cl_entity_t *e = RI.currententity; + mstudioseqdesc_t *pseqdesc; + int i; + + if( !m_pStudioHeader ) + return false; + + // check if we have valid mins\maxs + if( !VectorCompare( vec3_origin, RI.currentmodel->mins )) + { + // clipping bounding box + VectorCopy( RI.currentmodel->mins, mins ); + VectorCopy( RI.currentmodel->maxs, maxs ); + } + else + { + ClearBounds( mins, maxs ); + } + + // check sequence range + if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + // add sequence box to the model box + AddPointToBounds( pseqdesc->bbmin, mins, maxs ); + AddPointToBounds( pseqdesc->bbmax, mins, maxs ); + ClearBounds( studio_mins, studio_maxs ); + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p1[0] = ( i & 1 ) ? mins[0] : maxs[0]; + p1[1] = ( i & 2 ) ? mins[1] : maxs[1]; + p1[2] = ( i & 4 ) ? mins[2] : maxs[2]; + + Matrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 ); + AddPointToBounds( p2, studio_mins, studio_maxs ); + if( bbox ) VectorCopy( p2, bbox[i] ); + } + + if( !bbox && R_CullModel( e, studio_mins, studio_maxs )) + return false; // model culled + return true; // visible +} + +void R_StudioComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int i, numbones = 0; + float flTotal; + + for( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; + vec4_t *boneMat3 = (vec4_t *)g_studio.worldtransform[boneweights->bone[3]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flWeight3 = boneweights->weight[3] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3; + } + else if( numbones == 3 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + vec4_t *boneMat2 = (vec4_t *)g_studio.worldtransform[boneweights->bone[2]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2; + } + else if( numbones == 2 ) + { + vec4_t *boneMat0 = (vec4_t *)g_studio.worldtransform[boneweights->bone[0]]; + vec4_t *boneMat1 = (vec4_t *)g_studio.worldtransform[boneweights->bone[1]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flTotal = flWeight0 + flWeight1; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1; + } + else + { + Matrix3x4_Copy( result, g_studio.worldtransform[boneweights->bone[0]] ); + } +} + +/* +=============== +pfnGetCurrentEntity + +=============== +*/ +static cl_entity_t *pfnGetCurrentEntity( void ) +{ + return RI.currententity; +} + +/* +=============== +pfnPlayerInfo + +=============== +*/ +player_info_t *pfnPlayerInfo( int index ) +{ + if( !RI.drawWorld ) + return &gameui.playerinfo; + + if( index < 0 || index > cl.maxclients ) + return NULL; + return &cl.players[index]; +} + +/* +=============== +pfnMod_ForName + +=============== +*/ +static model_t *pfnMod_ForName( const char *model, int crash ) +{ + return Mod_ForName( model, crash, false ); +} + +/* +=============== +pfnGetPlayerState + +=============== +*/ +entity_state_t *R_StudioGetPlayerState( int index ) +{ + if( !RI.drawWorld ) + return &RI.currententity->curstate; + + if( index < 0 || index >= cl.maxclients ) + return NULL; + + return &cl.frames[cl.parsecountmod].playerstate[index]; +} + +/* +=============== +pfnGetViewEntity + +=============== +*/ +static cl_entity_t *pfnGetViewEntity( void ) +{ + return &clgame.viewent; +} + +/* +=============== +pfnGetEngineTimes + +=============== +*/ +static void pfnGetEngineTimes( int *framecount, double *current, double *old ) +{ + if( framecount ) *framecount = tr.realframecount; + if( current ) *current = cl.time; + if( old ) *old = cl.oldtime; +} + +/* +=============== +pfnGetViewInfo + +=============== +*/ +static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) +{ + if( origin ) VectorCopy( RI.vieworg, origin ); + if( forwardv ) VectorCopy( RI.vforward, forwardv ); + if( rightv ) VectorCopy( RI.vright, rightv ); + if( upv ) VectorCopy( RI.vup, upv ); +} + +/* +=============== +R_GetChromeSprite + +=============== +*/ +static model_t *R_GetChromeSprite( void ) +{ + return cl_sprite_shell; +} + +/* +=============== +pfnGetModelCounters + +=============== +*/ +static void pfnGetModelCounters( int **s, int **a ) +{ + *s = &g_studio.framecount; + *a = &r_stats.c_studio_models_drawn; +} + +/* +=============== +pfnGetAliasScale + +=============== +*/ +static void pfnGetAliasScale( float *x, float *y ) +{ + if( x ) *x = 1.0f; + if( y ) *y = 1.0f; +} + +/* +=============== +pfnStudioGetBoneTransform + +=============== +*/ +static float ****pfnStudioGetBoneTransform( void ) +{ + return (float ****)g_studio.bonestransform; +} + +/* +=============== +pfnStudioGetLightTransform + +=============== +*/ +static float ****pfnStudioGetLightTransform( void ) +{ + return (float ****)g_studio.lighttransform; +} + +/* +=============== +pfnStudioGetAliasTransform + +=============== +*/ +static float ***pfnStudioGetAliasTransform( void ) +{ + return NULL; +} + +/* +=============== +pfnStudioGetRotationMatrix + +=============== +*/ +static float ***pfnStudioGetRotationMatrix( void ) +{ + return (float ***)g_studio.rotationmatrix; +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3.0f); + + if( *pBlend < pseqdesc->blendstart[0] ) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0f; + *pBlend = 0; + } + else if( *pBlend > pseqdesc->blendend[0] ) + { + *pPitch -= pseqdesc->blendend[0] / 3.0f; + *pBlend = 255; + } + else + { + if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error + *pBlend = 127; + else *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0.0f; + } +} + +/* +==================== +R_StudioLerpMovement + +==================== +*/ +void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles ) +{ + float f = 1.0f; + + // don't do it if the goalstarttime hasn't updated in a while. + // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit + // was increased to 1.0 s., which is 2x the max lag we are accounting for. + if( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) + f = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); + + // Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_studio.time ); + VectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin ); + + if( !VectorCompare( e->curstate.angles, e->latched.prevangles )) + { + vec4_t q, q1, q2; + + AngleQuaternion( e->curstate.angles, q1, false ); + AngleQuaternion( e->latched.prevangles, q2, false ); + QuaternionSlerp( q2, q1, f, q ); + QuaternionAngle( q, angles ); + } + else VectorCopy( e->curstate.angles, angles ); +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void R_StudioSetUpTransform( cl_entity_t *e ) +{ + vec3_t origin, angles; + + VectorCopy( e->origin, origin ); + VectorCopy( e->angles, angles ); + + // interpolate monsters position (moved into UpdateEntityFields by user request) + if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP )) + { + R_StudioLerpMovement( e, g_studio.time, origin, angles ); + } + + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + angles[PITCH] = -angles[PITCH]; // stupid quake bug + + // don't rotate clients, only aim + if( e->player ) angles[PITCH] = 0.0f; + + Matrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f ); + + if( tr.fFlipViewModel ) + { + g_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1]; + g_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1]; + g_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1]; + } +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) +{ + double dfdt, f; + + if( g_studio.interpolate ) + { + if( g_studio.time < e->curstate.animtime ) dfdt = 0.0; + else dfdt = (g_studio.time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps; + } + else dfdt = 0; + + if( pseqdesc->numframes <= 1 ) f = 0.0; + else f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( pseqdesc->numframes > 1 ) + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + if( f < 0 ) f += (pseqdesc->numframes - 1); + } + else + { + if( f >= pseqdesc->numframes - 1.001 ) + f = pseqdesc->numframes - 1.001; + if( f < 0.0 ) f = 0.0; + } + return f; +} + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float R_StudioEstimateInterpolant( cl_entity_t *e ) +{ + float dadt = 1.0f; + + if( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) + { + dadt = ( g_studio.time - e->curstate.animtime ) / 0.1f; + if( dadt > 2.0f ) dadt = 2.0f; + } + + return dadt; +} + +/* +==================== +CL_GetStudioEstimatedFrame + +==================== +*/ +float CL_GetStudioEstimatedFrame( cl_entity_t *ent ) +{ + studiohdr_t *pstudiohdr; + mstudioseqdesc_t *pseqdesc; + int sequence; + + if( ent->model != NULL && ent->model->type == mod_studio ) + { + pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model ); + + if( pstudiohdr ) + { + sequence = bound( 0, ent->curstate.sequence, pstudiohdr->numseq - 1 ); + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + return R_StudioEstimateFrame( ent, pseqdesc ); + } + } + + return 0; +} + +/* +==================== +CL_GetSequenceDuration + +==================== +*/ +float CL_GetSequenceDuration( cl_entity_t *ent, int sequence ) +{ + studiohdr_t *pstudiohdr; + mstudioseqdesc_t *pseqdesc; + + if( ent->model != NULL && ent->model->type == mod_studio ) + { + pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( ent->model ); + + if( pstudiohdr ) + { + sequence = bound( 0, sequence, pstudiohdr->numseq - 1 ); + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + + if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 ) + return (float)pseqdesc->numframes / (float)pseqdesc->fps; + } + } + + return 0.1f; +} + +/* +==================== +StudioGetAnim + +==================== +*/ +void *R_StudioGetAnim( studiohdr_t *m_pStudioHeader, model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + size_t filesize; + byte *buf; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + if( pseqdesc->seqgroup == 0 ) + return ((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if( paSequences == NULL ) + { + paSequences = (cache_user_t *)Mem_Alloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t )); + m_pSubModel->submodels = (void *)paSequences; + } + + // check for already loaded + if( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] ))) + { + string filepath, modelname, modelpath; + + COM_FileBase( m_pSubModel->name, modelname ); + COM_ExtractFilePath( m_pSubModel->name, modelpath ); + + // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile + Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); + + buf = FS_LoadFile( filepath, &filesize, false ); + if( !buf || !filesize ) Host_Error( "StudioGetAnim: can't load %s\n", filepath ); + if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "StudioGetAnim: %s is corrupted\n", filepath ); + + Con_Printf( "loading: %s\n", filepath ); + + paSequences[pseqdesc->seqgroup].data = Mem_Alloc( com_studiocache, filesize ); + memcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize ); + Mem_Free( buf ); + } + + return ((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +StudioFxTransform + +==================== +*/ +void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if( !COM_RandomLong( 0, 49 )) + { + int axis = COM_RandomLong( 0, 1 ); + + if( axis == 1 ) axis = 2; // choose between x & z + VectorScale( transform[axis], COM_RandomFloat( 1.0f, 1.484f ), transform[axis] ); + } + else if( !COM_RandomLong( 0, 49 )) + { + float offset; + int axis = COM_RandomLong( 0, 1 ); + + if( axis == 1 ) axis = 2; // choose between x & z + offset = COM_RandomFloat( -10.0f, 10.0f ); + transform[COM_RandomLong( 0, 2 )][3] += offset; + } + break; + case kRenderFxExplode: + { + float scale; + + scale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f; + if( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200% + + transform[0][1] *= scale; + transform[1][1] *= scale; + transform[2][1] *= scale; + } + break; + } +} + +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen ) +{ + mstudiobonecontroller_t *pbonecontroller; + float value = 0.0f; + int i, j; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + i = pbonecontroller[j].index; + + if( i == STUDIO_MOUTH ) + { + // mouth hardcoded at controller 4 + value = (float)mouthopen / 64.0f; + value = bound( 0.0f, value, 1.0f ); + value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + else if( i < 4 ) + { + // check for 360% wrapping + if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP )) + { + if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 ) + { + int a = (pcontroller1[j] + 128) % 256; + int b = (pcontroller2[j] + 128) % 256; + value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start; + } + else + { + value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start; + } + } + else + { + value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f; + value = bound( 0.0f, value, 1.0f ); + value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + } + + switch( pbonecontroller[j].type & STUDIO_TYPES ) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = DEG2RAD( value ); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + +/* +==================== +StudioCalcBoneQuaternion + +==================== +*/ +void R_StudioCalcBoneQuaternion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec4_t q ) +{ + vec3_t angles1; + vec3_t angles2; + int j, k; + + for( j = 0; j < 3; j++ ) + { + if( !panim || panim->offset[j+3] == 0 ) + { + angles2[j] = angles1[j] = pbone->value[j+3]; // default; + } + else + { + mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); + + k = frame; + + // debug + if( panimvalue->num.total < panimvalue->num.valid ) + k = 0; + + // find span of values that includes the frame we want + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + // debug + if( panimvalue->num.total < panimvalue->num.valid ) + k = 0; + } + + // bah, missing blend! + if( panimvalue->num.valid > k ) + { + angles1[j] = panimvalue[k+1].value; + + if( panimvalue->num.valid > k + 1 ) + { + angles2[j] = panimvalue[k+2].value; + } + else + { + if( panimvalue->num.total > k + 1 ) + angles2[j] = angles1[j]; + else angles2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + angles1[j] = panimvalue[panimvalue->num.valid].value; + if( panimvalue->num.total > k + 1 ) + angles2[j] = angles1[j]; + else angles2[j] = panimvalue[panimvalue->num.valid+2].value; + } + + angles1[j] = pbone->value[j+3] + angles1[j] * pbone->scale[j+3]; + angles2[j] = pbone->value[j+3] + angles2[j] * pbone->scale[j+3]; + } + + if( pbone->bonecontroller[j+3] != -1 && adj != NULL ) + { + angles1[j] += adj[pbone->bonecontroller[j+3]]; + angles2[j] += adj[pbone->bonecontroller[j+3]]; + } + } + + if( !VectorCompare( angles1, angles2 )) + { + vec4_t q1, q2; + + AngleQuaternion( angles1, q1, true ); + AngleQuaternion( angles2, q2, true ); + QuaternionSlerp( q1, q2, s, q ); + } + else + { + AngleQuaternion( angles1, q, true ); + } +} + +/* +==================== +StudioCalcBonePosition + +==================== +*/ +void R_StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec3_t pos ) +{ + vec3_t origin1; + vec3_t origin2; + int j, k; + + for( j = 0; j < 3; j++ ) + { + if( !panim || panim->offset[j] == 0 ) + { + origin2[j] = origin1[j] = pbone->value[j]; // default; + } + else + { + mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); + + k = frame; + + // debug + if( panimvalue->num.total < panimvalue->num.valid ) + k = 0; + + // find span of values that includes the frame we want + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + // debug + if( panimvalue->num.total < panimvalue->num.valid ) + k = 0; + } + + // bah, missing blend! + if( panimvalue->num.valid > k ) + { + origin1[j] = panimvalue[k+1].value; + + if( panimvalue->num.valid > k + 1 ) + { + origin2[j] = panimvalue[k+2].value; + } + else + { + if( panimvalue->num.total > k + 1 ) + origin2[j] = origin1[j]; + else origin2[j] = panimvalue[panimvalue->num.valid+2].value; + } + } + else + { + origin1[j] = panimvalue[panimvalue->num.valid].value; + if( panimvalue->num.total > k + 1 ) + origin2[j] = origin1[j]; + else origin2[j] = panimvalue[panimvalue->num.valid+2].value; + } + + origin1[j] = pbone->value[j] + origin1[j] * pbone->scale[j]; + origin2[j] = pbone->value[j] + origin2[j] * pbone->scale[j]; + } + + if( pbone->bonecontroller[j] != -1 && adj != NULL ) + { + origin1[j] += adj[pbone->bonecontroller[j]]; + origin2[j] += adj[pbone->bonecontroller[j]]; + } + } + + if( !VectorCompare( origin1, origin2 )) + { + VectorLerp( origin1, s, origin2, pos ); + } + else + { + VectorCopy( origin1, pos ); + } +} + +/* +==================== +StudioSlerpBones + +==================== +*/ +void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) +{ + int i; + + s = bound( 0.0f, s, 1.0f ); + + for( i = 0; i < numbones; i++ ) + { + QuaternionSlerp( q1[i], q2[i], s, q1[i] ); + VectorLerp( pos1[i], s, pos2[i], pos1[i] ); + } +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i, frame; + float adj[MAXSTUDIOCONTROLLERS]; + float s, dadt; + mstudiobone_t *pbone; + + // bah, fix this bug with changing sequences too fast + if( f > pseqdesc->numframes - 1 ) + { + f = 0.0f; + } + else if( f < -0.01f ) + { + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + f = -0.01f; + } + + frame = (int)f; + + dadt = R_StudioEstimateInterpolant( e ); + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + R_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen ); + + for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ ) + { + R_StudioCalcBoneQuaternion( frame, s, pbone, panim, adj, q[i] ); + R_StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); + } + + if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f; +} + +/* +==================== +StudioMergeBones + +==================== +*/ +void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel ) +{ + int i, j; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec4_t q[MAXSTUDIOBONES]; + static float pos[MAXSTUDIOBONES][3]; + double f; + + if( e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + f = R_StudioEstimateFrame( e, pseqdesc ); + + panim = R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc ); + R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + for( j = 0; j < g_studio.cached_numbones; j++ ) + { + if( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] )) + { + Matrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] ); + break; + } + } + + if( j >= g_studio.cached_numbones ) + { + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + if( pbones[i].parent == -1 ) + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] ); + + // apply client-side effects to the transformation matrix + R_StudioFxTransform( e, g_studio.bonestransform[i] ); + } + else + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix ); + Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix ); + } + } + } +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void R_StudioSetupBones( cl_entity_t *e ) +{ + double f; + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + matrix3x4 bonematrix; + static vec3_t pos[MAXSTUDIOBONES]; + static vec4_t q[MAXSTUDIOBONES]; + static vec3_t pos2[MAXSTUDIOBONES]; + static vec4_t q2[MAXSTUDIOBONES]; + static vec3_t pos3[MAXSTUDIOBONES]; + static vec4_t q3[MAXSTUDIOBONES]; + static vec3_t pos4[MAXSTUDIOBONES]; + static vec4_t q4[MAXSTUDIOBONES]; + int i; + + if( e->curstate.sequence >= m_pStudioHeader->numseq ) + e->curstate.sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + + f = R_StudioEstimateFrame( e, pseqdesc ); + + panim = R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f ); + + if( pseqdesc->numblends > 1 ) + { + float s; + float dadt; + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f ); + + dadt = R_StudioEstimateInterpolant( e ); + s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s ); + + if( pseqdesc->numblends == 4 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f ); + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f ); + + s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + + s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s ); + } + } + + if( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq )) + { + // blend from last sequence + static vec3_t pos1b[MAXSTUDIOBONES]; + static vec4_t q1b[MAXSTUDIOBONES]; + float s; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence; + panim = R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + + // clip prevframe + R_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe ); + + if( pseqdesc->numblends > 1 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe ); + + s = (e->latched.prevseqblending[0]) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s ); + + if( pseqdesc->numblends == 4 ) + { + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe ); + + panim += m_pStudioHeader->numbones; + R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe ); + + s = (e->latched.prevseqblending[0]) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s ); + + s = (e->latched.prevseqblending[1]) / 255.0f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s ); + } + } + + s = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f; + R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s ); + } + else + { + // store prevframe otherwise + e->latched.prevframe = f; + } + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + // calc gait animation + if( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + qboolean copy_bones = true; + + if( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq ) + m_pPlayerInfo->gaitsequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + + panim = R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc ); + R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy_bones = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy_bones = true; + + if( !copy_bones ) continue; + + VectorCopy( pos2[i], pos[i] ); + Vector4Copy( q2[i], q[i] ); + } + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + + if( pbones[i].parent == -1 ) + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix ); + Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] ); + + // apply client-side effects to the transformation matrix + R_StudioFxTransform( e, g_studio.bonestransform[i] ); + } + else + { + Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix ); + Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix ); + } + } +} + +/* +==================== +StudioSaveBones + +==================== +*/ +static void R_StudioSaveBones( void ) +{ + mstudiobone_t *pbones; + int i; + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + g_studio.cached_numbones = m_pStudioHeader->numbones; + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] ); + Matrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] ); + Q_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 ); + } +} + +/* +==================== +StudioBuildNormalTable + +NOTE: m_pSubModel must be set +==================== +*/ +void R_StudioBuildNormalTable( void ) +{ + cl_entity_t *e = RI.currententity; + mstudiomesh_t *pmesh; + int i, j; + + Assert( m_pSubModel != NULL ); + + // reset chrome cache + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + g_studio.chromeage[i] = 0; + + for( i = 0; i < m_pSubModel->numverts; i++ ) + g_studio.normaltable[i] = -1; + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + while( i = *( ptricmds++ )) + { + if( i < 0 ) i = -i; + + for( ; i > 0; i--, ptricmds += 4 ) + { + if( g_studio.normaltable[ptricmds[0]] < 0 ) + g_studio.normaltable[ptricmds[0]] = ptricmds[1]; + } + } + } + + g_studio.chrome_origin[0] = cos( r_glowshellfreq.value * g_studio.time ) * 4000.0f; + g_studio.chrome_origin[1] = sin( r_glowshellfreq.value * g_studio.time ) * 4000.0f; + g_studio.chrome_origin[2] = cos( r_glowshellfreq.value * g_studio.time * 0.33f ) * 4000.0f; + + if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b ) + TriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 ); + else TriColor4ub( 255, 255, 255, 255 ); +} + +/* +==================== +StudioGenerateNormals + +NOTE: m_pSubModel must be set +g_studio.verts must be computed +==================== +*/ +void R_StudioGenerateNormals( void ) +{ + int v0, v1, v2; + vec3_t e0, e1, norm; + mstudiomesh_t *pmesh; + int i, j; + + Assert( m_pSubModel != NULL ); + + for( i = 0; i < m_pSubModel->numverts; i++ ) + VectorClear( g_studio.norms[i] ); + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + while( i = *( ptricmds++ )) + { + if( i < 0 ) + { + i = -i; + + if( i > 2 ) + { + v0 = ptricmds[0]; ptricmds += 4; + v1 = ptricmds[0]; ptricmds += 4; + + for( i -= 2; i > 0; i--, ptricmds += 4 ) + { + v2 = ptricmds[0]; + + VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 ); + VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 ); + CrossProduct( e1, e0, norm ); + + VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] ); + VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] ); + VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] ); + + v1 = v2; + } + } + else + { + ptricmds += i; + } + } + else + { + if( i > 2 ) + { + qboolean odd = false; + + v0 = ptricmds[0]; ptricmds += 4; + v1 = ptricmds[0]; ptricmds += 4; + + for( i -= 2; i > 0; i--, ptricmds += 4 ) + { + v2 = ptricmds[0]; + + VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 ); + VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 ); + CrossProduct( e1, e0, norm ); + + VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] ); + VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] ); + VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] ); + + if( odd ) v1 = v2; + else v0 = v2; + + odd = !odd; + } + } + else + { + ptricmds += i; + } + } + } + } + + for( i = 0; i < m_pSubModel->numverts; i++ ) + VectorNormalize( g_studio.norms[i] ); +} + +/* +==================== +StudioSetupChrome + +==================== +*/ +void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) +{ + float n; + + if( g_studio.chromeage[bone] != g_studio.framecount ) + { + // calculate vectors from the viewer to the bone. This roughly adjusts for position + vec3_t chromeupvec; // g_studio.chrome t vector in world reference frame + vec3_t chromerightvec; // g_studio.chrome s vector in world reference frame + vec3_t tmp; // vector pointing at bone in world reference frame + + VectorNegate( g_studio.chrome_origin, tmp ); + tmp[0] += g_studio.bonestransform[bone][0][3]; + tmp[1] += g_studio.bonestransform[bone][1][3]; + tmp[2] += g_studio.bonestransform[bone][2][3]; + + VectorNormalize( tmp ); + CrossProduct( tmp, RI.vright, chromeupvec ); + VectorNormalize( chromeupvec ); + CrossProduct( tmp, chromeupvec, chromerightvec ); + VectorNormalize( chromerightvec ); + + Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] ); + Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] ); + + g_studio.chromeage[bone] = g_studio.framecount; + } + + // calc s coord + n = DotProduct( normal, g_studio.chromeright[bone] ); + pchrome[0] = (n + 1.0f) * 32.0f; + + // calc t coord + n = DotProduct( normal, g_studio.chromeup[bone] ); + pchrome[1] = (n + 1.0f) * 32.0f; +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +static void R_StudioCalcAttachments( void ) +{ + mstudioattachment_t *pAtt; + vec3_t forward, bonepos; + vec3_t localOrg, localAng; + int i; + + // calculate attachment points + pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + for( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) + { + Matrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] ); + VectorSubtract( RI.currententity->attachment[i], RI.currententity->origin, localOrg ); + Matrix3x4_OriginFromMatrix( g_studio.lighttransform[pAtt[i].bone], bonepos ); + VectorSubtract( localOrg, bonepos, forward ); // make forward + VectorNormalizeFast( forward ); + VectorAngles( forward, localAng ); + } +} + +/* +=============== +pfnStudioSetupModel + +=============== +*/ +static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel ) +{ + int index; + + if( bodypart > m_pStudioHeader->numbodyparts ) + bodypart = 0; + + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; + + index = RI.currententity->curstate.body / m_pBodyPart->base; + index = index % m_pBodyPart->nummodels; + + m_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index; + + if( ppbodypart ) *ppbodypart = m_pBodyPart; + if( ppsubmodel ) *ppsubmodel = m_pSubModel; +} + +/* +=============== +R_StudioCheckBBox + +=============== +*/ +static int R_StudioCheckBBox( void ) +{ + if( !RI.currententity || !RI.currentmodel ) + return false; + + return R_StudioComputeBBox( NULL ); +} + +/* +=============== +R_StudioDynamicLight + +=============== +*/ +void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight ) +{ + movevars_t *mv = &clgame.movevars; + vec3_t lightDir, vecSrc, vecEnd; + vec3_t origin, dist, finalLight; + float add, radius, total; + colorVec light; + uint lnum; + dlight_t *dl; + + if( !plight || !ent ) + return; + + if( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT )) + { + plight->shadelight = 0; + plight->ambientlight = 192; + + VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f ); + VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + return; + } + + // determine plane to get lightvalues from: ceil or floor + if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) + VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); + else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); + + if( ent == RI.currententity ) + { + int sequence = bound( 0, ent->curstate.sequence, m_pStudioHeader->numseq - 1 ); + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + if( FBitSet( pseqdesc->flags, STUDIO_LIGHT_FROM_ROOT )) + Matrix3x4_OriginFromMatrix( g_studio.lighttransform[0], origin ); + else VectorCopy( ent->origin, origin ); + } + else VectorCopy( ent->origin, origin ); + + VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f ); + light.r = light.g = light.b = light.a = 0; + + if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) + { + msurface_t *psurf = NULL; + pmtrace_t trace; + + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) + { + vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f; + } + else + { + vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f; + vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f; + vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f; + } + + trace = CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY ); + if( trace.ent > 0 ) psurf = PM_TraceSurface( &clgame.pmove->physents[trace.ent], vecSrc, vecEnd ); + else psurf = PM_TraceSurface( clgame.pmove->physents, vecSrc, vecEnd ); + + if( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY ))) + { + VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); + + light.r = LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); + light.g = LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); + light.b = LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); + } + } + + if(( light.r + light.g + light.b ) == 0 ) + { + colorVec gcolor; + float grad[4]; + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + light = R_LightVec( vecSrc, vecEnd, g_studio.lightspot ); + + VectorScale( lightDir, 2048.0f, vecEnd ); + VectorAdd( vecEnd, vecSrc, vecEnd ); + + vecSrc[0] -= 16.0f; + vecSrc[1] -= 16.0f; + vecEnd[0] -= 16.0f; + vecEnd[1] -= 16.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] += 32.0f; + vecEnd[0] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[1] += 32.0f; + vecEnd[1] += 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + vecSrc[0] -= 32.0f; + vecEnd[0] -= 32.0f; + + gcolor = R_LightVec( vecSrc, vecEnd, NULL ); + grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; + + lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3]; + lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3]; + VectorNormalize( lightDir ); + } + + VectorSet( finalLight, light.r, light.g, light.b ); + ent->cvFloorColor = light; + + total = Q_max( Q_max( light.r, light.g ), light.b ); + if( total == 0.0f ) total = 1.0f; + + // scale lightdir by light intentsity + VectorScale( lightDir, total, lightDir ); + + for( lnum = 0, dl = cl_dlights; lnum < MAX_DLIGHTS; lnum++, dl++ ) + { + if( dl->die < g_studio.time || !r_dynamic->value ) + continue; + + VectorSubtract( ent->origin, dl->origin, dist ); + + radius = VectorLength( dist ); + add = (dl->radius - radius); + + if( add > 0.0f ) + { + total += add; + + if( radius > 1.0f ) + VectorScale( dist, ( add / radius ), dist ); + else VectorScale( dist, add, dist ); + + VectorAdd( lightDir, dist, lightDir ); + + finalLight[0] += LightToTexGamma( dl->color.r ) * ( add * 512.0f ); + finalLight[1] += LightToTexGamma( dl->color.g ) * ( add * 512.0f ); + finalLight[2] += LightToTexGamma( dl->color.b ) * ( add * 512.0f ); + } + } + + if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) + add = 0.6f; + else add = 0.9f; + + VectorScale( lightDir, add, lightDir ); + + plight->shadelight = VectorLength( lightDir ); + plight->ambientlight = total - plight->shadelight; + + total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] ); + + if( total > 0.0f ) + { + plight->color[0] = finalLight[0] * ( 1.0f / total ); + plight->color[1] = finalLight[1] * ( 1.0f / total ); + plight->color[2] = finalLight[2] * ( 1.0f / total ); + } + else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); + + if( plight->ambientlight > 128 ) + plight->ambientlight = 128; + + if( plight->ambientlight + plight->shadelight > 255 ) + plight->shadelight = 255 - plight->ambientlight; + + VectorNormalize2( lightDir, plight->plightvec ); +} + +/* +=============== +pfnStudioEntityLight + +=============== +*/ +void R_StudioEntityLight( alight_t *lightinfo ) +{ + int lnum, i, j, k; + float minstrength, dist2, f, r2; + float lstrength[MAX_LOCALLIGHTS]; + cl_entity_t *ent = RI.currententity; + vec3_t mid, origin, pos; + dlight_t *el; + + g_studio.numlocallights = 0; + + if( !ent || !r_dynamic->value ) + return; + + for( i = 0; i < MAX_LOCALLIGHTS; i++ ) + lstrength[i] = 0; + + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin ); + dist2 = 1000000.0f; + k = 0; + + for( lnum = 0, el = cl_elights; lnum < MAX_ELIGHTS; lnum++, el++ ) + { + if( el->die < g_studio.time || el->radius <= 0.0f ) + continue; + + if(( el->key & 0xFFF ) == ent->index ) + { + int att = (el->key >> 12) & 0xF; + + if( att ) VectorCopy( ent->attachment[att], el->origin ); + else VectorCopy( ent->origin, el->origin ); + } + + VectorCopy( el->origin, pos ); + VectorSubtract( origin, el->origin, mid ); + + f = DotProduct( mid, mid ); + r2 = el->radius * el->radius; + + if( f > r2 ) minstrength = r2 / f; + else minstrength = 1.0f; + + if( minstrength > 0.05f ) + { + if( g_studio.numlocallights >= MAX_LOCALLIGHTS ) + { + for( j = 0, k = -1; j < g_studio.numlocallights; j++ ) + { + if( lstrength[j] < dist2 && lstrength[j] < minstrength ) + { + dist2 = lstrength[j]; + k = j; + } + } + } + else k = g_studio.numlocallights; + + if( k != -1 ) + { + g_studio.locallightcolor[k].r = LightToTexGamma( el->color.r ); + g_studio.locallightcolor[k].g = LightToTexGamma( el->color.g ); + g_studio.locallightcolor[k].b = LightToTexGamma( el->color.b ); + g_studio.locallightR2[k] = r2; + g_studio.locallight[k] = el; + lstrength[k] = minstrength; + + if( k >= g_studio.numlocallights ) + g_studio.numlocallights = k + 1; + } + } + } +} + +/* +=============== +R_StudioSetupLighting + +=============== +*/ +void R_StudioSetupLighting( alight_t *plight ) +{ + float scale = 1.0f; + int i; + + if( !m_pStudioHeader || !plight ) + return; + + if( RI.currententity != NULL ) + scale = RI.currententity->curstate.scale; + + g_studio.ambientlight = plight->ambientlight; + g_studio.shadelight = plight->shadelight; + VectorCopy( plight->plightvec, g_studio.lightvec ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + Matrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] ); + if( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled + } + + VectorCopy( plight->color, g_studio.lightcolor ); +} + +/* +=============== +R_StudioLighting + +=============== +*/ +void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) +{ + float illum; + + if( FBitSet( flags, STUDIO_NF_FULLBRIGHT )) + { + *lv = 1.0f; + return; + } + + illum = g_studio.ambientlight; + + if( FBitSet( flags, STUDIO_NF_FLATSHADE )) + { + illum += g_studio.shadelight * 0.8f; + } + else + { + float r, lightcos; + + if( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] ); + else lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += g_studio.shadelight; + + r = SHADE_LAMBERT; + + // do modified hemispherical lighting + if( r <= 1.0f ) + { + r += 1.0f; + lightcos = (( r - 1.0f ) - lightcos) / r; + if( lightcos > 0.0f ) + illum += g_studio.shadelight * lightcos; + } + else + { + lightcos = (lightcos + ( r - 1.0f )) / r; + if( lightcos > 0.0f ) + illum -= g_studio.shadelight * lightcos; + } + + illum = Q_max( illum, 0.0f ); + } + + illum = Q_min( illum, 255.0f ); + *lv = illum * (1.0f / 255.0f); +} + +/* +==================== +R_LightLambert + +==================== +*/ +void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], vec3_t normal, vec3_t color ) +{ + vec3_t finalLight; + vec3_t localLight; + int i; + + VectorCopy( color, finalLight ); + + for( i = 0; i < g_studio.numlocallights; i++ ) + { + float r, r2; + + if( tr.fFlipViewModel ) + r = DotProduct( normal, light[i] ); + else r = -DotProduct( normal, light[i] ); + + if( r > 0.0f ) + { + if( light[i][3] == 0.0f ) + { + r2 = DotProduct( light[i], light[i] ); + + if( r2 > 0.0f ) + light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 )); + else light[i][3] = 0.0001f; + } + + localLight[0] = Q_min( g_studio.locallightcolor[i].r * r * light[i][3], 255.0f ); + localLight[1] = Q_min( g_studio.locallightcolor[i].g * r * light[i][3], 255.0f ); + localLight[2] = Q_min( g_studio.locallightcolor[i].b * r * light[i][3], 255.0f ); + VectorScale( localLight, ( 1.0f / 255.0f ), localLight ); + + finalLight[0] = Q_min( finalLight[0] + localLight[0], 1.0f ); + finalLight[1] = Q_min( finalLight[1] + localLight[1], 1.0f ); + finalLight[2] = Q_min( finalLight[2] + localLight[2], 1.0f ); + } + } + + pglColor4f( finalLight[0], finalLight[1], finalLight[2], tr.blend ); +} + +/* +==================== +R_LightStrength + +==================== +*/ +void R_LightStrength( int bone, vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] ) +{ + int i; + + if( g_studio.lightage[bone] != g_studio.framecount ) + { + for( i = 0; i < g_studio.numlocallights; i++ ) + { + dlight_t *el = g_studio.locallight[i]; + Matrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] ); + } + + g_studio.lightage[bone] = g_studio.framecount; + } + + for( i = 0; i < g_studio.numlocallights; i++ ) + { + VectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] ); + light[i][3] = 0.0f; + } +} + +/* +=============== +R_StudioSetupSkin + +=============== +*/ +static void R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index ) +{ + mstudiotexture_t *ptexture = NULL; + + if( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME )) + return; + + if( ptexturehdr == NULL ) + return; + + // NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable + if( m_fDoRemap ) ptexture = CL_GetRemapInfoForEntity( RI.currententity )->ptexture; + if( !ptexture ) ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex); // fallback + + if( r_lightmap->value && !r_fullbright->value ) + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + else GL_Bind( GL_TEXTURE0, ptexture[index].index ); +} + +/* +=============== +R_StudioGetTexture + +Doesn't changes studio global state at all +=============== +*/ +mstudiotexture_t *R_StudioGetTexture( cl_entity_t *e ) +{ + mstudiotexture_t *ptexture; + studiohdr_t *phdr, *thdr; + + if(( phdr = Mod_StudioExtradata( e->model )) == NULL ) + return NULL; + + thdr = m_pStudioHeader; + if( !thdr ) return NULL; + + if( m_fDoRemap ) ptexture = CL_GetRemapInfoForEntity( e )->ptexture; + else ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex); + + return ptexture; +} + +void R_StudioSetRenderamt( int iRenderamt ) +{ + if( !RI.currententity ) return; + + RI.currententity->curstate.renderamt = iRenderamt; + tr.blend = CL_FxBlend( RI.currententity ) / 255.0f; +} + +/* +=============== +R_StudioSetCullState + +sets true for enable backculling (for left-hand viewmodel) +=============== +*/ +void R_StudioSetCullState( int iCull ) +{ + g_iBackFaceCull = iCull; +} + +/* +=============== +R_StudioRenderShadow + +just a prefab for render shadow +=============== +*/ +void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 ) +{ + if( !p1 || !p2 || !p3 || !p4 ) + return; + + if( TriSpriteTexture( CL_ModelHandle( iSprite ), 0 )) + { + TriRenderMode( kRenderTransAlpha ); + TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3fv( p1 ); + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex3fv( p2 ); + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3fv( p3 ); + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3fv( p4 ); + pglEnd(); + + TriRenderMode( kRenderNormal ); + } +} + +/* +=============== +R_StudioMeshCompare + +Sorting opaque entities by model type +=============== +*/ +static int R_StudioMeshCompare( const sortedmesh_t *a, const sortedmesh_t *b ) +{ + if( FBitSet( a->flags, STUDIO_NF_ADDITIVE )) + return 1; + + if( FBitSet( a->flags, STUDIO_NF_MASKED )) + return -1; + + return 0; +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +static _inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +{ + float *lv; + int i; + + while( i = *( ptricmds++ )) + { + if( i < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else pglBegin( GL_TRIANGLE_STRIP ); + + for( ; i > 0; i--, ptricmds += 4 ) + { + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + + if( g_studio.numlocallights ) + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); + else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + pglTexCoord2f( ptricmds[2] * s, ptricmds[3] * t ); + pglVertex3fv( g_studio.verts[ptricmds[0]] ); + } + + pglEnd(); + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +static _inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +{ + float *lv; + int i; + + while( i = *( ptricmds++ )) + { + if( i < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else pglBegin( GL_TRIANGLE_STRIP ); + + for( ; i > 0; i--, ptricmds += 4 ) + { + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + if( g_studio.numlocallights ) + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); + else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + pglTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] )); + pglVertex3fv( g_studio.verts[ptricmds[0]] ); + } + + pglEnd(); + } +} + +/* +=============== +R_StudioDrawNormalMesh + +generic path +=============== +*/ +static _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +{ + float *lv, *av; + int i, idx; + qboolean glowShell = (scale > 0.0f) ? true : false; + vec3_t vert; + + while( i = *( ptricmds++ )) + { + if( i < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else pglBegin( GL_TRIANGLE_STRIP ); + + for( ; i > 0; i--, ptricmds += 4 ) + { + if( glowShell ) + { + idx = g_studio.normaltable[ptricmds[0]]; + av = g_studio.verts[ptricmds[0]]; + lv = g_studio.norms[ptricmds[0]]; + VectorMA( av, scale, lv, vert ); + pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t ); + pglVertex3fv( vert ); + } + else + { + idx = ptricmds[1]; + lv = (float *)g_studio.lightvalues[ptricmds[1]]; + if( g_studio.numlocallights ) + R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv ); + else pglColor4f( lv[0], lv[1], lv[2], tr.blend ); + pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t ); + pglVertex3fv( g_studio.verts[ptricmds[0]] ); + } + } + + pglEnd(); + } +} + +/* +=============== +R_StudioDrawPoints + +=============== +*/ +static void R_StudioDrawPoints( void ) +{ + int i, j, k, m_skinnum; + float shellscale = 0.0f; + qboolean need_sort = false; + byte *pvertbone; + byte *pnormbone; + vec3_t *pstudioverts; + vec3_t *pstudionorms; + mstudiotexture_t *ptexture; + mstudiomesh_t *pmesh; + short *pskinref; + float lv_tmp; + + if( !m_pStudioHeader ) return; + + // safety bounding the skinnum + m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 )); + ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); + pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex); + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); + pstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex); + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + + pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); + + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 ) + { + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); + mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex); + matrix3x4 skinMat; + + for( i = 0; i < m_pSubModel->numverts; i++ ) + { + R_StudioComputeSkinMatrix( &pvertweight[i], skinMat ); + Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.verts[i] ); + R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); + } + + for( i = 0; i < m_pSubModel->numnorms; i++ ) + { + R_StudioComputeSkinMatrix( &pnormweight[i], skinMat ); + Matrix3x4_VectorRotate( skinMat, pstudionorms[i], g_studio.norms[i] ); + } + } + else + { + for( i = 0; i < m_pSubModel->numverts; i++ ) + { + Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], g_studio.verts[i] ); + R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] ); + } + } + + // generate shared normals for properly scaling glowing shell + if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) + { + float factor = (1.0f / 128.0f); + shellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor ); + R_StudioBuildNormalTable(); + R_StudioGenerateNormals(); + } + + for( j = k = 0; j < m_pSubModel->nummesh; j++ ) + { + g_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags; + + // fill in sortedmesh info + g_studio.meshes[j].flags = g_nFaceFlags; + g_studio.meshes[j].mesh = &pmesh[j]; + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) + need_sort = true; + + if( RI.currententity->curstate.rendermode == kRenderTransAdd ) + { + for( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) + { + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); + VectorSet( g_studio.lightvalues[k], tr.blend, tr.blend, tr.blend ); + } + } + else + { + for( i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) + { + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS )) + R_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] ); + else R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); + + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms ); + VectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] ); + } + } + } + + if( r_studio_sort_textures->value && need_sort ) + { + // resort opaque and translucent meshes draw order + qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare ); + } + + // NOTE: rewind normals at start + pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + + for( j = 0; j < m_pSubModel->nummesh; j++ ) + { + float oldblend = tr.blend; + short *ptricmds; + float s, t; + + pmesh = g_studio.meshes[j].mesh; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags; + + s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; + t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + { + pglEnable( GL_ALPHA_TEST ); + pglAlphaFunc( GL_GREATER, 0.5f ); + pglDepthMask( GL_TRUE ); + if( R_ModelOpaque( RI.currententity->curstate.rendermode )) + tr.blend = 1.0f; + } + else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE )) + { + if( R_ModelOpaque( RI.currententity->curstate.rendermode )) + { + pglBlendFunc( GL_ONE, GL_ONE ); + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + } + else pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + } + + R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] ); + + if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME )) + R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale ); + else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS )) + R_StudioDrawFloatMesh( ptricmds, pstudionorms ); + else R_StudioDrawNormalMesh( ptricmds, pstudionorms, s, t ); + + if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED )) + { + pglAlphaFunc( GL_GREATER, 0.0f ); + pglDisable( GL_ALPHA_TEST ); + } + else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode )) + { + pglDepthMask( GL_TRUE ); + pglDisable( GL_BLEND ); + } + + r_stats.c_studio_polys += pmesh->numtris; + tr.blend = oldblend; + } +} + +/* +=============== +R_StudioDrawHulls + +=============== +*/ +static void R_StudioDrawHulls( void ) +{ + float alpha, lv; + int i, j; + + if( r_drawentities->value == 4 ) + alpha = 0.5f; + else alpha = 1.0f; + + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) + { + mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); + vec3_t tmp, p[8]; + + for( j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; + tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; + tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; + + Matrix3x4_VectorTransform( g_studio.bonestransform[pbbox[i].bone], tmp, p[j] ); + } + + j = (pbbox[i].group % 8); + + TriBegin( TRI_QUADS ); + TriColor4f( hullcolor[j][0], hullcolor[j][1], hullcolor[j][2], alpha ); + + for( j = 0; j < 6; j++ ) + { + VectorClear( tmp ); + tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; + R_StudioLighting( &lv, pbbox[i].bone, 0, tmp ); + + TriBrightness( lv ); + TriVertex3fv( p[boxpnt[j][0]] ); + TriVertex3fv( p[boxpnt[j][1]] ); + TriVertex3fv( p[boxpnt[j][2]] ); + TriVertex3fv( p[boxpnt[j][3]] ); + } + TriEnd(); + } +} + +/* +=============== +R_StudioDrawAbsBBox + +=============== +*/ +static void R_StudioDrawAbsBBox( void ) +{ + vec3_t p[8], tmp; + float lv; + int i; + + // looks ugly, skip + if( RI.currententity == &clgame.viewent ) + return; + + if( !R_StudioComputeBBox( p )) + return; + + GL_Bind( GL_TEXTURE0, tr.whiteTexture ); + TriColor4f( 0.5f, 0.5f, 1.0f, 0.5f ); + TriRenderMode( kRenderTransAdd ); + + TriBegin( TRI_QUADS ); + for( i = 0; i < 6; i++ ) + { + VectorClear( tmp ); + tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; + R_StudioLighting( &lv, -1, 0, tmp ); + + TriBrightness( lv ); + TriVertex3fv( p[boxpnt[i][0]] ); + TriVertex3fv( p[boxpnt[i][1]] ); + TriVertex3fv( p[boxpnt[i][2]] ); + TriVertex3fv( p[boxpnt[i][3]] ); + } + TriEnd(); +} + +/* +=============== +R_StudioDrawBones + +=============== +*/ +static void R_StudioDrawBones( void ) +{ + mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + vec3_t point; + int i; + + pglDisable( GL_TEXTURE_2D ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pbones[i].parent >= 0 ) + { + pglPointSize( 3.0f ); + pglColor3f( 1, 0.7f, 0 ); + pglBegin( GL_LINES ); + + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); + pglVertex3fv( point ); + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + + pglEnd(); + + pglColor3f( 0, 0, 0.8f ); + pglBegin( GL_POINTS ); + if( pbones[pbones[i].parent].parent != -1 ) + { + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[pbones[i].parent], point ); + pglVertex3fv( point ); + } + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + pglEnd(); + } + else + { + // draw parent bone node + pglPointSize( 5.0f ); + pglColor3f( 0.8f, 0, 0 ); + pglBegin( GL_POINTS ); + Matrix3x4_OriginFromMatrix( g_studio.bonestransform[i], point ); + pglVertex3fv( point ); + pglEnd(); + } + } + + pglPointSize( 1.0f ); + pglEnable( GL_TEXTURE_2D ); +} + +static void R_StudioDrawAttachments( void ) +{ + int i; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + for( i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + mstudioattachment_t *pattachments; + vec3_t v[4]; + + pattachments = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].org, v[0] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[0], v[1] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[1], v[2] ); + Matrix3x4_VectorTransform( g_studio.bonestransform[pattachments[i].bone], pattachments[i].vectors[2], v[3] ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[1] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv (v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[2] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv (v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[3] ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 0, 1, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( v[0] ); + pglEnd(); + pglPointSize( 1.0f ); + } + + pglEnable( GL_TEXTURE_2D ); + pglEnable( GL_DEPTH_TEST ); +} + +/* +=============== +R_StudioSetRemapColors + +=============== +*/ +void R_StudioSetRemapColors( int newTop, int newBottom ) +{ + CL_AllocRemapInfo( newTop, newBottom ); + + if( CL_GetRemapInfoForEntity( RI.currententity )) + { + CL_UpdateRemapInfo( newTop, newBottom ); + m_fDoRemap = true; + } +} + +/* +=============== +R_StudioSetupPlayerModel + +=============== +*/ +static model_t *R_StudioSetupPlayerModel( int index ) +{ + player_info_t *info; + player_model_t *state; + + if( !RI.drawWorld ) + { + // we are in gameui. + info = &gameui.playerinfo; + } + else + { + if( index < 0 || index >= cl.maxclients ) + return NULL; // bad client ? + info = &cl.players[index]; + } + + state = &cl.player_models[index]; + + // g-cont: force for "dev-mode", non-local games and menu preview + if(( host_developer.value || !Host_IsLocalGame( ) || !RI.drawWorld ) && info->model[0] ) + { + if( Q_strcmp( state->name, info->model )) + { + Q_strncpy( state->name, info->model, sizeof( state->name )); + state->name[sizeof( state->name ) - 1] = 0; + + Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model ); + + if( FS_FileExists( state->modelname, false )) + state->model = Mod_ForName( state->modelname, false, true ); + else state->model = NULL; + + if( !state->model ) + state->model = RI.currententity->model; + } + } + else + { + if( state->model != RI.currententity->model ) + state->model = RI.currententity->model; + state->name[0] = 0; + } + + return state->model; +} + +/* +================ +R_GetEntityRenderMode + +check for texture flags +================ +*/ +int R_GetEntityRenderMode( cl_entity_t *ent ) +{ + studiohdr_t *phdr; + mstudiotexture_t *ptexture; + cl_entity_t *oldent; + model_t *model; + int i; + + oldent = RI.currententity; + RI.currententity = ent; + + if( ent->player ) // check it for real playermodel + model = R_StudioSetupPlayerModel( ent->curstate.number - 1 ); + else model = ent->model; + + RI.currententity = oldent; + + if(( phdr = Mod_StudioExtradata( model )) == NULL ) + { + // forcing to choose right sorting type + if(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT )) + return kRenderTransAlpha; + return ent->curstate.rendermode; + } + ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex); + + for( i = 0; i < phdr->numtextures; i++, ptexture++ ) + { + // g-cont. this is not fully proper but better than was + if( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE )) + return kRenderTransAdd; +// if( FBitSet( ptexture->flags, STUDIO_NF_MASKED )) +// return kRenderTransAlpha; + } + return ent->curstate.rendermode; +} + +/* +=============== +R_StudioClientEvents + +=============== +*/ +static void R_StudioClientEvents( void ) +{ + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + cl_entity_t *e = RI.currententity; + int i, sequence; + float end, start; + + if( g_studio.frametime == 0.0 ) + return; // gamepaused + + // fill attachments with interpolated origin + if( m_pStudioHeader->numattachments <= 0 ) + { + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] ); + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] ); + } + + if( FBitSet( e->curstate.effects, EF_MUZZLEFLASH )) + { + dlight_t *el = CL_AllocElight( 0 ); + + ClearBits( e->curstate.effects, EF_MUZZLEFLASH ); + VectorCopy( e->attachment[0], el->origin ); + el->die = cl.time + 0.05f; + el->color.r = 255; + el->color.g = 192; + el->color.b = 64; + el->decay = 320; + el->radius = 24; + } + + sequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 ); + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + // no events for this animation + if( pseqdesc->numevents == 0 ) + return; + + end = R_StudioEstimateFrame( e, pseqdesc ); + start = end - e->curstate.framerate * host.frametime * pseqdesc->fps; + pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); + + if( e->latched.sequencetime == e->curstate.animtime ) + { + if( !FBitSet( pseqdesc->flags, STUDIO_LOOPING )) + start = -0.01f; + } + + for( i = 0; i < pseqdesc->numevents; i++ ) + { + // ignore all non-client-side events + if( pevent[i].event < EVENT_CLIENT ) + continue; + + if( (float)pevent[i].frame > start && pevent[i].frame <= end ) + clgame.dllFuncs.pfnStudioEvent( &pevent[i], e ); + } +} + +/* +=============== +R_StudioGetForceFaceFlags + +=============== +*/ +int R_StudioGetForceFaceFlags( void ) +{ + return g_nForceFaceFlags; +} + +/* +=============== +R_StudioSetForceFaceFlags + +=============== +*/ +void R_StudioSetForceFaceFlags( int flags ) +{ + g_nForceFaceFlags = flags; +} + +/* +=============== +pfnStudioSetHeader + +=============== +*/ +void R_StudioSetHeader( studiohdr_t *pheader ) +{ + m_pStudioHeader = pheader; + m_fDoRemap = false; +} + +/* +=============== +R_StudioSetRenderModel + +=============== +*/ +void R_StudioSetRenderModel( model_t *model ) +{ + RI.currentmodel = model; +} + +/* +=============== +R_StudioSetupRenderer + +=============== +*/ +static void R_StudioSetupRenderer( int rendermode ) +{ + studiohdr_t *phdr = m_pStudioHeader; + int i; + + if( rendermode > kRenderTransAdd ) rendermode = 0; + g_studio.rendermode = bound( 0, rendermode, kRenderTransAdd ); + + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_ALPHA_TEST ); + pglShadeModel( GL_SMOOTH ); + + // a point to setup local to world transform for boneweighted models + if( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO )) + { + // NOTE: extended boneinfo goes immediately after bones + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t )); + + for( i = 0; i < phdr->numbones; i++ ) + Matrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone ); + } +} + +/* +=============== +R_StudioRestoreRenderer + +=============== +*/ +static void R_StudioRestoreRenderer( void ) +{ + if( g_studio.rendermode != kRenderNormal ) + pglDisable( GL_BLEND ); + + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + pglShadeModel( GL_FLAT ); + m_fDoRemap = false; +} + +/* +=============== +R_StudioSetChromeOrigin + +=============== +*/ +void R_StudioSetChromeOrigin( void ) +{ + VectorCopy( RI.vieworg, g_studio.chrome_origin ); +} + +/* +=============== +pfnIsHardware + +Xash3D is always works in hardware mode +=============== +*/ +static int pfnIsHardware( void ) +{ + return 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D +} + +/* +=============== +R_StudioDrawPointsShadow + +=============== +*/ +static void R_StudioDrawPointsShadow( void ) +{ + float *av, height; + float vec_x, vec_y; + mstudiomesh_t *pmesh; + vec3_t point; + int i, k; + + if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) + return; + + if( glState.stencilEnabled ) + pglEnable( GL_STENCIL_TEST ); + + height = g_studio.lightspot[2] + 1.0f; + vec_x = -g_studio.lightvec[0] * 8.0f; + vec_y = -g_studio.lightvec[1] * 8.0f; + + for( k = 0; k < m_pSubModel->nummesh; k++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + k; + ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + + r_stats.c_studio_polys += pmesh->numtris; + + while( i = *( ptricmds++ )) + { + if( i < 0 ) + { + pglBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else + { + pglBegin( GL_TRIANGLE_STRIP ); + } + + + for( ; i > 0; i--, ptricmds += 4 ) + { + av = g_studio.verts[ptricmds[0]]; + point[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] )); + point[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] )); + point[2] = g_studio.lightspot[2] + 1.0f; + + pglVertex3fv( point ); + } + + pglEnd(); + } + } + + if( glState.stencilEnabled ) + pglDisable( GL_STENCIL_TEST ); +} + +/* +=============== +GL_StudioSetRenderMode + +set rendermode for studiomodel +=============== +*/ +void GL_StudioSetRenderMode( int rendermode ) +{ + switch( rendermode ) + { + case kRenderNormal: + break; + case kRenderTransColor: + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglEnable( GL_BLEND ); + break; + case kRenderTransAdd: + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4f( tr.blend, tr.blend, tr.blend, 1.0f ); + pglBlendFunc( GL_ONE, GL_ONE ); + pglDepthMask( GL_FALSE ); + pglEnable( GL_BLEND ); + break; + default: + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend ); + pglDepthMask( GL_TRUE ); + pglEnable( GL_BLEND ); + break; + } +} + +/* +=============== +GL_StudioDrawShadow + +g-cont: don't modify this code it's 100% matched with +original GoldSrc code and used in some mods to enable +studio shadows with some asm tricks +=============== +*/ +static void GL_StudioDrawShadow( void ) +{ + pglDepthMask( GL_TRUE ); + + if( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT )) + { + float color = 1.0 - (tr.blend * 0.5); + + pglDisable( GL_TEXTURE_2D ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglEnable( GL_BLEND ); + pglColor4f( 0.0f, 0.0f, 0.0f, 1.0f - color ); + + pglDepthFunc( GL_LESS ); + R_StudioDrawPointsShadow(); + pglDepthFunc( GL_LEQUAL ); + + pglEnable( GL_TEXTURE_2D ); + pglDisable( GL_BLEND ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglShadeModel( GL_SMOOTH ); + } +} + +/* +==================== +StudioRenderFinal + +==================== +*/ +void R_StudioRenderFinal( void ) +{ + int i, rendermode; + + rendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode; + R_StudioSetupRenderer( rendermode ); + + if( r_drawentities->value == 2 ) + { + R_StudioDrawBones(); + } + else if( r_drawentities->value == 3 ) + { + R_StudioDrawHulls(); + } + else + { + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + R_StudioSetupModel( i, &m_pBodyPart, &m_pSubModel ); + + GL_StudioSetRenderMode( rendermode ); + R_StudioDrawPoints(); + GL_StudioDrawShadow(); + } + } + + if( r_drawentities->value == 4 ) + { + TriRenderMode( kRenderTransAdd ); + R_StudioDrawHulls( ); + TriRenderMode( kRenderNormal ); + } + + if( r_drawentities->value == 5 ) + { + R_StudioDrawAbsBBox( ); + } + + if( r_drawentities->value == 6 ) + { + R_StudioDrawAttachments(); + } + + if( r_drawentities->value == 7 ) + { + vec3_t origin; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0.5, 0 ); + pglVertex3fv( origin ); + pglVertex3fv( g_studio.lightspot ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 1, 0, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( g_studio.lightspot ); + pglEnd(); + pglPointSize( 1.0f ); + + pglEnable( GL_DEPTH_TEST ); + pglEnable( GL_TEXTURE_2D ); + } + + R_StudioRestoreRenderer(); +} + +/* +==================== +StudioRenderModel + +==================== +*/ +void R_StudioRenderModel( void ) +{ + R_StudioSetChromeOrigin(); + R_StudioSetForceFaceFlags( 0 ); + + if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) + { + RI.currententity->curstate.renderfx = kRenderFxNone; + + R_StudioRenderFinal( ); + + R_StudioSetForceFaceFlags( STUDIO_NF_CHROME ); + TriSpriteTexture( R_GetChromeSprite(), 0 ); + RI.currententity->curstate.renderfx = kRenderFxGlowShell; + + R_StudioRenderFinal( ); + } + else + { + R_StudioRenderFinal( ); + } +} + +/* +==================== +StudioEstimateGait + +==================== +*/ +void R_StudioEstimateGait( entity_state_t *pplayer ) +{ + vec3_t est_velocity; + float dt; + + dt = bound( 0.0f, g_studio.frametime, 1.0f ); + + if( dt == 0.0f || m_pPlayerInfo->renderframe == tr.realframecount ) + { + m_flGaitMovement = 0; + return; + } + + VectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); + VectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin ); + m_flGaitMovement = VectorLength( est_velocity ); + + if( dt <= 0.0f || m_flGaitMovement / dt < 5.0f ) + { + m_flGaitMovement = 0.0f; + est_velocity[0] = 0.0f; + est_velocity[1] = 0.0f; + } + + if( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f ) + { + float flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; + + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + if( flYawDiff > 180.0f ) flYawDiff -= 360.0f; + if( flYawDiff < -180.0f ) flYawDiff += 360.0f; + + if( dt < 0.25f ) + flYawDiff *= dt * 4.0f; + else flYawDiff *= dt; + + m_pPlayerInfo->gaityaw += flYawDiff; + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; + + m_flGaitMovement = 0.0f; + } + else + { + m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI ); + if( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f; + if( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f; + } + +} + +/* +==================== +StudioProcessGait + +==================== +*/ +void R_StudioProcessGait( entity_state_t *pplayer ) +{ + mstudioseqdesc_t *pseqdesc; + int iBlend; + float dt, flYaw; // view direction relative to movement + + if( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq ) + RI.currententity->curstate.sequence = 0; + + dt = bound( 0.0f, g_studio.frametime, 1.0f ); + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence; + + R_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] ); + + RI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH]; + RI.currententity->curstate.blending[0] = iBlend; + RI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0]; + RI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0]; + R_StudioEstimateGait( pplayer ); + + // calc side to side turning + flYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw; + flYaw = flYaw - (int)(flYaw / 360) * 360; + if( flYaw < -180.0f ) flYaw = flYaw + 360.0f; + if( flYaw > 180.0f ) flYaw = flYaw - 360.0f; + + if( flYaw > 120.0f ) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw - 180.0f; + } + else if( flYaw < -120.0f ) + { + m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw = flYaw + 180.0f; + } + + // adjust torso + RI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f); + RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0]; + RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1]; + RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2]; + RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3]; + + RI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw; + if( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f; + RI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW]; + + if( pplayer->gaitsequence >= m_pStudioHeader->numseq ) + pplayer->gaitsequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; + + // calc gait frame + if( pseqdesc->linearmovement[0] > 0 ) + m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes; + else m_pPlayerInfo->gaitframe += pseqdesc->fps * dt; + + // do modulo + m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes; + if( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes; +} + +/* +=============== +R_StudioDrawPlayer + +=============== +*/ +static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer ) +{ + int m_nPlayerIndex; + alight_t lighting; + vec3_t dir; + + m_nPlayerIndex = pplayer->number - 1; + + if( m_nPlayerIndex < 0 || m_nPlayerIndex >= cl.maxclients ) + return 0; + + RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex ); + if( RI.currentmodel == NULL ) + return 0; + + R_StudioSetHeader((studiohdr_t *)Mod_StudioExtradata( RI.currentmodel )); + + if( pplayer->gaitsequence ) + { + vec3_t orig_angles; + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + VectorCopy( RI.currententity->angles, orig_angles ); + + R_StudioProcessGait( pplayer ); + + m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; + m_pPlayerInfo = NULL; + + R_StudioSetUpTransform( RI.currententity ); + VectorCopy( orig_angles, RI.currententity->angles ); + } + else + { + RI.currententity->curstate.controller[0] = 127; + RI.currententity->curstate.controller[1] = 127; + RI.currententity->curstate.controller[2] = 127; + RI.currententity->curstate.controller[3] = 127; + RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0]; + RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1]; + RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2]; + RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3]; + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + m_pPlayerInfo->gaitsequence = 0; + + R_StudioSetUpTransform( RI.currententity ); + } + + if( flags & STUDIO_RENDER ) + { + // see if the bounding box lets us trivially reject, also sets + if( !R_StudioCheckBBox( )) + return 0; + + r_stats.c_studio_models_drawn++; + g_studio.framecount++; // render data cache cookie + + if( m_pStudioHeader->numbodyparts == 0 ) + return 1; + } + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + R_StudioSetupBones( RI.currententity ); + R_StudioSaveBones( ); + + m_pPlayerInfo->renderframe = tr.realframecount; + m_pPlayerInfo = NULL; + + if( flags & STUDIO_EVENTS ) + { + R_StudioCalcAttachments( ); + R_StudioClientEvents( ); + + // copy attachments into global entity array + if( RI.currententity->index > 0 ) + { + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); + memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if( flags & STUDIO_RENDER ) + { + if( cl_himodels->value && RI.currentmodel != RI.currententity->model ) + { + // show highest resolution multiplayer model + RI.currententity->curstate.body = 255; + } + + if( !( !host_developer.value && cl.maxclients == 1 ) && ( RI.currentmodel == RI.currententity->model )) + RI.currententity->curstate.body = 1; // force helmet + + lighting.plightvec = dir; + R_StudioDynamicLight( RI.currententity, &lighting ); + + R_StudioEntityLight( &lighting ); + + // model and frame independant + R_StudioSetupLighting( &lighting ); + + m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); + + // get remap colors + g_nTopColor = m_pPlayerInfo->topcolor; + g_nBottomColor = m_pPlayerInfo->bottomcolor; + + if( g_nTopColor < 0 ) g_nTopColor = 0; + if( g_nTopColor > 360 ) g_nTopColor = 360; + if( g_nBottomColor < 0 ) g_nBottomColor = 0; + if( g_nBottomColor > 360 ) g_nBottomColor = 360; + + R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); + + R_StudioRenderModel( ); + m_pPlayerInfo = NULL; + + if( pplayer->weaponmodel ) + { + cl_entity_t saveent = *RI.currententity; + model_t *pweaponmodel = CL_ModelHandle( pplayer->weaponmodel ); + + m_pStudioHeader = (studiohdr_t *)Mod_StudioExtradata( pweaponmodel ); + + R_StudioMergeBones( RI.currententity, pweaponmodel ); + R_StudioSetupLighting( &lighting ); + R_StudioRenderModel( ); + R_StudioCalcAttachments( ); + + *RI.currententity = saveent; + } + } + + return 1; +} + +/* +=============== +R_StudioDrawModel + +=============== +*/ +static int R_StudioDrawModel( int flags ) +{ + alight_t lighting; + vec3_t dir; + + if( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer ) + { + entity_state_t deadplayer; + int result; + + if( RI.currententity->curstate.renderamt <= 0 || RI.currententity->curstate.renderamt > cl.maxclients ) + return 0; + + // get copy of player + deadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 ); + + // clear weapon, movement state + deadplayer.number = RI.currententity->curstate.renderamt; + deadplayer.weaponmodel = 0; + deadplayer.gaitsequence = 0; + + deadplayer.movetype = MOVETYPE_NONE; + VectorCopy( RI.currententity->curstate.angles, deadplayer.angles ); + VectorCopy( RI.currententity->curstate.origin, deadplayer.origin ); + + g_studio.interpolate = false; + result = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player + g_studio.interpolate = true; + + return result; + } + + R_StudioSetHeader((studiohdr_t *)Mod_StudioExtradata( RI.currentmodel )); + + R_StudioSetUpTransform( RI.currententity ); + + if( flags & STUDIO_RENDER ) + { + // see if the bounding box lets us trivially reject, also sets + if( !R_StudioCheckBBox( )) + return 0; + + r_stats.c_studio_models_drawn++; + g_studio.framecount++; // render data cache cookie + + if( m_pStudioHeader->numbodyparts == 0 ) + return 1; + } + + if( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW ) + R_StudioMergeBones( RI.currententity, RI.currentmodel ); + else R_StudioSetupBones( RI.currententity ); + + R_StudioSaveBones(); + + if( flags & STUDIO_EVENTS ) + { + R_StudioCalcAttachments( ); + R_StudioClientEvents( ); + + // copy attachments into global entity array + if( RI.currententity->index > 0 ) + { + cl_entity_t *ent = CL_GetEntityByIndex( RI.currententity->index ); + memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); + } + } + + if( flags & STUDIO_RENDER ) + { + lighting.plightvec = dir; + R_StudioDynamicLight( RI.currententity, &lighting ); + + R_StudioEntityLight( &lighting ); + + // model and frame independant + R_StudioSetupLighting( &lighting ); + + // get remap colors + g_nTopColor = RI.currententity->curstate.colormap & 0xFF; + g_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8; + + R_StudioSetRemapColors( g_nTopColor, g_nBottomColor ); + + R_StudioRenderModel(); + } + + return 1; +} + +/* +================= +R_StudioDrawModelInternal +================= +*/ +void R_StudioDrawModelInternal( cl_entity_t *e, int flags ) +{ + if( !RI.drawWorld ) + { + if( e->player ) + R_StudioDrawPlayer( flags, &e->curstate ); + else R_StudioDrawModel( flags ); + } + else + { + // select the properly method + if( e->player ) + pStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 )); + else pStudioDraw->StudioDrawModel( flags ); + } +} + +/* +================= +R_DrawStudioModel +================= +*/ +void R_DrawStudioModel( cl_entity_t *e ) +{ + if( FBitSet( RI.params, RP_ENVVIEW )) + return; + + R_StudioSetupTimings(); + + if( e->player ) + { + R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + } + else + { + if( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 ) + { + cl_entity_t *parent = CL_GetEntityByIndex( e->curstate.aiment ); + + if( parent && parent->model && parent->model->type == mod_studio ) + { + RI.currententity = parent; + R_StudioDrawModelInternal( RI.currententity, 0 ); + VectorCopy( parent->curstate.origin, e->curstate.origin ); + VectorCopy( parent->origin, e->origin ); + RI.currententity = e; + } + } + + R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS ); + } +} + +/* +================= +R_RunViewmodelEvents +================= +*/ +void R_RunViewmodelEvents( void ) +{ + int i; + + if( r_drawviewmodel->value == 0 ) + return; + + if( CL_IsThirdPerson( )) + return; + + // ignore in thirdperson, camera view or client is died + if( !RP_NORMALPASS() || cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 )) + return; + + RI.currententity = &clgame.viewent; + + if( !RI.currententity->model || RI.currententity->model->type != mod_studio ) + return; + + R_StudioSetupTimings(); + + for( i = 0; i < 4; i++ ) + VectorCopy( cl.simorg, RI.currententity->attachment[i] ); + RI.currentmodel = RI.currententity->model; + + R_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS ); +} + +/* +================= +R_DrawViewModel +================= +*/ +void R_DrawViewModel( void ) +{ + cl_entity_t *view = &clgame.viewent; + colorVec c; + + tr.ignore_lightgamma = true; + c = R_LightPoint( view->origin ); + tr.ignore_lightgamma = false; + cl.local.light_level = (c.r + c.g + c.b) / 3; + + if( r_drawviewmodel->value == 0 ) + return; + + if( CL_IsThirdPerson( )) + return; + + // ignore in thirdperson, camera view or client is died + if( !RP_NORMALPASS() || cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 )) + return; + + tr.blend = CL_FxBlend( view ) / 255.0f; + if( !R_ModelOpaque( view->curstate.rendermode ) && tr.blend <= 0.0f ) + return; // invisible ? + + RI.currententity = view; + + if( !RI.currententity->model ) + return; + + // adjust the depth range to prevent view model from poking into walls + pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); + RI.currentmodel = RI.currententity->model; + + // backface culling for left-handed weapons + if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) + { + tr.fFlipViewModel = true; + pglFrontFace( GL_CW ); + } + + switch( RI.currententity->model->type ) + { + case mod_alias: + R_DrawAliasModel( RI.currententity ); + break; + case mod_studio: + R_StudioSetupTimings(); + R_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER ); + break; + } + + // restore depth range + pglDepthRange( gldepthmin, gldepthmax ); + + // backface culling for left-handed weapons + if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull ) + { + tr.fFlipViewModel = false; + pglFrontFace( GL_CCW ); + } +} + +/* +==================== +R_StudioLoadTexture + +load model texture with unique name +==================== +*/ +static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture ) +{ + size_t size; + int flags = 0; + char texname[128], name[128], mdlname[128]; + imgfilter_t *filter = NULL; + texture_t *tx = NULL; + + if( ptexture->flags & STUDIO_NF_NORMALMAP ) + flags |= (TF_NORMALMAP); + + // store some textures for remapping + if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ) || !Q_strnicmp( ptexture->name, "remap", 5 )) + { + int i, size; + char val[6]; + byte *pixels; + + i = mod->numtextures; + mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); + size = ptexture->width * ptexture->height + 768; + tx = Mem_Alloc( mod->mempool, sizeof( *tx ) + size ); + mod->textures[i] = tx; + + // store ranges into anim_min, anim_max etc + if( !Q_strnicmp( ptexture->name, "DM_Base", 7 )) + { + Q_strncpy( tx->name, "DM_Base", sizeof( tx->name )); + tx->anim_min = PLATE_HUE_START; // topcolor start + tx->anim_max = PLATE_HUE_END; // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + tx->anim_total = SUIT_HUE_END;// bottomcolor end + } + else + { + Q_strncpy( tx->name, "DM_User", sizeof( tx->name )); // custom remapped + Q_strncpy( val, ptexture->name + 7, 4 ); + tx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start + Q_strncpy( val, ptexture->name + 11, 4 ); + tx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end + // bottomcolor start always equal is (topcolor end + 1) + Q_strncpy( val, ptexture->name + 15, 4 ); + tx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end + } + + tx->width = ptexture->width; + tx->height = ptexture->height; + + // the pixels immediately follow the structures + pixels = (byte *)phdr + ptexture->index; + memcpy( tx+1, pixels, size ); + + ptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image + flags |= TF_FORCE_COLOR; + + mod->numtextures++; // done + } + + Q_strncpy( mdlname, mod->name, sizeof( mdlname )); + COM_FileBase( ptexture->name, name ); + COM_StripExtension( mdlname ); + + // loading texture filter for studiomodel + if( !FBitSet( ptexture->flags, STUDIO_NF_COLORMAP )) + filter = R_FindTexFilter( va( "%s.mdl/%s", mdlname, name )); // grab texture filter + + if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS )) + SetBits( flags, TF_NOMIPMAP ); + + // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it + ptexture->index = (int)((byte *)phdr) + ptexture->index; + size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; + + if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + flags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing + + // build the texname + Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); + ptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags, filter ); + + if( !ptexture->index ) + { + ptexture->index = tr.defaultTexture; + } + else if( tx ) + { + // duplicate texnum for easy acess + tx->gl_texturenum = ptexture->index; + } +} + +/* +================= +Mod_StudioLoadTextures +================= +*/ +void Mod_StudioLoadTextures( model_t *mod, void *data ) +{ + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; + + if( !phdr || host.type == HOST_DEDICATED ) + return; + + ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + if( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS ) + { + for( i = 0; i < phdr->numtextures; i++ ) + R_StudioLoadTexture( mod, phdr, &ptexture[i] ); + } +} + +/* +================= +Mod_StudioLoadTextures +================= +*/ +void Mod_StudioUnloadTextures( void *data ) +{ + studiohdr_t *phdr = (studiohdr_t *)data; + mstudiotexture_t *ptexture; + int i; + + if( !phdr || host.type == HOST_DEDICATED ) + return; + + ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex); + + // release all textures + for( i = 0; i < phdr->numtextures; i++ ) + { + if( ptexture[i].index == tr.defaultTexture ) + continue; + GL_FreeTexture( ptexture[i].index ); + } +} + +static engine_studio_api_t gStudioAPI = +{ + Mod_Calloc, + Mod_CacheCheck, + Mod_LoadCacheFile, + pfnMod_ForName, + Mod_StudioExtradata, + CL_ModelHandle, + pfnGetCurrentEntity, + pfnPlayerInfo, + R_StudioGetPlayerState, + pfnGetViewEntity, + pfnGetEngineTimes, + pfnCVarGetPointer, + pfnGetViewInfo, + R_GetChromeSprite, + pfnGetModelCounters, + pfnGetAliasScale, + pfnStudioGetBoneTransform, + pfnStudioGetLightTransform, + pfnStudioGetAliasTransform, + pfnStudioGetRotationMatrix, + R_StudioSetupModel, + R_StudioCheckBBox, + R_StudioDynamicLight, + R_StudioEntityLight, + R_StudioSetupLighting, + R_StudioDrawPoints, + R_StudioDrawHulls, + R_StudioDrawAbsBBox, + R_StudioDrawBones, + R_StudioSetupSkin, + R_StudioSetRemapColors, + R_StudioSetupPlayerModel, + R_StudioClientEvents, + R_StudioGetForceFaceFlags, + R_StudioSetForceFaceFlags, + R_StudioSetHeader, + R_StudioSetRenderModel, + R_StudioSetupRenderer, + R_StudioRestoreRenderer, + R_StudioSetChromeOrigin, + pfnIsHardware, + GL_StudioDrawShadow, + GL_StudioSetRenderMode, + R_StudioSetRenderamt, + R_StudioSetCullState, + R_StudioRenderShadow, +}; + +static r_studio_interface_t gStudioDraw = +{ + STUDIO_INTERFACE_VERSION, + R_StudioDrawModel, + R_StudioDrawPlayer, +}; + +/* +=============== +CL_InitStudioAPI + +Initialize client studio +=============== +*/ +void CL_InitStudioAPI( void ) +{ + pStudioDraw = &gStudioDraw; + + // Xash will be used internal StudioModelRenderer + if( !clgame.dllFuncs.pfnGetStudioModelInterface ) + return; + + Con_DPrintf( "InitStudioAPI " ); + + if( clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI )) + { + Con_DPrintf( "- ok\n" ); + return; + } + + Con_DPrintf( "- failed\n" ); + + // NOTE: we always return true even if game interface was not correct + // because we need Draw our StudioModels + // just restore pointer to builtin function + pStudioDraw = &gStudioDraw; +} \ No newline at end of file diff --git a/engine/client/gl_vidnt.c b/engine/client/gl_vidnt.c new file mode 100644 index 00000000..e45b15b9 --- /dev/null +++ b/engine/client/gl_vidnt.c @@ -0,0 +1,1766 @@ +/* +gl_vidnt.c - NT GL vid component +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "mod_local.h" +#include "input.h" + +#define VID_AUTOMODE "-1" +#define VID_DEFAULTMODE 3.0f +#define DISP_CHANGE_BADDUALVIEW -6 // MSVC 6.0 doesn't +#define num_vidmodes ARRAYSIZE( vidmode ) +#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_SYSMENU|WS_CAPTION|WS_VISIBLE) +#define WINDOW_EX_STYLE (0) +#define WINDOW_NAME "Xash3D Window" // Half-Life + +convar_t *gl_extensions; +convar_t *gl_texture_anisotropy; +convar_t *gl_texture_lodbias; +convar_t *gl_texture_nearest; +convar_t *gl_lightmap_nearest; +convar_t *gl_keeptjunctions; +convar_t *gl_showtextures; +convar_t *gl_detailscale; +convar_t *gl_check_errors; +convar_t *gl_polyoffset; +convar_t *gl_wireframe; +convar_t *gl_finish; +convar_t *gl_nosort; +convar_t *gl_vsync; +convar_t *gl_clear; +convar_t *gl_test; + +convar_t *window_xpos; +convar_t *window_ypos; +convar_t *r_speeds; +convar_t *r_fullbright; +convar_t *r_norefresh; +convar_t *r_lighting_extended; +convar_t *r_lighting_modulate; +convar_t *r_lighting_ambient; +convar_t *r_detailtextures; +convar_t *r_drawentities; +convar_t *r_adjust_fov; +convar_t *r_decals; +convar_t *r_novis; +convar_t *r_nocull; +convar_t *r_lockpvs; +convar_t *r_lockfrustum; +convar_t *r_traceglow; +convar_t *r_dynamic; +convar_t *r_lightmap; + +convar_t *vid_displayfrequency; +convar_t *vid_fullscreen; +convar_t *vid_brightness; +convar_t *vid_gamma; +convar_t *vid_mode; + +byte *r_temppool; + +ref_globals_t tr; +glconfig_t glConfig; +glstate_t glState; +glwstate_t glw_state; + +typedef enum +{ + rserr_ok, + rserr_invalid_fullscreen, + rserr_invalid_mode, + rserr_unknown +} rserr_t; + +typedef struct vidmode_s +{ + const char *desc; + int width; + int height; + qboolean wideScreen; +} vidmode_t; + +vidmode_t vidmode[] = +{ +{ "640 x 480", 640, 480, false }, +{ "800 x 600", 800, 600, false }, +{ "960 x 720", 960, 720, false }, +{ "1024 x 768", 1024, 768, false }, +{ "1152 x 864", 1152, 864, false }, +{ "1280 x 800", 1280, 800, false }, +{ "1280 x 960", 1280, 960, false }, +{ "1280 x 1024", 1280, 1024, false }, +{ "1600 x 1200", 1600, 1200, false }, +{ "2048 x 1536", 2048, 1536, false }, +{ "800 x 480 (wide)", 800, 480, true }, +{ "856 x 480 (wide)", 856, 480, true }, +{ "960 x 540 (wide)", 960, 540, true }, +{ "1024 x 576 (wide)", 1024, 576, true }, +{ "1024 x 600 (wide)", 1024, 600, true }, +{ "1280 x 720 (wide)", 1280, 720, true }, +{ "1360 x 768 (wide)", 1360, 768, true }, +{ "1366 x 768 (wide)", 1366, 768, true }, +{ "1440 x 900 (wide)", 1440, 900, true }, +{ "1680 x 1050 (wide)", 1680, 1050, true }, +{ "1920 x 1080 (wide)", 1920, 1080, true }, +{ "1920 x 1200 (wide)", 1920, 1200, true }, +{ "2560 x 1440 (wide)", 2560, 1440, true }, +{ "2560 x 1600 (wide)", 2560, 1600, true }, +{ "1600 x 900 (wide)", 1600, 900, true }, +{ "3840 x 2160 (wide)", 3840, 2160, true }, +}; + +static dllfunc_t opengl_110funcs[] = +{ +{ "glClearColor" , (void **)&pglClearColor }, +{ "glClear" , (void **)&pglClear }, +{ "glAlphaFunc" , (void **)&pglAlphaFunc }, +{ "glBlendFunc" , (void **)&pglBlendFunc }, +{ "glCullFace" , (void **)&pglCullFace }, +{ "glDrawBuffer" , (void **)&pglDrawBuffer }, +{ "glReadBuffer" , (void **)&pglReadBuffer }, +{ "glAccum" , (void **)&pglAccum }, +{ "glEnable" , (void **)&pglEnable }, +{ "glDisable" , (void **)&pglDisable }, +{ "glEnableClientState" , (void **)&pglEnableClientState }, +{ "glDisableClientState" , (void **)&pglDisableClientState }, +{ "glGetBooleanv" , (void **)&pglGetBooleanv }, +{ "glGetDoublev" , (void **)&pglGetDoublev }, +{ "glGetFloatv" , (void **)&pglGetFloatv }, +{ "glGetIntegerv" , (void **)&pglGetIntegerv }, +{ "glGetError" , (void **)&pglGetError }, +{ "glGetString" , (void **)&pglGetString }, +{ "glFinish" , (void **)&pglFinish }, +{ "glFlush" , (void **)&pglFlush }, +{ "glClearDepth" , (void **)&pglClearDepth }, +{ "glDepthFunc" , (void **)&pglDepthFunc }, +{ "glDepthMask" , (void **)&pglDepthMask }, +{ "glDepthRange" , (void **)&pglDepthRange }, +{ "glFrontFace" , (void **)&pglFrontFace }, +{ "glDrawElements" , (void **)&pglDrawElements }, +{ "glDrawArrays" , (void **)&pglDrawArrays }, +{ "glColorMask" , (void **)&pglColorMask }, +{ "glIndexPointer" , (void **)&pglIndexPointer }, +{ "glVertexPointer" , (void **)&pglVertexPointer }, +{ "glNormalPointer" , (void **)&pglNormalPointer }, +{ "glColorPointer" , (void **)&pglColorPointer }, +{ "glTexCoordPointer" , (void **)&pglTexCoordPointer }, +{ "glArrayElement" , (void **)&pglArrayElement }, +{ "glColor3f" , (void **)&pglColor3f }, +{ "glColor3fv" , (void **)&pglColor3fv }, +{ "glColor4f" , (void **)&pglColor4f }, +{ "glColor4fv" , (void **)&pglColor4fv }, +{ "glColor3ub" , (void **)&pglColor3ub }, +{ "glColor4ub" , (void **)&pglColor4ub }, +{ "glColor4ubv" , (void **)&pglColor4ubv }, +{ "glTexCoord1f" , (void **)&pglTexCoord1f }, +{ "glTexCoord2f" , (void **)&pglTexCoord2f }, +{ "glTexCoord3f" , (void **)&pglTexCoord3f }, +{ "glTexCoord4f" , (void **)&pglTexCoord4f }, +{ "glTexCoord1fv" , (void **)&pglTexCoord1fv }, +{ "glTexCoord2fv" , (void **)&pglTexCoord2fv }, +{ "glTexCoord3fv" , (void **)&pglTexCoord3fv }, +{ "glTexCoord4fv" , (void **)&pglTexCoord4fv }, +{ "glTexGenf" , (void **)&pglTexGenf }, +{ "glTexGenfv" , (void **)&pglTexGenfv }, +{ "glTexGeni" , (void **)&pglTexGeni }, +{ "glVertex2f" , (void **)&pglVertex2f }, +{ "glVertex3f" , (void **)&pglVertex3f }, +{ "glVertex3fv" , (void **)&pglVertex3fv }, +{ "glNormal3f" , (void **)&pglNormal3f }, +{ "glNormal3fv" , (void **)&pglNormal3fv }, +{ "glBegin" , (void **)&pglBegin }, +{ "glEnd" , (void **)&pglEnd }, +{ "glLineWidth" , (void**)&pglLineWidth }, +{ "glPointSize" , (void**)&pglPointSize }, +{ "glMatrixMode" , (void **)&pglMatrixMode }, +{ "glOrtho" , (void **)&pglOrtho }, +{ "glRasterPos2f" , (void **) &pglRasterPos2f }, +{ "glFrustum" , (void **)&pglFrustum }, +{ "glViewport" , (void **)&pglViewport }, +{ "glPushMatrix" , (void **)&pglPushMatrix }, +{ "glPopMatrix" , (void **)&pglPopMatrix }, +{ "glPushAttrib" , (void **)&pglPushAttrib }, +{ "glPopAttrib" , (void **)&pglPopAttrib }, +{ "glLoadIdentity" , (void **)&pglLoadIdentity }, +{ "glLoadMatrixd" , (void **)&pglLoadMatrixd }, +{ "glLoadMatrixf" , (void **)&pglLoadMatrixf }, +{ "glMultMatrixd" , (void **)&pglMultMatrixd }, +{ "glMultMatrixf" , (void **)&pglMultMatrixf }, +{ "glRotated" , (void **)&pglRotated }, +{ "glRotatef" , (void **)&pglRotatef }, +{ "glScaled" , (void **)&pglScaled }, +{ "glScalef" , (void **)&pglScalef }, +{ "glTranslated" , (void **)&pglTranslated }, +{ "glTranslatef" , (void **)&pglTranslatef }, +{ "glReadPixels" , (void **)&pglReadPixels }, +{ "glDrawPixels" , (void **)&pglDrawPixels }, +{ "glStencilFunc" , (void **)&pglStencilFunc }, +{ "glStencilMask" , (void **)&pglStencilMask }, +{ "glStencilOp" , (void **)&pglStencilOp }, +{ "glClearStencil" , (void **)&pglClearStencil }, +{ "glIsEnabled" , (void **)&pglIsEnabled }, +{ "glIsList" , (void **)&pglIsList }, +{ "glIsTexture" , (void **)&pglIsTexture }, +{ "glTexEnvf" , (void **)&pglTexEnvf }, +{ "glTexEnvfv" , (void **)&pglTexEnvfv }, +{ "glTexEnvi" , (void **)&pglTexEnvi }, +{ "glTexParameterf" , (void **)&pglTexParameterf }, +{ "glTexParameterfv" , (void **)&pglTexParameterfv }, +{ "glTexParameteri" , (void **)&pglTexParameteri }, +{ "glHint" , (void **)&pglHint }, +{ "glPixelStoref" , (void **)&pglPixelStoref }, +{ "glPixelStorei" , (void **)&pglPixelStorei }, +{ "glGenTextures" , (void **)&pglGenTextures }, +{ "glDeleteTextures" , (void **)&pglDeleteTextures }, +{ "glBindTexture" , (void **)&pglBindTexture }, +{ "glTexImage1D" , (void **)&pglTexImage1D }, +{ "glTexImage2D" , (void **)&pglTexImage2D }, +{ "glTexSubImage1D" , (void **)&pglTexSubImage1D }, +{ "glTexSubImage2D" , (void **)&pglTexSubImage2D }, +{ "glCopyTexImage1D" , (void **)&pglCopyTexImage1D }, +{ "glCopyTexImage2D" , (void **)&pglCopyTexImage2D }, +{ "glCopyTexSubImage1D" , (void **)&pglCopyTexSubImage1D }, +{ "glCopyTexSubImage2D" , (void **)&pglCopyTexSubImage2D }, +{ "glScissor" , (void **)&pglScissor }, +{ "glGetTexImage" , (void **)&pglGetTexImage }, +{ "glGetTexEnviv" , (void **)&pglGetTexEnviv }, +{ "glPolygonOffset" , (void **)&pglPolygonOffset }, +{ "glPolygonMode" , (void **)&pglPolygonMode }, +{ "glPolygonStipple" , (void **)&pglPolygonStipple }, +{ "glClipPlane" , (void **)&pglClipPlane }, +{ "glGetClipPlane" , (void **)&pglGetClipPlane }, +{ "glShadeModel" , (void **)&pglShadeModel }, +{ "glGetTexLevelParameteriv" , (void **)&pglGetTexLevelParameteriv }, +{ "glGetTexLevelParameterfv" , (void **)&pglGetTexLevelParameterfv }, +{ "glFogfv" , (void **)&pglFogfv }, +{ "glFogf" , (void **)&pglFogf }, +{ "glFogi" , (void **)&pglFogi }, +{ NULL , NULL } +}; + +static dllfunc_t debugoutputfuncs[] = +{ +{ "glDebugMessageControlARB" , (void **)&pglDebugMessageControlARB }, +{ "glDebugMessageInsertARB" , (void **)&pglDebugMessageInsertARB }, +{ "glDebugMessageCallbackARB" , (void **)&pglDebugMessageCallbackARB }, +{ "glGetDebugMessageLogARB" , (void **)&pglGetDebugMessageLogARB }, +{ NULL , NULL } +}; + +static dllfunc_t multitexturefuncs[] = +{ +{ "glMultiTexCoord1fARB" , (void **)&pglMultiTexCoord1f }, +{ "glMultiTexCoord2fARB" , (void **)&pglMultiTexCoord2f }, +{ "glMultiTexCoord3fARB" , (void **)&pglMultiTexCoord3f }, +{ "glMultiTexCoord4fARB" , (void **)&pglMultiTexCoord4f }, +{ "glActiveTextureARB" , (void **)&pglActiveTexture }, +{ "glActiveTextureARB" , (void **)&pglActiveTextureARB }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTexture }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTextureARB }, +{ NULL , NULL } +}; + +static dllfunc_t texture3dextfuncs[] = +{ +{ "glTexImage3DEXT" , (void **)&pglTexImage3D }, +{ "glTexSubImage3DEXT" , (void **)&pglTexSubImage3D }, +{ "glCopyTexSubImage3DEXT" , (void **)&pglCopyTexSubImage3D }, +{ NULL , NULL } +}; + +static dllfunc_t texturecompressionfuncs[] = +{ +{ "glCompressedTexImage3DARB" , (void **)&pglCompressedTexImage3DARB }, +{ "glCompressedTexImage2DARB" , (void **)&pglCompressedTexImage2DARB }, +{ "glCompressedTexImage1DARB" , (void **)&pglCompressedTexImage1DARB }, +{ "glCompressedTexSubImage3DARB" , (void **)&pglCompressedTexSubImage3DARB }, +{ "glCompressedTexSubImage2DARB" , (void **)&pglCompressedTexSubImage2DARB }, +{ "glCompressedTexSubImage1DARB" , (void **)&pglCompressedTexSubImage1DARB }, +{ "glGetCompressedTexImageARB" , (void **)&pglGetCompressedTexImage }, +{ NULL , NULL } +}; + +static dllfunc_t wgl_funcs[] = +{ +{ "wglSwapBuffers" , (void **)&pwglSwapBuffers }, +{ "wglCreateContext" , (void **)&pwglCreateContext }, +{ "wglDeleteContext" , (void **)&pwglDeleteContext }, +{ "wglMakeCurrent" , (void **)&pwglMakeCurrent }, +{ "wglGetCurrentContext" , (void **)&pwglGetCurrentContext }, +{ NULL , NULL } +}; + +static dllfunc_t wglproc_funcs[] = +{ +{ "wglGetProcAddress" , (void **)&pwglGetProcAddress }, +{ NULL, NULL } +}; + +static dllfunc_t wglswapintervalfuncs[] = +{ +{ "wglSwapIntervalEXT" , (void **)&pwglSwapIntervalEXT }, +{ NULL, NULL } +}; + +static dllfunc_t wglgetextensionsstring[] = +{ +{ "wglGetExtensionsStringEXT" , (void **)&pwglGetExtensionsStringEXT }, +{ NULL, NULL } +}; + +dll_info_t opengl_dll = { "opengl32.dll", wgl_funcs, true }; + +/* +======================== +DebugCallback + +For ARB_debug_output +======================== +*/ +static void CALLBACK GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLuint severity, GLint length, const GLcharARB *message, GLvoid *userParam ) +{ + switch( type ) + { + case GL_DEBUG_TYPE_ERROR_ARB: + Con_Printf( S_OPENGL_ERROR "%s\n", message ); + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: + Con_Printf( S_OPENGL_WARN "%s\n", message ); + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: + Con_Printf( S_OPENGL_WARN "%s\n", message ); + break; + case GL_DEBUG_TYPE_PORTABILITY_ARB: + if( host_developer.value < DEV_EXTENDED ) + return; + Con_Printf( S_OPENGL_WARN "%s\n", message ); + break; + case GL_DEBUG_TYPE_PERFORMANCE_ARB: + if( host_developer.value < DEV_EXTENDED ) + return; + Con_Printf( S_OPENGL_NOTE "%s\n", message ); + break; + case GL_DEBUG_TYPE_OTHER_ARB: + default: Con_Printf( S_OPENGL_NOTE "%s\n", message ); + break; + } +} + +/* +================= +GL_SetExtension +================= +*/ +void GL_SetExtension( int r_ext, int enable ) +{ + if( r_ext >= 0 && r_ext < GL_EXTCOUNT ) + glConfig.extension[r_ext] = enable ? GL_TRUE : GL_FALSE; + else Con_Printf( S_ERROR "GL_SetExtension: invalid extension %d\n", r_ext ); +} + +/* +================= +GL_Support +================= +*/ +qboolean GL_Support( int r_ext ) +{ + if( r_ext >= 0 && r_ext < GL_EXTCOUNT ) + return glConfig.extension[r_ext] ? true : false; + Con_Printf( S_ERROR "GL_Support: invalid extension %d\n", r_ext ); + + return false; +} + +/* +================= +GL_MaxTextureUnits +================= +*/ +int GL_MaxTextureUnits( void ) +{ + if( GL_Support( GL_SHADER_GLSL100_EXT )) + return Q_min( Q_max( glConfig.max_texture_coords, glConfig.max_teximage_units ), MAX_TEXTURE_UNITS ); + return glConfig.max_texture_units; +} + +/* +================= +GL_GetProcAddress +================= +*/ +void *GL_GetProcAddress( const char *name ) +{ + void *p = NULL; + + if( pwglGetProcAddress != NULL ) + p = (void *)pwglGetProcAddress( name ); + if( !p ) p = (void *)Sys_GetProcAddress( &opengl_dll, name ); + + return p; +} + +/* +================= +GL_CheckExtension +================= +*/ +void GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char *cvarname, int r_ext ) +{ + const dllfunc_t *func; + convar_t *parm = NULL; + const char *extensions_string; + + MsgDev( D_NOTE, "GL_CheckExtension: %s ", name ); + GL_SetExtension( r_ext, true ); + + if( cvarname ) + { + // system config disable extensions + parm = Cvar_Get( cvarname, "1", FCVAR_GLCONFIG, va( CVAR_GLCONFIG_DESCRIPTION, name )); + } + + if(( parm && !CVAR_TO_BOOL( parm )) || ( !CVAR_TO_BOOL( gl_extensions ) && r_ext != GL_OPENGL_110 )) + { + MsgDev( D_NOTE, "- disabled\n" ); + GL_SetExtension( r_ext, false ); + return; // nothing to process at + } + + extensions_string = glConfig.extensions_string; + + if( name[0] == 'W' && name[1] == 'G' && name[2] == 'L' && glConfig.wgl_extensions_string != NULL ) + extensions_string = glConfig.wgl_extensions_string; + + if(( name[2] == '_' || name[3] == '_' ) && !Q_strstr( extensions_string, name )) + { + GL_SetExtension( r_ext, false ); // update render info + MsgDev( D_NOTE, "- ^1failed\n" ); + return; + } + + // clear exports + for( func = funcs; func && func->name; func++ ) + *func->func = NULL; + + for( func = funcs; func && func->name != NULL; func++ ) + { + // functions are cleared before all the extensions are evaluated + if((*func->func = (void *)GL_GetProcAddress( func->name )) == NULL ) + GL_SetExtension( r_ext, false ); // one or more functions are invalid, extension will be disabled + } + + if( GL_Support( r_ext )) + MsgDev( D_NOTE, "- ^2enabled\n" ); + else MsgDev( D_NOTE, "- ^1failed\n" ); +} + +/* +=============== +GL_UpdateSwapInterval +=============== +*/ +void GL_UpdateSwapInterval( void ) +{ + // disable VSync while level is loading + if( cls.state < ca_active ) + { + if( pwglSwapIntervalEXT != NULL ) + pwglSwapIntervalEXT( 0 ); + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + } + else if( FBitSet( gl_vsync->flags, FCVAR_CHANGED )) + { + ClearBits( gl_vsync->flags, FCVAR_CHANGED ); + + if( pwglSwapIntervalEXT != NULL ) + pwglSwapIntervalEXT( bound( -1, (int)gl_vsync->value, 1 )); + } +} + +/* +=============== +GL_SetDefaultTexState +=============== +*/ +static void GL_SetDefaultTexState( void ) +{ + int i; + + memset( glState.currentTextures, -1, MAX_TEXTURE_UNITS * sizeof( *glState.currentTextures )); + memset( glState.texCoordArrayMode, 0, MAX_TEXTURE_UNITS * sizeof( *glState.texCoordArrayMode )); + memset( glState.genSTEnabled, 0, MAX_TEXTURE_UNITS * sizeof( *glState.genSTEnabled )); + + for( i = 0; i < MAX_TEXTURE_UNITS; i++ ) + { + glState.currentTextureTargets[i] = GL_NONE; + glState.texIdentityMatrix[i] = true; + } +} + +/* +=============== +GL_SetDefaultState +=============== +*/ +static void GL_SetDefaultState( void ) +{ + memset( &glState, 0, sizeof( glState )); + GL_SetDefaultTexState (); + + // init draw stack + tr.draw_list = &tr.draw_stack[0]; + tr.draw_stack_pos = 0; +} + +/* +=============== +GL_ContextError +=============== +*/ +static void GL_ContextError( void ) +{ + DWORD error = GetLastError(); + + if( error == ( 0xc0070000|ERROR_INVALID_VERSION_ARB )) + Con_Printf( S_ERROR "Unsupported OpenGL context version (%s).\n", "2.0" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PROFILE_ARB )) + Con_Printf( S_ERROR "Unsupported OpenGL profile (%s).\n", "compat" ); + else if( error == ( 0xc0070000|ERROR_INVALID_OPERATION )) + Con_Printf( S_ERROR "wglCreateContextAttribsARB returned invalid operation.\n" ); + else if( error == ( 0xc0070000|ERROR_DC_NOT_FOUND )) + Con_Printf( S_ERROR "wglCreateContextAttribsARB returned dc not found.\n" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PIXEL_FORMAT )) + Con_Printf( S_ERROR "wglCreateContextAttribsARB returned dc not found.\n" ); + else if( error == ( 0xc0070000|ERROR_NO_SYSTEM_RESOURCES )) + Con_Printf( S_ERROR "wglCreateContextAttribsARB ran out of system resources.\n" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PARAMETER )) + Con_Printf( S_ERROR "wglCreateContextAttribsARB reported invalid parameter.\n" ); + else Con_Printf( S_ERROR "Unknown error creating an OpenGL (%s) Context.\n", "2.0" ); +} + +/* +================= +GL_CreateContext +================= +*/ +qboolean GL_CreateContext( void ) +{ + HGLRC hBaseRC; + + glw_state.extended = false; + + if(!( glw_state.hGLRC = pwglCreateContext( glw_state.hDC ))) + return GL_DeleteContext(); + + if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) + return GL_DeleteContext(); + + if( !Sys_CheckParm( "-gldebug" ) || !host_developer.value ) // debug bit kill the perfomance + return true; + + pwglCreateContextAttribsARB = GL_GetProcAddress( "wglCreateContextAttribsARB" ); + + if( pwglCreateContextAttribsARB != NULL ) + { + int attribs[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, 2, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, +// WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + 0 + }; + + hBaseRC = glw_state.hGLRC; // backup + glw_state.hGLRC = NULL; + + if( !( glw_state.hGLRC = pwglCreateContextAttribsARB( glw_state.hDC, NULL, attribs ))) + { + glw_state.hGLRC = hBaseRC; + GL_ContextError(); + return true; // just use old context + } + + if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) + { + pwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = hBaseRC; + GL_ContextError(); + return true; + } + + MsgDev( D_NOTE, "GL_CreateContext: using extended context\n" ); + pwglDeleteContext( hBaseRC ); // release first context + glw_state.extended = true; + } + + return true; +} + +/* +================= +GL_UpdateContext +================= +*/ +qboolean GL_UpdateContext( void ) +{ + if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) + return GL_DeleteContext(); + + return true; +} + +/* +================= +GL_DeleteContext + +always return false +================= +*/ +qboolean GL_DeleteContext( void ) +{ + if( pwglMakeCurrent ) + pwglMakeCurrent( NULL, NULL ); + + if( glw_state.hGLRC ) + { + if( pwglDeleteContext ) + pwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = NULL; + } + + if( glw_state.hDC ) + { + ReleaseDC( host.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + } + + return false; +} + +/* +================= +VID_ChoosePFD +================= +*/ +static int VID_ChoosePFD( PIXELFORMATDESCRIPTOR *pfd, int colorBits, int alphaBits, int depthBits, int stencilBits ) +{ + int pixelFormat = 0; + + MsgDev( D_NOTE, "VID_ChoosePFD( color %i, alpha %i, depth %i, stencil %i )\n", colorBits, alphaBits, depthBits, stencilBits ); + + // Fill out the PFD + pfd->nSize = sizeof (PIXELFORMATDESCRIPTOR); + pfd->nVersion = 1; + pfd->dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + pfd->iPixelType = PFD_TYPE_RGBA; + + pfd->cColorBits = colorBits; + pfd->cRedBits = 0; + pfd->cRedShift = 0; + pfd->cGreenBits = 0; + pfd->cGreenShift = 0; + pfd->cBlueBits = 0; + pfd->cBlueShift = 0; // wow! Blue Shift %) + + pfd->cAlphaBits = alphaBits; + pfd->cAlphaShift = 0; + + pfd->cAccumBits = 0; + pfd->cAccumRedBits = 0; + pfd->cAccumGreenBits = 0; + pfd->cAccumBlueBits = 0; + pfd->cAccumAlphaBits= 0; + + pfd->cDepthBits = depthBits; + pfd->cStencilBits = stencilBits; + + pfd->cAuxBuffers = 0; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->bReserved = 0; + + pfd->dwLayerMask = 0; + pfd->dwVisibleMask = 0; + pfd->dwDamageMask = 0; + + // count PFDs + pixelFormat = ChoosePixelFormat( glw_state.hDC, pfd ); + + if( !pixelFormat ) + { + Con_Printf( S_ERROR "VID_ChoosePFD failed\n" ); + return 0; + } + + return pixelFormat; +} + +/* +================= +VID_StartupGamma +================= +*/ +void VID_StartupGamma( void ) +{ + BuildGammaTable( vid_gamma->value, vid_brightness->value ); + MsgDev( D_NOTE, "VID_StartupGamma: gamma %g brightness %g\n", vid_gamma->value, vid_brightness->value ); + ClearBits( vid_brightness->flags, FCVAR_CHANGED ); + ClearBits( vid_gamma->flags, FCVAR_CHANGED ); +} + +/* +================= +VID_InitDefaultResolution +================= +*/ +void VID_InitDefaultResolution( void ) +{ + // we need to have something valid here + // until video subsystem initialized + glState.width = 640; + glState.height = 480; +} + +/* +================= +VID_GetModeString +================= +*/ +const char *VID_GetModeString( int vid_mode ) +{ + if( vid_mode >= 0 && vid_mode < num_vidmodes ) + return vidmode[vid_mode].desc; + return NULL; // out of bounds +} + +/* +================= +GL_SetPixelformat +================= +*/ +qboolean GL_SetPixelformat( void ) +{ + PIXELFORMATDESCRIPTOR PFD; + int alphaBits = 8; + int stencilBits = 8; + int pixelFormat = 0; + + if(( glw_state.hDC = GetDC( host.hWnd )) == NULL ) + return false; + + if( glw_state.desktopBitsPixel < 32 ) + { + // clear alphabits in case we in 16-bit mode + alphaBits = 0; + } + + // choose a pixel format + pixelFormat = VID_ChoosePFD( &PFD, 24, alphaBits, 32, stencilBits ); + + if( !pixelFormat ) + { + // try again with default color/depth/stencil + pixelFormat = VID_ChoosePFD( &PFD, 24, 0, 32, 0 ); + + if( !pixelFormat ) + { + Con_Printf( S_ERROR "GL_SetPixelformat: failed to find an appropriate PIXELFORMAT\n" ); + return false; + } + } + + // set the pixel format + if( !SetPixelFormat( glw_state.hDC, pixelFormat, &PFD )) + { + Con_Printf( S_ERROR "GL_SetPixelformat: failed\n" ); + return false; + } + + DescribePixelFormat( glw_state.hDC, pixelFormat, sizeof( PIXELFORMATDESCRIPTOR ), &PFD ); + + if( PFD.dwFlags & PFD_GENERIC_FORMAT ) + { + if( PFD.dwFlags & PFD_GENERIC_ACCELERATED ) + { + MsgDev( D_NOTE, "VID_ChoosePFD: using Generic MCD acceleration\n" ); + } + else + { + Con_Printf( S_ERROR "GL_SetPixelformat: no hardware acceleration found\n" ); + return false; + } + } + else + { + MsgDev( D_NOTE, "VID_ChoosePFD: using hardware acceleration\n"); + } + + glConfig.color_bits = PFD.cColorBits; + glConfig.alpha_bits = PFD.cAlphaBits; + glConfig.depth_bits = PFD.cDepthBits; + glConfig.stencil_bits = PFD.cStencilBits; + + if( PFD.cStencilBits != 0 ) + glState.stencilEnabled = true; + else glState.stencilEnabled = false; + + // print out PFD specifics + MsgDev( D_NOTE, "GL PFD: color( %d-bits ) alpha( %d-bits ) Z( %d-bit )\n", PFD.cColorBits, PFD.cAlphaBits, PFD.cDepthBits ); + + return true; +} + +/* +================= +R_SaveVideoMode +================= +*/ +void R_SaveVideoMode( int vid_mode ) +{ + int mode = bound( 0, vid_mode, num_vidmodes ); // check range + + glState.width = vidmode[mode].width; + glState.height = vidmode[mode].height; + glState.wideScreen = vidmode[mode].wideScreen; + Cvar_SetValue( "vid_mode", mode ); // merge if it out of bounds + + MsgDev( D_NOTE, "Set: %s [%dx%d]\n", vidmode[mode].desc, vidmode[mode].width, vidmode[mode].height ); +} + +/* +================= +R_DescribeVIDMode +================= +*/ +qboolean R_DescribeVIDMode( int width, int height ) +{ + int i; + + for( i = 0; i < sizeof( vidmode ) / sizeof( vidmode[0] ); i++ ) + { + if( vidmode[i].width == width && vidmode[i].height == height ) + { + // found specified mode + Cvar_SetValue( "vid_mode", i ); + return true; + } + } + + return false; +} + +/* +================= +VID_CreateWindow +================= +*/ +qboolean VID_CreateWindow( int width, int height, qboolean fullscreen ) +{ + int x = 0, y = 0, w, h; + int stylebits = WINDOW_STYLE; + int exstyle = WINDOW_EX_STYLE; + static string wndname; + HWND window; + RECT rect; + WNDCLASS wc; + + Q_strncpy( wndname, GI->title, sizeof( wndname )); + + // register the frame class + wc.style = CS_OWNDC|CS_NOCLOSE; + wc.lpfnWndProc = (WNDPROC)IN_WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = host.hInst; + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = (void *)COLOR_3DSHADOW; + wc.lpszClassName = WINDOW_NAME; + wc.lpszMenuName = 0; + wc.hIcon = 0; + + // find the icon file in the filesystem + if( FS_FileExists( GI->iconpath, true )) + { + if( FS_GetDiskPath( GI->iconpath, true )) + { + string localPath; + Q_snprintf( localPath, sizeof( localPath ), "%s/%s", GI->gamedir, GI->iconpath ); + wc.hIcon = LoadImage( NULL, localPath, IMAGE_ICON, 0, 0, LR_LOADFROMFILE|LR_DEFAULTSIZE ); + } + else Con_Printf( "Extract %s from pak if you want to see it.\n", GI->iconpath ); + } + + // couldn't loaded for some reasons? use default + if( !wc.hIcon ) wc.hIcon = LoadIcon( host.hInst, MAKEINTRESOURCE( 101 )); + + if( !RegisterClass( &wc )) + { + Con_Printf( S_ERROR "VID_CreateWindow: couldn't register window class %s\n" WINDOW_NAME ); + return false; + } + + if( fullscreen ) + { + stylebits = WS_POPUP|WS_VISIBLE; + exstyle = WS_EX_TOPMOST; + } + + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + + AdjustWindowRect( &rect, stylebits, FALSE ); + w = rect.right - rect.left; + h = rect.bottom - rect.top; + +#if 0 + RECT WindowRect; + unsigned WindowHeight; + HWND WindowHandle; + + WindowHandle = FindWindow("Shell_TrayWnd", NULL); + GetWindowRect(WindowHandle, &WindowRect); + WindowHeight = WindowRect.bottom - WindowRect.top; +#endif + + if( !fullscreen ) + { + x = window_xpos->value; + y = window_ypos->value; + + // adjust window coordinates if necessary + // so that the window is completely on screen + if( x < 0 ) x = 0; + if( y < 0 ) y = 0; + + if( Cvar_VariableInteger( "vid_mode" ) != glConfig.prev_mode ) + { + // adjust window in the screen size + if( x + w > glw_state.desktopWidth ) + x = ( glw_state.desktopWidth - w ); + + if( y + h > glw_state.desktopHeight ) + y = ( glw_state.desktopHeight - h ); + } + } + + window = CreateWindowEx( exstyle, WINDOW_NAME, wndname, stylebits, x, y, w, h, NULL, NULL, host.hInst, NULL ); + + if( host.hWnd != window ) + { + // make sure what CreateWindowEx call the IN_WndProc + Con_Printf( S_WARN "VID_CreateWindow: bad hWnd for '%s'\n", wndname ); + } + + if( !host.hWnd ) + { + // host.hWnd must be filled in IN_WndProc + Con_Printf( S_ERROR "VID_CreateWindow: couldn't create '%s'\n", wndname ); + return false; + } + + ShowWindow( host.hWnd, SW_SHOW ); + UpdateWindow( host.hWnd ); + + // init all the gl stuff for the window + if( !GL_SetPixelformat( )) + { + ShowWindow( host.hWnd, SW_HIDE ); + DestroyWindow( host.hWnd ); + host.hWnd = NULL; + + UnregisterClass( WINDOW_NAME, host.hInst ); + Con_Printf( S_ERROR "OpenGL driver not installed\n" ); + + return false; + } + + if( !glw_state.initialized ) + { + if( !GL_CreateContext( )) + return false; + + VID_StartupGamma(); + } + else + { + if( !GL_UpdateContext( )) + return false; + } + + SetForegroundWindow( host.hWnd ); + SetFocus( host.hWnd ); + + return true; +} + +/* +================= +VID_DestroyWindow +================= +*/ +void VID_DestroyWindow( void ) +{ + if( pwglMakeCurrent ) + pwglMakeCurrent( NULL, NULL ); + + if( glw_state.hDC ) + { + ReleaseDC( host.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + } + + if( host.hWnd ) + { + DestroyWindow ( host.hWnd ); + host.hWnd = NULL; + } + + UnregisterClass( WINDOW_NAME, host.hInst ); + + if( glState.fullScreen ) + { + ChangeDisplaySettings( 0, 0 ); + glState.fullScreen = false; + } +} + +/* +================= +R_ChangeDisplaySettings +================= +*/ +rserr_t R_ChangeDisplaySettings( int vid_mode, qboolean fullscreen ) +{ + int width, height; + int cds_result; + HDC hDC; + + R_SaveVideoMode( vid_mode ); + + width = glState.width; + height = glState.height; + + // check our desktop attributes + hDC = GetDC( GetDesktopWindow( )); + glw_state.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + glw_state.desktopWidth = GetDeviceCaps( hDC, HORZRES ); + glw_state.desktopHeight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + // destroy the existing window + if( host.hWnd ) VID_DestroyWindow(); + + // do a CDS if needed + if( fullscreen ) + { + DEVMODE dm; + + memset( &dm, 0, sizeof( dm )); + dm.dmSize = sizeof( dm ); + dm.dmPelsWidth = width; + dm.dmPelsHeight = height; + dm.dmFields = DM_PELSWIDTH|DM_PELSHEIGHT; + + if( vid_displayfrequency->value > 0 ) + { + if( vid_displayfrequency->value < 60 ) Cvar_SetValue( "vid_displayfrequency", 60 ); + if( vid_displayfrequency->value > 100 ) Cvar_SetValue( "vid_displayfrequency", 100 ); + + dm.dmFields |= DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = vid_displayfrequency->value; + } + + cds_result = ChangeDisplaySettings( &dm, CDS_FULLSCREEN ); + + if( cds_result == DISP_CHANGE_SUCCESSFUL ) + { + glState.fullScreen = true; + + if( !VID_CreateWindow( width, height, true )) + return rserr_invalid_mode; + return rserr_ok; + } + else if( cds_result == DISP_CHANGE_BADDUALVIEW ) + { + dm.dmPelsWidth = width * 2; + dm.dmPelsHeight = height; + dm.dmFields = DM_PELSWIDTH|DM_PELSHEIGHT; + + // our first CDS failed, so maybe we're running on some weird dual monitor system + if( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL ) + { + ChangeDisplaySettings( 0, 0 ); + glState.fullScreen = false; + if( !VID_CreateWindow( width, height, false )) + return rserr_invalid_mode; + return rserr_invalid_fullscreen; + } + else + { + if( !VID_CreateWindow( width, height, true )) + return rserr_invalid_mode; + glState.fullScreen = true; + return rserr_ok; + } + } + else + { + int freq_specified = 0; + + if( vid_displayfrequency->value > 0 ) + { + // clear out custom frequency + freq_specified = vid_displayfrequency->value; + Cvar_SetValue( "vid_displayfrequency", 0.0f ); + dm.dmFields &= ~DM_DISPLAYFREQUENCY; + dm.dmDisplayFrequency = 0; + } + + // our first CDS failed, so maybe we're running with too high displayfrequency + if( ChangeDisplaySettings( &dm, CDS_FULLSCREEN ) != DISP_CHANGE_SUCCESSFUL ) + { + ChangeDisplaySettings( 0, 0 ); + glState.fullScreen = false; + if( !VID_CreateWindow( width, height, false )) + return rserr_invalid_mode; + return rserr_invalid_fullscreen; + } + else + { + if( !VID_CreateWindow( width, height, true )) + return rserr_invalid_mode; + + if( freq_specified ) + Con_Printf( S_ERROR "VID_SetMode: display frequency %i Hz is not supported\n", freq_specified ); + glState.fullScreen = true; + return rserr_ok; + } + } + } + else + { + ChangeDisplaySettings( 0, 0 ); + glState.fullScreen = false; + if( !VID_CreateWindow( width, height, false )) + return rserr_invalid_mode; + } + + return rserr_ok; +} + +/* +================== +VID_SetMode + +Set the described video mode +================== +*/ +qboolean VID_SetMode( void ) +{ + qboolean fullscreen; + rserr_t err; + + if( vid_mode->value == -1 ) // trying to get resolution automatically by default + { + HDC hDCScreen = GetDC( NULL ); + int iScreenWidth = GetDeviceCaps( hDCScreen, HORZRES ); + int iScreenHeight = GetDeviceCaps( hDCScreen, VERTRES ); + + ReleaseDC( NULL, hDCScreen ); + + if( R_DescribeVIDMode( iScreenWidth, iScreenHeight )) + { + MsgDev( D_NOTE, "found specified vid mode %i [%ix%i]\n", (int)vid_mode->value, iScreenWidth, iScreenHeight ); + Cvar_SetValue( "fullscreen", 1 ); + } + else + { + MsgDev( D_NOTE, "failed to set specified vid mode [%ix%i]\n", iScreenWidth, iScreenHeight ); + Cvar_SetValue( "vid_mode", VID_DEFAULTMODE ); + } + } + + fullscreen = vid_fullscreen->value; + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + + if(( err = R_ChangeDisplaySettings( vid_mode->value, fullscreen )) == rserr_ok ) + { + glConfig.prev_mode = vid_mode->value; + } + else + { + if( err == rserr_invalid_fullscreen ) + { + Cvar_SetValue( "fullscreen", 0 ); + Con_Printf( S_ERROR "VID_SetMode: fullscreen unavailable in this mode\n" ); + if(( err = R_ChangeDisplaySettings( vid_mode->value, false )) == rserr_ok ) + return true; + } + else if( err == rserr_invalid_mode ) + { + Con_Printf( S_ERROR "VID_SetMode: invalid mode\n" ); + Cvar_SetValue( "vid_mode", glConfig.prev_mode ); + } + + // try setting it back to something safe + if(( err = R_ChangeDisplaySettings( glConfig.prev_mode, false )) != rserr_ok ) + { + Con_Printf( S_ERROR "VID_SetMode: could not revert to safe mode\n" ); + return false; + } + } + + return true; +} + +/* +================== +VID_CheckChanges + +check vid modes and fullscreen +================== +*/ +void VID_CheckChanges( void ) +{ + if( FBitSet( cl_allow_levelshots->flags, FCVAR_CHANGED )) + { + GL_FreeTexture( cls.loadingBar ); + SCR_RegisterTextures(); // reload 'lambda' image + ClearBits( cl_allow_levelshots->flags, FCVAR_CHANGED ); + } + + if( host.renderinfo_changed ) + { + if( !VID_SetMode( )) + { + Sys_Error( "Can't re-initialize video subsystem\n" ); + } + else + { + host.renderinfo_changed = false; + SCR_VidInit(); // tell the client.dll what vid_mode has changed + } + } +} + +/* +================== +R_Init_OpenGL +================== +*/ +qboolean R_Init_OpenGL( void ) +{ + Sys_LoadLibrary( &opengl_dll ); // load opengl32.dll + + if( !opengl_dll.link ) + return false; + + if( Sys_CheckParm( "-gldebug" ) && host_developer.value ) + GL_CheckExtension( "OpenGL Internal ProcAddress", wglproc_funcs, NULL, GL_WGL_PROCADDRESS ); + + return VID_SetMode(); +} + +/* +================== +R_Free_OpenGL +================== +*/ +void R_Free_OpenGL( void ) +{ + GL_DeleteContext (); + + VID_DestroyWindow (); + + Sys_FreeLibrary( &opengl_dll ); + + // now all extensions are disabled + memset( glConfig.extension, 0, sizeof( glConfig.extension )); + glw_state.initialized = false; +} + +/* +=============== +GL_SetDefaults +=============== +*/ +static void GL_SetDefaults( void ) +{ + pglFinish(); + + pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); + + pglDisable( GL_DEPTH_TEST ); + pglDisable( GL_CULL_FACE ); + pglDisable( GL_SCISSOR_TEST ); + pglDepthFunc( GL_LEQUAL ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + + if( glState.stencilEnabled ) + { + pglDisable( GL_STENCIL_TEST ); + pglStencilMask( ( GLuint ) ~0 ); + pglStencilFunc( GL_EQUAL, 0, ~0 ); + pglStencilOp( GL_KEEP, GL_INCR, GL_INCR ); + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglPolygonOffset( -1.0f, -2.0f ); + + GL_CleanupAllTextureUnits(); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglDisable( GL_POLYGON_OFFSET_FILL ); + pglAlphaFunc( GL_GREATER, 0.0f ); + pglEnable( GL_TEXTURE_2D ); + pglShadeModel( GL_SMOOTH ); + pglFrontFace( GL_CCW ); + + pglPointSize( 1.2f ); + pglLineWidth( 1.2f ); + + GL_Cull( GL_NONE ); +} + +/* +================= +R_RenderInfo_f +================= +*/ +void R_RenderInfo_f( void ) +{ + Con_Printf( "\n" ); + Con_Printf( "GL_VENDOR: %s\n", glConfig.vendor_string ); + Con_Printf( "GL_RENDERER: %s\n", glConfig.renderer_string ); + Con_Printf( "GL_VERSION: %s\n", glConfig.version_string ); + + // don't spam about extensions + if( host_developer.value >= DEV_EXTENDED ) + { + Con_Printf( "GL_EXTENSIONS: %s\n", glConfig.extensions_string ); + + if( glConfig.wgl_extensions_string != NULL ) + Con_Printf( "\nWGL_EXTENSIONS: %s\n", glConfig.wgl_extensions_string ); + } + + Con_Printf( "GL_MAX_TEXTURE_SIZE: %i\n", glConfig.max_2d_texture_size ); + + if( GL_Support( GL_ARB_MULTITEXTURE )) + Con_Printf( "GL_MAX_TEXTURE_UNITS_ARB: %i\n", glConfig.max_texture_units ); + if( GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + Con_Printf( "GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB: %i\n", glConfig.max_cubemap_size ); + if( GL_Support( GL_ANISOTROPY_EXT )) + Con_Printf( "GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT: %.1f\n", glConfig.max_texture_anisotropy ); + if( GL_Support( GL_TEXTURE_2D_RECT_EXT )) + Con_Printf( "GL_MAX_RECTANGLE_TEXTURE_SIZE: %i\n", glConfig.max_2d_rectangle_size ); + if( GL_Support( GL_TEXTURE_ARRAY_EXT )) + Con_Printf( "GL_MAX_ARRAY_TEXTURE_LAYERS_EXT: %i\n", glConfig.max_2d_texture_layers ); + if( GL_Support( GL_SHADER_GLSL100_EXT )) + { + Con_Printf( "GL_MAX_TEXTURE_COORDS_ARB: %i\n", glConfig.max_texture_coords ); + Con_Printf( "GL_MAX_TEXTURE_IMAGE_UNITS_ARB: %i\n", glConfig.max_teximage_units ); + Con_Printf( "GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB: %i\n", glConfig.max_vertex_uniforms ); + Con_Printf( "GL_MAX_VERTEX_ATTRIBS_ARB: %i\n", glConfig.max_vertex_attribs ); + } + + Con_Printf( "\n" ); + Con_Printf( "MODE: %s\n", vidmode[(int)vid_mode->value].desc ); + Con_Printf( "\n" ); + Con_Printf( "VERTICAL SYNC: %s\n", gl_vsync->value ? "enabled" : "disabled" ); + Con_Printf( "Color %d bits, Alpha %d bits, Depth %d bits, Stencil %d bits\n", glConfig.color_bits, + glConfig.alpha_bits, glConfig.depth_bits, glConfig.stencil_bits ); +} + +//======================================================================= + +/* +================= +GL_InitCommands +================= +*/ +void GL_InitCommands( void ) +{ + // system screen width and height (don't suppose for change from console at all) + r_speeds = Cvar_Get( "r_speeds", "0", FCVAR_ARCHIVE, "shows renderer speeds" ); + r_fullbright = Cvar_Get( "r_fullbright", "0", FCVAR_CHEAT, "disable lightmaps, get fullbright for entities" ); + r_norefresh = Cvar_Get( "r_norefresh", "0", 0, "disable 3D rendering (use with caution)" ); + r_lighting_extended = Cvar_Get( "r_lighting_extended", "1", FCVAR_ARCHIVE, "allow to get lighting from world and bmodels" ); + r_lighting_modulate = Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); + r_lighting_ambient = Cvar_Get( "r_lighting_ambient", "0.3", FCVAR_ARCHIVE, "map ambient lighting scale" ); + r_adjust_fov = Cvar_Get( "r_adjust_fov", "1", FCVAR_ARCHIVE, "making FOV adjustment for wide-screens" ); + r_novis = Cvar_Get( "r_novis", "0", 0, "ignore vis information (perfomance test)" ); + r_nocull = Cvar_Get( "r_nocull", "0", 0, "ignore frustrum culling (perfomance test)" ); + r_detailtextures = Cvar_Get( "r_detailtextures", "1", FCVAR_ARCHIVE, "enable detail textures support, use '2' for autogenerate detail.txt" ); + r_lockpvs = Cvar_Get( "r_lockpvs", "0", FCVAR_CHEAT, "lockpvs area at current point (pvs test)" ); + r_lockfrustum = Cvar_Get( "r_lockfrustum", "0", FCVAR_CHEAT, "lock frustrum area at current point (cull test)" ); + r_dynamic = Cvar_Get( "r_dynamic", "1", FCVAR_ARCHIVE, "allow dynamic lighting (dlights, lightstyles)" ); + r_traceglow = Cvar_Get( "r_traceglow", "1", FCVAR_ARCHIVE, "cull flares behind models" ); + r_lightmap = Cvar_Get( "r_lightmap", "0", FCVAR_CHEAT, "lightmap debugging tool" ); + r_drawentities = Cvar_Get( "r_drawentities", "1", FCVAR_CHEAT, "render entities" ); + r_decals = Cvar_Get( "r_decals", "4096", FCVAR_ARCHIVE, "sets the maximum number of decals" ); + window_xpos = Cvar_Get( "_window_xpos", "130", FCVAR_RENDERINFO, "window position by horizontal" ); + window_ypos = Cvar_Get( "_window_ypos", "48", FCVAR_RENDERINFO, "window position by vertical" ); + + gl_extensions = Cvar_Get( "gl_allow_extensions", "1", FCVAR_GLCONFIG, "allow gl_extensions" ); + gl_texture_nearest = Cvar_Get( "gl_texture_nearest", "0", FCVAR_ARCHIVE, "disable texture filter" ); + gl_lightmap_nearest = Cvar_Get( "gl_lightmap_nearest", "0", FCVAR_ARCHIVE, "disable lightmap filter" ); + gl_check_errors = Cvar_Get( "gl_check_errors", "1", FCVAR_ARCHIVE, "ignore video engine errors" ); + gl_vsync = Cvar_Get( "gl_vsync", "0", FCVAR_ARCHIVE, "enable vertical syncronization" ); + gl_detailscale = Cvar_Get( "gl_detailscale", "4.0", FCVAR_ARCHIVE, "default scale applies while auto-generate list of detail textures" ); + gl_texture_anisotropy = Cvar_Get( "gl_anisotropy", "8", FCVAR_ARCHIVE, "textures anisotropic filter" ); + gl_texture_lodbias = Cvar_Get( "gl_texture_lodbias", "0.0", FCVAR_ARCHIVE, "LOD bias for mipmapped textures (perfomance|quality)" ); + gl_keeptjunctions = Cvar_Get( "gl_keeptjunctions", "1", FCVAR_ARCHIVE, "removing tjuncs causes blinking pixels" ); + gl_showtextures = Cvar_Get( "r_showtextures", "0", FCVAR_CHEAT, "show all uploaded textures" ); + gl_finish = Cvar_Get( "gl_finish", "0", FCVAR_ARCHIVE, "use glFinish instead of glFlush" ); + gl_nosort = Cvar_Get( "gl_nosort", "0", FCVAR_ARCHIVE, "disable sorting of translucent surfaces" ); + gl_clear = Cvar_Get( "gl_clear", "0", FCVAR_ARCHIVE, "clearing screen after each frame" ); + gl_test = Cvar_Get( "gl_test", "0", 0, "engine developer cvar for quick testing new features" ); + gl_wireframe = Cvar_Get( "gl_wireframe", "0", FCVAR_ARCHIVE|FCVAR_SPONLY, "show wireframe overlay" ); + + // these cvar not used by engine but some mods requires this + gl_polyoffset = Cvar_Get( "gl_polyoffset", "2.0", FCVAR_ARCHIVE, "polygon offset for decals" ); + + // make sure gl_vsync is checked after vid_restart + SetBits( gl_vsync->flags, FCVAR_CHANGED ); + + vid_gamma = Cvar_Get( "gamma", "2.5", FCVAR_ARCHIVE, "gamma amount" ); + vid_brightness = Cvar_Get( "brightness", "0.0", FCVAR_ARCHIVE, "brighntess factor" ); + vid_mode = Cvar_Get( "vid_mode", VID_AUTOMODE, FCVAR_RENDERINFO|FCVAR_VIDRESTART, "display resolution mode" ); + vid_fullscreen = Cvar_Get( "fullscreen", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "enable fullscreen mode" ); + vid_displayfrequency = Cvar_Get ( "vid_displayfrequency", "0", FCVAR_RENDERINFO|FCVAR_VIDRESTART, "fullscreen refresh rate" ); + + Cmd_AddCommand( "r_info", R_RenderInfo_f, "display renderer info" ); + + // apply actual video mode to window + Cbuf_AddText( "exec video.cfg\n" ); + Cbuf_Execute(); +} + +/* +================= +GL_RemoveCommands +================= +*/ +void GL_RemoveCommands( void ) +{ + Cmd_RemoveCommand( "r_info"); +} + +/* +================= +GL_InitExtensions +================= +*/ +void GL_InitExtensions( void ) +{ + // initialize gl extensions + GL_CheckExtension( "OpenGL 1.1.0", opengl_110funcs, NULL, GL_OPENGL_110 ); + + // get our various GL strings + glConfig.vendor_string = pglGetString( GL_VENDOR ); + glConfig.renderer_string = pglGetString( GL_RENDERER ); + glConfig.version_string = pglGetString( GL_VERSION ); + glConfig.extensions_string = pglGetString( GL_EXTENSIONS ); + Con_Printf( "Video: %s\n", glConfig.renderer_string ); + + // intialize wrapper type + glConfig.context = CONTEXT_TYPE_GL; + glConfig.wrapper = GLES_WRAPPER_NONE; + + if( Q_stristr( glConfig.renderer_string, "geforce" )) + glConfig.hardware_type = GLHW_NVIDIA; + else if( Q_stristr( glConfig.renderer_string, "quadro fx" )) + glConfig.hardware_type = GLHW_NVIDIA; + else if( Q_stristr(glConfig.renderer_string, "rv770" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr(glConfig.renderer_string, "radeon hd" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr( glConfig.renderer_string, "eah4850" ) || Q_stristr( glConfig.renderer_string, "eah4870" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr( glConfig.renderer_string, "radeon" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr( glConfig.renderer_string, "intel" )) + glConfig.hardware_type = GLHW_INTEL; + else glConfig.hardware_type = GLHW_GENERIC; + + // initalize until base opengl functions loaded (old-context) + if( !Sys_CheckParm( "-gldebug" ) || !host_developer.value ) + GL_CheckExtension( "OpenGL Internal ProcAddress", wglproc_funcs, NULL, GL_WGL_PROCADDRESS ); + + // windows-specific extensions + GL_CheckExtension( "WGL Extensions String", wglgetextensionsstring, NULL, GL_WGL_EXTENSIONS ); + + if( pwglGetExtensionsStringEXT != NULL ) + glConfig.wgl_extensions_string = pwglGetExtensionsStringEXT(); + else glConfig.wgl_extensions_string = NULL; + + // initalize until base opengl functions loaded + GL_CheckExtension( "WGL_EXT_swap_control", wglswapintervalfuncs, NULL, GL_WGL_SWAPCONTROL ); + + // multitexture + glConfig.max_texture_units = glConfig.max_texture_coords = glConfig.max_teximage_units = 1; + GL_CheckExtension( "GL_ARB_multitexture", multitexturefuncs, "gl_arb_multitexture", GL_ARB_MULTITEXTURE ); + + if( GL_Support( GL_ARB_MULTITEXTURE )) + pglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units ); + + if( glConfig.max_texture_units == 1 ) + GL_SetExtension( GL_ARB_MULTITEXTURE, false ); + + // 3d texture support + GL_CheckExtension( "GL_EXT_texture3D", texture3dextfuncs, "gl_texture_3d", GL_TEXTURE_3D_EXT ); + + if( GL_Support( GL_TEXTURE_3D_EXT )) + { + pglGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size ); + + if( glConfig.max_3d_texture_size < 32 ) + { + GL_SetExtension( GL_TEXTURE_3D_EXT, false ); + Con_Printf( S_ERROR "GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n" ); + } + } + + // 2d texture array support + GL_CheckExtension( "GL_EXT_texture_array", texture3dextfuncs, "gl_texture_2d_array", GL_TEXTURE_ARRAY_EXT ); + + if( GL_Support( GL_TEXTURE_ARRAY_EXT )) + pglGetIntegerv( GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &glConfig.max_2d_texture_layers ); + + // cubemaps support + GL_CheckExtension( "GL_ARB_texture_cube_map", NULL, "gl_texture_cubemap", GL_TEXTURE_CUBEMAP_EXT ); + + if( GL_Support( GL_TEXTURE_CUBEMAP_EXT )) + { + pglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size ); + + // check for seamless cubemaps too + GL_CheckExtension( "GL_ARB_seamless_cube_map", NULL, "gl_texture_cubemap_seamless", GL_ARB_SEAMLESS_CUBEMAP ); + } + + GL_CheckExtension( "GL_ARB_texture_non_power_of_two", NULL, "gl_texture_npot", GL_ARB_TEXTURE_NPOT_EXT ); + GL_CheckExtension( "GL_ARB_texture_compression", texturecompressionfuncs, "gl_texture_dxt_compression", GL_TEXTURE_COMPRESSION_EXT ); + GL_CheckExtension( "GL_EXT_texture_edge_clamp", NULL, NULL, GL_CLAMPTOEDGE_EXT ); + + if( !GL_Support( GL_CLAMPTOEDGE_EXT )) + GL_CheckExtension( "GL_SGIS_texture_edge_clamp", NULL, NULL, GL_CLAMPTOEDGE_EXT ); + + glConfig.max_texture_anisotropy = 0.0f; + GL_CheckExtension( "GL_EXT_texture_filter_anisotropic", NULL, "gl_texture_anisotropic_filter", GL_ANISOTROPY_EXT ); + + if( GL_Support( GL_ANISOTROPY_EXT )) + pglGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glConfig.max_texture_anisotropy ); + + // g-cont. because lodbias it too glitchy on Intel's cards + if( glConfig.hardware_type != GLHW_INTEL ) + GL_CheckExtension( "GL_EXT_texture_lod_bias", NULL, "gl_texture_mipmap_biasing", GL_TEXTURE_LOD_BIAS ); + + if( GL_Support( GL_TEXTURE_LOD_BIAS )) + pglGetFloatv( GL_MAX_TEXTURE_LOD_BIAS_EXT, &glConfig.max_texture_lod_bias ); + + GL_CheckExtension( "GL_ARB_texture_border_clamp", NULL, NULL, GL_CLAMP_TEXBORDER_EXT ); + + GL_CheckExtension( "GL_ARB_depth_texture", NULL, NULL, GL_DEPTH_TEXTURE ); + GL_CheckExtension( "GL_ARB_texture_float", NULL, "gl_texture_float", GL_ARB_TEXTURE_FLOAT_EXT ); + GL_CheckExtension( "GL_ARB_depth_buffer_float", NULL, "gl_texture_float", GL_ARB_DEPTH_FLOAT_EXT ); + GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, NULL, GL_EXT_GPU_SHADER4 ); // don't confuse users + GL_CheckExtension( "GL_ARB_shading_language_100", NULL, NULL, GL_SHADER_GLSL100_EXT ); +// GL_CheckExtension( "GL_ARB_texture_rg", NULL, "gl_arb_texture_rg", GL_ARB_TEXTURE_RG ); + + // this won't work without extended context + if( glw_state.extended ) + GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, "gl_debug_output", GL_DEBUG_OUTPUT ); + + // rectangle textures support + GL_CheckExtension( "GL_ARB_texture_rectangle", NULL, "gl_texture_rectangle", GL_TEXTURE_2D_RECT_EXT ); + + if( GL_Support( GL_SHADER_GLSL100_EXT )) + { + pglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords ); + pglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_teximage_units ); + + // check for hardware skinning + pglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.max_vertex_uniforms ); + pglGetIntegerv( GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.max_vertex_attribs ); + + if( glConfig.hardware_type == GLHW_RADEON ) + glConfig.max_vertex_uniforms /= 4; // radeon returns not correct info + } + else + { + // just get from multitexturing + glConfig.max_texture_coords = glConfig.max_teximage_units = glConfig.max_texture_units; + } + + pglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.max_2d_texture_size ); + if( glConfig.max_2d_texture_size <= 0 ) glConfig.max_2d_texture_size = 256; + + if( GL_Support( GL_TEXTURE_2D_RECT_EXT )) + pglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &glConfig.max_2d_rectangle_size ); + + Cvar_Get( "gl_max_size", va( "%i", glConfig.max_2d_texture_size ), 0, "opengl texture max dims" ); + + // MCD has buffering issues + if( Q_stristr( glConfig.renderer_string, "gdi" )) + Cvar_SetValue( "gl_finish", 1 ); + + Cvar_Set( "gl_anisotropy", va( "%f", bound( 0, gl_texture_anisotropy->value, glConfig.max_texture_anisotropy ))); + + if( GL_Support( GL_TEXTURE_COMPRESSION_EXT )) + Image_AddCmdFlags( IL_DDS_HARDWARE ); + + // enable gldebug if allowed + if( GL_Support( GL_DEBUG_OUTPUT )) + { + pglDebugMessageCallbackARB( GL_DebugOutput, NULL ); + + // force everything to happen in the main thread instead of in a separate driver thread + pglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + + // enable all the low priority messages + if( host_developer.value >= DEV_EXTENDED ) + pglDebugMessageControlARB( GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, true ); + } + + tr.framecount = tr.visframecount = 1; + glw_state.initialized = true; +} + +/* +=============== +R_Init +=============== +*/ +qboolean R_Init( void ) +{ + if( glw_state.initialized ) + return true; + + // give initial OpenGL configuration + Cbuf_AddText( "exec opengl.cfg\n" ); + + GL_InitCommands(); + GL_InitRandomTable(); + GL_SetDefaultState(); + + // create the window and set up the context + if( !R_Init_OpenGL( )) + { + GL_RemoveCommands(); + R_Free_OpenGL(); + + Sys_Error( "Can't initialize video subsystem\nProbably driver was not installed" ); + return false; + } + + host.renderinfo_changed = false; + r_temppool = Mem_AllocPool( "Render Zone" ); + + GL_InitExtensions(); + GL_SetDefaults(); + R_InitImages(); + R_SpriteInit(); + R_StudioInit(); + R_AliasInit(); + R_ClearDecals(); + R_ClearScene(); + + // initialize screen + SCR_Init(); + + return true; +} + +/* +=============== +R_Shutdown +=============== +*/ +void R_Shutdown( void ) +{ + model_t *mod; + int i; + + if( !glw_state.initialized ) + return; + + // release SpriteTextures + for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ ) + { + if( !mod->name[0] ) continue; + Mod_UnloadSpriteModel( mod ); + } + memset( clgame.sprites, 0, sizeof( clgame.sprites )); + + GL_RemoveCommands(); + R_ShutdownImages(); + + Mem_FreePool( &r_temppool ); + + // shut down OS specific OpenGL stuff like contexts, etc. + R_Free_OpenGL(); +} + +/* +================= +GL_CheckForErrors + +obsolete +================= +*/ +void GL_CheckForErrors_( const char *filename, const int fileline ) +{ + int err; + char *str; + + if( !gl_check_errors->value ) + return; + + if(( err = pglGetError( )) == GL_NO_ERROR ) + return; + + switch( err ) + { + case GL_STACK_OVERFLOW: + str = "GL_STACK_OVERFLOW"; + break; + case GL_STACK_UNDERFLOW: + str = "GL_STACK_UNDERFLOW"; + break; + case GL_INVALID_ENUM: + str = "GL_INVALID_ENUM"; + break; + case GL_INVALID_VALUE: + str = "GL_INVALID_VALUE"; + break; + case GL_INVALID_OPERATION: + str = "GL_INVALID_OPERATION"; + break; + case GL_OUT_OF_MEMORY: + str = "GL_OUT_OF_MEMORY"; + break; + default: + str = "UNKNOWN ERROR"; + break; + } + + Con_Printf( S_OPENGL_ERROR "%s (called at %s:%i)\n", str, filename, fileline ); +} \ No newline at end of file diff --git a/engine/client/gl_warp.c b/engine/client/gl_warp.c new file mode 100644 index 00000000..413619cf --- /dev/null +++ b/engine/client/gl_warp.c @@ -0,0 +1,815 @@ +/* +gl_warp.c - sky and water polygons +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "wadfile.h" + +#define SKYCLOUDS_QUALITY 12 +#define MAX_CLIP_VERTS 128 // skybox clip vertices +#define TURBSCALE ( 256.0f / ( M_PI2 )) +static const char* r_skyBoxSuffix[6] = { "rt", "bk", "lf", "ft", "up", "dn" }; +static const int r_skyTexOrder[6] = { 0, 2, 1, 3, 4, 5 }; + +static const vec3_t skyclip[6] = +{ +{ 1, 1, 0 }, +{ 1, -1, 0 }, +{ 0, -1, 1 }, +{ 0, 1, 1 }, +{ 1, 0, 1 }, +{ -1, 0, 1 } +}; + +// 1 = s, 2 = t, 3 = 2048 +static const int st_to_vec[6][3] = +{ +{ 3, -1, 2 }, +{ -3, 1, 2 }, +{ 1, 3, 2 }, +{ -1, -3, 2 }, +{ -2, -1, 3 }, // 0 degrees yaw, look straight up +{ 2, -1, -3 } // look straight down +}; + +// s = [0]/[2], t = [1]/[2] +static const int vec_to_st[6][3] = +{ +{ -2, 3, 1 }, +{ 2, 3, -1 }, +{ 1, 3, 2 }, +{ -1, 3, -2 }, +{ -2, -1, 3 }, +{ -2, 1, -3 } +}; + +// speed up sin calculations +float r_turbsin[] = +{ + #include "warpsin.h" +}; + +#define SKYBOX_MISSED 0 +#define SKYBOX_HLSTYLE 1 +#define SKYBOX_Q1STYLE 2 + +static int CheckSkybox( const char *name ) +{ + const char *skybox_ext[3] = { "dds", "tga", "bmp" }; + int i, j, num_checked_sides; + const char *sidename; + + // search for skybox images + for( i = 0; i < 3; i++ ) + { + num_checked_sides = 0; + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + if( FS_FileExists( sidename, false )) + num_checked_sides++; + + } + + if( num_checked_sides == 6 ) + return SKYBOX_HLSTYLE; // image exists + + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s_%s.%s", name, r_skyBoxSuffix[j], skybox_ext[i] ); + if( FS_FileExists( sidename, false )) + num_checked_sides++; + } + + if( num_checked_sides == 6 ) + return SKYBOX_Q1STYLE; // images exists + } + + return SKYBOX_MISSED; +} + +void DrawSkyPolygon( int nump, vec3_t vecs ) +{ + int i, j, axis; + float s, t, dv, *vp; + vec3_t v, av; + + // decide which face it maps to + VectorClear( v ); + + for( i = 0, vp = vecs; i < nump; i++, vp += 3 ) + VectorAdd( vp, v, v ); + + av[0] = fabs( v[0] ); + av[1] = fabs( v[1] ); + av[2] = fabs( v[2] ); + + if( av[0] > av[1] && av[0] > av[2] ) + axis = (v[0] < 0) ? 1 : 0; + else if( av[1] > av[2] && av[1] > av[0] ) + axis = (v[1] < 0) ? 3 : 2; + else axis = (v[2] < 0) ? 5 : 4; + + // project new texture coords + for( i = 0; i < nump; i++, vecs += 3 ) + { + j = vec_to_st[axis][2]; + dv = (j > 0) ? vecs[j-1] : -vecs[-j-1]; + + if( dv == 0.0f ) continue; + + j = vec_to_st[axis][0]; + s = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv; + + j = vec_to_st[axis][1]; + t = (j < 0) ? -vecs[-j-1] / dv : vecs[j-1] / dv; + + if( s < RI.skyMins[0][axis] ) RI.skyMins[0][axis] = s; + if( t < RI.skyMins[1][axis] ) RI.skyMins[1][axis] = t; + if( s > RI.skyMaxs[0][axis] ) RI.skyMaxs[0][axis] = s; + if( t > RI.skyMaxs[1][axis] ) RI.skyMaxs[1][axis] = t; + } +} + +/* +============== +ClipSkyPolygon +============== +*/ +void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) +{ + const float *norm; + float *v, d, e; + qboolean front, back; + float dists[MAX_CLIP_VERTS + 1]; + int sides[MAX_CLIP_VERTS + 1]; + vec3_t newv[2][MAX_CLIP_VERTS + 1]; + int newc[2]; + int i, j; + + if( nump > MAX_CLIP_VERTS ) + Host_Error( "ClipSkyPolygon: MAX_CLIP_VERTS\n" ); +loc1: + if( stage == 6 ) + { + // fully clipped, so draw it + DrawSkyPolygon( nump, vecs ); + return; + } + + front = back = false; + norm = skyclip[stage]; + for( i = 0, v = vecs; i < nump; i++, v += 3 ) + { + d = DotProduct( v, norm ); + if( d > ON_EPSILON ) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if( d < -ON_EPSILON ) + { + back = true; + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + dists[i] = d; + } + + if( !front || !back ) + { + // not clipped + stage++; + goto loc1; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + VectorCopy( vecs, ( vecs + ( i * 3 ))); + newc[0] = newc[1] = 0; + + for( i = 0, v = vecs; i < nump; i++, v += 3 ) + { + switch( sides[i] ) + { + case SIDE_FRONT: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + break; + case SIDE_BACK: + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + case SIDE_ON: + VectorCopy( v, newv[0][newc[0]] ); + newc[0]++; + VectorCopy( v, newv[1][newc[1]] ); + newc[1]++; + break; + } + + if( sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) + continue; + + d = dists[i] / ( dists[i] - dists[i+1] ); + for( j = 0; j < 3; j++ ) + { + e = v[j] + d * ( v[j+3] - v[j] ); + newv[0][newc[0]][j] = e; + newv[1][newc[1]][j] = e; + } + newc[0]++; + newc[1]++; + } + + // continue + ClipSkyPolygon( newc[0], newv[0][0], stage + 1 ); + ClipSkyPolygon( newc[1], newv[1][0], stage + 1 ); +} + +void MakeSkyVec( float s, float t, int axis ) +{ + int j, k, farclip; + vec3_t v, b; + + farclip = RI.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + v[j] = (k < 0) ? -b[-k-1] : b[k-1]; + v[j] += RI.cullorigin[j]; + } + + // avoid bilerp seam + s = (s + 1.0f) * 0.5f; + t = (t + 1.0f) * 0.5f; + + if( s < 1.0f / 512.0f ) + s = 1.0f / 512.0f; + else if( s > 511.0f / 512.0f ) + s = 511.0f / 512.0f; + if( t < 1.0f / 512.0f ) + t = 1.0f / 512.0f; + else if( t > 511.0f / 512.0f ) + t = 511.0f / 512.0f; + + t = 1.0f - t; + + pglTexCoord2f( s, t ); + pglVertex3fv( v ); +} + +/* +============== +R_ClearSkyBox +============== +*/ +void R_ClearSkyBox( void ) +{ + int i; + + for( i = 0; i < 6; i++ ) + { + RI.skyMins[0][i] = RI.skyMins[1][i] = 9999999.0f; + RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999.0f; + } +} + +/* +================= +R_AddSkyBoxSurface +================= +*/ +void R_AddSkyBoxSurface( msurface_t *fa ) +{ + vec3_t verts[MAX_CLIP_VERTS]; + glpoly_t *p; + float *v; + int i; + + if( FBitSet( world.flags, FWORLD_SKYSPHERE ) && fa->polys && !FBitSet( world.flags, FWORLD_CUSTOM_SKYBOX )) + { + glpoly_t *p = fa->polys; + + // draw the sky poly + pglBegin( GL_POLYGON ); + for( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE ) + { + pglTexCoord2f( v[3], v[4] ); + pglVertex3fv( v ); + } + pglEnd (); + } + + // calculate vertex values for sky box + for( p = fa->polys; p; p = p->next ) + { + for( i = 0; i < p->numverts; i++ ) + VectorSubtract( p->verts[i], RI.cullorigin, verts[i] ); + ClipSkyPolygon( p->numverts, verts[0], 0 ); + } +} + +/* +============== +R_UnloadSkybox + +Unload previous skybox +============== +*/ +void R_UnloadSkybox( void ) +{ + int i; + + // release old skybox + for( i = 0; i < 6; i++ ) + { + if( !tr.skyboxTextures[i] ) continue; + GL_FreeTexture( tr.skyboxTextures[i] ); + } + + tr.skyboxbasenum = 5800; // set skybox base (to let some mods load hi-res skyboxes) + + memset( tr.skyboxTextures, 0, sizeof( tr.skyboxTextures )); + ClearBits( world.flags, FWORLD_CUSTOM_SKYBOX ); +} + +/* +============== +R_DrawSkybox +============== +*/ +void R_DrawSkyBox( void ) +{ + int i; + + RI.isSkyVisible = true; + + // don't fogging skybox (this fix old Half-Life bug) + if( !RI.fogSkybox ) R_AllowFog( false ); + + pglDisable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + + for( i = 0; i < 6; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + + if( tr.skyboxTextures[r_skyTexOrder[i]] ) + GL_Bind( GL_TEXTURE0, tr.skyboxTextures[r_skyTexOrder[i]] ); + else GL_Bind( GL_TEXTURE0, tr.skyTexture ); // stub + + pglBegin( GL_QUADS ); + MakeSkyVec( RI.skyMins[0][i], RI.skyMins[1][i], i ); + MakeSkyVec( RI.skyMins[0][i], RI.skyMaxs[1][i], i ); + MakeSkyVec( RI.skyMaxs[0][i], RI.skyMaxs[1][i], i ); + MakeSkyVec( RI.skyMaxs[0][i], RI.skyMins[1][i], i ); + pglEnd(); + } + + if( !RI.fogSkybox ) + R_AllowFog( true ); + R_LoadIdentity(); +} + +/* +=============== +R_SetupSky +=============== +*/ +void R_SetupSky( const char *skyboxname ) +{ + char loadname[MAX_QPATH]; + char sidename[MAX_QPATH]; + int i, result; + + if( !COM_CheckString( skyboxname )) + { + R_UnloadSkybox(); + return; // clear old skybox + } + + Q_snprintf( loadname, sizeof( loadname ), "gfx/env/%s", skyboxname ); + COM_StripExtension( loadname ); + + // kill the underline suffix to find them manually later + if( loadname[Q_strlen( loadname ) - 1] == '_' ) + loadname[Q_strlen( loadname ) - 1] = '\0'; + result = CheckSkybox( loadname ); + + // to prevent infinite recursion if default skybox was missed + if( result == SKYBOX_MISSED && Q_stricmp( loadname, DEFAULT_SKYBOX_PATH )) + { + Con_Reportf( S_WARN "missed or incomplete skybox '%s'\n", skyboxname ); + R_SetupSky( "desert" ); // force to default + return; + } + + // release old skybox + R_UnloadSkybox(); + Con_DPrintf( "SKY: " ); + + for( i = 0; i < 6; i++ ) + { + if( result == SKYBOX_HLSTYLE ) + Q_snprintf( sidename, sizeof( sidename ), "%s%s", loadname, r_skyBoxSuffix[i] ); + else Q_snprintf( sidename, sizeof( sidename ), "%s_%s", loadname, r_skyBoxSuffix[i] ); + + tr.skyboxTextures[i] = GL_LoadTexture( sidename, NULL, 0, TF_CLAMP|TF_SKY, NULL ); + if( !tr.skyboxTextures[i] ) break; + Con_DPrintf( "%s%s%s", skyboxname, r_skyBoxSuffix[i], i != 5 ? ", " : ". " ); + } + + if( i == 6 ) + { + SetBits( world.flags, FWORLD_CUSTOM_SKYBOX ); + Con_DPrintf( "done\n" ); + return; // loaded + } + + Con_DPrintf( "^2failed\n" ); + R_UnloadSkybox(); +} + +//============================================================================== +// +// RENDER CLOUDS +// +//============================================================================== +/* +============== +R_CloudVertex +============== +*/ +void R_CloudVertex( float s, float t, int axis, vec3_t v ) +{ + int j, k, farclip; + vec3_t b; + + farclip = RI.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + v[j] = (k < 0) ? -b[-k-1] : b[k-1]; + v[j] += RI.cullorigin[j]; + } +} + +/* +============= +R_CloudTexCoord +============= +*/ +void R_CloudTexCoord( vec3_t v, float speed, float *s, float *t ) +{ + float length, speedscale; + vec3_t dir; + + speedscale = cl.time * speed; + speedscale -= (int)speedscale & ~127; + + VectorSubtract( v, RI.vieworg, dir ); + dir[2] *= 3.0f; // flatten the sphere + + length = VectorLength( dir ); + length = 6.0f * 63.0f / length; + + *s = ( speedscale + dir[0] * length ) * (1.0f / 128.0f); + *t = ( speedscale + dir[1] * length ) * (1.0f / 128.0f); +} + +/* +=============== +R_CloudDrawPoly +=============== +*/ +void R_CloudDrawPoly( glpoly_t *p ) +{ + float s, t; + float *v; + int i; + + GL_SetRenderMode( kRenderNormal ); + GL_Bind( GL_TEXTURE0, tr.solidskyTexture ); + + pglBegin( GL_QUADS ); + for( i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE ) + { + R_CloudTexCoord( v, 8.0f, &s, &t ); + pglTexCoord2f( s, t ); + pglVertex3fv( v ); + } + pglEnd(); + + GL_SetRenderMode( kRenderTransTexture ); + GL_Bind( GL_TEXTURE0, tr.alphaskyTexture ); + + pglBegin( GL_QUADS ); + for( i = 0, v = p->verts[0]; i < 4; i++, v += VERTEXSIZE ) + { + R_CloudTexCoord( v, 16.0f, &s, &t ); + pglTexCoord2f( s, t ); + pglVertex3fv( v ); + } + pglEnd(); + + pglDisable( GL_BLEND ); +} + +/* +============== +R_CloudRenderSide +============== +*/ +void R_CloudRenderSide( int axis ) +{ + vec3_t verts[4]; + float di, qi, dj, qj; + vec3_t vup, vright; + vec3_t temp, temp2; + glpoly_t p[1]; + int i, j; + + R_CloudVertex( -1.0f, -1.0f, axis, verts[0] ); + R_CloudVertex( -1.0f, 1.0f, axis, verts[1] ); + R_CloudVertex( 1.0f, 1.0f, axis, verts[2] ); + R_CloudVertex( 1.0f, -1.0f, axis, verts[3] ); + + VectorSubtract( verts[2], verts[3], vup ); + VectorSubtract( verts[2], verts[1], vright ); + + p->numverts = 4; + di = SKYCLOUDS_QUALITY; + qi = 1.0 / di; + dj = (axis < 4) ? di * 2 : di; //subdivide vertically more than horizontally on skybox sides + qj = 1.0 / dj; + + for( i = 0; i < di; i++ ) + { + for( j = 0; j < dj; j++ ) + { + if( i * qi < RI.skyMins[0][axis] / 2 + 0.5f - qi + || i * qi > RI.skyMaxs[0][axis] / 2 + 0.5f + || j * qj < RI.skyMins[1][axis] / 2 + 0.5f - qj + || j * qj > RI.skyMaxs[1][axis] / 2 + 0.5f ) + continue; + + VectorScale( vright, qi * i, temp ); + VectorScale( vup, qj * j, temp2 ); + VectorAdd( temp, temp2, temp ); + VectorAdd( verts[0], temp, p->verts[0] ); + + VectorScale( vup, qj, temp ); + VectorAdd( p->verts[0], temp, p->verts[1] ); + + VectorScale( vright, qi, temp ); + VectorAdd( p->verts[1], temp, p->verts[2] ); + + VectorAdd( p->verts[0], temp, p->verts[3] ); + + R_CloudDrawPoly( p ); + } + } +} + +/* +============== +R_DrawClouds + +Quake-style clouds +============== +*/ +void R_DrawClouds( void ) +{ + int i; + + RI.isSkyVisible = true; + + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity * 0.25f ); + pglDepthFunc( GL_GEQUAL ); + pglDepthMask( GL_FALSE ); + + for( i = 0; i < 6; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + R_CloudRenderSide( i ); + } + + pglDepthFunc( GL_LEQUAL ); + pglDepthMask( GL_TRUE ); + + if( RI.fogEnabled ) + pglFogf( GL_FOG_DENSITY, RI.fogDensity ); +} + +/* +============= +R_InitSkyClouds + +A sky texture is 256*128, with the right side being a masked overlay +============== +*/ +void R_InitSkyClouds( mip_t *mt, texture_t *tx, qboolean custom_palette ) +{ + rgbdata_t r_temp, *r_sky; + uint *trans, *rgba; + uint transpix; + int r, g, b; + int i, j, p; + char texname[32]; + + if( !glw_state.initialized ) + return; + + Q_snprintf( texname, sizeof( texname ), "%s%s.mip", ( mt->offsets[0] > 0 ) ? "#" : "", tx->name ); + + if( mt->offsets[0] > 0 ) + { + int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); + + if( custom_palette ) size += sizeof( short ) + 768; + r_sky = FS_LoadImage( texname, (byte *)mt, size ); + } + else + { + // okay, loading it from wad + r_sky = FS_LoadImage( texname, NULL, 0 ); + } + + // make sure what sky image is valid + if( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 ) + { + MsgDev( D_ERROR, "R_InitSky: unable to load sky texture %s\n", tx->name ); + if( r_sky ) FS_FreeImage( r_sky ); + return; + } + + // make an average value for the back to avoid + // a fringe on the top level + trans = Mem_Alloc( r_temppool, r_sky->height * r_sky->height * sizeof( *trans )); + r = g = b = 0; + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j + r_sky->height]; + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + r += ((byte *)rgba)[0]; + g += ((byte *)rgba)[1]; + b += ((byte *)rgba)[2]; + } + } + + ((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height ); + ((byte *)&transpix)[3] = 0; + + // build a temporary image + r_temp = *r_sky; + r_temp.width = r_sky->width >> 1; + r_temp.height = r_sky->height; + r_temp.type = PF_RGBA_32; + r_temp.flags = IMAGE_HAS_COLOR; + r_temp.size = r_temp.width * r_temp.height * 4; + r_temp.buffer = (byte *)trans; + r_temp.palette = NULL; + + // load it in + tr.solidskyTexture = GL_LoadTextureInternal( "solid_sky", &r_temp, TF_NOMIPMAP, false ); + + for( i = 0; i < r_sky->width >> 1; i++ ) + { + for( j = 0; j < r_sky->height; j++ ) + { + p = r_sky->buffer[i * r_sky->width + j]; + + if( p == 0 ) + { + trans[(i * r_sky->height) + j] = transpix; + } + else + { + rgba = (uint *)r_sky->palette + p; + trans[(i * r_sky->height) + j] = *rgba; + } + } + } + + r_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + + // load it in + tr.alphaskyTexture = GL_LoadTextureInternal( "alpha_sky", &r_temp, TF_NOMIPMAP, false ); + + // clean up + FS_FreeImage( r_sky ); + Mem_Free( trans ); +} + +/* +============= +EmitWaterPolys + +Does a water warp on the pre-fragmented glpoly_t chain +============= +*/ +void EmitWaterPolys( msurface_t *warp, qboolean reverse ) +{ + float *v, nv, waveHeight; + float s, t, os, ot; + glpoly_t *p; + int i; + + if( !warp->polys ) return; + + // set the current waveheight + if( warp->polys->verts[0][2] >= RI.vieworg[2] ) + waveHeight = -RI.currententity->curstate.scale; + else waveHeight = RI.currententity->curstate.scale; + + // reset fog color for nonlightmapped water + GL_ResetFogColor(); + + if( FBitSet( warp->flags, SURF_DRAWTURB_QUADS )) + pglBegin( GL_QUADS ); + + for( p = warp->polys; p; p = p->next ) + { + if( reverse ) + v = p->verts[0] + ( p->numverts - 1 ) * VERTEXSIZE; + else v = p->verts[0]; + + if( !FBitSet( warp->flags, SURF_DRAWTURB_QUADS )) + pglBegin( GL_POLYGON ); + + for( i = 0; i < p->numverts; i++ ) + { + if( waveHeight ) + { + nv = r_turbsin[(int)(cl.time * 160.0f + v[1] + v[0]) & 255] + 8.0f; + nv = (r_turbsin[(int)(v[0] * 5.0f + cl.time * 171.0f - v[1]) & 255] + 8.0f ) * 0.8f + nv; + nv = nv * waveHeight + v[2]; + } + else nv = v[2]; + + os = v[3]; + ot = v[4]; + + s = os + r_turbsin[(int)((ot * 0.125f + cl.time) * TURBSCALE) & 255]; + s *= ( 1.0f / SUBDIVIDE_SIZE ); + + t = ot + r_turbsin[(int)((os * 0.125f + cl.time) * TURBSCALE) & 255]; + t *= ( 1.0f / SUBDIVIDE_SIZE ); + + pglTexCoord2f( s, t ); + pglVertex3f( v[0], v[1], nv ); + + if( reverse ) + v -= VERTEXSIZE; + else v += VERTEXSIZE; + } + + if( !FBitSet( warp->flags, SURF_DRAWTURB_QUADS )) + pglEnd(); + } + + if( FBitSet( warp->flags, SURF_DRAWTURB_QUADS )) + pglEnd(); + + GL_SetupFogColorForSurfaces(); +} \ No newline at end of file diff --git a/engine/client/s_backend.c b/engine/client/s_backend.c new file mode 100644 index 00000000..d234be55 --- /dev/null +++ b/engine/client/s_backend.c @@ -0,0 +1,483 @@ +/* +s_backend.c - sound hardware output +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include + +#define iDirectSoundCreate( a, b, c ) pDirectSoundCreate( a, b, c ) + +static HRESULT ( _stdcall *pDirectSoundCreate)(GUID* lpGUID, LPDIRECTSOUND* lpDS, IUnknown* pUnkOuter ); + +static dllfunc_t dsound_funcs[] = +{ +{ "DirectSoundCreate", (void **) &pDirectSoundCreate }, +{ NULL, NULL } +}; + +dll_info_t dsound_dll = { "dsound.dll", dsound_funcs, false }; + +#define SAMPLE_16BIT_SHIFT 1 +#define SECONDARY_BUFFER_SIZE 0x10000 + +typedef enum +{ + SIS_SUCCESS, + SIS_FAILURE, + SIS_NOTAVAIL +} si_state_t; + +static qboolean snd_firsttime = true; +static qboolean primary_format_set; +static HWND snd_hwnd; + +/* +======================================================================= +Global variables. Must be visible to window-procedure function +so it can unlock and free the data block after it has been played. +======================================================================= +*/ +static DWORD locksize; +static HPSTR lpData, lpData2; +static DWORD gSndBufSize; +static MMTIME mmstarttime; +static LPDIRECTSOUNDBUFFER pDSBuf, pDSPBuf; +static LPDIRECTSOUND pDS; + +qboolean SNDDMA_InitDirect( void *hInst ); +void SNDDMA_FreeSound( void ); + +static const char *DSoundError( int error ) +{ + switch( error ) + { + case DSERR_BUFFERLOST: + return "buffer is lost"; + case DSERR_INVALIDCALL: + return "invalid call"; + case DSERR_INVALIDPARAM: + return "invalid param"; + case DSERR_PRIOLEVELNEEDED: + return "invalid priority level"; + } + + return "unknown error"; +} + +/* +================== +DS_CreateBuffers +================== +*/ +static qboolean DS_CreateBuffers( void *hInst ) +{ + WAVEFORMATEX pformat, format; + DSBCAPS dsbcaps; + DSBUFFERDESC dsbuf; + + memset( &format, 0, sizeof( format )); + format.wFormatTag = WAVE_FORMAT_PCM; + format.nChannels = 2; + format.wBitsPerSample = 16; + format.nSamplesPerSec = SOUND_DMA_SPEED; + format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + if( pDS->lpVtbl->SetCooperativeLevel( pDS, hInst, DSSCL_EXCLUSIVE ) != DS_OK ) + { + Con_DPrintf( S_ERROR "DirectSound: failed to set EXCLUSIVE coop level\n" ); + SNDDMA_FreeSound(); + return false; + } + + // get access to the primary buffer, if possible, so we can set the sound hardware format + memset( &dsbuf, 0, sizeof( dsbuf )); + dsbuf.dwSize = sizeof( DSBUFFERDESC ); + dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbuf.dwBufferBytes = 0; + dsbuf.lpwfxFormat = NULL; + + memset( &dsbcaps, 0, sizeof( dsbcaps )); + dsbcaps.dwSize = sizeof( dsbcaps ); + primary_format_set = false; + + if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSPBuf, NULL ) == DS_OK ) + { + pformat = format; + + if( pDSPBuf->lpVtbl->SetFormat( pDSPBuf, &pformat ) != DS_OK ) + { + if( snd_firsttime ) + Con_DPrintf( S_ERROR "DirectSound: failed to set primary sound format\n" ); + } + else + { + primary_format_set = true; + } + } + + // create the secondary buffer we'll actually work with + memset( &dsbuf, 0, sizeof( dsbuf )); + dsbuf.dwSize = sizeof( DSBUFFERDESC ); + dsbuf.dwFlags = (DSBCAPS_CTRLFREQUENCY|DSBCAPS_LOCSOFTWARE); + dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; + dsbuf.lpwfxFormat = &format; + + memset( &dsbcaps, 0, sizeof( dsbcaps )); + dsbcaps.dwSize = sizeof( dsbcaps ); + + if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK ) + { + // couldn't get hardware, fallback to software. + dsbuf.dwFlags = (DSBCAPS_LOCSOFTWARE|DSBCAPS_GETCURRENTPOSITION2); + + if( pDS->lpVtbl->CreateSoundBuffer( pDS, &dsbuf, &pDSBuf, NULL ) != DS_OK ) + { + Con_DPrintf( S_ERROR "DirectSound: failed to create secondary buffer\n" ); + SNDDMA_FreeSound (); + return false; + } + } + + if( pDSBuf->lpVtbl->GetCaps( pDSBuf, &dsbcaps ) != DS_OK ) + { + Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" ); + SNDDMA_FreeSound (); + return false; + } + + // make sure mixer is active + if( pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ) != DS_OK ) + { + Con_DPrintf( S_ERROR "DirectSound: failed to create circular buffer\n" ); + SNDDMA_FreeSound (); + return false; + } + + // we don't want anyone to access the buffer directly w/o locking it first + lpData = NULL; + dma.samplepos = 0; + snd_hwnd = (HWND)hInst; + gSndBufSize = dsbcaps.dwBufferBytes; + dma.samples = gSndBufSize / 2; + dma.buffer = (byte *)lpData; + + SNDDMA_BeginPainting(); + if( dma.buffer ) memset( dma.buffer, 0, dma.samples * 2 ); + SNDDMA_Submit(); + + return true; +} + +/* +================== +DS_DestroyBuffers +================== +*/ +static void DS_DestroyBuffers( void ) +{ + if( pDS ) pDS->lpVtbl->SetCooperativeLevel( pDS, snd_hwnd, DSSCL_NORMAL ); + + if( pDSBuf ) + { + pDSBuf->lpVtbl->Stop( pDSBuf ); + pDSBuf->lpVtbl->Release( pDSBuf ); + } + + // only release primary buffer if it's not also the mixing buffer we just released + if( pDSPBuf && ( pDSBuf != pDSPBuf )) + pDSPBuf->lpVtbl->Release( pDSPBuf ); + + dma.buffer = NULL; + pDSPBuf = NULL; + pDSBuf = NULL; +} + +/* +================== +SNDDMA_LockSound +================== +*/ +void SNDDMA_LockSound( void ) +{ + if( pDSBuf != NULL ) + pDSBuf->lpVtbl->Stop( pDSBuf ); +} + +/* +================== +SNDDMA_LockSound +================== +*/ +void SNDDMA_UnlockSound( void ) +{ + if( pDSBuf != NULL ) + pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ); +} + +/* +================== +SNDDMA_FreeSound +================== +*/ +void SNDDMA_FreeSound( void ) +{ + if( pDS ) + { + DS_DestroyBuffers(); + pDS->lpVtbl->Release( pDS ); + Sys_FreeLibrary( &dsound_dll ); + } + + lpData = NULL; + pDSPBuf = NULL; + pDSBuf = NULL; + pDS = NULL; +} + +/* +================== +SNDDMA_InitDirect + +Direct-Sound support +================== +*/ +si_state_t SNDDMA_InitDirect( void *hInst ) +{ + DSCAPS dscaps; + HRESULT hresult; + + if( !dsound_dll.link ) + { + if( !Sys_LoadLibrary( &dsound_dll )) + return SIS_FAILURE; + } + + if(( hresult = iDirectSoundCreate( NULL, &pDS, NULL )) != DS_OK ) + { + if( hresult != DSERR_ALLOCATED ) + return SIS_FAILURE; + + Con_DPrintf( S_ERROR "DirectSound: hardware already in use\n" ); + return SIS_NOTAVAIL; + } + + dscaps.dwSize = sizeof( dscaps ); + + if( pDS->lpVtbl->GetCaps( pDS, &dscaps ) != DS_OK ) + Con_DPrintf( S_ERROR "DirectSound: failed to get capabilities\n" ); + + if( FBitSet( dscaps.dwFlags, DSCAPS_EMULDRIVER )) + { + Con_DPrintf( S_ERROR "DirectSound: driver not installed\n" ); + SNDDMA_FreeSound(); + return SIS_FAILURE; + } + + if( !DS_CreateBuffers( hInst )) + return SIS_FAILURE; + + return SIS_SUCCESS; +} + +/* +================== +SNDDMA_Init + +Try to find a sound device to mix for. +Returns false if nothing is found. +================== +*/ +int SNDDMA_Init( void *hInst ) +{ + // already initialized + if( dma.initialized ) return true; + + memset( &dma, 0, sizeof( dma )); + + // init DirectSound + if( SNDDMA_InitDirect( hInst ) != SIS_SUCCESS ) + return false; + + if( snd_firsttime ) + Con_Printf( "Audio: DirectSound\n" ); + dma.initialized = true; + snd_firsttime = false; + + return true; +} + +/* +============== +SNDDMA_GetDMAPos + +return the current sample position (in mono samples read) +inside the recirculating dma buffer, so the mixing code will know +how many sample are required to fill it up. +=============== +*/ +int SNDDMA_GetDMAPos( void ) +{ + int s; + MMTIME mmtime; + DWORD dwWrite; + + if( !dma.initialized ) + return 0; + + mmtime.wType = TIME_SAMPLES; + pDSBuf->lpVtbl->GetCurrentPosition( pDSBuf, &mmtime.u.sample, &dwWrite ); + s = mmtime.u.sample - mmstarttime.u.sample; + + s >>= SAMPLE_16BIT_SHIFT; + s &= (dma.samples - 1); + + return s; +} + +/* +============== +SNDDMA_GetSoundtime + +update global soundtime +=============== +*/ +int SNDDMA_GetSoundtime( void ) +{ + static int buffers, oldsamplepos; + int samplepos, fullsamples; + + fullsamples = dma.samples / 2; + + // it is possible to miscount buffers + // if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + + if( samplepos < oldsamplepos ) + { + buffers++; // buffer wrapped + + if( paintedtime > 0x40000000 ) + { + // time to chop things off to avoid 32 bit limits + buffers = 0; + paintedtime = fullsamples; + S_StopAllSounds( true ); + } + } + + oldsamplepos = samplepos; + + return (buffers * fullsamples + samplepos / 2); +} + +/* +============== +SNDDMA_BeginPainting + +Makes sure dma.buffer is valid +=============== +*/ +void SNDDMA_BeginPainting( void ) +{ + int reps; + DWORD dwSize2; + DWORD *pbuf, *pbuf2; + HRESULT hr; + DWORD dwStatus; + + if( !pDSBuf ) return; + + // if the buffer was lost or stopped, restore it and/or restart it + if( pDSBuf->lpVtbl->GetStatus( pDSBuf, &dwStatus ) != DS_OK ) + Con_DPrintf( S_ERROR "BeginPainting: couldn't get sound buffer status\n" ); + + if( dwStatus & DSBSTATUS_BUFFERLOST ) + pDSBuf->lpVtbl->Restore( pDSBuf ); + + if( !FBitSet( dwStatus, DSBSTATUS_PLAYING )) + pDSBuf->lpVtbl->Play( pDSBuf, 0, 0, DSBPLAY_LOOPING ); + + // lock the dsound buffer + dma.buffer = NULL; + reps = 0; + + while(( hr = pDSBuf->lpVtbl->Lock( pDSBuf, 0, gSndBufSize, &pbuf, &locksize, &pbuf2, &dwSize2, 0 )) != DS_OK ) + { + if( hr != DSERR_BUFFERLOST ) + { + Con_DPrintf( S_ERROR "BeginPainting: %s\n", DSoundError( hr )); + S_Shutdown (); + return; + } + else pDSBuf->lpVtbl->Restore( pDSBuf ); + if( ++reps > 2 ) return; + } + + dma.buffer = (byte *)pbuf; +} + +/* +============== +SNDDMA_Submit + +Send sound to device if buffer isn't really the dma buffer +Also unlocks the dsound buffer +=============== +*/ +void SNDDMA_Submit( void ) +{ + if( !dma.buffer ) return; + // unlock the dsound buffer + if( pDSBuf ) pDSBuf->lpVtbl->Unlock( pDSBuf, dma.buffer, locksize, NULL, 0 ); +} + +/* +============== +SNDDMA_Shutdown + +Reset the sound device for exiting +=============== +*/ +void SNDDMA_Shutdown( void ) +{ + if( !dma.initialized ) return; + dma.initialized = false; + SNDDMA_FreeSound(); +} + +/* +=========== +S_Activate + +Called when the main window gains or loses focus. +The window have been destroyed and recreated +between a deactivate and an activate. +=========== +*/ +void S_Activate( qboolean active, void *hInst ) +{ + if( !dma.initialized ) return; + snd_hwnd = (HWND)hInst; + + if( !pDS || !snd_hwnd ) + return; + + if( active ) + DS_CreateBuffers( snd_hwnd ); + else DS_DestroyBuffers(); +} \ No newline at end of file diff --git a/engine/client/s_dsp.c b/engine/client/s_dsp.c new file mode 100644 index 00000000..f0e25270 --- /dev/null +++ b/engine/client/s_dsp.c @@ -0,0 +1,905 @@ +/* +s_dsp.c - digital signal processing algorithms for audio FX +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "sound.h" + +#define MAX_DELAY 0.4f +#define MAX_ROOM_TYPES ARRAYSIZE( rgsxpre ) + +#define MONODLY 0 +#define MAX_MONO_DELAY 0.4f + +#define REVERBPOS 1 +#define MAX_REVERB_DELAY 0.1f + +#define STEREODLY 3 +#define MAX_STEREO_DELAY 0.1f + +#define REVERB_XFADE 32 + +#define MAXDLY (STEREODLY + 1) +#define MAXLP 10 +#define MAXPRESETS ARRAYSIZE( rgsxpre ) + +typedef struct sx_preset_s +{ + float room_lp; // lowpass + float room_mod; // modulation + + // reverb + float room_size; + float room_refl; + float room_rvblp; + + // delay + float room_delay; + float room_feedback; + float room_dlylp; + float room_left; +} sx_preset_t; + +typedef struct dly_s +{ + size_t cdelaysamplesmax; // delay line array size + + // delay line pointers + size_t idelayinput; + size_t idelayoutput; + + // crossfade + size_t idelayoutputxf; // output pointer + int xfade; // value + + int delaysamples; // delay setting + int delayfeedback; // feedback setting + + // lowpass + int lp; // is lowpass enabled + int lp0, lp1, lp2; // lowpass buffer + + // modulation + int mod; + int modcur; + + // delay line + int *lpdelayline; +} dly_t; + +const sx_preset_t rgsxpre[] = +{ +// -------reverb-------- -------delay-------- +// lp mod size refl rvblp delay feedback dlylp left +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0 }, // 0 off +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.065, 0.1, 0.0, 0.01 }, // 1 generic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.02, 0.75, 0.0, 0.01 }, // 2 metalic +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.03, 0.78, 0.0, 0.02 }, // 3 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.77, 0.0, 0.03 }, // 4 +{ 0.0, 0.0, 0.05, 0.85, 1.0, 0.008, 0.96, 2.0, 0.01 }, // 5 tunnel +{ 0.0, 0.0, 0.05, 0.88, 1.0, 0.01, 0.98, 2.0, 0.02 }, // 6 +{ 0.0, 0.0, 0.05, 0.92, 1.0, 0.015, 0.995, 2.0, 0.04 }, // 7 +{ 0.0, 0.0, 0.05, 0.84, 1.0, 0.0, 0.0, 2.0, 0.012 }, // 8 chamber +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.0, 0.0, 2.0, 0.008 }, // 9 +{ 0.0, 0.0, 0.05, 0.95, 1.0, 0.0, 0.0, 2.0, 0.004 }, // 10 +{ 0.0, 0.0, 0.05, 0.7, 0.0, 0.0, 0.0, 2.0, 0.012 }, // 11 brite +{ 0.0, 0.0, 0.055, 0.78, 0.0, 0.0, 0.0, 2.0, 0.008 }, // 12 +{ 0.0, 0.0, 0.05, 0.86, 0.0, 0.0, 0.0, 2.0, 0.002 }, // 13 +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.01 }, // 14 water +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.06, 0.85, 2.0, 0.02 }, // 15 +{ 1.0, 0.0, 0.0, 0.0, 1.0, 0.2, 0.6, 2.0, 0.05 }, // 16 +{ 0.0, 0.0, 0.05, 0.8, 1.0, 0.0, 0.48, 2.0, 0.016 }, // 17 concrete +{ 0.0, 0.0, 0.06, 0.9, 1.0, 0.0, 0.52, 2.0, 0.01 }, // 18 +{ 0.0, 0.0, 0.07, 0.94, 1.0, 0.3, 0.6, 2.0, 0.008 }, // 19 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.3, 0.42, 2.0, 0.0 }, // 20 outside +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.35, 0.48, 2.0, 0.0 }, // 21 +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.38, 0.6, 2.0, 0.0 }, // 22 +{ 0.0, 0.0, 0.05, 0.9, 1.0, 0.2, 0.28, 0.0, 0.0 }, // 23 cavern +{ 0.0, 0.0, 0.07, 0.9, 1.0, 0.3, 0.4, 0.0, 0.0 }, // 24 +{ 0.0, 0.0, 0.09, 0.9, 1.0, 0.35, 0.5, 0.0, 0.0 }, // 25 +{ 0.0, 1.0, 0.01, 0.9, 0.0, 0.0, 0.0, 2.0, 0.05 }, // 26 weirdo +{ 0.0, 0.0, 0.0, 0.0, 1.0, 0.009, 0.999, 2.0, 0.04 }, // 27 +{ 0.0, 0.0, 0.001, 0.999, 0.0, 0.2, 0.8, 2.0, 0.05 } // 28 +}; + +// cvars +convar_t *dsp_off; // disable dsp +convar_t *roomwater_type; // water room_type +convar_t *room_type; // current room type +convar_t *hisound; // DSP quality + +// underwater/special fx modulations +convar_t *sxmod_mod; +convar_t *sxmod_lowpass; + +// stereo delay(no feedback) +convar_t *sxste_delay; // straight left delay + +// mono reverb +convar_t *sxrvb_lp; // lowpass +convar_t *sxrvb_feedback; // reverb decay. Higher -- longer +convar_t *sxrvb_size; // room size. Higher -- larger + +// mono delay +convar_t *sxdly_lp; // lowpass +convar_t *sxdly_feedback; // cycles +convar_t *sxdly_delay; // current delay in seconds + +convar_t *dsp_room; // for compability +int idsp_dma_speed; +int idsp_room; +int room_typeprev; + +// routines +int sxamodl, sxamodr; // amplitude modulation values +int sxamodlt, sxamodrt; // modulation targets +int sxmod1cur, sxmod2cur; +int sxmod1, sxmod2; +int sxhires; + +portable_samplepair_t *paintto = NULL; + +dly_t rgsxdly[MAXDLY]; // stereo is last +int rgsxlp[MAXLP]; + +void SX_Profiling_f( void ); + +/* +============ +SX_ReloadRoomFX + +============ +*/ +void SX_ReloadRoomFX( void ) +{ + if( !dsp_room ) return; // not initialized + + SetBits( sxste_delay->flags, FCVAR_CHANGED ); + SetBits( sxrvb_feedback->flags, FCVAR_CHANGED ); + SetBits( sxdly_delay->flags, FCVAR_CHANGED ); + SetBits( room_type->flags, FCVAR_CHANGED ); +} + +/* +============ +SX_Init() + +Starts sound crackling system +============ +*/ +void SX_Init( void ) +{ + memset( rgsxdly, 0, sizeof( rgsxdly )); + memset( rgsxlp, 0, sizeof( rgsxlp )); + + sxamodr = sxamodl = sxamodrt = sxamodlt = 255; + idsp_dma_speed = SOUND_11k; + + hisound = Cvar_Get( "room_hires", "2", FCVAR_ARCHIVE, "dsp quality. 1 for 22k, 2 for 44k(recommended) and 3 for 96k" ); + sxhires = 2; + + sxmod1cur = sxmod1 = 350 * ( idsp_dma_speed / SOUND_11k ); + sxmod2cur = sxmod2 = 450 * ( idsp_dma_speed / SOUND_11k ); + + dsp_off = Cvar_Get( "dsp_off", "0", 0, "disable DSP processing" ); + roomwater_type = Cvar_Get( "waterroom_type", "14", 0, "water room type" ); + room_type = Cvar_Get( "room_type", "0", 0, "current room type preset" ); + + sxmod_lowpass = Cvar_Get( "room_lp", "0", 0, "for water fx, lowpass for entire room" ); + sxmod_mod = Cvar_Get( "room_mod", "0", 0, "stereo amptitude modulation for room" ); + + sxrvb_size = Cvar_Get( "room_size", "0", 0, "reverb: initial reflection size" ); + sxrvb_feedback = Cvar_Get( "room_refl", "0", 0, "reverb: decay time" ); + sxrvb_lp = Cvar_Get( "room_rvblp", "1", 0, "reverb: low pass filtering level" ); + + sxdly_delay = Cvar_Get( "room_delay", "0.8", 0, "mono delay: delay time" ); + sxdly_feedback = Cvar_Get( "room_feedback", "0.2", 0, "mono delay: decay time" ); + sxdly_lp = Cvar_Get( "room_dlylp", "1", 0, "mono delay: low pass filtering level" ); + + sxste_delay = Cvar_Get( "room_left", "0", 0, "left channel delay time" ); + + Cmd_AddCommand( "dsp_profile", SX_Profiling_f, "dsp stress-test, first argument is room_type" ); + + // for compability + dsp_room = room_type; + + SX_ReloadRoomFX(); +} + +/* +=========== +DLY_Free + +Free memory allocated for DSP +=========== +*/ +void DLY_Free( int idelay ) +{ + Assert( idelay >= 0 && idelay < MAXDLY ); + + if( rgsxdly[idelay].lpdelayline ) + { + Z_Free( rgsxdly[idelay].lpdelayline ); + rgsxdly[idelay].lpdelayline = NULL; + } +} + +/* +========== +SX_Shutdown + +Stop DSP processor +========== +*/ +void SX_Free( void ) +{ + int i; + + for( i = 0; i <= 3; i++ ) + DLY_Free( i ); + + Cmd_RemoveCommand( "dsp_profile" ); +} + + +/* +=========== +DLY_Init + +Initialize dly +=========== +*/ +int DLY_Init( int idelay, float delay ) +{ + dly_t *cur; + + // DLY_Init called anytime with constants. So valid it in debug builds only. + Assert( idelay >= 0 && idelay < MAXDLY ); + Assert( delay > 0.0f && delay <= MAX_DELAY ); + + DLY_Free( idelay ); // free dly if it's allocated + + cur = &rgsxdly[idelay]; + cur->cdelaysamplesmax = ((int)(delay * idsp_dma_speed) << sxhires) + 1; + cur->lpdelayline = (int *)Z_Malloc( cur->cdelaysamplesmax * sizeof( int )); + cur->xfade = 0; + + // init modulation + cur->mod = cur->modcur = 0; + + // init lowpass + cur->lp = 1; + cur->lp0 = cur->lp1 = cur->lp2 = 0; + + cur->idelayinput = 0; + cur->idelayoutput = cur->cdelaysamplesmax - cur->delaysamples; // NOTE: delaysamples must be set!!! + + + return 1; +} + +/* +============ +DLY_MovePointer + +Checks overflow and moves pointer +============ +*/ +_inline void DLY_MovePointer( dly_t *dly ) +{ + if( ++dly->idelayinput >= dly->cdelaysamplesmax ) + dly->idelayinput = 0; + + if( ++dly->idelayoutput >= dly->cdelaysamplesmax ) + dly->idelayoutput = 0; +} + +/* +============= +DLY_CheckNewStereoDelayVal + +Update stereo processor settings if we are in new room +============= +*/ +void DLY_CheckNewStereoDelayVal( void ) +{ + dly_t *const dly = &rgsxdly[STEREODLY]; + float delay = sxste_delay->value; + + if( !FBitSet( sxste_delay->flags, FCVAR_CHANGED )) + return; + + if( delay == 0 ) + { + DLY_Free( STEREODLY ); + } + else + { + int samples; + + delay = Q_min( delay, MAX_STEREO_DELAY ); + samples = (int)(delay * idsp_dma_speed) << sxhires; + + // re-init dly + if( !dly->lpdelayline ) + { + dly->delaysamples = samples; + DLY_Init( STEREODLY, MAX_STEREO_DELAY ); + } + + if( dly->delaysamples != samples ) + { + dly->xfade = 128; + dly->idelayoutputxf = dly->idelayinput - samples; + if( dly->idelayoutputxf < 0 ) + dly->idelayoutputxf += dly->cdelaysamplesmax; + } + + dly->modcur = dly->mod = 0; + + if( dly->delaysamples == 0 ) + DLY_Free( STEREODLY ); + } + + ClearBits( sxste_delay->flags, FCVAR_CHANGED ); +} + +/* +============= +DLY_DoStereoDelay + +Do stereo processing +============= +*/ +void DLY_DoStereoDelay( int count ) +{ + int delay, samplexf; + dly_t *const dly = &rgsxdly[STEREODLY]; + portable_samplepair_t *paint = paintto; + + if( !dly->lpdelayline ) + return; // inactive + + for( ; count; count--, paint++ ) + { + if( dly->mod && --dly->modcur < 0 ) + dly->modcur = dly->mod; + + delay = dly->lpdelayline[dly->idelayoutput]; + + // process only if crossfading, active left value or delayline + if( delay || paint->left || dly->xfade ) + { + // set up new crossfade, if not crossfading, not modulating, but going to + if( !dly->xfade && !dly->modcur && dly->mod ) + { + dly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * dly->delaysamples ) >> 9 ); + + dly->xfade = 128; + } + + dly->idelayoutputxf %= dly->cdelaysamplesmax; + + // modify delay, if crossfading + if( dly->xfade ) + { + samplexf = dly->lpdelayline[dly->idelayoutputxf] * (128 - dly->xfade) >> 7; + delay = samplexf + ((delay * dly->xfade) >> 7); + + if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax ) + dly->idelayoutputxf = 0; + + if( --dly->xfade == 0 ) + dly->idelayoutput = dly->idelayoutputxf; + } + + // save left value to delay line + dly->lpdelayline[dly->idelayinput] = CLIP( paint->left ); + + // paint new delay value + paint->left = delay; + } + else + { + // clear delay line + dly->lpdelayline[dly->idelayinput] = 0; + } + + DLY_MovePointer( dly ); + } +} + +/* +============= +DLY_CheckNewDelayVal + +Update delay processor settings if we are in new room +============= +*/ +void DLY_CheckNewDelayVal( void ) +{ + float delay = sxdly_delay->value; + dly_t *const dly = &rgsxdly[MONODLY]; + + if( FBitSet( sxdly_delay->flags, FCVAR_CHANGED )) + { + if( delay == 0 ) + { + DLY_Free( MONODLY ); + } + else + { + delay = min( delay, MAX_MONO_DELAY ); + dly->delaysamples = (int)(delay * idsp_dma_speed) << sxhires; + + // init dly + if( !dly->lpdelayline ) + DLY_Init( MONODLY, MAX_MONO_DELAY ); + + if( dly->lpdelayline ) + { + memset( dly->lpdelayline, 0, dly->cdelaysamplesmax * sizeof( int ) ); + dly->lp0 = dly->lp1 = dly->lp2 = 0; + } + + dly->idelayinput = 0; + dly->idelayoutput = dly->cdelaysamplesmax - dly->delaysamples; + + if( !dly->delaysamples ) + DLY_Free( MONODLY ); + + } + } + + ClearBits( sxdly_delay->flags, FCVAR_CHANGED ); + dly->lp = sxdly_lp->value; + dly->delayfeedback = 255 * sxdly_feedback->value; +} + +/* +============= +DLY_DoDelay + +Do delay processing +============= +*/ +void DLY_DoDelay( int count ) +{ + dly_t *const dly = &rgsxdly[MONODLY]; + portable_samplepair_t *paint = paintto; + int delay; + + if( !dly->lpdelayline || !count ) + return; // inactive + + for( ; count; count--, paint++ ) + { + delay = dly->lpdelayline[dly->idelayoutput]; + + // don't process if delay line and left/right samples are zero + if( delay || paint->left || paint->right ) + { + // calculate delayed value from average + int val = (( paint->left + paint->right ) >> 1 ) + (( dly->delayfeedback * delay ) >> 8); + val = CLIP( val ); + + if( dly->lp ) // lowpass + { + dly->lp0 = dly->lp1; + dly->lp1 = val; + val = ( dly->lp0 + dly->lp1 + (val << 1) ) >> 2; + } + + dly->lpdelayline[dly->idelayinput] = val; + + val >>= 2; + + paint->left = CLIP( paint->left + val ); + paint->right = CLIP( paint->right + val ); + } + else + { + dly->lpdelayline[dly->idelayinput] = 0; + dly->lp0 = dly->lp1 = 0; + } + + DLY_MovePointer( dly ); + } +} + +/* +=========== +RVB_SetUpDly + +Set up dly for reverb +=========== +*/ +void RVB_SetUpDly( int pos, float delay, int kmod ) +{ + int samples; + + delay = Q_min( delay, MAX_REVERB_DELAY ); + samples = (int)(delay * idsp_dma_speed) << sxhires; + + if( !rgsxdly[pos].lpdelayline ) + { + rgsxdly[pos].delaysamples = samples; + DLY_Init( pos, MAX_REVERB_DELAY ); + } + + rgsxdly[pos].modcur = rgsxdly[pos].mod = (int)(kmod * idsp_dma_speed / SOUND_11k) << sxhires; + + // set up crossfade, if delay has changed + if( rgsxdly[pos].delaysamples != samples ) + { + rgsxdly[pos].idelayoutputxf = rgsxdly[pos].idelayinput - samples; + if( rgsxdly[pos].idelayoutputxf < 0 ) + rgsxdly[pos].idelayoutputxf += rgsxdly[pos].cdelaysamplesmax; + rgsxdly[pos].xfade = 32; + } + + if( !rgsxdly[pos].delaysamples ) + DLY_Free( pos ); + +} + +/* +=========== +RVB_CheckNewReverbVal + +Update reverb settings if we are in new room +=========== +*/ +void RVB_CheckNewReverbVal( void ) +{ + dly_t *const dly1 = &rgsxdly[REVERBPOS]; + dly_t *const dly2 = &rgsxdly[REVERBPOS + 1]; + float delay = sxrvb_size->value; + + if( FBitSet( sxrvb_size->flags, FCVAR_CHANGED )) + { + if( delay == 0.0f ) + { + DLY_Free( REVERBPOS ); + DLY_Free( REVERBPOS + 1 ); + } + else + { + RVB_SetUpDly( REVERBPOS, sxrvb_size->value, 500 ); + RVB_SetUpDly( REVERBPOS+1, sxrvb_size->value * 0.71f, 700 ); + } + } + + ClearBits( sxrvb_size->flags, FCVAR_CHANGED ); + dly1->lp = dly2->lp = sxrvb_lp->value; + dly1->delayfeedback = dly2->delayfeedback = (int)(255 * sxrvb_feedback->value); +} + +/* +=========== +RVB_DoReverbForOneDly + +Do reverberation for one dly +=========== +*/ +int RVB_DoReverbForOneDly( dly_t *dly, const int vlr, const portable_samplepair_t *samplepair ) +{ + int delay; + int samplexf; + int val, valt; + int voutm = 0; + + if( --dly->modcur < 0 ) + dly->modcur = dly->mod; + + delay = dly->lpdelayline[dly->idelayoutput]; + + if( dly->xfade || delay || samplepair->left || samplepair->right ) + { + // modulate delay rate + if( !dly->mod ) + { + dly->idelayoutputxf = dly->idelayoutput + ((COM_RandomLong( 0, 255 ) * delay) >> 9 ); + + if( dly->idelayoutputxf >= dly->cdelaysamplesmax ) + dly->idelayoutputxf -= dly->cdelaysamplesmax; + + dly->xfade = REVERB_XFADE; + } + + if( dly->xfade ) + { + samplexf = (dly->lpdelayline[dly->idelayoutputxf] * (REVERB_XFADE - dly->xfade)) / REVERB_XFADE; + delay = ((delay * dly->xfade) / REVERB_XFADE) + samplexf; + + if( ++dly->idelayoutputxf >= dly->cdelaysamplesmax ) + dly->idelayoutputxf = 0; + + if( --dly->xfade == 0 ) + dly->idelayoutput = dly->idelayoutputxf; + } + + + if( delay ) + { + val = vlr + ( ( dly->delayfeedback * delay ) >> 8 ); + val = CLIP( val ); + } + else + val = vlr; + + if( dly->lp ) + { + valt = (dly->lp0 + val) >> 1; + dly->lp0 = val; + } + else + valt = val; + + voutm = dly->lpdelayline[dly->idelayinput] = valt; + } + else + { + voutm = dly->lpdelayline[dly->idelayinput] = 0; + dly->lp0 = 0; + } + + DLY_MovePointer( dly ); + + return voutm; + +} + +/* +=========== +RVB_DoReverb + +Do reverberation processing +=========== +*/ +void RVB_DoReverb( int count ) +{ + dly_t *const dly1 = &rgsxdly[REVERBPOS]; + dly_t *const dly2 = &rgsxdly[REVERBPOS+1]; + portable_samplepair_t *paint = paintto; + int vlr, voutm; + + if( !dly1->lpdelayline ) + return; + + for( ; count; count--, paint++ ) + { + vlr = ( paint->left + paint->right ) >> 1; + + voutm = RVB_DoReverbForOneDly( dly1, vlr, paint ); + voutm += RVB_DoReverbForOneDly( dly2, vlr, paint ); + + voutm = (11 * voutm) >> 6; + + paint->left = CLIP( paint->left + voutm ); + paint->right = CLIP( paint->right + voutm ); + } +} + +/* +=========== +RVB_DoAMod + +Do amplification modulation processing +=========== +*/ +void RVB_DoAMod( int count ) +{ + portable_samplepair_t *paint = paintto; + + if( !sxmod_lowpass->value && !sxmod_mod->value ) + return; + + for( ; count; count--, paint++ ) + { + portable_samplepair_t res = *paint; + + if( sxmod_lowpass->value ) + { + res.left = rgsxlp[0] + rgsxlp[1] + rgsxlp[2] + rgsxlp[3] + rgsxlp[4] + res.left; + res.right = rgsxlp[5] + rgsxlp[6] + rgsxlp[7] + rgsxlp[8] + rgsxlp[9] + res.right; + + res.left >>= 2; + res.right >>= 2; + + rgsxlp[0] = rgsxlp[1]; + rgsxlp[1] = rgsxlp[2]; + rgsxlp[2] = rgsxlp[3]; + rgsxlp[3] = rgsxlp[4]; + rgsxlp[4] = paint->left; + + rgsxlp[5] = rgsxlp[6]; + rgsxlp[6] = rgsxlp[7]; + rgsxlp[7] = rgsxlp[8]; + rgsxlp[8] = rgsxlp[9]; + rgsxlp[9] = paint->right; + } + + if( sxmod_mod->value ) + { + if( --sxmod1cur < 0 ) + sxmod1cur = sxmod1; + + if( !sxmod1 ) + sxamodlt = COM_RandomLong( 32, 255 ); + + if( --sxmod2cur < 0 ) + sxmod2cur = sxmod2; + + if( !sxmod2 ) + sxamodrt = COM_RandomLong( 32, 255 ); + + res.left = (sxamodl * res.left) >> 8; + res.right = (sxamodr * res.right) >> 8; + + if( sxamodl < sxamodlt ) + sxamodl++; + else if( sxamodl > sxamodlt ) + sxamodl--; + + if( sxamodr < sxamodrt ) + sxamodr++; + else if( sxamodr > sxamodrt ) + sxamodr--; + } + + paint->left = CLIP(res.left); + paint->right = CLIP(res.right); + } +} + +/* +=========== +DSP_Process + +(xash dsp interface) +=========== +*/ +void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ) +{ + if( dsp_off->value ) + return; + + // don't process DSP while in menu + if( cls.key_dest == key_menu || !sampleCount ) + return; + + // preset is already installed by CheckNewDspPresets + paintto = pbfront; + + RVB_DoAMod( sampleCount ); + RVB_DoReverb( sampleCount ); + DLY_DoDelay( sampleCount ); + DLY_DoStereoDelay( sampleCount ); +} + +/* +=========== +DSP_ClearState + +(xash dsp interface) +=========== +*/ +void DSP_ClearState( void ) +{ + Cvar_SetValue( "room_type", 0.0f ); + SX_ReloadRoomFX(); +} + +/* +=========== +CheckNewDspPresets + +(xash dsp interface) +=========== +*/ +void CheckNewDspPresets( void ) +{ + if( dsp_off->value != 0.0f ) + return; + + if( s_listener.waterlevel > 2 ) + idsp_room = roomwater_type->value; + else idsp_room = room_type->value; + + if( FBitSet( hisound->flags, FCVAR_CHANGED )) + { + sxhires = hisound->value; + ClearBits( hisound->flags, FCVAR_CHANGED ); + } + + if( idsp_room == room_typeprev && idsp_room == 0 ) + return; + + if( idsp_room > MAX_ROOM_TYPES ) + return; + + if( idsp_room != room_typeprev ) + { + const sx_preset_t *cur = rgsxpre + idsp_room; + + Cvar_SetValue( "room_lp", cur->room_lp ); + Cvar_SetValue( "room_mod", cur->room_mod ); + Cvar_SetValue( "room_size", cur->room_size ); + Cvar_SetValue( "room_refl", cur->room_refl ); + Cvar_SetValue( "room_rvblp", cur->room_rvblp ); + Cvar_SetValue( "room_delay", cur->room_delay ); + Cvar_SetValue( "room_feedback", cur->room_feedback ); + Cvar_SetValue( "room_dlylp", cur->room_dlylp ); + Cvar_SetValue( "room_left", cur->room_left ); + } + + room_typeprev = idsp_room; + + RVB_CheckNewReverbVal( ); + DLY_CheckNewDelayVal( ); + DLY_CheckNewStereoDelayVal(); +} + +/* +=========== +DSP_GetGain + +(xash dsp interface) +=========== +*/ +float DSP_GetGain( int idsp ) +{ + return 1.0f; +} + +void SX_Profiling_f( void ) +{ + portable_samplepair_t testbuffer[512]; + float oldroom = room_type->value; + double start, end; + int i, calls; + + for( i = 0; i < 512; i++ ) + { + testbuffer[i].left = COM_RandomLong( 0, 3000 ); + testbuffer[i].right = COM_RandomLong( 0, 3000 ); + } + + if( Cmd_Argc() > 1 ) + { + Cvar_SetValue( "room_type", Q_atof( Cmd_Argv( 1 ))); + SX_ReloadRoomFX(); + CheckNewDspPresets(); // we just need idsp_room immediately, for message below + } + + Con_Printf( "Profiling 10000 calls to DSP. Sample count is 512, room_type is %i\n", idsp_room ); + + start = Sys_DoubleTime(); + for( calls = 10000; calls; calls-- ) + { + DSP_Process( idsp_room, testbuffer, 512 ); + } + end = Sys_DoubleTime(); + + Con_Printf( "----------\nTook %g seconds.\n", end - start ); + + if( Cmd_Argc() > 1 ) + { + Cvar_SetValue( "room_type", oldroom ); + SX_ReloadRoomFX(); + CheckNewDspPresets(); + } +} \ No newline at end of file diff --git a/engine/client/s_load.c b/engine/client/s_load.c new file mode 100644 index 00000000..86a12556 --- /dev/null +++ b/engine/client/s_load.c @@ -0,0 +1,395 @@ +/* +s_load.c - sounds managment +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" + +// during registration it is possible to have more sounds +// than could actually be referenced during gameplay, +// because we don't want to free anything until we are +// sure we won't need it. +#define MAX_SFX 8192 +#define MAX_SFX_HASH (MAX_SFX/4) + +static int s_numSfx = 0; +static sfx_t s_knownSfx[MAX_SFX]; +static sfx_t *s_sfxHashList[MAX_SFX_HASH]; +static string s_sentenceImmediateName; // keep dummy sentence name +qboolean s_registering = false; +int s_registration_sequence = 0; + +/* +================= +S_SoundList_f +================= +*/ +void S_SoundList_f( void ) +{ + sfx_t *sfx; + wavdata_t *sc; + int i, totalSfx = 0; + int totalSize = 0; + + for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ ) + { + if( !sfx->servercount ) + continue; + + sc = sfx->cache; + if( sc ) + { + totalSize += sc->size; + + if( sc->loopStart >= 0 ) Con_Printf( "L" ); + else Con_Printf( " " ); + Con_Printf( " (%2db) %s : sound/%s\n", sc->width * 8, Q_memprint( sc->size ), sfx->name ); + totalSfx++; + } + } + + Con_Printf( "-------------------------------------------\n" ); + Con_Printf( "%i total sounds\n", totalSfx ); + Con_Printf( "%s total memory\n", Q_memprint( totalSize )); + Con_Printf( "\n" ); +} + +// return true if char 'c' is one of 1st 2 characters in pch +qboolean S_TestSoundChar( const char *pch, char c ) +{ + char *pcht = (char *)pch; + int i; + + if( !pch || !*pch ) + return false; + + // check first 2 characters + for( i = 0; i < 2; i++ ) + { + if( *pcht == c ) + return true; + pcht++; + } + return false; +} + +// return pointer to first valid character in file name +char *S_SkipSoundChar( const char *pch ) +{ + char *pcht = (char *)pch; + + // check first character + if( *pcht == '!' ) + pcht++; + return pcht; +} + +/* +================= +S_CreateDefaultSound +================= +*/ +static wavdata_t *S_CreateDefaultSound( void ) +{ + wavdata_t *sc; + + sc = Mem_Alloc( sndpool, sizeof( wavdata_t )); + + sc->width = 2; + sc->channels = 1; + sc->loopStart = -1; + sc->rate = SOUND_DMA_SPEED; + sc->samples = SOUND_DMA_SPEED; + sc->size = sc->samples * sc->width * sc->channels; + sc->buffer = Mem_Alloc( sndpool, sc->size ); + + return sc; +} + +/* +================= +S_LoadSound +================= +*/ +wavdata_t *S_LoadSound( sfx_t *sfx ) +{ + wavdata_t *sc = NULL; + + if( !sfx ) return NULL; + if( sfx->cache ) return sfx->cache; // see if still in memory + + if( Q_stricmp( sfx->name, "*default" )) + { + // load it from disk + if( sfx->name[0] == '*' ) + sc = FS_LoadSound( sfx->name + 1, NULL, 0 ); + else sc = FS_LoadSound( sfx->name, NULL, 0 ); + } + + if( !sc ) sc = S_CreateDefaultSound(); + + if( sc->rate < SOUND_11k ) // some bad sounds + Sound_Process( &sc, SOUND_11k, sc->width, SOUND_RESAMPLE ); + else if( sc->rate > SOUND_11k && sc->rate < SOUND_22k ) // some bad sounds + Sound_Process( &sc, SOUND_22k, sc->width, SOUND_RESAMPLE ); + else if( sc->rate > SOUND_22k && sc->rate <= SOUND_32k ) // some bad sounds + Sound_Process( &sc, SOUND_44k, sc->width, SOUND_RESAMPLE ); + + sfx->cache = sc; + + return sfx->cache; +} + +// ======================================================================= +// Load a sound +// ======================================================================= +/* +================== +S_FindName + +================== +*/ +sfx_t *S_FindName( const char *pname, int *pfInCache ) +{ + sfx_t *sfx; + uint i, hash; + string name; + + if( !COM_CheckString( pname ) || !dma.initialized ) + return NULL; + + if( Q_strlen( pname ) >= MAX_STRING ) + { + MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", pname ); + return NULL; + } + + Q_strncpy( name, pname, sizeof( name )); + COM_FixSlashes( name ); + + // see if already loaded + hash = COM_HashKey( name, MAX_SFX_HASH ); + for( sfx = s_sfxHashList[hash]; sfx; sfx = sfx->hashNext ) + { + if( !Q_strcmp( sfx->name, name )) + { + if( pfInCache ) + { + // indicate whether or not sound is currently in the cache. + *pfInCache = ( sfx->cache != NULL ) ? true : false; + } + // prolonge registration + sfx->servercount = s_registration_sequence; + return sfx; + } + } + + // find a free sfx slot spot + for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++) + if( !sfx->name[0] ) break; // free spot + + if( i == s_numSfx ) + { + if( s_numSfx == MAX_SFX ) + { + MsgDev( D_ERROR, "S_FindName: MAX_SFX limit exceeded\n" ); + return NULL; + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + memset( sfx, 0, sizeof( *sfx )); + if( pfInCache ) *pfInCache = false; + Q_strncpy( sfx->name, name, MAX_STRING ); + sfx->servercount = s_registration_sequence; + sfx->hashValue = COM_HashKey( sfx->name, MAX_SFX_HASH ); + + // link it in + sfx->hashNext = s_sfxHashList[sfx->hashValue]; + s_sfxHashList[sfx->hashValue] = sfx; + + return sfx; +} + +/* +================== +S_FreeSound +================== +*/ +void S_FreeSound( sfx_t *sfx ) +{ + sfx_t *hashSfx; + sfx_t **prev; + + if( !sfx || !sfx->name[0] ) return; + + // de-link it from the hash tree + prev = &s_sfxHashList[sfx->hashValue]; + while( 1 ) + { + hashSfx = *prev; + if( !hashSfx ) + break; + + if( hashSfx == sfx ) + { + *prev = hashSfx->hashNext; + break; + } + prev = &hashSfx->hashNext; + } + + if( sfx->cache ) FS_FreeSound( sfx->cache ); + memset( sfx, 0, sizeof( *sfx )); +} + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_BeginRegistration( void ) +{ + int i; + + s_registration_sequence++; + s_registering = true; + + // create unused 0-entry + S_RegisterSound( "*default" ); + + snd_ambient = false; + + // check for automatic ambient sounds + for( i = 0; i < NUM_AMBIENTS; i++ ) + { + if( !GI->ambientsound[i][0] ) + continue; // empty slot + + if( !ambient_sfx[i] ) + MsgDev( D_NOTE, "Loading ambient[%i]: ^2%s^7\n", i, GI->ambientsound[i] ); + ambient_sfx[i] = S_RegisterSound( GI->ambientsound[i] ); + if( ambient_sfx[i] ) snd_ambient = true; // allow auto-ambients + } +} + +/* +===================== +S_EndRegistration + +===================== +*/ +void S_EndRegistration( void ) +{ + sfx_t *sfx; + int i; + + if( !s_registering || !dma.initialized ) + return; + + // free any sounds not from this registration sequence + for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ ) + { + if( !sfx->name[0] ) continue; + if( sfx->servercount != s_registration_sequence ) + S_FreeSound( sfx ); // don't need this sound + } + + // load everything in + for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ ) + { + if( !sfx->name[0] ) continue; + S_LoadSound( sfx ); + } + s_registering = false; +} + +/* +================== +S_RegisterSound + +================== +*/ +sound_t S_RegisterSound( const char *name ) +{ + sfx_t *sfx; + + if( !COM_CheckString( name ) || !dma.initialized ) + return -1; + + if( S_TestSoundChar( name, '!' )) + { + Q_strncpy( s_sentenceImmediateName, name, sizeof( s_sentenceImmediateName )); + return SENTENCE_INDEX; + } + + // some stupid mappers used leading '/' or '\' in path to models or sounds + if( name[0] == '/' || name[0] == '\\' ) name++; + if( name[0] == '/' || name[0] == '\\' ) name++; + + sfx = S_FindName( name, NULL ); + if( !sfx ) return -1; + + sfx->servercount = s_registration_sequence; + if( !s_registering ) S_LoadSound( sfx ); + + return sfx - s_knownSfx; +} + +sfx_t *S_GetSfxByHandle( sound_t handle ) +{ + if( handle == -1 || !dma.initialized ) + return NULL; + + if( handle == SENTENCE_INDEX ) + { + // create new sfx + return S_FindName( s_sentenceImmediateName, NULL ); + } + + if( handle < 0 || handle >= s_numSfx ) + { + MsgDev( D_ERROR, "S_GetSfxByHandle: handle %i out of range (%i)\n", handle, s_numSfx ); + return NULL; + } + return &s_knownSfx[handle]; +} + +/* +================= +S_FreeSounds +================= +*/ +void S_FreeSounds( void ) +{ + sfx_t *sfx; + int i; + + if( !dma.initialized ) + return; + + // stop all sounds + S_StopAllSounds( true ); + + // free all sounds + for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ ) + S_FreeSound( sfx ); + + memset( s_knownSfx, 0, sizeof( s_knownSfx )); + memset( s_sfxHashList, 0, sizeof( s_sfxHashList )); + + s_numSfx = 0; +} \ No newline at end of file diff --git a/engine/client/s_main.c b/engine/client/s_main.c new file mode 100644 index 00000000..6d3eb713 --- /dev/null +++ b/engine/client/s_main.c @@ -0,0 +1,2244 @@ +/* +s_main.c - sound engine +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include "client.h" +#include "con_nprint.h" +#include "gl_local.h" +#include "pm_local.h" + +#define SND_CLIP_DISTANCE 1000.0f + +dma_t dma; +byte *sndpool; +static soundfade_t soundfade; +channel_t channels[MAX_CHANNELS]; +sound_t ambient_sfx[NUM_AMBIENTS]; +rawchan_t *raw_channels[MAX_RAW_CHANNELS]; +qboolean snd_ambient = false; +qboolean snd_fade_sequence = false; +listener_t s_listener; +int total_channels; +int soundtime; // sample PAIRS +int paintedtime; // sample PAIRS +static int trace_count = 0; +static int last_trace_chan = 0; +static byte s_fatphs[MAX_MAP_LEAFS/8]; // PHS array for snd module + +convar_t *s_volume; +convar_t *s_musicvolume; +convar_t *s_show; +convar_t *s_mixahead; +convar_t *s_lerping; +convar_t *s_ambient_level; +convar_t *s_ambient_fade; +convar_t *s_combine_sounds; +convar_t *snd_foliage_db_loss; +convar_t *snd_gain; +convar_t *snd_gain_max; +convar_t *snd_gain_min; +convar_t *s_refdist; +convar_t *s_refdb; +convar_t *s_cull; // cull sounds by geometry +convar_t *s_test; // cvar for testing new effects +convar_t *s_phs; + +/* +============================================================================= + + SOUND COMMON UTILITES + +============================================================================= +*/ +// dB = 20 log (amplitude/32768) 0 to -90.3dB +// amplitude = 32768 * 10 ^ (dB/20) 0 to +/- 32768 +// gain = amplitude/32768 0 to 1.0 +_inline float Gain_To_dB( float gain ) { return 20 * log( gain ); } +_inline float dB_To_Gain ( float dB ) { return pow( 10, dB / 20.0f ); } +_inline float Gain_To_Amplitude( float gain ) { return gain * 32768; } +_inline float Amplitude_To_Gain( float amplitude ) { return amplitude / 32768; } + +// convert sound db level to approximate sound source radius, +// used only for determining how much of sound is obscured by world +_inline float dB_To_Radius( float db ) +{ + return (SND_RADIUS_MIN + (SND_RADIUS_MAX - SND_RADIUS_MIN) * (db - SND_DB_MIN) / (SND_DB_MAX - SND_DB_MIN)); +} + +/* +============================================================================= + + SOUNDS PROCESSING + +============================================================================= +*/ +/* +================= +S_GetMasterVolume +================= +*/ +float S_GetMasterVolume( void ) +{ + float scale = 1.0f; + + if( !s_listener.inmenu && soundfade.percent != 0 ) + { + scale = bound( 0.0f, soundfade.percent / 100.0f, 1.0f ); + scale = 1.0f - scale; + } + return s_volume->value * scale; +} + +/* +================= +S_FadeClientVolume +================= +*/ +void S_FadeClientVolume( float fadePercent, float fadeOutSeconds, float holdTime, float fadeInSeconds ) +{ + soundfade.starttime = cl.mtime[0]; + soundfade.initial_percent = fadePercent; + soundfade.fadeouttime = fadeOutSeconds; + soundfade.holdtime = holdTime; + soundfade.fadeintime = fadeInSeconds; +} + +/* +================= +S_IsClient +================= +*/ +qboolean S_IsClient( int entnum ) +{ + return ( entnum == s_listener.entnum ); +} + + +// free channel so that it may be allocated by the +// next request to play a sound. If sound is a +// word in a sentence, release the sentence. +// Works for static, dynamic, sentence and stream sounds +/* +================= +S_FreeChannel +================= +*/ +void S_FreeChannel( channel_t *ch ) +{ + ch->sfx = NULL; + ch->name[0] = '\0'; + ch->use_loop = false; + ch->isSentence = false; + + // clear mixer + memset( &ch->pMixer, 0, sizeof( ch->pMixer )); + + SND_CloseMouth( ch ); +} + +/* +================= +S_UpdateSoundFade +================= +*/ +void S_UpdateSoundFade( void ) +{ + float f, totaltime, elapsed; + + // determine current fade value. + // assume no fading remains + soundfade.percent = 0; + + totaltime = soundfade.fadeouttime + soundfade.fadeintime + soundfade.holdtime; + + elapsed = cl.mtime[0] - soundfade.starttime; + + // clock wrapped or reset (BUG) or we've gone far enough + if( elapsed < 0.0f || elapsed >= totaltime || totaltime <= 0.0f ) + return; + + // We are in the fade time, so determine amount of fade. + if( soundfade.fadeouttime > 0.0f && ( elapsed < soundfade.fadeouttime )) + { + // ramp up + f = elapsed / soundfade.fadeouttime; + } + else if( elapsed <= ( soundfade.fadeouttime + soundfade.holdtime )) // Inside the hold time + { + // stay + f = 1.0f; + } + else + { + // ramp down + f = ( elapsed - ( soundfade.fadeouttime + soundfade.holdtime ) ) / soundfade.fadeintime; + f = 1.0f - f; // backward interpolated... + } + + // spline it. + f = SimpleSpline( f ); + f = bound( 0.0f, f, 1.0f ); + + soundfade.percent = soundfade.initial_percent * f; + + if( snd_fade_sequence ) + S_FadeMusicVolume( soundfade.percent ); + + if( snd_fade_sequence && soundfade.percent == 100.0f ) + { + S_StopAllSounds( false ); + S_StopBackgroundTrack(); + snd_fade_sequence = false; + } +} + +/* +================= +SND_ChannelOkToTrace + +All new sounds must traceline once, +but cap the max number of tracelines performed per frame +for longer or looping sounds to SND_TRACE_UPDATE_MAX. +================= +*/ +qboolean SND_ChannelOkToTrace( channel_t *ch ) +{ + int i, j; + + // always trace first time sound is spatialized + if( ch->bfirstpass ) return true; + + // if already traced max channels, return + if( trace_count >= SND_TRACE_UPDATE_MAX ) + return false; + + // search through all channels starting at g_snd_last_trace_chan index + j = last_trace_chan; + + for( i = 0; i < total_channels; i++ ) + { + if( &( channels[j] ) == ch ) + { + ch->bTraced = true; + trace_count++; + return true; + } + + // wrap channel index + if( ++j >= total_channels ) + j = 0; + } + + // why didn't we find this channel? + return false; +} + +/* +================= +SND_ChannelTraceReset + +reset counters for traceline limiting per audio update +================= +*/ +void SND_ChannelTraceReset( void ) +{ + int i; + + // reset search point - make sure we start counting from a new spot + // in channel list each time + last_trace_chan += SND_TRACE_UPDATE_MAX; + + // wrap at total_channels + if( last_trace_chan >= total_channels ) + last_trace_chan = last_trace_chan - total_channels; + + // reset traceline counter + trace_count = 0; + + // reset channel traceline flag + for( i = 0; i < total_channels; i++ ) + channels[i].bTraced = false; +} + +/* +================= +SND_PickDynamicChannel + +Select a channel from the dynamic channel allocation area. For the given entity, +override any other sound playing on the same channel (see code comments below for +exceptions). +================= +*/ +channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean *ignore ) +{ + int ch_idx; + int first_to_die; + int life_left; + int timeleft; + + // check for replacement sound, or find the best one to replace + first_to_die = -1; + life_left = 0x7fffffff; + if( ignore ) *ignore = false; + + for( ch_idx = NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++ ) + { + channel_t *ch = &channels[ch_idx]; + + // Never override a streaming sound that is currently playing or + // voice over IP data that is playing or any sound on CHAN_VOICE( acting ) + if( ch->sfx && ( ch->entchannel == CHAN_STREAM )) + continue; + + if( channel != CHAN_AUTO && ch->entnum == entnum && ( ch->entchannel == channel || channel == -1 )) + { + // always override sound from same entity + first_to_die = ch_idx; + break; + } + + // don't let monster sounds override player sounds + if( ch->sfx && S_IsClient( ch->entnum ) && !S_IsClient( entnum )) + continue; + + // try to pick the sound with the least amount of data left to play + timeleft = 0; + if( ch->sfx ) + { + timeleft = 1; // ch->end - paintedtime + } + + if( timeleft < life_left ) + { + life_left = timeleft; + first_to_die = ch_idx; + } + } + + if( first_to_die == -1 ) + return NULL; + + if( channels[first_to_die].sfx ) + { + // don't restart looping sounds for the same entity + wavdata_t *sc = channels[first_to_die].sfx->cache; + + if( sc && sc->loopStart != -1 ) + { + channel_t *ch = &channels[first_to_die]; + + if( ch->entnum == entnum && ch->entchannel == channel && ch->sfx == sfx ) + { + if( ignore ) *ignore = true; + // same looping sound, same ent, same channel, don't restart the sound + return NULL; + } + } + + // be sure and release previous channel if sentence. + S_FreeChannel( &( channels[first_to_die] )); + } + + return &channels[first_to_die]; +} + +/* +===================== +SND_PickStaticChannel + +Pick an empty channel from the static sound area, or allocate a new +channel. Only fails if we're at max_channels (128!!!) or if +we're trying to allocate a channel for a stream sound that is +already playing. +===================== +*/ +channel_t *SND_PickStaticChannel( const vec3_t pos, sfx_t *sfx ) +{ + channel_t *ch = NULL; + int i; + + // check for replacement sound, or find the best one to replace + for( i = MAX_DYNAMIC_CHANNELS; i < total_channels; i++ ) + { + if( channels[i].sfx == NULL ) + break; + + if( VectorCompare( pos, channels[i].origin ) && channels[i].sfx == sfx ) + break; + } + + if( i < total_channels ) + { + // reuse an empty static sound channel + ch = &channels[i]; + } + else + { + // no empty slots, alloc a new static sound channel + if( total_channels == MAX_CHANNELS ) + { + MsgDev( D_ERROR, "S_PickStaticChannel: no free channels\n" ); + return NULL; + } + + // get a channel for the static sound + ch = &channels[total_channels]; + total_channels++; + } + return ch; +} + +/* +================= +S_AlterChannel + +search through all channels for a channel that matches this +soundsource, entchannel and sfx, and perform alteration on channel +as indicated by 'flags' parameter. If shut down request and +sfx contains a sentence name, shut off the sentence. +returns TRUE if sound was altered, +returns FALSE if sound was not found (sound is not playing) +================= +*/ +int S_AlterChannel( int entnum, int channel, sfx_t *sfx, int vol, int pitch, int flags ) +{ + channel_t *ch; + int i; + + if( S_TestSoundChar( sfx->name, '!' )) + { + // This is a sentence name. + // For sentences: assume that the entity is only playing one sentence + // at a time, so we can just shut off + // any channel that has ch->isSentence >= 0 and matches the entnum. + + for( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ ) + { + if( ch->entnum == entnum && ch->entchannel == channel && ch->sfx && ch->isSentence ) + { + if( flags & SND_CHANGE_PITCH ) + ch->basePitch = pitch; + + if( flags & SND_CHANGE_VOL ) + ch->master_vol = vol; + + if( flags & SND_STOP ) + S_FreeChannel( ch ); + + return true; + } + } + // channel not found + return false; + + } + + // regular sound or streaming sound + for( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ ) + { + if( ch->entnum == entnum && ch->entchannel == channel && ch->sfx == sfx ) + { + if( flags & SND_CHANGE_PITCH ) + ch->basePitch = pitch; + + if( flags & SND_CHANGE_VOL ) + ch->master_vol = vol; + + if( flags & SND_STOP ) + S_FreeChannel( ch ); + + return true; + } + } + return false; +} + +/* +================= +SND_FadeToNewGain + +always ramp channel gain changes over time +returns ramped gain, given new target gain +================= +*/ +float SND_FadeToNewGain( channel_t *ch, float gain_new ) +{ + float speed, frametime; + + if( gain_new == -1.0 ) + { + // if -1 passed in, just keep fading to existing target + gain_new = ch->ob_gain_target; + } + + // if first time updating, store new gain into gain & target, return + // if gain_new is close to existing gain, store new gain into gain & target, return + if( ch->bfirstpass || ( fabs( gain_new - ch->ob_gain ) < 0.01f )) + { + ch->ob_gain = gain_new; + ch->ob_gain_target = gain_new; + ch->ob_gain_inc = 0.0f; + return gain_new; + } + + // set up new increment to new target + frametime = s_listener.frametime; + speed = ( frametime / SND_GAIN_FADE_TIME ) * ( gain_new - ch->ob_gain ); + + ch->ob_gain_inc = fabs( speed ); + + // ch->ob_gain_inc = fabs( gain_new - ch->ob_gain ) / 10.0f; + ch->ob_gain_target = gain_new; + + // if not hit target, keep approaching + if( fabs( ch->ob_gain - ch->ob_gain_target ) > 0.01f ) + { + ch->ob_gain = ApproachVal( ch->ob_gain_target, ch->ob_gain, ch->ob_gain_inc ); + } + else + { + // close enough, set gain = target + ch->ob_gain = ch->ob_gain_target; + } + + return ch->ob_gain; +} + +/* +================= +SND_GetGainObscured + +drop gain on channel if sound emitter obscured by +world, unbroken windows, closed doors, large solid entities etc. +================= +*/ +float SND_GetGainObscured( channel_t *ch, qboolean fplayersound, qboolean flooping ) +{ + float gain = 1.0f; + vec3_t endpoint; + int count = 1; + pmtrace_t tr; + + if( fplayersound ) return gain; // unchanged + + // during signon just apply regular state machine since world hasn't been + // created or settled yet... + if( !CL_Active( )) + { + gain = SND_FadeToNewGain( ch, -1.0f ); + return gain; + } + + // don't do gain obscuring more than once on short one-shot sounds + if( !ch->bfirstpass && !ch->isSentence && !flooping && ( ch->entchannel != CHAN_STREAM )) + { + gain = SND_FadeToNewGain( ch, -1.0f ); + return gain; + } + + // if long or looping sound, process N channels per frame - set 'processed' flag, clear by + // cycling through all channels - this maintains a cap on traces per frame + if( !SND_ChannelOkToTrace( ch )) + { + // just keep updating fade to existing target gain - no new trace checking + gain = SND_FadeToNewGain( ch, -1.0 ); + return gain; + } + + // set up traceline from player eyes to sound emitting entity origin + VectorCopy( ch->origin, endpoint ); + + tr = CL_TraceLine( s_listener.origin, endpoint, PM_STUDIO_IGNORE ); + + if(( tr.fraction < 1.0f || tr.allsolid || tr.startsolid ) && tr.fraction < 0.99f ) + { + // can't see center of sound source: + // build extents based on dB sndlvl of source, + // test to see how many extents are visible, + // drop gain by g_snd_obscured_loss_db per extent hidden + vec3_t endpoints[4]; + int i, sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult ); + vec3_t vecl, vecr, vecl2, vecr2; + vec3_t vsrc_forward; + vec3_t vsrc_right; + vec3_t vsrc_up; + float radius; + + // get radius + if( ch->radius > 0 ) radius = ch->radius; + else radius = dB_To_Radius( sndlvl ); // approximate radius from soundlevel + + // set up extent endpoints - on upward or downward diagonals, facing player + for( i = 0; i < 4; i++ ) VectorCopy( endpoint, endpoints[i] ); + + // vsrc_forward is normalized vector from sound source to listener + VectorSubtract( s_listener.origin, endpoint, vsrc_forward ); + VectorNormalize( vsrc_forward ); + VectorVectors( vsrc_forward, vsrc_right, vsrc_up ); + + VectorAdd( vsrc_up, vsrc_right, vecl ); + + // if src above listener, force 'up' vector to point down - create diagonals up & down + if( endpoint[2] > s_listener.origin[2] + ( 10 * 12 )) + vsrc_up[2] = -vsrc_up[2]; + + VectorSubtract( vsrc_up, vsrc_right, vecr ); + VectorNormalize( vecl ); + VectorNormalize( vecr ); + + // get diagonal vectors from sound source + VectorScale( vecl, radius, vecl2 ); + VectorScale( vecr, radius, vecr2 ); + VectorScale( vecl, (radius / 2.0f), vecl ); + VectorScale( vecr, (radius / 2.0f), vecr ); + + // endpoints from diagonal vectors + VectorAdd( endpoints[0], vecl, endpoints[0] ); + VectorAdd( endpoints[1], vecr, endpoints[1] ); + VectorAdd( endpoints[2], vecl2, endpoints[2] ); + VectorAdd( endpoints[3], vecr2, endpoints[3] ); + + // drop gain for each point on radius diagonal that is obscured + for( count = 0, i = 0; i < 4; i++ ) + { + // UNDONE: some endpoints are in walls - in this case, trace from the wall hit location + tr = CL_TraceLine( s_listener.origin, endpoints[i], PM_STUDIO_IGNORE ); + + if(( tr.fraction < 1.0f || tr.allsolid || tr.startsolid ) && tr.fraction < 0.99f && !tr.startsolid ) + { + // skip first obscured point: at least 2 points + center should be obscured to hear db loss + if( ++count > 1 ) gain = gain * dB_To_Gain( SND_OBSCURED_LOSS_DB ); + } + } + } + + // crossfade to new gain + gain = SND_FadeToNewGain( ch, gain ); + + return gain; +} + +/* +================= +SND_GetGain + +The complete gain calculation, with SNDLVL given in dB is: +GAIN = 1/dist * snd_refdist * 10 ^ (( SNDLVL - snd_refdb - (dist * snd_foliage_db_loss / 1200)) / 20 ) +for gain > SND_GAIN_THRESH, start curve smoothing with +GAIN = 1 - 1 / (Y * GAIN ^ SND_GAIN_POWER) +where Y = -1 / ( (SND_GAIN_THRESH ^ SND_GAIN_POWER) * ( SND_GAIN_THRESH - 1 )) +gain curve construction +================= +*/ +float SND_GetGain( channel_t *ch, qboolean fplayersound, qboolean flooping, float dist ) +{ + float gain = snd_gain->value; + + if( ch->dist_mult ) + { + // test additional attenuation + // at 30c, 14.7psi, 60% humidity, 1000Hz == 0.22dB / 100ft. + // dense foliage is roughly 2dB / 100ft + float additional_dB_loss = snd_foliage_db_loss->value * (dist / 1200); + float additional_dist_mult = pow( 10, additional_dB_loss / 20 ); + float relative_dist = dist * ch->dist_mult * additional_dist_mult; + + // hard code clamp gain to 10x normal (assumes volume and external clipping) + if( relative_dist > 0.1f ) + gain *= ( 1.0f / relative_dist ); + else gain *= 10.0f; + + // if gain passess threshold, compress gain curve such that gain smoothly approaches 1.0 + if( gain > SND_GAIN_COMP_THRESH ) + { + float snd_gain_comp_power = SND_GAIN_COMP_EXP_MAX; + int sndlvl = DIST_MULT_TO_SNDLVL( ch->dist_mult ); + float Y; + + // decrease compression curve fit for higher sndlvl values + if( sndlvl > SND_DB_MED ) + { + // snd_gain_power varies from max to min as sndlvl varies from 90 to 140 + snd_gain_comp_power = RemapVal((float)sndlvl, SND_DB_MED, SND_DB_MAX, SND_GAIN_COMP_EXP_MAX, SND_GAIN_COMP_EXP_MIN ); + } + + // calculate crossover point + Y = -1.0f / ( pow( SND_GAIN_COMP_THRESH, snd_gain_comp_power ) * ( SND_GAIN_COMP_THRESH - 1 )); + + // calculate compressed gain + gain = 1.0f - 1.0f / (Y * pow( gain, snd_gain_comp_power )); + gain = gain * snd_gain_max->value; + } + + if( gain < snd_gain_min->value ) + { + // sounds less than snd_gain_min fall off to 0 in distance it took them to fall to snd_gain_min + gain = snd_gain_min->value * ( 2.0f - relative_dist * snd_gain_min->value ); + if( gain <= 0.0f ) gain = 0.001f; // don't propagate 0 gain + } + } + + if( fplayersound ) + { + // player weapon sounds get extra gain - this compensates + // for npc distance effect weapons which mix louder as L+R into L, R + if( ch->entchannel == CHAN_WEAPON ) + gain = gain * dB_To_Gain( SND_GAIN_PLAYER_WEAPON_DB ); + } + + // modify gain if sound source not visible to player + gain = gain * SND_GetGainObscured( ch, fplayersound, flooping ); + + return gain; +} + +/* +================= +SND_CheckPHS + +using a 'fat' radius +================= +*/ +qboolean SND_CheckPHS( channel_t *ch ) +{ + mleaf_t *leaf; + + if( !s_phs->value ) + return true; + + if( !ch->dist_mult && ch->entnum ) + return true; // no attenuation + + if( ch->movetype == MOVETYPE_PUSH ) + { + if( Mod_BoxVisible( ch->absmin, ch->absmax, s_listener.pasbytes )) + return true; + } + else + { + leaf = Mod_PointInLeaf( ch->origin, cl.worldmodel->nodes ); + + if( CHECKVISBIT( s_listener.pasbytes, leaf->cluster )) + return true; + } + + return false; +} + +/* +================= +S_SpatializeChannel +================= +*/ +void S_SpatializeChannel( int *left_vol, int *right_vol, int master_vol, float gain, float dot, float dist ) +{ + float lscale, rscale, scale; + + rscale = 1.0f + dot; + lscale = 1.0f - dot; + + // add in distance effect + if( s_cull->value ) scale = gain * rscale / 2; + else scale = ( 1.0f - dist ) * rscale; + *right_vol = (int)( master_vol * scale ); + + if( s_cull->value ) scale = gain * lscale / 2; + else scale = ( 1.0f - dist ) * lscale; + *left_vol = (int)( master_vol * scale ); + + *right_vol = bound( 0, *right_vol, 255 ); + *left_vol = bound( 0, *left_vol, 255 ); +} + +/* +================= +SND_Spatialize +================= +*/ +void SND_Spatialize( channel_t *ch ) +{ + vec3_t source_vec; + float dist, dot, gain = 1.0f; + qboolean fplayersound = false; + qboolean looping = false; + wavdata_t *pSource; + + // anything coming from the view entity will allways be full volume + if( S_IsClient( ch->entnum )) + { + if( !s_cull->value ) + { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + return; + } + + // sounds coming from listener actually come from a short distance directly in front of listener + fplayersound = true; + } + + pSource = ch->sfx->cache; + + if( ch->use_loop && pSource && pSource->loopStart != -1 ) + looping = true; + + if( !ch->staticsound ) + { + if( !CL_GetEntitySpatialization( ch ) || !SND_CheckPHS( ch )) + { + // origin is null and entity not exist on client + ch->leftvol = ch->rightvol = 0; + ch->bfirstpass = false; + return; + } + } + + // source_vec is vector from listener to sound source + // player sounds come from 1' in front of player + if( fplayersound ) VectorScale( s_listener.forward, 12.0f, source_vec ); + else VectorSubtract( ch->origin, s_listener.origin, source_vec ); + + // normalize source_vec and get distance from listener to source + dist = VectorNormalizeLength( source_vec ); + dot = DotProduct( s_listener.right, source_vec ); + + // for sounds with a radius, spatialize left/right evenly within the radius + if( ch->radius > 0 && dist < ch->radius ) + { + float interval = ch->radius * 0.5f; + float blend = dist - interval; + + if( blend < 0 ) blend = 0; + blend /= interval; + + // blend is 0.0 - 1.0, from 50% radius -> 100% radius + // at radius * 0.5, dot is 0 (ie: sound centered left/right) + // at radius dot == dot + dot *= blend; + } + + if( s_cull->value ) + { + // calculate gain based on distance, atmospheric attenuation, interposed objects + // perform compression as gain approaches 1.0 + gain = SND_GetGain( ch, fplayersound, looping, dist ); + } + + // don't pan sounds with no attenuation + if( ch->dist_mult <= 0.0f ) dot = 0.0f; + + // fill out channel volumes for single location + S_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, gain, dot, dist * ch->dist_mult ); + + // if playing a word, set volume + VOX_SetChanVol( ch ); + + // end of first time spatializing sound + if( CL_Active( )) ch->bfirstpass = false; +} + +/* +==================== +S_StartSound + +Start a sound effect for the given entity on the given channel (ie; voice, weapon etc). +Try to grab a channel out of the 8 dynamic spots available. +Currently used for looping sounds, streaming sounds, sentences, and regular entity sounds. +NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in. +Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100 + +NOTE: it's not a good idea to play looping sounds through StartDynamicSound, because +if the looping sound starts out of range, or is bumped from the buffer by another sound +it will never be restarted. Use StartStaticSound (pass CHAN_STATIC to EMIT_SOUND or +SV_StartSound. +==================== +*/ +void S_StartSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags ) +{ + wavdata_t *pSource; + sfx_t *sfx = NULL; + channel_t *target_chan, *check; + int vol, ch_idx; + qboolean bIgnore = false; + + if( !dma.initialized ) return; + sfx = S_GetSfxByHandle( handle ); + if( !sfx ) return; + + vol = bound( 0, fvol * 255, 255 ); + if( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues + + if( flags & ( SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH )) + { + if( S_AlterChannel( ent, chan, sfx, vol, pitch, flags )) + return; + + if( flags & SND_STOP ) return; + // fall through - if we're not trying to stop the sound, + // and we didn't find it (it's not playing), go ahead and start it up + } + + if( pitch == 0 ) + { + MsgDev( D_WARN, "S_StartSound: ( %s ) ignored, called with pitch 0\n", sfx->name ); + return; + } + + if( !pos ) pos = RI.vieworg; + + // pick a channel to play on + if( chan == CHAN_STATIC ) target_chan = SND_PickStaticChannel( pos, sfx ); + else target_chan = SND_PickDynamicChannel( ent, chan, sfx, &bIgnore ); + + if( !target_chan ) + { + if( !bIgnore ) + Con_DPrintf( S_ERROR "dropped sound \"%s%s\"\n", DEFAULT_SOUNDPATH, sfx->name ); + return; + } + + // spatialize + memset( target_chan, 0, sizeof( *target_chan )); + + VectorCopy( pos, target_chan->origin ); + target_chan->staticsound = ( ent == 0 ) ? true : false; + target_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true; + target_chan->localsound = (flags & SND_LOCALSOUND) ? true : false; + target_chan->dist_mult = (attn / SND_CLIP_DISTANCE); + target_chan->master_vol = vol; + target_chan->entnum = ent; + target_chan->entchannel = chan; + target_chan->basePitch = pitch; + target_chan->isSentence = false; + target_chan->radius = 0.0f; + target_chan->sfx = sfx; + + // initialize gain due to obscured sound source + target_chan->bfirstpass = true; + target_chan->ob_gain = 0.0f; + target_chan->ob_gain_inc = 0.0f; + target_chan->ob_gain_target = 0.0f; + target_chan->bTraced = false; + + pSource = NULL; + + if( S_TestSoundChar( sfx->name, '!' )) + { + // this is a sentence + // link all words and load the first word + // NOTE: sentence names stored in the cache lookup are + // prepended with a '!'. Sentence names stored in the + // sentence file do not have a leading '!'. + VOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name )); + Q_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name )); + sfx = target_chan->sfx; + if( sfx ) pSource = sfx->cache; + } + else + { + // regular or streamed sound fx + pSource = S_LoadSound( sfx ); + target_chan->name[0] = '\0'; + } + + if( !pSource ) + { + S_FreeChannel( target_chan ); + return; + } + + SND_Spatialize( target_chan ); + + // If a client can't hear a sound when they FIRST receive the StartSound message, + // the client will never be able to hear that sound. This is so that out of + // range sounds don't fill the playback buffer. For streaming sounds, we bypass this optimization. + if( !target_chan->leftvol && !target_chan->rightvol ) + { + // looping sounds don't use this optimization because they should stick around until they're killed. + if( !sfx->cache || sfx->cache->loopStart == -1 ) + { + // if this is a streaming sound, play the whole thing. + if( chan != CHAN_STREAM ) + { + S_FreeChannel( target_chan ); + return; // not audible at all + } + } + } + + // Init client entity mouth movement vars + SND_InitMouth( ent, chan ); + + for( ch_idx = NUM_AMBIENTS, check = channels + NUM_AMBIENTS; ch_idx < MAX_DYNAMIC_CHANNELS; ch_idx++, check++) + { + if( check == target_chan ) continue; + + if( check->sfx == sfx && !check->pMixer.sample ) + { + // skip up to 0.1 seconds of audio + int skip = COM_RandomLong( 0, (long)( 0.1f * check->sfx->cache->rate )); + + S_SetSampleStart( check, sfx->cache, skip ); + break; + } + } +} + +/* +==================== +S_RestoreSound + +Restore a sound effect for the given entity on the given channel +==================== +*/ +void S_RestoreSound( const vec3_t pos, int ent, int chan, sound_t handle, float fvol, float attn, int pitch, int flags, double sample, double end, int wordIndex ) +{ + wavdata_t *pSource; + sfx_t *sfx = NULL; + channel_t *target_chan; + qboolean bIgnore = false; + int vol; + + if( !dma.initialized ) return; + sfx = S_GetSfxByHandle( handle ); + if( !sfx ) return; + + vol = bound( 0, fvol * 255, 255 ); + if( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues + + if( pitch == 0 ) + { + MsgDev( D_WARN, "S_RestoreSound: ( %s ) ignored, called with pitch 0\n", sfx->name ); + return; + } + + // pick a channel to play on + if( chan == CHAN_STATIC ) target_chan = SND_PickStaticChannel( pos, sfx ); + else target_chan = SND_PickDynamicChannel( ent, chan, sfx, &bIgnore ); + + if( !target_chan ) + { + if( !bIgnore ) + Con_DPrintf( S_ERROR "dropped sound \"%s%s\"\n", DEFAULT_SOUNDPATH, sfx->name ); + return; + } + + // spatialize + memset( target_chan, 0, sizeof( *target_chan )); + + VectorCopy( pos, target_chan->origin ); + target_chan->staticsound = ( ent == 0 ) ? true : false; + target_chan->use_loop = (flags & SND_STOP_LOOPING) ? false : true; + target_chan->localsound = (flags & SND_LOCALSOUND) ? true : false; + target_chan->dist_mult = (attn / SND_CLIP_DISTANCE); + target_chan->master_vol = vol; + target_chan->entnum = ent; + target_chan->entchannel = chan; + target_chan->basePitch = pitch; + target_chan->isSentence = false; + target_chan->radius = 0.0f; + target_chan->sfx = sfx; + + // initialize gain due to obscured sound source + target_chan->bfirstpass = true; + target_chan->ob_gain = 0.0f; + target_chan->ob_gain_inc = 0.0f; + target_chan->ob_gain_target = 0.0f; + target_chan->bTraced = false; + + pSource = NULL; + + if( S_TestSoundChar( sfx->name, '!' )) + { + // this is a sentence + // link all words and load the first word + // NOTE: sentence names stored in the cache lookup are + // prepended with a '!'. Sentence names stored in the + // sentence file do not have a leading '!'. + VOX_LoadSound( target_chan, S_SkipSoundChar( sfx->name )); + Q_strncpy( target_chan->name, sfx->name, sizeof( target_chan->name )); + + // not a first word in sentence! + if( wordIndex != 0 ) + { + VOX_FreeWord( target_chan ); // release first loaded word + target_chan->wordIndex = wordIndex; // restore current word + VOX_LoadWord( target_chan ); + + if( target_chan->currentWord ) + { + target_chan->sfx = target_chan->words[target_chan->wordIndex].sfx; + sfx = target_chan->sfx; + pSource = sfx->cache; + } + } + else + { + sfx = target_chan->sfx; + if( sfx ) pSource = sfx->cache; + } + } + else + { + // regular or streamed sound fx + pSource = S_LoadSound( sfx ); + target_chan->name[0] = '\0'; + } + + if( !pSource ) + { + S_FreeChannel( target_chan ); + return; + } + + SND_Spatialize( target_chan ); + + // NOTE: first spatialization may be failed because listener position is invalid at this time + // so we should keep all sounds an actual and waiting for player spawn. + + // apply the sample offests + target_chan->pMixer.sample = sample; + target_chan->pMixer.forcedEndSample = end; + + // Init client entity mouth movement vars + SND_InitMouth( ent, chan ); +} + +/* +================= +S_AmbientSound + +Start playback of a sound, loaded into the static portion of the channel array. +Currently, this should be used for looping ambient sounds, looping sounds +that should not be interrupted until complete, non-creature sentences, +and one-shot ambient streaming sounds. Can also play 'regular' sounds one-shot, +in case designers want to trigger regular game sounds. +Pitch changes playback pitch of wave by % above or below 100. Ignored if pitch == 100 + +NOTE: volume is 0.0 - 1.0 and attenuation is 0.0 - 1.0 when passed in. +================= +*/ +void S_AmbientSound( const vec3_t pos, int ent, sound_t handle, float fvol, float attn, int pitch, int flags ) +{ + channel_t *ch; + wavdata_t *pSource = NULL; + sfx_t *sfx = NULL; + int vol, fvox = 0; + float radius = SND_RADIUS_MAX; + + if( !dma.initialized ) return; + sfx = S_GetSfxByHandle( handle ); + if( !sfx ) return; + + vol = bound( 0, fvol * 255, 255 ); + if( pitch <= 1 ) pitch = PITCH_NORM; // Invasion issues + + if( flags & (SND_STOP|SND_CHANGE_VOL|SND_CHANGE_PITCH)) + { + if( S_AlterChannel( ent, CHAN_STATIC, sfx, vol, pitch, flags )) + return; + if( flags & SND_STOP ) return; + } + + if( pitch == 0 ) + { + MsgDev( D_WARN, "S_AmbientSound: ( %s ) ignored, called with pitch 0\n", sfx->name ); + return; + } + + // pick a channel to play on from the static area + ch = SND_PickStaticChannel( pos, sfx ); + if( !ch ) return; + + VectorCopy( pos, ch->origin ); + ch->entnum = ent; + + CL_GetEntitySpatialization( ch ); + + if( S_TestSoundChar( sfx->name, '!' )) + { + // this is a sentence. link words to play in sequence. + // NOTE: sentence names stored in the cache lookup are + // prepended with a '!'. Sentence names stored in the + // sentence file do not have a leading '!'. + + // link all words and load the first word + VOX_LoadSound( ch, S_SkipSoundChar( sfx->name )); + Q_strncpy( ch->name, sfx->name, sizeof( ch->name )); + sfx = ch->sfx; + if( sfx ) pSource = sfx->cache; + fvox = 1; + } + else + { + // load regular or stream sound + pSource = S_LoadSound( sfx ); + ch->sfx = sfx; + ch->isSentence = false; + ch->name[0] = '\0'; + } + + if( !pSource ) + { + S_FreeChannel( ch ); + return; + } + + // never update positions if source entity is 0 + ch->staticsound = ( ent == 0 ) ? true : false; + ch->use_loop = (flags & SND_STOP_LOOPING) ? false : true; + ch->localsound = (flags & SND_LOCALSOUND) ? true : false; + ch->master_vol = vol; + ch->dist_mult = (attn / SND_CLIP_DISTANCE); + ch->entchannel = CHAN_STATIC; + ch->basePitch = pitch; + ch->radius = radius; + + // initialize gain due to obscured sound source + ch->bfirstpass = true; + ch->ob_gain = 0.0; + ch->ob_gain_inc = 0.0; + ch->ob_gain_target = 0.0; + ch->bTraced = false; + + SND_Spatialize( ch ); +} + +/* +================== +S_StartLocalSound +================== +*/ +void S_StartLocalSound( const char *name, float volume, qboolean reliable ) +{ + sound_t sfxHandle; + int flags = (SND_LOCALSOUND|SND_STOP_LOOPING); + int channel = CHAN_AUTO; + + if( reliable ) channel = CHAN_STATIC; + + if( !dma.initialized ) return; + sfxHandle = S_RegisterSound( name ); + S_StartSound( NULL, s_listener.entnum, channel, sfxHandle, volume, ATTN_NONE, PITCH_NORM, flags ); +} + +/* +================== +S_GetCurrentStaticSounds + +grab all static sounds playing at current channel +================== +*/ +int S_GetCurrentStaticSounds( soundlist_t *pout, int size ) +{ + int sounds_left = size; + int i; + + if( !dma.initialized ) + return 0; + + for( i = MAX_DYNAMIC_CHANNELS; i < total_channels && sounds_left; i++ ) + { + if( channels[i].entchannel == CHAN_STATIC && channels[i].sfx && channels[i].sfx->name[0] ) + { + if( channels[i].isSentence && channels[i].name[0] ) + Q_strncpy( pout->name, channels[i].name, sizeof( pout->name )); + else Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name )); + pout->entnum = channels[i].entnum; + VectorCopy( channels[i].origin, pout->origin ); + pout->volume = (float)channels[i].master_vol / 255.0f; + pout->attenuation = channels[i].dist_mult * SND_CLIP_DISTANCE; + pout->looping = ( channels[i].use_loop && channels[i].sfx->cache->loopStart != -1 ); + pout->pitch = channels[i].basePitch; + pout->channel = channels[i].entchannel; + pout->wordIndex = channels[i].wordIndex; + pout->samplePos = channels[i].pMixer.sample; + pout->forcedEnd = channels[i].pMixer.forcedEndSample; + + sounds_left--; + pout++; + } + } + + return ( size - sounds_left ); +} + +/* +================== +S_GetCurrentStaticSounds + +grab all static sounds playing at current channel +================== +*/ +int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ) +{ + int sounds_left = size; + int i, looped; + + if( !dma.initialized ) + return 0; + + for( i = 0; i < MAX_CHANNELS && sounds_left; i++ ) + { + if( !channels[i].sfx || !channels[i].sfx->name[0] || !Q_stricmp( channels[i].sfx->name, "*default" )) + continue; // don't serialize default sounds + + looped = ( channels[i].use_loop && channels[i].sfx->cache->loopStart != -1 ); + + if( channels[i].entchannel == CHAN_STATIC && looped && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + continue; // never serialize static looped sounds. It will be restoring in game code + + if( channels[i].isSentence && channels[i].name[0] ) + Q_strncpy( pout->name, channels[i].name, sizeof( pout->name )); + else Q_strncpy( pout->name, channels[i].sfx->name, sizeof( pout->name )); + pout->entnum = (channels[i].entnum < 0) ? 0 : channels[i].entnum; + VectorCopy( channels[i].origin, pout->origin ); + pout->volume = (float)channels[i].master_vol / 255.0f; + pout->attenuation = channels[i].dist_mult * SND_CLIP_DISTANCE; + pout->pitch = channels[i].basePitch; + pout->channel = channels[i].entchannel; + pout->wordIndex = channels[i].wordIndex; + pout->samplePos = channels[i].pMixer.sample; + pout->forcedEnd = channels[i].pMixer.forcedEndSample; + pout->looping = looped; + + sounds_left--; + pout++; + } + + return ( size - sounds_left ); +} + +/* +=================== +S_InitAmbientChannels +=================== +*/ +void S_InitAmbientChannels( void ) +{ + int ambient_channel; + channel_t *chan; + + for( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ ) + { + chan = &channels[ambient_channel]; + + chan->staticsound = true; + chan->use_loop = true; + chan->entchannel = CHAN_STATIC; + chan->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE); + chan->basePitch = PITCH_NORM; + } +} + +/* +=================== +S_UpdateAmbientSounds +=================== +*/ +void S_UpdateAmbientSounds( void ) +{ + mleaf_t *leaf; + float vol; + int ambient_channel; + channel_t *chan; + + if( !snd_ambient ) return; + + // calc ambient sound levels + if( !cl.worldmodel ) return; + + leaf = Mod_PointInLeaf( s_listener.origin, cl.worldmodel->nodes ); + + if( !leaf || !s_ambient_level->value ) + { + for( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ ) + channels[ambient_channel].sfx = NULL; + return; + } + + for( ambient_channel = 0; ambient_channel < NUM_AMBIENTS; ambient_channel++ ) + { + chan = &channels[ambient_channel]; + chan->sfx = S_GetSfxByHandle( ambient_sfx[ambient_channel] ); + + if( !chan->sfx ) continue; + + vol = s_ambient_level->value * leaf->ambient_sound_level[ambient_channel]; + if( vol < 0 ) vol = 0; + + // don't adjust volume too fast + if( chan->master_vol < vol ) + { + chan->master_vol += s_listener.frametime * s_ambient_fade->value; + if( chan->master_vol > vol ) chan->master_vol = vol; + } + else if( chan->master_vol > vol ) + { + chan->master_vol -= s_listener.frametime * s_ambient_fade->value; + if( chan->master_vol < vol ) chan->master_vol = vol; + } + + chan->leftvol = chan->rightvol = chan->master_vol; + } +} + +/* +============================================================================= + + SOUND STREAM RAW SAMPLES + +============================================================================= +*/ +/* +=================== +S_FindRawChannel +=================== +*/ +rawchan_t *S_FindRawChannel( int entnum, qboolean create ) +{ + int i, free; + int best, best_time; + size_t raw_samples = 0; + rawchan_t *ch; + + if( !entnum ) return NULL; // world is unused + + // check for replacement sound, or find the best one to replace + best_time = 0x7fffffff; + best = free = -1; + + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + ch = raw_channels[i]; + + if( free < 0 && !ch ) + { + free = i; + } + else if( ch ) + { + int time; + + // exact match + if( ch->entnum == entnum ) + return ch; + + time = ch->s_rawend - paintedtime; + if( time < best_time ) + { + best = i; + best_time = time; + } + } + } + + if( !create ) return NULL; + + if( free >= 0 ) best = free; + if( best < 0 ) return NULL; // no free slots + + if( !raw_channels[best] ) + { + raw_samples = MAX_RAW_SAMPLES; + raw_channels[best] = Mem_Alloc( sndpool, sizeof( *ch ) + sizeof( portable_samplepair_t ) * ( raw_samples - 1 )); + } + + ch = raw_channels[best]; + ch->max_samples = raw_samples; + ch->entnum = entnum; + ch->s_rawend = 0; + + return ch; +} + +/* +=================== +S_RawSamplesStereo +=================== +*/ +static uint S_RawSamplesStereo( portable_samplepair_t *rawsamples, uint rawend, uint max_samples, uint samples, uint rate, word width, word channels, const byte *data ) +{ + uint fracstep, samplefrac; + uint src, dst; + + if( rawend < paintedtime ) + rawend = paintedtime; + + fracstep = ((double) rate / (double)SOUND_DMA_SPEED) * (double)(1 << S_RAW_SAMPLES_PRECISION_BITS); + samplefrac = 0; + + if( width == 2 ) + { + const short *in = (const short *)data; + + if( channels == 2 ) + { + for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS )) + { + dst = rawend++ & ( max_samples - 1 ); + rawsamples[dst].left = in[src*2+0]; + rawsamples[dst].right = in[src*2+1]; + } + } + else + { + for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS )) + { + dst = rawend++ & ( max_samples - 1 ); + rawsamples[dst].left = in[src]; + rawsamples[dst].right = in[src]; + } + } + } + else + { + if( channels == 2 ) + { + const char *in = (const char *)data; + + for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS )) + { + dst = rawend++ & ( max_samples - 1 ); + rawsamples[dst].left = in[src*2+0] << 8; + rawsamples[dst].right = in[src*2+1] << 8; + } + } + else + { + for( src = 0; src < samples; samplefrac += fracstep, src = ( samplefrac >> S_RAW_SAMPLES_PRECISION_BITS )) + { + dst = rawend++ & ( max_samples - 1 ); + rawsamples[dst].left = ( data[src] - 128 ) << 8; + rawsamples[dst].right = ( data[src] - 128 ) << 8; + } + } + } + + return rawend; +} + +/* +=================== +S_RawEntSamples +=================== +*/ +static void S_RawEntSamples( int entnum, uint samples, uint rate, word width, word channels, const byte *data, int snd_vol ) +{ + rawchan_t *ch; + + if( snd_vol < 0 ) + snd_vol = 0; + + if( !( ch = S_FindRawChannel( entnum, true ))) + return; + + ch->master_vol = snd_vol; + ch->dist_mult = (ATTN_NONE / SND_CLIP_DISTANCE); + ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data ); + ch->leftvol = ch->rightvol = snd_vol; +} + +/* +=================== +S_RawSamples +=================== +*/ +void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ) +{ + int snd_vol; + + if( entnum < 0 ) snd_vol = 256; // bg track or movie track + if( snd_vol < 0 ) snd_vol = 0; // fixup negative values + + S_RawEntSamples( entnum, samples, rate, width, channels, data, snd_vol ); +} + +/* +=================== +S_PositionedRawSamples +=================== +*/ +static void S_PositionedRawSamples( int entnum, float fvol, float attn, uint samples, uint rate, word width, word channels, const byte *data ) +{ + rawchan_t *ch; + + if( entnum < 0 || entnum >= GI->max_edicts ) + return; + + if( !( ch = S_FindRawChannel( entnum, true ))) + return; + + ch->master_vol = bound( 0, fvol * 255, 255 ); + ch->dist_mult = (attn / SND_CLIP_DISTANCE); + ch->s_rawend = S_RawSamplesStereo( ch->rawsamples, ch->s_rawend, ch->max_samples, samples, rate, width, channels, data ); +} + +/* +=================== +S_GetRawSamplesLength +=================== +*/ +uint S_GetRawSamplesLength( int entnum ) +{ + rawchan_t *ch; + + if( !( ch = S_FindRawChannel( entnum, false ))) + return 0; + + return ch->s_rawend <= paintedtime ? 0 : (float)(ch->s_rawend - paintedtime) * DMA_MSEC_PER_SAMPLE; +} + +/* +=================== +S_ClearRawChannel +=================== +*/ +void S_ClearRawChannel( int entnum ) +{ + rawchan_t *ch; + + if( !( ch = S_FindRawChannel( entnum, false ))) + return; + + ch->s_rawend = 0; +} + +/* +=================== +S_FreeIdleRawChannels + +Free raw channel that have been idling for too long. +=================== +*/ +static void S_FreeIdleRawChannels( void ) +{ + int i; + + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + rawchan_t *ch = raw_channels[i]; + + if( !ch ) continue; + + if( ch->s_rawend >= paintedtime ) + continue; + + if(( paintedtime - ch->s_rawend ) / SOUND_DMA_SPEED >= S_RAW_SOUND_IDLE_SEC ) + { + raw_channels[i] = NULL; + Mem_Free( ch ); + } + } +} + +/* +=================== +S_ClearRawChannels +=================== +*/ +static void S_ClearRawChannels( void ) +{ + int i; + + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + rawchan_t *ch = raw_channels[i]; + + if( !ch ) continue; + ch->s_rawend = 0; + } +} + +/* +=================== +S_SpatializeRawChannels +=================== +*/ +static void S_SpatializeRawChannels( void ) +{ + int i; + + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + rawchan_t *ch = raw_channels[i]; + vec3_t source_vec; + float dist, dot; + + if( !ch ) continue; + + if( ch->s_rawend < paintedtime ) + { + ch->leftvol = ch->rightvol = 0; + continue; + } + + // spatialization + if( !S_IsClient( ch->entnum ) && ch->dist_mult && ch->entnum >= 0 && ch->entnum < GI->max_edicts ) + { + if( !CL_GetMovieSpatialization( ch )) + { + // origin is null and entity not exist on client + ch->leftvol = ch->rightvol = 0; + } + else + { + VectorSubtract( ch->origin, s_listener.origin, source_vec ); + + // normalize source_vec and get distance from listener to source + dist = VectorNormalizeLength( source_vec ); + dot = DotProduct( s_listener.right, source_vec ); + + // for sounds with a radius, spatialize left/right evenly within the radius + if( ch->radius > 0 && dist < ch->radius ) + { + float interval = ch->radius * 0.5f; + float blend = dist - interval; + + if( blend < 0 ) blend = 0; + blend /= interval; + + // blend is 0.0 - 1.0, from 50% radius -> 100% radius + // at radius * 0.5, dot is 0 (ie: sound centered left/right) + // at radius dot == dot + dot *= blend; + } + + // don't pan sounds with no attenuation + if( ch->dist_mult <= 0.0f ) dot = 0.0f; + + // fill out channel volumes for single location + S_SpatializeChannel( &ch->leftvol, &ch->rightvol, ch->master_vol, 1.0f, dot, dist * ch->dist_mult ); + } + } + else + { + ch->leftvol = ch->rightvol = ch->master_vol; + } + } +} + +/* +=================== +S_FreeRawChannels +=================== +*/ +static void S_FreeRawChannels( void ) +{ + int i; + + // free raw samples + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + if( raw_channels[i] ) + Mem_Free( raw_channels[i] ); + } + + memset( raw_channels, 0, sizeof( raw_channels )); +} + +//============================================================================= + +/* +================== +S_ClearBuffer +================== +*/ +void S_ClearBuffer( void ) +{ + S_ClearRawChannels(); + + SNDDMA_BeginPainting (); + if( dma.buffer ) memset( dma.buffer, 0, dma.samples * 2 ); + SNDDMA_Submit (); + + MIX_ClearAllPaintBuffers( PAINTBUFFER_SIZE, true ); +} + +/* +================== +S_StopSound + +stop all sounds for entity on a channel. +================== +*/ +void S_StopSound( int entnum, int channel, const char *soundname ) +{ + sfx_t *sfx; + + if( !dma.initialized ) return; + sfx = S_FindName( soundname, NULL ); + S_AlterChannel( entnum, channel, sfx, 0, 0, SND_STOP ); +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_StopAllSounds( qboolean ambient ) +{ + int i; + + if( !dma.initialized ) return; + total_channels = MAX_DYNAMIC_CHANNELS; // no statics + + for( i = 0; i < MAX_CHANNELS; i++ ) + { + if( !channels[i].sfx ) continue; + S_FreeChannel( &channels[i] ); + } + + DSP_ClearState(); + + // clear all the channels + memset( channels, 0, sizeof( channels )); + + // restart the ambient sounds + if( ambient ) S_InitAmbientChannels (); + + S_ClearBuffer (); + + // clear any remaining soundfade + memset( &soundfade, 0, sizeof( soundfade )); +} + +//============================================================================= +void S_UpdateChannels( void ) +{ + uint endtime; + int samps; + + SNDDMA_BeginPainting(); + + if( !dma.buffer ) return; + + // updates DMA time + soundtime = SNDDMA_GetSoundtime(); + + // soundtime - total samples that have been played out to hardware at dmaspeed + // paintedtime - total samples that have been mixed at speed + // endtime - target for samples in mixahead buffer at speed + endtime = soundtime + s_mixahead->value * SOUND_DMA_SPEED; + samps = dma.samples >> 1; + + if((int)(endtime - soundtime) > samps ) + endtime = soundtime + samps; + + if(( endtime - paintedtime ) & 0x3 ) + { + // the difference between endtime and painted time should align on + // boundaries of 4 samples. this is important when upsampling from 11khz -> 44khz. + endtime -= ( endtime - paintedtime ) & 0x3; + } + + MIX_PaintChannels( endtime ); + + SNDDMA_Submit(); +} + +/* +================= +S_ExtraUpdate + +Don't let sound skip if going slow +================= +*/ +void S_ExtraUpdate( void ) +{ + if( !dma.initialized ) return; + S_UpdateChannels (); +} + +/* +============ +SND_UpdateSound + +Called once each time through the main loop +============ +*/ +void SND_UpdateSound( void ) +{ + int i, j, total; + channel_t *ch, *combine; + con_nprint_t info; + + if( !dma.initialized ) return; + + // if the loading plaque is up, clear everything + // out to make sure we aren't looping a dirty + // dma buffer while loading + // update any client side sound fade + S_UpdateSoundFade(); + + // release raw-channels that no longer used more than 10 secs + S_FreeIdleRawChannels(); + + s_listener.entnum = cl.viewentity; // can be camera entity too + s_listener.frametime = (cl.time - cl.oldtime); + s_listener.waterlevel = cl.local.waterlevel; + s_listener.active = CL_IsInGame(); + s_listener.inmenu = CL_IsInMenu(); + s_listener.paused = cl.paused; + + VectorCopy( RI.vieworg, s_listener.origin ); + VectorCopy( cl.simvel, s_listener.velocity ); + AngleVectors( RI.viewangles, s_listener.forward, s_listener.right, s_listener.up ); + + if( cl.worldmodel != NULL ) + Mod_FatPVS( s_listener.origin, FATPHS_RADIUS, s_listener.pasbytes, world.visbytes, false, !s_phs->value ); + + // update general area ambient sound sources + S_UpdateAmbientSounds(); + + combine = NULL; + + // update spatialization for static and dynamic sounds + for( i = NUM_AMBIENTS, ch = channels + NUM_AMBIENTS; i < total_channels; i++, ch++ ) + { + if( !ch->sfx ) continue; + SND_Spatialize( ch ); // respatialize channel + + if( !ch->leftvol && !ch->rightvol ) + continue; + + // try to combine static sounds with a previous channel of the same + // sound effect so we don't mix five torches every frame + // g-cont: perfomance option, probably kill stereo effect in most cases + if( i >= MAX_DYNAMIC_CHANNELS && s_combine_sounds->value ) + { + // see if it can just use the last one + if( combine && combine->sfx == ch->sfx ) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + continue; + } + + // search for one + combine = channels + MAX_DYNAMIC_CHANNELS; + + for( j = MAX_DYNAMIC_CHANNELS; j < i; j++, combine++ ) + { + if( combine->sfx == ch->sfx ) + break; + } + + if( j == total_channels ) + { + combine = NULL; + } + else + { + if( combine != ch ) + { + combine->leftvol += ch->leftvol; + combine->rightvol += ch->rightvol; + ch->leftvol = ch->rightvol = 0; + } + continue; + } + } + } + + S_SpatializeRawChannels(); + + // debugging output + if( s_show->value ) + { + info.color[0] = 1.0f; + info.color[1] = 0.6f; + info.color[2] = 0.0f; + info.time_to_live = 0.5f; + + for( i = 0, total = 1, ch = channels; i < MAX_CHANNELS; i++, ch++ ) + { + if( ch->sfx && ( ch->leftvol || ch->rightvol )) + { + info.index = total; + Con_NXPrintf( &info, "chan %i, pos (%.f %.f %.f) ent %i, lv%3i rv%3i %s\n", + i, ch->origin[0], ch->origin[1], ch->origin[2], ch->entnum, ch->leftvol, ch->rightvol, ch->sfx->name ); + total++; + } + } + + // to differentiate modes + if( s_cull->value && s_phs->value ) + VectorSet( info.color, 0.0f, 1.0f, 0.0f ); + else if( s_phs->value ) + VectorSet( info.color, 1.0f, 1.0f, 0.0f ); + else if( s_cull->value ) + VectorSet( info.color, 1.0f, 0.0f, 0.0f ); + else VectorSet( info.color, 1.0f, 1.0f, 1.0f ); + info.index = 0; + + Con_NXPrintf( &info, "room_type: %i ----(%i)---- painted: %i\n", idsp_room, total - 1, paintedtime ); + } + + S_StreamBackgroundTrack (); + S_StreamSoundTrack (); + + // mix some sound + S_UpdateChannels (); +} + +/* +=============================================================================== + +console functions + +=============================================================================== +*/ +void S_Play_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "play \n" ); + return; + } + + S_StartLocalSound( Cmd_Argv( 1 ), VOL_NORM, false ); +} + +void S_PlayVol_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "playvol \n" ); + return; + } + + S_StartLocalSound( Cmd_Argv( 1 ), Q_atof( Cmd_Argv( 2 )), false ); +} + +void S_Say_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "speak \n" ); + return; + } + + S_StartLocalSound( Cmd_Argv( 1 ), 1.0f, false ); +} + +void S_SayReliable_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "spk \n" ); + return; + } + + S_StartLocalSound( Cmd_Argv( 1 ), 1.0f, true ); +} + +/* +================= +S_Music_f +================= +*/ +void S_Music_f( void ) +{ + int c = Cmd_Argc(); + + // run background track + if( c == 1 ) + { + // blank name stopped last track + S_StopBackgroundTrack(); + } + else if( c == 2 ) + { + string intro, main, track; + char *ext[] = { "mp3", "wav" }; + int i; + + Q_strncpy( track, Cmd_Argv( 1 ), sizeof( track )); + Q_snprintf( intro, sizeof( intro ), "%s_intro", Cmd_Argv( 1 )); + Q_snprintf( main, sizeof( main ), "%s_main", Cmd_Argv( 1 )); + + for( i = 0; i < 2; i++ ) + { + const char *intro_path = va( "media/%s.%s", intro, ext[i] ); + const char *main_path = va( "media/%s.%s", main, ext[i] ); + + if( FS_FileExists( intro_path, false ) && FS_FileExists( main_path, false )) + { + // combined track with introduction and main loop theme + S_StartBackgroundTrack( intro, main, 0, false ); + break; + } + else if( FS_FileExists( va( "media/%s.%s", track, ext[i] ), false )) + { + // single non-looped theme + S_StartBackgroundTrack( track, NULL, 0, false ); + break; + } + } + + } + else if( c == 3 ) + { + S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), 0, false ); + } + else if( c == 4 && Q_atoi( Cmd_Argv( 3 )) != 0 ) + { + // restore command for singleplayer: all arguments are valid + S_StartBackgroundTrack( Cmd_Argv( 1 ), Cmd_Argv( 2 ), Q_atoi( Cmd_Argv( 3 )), false ); + } + else Con_Printf( S_USAGE "music [loopfile]\n" ); +} + +/* +================= +S_StopSound_f +================= +*/ +void S_StopSound_f( void ) +{ + S_StopAllSounds( true ); +} + +/* +================= +S_SoundFade_f +================= +*/ +void S_SoundFade_f( void ) +{ + int c = Cmd_Argc(); + float fadeTime = 5.0f; + + if( c == 2 ) + fadeTime = bound( 1.0f, atof( Cmd_Argv( 1 )), 60.0f ); + + S_FadeClientVolume( 100.0f, fadeTime, 1.0f, 0.0f ); + snd_fade_sequence = true; +} + +/* +================= +S_SoundInfo_f +================= +*/ +void S_SoundInfo_f( void ) +{ + Con_Printf( "Audio: DirectSound\n" ); + Con_Printf( "%5d channel(s)\n", 2 ); + Con_Printf( "%5d samples\n", dma.samples ); + Con_Printf( "%5d bits/sample\n", 16 ); + Con_Printf( "%5d bytes/sec\n", SOUND_DMA_SPEED ); + Con_Printf( "%5d total_channels\n", total_channels ); + + S_PrintBackgroundTrackState (); +} + +/* +================ +S_Init +================ +*/ +qboolean S_Init( void ) +{ + if( Sys_CheckParm( "-nosound" )) + { + MsgDev( D_INFO, "Audio: Disabled\n" ); + return false; + } + + s_volume = Cvar_Get( "volume", "0.7", FCVAR_ARCHIVE, "sound volume" ); + s_musicvolume = Cvar_Get( "MP3Volume", "1.0", FCVAR_ARCHIVE, "background music volume" ); + s_mixahead = Cvar_Get( "_snd_mixahead", "0.12", 0, "how much sound to mix ahead of time" ); + s_show = Cvar_Get( "s_show", "0", FCVAR_ARCHIVE, "show playing sounds" ); + s_lerping = Cvar_Get( "s_lerping", "0", FCVAR_ARCHIVE, "apply interpolation to sound output" ); + s_ambient_level = Cvar_Get( "ambient_level", "0.3", FCVAR_ARCHIVE, "volume of environment noises (water and wind)" ); + s_ambient_fade = Cvar_Get( "ambient_fade", "1000", FCVAR_ARCHIVE, "rate of volume fading when client is moving" ); + s_combine_sounds = Cvar_Get( "s_combine_channels", "1", FCVAR_ARCHIVE, "combine channels with same sounds" ); + snd_foliage_db_loss = Cvar_Get( "snd_foliage_db_loss", "4", 0, "foliage loss factor" ); + snd_gain_max = Cvar_Get( "snd_gain_max", "1", 0, "gain maximal threshold" ); + snd_gain_min = Cvar_Get( "snd_gain_min", "0.01", 0, "gain minimal threshold" ); + s_refdist = Cvar_Get( "s_refdist", "36", 0, "soundlevel reference distance" ); + s_refdb = Cvar_Get( "s_refdb", "60", 0, "soundlevel refernce dB" ); + snd_gain = Cvar_Get( "snd_gain", "1", 0, "sound default gain" ); + s_cull = Cvar_Get( "s_cull", "0", FCVAR_ARCHIVE, "cull sounds by geometry" ); + s_test = Cvar_Get( "s_test", "0", 0, "engine developer cvar for quick testing new features" ); + s_phs = Cvar_Get( "s_phs", "0", FCVAR_ARCHIVE, "cull sounds by PHS" ); + + Cmd_AddCommand( "play", S_Play_f, "playing a specified sound file" ); + Cmd_AddCommand( "playvol", S_PlayVol_f, "playing a specified sound file with specified volume" ); + Cmd_AddCommand( "stopsound", S_StopSound_f, "stop all sounds" ); + Cmd_AddCommand( "music", S_Music_f, "starting a background track" ); + Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" ); + Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" ); + Cmd_AddCommand( "s_fade", S_SoundFade_f, "fade all sounds then stop all" ); + Cmd_AddCommand( "+voicerecord", Cmd_Null_f, "start voice recording (non-implemented)" ); + Cmd_AddCommand( "-voicerecord", Cmd_Null_f, "stop voice recording (non-implemented)" ); + Cmd_AddCommand( "spk", S_SayReliable_f, "reliable play a specified sententce" ); + Cmd_AddCommand( "speak", S_Say_f, "playing a specified sententce" ); + + if( !SNDDMA_Init( host.hWnd )) + { + MsgDev( D_INFO, "S_Init: sound system can't be initialized\n" ); + return false; + } + + sndpool = Mem_AllocPool( "Sound Zone" ); + soundtime = 0; + paintedtime = 0; + + // clear ambient sounds + memset( ambient_sfx, 0, sizeof( ambient_sfx )); + + MIX_InitAllPaintbuffers (); + SX_Init (); + S_InitScaletable (); + S_StopAllSounds ( true ); + VOX_Init (); + + return true; +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= +void S_Shutdown( void ) +{ + if( !dma.initialized ) return; + + Cmd_RemoveCommand( "play" ); + Cmd_RemoveCommand( "playvol" ); + Cmd_RemoveCommand( "stopsound" ); + Cmd_RemoveCommand( "music" ); + Cmd_RemoveCommand( "soundlist" ); + Cmd_RemoveCommand( "s_info" ); + Cmd_RemoveCommand( "s_fade" ); + Cmd_RemoveCommand( "+voicerecord" ); + Cmd_RemoveCommand( "-voicerecord" ); + Cmd_RemoveCommand( "speak" ); + Cmd_RemoveCommand( "spk" ); + + S_StopAllSounds (false); + S_FreeRawChannels (); + S_FreeSounds (); + VOX_Shutdown (); + SX_Free (); + + SNDDMA_Shutdown (); + MIX_FreeAllPaintbuffers (); + Mem_FreePool( &sndpool ); +} \ No newline at end of file diff --git a/engine/client/s_mix.c b/engine/client/s_mix.c new file mode 100644 index 00000000..d1368cb2 --- /dev/null +++ b/engine/client/s_mix.c @@ -0,0 +1,1062 @@ +/* +s_mix.c - portable code to mix sounds +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include "client.h" + +#define IPAINTBUFFER 0 +#define IROOMBUFFER 1 +#define ISTREAMBUFFER 2 + +#define FILTERTYPE_NONE 0 +#define FILTERTYPE_LINEAR 1 +#define FILTERTYPE_CUBIC 2 + +#define CCHANVOLUMES 2 + +#define SND_SCALE_BITS 7 +#define SND_SCALE_SHIFT (8 - SND_SCALE_BITS) +#define SND_SCALE_LEVELS (1 << SND_SCALE_BITS) + +portable_samplepair_t *g_curpaintbuffer; +portable_samplepair_t streambuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t facingbuffer[(PAINTBUFFER_SIZE+1)]; +portable_samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)]; +paintbuffer_t paintbuffers[CPAINTBUFFERS]; + +int snd_scaletable[SND_SCALE_LEVELS][256]; + +void S_InitScaletable( void ) +{ + int i, j; + + for( i = 0; i < SND_SCALE_LEVELS; i++ ) + { + for( j = 0; j < 256; j++ ) + snd_scaletable[i][j] = ((signed char)j) * i * (1<> 1) - 1); + + while( lpaintedtime < endtime ) + { + // handle recirculating buffer issues + lpos = lpaintedtime & sampleMask; + + snd_out = (short *)pbuf + (lpos << 1); + + snd_linear_count = (dma.samples>>1) - lpos; + if( lpaintedtime + snd_linear_count > endtime ) + snd_linear_count = endtime - lpaintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + for( i = 0; i < snd_linear_count; i += 2 ) + { + val = (snd_p[i+0] * 256) >> 8; + + if( val > 0x7fff ) snd_out[i+0] = 0x7fff; + else if( val < (short)0x8000 ) + snd_out[i+0] = (short)0x8000; + else snd_out[i+0] = val; + + val = (snd_p[i+1] * 256) >> 8; + if( val > 0x7fff ) snd_out[i+1] = 0x7fff; + else if( val < (short)0x8000 ) + snd_out[i+1] = (short)0x8000; + else snd_out[i+1] = val; + } + + snd_p += snd_linear_count; + lpaintedtime += (snd_linear_count >> 1); + } +} + +//=============================================================================== +// Mix buffer (paintbuffer) management routines +//=============================================================================== +// Activate a paintbuffer. All active paintbuffers are mixed in parallel within +// MIX_MixChannelsToPaintbuffer, according to flags +_inline void MIX_ActivatePaintbuffer( int ipaintbuffer ) +{ + Assert( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].factive = true; +} + +// don't mix into this paintbuffer +_inline void MIX_DeactivatePaintbuffer( int ipaintbuffer ) +{ + Assert( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].factive = false; +} + +_inline void MIX_SetCurrentPaintbuffer( int ipaintbuffer ) +{ + Assert( ipaintbuffer < CPAINTBUFFERS ); + g_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf; + Assert( g_curpaintbuffer != NULL ); +} + +_inline int MIX_GetCurrentPaintbufferIndex( void ) +{ + int i; + + for( i = 0; i < CPAINTBUFFERS; i++ ) + { + if( g_curpaintbuffer == paintbuffers[i].pbuf ) + return i; + } + return 0; +} + +_inline paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) +{ + int ipaint = MIX_GetCurrentPaintbufferIndex(); + + Assert( ipaint < CPAINTBUFFERS ); + return &paintbuffers[ipaint]; +} + +// Don't mix into any paintbuffers +_inline void MIX_DeactivateAllPaintbuffers( void ) +{ + int i; + + for( i = 0; i < CPAINTBUFFERS; i++ ) + paintbuffers[i].factive = false; +} + +// set upsampling filter indexes back to 0 +_inline void MIX_ResetPaintbufferFilterCounters( void ) +{ + int i; + + for( i = 0; i < CPAINTBUFFERS; i++ ) + paintbuffers[i].ifilter = FILTERTYPE_NONE; +} + +_inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) +{ + Assert( ipaintbuffer < CPAINTBUFFERS ); + paintbuffers[ipaintbuffer].ifilter = 0; +} + +// return pointer to front paintbuffer pbuf, given index +_inline portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer ) +{ + Assert( ipaintbuffer < CPAINTBUFFERS ); + return paintbuffers[ipaintbuffer].pbuf; +} + +_inline paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint ) +{ + Assert( ipaint < CPAINTBUFFERS ); + return &paintbuffers[ipaint]; +} + +void MIX_FreeAllPaintbuffers( void ) +{ + // clear paintbuffer structs + memset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t )); +} + +// Initialize paintbuffers array, set current paint buffer to main output buffer IPAINTBUFFER +void MIX_InitAllPaintbuffers( void ) +{ + // clear paintbuffer structs + memset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t )); + + paintbuffers[IPAINTBUFFER].pbuf = paintbuffer; + paintbuffers[IROOMBUFFER].pbuf = roombuffer; + paintbuffers[ISTREAMBUFFER].pbuf = streambuffer; + + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); +} + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ +void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) +{ + int *lscale, *rscale; + int i, data; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for( i = 0; i < outCount; i++ ) + { + data = pData[i]; + pbuf[i].left += lscale[data]; + pbuf[i].right += rscale[data]; + } +} + +void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount ) +{ + int *lscale, *rscale; + uint left, right; + word *data; + int i; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + data = (word *)pData; + + for( i = 0; i < outCount; i++, data++ ) + { + left = (byte)((*data & 0x00FF)); + right = (byte)((*data & 0xFF00) >> 8); + pbuf[i].left += lscale[left]; + pbuf[i].right += rscale[right]; + } +} + +void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) +{ + int left, right; + int i, data; + + for( i = 0; i < outCount; i++ ) + { + data = pData[i]; + left = ( data * volume[0]) >> 8; + right = (data * volume[1]) >> 8; + pbuf[i].left += left; + pbuf[i].right += right; + } +} + +void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount ) +{ + uint *data; + int left, right; + int i; + + data = (uint *)pData; + + for( i = 0; i < outCount; i++, data++ ) + { + left = (signed short)((*data & 0x0000FFFF)); + right = (signed short)((*data & 0xFFFF0000) >> 16); + + left = (left * volume[0]) >> 8; + right = (right * volume[1]) >> 8; + + pbuf[i].left += left; + pbuf[i].right += right; + } +} + +void S_Mix8MonoTimeCompress( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) +{ +} + +void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress ) +{ + int i, sampleIndex = 0; + uint sampleFrac = inputOffset; + int *lscale, *rscale; + + if( timecompress != 0 ) + { + S_Mix8MonoTimeCompress( pbuf, volume, pData, inputOffset, rateScale, outCount, timecompress ); +// return; + } + + // Not using pitch shift? + if( rateScale == FIX( 1 )) + { + S_PaintMonoFrom8( pbuf, volume, pData, outCount ); + return; + } + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for( i = 0; i < outCount; i++ ) + { + pbuf[i].left += lscale[pData[sampleIndex]]; + pbuf[i].right += rscale[pData[sampleIndex]]; + sampleFrac += rateScale; + sampleIndex += FIX_INTPART( sampleFrac ); + sampleFrac = FIX_FRACPART( sampleFrac ); + } +} + +void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount ) +{ + int i, sampleIndex = 0; + uint sampleFrac = inputOffset; + int *lscale, *rscale; + + // Not using pitch shift? + if( rateScale == FIX( 1 )) + { + S_PaintStereoFrom8( pbuf, volume, pData, outCount ); + return; + } + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for( i = 0; i < outCount; i++ ) + { + pbuf[i].left += lscale[pData[sampleIndex+0]]; + pbuf[i].right += rscale[pData[sampleIndex+1]]; + sampleFrac += rateScale; + sampleIndex += FIX_INTPART( sampleFrac )<<1; + sampleFrac = FIX_FRACPART( sampleFrac ); + } +} + +void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) +{ + int i, sampleIndex = 0; + uint sampleFrac = inputOffset; + + // Not using pitch shift? + if( rateScale == FIX( 1 )) + { + S_PaintMonoFrom16( pbuf, volume, pData, outCount ); + return; + } + + for( i = 0; i < outCount; i++ ) + { + pbuf[i].left += (volume[0] * (int)( pData[sampleIndex] ))>>8; + pbuf[i].right += (volume[1] * (int)( pData[sampleIndex] ))>>8; + sampleFrac += rateScale; + sampleIndex += FIX_INTPART( sampleFrac ); + sampleFrac = FIX_FRACPART( sampleFrac ); + } +} + +void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount ) +{ + int i, sampleIndex = 0; + uint sampleFrac = inputOffset; + + // Not using pitch shift? + if( rateScale == FIX( 1 )) + { + S_PaintStereoFrom16( pbuf, volume, pData, outCount ); + return; + } + + for( i = 0; i < outCount; i++ ) + { + pbuf[i].left += (volume[0] * (int)( pData[sampleIndex+0] ))>>8; + pbuf[i].right += (volume[1] * (int)( pData[sampleIndex+1] ))>>8; + sampleFrac += rateScale; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount, int timecompress ) +{ + int pvol[CCHANVOLUMES]; + paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); + wavdata_t *pSource = pChannel->sfx->cache; + portable_samplepair_t *pbuf; + + Assert( pSource != NULL ); + + pvol[0] = bound( 0, pChannel->leftvol, 255 ); + pvol[1] = bound( 0, pChannel->rightvol, 255 ); + pbuf = ppaint->pbuf + outputOffset; + + if( pSource->channels == 1 ) + { + if( pSource->width == 1 ) + S_Mix8Mono( pbuf, pvol, (char *)pData, inputOffset, fracRate, outCount, timecompress ); + else S_Mix16Mono( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount ); + } + else + { + if( pSource->width == 1 ) + S_Mix8Stereo( pbuf, pvol, (char *)pData, inputOffset, fracRate, outCount ); + else S_Mix16Stereo( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount ); + } +} + +int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outRate, int outOffset, int timeCompress ) +{ + // save this to compute total output + int startingOffset = outOffset; + float inputRate = ( pChannel->pitch * pChannel->sfx->cache->rate ); + float rate = inputRate / outRate; + + // shouldn't be playing this if finished, but return if we are + if( pChannel->pMixer.finished ) + return 0; + + // If we are terminating this wave prematurely, then make sure we detect the limit + if( pChannel->pMixer.forcedEndSample ) + { + // how many total input samples will we need? + int samplesRequired = (int)(sampleCount * rate); + + // will this hit the end? + if( pChannel->pMixer.sample + samplesRequired >= pChannel->pMixer.forcedEndSample ) + { + // yes, mark finished and truncate the sample request + pChannel->pMixer.finished = true; + sampleCount = (int)((pChannel->pMixer.forcedEndSample - pChannel->pMixer.sample) / rate ); + } + } + + while( sampleCount > 0 ) + { + int availableSamples, outSampleCount; + wavdata_t *pSource = pChannel->sfx->cache; + qboolean use_loop = pChannel->use_loop; + char *pData = NULL; + double sampleFrac; + int i, j; + + // compute number of input samples required + double end = pChannel->pMixer.sample + rate * sampleCount; + int inputSampleCount = (int)(ceil( end ) - floor( pChannel->pMixer.sample )); + + availableSamples = S_GetOutputData( pSource, &pData, pChannel->pMixer.sample, inputSampleCount, use_loop ); + + // none available, bail out + if( !availableSamples ) break; + + sampleFrac = pChannel->pMixer.sample - floor( pChannel->pMixer.sample ); + + if( availableSamples < inputSampleCount ) + { + // how many samples are there given the number of input samples and the rate. + outSampleCount = (int)ceil(( availableSamples - sampleFrac ) / rate ); + } + else + { + outSampleCount = sampleCount; + } + + // Verify that we won't get a buffer overrun. + Assert( floor( sampleFrac + rate * ( outSampleCount - 1 )) <= availableSamples ); + + // save current paintbuffer + j = MIX_GetCurrentPaintbufferIndex(); + + for( i = 0; i < CPAINTBUFFERS; i++ ) + { + if( !paintbuffers[i].factive ) + continue; + + // mix chan into all active paintbuffers + MIX_SetCurrentPaintbuffer( i ); + + S_MixChannel( pChannel, pData, outOffset, FIX_FLOAT( sampleFrac ), FIX_FLOAT( rate ), outSampleCount, timeCompress ); + } + + MIX_SetCurrentPaintbuffer( j ); + + pChannel->pMixer.sample += outSampleCount * rate; + outOffset += outSampleCount; + sampleCount -= outSampleCount; + } + + // Did we run out of samples? if so, mark finished + if( sampleCount > 0 ) + { + pChannel->pMixer.finished = true; + } + + // total number of samples mixed !!! at the output clock rate !!! + return outOffset - startingOffset; +} + +qboolean S_ShouldContinueMixing( channel_t *ch ) +{ + if( ch->isSentence ) + { + if( ch->currentWord ) + return true; + return false; + } + + return !ch->pMixer.finished; +} + +// Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached. +// endtime: time in 44khz samples to mix +// rate: ignore samples which are not natively at this rate (for multipass mixing/filtering) +// if rate == SOUND_ALL_RATES then mix all samples this pass +// flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry' +// outputRate: target mix rate for all samples. Note, if outputRate = SOUND_DMA_SPEED, then +// this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed. +// if( endtime - paintedtime ) is not aligned on boundaries of 4, +// we'll miss data if outputRate < SOUND_DMA_SPEED! +void MIX_MixChannelsToPaintbuffer( int endtime, int rate, int outputRate ) +{ + channel_t *ch; + wavdata_t *pSource; + int i, sampleCount; + qboolean bZeroVolume; + + // mix each channel into paintbuffer + ch = channels; + + // validate parameters + Assert( outputRate <= SOUND_DMA_SPEED ); + + // make sure we're not discarding data + Assert( !(( endtime - paintedtime ) & 0x3 ) || ( outputRate == SOUND_DMA_SPEED )); + + // 44k: try to mix this many samples at outputRate + sampleCount = ( endtime - paintedtime ) / ( SOUND_DMA_SPEED / outputRate ); + + if( sampleCount <= 0 ) return; + + for( i = 0; i < total_channels; i++, ch++ ) + { + if( !ch->sfx ) continue; + + // NOTE: background map is allow both type sounds: menu and game + if( !cl.background ) + { + if( cls.key_dest == key_console && ch->localsound ) + { + // play, playvol + } + else if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound ) + { + // play only local sounds, keep pause for other + continue; + } + else if( !s_listener.inmenu && !s_listener.active && !ch->staticsound ) + { + // play only ambient sounds, keep pause for other + continue; + } + } + else if( cls.key_dest == key_console ) + continue; // silent mode in console + + pSource = S_LoadSound( ch->sfx ); + + // Don't mix sound data for sounds with zero volume. If it's a non-looping sound, + // just remove the sound when its volume goes to zero. + bZeroVolume = !ch->leftvol && !ch->rightvol; + + if( !bZeroVolume ) + { + // this values matched with GoldSrc + if( ch->leftvol < 8 && ch->rightvol < 8 ) + bZeroVolume = true; + } + + if( !pSource || ( bZeroVolume && pSource->loopStart == -1 )) + { + if( !pSource ) + { + S_FreeChannel( ch ); + continue; + } + } + else if( bZeroVolume ) + { + continue; + } + + // multipass mixing - only mix samples of specified sample rate + switch( rate ) + { + case SOUND_11k: + case SOUND_22k: + case SOUND_44k: + if( rate != pSource->rate ) + continue; + break; + default: break; + } + + // get playback pitch + if( ch->isSentence ) + ch->pitch = VOX_ModifyPitch( ch, ch->basePitch * 0.01f ); + else ch->pitch = ch->basePitch * 0.01f; + + if( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE )) + { + if( pSource->width == 1 ) + SND_MoveMouth8( ch, pSource, sampleCount ); + else SND_MoveMouth16( ch, pSource, sampleCount ); + } + + // mix channel to all active paintbuffers. + // NOTE: must be called once per channel only - consecutive calls retrieve additional data. + if( ch->isSentence ) + VOX_MixDataToDevice( ch, sampleCount, outputRate, 0 ); + else S_MixDataToDevice( ch, sampleCount, outputRate, 0, 0 ); + + if( !S_ShouldContinueMixing( ch )) + { + S_FreeChannel( ch ); + } + } +} + +// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer +_inline portable_samplepair_t *S_GetNextpFilter( int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem ) +{ + // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples + if( i == -1 ) return (&(pfiltermem[0])); + if( i == 0 ) return (&(pfiltermem[1])); + if( i == 1 ) return (&(pfiltermem[2])); + + // return from paintbuffer, where samples are doubled. + // even samples are to be replaced with interpolated value. + return (&(pbuffer[(i-2) * 2 + 1])); +} + +// pass forward over passed in buffer and cubic interpolate all odd samples +// pbuffer: buffer to filter (in place) +// prevfilter: filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC +// if NULL then perform no filtering. +// count: how many samples to upsample. will become count*2 samples in buffer, in place. + +void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +{ + +// implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples. +// pbuffer: contains samples at 0, 2, 4, 6... +// temppaintbuffer is temp buffer, same size as paintbuffer, used to store processed values +// count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6... + +// finpos is the fractional, inpos the integer part. +// finpos = 0.5 for upsampling by 2x +// inpos is the position of the sample + +// xm1 = x [inpos - 1]; +// x0 = x [inpos + 0]; +// x1 = x [inpos + 1]; +// x2 = x [inpos + 2]; +// a = (3 * (x0-x1) - xm1 + x2) / 2; +// b = 2*x1 + xm1 - (5*x0 + x2) / 2; +// c = (x1 - xm1) / 2; +// y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0; + + int i, upCount = count << 1; + int a, b, c; + int xm1, x0, x1, x2; + portable_samplepair_t *psamp0; + portable_samplepair_t *psamp1; + portable_samplepair_t *psamp2; + portable_samplepair_t *psamp3; + int outpos = 0; + + Assert( upCount <= PAINTBUFFER_SIZE ); + + // pfiltermem holds 6 samples from previous buffer pass + // process 'count' samples + for( i = 0; i < count; i++) + { + // get source sample pointer + psamp0 = S_GetNextpFilter( i-1, pbuffer, pfiltermem ); + psamp1 = S_GetNextpFilter( i+0, pbuffer, pfiltermem ); + psamp2 = S_GetNextpFilter( i+1, pbuffer, pfiltermem ); + psamp3 = S_GetNextpFilter( i+2, pbuffer, pfiltermem ); + + // write out original sample to interpolation buffer + temppaintbuffer[outpos++] = *psamp1; + + // get all left samples for interpolation window + xm1 = psamp0->left; + x0 = psamp1->left; + x1 = psamp2->left; + x2 = psamp3->left; + + // interpolate + a = (3 * (x0-x1) - xm1 + x2) / 2; + b = 2*x1 + xm1 - (5*x0 + x2) / 2; + c = (x1 - xm1) / 2; + + // write out interpolated sample + temppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0; + + // get all right samples for window + xm1 = psamp0->right; + x0 = psamp1->right; + x1 = psamp2->right; + x2 = psamp3->right; + + // interpolate + a = (3 * (x0-x1) - xm1 + x2) / 2; + b = 2*x1 + xm1 - (5*x0 + x2) / 2; + c = (x1 - xm1) / 2; + + // write out interpolated sample, increment output counter + temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0; + + Assert( outpos <= ( sizeof( temppaintbuffer ) / sizeof( temppaintbuffer[0] ))); + } + + Assert( cfltmem >= 3 ); + + // save last 3 samples from paintbuffer + pfiltermem[0] = pbuffer[upCount - 5]; + pfiltermem[1] = pbuffer[upCount - 3]; + pfiltermem[2] = pbuffer[upCount - 1]; + + // copy temppaintbuffer back into paintbuffer + for( i = 0; i < upCount; i++ ) + pbuffer[i] = temppaintbuffer[i]; +} + +// pass forward over passed in buffer and linearly interpolate all odd samples +// pbuffer: buffer to filter (in place) +// prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR +// if NULL then perform no filtering. +// count: how many samples to upsample. will become count*2 samples in buffer, in place. +void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +{ + int i, upCount = count<<1; + + Assert( upCount <= PAINTBUFFER_SIZE ); + Assert( cfltmem >= 1 ); + + // use interpolation value from previous mix + pbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1; + pbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1; + + for( i = 2; i < upCount; i += 2 ) + { + // use linear interpolation for upsampling + pbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1; + pbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1; + } + + // save last value to be played out in buffer + *pfiltermem = pbuffer[upCount - 1]; +} + +// upsample by 2x, optionally using interpolation +// count: how many samples to upsample. will become count*2 samples in buffer, in place. +// pbuffer: buffer to upsample into (in place) +// pfiltermem: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR +// if NULL then perform no filtering. +// cfltmem: max number of sample pairs filter can use +// filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter. +void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) +{ + int upCount = count<<1; + int i, j; + + // reverse through buffer, duplicating contents for 'count' samples + for( i = upCount - 1, j = count - 1; j >= 0; i-=2, j-- ) + { + pbuffer[i] = pbuffer[j]; + pbuffer[i-1] = pbuffer[j]; + } + + if( !s_lerping->value ) return; + + // pass forward through buffer, interpolate all even slots + switch( filtertype ) + { + case FILTERTYPE_LINEAR: + S_Interpolate2xLinear( pbuffer, pfiltermem, cfltmem, count ); + break; + case FILTERTYPE_CUBIC: + S_Interpolate2xCubic( pbuffer, pfiltermem, cfltmem, count ); + break; + default: // no filter + break; + } +} + +// zero out all paintbuffers +void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters ) +{ + int count = min( SampleCount, PAINTBUFFER_SIZE ); + int i; + + // zero out all paintbuffer data (ignore sampleCount) + for( i = 0; i < CPAINTBUFFERS; i++ ) + { + if( paintbuffers[i].pbuf != NULL ) + memset( paintbuffers[i].pbuf, 0, (count+1) * sizeof( portable_samplepair_t )); + + if( clearFilters ) + { + memset( paintbuffers[i].fltmem, 0, sizeof( paintbuffers[i].fltmem )); + } + } + + if( clearFilters ) + { + MIX_ResetPaintbufferFilterCounters(); + } +} + +// mixes pbuf1 + pbuf2 into pbuf3, count samples +// fgain is output gain 0-1.0 +// NOTE: pbuf3 may equal pbuf1 or pbuf2! +void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain ) +{ + portable_samplepair_t *pbuf1, *pbuf2, *pbuf3; + int i, gain; + + gain = 256 * fgain; + + Assert( count <= PAINTBUFFER_SIZE ); + Assert( ibuf1 < CPAINTBUFFERS ); + Assert( ibuf2 < CPAINTBUFFERS ); + Assert( ibuf3 < CPAINTBUFFERS ); + + pbuf1 = paintbuffers[ibuf1].pbuf; + pbuf2 = paintbuffers[ibuf2].pbuf; + pbuf3 = paintbuffers[ibuf3].pbuf; + + // destination buffer stereo - average n chans down to stereo + + // destination 2ch: + // pb1 2ch + pb2 2ch -> pb3 2ch + // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch + // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch + + // mix front channels + for( i = 0; i < count; i++ ) + { + pbuf3[i].left = pbuf1[i].left; + pbuf3[i].right = pbuf1[i].right; + pbuf3[i].left += (pbuf2[i].left * gain) >> 8; + pbuf3[i].right += (pbuf2[i].right * gain) >> 8; + } +} + +void MIX_CompressPaintbuffer( int ipaint, int count ) +{ + portable_samplepair_t *pbuf; + paintbuffer_t *ppaint; + int i; + + ppaint = MIX_GetPPaintFromIPaint( ipaint ); + pbuf = ppaint->pbuf; + + for( i = 0; i < count; i++, pbuf++ ) + { + pbuf->left = CLIP( pbuf->left ); + pbuf->right = CLIP( pbuf->right ); + } +} + +void S_MixUpsample( int sampleCount, int filtertype ) +{ + paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr(); + int ifilter = ppaint->ifilter; + + Assert( ifilter < CPAINTFILTERS ); + + S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype ); + + // make sure on next upsample pass for this paintbuffer, new filter memory is used + ppaint->ifilter++; +} + +void MIX_MixStreamBuffer( int end ) +{ + portable_samplepair_t *pbuf; + rawchan_t *ch; + + pbuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER ); + ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, false ); + + // clear the paint buffer + if( s_listener.paused || !ch || ch->s_rawend < paintedtime ) + { + memset( pbuf, 0, (end - paintedtime) * sizeof( portable_samplepair_t )); + } + else + { + int i, stop; + + // copy from the streaming sound source + stop = (end < ch->s_rawend) ? end : ch->s_rawend; + + for( i = paintedtime; i < stop; i++ ) + { + pbuf[i-paintedtime].left = ( ch->rawsamples[i & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8; + pbuf[i-paintedtime].right = ( ch->rawsamples[i & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8; + } + + for( ; i < end; i++ ) + pbuf[i-paintedtime].left = pbuf[i-paintedtime].right = 0; + } +} + +void MIX_MixRawSamplesBuffer( int end ) +{ + portable_samplepair_t *pbuf; + uint i, j, stop; + + pbuf = MIX_GetCurrentPaintbufferPtr()->pbuf; + + if( s_listener.paused ) return; + + // paint in the raw channels + for( i = 0; i < MAX_RAW_CHANNELS; i++ ) + { + // copy from the streaming sound source + rawchan_t *ch = raw_channels[i]; + + // background track should be mixing into another buffer + if( !ch || ch->entnum == S_RAW_SOUND_BACKGROUNDTRACK ) + continue; + + // not audible + if( !ch->leftvol && !ch->rightvol ) + continue; + + stop = (end < ch->s_rawend) ? end : ch->s_rawend; + + for( j = paintedtime; j < stop; j++ ) + { + pbuf[j-paintedtime].left += ( ch->rawsamples[j & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8; + pbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8; + } + } +} + +// upsample and mix sounds into final 44khz versions of: +// IROOMBUFFER, IFACINGBUFFER, IFACINGAWAY +// dsp fx are then applied to these buffers by the caller. +// caller also remixes all into final IPAINTBUFFER output. +void MIX_UpsampleAllPaintbuffers( int end, int count ) +{ + // process stream buffer + MIX_MixStreamBuffer( end ); + + // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction + // These buffers are facing, facingaway, room + // These 3 mixed buffers are then each upsampled to 22khz. + + // 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + // These 3 mixed buffers are then each upsampled to 44khz. + + // 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + + MIX_DeactivateAllPaintbuffers(); + + // set paintbuffer upsample filter indices to 0 + MIX_ResetPaintbufferFilterCounters(); + + // only mix to roombuffer if dsp fx are on KDB: perf + MIX_ActivatePaintbuffer( IROOMBUFFER ); // operates on MIX_MixChannelsToPaintbuffer + + // mix 11khz sounds: + MIX_MixChannelsToPaintbuffer( end, SOUND_11k, SOUND_11k ); + + // upsample all 11khz buffers by 2x + // only upsample roombuffer if dsp fx are on KDB: perf + MIX_SetCurrentPaintbuffer( IROOMBUFFER ); // operates on MixUpSample + S_MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + + // mix 22khz sounds: + MIX_MixChannelsToPaintbuffer( end, SOUND_22k, SOUND_22k ); + + // upsample all 22khz buffers by 2x + // only upsample roombuffer if dsp fx are on KDB: perf + MIX_SetCurrentPaintbuffer( IROOMBUFFER ); + S_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_22k ), FILTERTYPE_LINEAR ); + + // mix all 44khz sounds to all active paintbuffers + MIX_MixChannelsToPaintbuffer( end, SOUND_44k, SOUND_DMA_SPEED ); + + // mix raw samples from the video streams + MIX_SetCurrentPaintbuffer( IROOMBUFFER ); + MIX_MixRawSamplesBuffer( end ); + + MIX_DeactivateAllPaintbuffers(); + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); +} + +void MIX_PaintChannels( int endtime ) +{ + int end, count; + float dsp_room_gain; + + CheckNewDspPresets(); + + // get dsp preset gain values, update gain crossfaders, + // used when mixing dsp processed buffers into paintbuffer + dsp_room_gain = DSP_GetGain( idsp_room ); // update crossfader - gain only used in MIX_ScaleChannelVolume + + while( paintedtime < endtime ) + { + // if paintbuffer is smaller than DMA buffer + end = endtime; + if( endtime - paintedtime > PAINTBUFFER_SIZE ) + end = paintedtime + PAINTBUFFER_SIZE; + + // number of 44khz samples to mix into paintbuffer, up to paintbuffer size + count = end - paintedtime; + + // clear the all mix buffers + MIX_ClearAllPaintBuffers( count, false ); + + MIX_UpsampleAllPaintbuffers( end, count ); + + // process all sounds with DSP + DSP_Process( idsp_room, MIX_GetPFrontFromIPaint( IROOMBUFFER ), count ); + + // add music or soundtrack from movie (no dsp) + MIX_MixPaintbuffers( IPAINTBUFFER, IROOMBUFFER, IPAINTBUFFER, count, S_GetMasterVolume() ); + + // add music or soundtrack from movie (no dsp) + MIX_MixPaintbuffers( IPAINTBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, S_GetMusicVolume() ); + + // clip all values > 16 bit down to 16 bit + MIX_CompressPaintbuffer( IPAINTBUFFER, count ); + + // transfer IPAINTBUFFER paintbuffer out to DMA buffer + MIX_SetCurrentPaintbuffer( IPAINTBUFFER ); + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + paintedtime = end; + } +} \ No newline at end of file diff --git a/engine/client/s_mouth.c b/engine/client/s_mouth.c new file mode 100644 index 00000000..c242f8be --- /dev/null +++ b/engine/client/s_mouth.c @@ -0,0 +1,152 @@ +/* +s_mouth.c - animate mouth +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include "client.h" +#include "const.h" + +#define CAVGSAMPLES 10 + +void SND_InitMouth( int entnum, int entchannel ) +{ + if(( entchannel == CHAN_VOICE || entchannel == CHAN_STREAM ) && entnum > 0 ) + { + cl_entity_t *clientEntity; + + // init mouth movement vars + clientEntity = CL_GetEntityByIndex( entnum ); + + if( clientEntity ) + { + clientEntity->mouth.mouthopen = 0; + clientEntity->mouth.sndcount = 0; + clientEntity->mouth.sndavg = 0; + } + } +} + +void SND_CloseMouth( channel_t *ch ) +{ + if( ch->entchannel == CHAN_VOICE || ch->entchannel == CHAN_STREAM ) + { + cl_entity_t *clientEntity; + + clientEntity = CL_GetEntityByIndex( ch->entnum ); + + if( clientEntity ) + { + // shut mouth + clientEntity->mouth.mouthopen = 0; + } + } +} + +void SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count ) +{ + cl_entity_t *clientEntity; + char *pdata = NULL; + mouth_t *pMouth = NULL; + int scount, pos = 0; + int savg, data; + uint i; + + clientEntity = CL_GetEntityByIndex( ch->entnum ); + if( !clientEntity ) return; + + pMouth = &clientEntity->mouth; + + if( ch->isSentence ) + { + if( ch->currentWord ) + pos = ch->currentWord->sample; + } + else pos = ch->pMixer.sample; + + count = S_GetOutputData( pSource, &pdata, pos, count, ch->use_loop ); + if( pdata == NULL ) return; + + i = 0; + scount = pMouth->sndcount; + savg = 0; + + while( i < count && scount < CAVGSAMPLES ) + { + data = pdata[i]; + savg += abs( data ); + + i += 80 + ((byte)data & 0x1F); + scount++; + } + + pMouth->sndavg += savg; + pMouth->sndcount = (byte)scount; + + if( pMouth->sndcount >= CAVGSAMPLES ) + { + pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES; + pMouth->sndavg = 0; + pMouth->sndcount = 0; + } +} + +void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count ) +{ + cl_entity_t *clientEntity; + short *pdata = NULL; + mouth_t *pMouth = NULL; + int savg, data; + int scount, pos = 0; + uint i; + + clientEntity = CL_GetEntityByIndex( ch->entnum ); + if( !clientEntity ) return; + + pMouth = &clientEntity->mouth; + + if( ch->isSentence ) + { + if( ch->currentWord ) + pos = ch->currentWord->sample; + } + else pos = ch->pMixer.sample; + + count = S_GetOutputData( pSource, &pdata, pos, count, ch->use_loop ); + if( pdata == NULL ) return; + + i = 0; + scount = pMouth->sndcount; + savg = 0; + + while( i < count && scount < CAVGSAMPLES ) + { + data = pdata[i]; + data = (bound( -32767, data, 0x7ffe ) >> 8); + savg += abs( data ); + + i += 80 + ((byte)data & 0x1F); + scount++; + } + + pMouth->sndavg += savg; + pMouth->sndcount = (byte)scount; + + if( pMouth->sndcount >= CAVGSAMPLES ) + { + pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES; + pMouth->sndavg = 0; + pMouth->sndcount = 0; + } +} \ No newline at end of file diff --git a/engine/client/s_stream.c b/engine/client/s_stream.c new file mode 100644 index 00000000..40d0f99b --- /dev/null +++ b/engine/client/s_stream.c @@ -0,0 +1,343 @@ +/* +s_stream.c - sound streaming +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include "client.h" + +static bg_track_t s_bgTrack; +static musicfade_t musicfade; // controlled by game dlls + +/* +================= +S_PrintBackgroundTrackState +================= +*/ +void S_PrintBackgroundTrackState( void ) +{ + Con_Printf( "BackgroundTrack: " ); + + if( s_bgTrack.current[0] && s_bgTrack.loopName[0] ) + Con_Printf( "intro %s, loop %s\n", s_bgTrack.current, s_bgTrack.loopName ); + else if( s_bgTrack.current[0] ) + Con_Printf( "%s\n", s_bgTrack.current ); + else if( s_bgTrack.loopName[0] ) + Con_Printf( "%s [loop]\n", s_bgTrack.loopName ); + else Con_Printf( "not playing\n" ); +} + +/* +================= +S_FadeMusicVolume +================= +*/ +void S_FadeMusicVolume( float fadePercent ) +{ + musicfade.percent = bound( 0.0f, fadePercent, 100.0f ); +} + +/* +================= +S_GetMusicVolume +================= +*/ +float S_GetMusicVolume( void ) +{ + float scale = 1.0f; + + if( !s_listener.inmenu && musicfade.percent != 0 ) + { + scale = bound( 0.0f, musicfade.percent / 100.0f, 1.0f ); + scale = 1.0f - scale; + } + + return s_musicvolume->value * scale; +} + +/* +================= +S_StartBackgroundTrack +================= +*/ +void S_StartBackgroundTrack( const char *introTrack, const char *mainTrack, long position, qboolean fullpath ) +{ + S_StopBackgroundTrack(); + + if( !dma.initialized ) return; + + // check for special symbols + if( introTrack && *introTrack == '*' ) + introTrack = NULL; + + if( mainTrack && *mainTrack == '*' ) + mainTrack = NULL; + + if(( !introTrack || !*introTrack ) && ( !mainTrack || !*mainTrack )) + return; + + if( !introTrack ) introTrack = mainTrack; + if( !*introTrack ) return; + + if( !mainTrack || !*mainTrack ) s_bgTrack.loopName[0] = '\0'; + else Q_strncpy( s_bgTrack.loopName, mainTrack, sizeof( s_bgTrack.loopName )); +if( fullpath ) Msg( "MP3:Playing: %s\n", introTrack ); + // open stream + s_bgTrack.stream = FS_OpenStream( va( "media/%s", introTrack )); + Q_strncpy( s_bgTrack.current, introTrack, sizeof( s_bgTrack.current )); + memset( &musicfade, 0, sizeof( musicfade )); // clear any soundfade + s_bgTrack.source = cls.key_dest; + + if( position != 0 ) + { + // restore message, update song position + FS_SetStreamPos( s_bgTrack.stream, position ); + } +} + +/* +================= +S_StopBackgroundTrack +================= +*/ +void S_StopBackgroundTrack( void ) +{ + s_listener.stream_paused = false; + + if( !dma.initialized ) return; + if( !s_bgTrack.stream ) return; + + FS_FreeStream( s_bgTrack.stream ); + memset( &s_bgTrack, 0, sizeof( bg_track_t )); + memset( &musicfade, 0, sizeof( musicfade )); +} + +/* +================= +S_StreamSetPause +================= +*/ +void S_StreamSetPause( int pause ) +{ + s_listener.stream_paused = pause; +} + +/* +================= +S_StreamGetCurrentState + +save\restore code +================= +*/ +qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ) +{ + if( !s_bgTrack.stream ) + return false; // not active + + if( currentTrack ) + { + if( s_bgTrack.current[0] ) + Q_strncpy( currentTrack, s_bgTrack.current, MAX_STRING ); + else Q_strncpy( currentTrack, "*", MAX_STRING ); // no track + } + + if( loopTrack ) + { + if( s_bgTrack.loopName[0] ) + Q_strncpy( loopTrack, s_bgTrack.loopName, MAX_STRING ); + else Q_strncpy( loopTrack, "*", MAX_STRING ); // no track + } + + if( position ) + *position = FS_GetStreamPos( s_bgTrack.stream ); + + return true; +} + +/* +================= +S_StreamBackgroundTrack +================= +*/ +void S_StreamBackgroundTrack( void ) +{ + int bufferSamples; + int fileSamples; + byte raw[MAX_RAW_SAMPLES]; + int r, fileBytes; + rawchan_t *ch = NULL; + + if( !dma.initialized || !s_bgTrack.stream || s_listener.streaming ) + return; + + // don't bother playing anything if musicvolume is 0 + if( !s_musicvolume->value || s_listener.paused || s_listener.stream_paused ) + return; + + if( !cl.background ) + { + // pause music by source type + if( s_bgTrack.source == key_game && cls.key_dest == key_menu ) return; + if( s_bgTrack.source == key_menu && cls.key_dest != key_menu ) return; + } + else if( cls.key_dest == key_console ) + return; + + ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, true ); + + Assert( ch != NULL ); + + // see how many samples should be copied into the raw buffer + if( ch->s_rawend < soundtime ) + ch->s_rawend = soundtime; + + while( ch->s_rawend < soundtime + ch->max_samples ) + { + wavdata_t *info = FS_StreamInfo( s_bgTrack.stream ); + + bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); + if( fileSamples <= 1 ) return; // no more samples need + + // our max buffer size + fileBytes = fileSamples * ( info->width * info->channels ); + + if( fileBytes > sizeof( raw )) + { + fileBytes = sizeof( raw ); + fileSamples = fileBytes / ( info->width * info->channels ); + } + + // read + r = FS_ReadStream( s_bgTrack.stream, fileBytes, raw ); + + if( r < fileBytes ) + { + fileBytes = r; + fileSamples = r / ( info->width * info->channels ); + } + + if( r > 0 ) + { + // add to raw buffer + S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_BACKGROUNDTRACK ); + } + else + { + // loop + if( s_bgTrack.loopName[0] ) + { + FS_FreeStream( s_bgTrack.stream ); + s_bgTrack.stream = FS_OpenStream( va( "media/%s", s_bgTrack.loopName )); + Q_strncpy( s_bgTrack.current, s_bgTrack.loopName, sizeof( s_bgTrack.current )); + + if( !s_bgTrack.stream ) return; + } + else + { + S_StopBackgroundTrack(); + return; + } + } + + } +} + +/* +================= +S_StartStreaming +================= +*/ +void S_StartStreaming( void ) +{ + if( !dma.initialized ) return; + // begin streaming movie soundtrack + s_listener.streaming = true; +} + +/* +================= +S_StopStreaming +================= +*/ +void S_StopStreaming( void ) +{ + if( !dma.initialized ) return; + s_listener.streaming = false; +} + +/* +================= +S_StreamSoundTrack +================= +*/ +void S_StreamSoundTrack( void ) +{ + int bufferSamples; + int fileSamples; + byte raw[MAX_RAW_SAMPLES]; + int r, fileBytes; + rawchan_t *ch = NULL; + + if( !dma.initialized || !s_listener.streaming || s_listener.paused ) + return; + + ch = S_FindRawChannel( S_RAW_SOUND_SOUNDTRACK, true ); + + Assert( ch != NULL ); + + // see how many samples should be copied into the raw buffer + if( ch->s_rawend < soundtime ) + ch->s_rawend = soundtime; + + while( ch->s_rawend < soundtime + ch->max_samples ) + { + wavdata_t *info = SCR_GetMovieInfo(); + + if( !info ) break; // bad soundtrack? + + bufferSamples = ch->max_samples - (ch->s_rawend - soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * ((float)info->rate / SOUND_DMA_SPEED ); + if( fileSamples <= 1 ) return; // no more samples need + + // our max buffer size + fileBytes = fileSamples * ( info->width * info->channels ); + + if( fileBytes > sizeof( raw )) + { + fileBytes = sizeof( raw ); + fileSamples = fileBytes / ( info->width * info->channels ); + } + + // read audio stream + r = SCR_GetAudioChunk( raw, fileBytes ); + + if( r < fileBytes ) + { + fileBytes = r; + fileSamples = r / ( info->width * info->channels ); + } + + if( r > 0 ) + { + // add to raw buffer + S_RawSamples( fileSamples, info->rate, info->width, info->channels, raw, S_RAW_SOUND_SOUNDTRACK ); + } + else break; // no more samples for this frame + } +} \ No newline at end of file diff --git a/engine/client/s_utils.c b/engine/client/s_utils.c new file mode 100644 index 00000000..78822be0 --- /dev/null +++ b/engine/client/s_utils.c @@ -0,0 +1,306 @@ +/* +s_utils.c - common sound functions +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" + +// hardcoded macros to test for zero crossing +#define ZERO_X_8( b ) (( b ) < 2 && ( b ) > -2 ) +#define ZERO_X_16( b ) (( b ) < 512 && ( b ) > -512 ) + +//----------------------------------------------------------------------------- +// Purpose: Search backward for a zero crossing starting at sample +// Input : sample - starting point +// Output : position of zero crossing +//----------------------------------------------------------------------------- +int S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample ) +{ + if( pWaveData == NULL ) + return sample; + + if( pWaveData->type == WF_PCMDATA ) + { + int sampleSize; + + sampleSize = pWaveData->width * pWaveData->channels; + + // this can never be zero -- other functions divide by this. + // This should never happen, but avoid crashing + if( sampleSize <= 0 ) sampleSize = 1; + + if( pWaveData->width == 1 ) + { + char *pData = pWaveData->buffer + sample * sampleSize; + qboolean zero = false; + + if( pWaveData->channels == 1 ) + { + while( sample > 0 && !zero ) + { + if( ZERO_X_8( *pData )) + { + zero = true; + } + else + { + sample--; + pData--; + } + } + } + else + { + while( sample > 0 && !zero ) + { + if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] )) + { + zero = true; + } + else + { + sample--; + pData--; + } + } + } + } + else + { + short *pData = (short *)(pWaveData->buffer + sample * sampleSize); + qboolean zero = false; + + if( pWaveData->channels == 1 ) + { + while( sample > 0 && !zero ) + { + if( ZERO_X_16(*pData )) + { + zero = true; + } + else + { + pData--; + sample--; + } + } + } + else + { + while( sample > 0 && !zero ) + { + if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] )) + { + zero = true; + } + else + { + sample--; + pData--; + } + } + } + } + } + + return sample; +} + +//----------------------------------------------------------------------------- +// Purpose: Search forward for a zero crossing +// Input : sample - starting point +// Output : position of found zero crossing +//----------------------------------------------------------------------------- +int S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample ) +{ + if( pWaveData == NULL ) + return sample; + + if( pWaveData->type == WF_PCMDATA ) + { + int sampleSize; + + sampleSize = pWaveData->width * pWaveData->channels; + + // this can never be zero -- other functions divide by this. + // This should never happen, but avoid crashing + if( sampleSize <= 0 ) sampleSize = 1; + + if( pWaveData->width == 1 ) // 8-bit + { + char *pData = pWaveData->buffer + sample * sampleSize; + qboolean zero = false; + + if( pWaveData->channels == 1 ) + { + while( sample < pWaveData->samples && !zero ) + { + if( ZERO_X_8( *pData )) + { + zero = true; + } + else + { + sample++; + pData++; + } + } + } + else + { + while( sample < pWaveData->samples && !zero ) + { + if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] )) + { + zero = true; + } + else + { + sample++; + pData++; + } + } + } + } + else + { + short *pData = (short *)(pWaveData->buffer + sample * sampleSize); + qboolean zero = false; + + if( pWaveData->channels == 1 ) + { + while( sample > 0 && !zero ) + { + if( ZERO_X_16( *pData )) + { + zero = true; + } + else + { + pData++; + sample++; + } + } + } + else + { + while( sample > 0 && !zero ) + { + if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] )) + { + zero = true; + } + else + { + sample++; + pData++; + } + } + } + } + } + + return sample; +} + +//----------------------------------------------------------------------------- +// Purpose: wrap the position wrt looping +// Input : samplePosition - absolute position +// Output : int - looped position +//----------------------------------------------------------------------------- +int S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean use_loop ) +{ + // if the wave is looping and we're past the end of the sample + // convert to a position within the loop + // At the end of the loop, we return a short buffer, and subsequent call + // will loop back and get the rest of the buffer + if( pSource->loopStart >= 0 && samplePosition >= pSource->samples && use_loop ) + { + // size of loop + int loopSize = pSource->samples - pSource->loopStart; + + // subtract off starting bit of the wave + samplePosition -= pSource->loopStart; + + if( loopSize ) + { + // "real" position in memory (mod off extra loops) + samplePosition = pSource->loopStart + ( samplePosition % loopSize ); + } + // ERROR? if no loopSize + } + + return samplePosition; +} + +int S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop ) +{ + int totalSampleCount; + int sampleSize; + + // handle position looping + samplePosition = S_ConvertLoopedPosition( pSource, samplePosition, use_loop ); + + // how many samples are available (linearly not counting looping) + totalSampleCount = pSource->samples - samplePosition; + + // may be asking for a sample out of range, clip at zero + if( totalSampleCount < 0 ) totalSampleCount = 0; + + // clip max output samples to max available + if( sampleCount > totalSampleCount ) + sampleCount = totalSampleCount; + + sampleSize = pSource->width * pSource->channels; + + // this can never be zero -- other functions divide by this. + // This should never happen, but avoid crashing + if( sampleSize <= 0 ) sampleSize = 1; + + // byte offset in sample database + samplePosition *= sampleSize; + + // if we are returning some samples, store the pointer + if( sampleCount ) + { + *pData = pSource->buffer + samplePosition; + } + + return sampleCount; +} + +// move the current position to newPosition +void S_SetSampleStart( channel_t *pChan, wavdata_t *pSource, int newPosition ) +{ + if( pSource ) + newPosition = S_ZeroCrossingAfter( pSource, newPosition ); + + pChan->pMixer.sample = newPosition; +} + +// end playback at newEndPosition +void S_SetSampleEnd( channel_t *pChan, wavdata_t *pSource, int newEndPosition ) +{ + // forced end of zero means play the whole sample + if( !newEndPosition ) newEndPosition = 1; + + if( pSource ) + newEndPosition = S_ZeroCrossingBefore( pSource, newEndPosition ); + + // past current position? limit. + if( newEndPosition < pChan->pMixer.sample ) + newEndPosition = pChan->pMixer.sample; + + pChan->pMixer.forcedEndSample = newEndPosition; +} \ No newline at end of file diff --git a/engine/client/s_vox.c b/engine/client/s_vox.c new file mode 100644 index 00000000..f36ef5a5 --- /dev/null +++ b/engine/client/s_vox.c @@ -0,0 +1,681 @@ +/* +s_vox.c - npc sentences +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "sound.h" +#include "const.h" + +sentence_t g_Sentences[MAX_SENTENCES]; +static uint g_numSentences; +static char *rgpparseword[CVOXWORDMAX]; // array of pointers to parsed words +static char voxperiod[] = "_period"; // vocal pause +static char voxcomma[] = "_comma"; // vocal pause + +static int IsNextWord( const char c ) +{ + if( c == '.' || c == ',' || c == ' ' || c == '(' ) + return 1; + return 0; +} + +static int IsSkipSpace( const char c ) +{ + if( c == ',' || c == '.' || c == ' ' ) + return 1; + return 0; +} + +static int IsWhiteSpace( const char space ) +{ + if( space == ' ' || space == '\t' || space == '\r' || space == '\n' ) + return 1; + return 0; +} + +static int IsCommandChar( const char c ) +{ + if( c == 'v' || c == 'p' || c == 's' || c == 'e' || c == 't' ) + return 1; + return 0; +} + +static int IsDelimitChar( const char c ) +{ + if( c == '(' || c == ')' ) + return 1; + return 0; +} + +static char *ScanForwardUntil( char *string, const char scan ) +{ + while( string[0] ) + { + if( string[0] == scan ) + return string; + string++; + } + return string; +} + +// backwards scan psz for last '/' +// return substring in szpath null terminated +// if '/' not found, return 'vox/' +static char *VOX_GetDirectory( char *szpath, char *psz ) +{ + char c; + int cb = 0; + char *p = psz + Q_strlen( psz ) - 1; + + // scan backwards until first '/' or start of string + c = *p; + while( p > psz && c != '/' ) + { + c = *( --p ); + cb++; + } + + if( c != '/' ) + { + // didn't find '/', return default directory + Q_strcpy( szpath, "vox/" ); + return psz; + } + + cb = Q_strlen( psz ) - cb; + memcpy( szpath, psz, cb ); + szpath[cb] = 0; + + return p + 1; +} + +// scan g_Sentences, looking for pszin sentence name +// return pointer to sentence data if found, null if not +// CONSIDER: if we have a large number of sentences, should +// CONSIDER: sort strings in g_Sentences and do binary search. +char *VOX_LookupString( const char *pSentenceName, int *psentencenum ) +{ + int i; + + if( Q_isdigit( pSentenceName ) && (i = Q_atoi( pSentenceName )) < g_numSentences ) + { + if( psentencenum ) *psentencenum = i; + return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); + } + + for( i = 0; i < g_numSentences; i++ ) + { + if( !Q_stricmp( pSentenceName, g_Sentences[i].pName )) + { + if( psentencenum ) *psentencenum = i; + return (g_Sentences[i].pName + Q_strlen( g_Sentences[i].pName ) + 1 ); + } + } + + return NULL; +} + +// parse a null terminated string of text into component words, with +// pointers to each word stored in rgpparseword +// note: this code actually alters the passed in string! +char **VOX_ParseString( char *psz ) +{ + int i, fdone = 0; + char c, *p = psz; + + memset( rgpparseword, 0, sizeof( char* ) * CVOXWORDMAX ); + + if( !psz ) return NULL; + + i = 0; + rgpparseword[i++] = psz; + + while( !fdone && i < CVOXWORDMAX ) + { + // scan up to next word + c = *p; + while( c && !IsNextWord( c )) + c = *(++p); + + // if '(' then scan for matching ')' + if( c == '(' ) + { + p = ScanForwardUntil( p, ')' ); + c = *(++p); + if( !c ) fdone = 1; + } + + if( fdone || !c ) + { + fdone = 1; + } + else + { + // if . or , insert pause into rgpparseword, + // unless this is the last character + if(( c == '.' || c == ',' ) && *(p+1) != '\n' && *(p+1) != '\r' && *(p+1) != 0 ) + { + if( c == '.' ) rgpparseword[i++] = voxperiod; + else rgpparseword[i++] = voxcomma; + + if( i >= CVOXWORDMAX ) + break; + } + + // null terminate substring + *p++ = 0; + + // skip whitespace + c = *p; + while( c && IsSkipSpace( c )) + c = *(++p); + + if( !c ) fdone = 1; + else rgpparseword[i++] = p; + } + } + + return rgpparseword; +} + +float VOX_GetVolumeScale( channel_t *pchan ) +{ + if( pchan->currentWord ) + { + if ( pchan->words[pchan->wordIndex].volume ) + { + float volume = pchan->words[pchan->wordIndex].volume * 0.01f; + if( volume < 1.0f ) return volume; + } + } + + return 1.0f; +} + +void VOX_SetChanVol( channel_t *ch ) +{ + float scale; + + if( !ch->currentWord ) + return; + + scale = VOX_GetVolumeScale( ch ); + if( scale == 1.0f ) return; + + ch->rightvol = (int)(ch->rightvol * scale); + ch->leftvol = (int)(ch->leftvol * scale); +} + +float VOX_ModifyPitch( channel_t *ch, float pitch ) +{ + if( ch->currentWord ) + { + if( ch->words[ch->wordIndex].pitch > 0 ) + { + pitch += ( ch->words[ch->wordIndex].pitch - PITCH_NORM ) * 0.01f; + } + } + + return pitch; +} + +//=============================================================================== +// Get any pitch, volume, start, end params into voxword +// and null out trailing format characters +// Format: +// someword(v100 p110 s10 e20) +// +// v is volume, 0% to n% +// p is pitch shift up 0% to n% +// s is start wave offset % +// e is end wave offset % +// t is timecompression % +// +// pass fFirst == 1 if this is the first string in sentence +// returns 1 if valid string, 0 if parameter block only. +// +// If a ( xxx ) parameter block does not directly follow a word, +// then that 'default' parameter block will be used as the default value +// for all following words. Default parameter values are reset +// by another 'default' parameter block. Default parameter values +// for a single word are overridden for that word if it has a parameter block. +// +//=============================================================================== +int VOX_ParseWordParams( char *psz, voxword_t *pvoxword, int fFirst ) +{ + char *pszsave = psz; + char c, ct, sznum[8]; + static voxword_t voxwordDefault; + int i; + + // init to defaults if this is the first word in string. + if( fFirst ) + { + voxwordDefault.pitch = -1; + voxwordDefault.volume = 100; + voxwordDefault.start = 0; + voxwordDefault.end = 100; + voxwordDefault.fKeepCached = 0; + voxwordDefault.timecompress = 0; + } + + *pvoxword = voxwordDefault; + + // look at next to last char to see if we have a + // valid format: + c = *( psz + Q_strlen( psz ) - 1 ); + + // no formatting, return + if( c != ')' ) return 1; + + // scan forward to first '(' + c = *psz; + while( !IsDelimitChar( c )) + c = *(++psz); + + // bogus formatting + if( c == ')' ) return 0; + + // null terminate + *psz = 0; + ct = *(++psz); + + while( 1 ) + { + // scan until we hit a character in the commandSet + while( ct && !IsCommandChar( ct )) + ct = *(++psz); + + if( ct == ')' ) + break; + + memset( sznum, 0, sizeof( sznum )); + i = 0; + + c = *(++psz); + + if( !isdigit( c )) + break; + + // read number + while( isdigit( c ) && i < sizeof( sznum ) - 1 ) + { + sznum[i++] = c; + c = *(++psz); + } + + // get value of number + i = Q_atoi( sznum ); + + switch( ct ) + { + case 'v': pvoxword->volume = i; break; + case 'p': pvoxword->pitch = i; break; + case 's': pvoxword->start = i; break; + case 'e': pvoxword->end = i; break; + case 't': pvoxword->timecompress = i; break; + } + + ct = c; + } + + // if the string has zero length, this was an isolated + // parameter block. Set default voxword to these + // values + if( Q_strlen( pszsave ) == 0 ) + { + voxwordDefault = *pvoxword; + return 0; + } + + return 1; +} + +void VOX_LoadWord( channel_t *pchan ) +{ + if( pchan->words[pchan->wordIndex].sfx ) + { + wavdata_t *pSource = S_LoadSound( pchan->words[pchan->wordIndex].sfx ); + + if( pSource ) + { + int start = pchan->words[pchan->wordIndex].start; + int end = pchan->words[pchan->wordIndex].end; + + // apply mixer + pchan->currentWord = &pchan->pMixer; + pchan->currentWord->pData = pSource; + + // don't allow overlapped ranges + if( end <= start ) end = 0; + + if( start || end ) + { + int sampleCount = pSource->samples; + + if( start ) + { + S_SetSampleStart( pchan, pSource, (int)(sampleCount * 0.01f * start)); + } + + if( end ) + { + S_SetSampleEnd( pchan, pSource, (int)(sampleCount * 0.01f * end)); + } + } + } + } +} + +void VOX_FreeWord( channel_t *pchan ) +{ + pchan->currentWord = NULL; // sentence is finished + memset( &pchan->pMixer, 0, sizeof( pchan->pMixer )); + + // release unused sounds + if( pchan->words[pchan->wordIndex].sfx ) + { + // If this wave wasn't precached by the game code + if( !pchan->words[pchan->wordIndex].fKeepCached ) + { + FS_FreeSound( pchan->words[pchan->wordIndex].sfx->cache ); + pchan->words[pchan->wordIndex].sfx->cache = NULL; + pchan->words[pchan->wordIndex].sfx = NULL; + } + } +} + +void VOX_LoadFirstWord( channel_t *pchan, voxword_t *pwords ) +{ + int i = 0; + + // copy each pointer in the sfx temp array into the + // sentence array, and set the channel to point to the + // sentence array + while( pwords[i].sfx != NULL ) + { + pchan->words[i] = pwords[i]; + i++; + } + pchan->words[i].sfx = NULL; + + pchan->wordIndex = 0; + VOX_LoadWord( pchan ); +} + +// return number of samples mixed +int VOX_MixDataToDevice( channel_t *pchan, int sampleCount, int outputRate, int outputOffset ) +{ + // save this to compute total output + int startingOffset = outputOffset; + + if( !pchan->currentWord ) + return 0; + + while( sampleCount > 0 && pchan->currentWord ) + { + int timeCompress = pchan->words[pchan->wordIndex].timecompress; + int outputCount = S_MixDataToDevice( pchan, sampleCount, outputRate, outputOffset, timeCompress ); + + outputOffset += outputCount; + sampleCount -= outputCount; + + // if we finished load a next word + if( pchan->currentWord->finished ) + { + VOX_FreeWord( pchan ); + pchan->wordIndex++; + VOX_LoadWord( pchan ); + + if( pchan->currentWord ) + { + pchan->sfx = pchan->words[pchan->wordIndex].sfx; + } + } + } + return outputOffset - startingOffset; +} + +// link all sounds in sentence, start playing first word. +void VOX_LoadSound( channel_t *pchan, const char *pszin ) +{ + char buffer[512]; + int i, cword; + char pathbuffer[64]; + char szpath[32]; + voxword_t rgvoxword[CVOXWORDMAX]; + char *psz; + + if( !pszin || !*pszin ) + return; + + memset( rgvoxword, 0, sizeof( voxword_t ) * CVOXWORDMAX ); + memset( buffer, 0, sizeof( buffer )); + + // lookup actual string in g_Sentences, + // set pointer to string data + psz = VOX_LookupString( pszin, NULL ); + + if( !psz ) + { + Con_DPrintf( S_ERROR "VOX_LoadSound: no such sentence %s\n", pszin ); + return; + } + + // get directory from string, advance psz + psz = VOX_GetDirectory( szpath, psz ); + + if( Q_strlen( psz ) > sizeof( buffer ) - 1 ) + { + MsgDev( D_ERROR, "VOX_LoadSound: sentence is too long %s\n", psz ); + return; + } + + // copy into buffer + Q_strcpy( buffer, psz ); + psz = buffer; + + // parse sentence (also inserts null terminators between words) + VOX_ParseString( psz ); + + // for each word in the sentence, construct the filename, + // lookup the sfx and save each pointer in a temp array + + i = 0; + cword = 0; + while( rgpparseword[i] ) + { + // Get any pitch, volume, start, end params into voxword + if( VOX_ParseWordParams( rgpparseword[i], &rgvoxword[cword], i == 0 )) + { + // this is a valid word (as opposed to a parameter block) + Q_strcpy( pathbuffer, szpath ); + Q_strncat( pathbuffer, rgpparseword[i], sizeof( pathbuffer )); + Q_strncat( pathbuffer, ".wav", sizeof( pathbuffer )); + + // find name, if already in cache, mark voxword + // so we don't discard when word is done playing + rgvoxword[cword].sfx = S_FindName( pathbuffer, &( rgvoxword[cword].fKeepCached )); + cword++; + } + i++; + } + + VOX_LoadFirstWord( pchan, rgvoxword ); + + pchan->isSentence = true; + pchan->sfx = rgvoxword[0].sfx; +} + +//----------------------------------------------------------------------------- +// Purpose: Take a NULL terminated sentence, and parse any commands contained in +// {}. The string is rewritten in place with those commands removed. +// +// Input : *pSentenceData - sentence data to be modified in place +// sentenceIndex - global sentence table index for any data that is +// parsed out +//----------------------------------------------------------------------------- +void VOX_ParseLineCommands( char *pSentenceData, int sentenceIndex ) +{ + char tempBuffer[512]; + char *pNext, *pStart; + int length, tempBufferPos = 0; + + if( !pSentenceData ) + return; + + pStart = pSentenceData; + + while( *pSentenceData ) + { + pNext = ScanForwardUntil( pSentenceData, '{' ); + + // find length of "good" portion of the string (not a {} command) + length = pNext - pSentenceData; + if( tempBufferPos + length > sizeof( tempBuffer )) + { + MsgDev( D_ERROR, "sentence too long!\n" ); + return; + } + + // Copy good string to temp buffer + memcpy( tempBuffer + tempBufferPos, pSentenceData, length ); + + // move the copy position + tempBufferPos += length; + + pSentenceData = pNext; + + // skip ahead of the opening brace + if( *pSentenceData ) pSentenceData++; + + // skip whitespace + while( *pSentenceData && *pSentenceData <= 32 ) + pSentenceData++; + + // simple comparison of string commands: + switch( Q_tolower( *pSentenceData )) + { + case 'l': + // all commands starting with the letter 'l' here + if( !Q_strnicmp( pSentenceData, "len", 3 )) + { + g_Sentences[sentenceIndex].length = Q_atof( pSentenceData + 3 ); + } + break; + case 0: + default: + break; + } + + pSentenceData = ScanForwardUntil( pSentenceData, '}' ); + + // skip the closing brace + if( *pSentenceData ) pSentenceData++; + + // skip trailing whitespace + while( *pSentenceData && *pSentenceData <= 32 ) + pSentenceData++; + } + + if( tempBufferPos < sizeof( tempBuffer )) + { + // terminate cleaned up copy + tempBuffer[tempBufferPos] = 0; + + // copy it over the original data + Q_strcpy( pStart, tempBuffer ); + } +} + +// Load sentence file into memory, insert null terminators to +// delimit sentence name/sentence pairs. Keep pointer to each +// sentence name so we can search later. +void VOX_ReadSentenceFile( const char *psentenceFileName ) +{ + char c, *pch, *pFileData; + char *pchlast, *pSentenceData; + int fileSize; + + // load file + pFileData = (char *)FS_LoadFile( psentenceFileName, &fileSize, false ); + if( !pFileData ) return; // this game just doesn't used vox sound system + + pch = pFileData; + pchlast = pch + fileSize; + + while( pch < pchlast ) + { + // only process this pass on sentences + pSentenceData = NULL; + + // skip newline, cr, tab, space + + c = *pch; + while( pch < pchlast && IsWhiteSpace( c )) + c = *(++pch); + + // skip entire line if first char is / + if( *pch != '/' ) + { + sentence_t *pSentence = &g_Sentences[g_numSentences++]; + + pSentence->pName = pch; + pSentence->length = 0; + + // scan forward to first space, insert null terminator + // after sentence name + + c = *pch; + while( pch < pchlast && c != ' ' ) + c = *(++pch); + + if( pch < pchlast ) + *pch++ = 0; + + // a sentence may have some line commands, make an extra pass + pSentenceData = pch; + } + + // scan forward to end of sentence or eof + while( pch < pchlast && pch[0] != '\n' && pch[0] != '\r' ) + pch++; + + // insert null terminator + if( pch < pchlast ) *pch++ = 0; + + // If we have some sentence data, parse out any line commands + if( pSentenceData && pSentenceData < pchlast ) + { + int index = g_numSentences - 1; + + // the current sentence has an index of count-1 + VOX_ParseLineCommands( pSentenceData, index ); + } + } +} + +void VOX_Init( void ) +{ + memset( g_Sentences, 0, sizeof( g_Sentences )); + g_numSentences = 0; + + VOX_ReadSentenceFile( DEFAULT_SOUNDPATH "sentences.txt" ); +} + + +void VOX_Shutdown( void ) +{ + g_numSentences = 0; +} \ No newline at end of file diff --git a/engine/client/sound.h b/engine/client/sound.h new file mode 100644 index 00000000..2d790a3b --- /dev/null +++ b/engine/client/sound.h @@ -0,0 +1,359 @@ +/* +sound.h - sndlib main header +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef SOUND_H +#define SOUND_H + +extern byte *sndpool; + +#include "mathlib.h" + +// sound engine rate defines +#define SOUND_DMA_SPEED 44100 // hardware playback rate +#define SOUND_11k 11025 // 11khz sample rate +#define SOUND_16k 16000 // 16khz sample rate +#define SOUND_22k 22050 // 22khz sample rate +#define SOUND_32k 32000 // 32khz sample rate +#define SOUND_44k 44100 // 44khz sample rate +#define DMA_MSEC_PER_SAMPLE ((float)(1000.0 / SOUND_DMA_SPEED)) + +#define SND_TRACE_UPDATE_MAX 2 // max of N channels may be checked for obscured source per frame +#define SND_RADIUS_MAX 240.0f // max sound source radius +#define SND_RADIUS_MIN 24.0f // min sound source radius +#define SND_OBSCURED_LOSS_DB -2.70f // dB loss due to obscured sound source + +// calculate gain based on atmospheric attenuation. +// as gain excedes threshold, round off (compress) towards 1.0 using spline +#define SND_GAIN_COMP_EXP_MAX 2.5f // Increasing SND_GAIN_COMP_EXP_MAX fits compression curve + // more closely to original gain curve as it approaches 1.0. +#define SND_GAIN_FADE_TIME 0.25f // xfade seconds between obscuring gain changes +#define SND_GAIN_COMP_EXP_MIN 0.8f +#define SND_GAIN_COMP_THRESH 0.5f // gain value above which gain curve is rounded to approach 1.0 +#define SND_DB_MAX 140.0f // max db of any sound source +#define SND_DB_MED 90.0f // db at which compression curve changes +#define SND_DB_MIN 60.0f // min db of any sound source +#define SND_GAIN_PLAYER_WEAPON_DB 2.0f // increase player weapon gain by N dB + +// fixed point stuff for real-time resampling +#define FIX_BITS 28 +#define FIX_SCALE (1 << FIX_BITS) +#define FIX_MASK ((1 << FIX_BITS)-1) +#define FIX_FLOAT(a) ((int)((a) * FIX_SCALE)) +#define FIX(a) (((int)(a)) << FIX_BITS) +#define FIX_INTPART(a) (((int)(a)) >> FIX_BITS) +#define FIX_FRACTION(a,b) (FIX(a)/(b)) +#define FIX_FRACPART(a) ((a) & FIX_MASK) + +#define SNDLVL_TO_DIST_MULT( sndlvl ) \ + ( sndlvl ? ((pow( 10, s_refdb->value / 20 ) / pow( 10, (float)sndlvl / 20 )) / s_refdist->value ) : 0 ) + +#define DIST_MULT_TO_SNDLVL( dist_mult ) \ + (int)( dist_mult ? ( 20 * log10( pow( 10, s_refdb->value / 20 ) / (dist_mult * s_refdist->value ))) : 0 ) + +// NOTE: clipped sound at 32760 to avoid overload +#define CLIP( x ) (( x ) > 32760 ? 32760 : (( x ) < -32760 ? -32760 : ( x ))) +#define SWAP( a, b, t ) {(t) = (a); (a) = (b); (b) = (t);} +#define AVG( a, b ) (((a) + (b)) >> 1 ) +#define AVG4( a, b, c, d ) (((a) + (b) + (c) + (d)) >> 2 ) + +#define PAINTBUFFER_SIZE 1024 // 44k: was 512 +#define PAINTBUFFER (g_curpaintbuffer) +#define CPAINTBUFFERS 3 + +// sound mixing buffer +#define CPAINTFILTERMEM 3 +#define CPAINTFILTERS 4 // maximum number of consecutive upsample passes per paintbuffer + +#define S_RAW_SOUND_IDLE_SEC 10 // time interval for idling raw sound before it's freed +#define S_RAW_SOUND_BACKGROUNDTRACK -2 +#define S_RAW_SOUND_SOUNDTRACK -1 +#define S_RAW_SAMPLES_PRECISION_BITS 14 + +typedef struct +{ + int left; + int right; +} portable_samplepair_t; + +typedef struct +{ + qboolean factive; // if true, mix to this paintbuffer using flags + portable_samplepair_t *pbuf; // front stereo mix buffer, for 2 or 4 channel mixing + int ifilter; // current filter memory buffer to use for upsampling pass + portable_samplepair_t fltmem[CPAINTFILTERS][CPAINTFILTERMEM]; +} paintbuffer_t; + +typedef struct sfx_s +{ + char name[MAX_QPATH]; + wavdata_t *cache; + + int servercount; + uint hashValue; + struct sfx_s *hashNext; +} sfx_t; + +extern portable_samplepair_t paintbuffer[]; +extern portable_samplepair_t roombuffer[]; +extern portable_samplepair_t temppaintbuffer[]; +extern portable_samplepair_t *g_curpaintbuffer; +extern paintbuffer_t paintbuffers[]; + +// structure used for fading in and out client sound volume. +typedef struct +{ + float initial_percent; + float percent; // how far to adjust client's volume down by. + float starttime; // GetHostTime() when we started adjusting volume + float fadeouttime; // # of seconds to get to faded out state + float holdtime; // # of seconds to hold + float fadeintime; // # of seconds to restore +} soundfade_t; + +typedef struct +{ + float percent; +} musicfade_t; + +typedef struct +{ + int samples; // mono samples in buffer + int samplepos; // in mono samples + byte *buffer; + qboolean initialized; // sound engine is active +} dma_t; + +#include "vox.h" + +typedef struct +{ + double sample; + + wavdata_t *pData; + double forcedEndSample; + qboolean finished; +} mixer_t; + +typedef struct rawchan_s +{ + int entnum; + int master_vol; + int leftvol; // 0-255 left volume + int rightvol; // 0-255 right volume + float dist_mult; // distance multiplier (attenuation/clipK) + vec3_t origin; // only use if fixed_origin is set + float radius; // radius of this sound effect + volatile uint s_rawend; + size_t max_samples; // buffer length + portable_samplepair_t rawsamples[1]; // variable sized +} rawchan_t; + +typedef struct channel_s +{ + char name[16]; // keept sentence name + sfx_t *sfx; // sfx number + + int leftvol; // 0-255 left volume + int rightvol; // 0-255 right volume + + int entnum; // entity soundsource + int entchannel; // sound channel (CHAN_STREAM, CHAN_VOICE, etc.) + vec3_t origin; // only use if fixed_origin is set + float dist_mult; // distance multiplier (attenuation/clipK) + int master_vol; // 0-255 master volume + qboolean isSentence; // bit who indicated sentence + int basePitch; // base pitch percent (100% is normal pitch playback) + float pitch; // real-time pitch after any modulation or shift by dynamic data + qboolean use_loop; // don't loop default and local sounds + qboolean staticsound; // use origin instead of fetching entnum's origin + qboolean localsound; // it's a local menu sound (not looped, not paused) + mixer_t pMixer; + + // sound culling + qboolean bfirstpass; // true if this is first time sound is spatialized + float ob_gain; // gain drop if sound source obscured from listener + float ob_gain_target; // target gain while crossfading between ob_gain & ob_gain_target + float ob_gain_inc; // crossfade increment + qboolean bTraced; // true if channel was already checked this frame for obscuring + float radius; // radius of this sound effect + vec3_t absmin, absmax; // filled in CL_GetEntitySpatialization + int movetype; // to determine point entities + + // sentence mixer + int wordIndex; + mixer_t *currentWord; // NULL if sentence is finished + voxword_t words[CVOXWORDMAX]; +} channel_t; + +typedef struct +{ + vec3_t origin; // simorg + view_ofs + vec3_t velocity; + vec3_t forward; + vec3_t right; + vec3_t up; + + int entnum; + int waterlevel; + float frametime; // used for sound fade + qboolean active; + qboolean inmenu; // listener in-menu ? + qboolean paused; + qboolean streaming; // playing AVI-file + qboolean stream_paused; // pause only background track + + byte pasbytes[(MAX_MAP_LEAFS+7)/8];// actual PHS for current frame +} listener_t; + +typedef struct +{ + string current; // a currently playing track + string loopName; // may be empty + stream_t *stream; + int source; // may be game, menu, etc +} bg_track_t; + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init( void *hInst ); +int SNDDMA_GetSoundtime( void ); +void SNDDMA_Shutdown( void ); +void SNDDMA_BeginPainting( void ); +void SNDDMA_Submit( void ); +void SNDDMA_LockSound( void ); +void SNDDMA_UnlockSound( void ); + +//==================================================================== + +#define MAX_DYNAMIC_CHANNELS (60 + NUM_AMBIENTS) +#define MAX_CHANNELS (256 + MAX_DYNAMIC_CHANNELS) // Scourge Of Armagon has too many static sounds on hip2m4.bsp +#define MAX_RAW_CHANNELS 16 +#define MAX_RAW_SAMPLES 8192 + +extern sound_t ambient_sfx[NUM_AMBIENTS]; +extern qboolean snd_ambient; +extern channel_t channels[MAX_CHANNELS]; +extern rawchan_t *raw_channels[MAX_RAW_CHANNELS]; +extern int total_channels; +extern int paintedtime; +extern int soundtime; +extern listener_t s_listener; +extern int idsp_room; +extern dma_t dma; + +extern convar_t *s_volume; +extern convar_t *s_musicvolume; +extern convar_t *s_show; +extern convar_t *s_mixahead; +extern convar_t *s_lerping; +extern convar_t *dsp_off; +extern convar_t *s_test; // cvar to testify new effects + +void S_InitScaletable( void ); +wavdata_t *S_LoadSound( sfx_t *sfx ); +float S_GetMasterVolume( void ); +float S_GetMusicVolume( void ); + +// +// s_main.c +// +void S_FreeChannel( channel_t *ch ); + +// +// s_mix.c +// +int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset, int timeCompress ); +void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters ); +void MIX_InitAllPaintbuffers( void ); +void MIX_FreeAllPaintbuffers( void ); +void MIX_PaintChannels( int endtime ); + +// s_load.c +qboolean S_TestSoundChar( const char *pch, char c ); +char *S_SkipSoundChar( const char *pch ); +sfx_t *S_FindName( const char *name, int *pfInCache ); +sound_t S_RegisterSound( const char *name ); +void S_FreeSound( sfx_t *sfx ); + +// s_dsp.c +void SX_Init( void ); +void SX_Free( void ); +void CheckNewDspPresets( void ); +void DSP_Process( int idsp, portable_samplepair_t *pbfront, int sampleCount ); +float DSP_GetGain( int idsp ); +void DSP_ClearState( void ); + +qboolean S_Init( void ); +void S_Shutdown( void ); +void S_Activate( qboolean active, void *hInst ); +void S_SoundList_f( void ); +void S_SoundInfo_f( void ); + +channel_t *SND_PickDynamicChannel( int entnum, int channel, sfx_t *sfx, qboolean *ignore ); +channel_t *SND_PickStaticChannel( const vec3_t pos, sfx_t *sfx ); +int S_GetCurrentStaticSounds( soundlist_t *pout, int size ); +int S_GetCurrentDynamicSounds( soundlist_t *pout, int size ); +sfx_t *S_GetSfxByHandle( sound_t handle ); +rawchan_t *S_FindRawChannel( int entnum, qboolean create ); +void S_RawSamples( uint samples, uint rate, word width, word channels, const byte *data, int entnum ); +void S_StopSound( int entnum, int channel, const char *soundname ); +uint S_GetRawSamplesLength( int entnum ); +void S_ClearRawChannel( int entnum ); +void S_StopAllSounds( qboolean ambient ); +void S_FreeSounds( void ); + +// +// s_mouth.c +// +void SND_InitMouth( int entnum, int entchannel ); +void SND_MoveMouth8( channel_t *ch, wavdata_t *pSource, int count ); +void SND_MoveMouth16( channel_t *ch, wavdata_t *pSource, int count ); +void SND_CloseMouth( channel_t *ch ); + +// +// s_stream.c +// +void S_StreamSoundTrack( void ); +void S_StreamBackgroundTrack( void ); +qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); +void S_PrintBackgroundTrackState( void ); +void S_FadeMusicVolume( float fadePercent ); + +// +// s_utils.c +// +int S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample ); +int S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample ); +int S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop ); +void S_SetSampleStart( channel_t *pChan, wavdata_t *pSource, int newPosition ); +void S_SetSampleEnd( channel_t *pChan, wavdata_t *pSource, int newEndPosition ); + +// +// s_vox.c +// +void VOX_Init( void ); +void VOX_Shutdown( void ); +void VOX_SetChanVol( channel_t *ch ); +void VOX_LoadSound( channel_t *pchan, const char *psz ); +float VOX_ModifyPitch( channel_t *ch, float pitch ); +int VOX_MixDataToDevice( channel_t *pChannel, int sampleCount, int outputRate, int outputOffset ); + +#endif//SOUND_H \ No newline at end of file diff --git a/engine/client/vgui/vgui_clip.cpp b/engine/client/vgui/vgui_clip.cpp new file mode 100644 index 00000000..c3b91848 --- /dev/null +++ b/engine/client/vgui/vgui_clip.cpp @@ -0,0 +1,110 @@ +/* +vgui_clip.cpp - clip in 2D space +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "vgui_draw.h" +#include "wrect.h" + +//----------------------------------------------------------------------------- +// For simulated scissor tests... +//----------------------------------------------------------------------------- +static wrect_t g_ScissorRect; +static qboolean g_bScissor = false; + +//----------------------------------------------------------------------------- +// Enable/disable scissoring... +//----------------------------------------------------------------------------- +void EnableScissor( qboolean enable ) +{ + g_bScissor = enable; +} + +void SetScissorRect( int left, int top, int right, int bottom ) +{ + // Check for a valid rectangle... + Assert( left <= right ); + Assert( top <= bottom ); + + g_ScissorRect.left = left; + g_ScissorRect.top = top; + g_ScissorRect.right = right; + g_ScissorRect.bottom = bottom; +} + +//----------------------------------------------------------------------------- +// Purpose: Used for clipping, produces an interpolated texture coordinate +//----------------------------------------------------------------------------- +inline float InterpTCoord( float val, float mins, float maxs, float tMin, float tMax ) +{ + float flPercent; + + if( mins != maxs ) + flPercent = (float)(val - mins) / (maxs - mins); + else flPercent = 0.5f; + + return tMin + (tMax - tMin) * flPercent; +} + +//----------------------------------------------------------------------------- +// Purpose: Does a scissor clip of the input rectangle. +// Returns false if it is completely clipped off. +//----------------------------------------------------------------------------- +qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ) +{ + if( g_bScissor ) + { + // pick whichever left side is larger + if( g_ScissorRect.left > inUL.point[0] ) + pOutUL->point[0] = g_ScissorRect.left; + else + pOutUL->point[0] = inUL.point[0]; + + // pick whichever right side is smaller + if( g_ScissorRect.right <= inLR.point[0] ) + pOutLR->point[0] = g_ScissorRect.right; + else + pOutLR->point[0] = inLR.point[0]; + + // pick whichever top side is larger + if( g_ScissorRect.top > inUL.point[1] ) + pOutUL->point[1] = g_ScissorRect.top; + else + pOutUL->point[1] = inUL.point[1]; + + // pick whichever bottom side is smaller + if( g_ScissorRect.bottom <= inLR.point[1] ) + pOutLR->point[1] = g_ScissorRect.bottom; + else + pOutLR->point[1] = inLR.point[1]; + + // Check for non-intersecting + if(( pOutUL->point[0] > pOutLR->point[0] ) || ( pOutUL->point[1] > pOutLR->point[1] )) + { + return false; + } + + pOutUL->coord[0] = InterpTCoord(pOutUL->point[0], inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); + pOutLR->coord[0] = InterpTCoord(pOutLR->point[0], inUL.point[0], inLR.point[0], inUL.coord[0], inLR.coord[0] ); + + pOutUL->coord[1] = InterpTCoord(pOutUL->point[1], inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); + pOutLR->coord[1] = InterpTCoord(pOutLR->point[1], inUL.point[1], inLR.point[1], inUL.coord[1], inLR.coord[1] ); + } + else + { + *pOutUL = inUL; + *pOutLR = inLR; + } + return true; +} \ No newline at end of file diff --git a/engine/client/vgui/vgui_draw.c b/engine/client/vgui/vgui_draw.c new file mode 100644 index 00000000..ef087985 --- /dev/null +++ b/engine/client/vgui/vgui_draw.c @@ -0,0 +1,218 @@ +/* +vgui_draw.c - vgui draw methods +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include "vgui_draw.h" + +convar_t *vgui_colorstrings; +int g_textures[VGUI_MAX_TEXTURES]; +int g_textureId = 0; + +/* +================ +VGUI_DrawInit + +Startup VGUI backend +================ +*/ +void VGUI_DrawInit( void ) +{ + memset( g_textures, 0, sizeof( g_textures )); + g_textureId = 0; + + vgui_colorstrings = Cvar_Get( "vgui_colorstrings", "0", FCVAR_ARCHIVE, "allow colorstrings in VGUI texts" ); +} + +/* +================ +VGUI_DrawShutdown + +Release all the textures +================ +*/ +void VGUI_DrawShutdown( void ) +{ + int i; + + for( i = 1; i < g_textureId; i++ ) + { + GL_FreeImage( va( "*vgui%i", i )); + } +} + +/* +================ +VGUI_GenerateTexture + +generate unique texture number +================ +*/ +int VGUI_GenerateTexture( void ) +{ + if( ++g_textureId >= VGUI_MAX_TEXTURES ) + Host_Error( "VGUI_GenerateTexture: VGUI_MAX_TEXTURES limit exceeded\n" ); + return g_textureId; +} + +/* +================ +VGUI_UploadTexture + +Upload texture into video memory +================ +*/ +void VGUI_UploadTexture( int id, const char *buffer, int width, int height ) +{ + rgbdata_t r_image; + char texName[32]; + + if( id <= 0 || id >= VGUI_MAX_TEXTURES ) + { + MsgDev( D_ERROR, "VGUI_UploadTexture: bad texture %i. Ignored\n", id ); + return; + } + + Q_snprintf( texName, sizeof( texName ), "*vgui%i", id ); + memset( &r_image, 0, sizeof( r_image )); + + r_image.width = width; + r_image.height = height; + r_image.type = PF_RGBA_32; + r_image.size = r_image.width * r_image.height * 4; + r_image.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA; + r_image.buffer = (byte *)buffer; + + g_textures[id] = GL_LoadTextureInternal( texName, &r_image, TF_IMAGE, false ); +} + +/* +================ +VGUI_SetupDrawingRect + +setup transparency etc +================ +*/ +void VGUI_SetupDrawingRect( int *pColor ) +{ + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +} + +/* +================ +VGUI_SetupDrawingImage + +setup transparency etc +================ +*/ +void VGUI_SetupDrawingImage( int *pColor ) +{ + pglEnable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglColor4ub( pColor[0], pColor[1], pColor[2], 255 - pColor[3] ); +} + +/* +================ +VGUI_BindTexture + +bind VGUI texture through private index +================ +*/ +void VGUI_BindTexture( int id ) +{ + if( id > 0 && id < VGUI_MAX_TEXTURES && g_textures[id] ) + { + GL_Bind( GL_TEXTURE0, g_textures[id] ); + } + else + { + // NOTE: same as bogus index 2700 in GoldSrc + GL_Bind( GL_TEXTURE0, g_textures[1] ); + } +} + +/* +================ +VGUI_EnableTexture + +disable texturemode for fill rectangle +================ +*/ +void VGUI_EnableTexture( qboolean enable ) +{ + if( enable ) pglEnable( GL_TEXTURE_2D ); + else pglDisable( GL_TEXTURE_2D ); +} + +/* +================ +VGUI_DrawQuad + +generic method to fill rectangle +================ +*/ +void VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ) +{ + pglBegin( GL_QUADS ); + pglTexCoord2f( ul->coord[0], ul->coord[1] ); + pglVertex2f( ul->point[0], ul->point[1] ); + + pglTexCoord2f( lr->coord[0], ul->coord[1] ); + pglVertex2f( lr->point[0], ul->point[1] ); + + pglTexCoord2f( lr->coord[0], lr->coord[1] ); + pglVertex2f( lr->point[0], lr->point[1] ); + + pglTexCoord2f( ul->coord[0], lr->coord[1] ); + pglVertex2f( ul->point[0], lr->point[1] ); + pglEnd(); +} + +/* +================ +VGUI_DrawBuffer + +render the quads array +================ +*/ +void VGUI_DrawBuffer( const vpoint_t *buffer, int numVerts ) +{ + if( numVerts <= 0 ) return; + + pglEnable( GL_BLEND ); + pglEnable( GL_ALPHA_TEST ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + pglEnableClientState( GL_VERTEX_ARRAY ); + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglEnableClientState( GL_COLOR_ARRAY ); + + pglTexCoordPointer( 2, GL_FLOAT, sizeof( vpoint_t ), &buffer->coord[0] ); + pglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( vpoint_t ), &buffer->color[0] ); + pglVertexPointer( 2, GL_FLOAT, sizeof( vpoint_t ), &buffer->point[0] ); + pglDrawArrays( GL_QUADS, 0, numVerts ); + + pglDisableClientState( GL_VERTEX_ARRAY ); + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisableClientState( GL_COLOR_ARRAY ); +} \ No newline at end of file diff --git a/engine/client/vgui/vgui_draw.h b/engine/client/vgui/vgui_draw.h new file mode 100644 index 00000000..a4169d0e --- /dev/null +++ b/engine/client/vgui/vgui_draw.h @@ -0,0 +1,77 @@ +/* +vgui_draw.h - vgui draw methods +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef VGUI_DRAW_H +#define VGUI_DRAW_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define VGUI_MAX_TEXTURES 2048 // a half of total textures count + +extern rgba_t g_color_table[8]; // for colored strings support +extern convar_t *vgui_colorstrings; + +// VGUI generic vertex +typedef struct +{ + vec2_t point; + vec2_t coord; + byte color[4]; +} vpoint_t; + +// +// vgui_backend.c +// + +void VGUI_DrawInit( void ); +void VGUI_DrawShutdown( void ); +void VGUI_SetupDrawingRect( int *pColor ); +void VGUI_SetupDrawingImage( int *pColor ); +void VGUI_BindTexture( int id ); +void VGUI_EnableTexture( qboolean enable ); +void VGUI_UploadTexture( int id, const char *buffer, int width, int height ); +LONG VGUI_SurfaceWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); +void VGUI_DrawQuad( const vpoint_t *ul, const vpoint_t *lr ); +void VGUI_DrawBuffer( const vpoint_t *buffer, int numVerts ); +int VGUI_GenerateTexture( void ); +void *VGui_GetPanel( void ); + +#ifdef __cplusplus +void EnableScissor( qboolean enable ); +void SetScissorRect( int left, int top, int right, int bottom ); +qboolean ClipRect( const vpoint_t &inUL, const vpoint_t &inLR, vpoint_t *pOutUL, vpoint_t *pOutLR ); +#endif + +// +// gl_vidnt.c +// +qboolean R_DescribeVIDMode( int width, int height ); + +// +// vgui_int.c +// +void VGui_Startup( void ); +void VGui_Shutdown( void ); +void *VGui_GetPanel( void ); +void VGui_Paint( int paintAll ); +void VGui_RunFrame( void ); +void VGui_ViewportPaintBackground( int extents[4] ); + +#ifdef __cplusplus +} +#endif +#endif//VGUI_DRAW_H \ No newline at end of file diff --git a/engine/client/vgui/vgui_input.cpp b/engine/client/vgui/vgui_input.cpp new file mode 100644 index 00000000..f6afb6d4 --- /dev/null +++ b/engine/client/vgui/vgui_input.cpp @@ -0,0 +1,290 @@ +/* +vgui_input.cpp - handle kb & mouse +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#define OEMRESOURCE // for OCR_* cursor junk + +#include "common.h" +#include "client.h" +#include "vgui_draw.h" +#include "vgui_main.h" +#include "input.h" + +static KeyCode s_pVirtualKeyTrans[256]; +static HICON s_pDefaultCursor[20]; +static HICON s_hCurrentCursor = NULL; + +void VGUI_InitCursors( void ) +{ + // load up all default cursors + s_pDefaultCursor[Cursor::dc_none] = NULL; + s_pDefaultCursor[Cursor::dc_arrow] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_NORMAL ); + s_pDefaultCursor[Cursor::dc_ibeam] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_IBEAM ); + s_pDefaultCursor[Cursor::dc_hourglass]= (HICON)LoadCursor( NULL, (LPCTSTR)OCR_WAIT ); + s_pDefaultCursor[Cursor::dc_crosshair]= (HICON)LoadCursor( NULL, (LPCTSTR)OCR_CROSS ); + s_pDefaultCursor[Cursor::dc_up] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_UP ); + s_pDefaultCursor[Cursor::dc_sizenwse] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_SIZENWSE ); + s_pDefaultCursor[Cursor::dc_sizenesw] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_SIZENESW ); + s_pDefaultCursor[Cursor::dc_sizewe] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_SIZEWE ); + s_pDefaultCursor[Cursor::dc_sizens] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_SIZENS ); + s_pDefaultCursor[Cursor::dc_sizeall] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_SIZEALL ); + s_pDefaultCursor[Cursor::dc_no] = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_NO ); + s_pDefaultCursor[Cursor::dc_hand] = (HICON)LoadCursor( NULL, (LPCTSTR)32649 ); + + s_hCurrentCursor = s_pDefaultCursor[Cursor::dc_arrow]; + host.mouse_visible = true; +} + +void VGUI_CursorSelect( Cursor *cursor ) +{ + Assert( cursor != NULL ); + + host.mouse_visible = true; + + switch( cursor->getDefaultCursor( )) + { + case Cursor::dc_user: + case Cursor::dc_none: + host.mouse_visible = false; + break; + case Cursor::dc_arrow: + case Cursor::dc_ibeam: + case Cursor::dc_hourglass: + case Cursor::dc_crosshair: + case Cursor::dc_up: + case Cursor::dc_sizenwse: + case Cursor::dc_sizenesw: + case Cursor::dc_sizewe: + case Cursor::dc_sizens: + case Cursor::dc_sizeall: + case Cursor::dc_no: + case Cursor::dc_hand: + s_hCurrentCursor = s_pDefaultCursor[cursor->getDefaultCursor()]; + break; + default: + host.mouse_visible = false; + Assert( 0 ); + break; + } + + VGUI_ActivateCurrentCursor(); +} + +void VGUI_ActivateCurrentCursor( void ) +{ + if( cls.key_dest != key_game || cl.paused ) + return; + + if( host.mouse_visible ) + { + while( ShowCursor( true ) < 0 ); + SetCursor( s_hCurrentCursor ); + } + else + { + while( ShowCursor( false ) >= 0 ); + SetCursor( NULL ); + } +} + +void VGUI_InitKeyTranslationTable( void ) +{ + static bool bInitted = false; + + if( bInitted ) return; + bInitted = true; + + // set virtual key translation table + memset( s_pVirtualKeyTrans, -1, sizeof( s_pVirtualKeyTrans )); + + s_pVirtualKeyTrans['0'] = KEY_0; + s_pVirtualKeyTrans['1'] = KEY_1; + s_pVirtualKeyTrans['2'] = KEY_2; + s_pVirtualKeyTrans['3'] = KEY_3; + s_pVirtualKeyTrans['4'] = KEY_4; + s_pVirtualKeyTrans['5'] = KEY_5; + s_pVirtualKeyTrans['6'] = KEY_6; + s_pVirtualKeyTrans['7'] = KEY_7; + s_pVirtualKeyTrans['8'] = KEY_8; + s_pVirtualKeyTrans['9'] = KEY_9; + s_pVirtualKeyTrans['A'] = s_pVirtualKeyTrans['a'] = KEY_A; + s_pVirtualKeyTrans['B'] = s_pVirtualKeyTrans['b'] = KEY_B; + s_pVirtualKeyTrans['C'] = s_pVirtualKeyTrans['c'] = KEY_C; + s_pVirtualKeyTrans['D'] = s_pVirtualKeyTrans['d'] = KEY_D; + s_pVirtualKeyTrans['E'] = s_pVirtualKeyTrans['e'] = KEY_E; + s_pVirtualKeyTrans['F'] = s_pVirtualKeyTrans['f'] = KEY_F; + s_pVirtualKeyTrans['G'] = s_pVirtualKeyTrans['g'] = KEY_G; + s_pVirtualKeyTrans['H'] = s_pVirtualKeyTrans['h'] = KEY_H; + s_pVirtualKeyTrans['I'] = s_pVirtualKeyTrans['i'] = KEY_I; + s_pVirtualKeyTrans['J'] = s_pVirtualKeyTrans['j'] = KEY_J; + s_pVirtualKeyTrans['K'] = s_pVirtualKeyTrans['k'] = KEY_K; + s_pVirtualKeyTrans['L'] = s_pVirtualKeyTrans['l'] = KEY_L; + s_pVirtualKeyTrans['M'] = s_pVirtualKeyTrans['m'] = KEY_M; + s_pVirtualKeyTrans['N'] = s_pVirtualKeyTrans['n'] = KEY_N; + s_pVirtualKeyTrans['O'] = s_pVirtualKeyTrans['o'] = KEY_O; + s_pVirtualKeyTrans['P'] = s_pVirtualKeyTrans['p'] = KEY_P; + s_pVirtualKeyTrans['Q'] = s_pVirtualKeyTrans['q'] = KEY_Q; + s_pVirtualKeyTrans['R'] = s_pVirtualKeyTrans['r'] = KEY_R; + s_pVirtualKeyTrans['S'] = s_pVirtualKeyTrans['s'] = KEY_S; + s_pVirtualKeyTrans['T'] = s_pVirtualKeyTrans['t'] = KEY_T; + s_pVirtualKeyTrans['U'] = s_pVirtualKeyTrans['u'] = KEY_U; + s_pVirtualKeyTrans['V'] = s_pVirtualKeyTrans['v'] = KEY_V; + s_pVirtualKeyTrans['W'] = s_pVirtualKeyTrans['w'] = KEY_W; + s_pVirtualKeyTrans['X'] = s_pVirtualKeyTrans['x'] = KEY_X; + s_pVirtualKeyTrans['Y'] = s_pVirtualKeyTrans['y'] = KEY_Y; + s_pVirtualKeyTrans['Z'] = s_pVirtualKeyTrans['z'] = KEY_Z; + s_pVirtualKeyTrans[VK_NUMPAD0] = KEY_PAD_0; + s_pVirtualKeyTrans[VK_NUMPAD1] = KEY_PAD_1; + s_pVirtualKeyTrans[VK_NUMPAD2] = KEY_PAD_2; + s_pVirtualKeyTrans[VK_NUMPAD3] = KEY_PAD_3; + s_pVirtualKeyTrans[VK_NUMPAD4] = KEY_PAD_4; + s_pVirtualKeyTrans[VK_NUMPAD5] = KEY_PAD_5; + s_pVirtualKeyTrans[VK_NUMPAD6] = KEY_PAD_6; + s_pVirtualKeyTrans[VK_NUMPAD7] = KEY_PAD_7; + s_pVirtualKeyTrans[VK_NUMPAD8] = KEY_PAD_8; + s_pVirtualKeyTrans[VK_NUMPAD9] = KEY_PAD_9; + s_pVirtualKeyTrans[VK_DIVIDE] = KEY_PAD_DIVIDE; + s_pVirtualKeyTrans[VK_MULTIPLY] = KEY_PAD_MULTIPLY; + s_pVirtualKeyTrans[VK_SUBTRACT] = KEY_PAD_MINUS; + s_pVirtualKeyTrans[VK_ADD] = KEY_PAD_PLUS; + s_pVirtualKeyTrans[VK_RETURN] = KEY_PAD_ENTER; + s_pVirtualKeyTrans[VK_DECIMAL] = KEY_PAD_DECIMAL; + s_pVirtualKeyTrans[0xdb] = KEY_LBRACKET; + s_pVirtualKeyTrans[0xdd] = KEY_RBRACKET; + s_pVirtualKeyTrans[0xba] = KEY_SEMICOLON; + s_pVirtualKeyTrans[0xde] = KEY_APOSTROPHE; + s_pVirtualKeyTrans[0xc0] = KEY_BACKQUOTE; + s_pVirtualKeyTrans[0xbc] = KEY_COMMA; + s_pVirtualKeyTrans[0xbe] = KEY_PERIOD; + s_pVirtualKeyTrans[0xbf] = KEY_SLASH; + s_pVirtualKeyTrans[0xdc] = KEY_BACKSLASH; + s_pVirtualKeyTrans[0xbd] = KEY_MINUS; + s_pVirtualKeyTrans[0xbb] = KEY_EQUAL; + s_pVirtualKeyTrans[VK_RETURN] = KEY_ENTER; + s_pVirtualKeyTrans[VK_SPACE] = KEY_SPACE; + s_pVirtualKeyTrans[VK_BACK] = KEY_BACKSPACE; + s_pVirtualKeyTrans[VK_TAB] = KEY_TAB; + s_pVirtualKeyTrans[VK_CAPITAL] = KEY_CAPSLOCK; + s_pVirtualKeyTrans[VK_NUMLOCK] = KEY_NUMLOCK; + s_pVirtualKeyTrans[VK_ESCAPE] = KEY_ESCAPE; + s_pVirtualKeyTrans[VK_SCROLL] = KEY_SCROLLLOCK; + s_pVirtualKeyTrans[VK_INSERT] = KEY_INSERT; + s_pVirtualKeyTrans[VK_DELETE] = KEY_DELETE; + s_pVirtualKeyTrans[VK_HOME] = KEY_HOME; + s_pVirtualKeyTrans[VK_END] = KEY_END; + s_pVirtualKeyTrans[VK_PRIOR] = KEY_PAGEUP; + s_pVirtualKeyTrans[VK_NEXT] = KEY_PAGEDOWN; + s_pVirtualKeyTrans[VK_PAUSE] = KEY_BREAK; + s_pVirtualKeyTrans[VK_SHIFT] = KEY_RSHIFT; + s_pVirtualKeyTrans[VK_SHIFT] = KEY_LSHIFT; // SHIFT -> left SHIFT + s_pVirtualKeyTrans[VK_MENU] = KEY_RALT; + s_pVirtualKeyTrans[VK_MENU] = KEY_LALT; // ALT -> left ALT + s_pVirtualKeyTrans[VK_CONTROL] = KEY_RCONTROL; + s_pVirtualKeyTrans[VK_CONTROL] = KEY_LCONTROL; // CTRL -> left CTRL + s_pVirtualKeyTrans[VK_LWIN] = KEY_LWIN; + s_pVirtualKeyTrans[VK_RWIN] = KEY_RWIN; + s_pVirtualKeyTrans[VK_APPS] = KEY_APP; + s_pVirtualKeyTrans[VK_UP] = KEY_UP; + s_pVirtualKeyTrans[VK_LEFT] = KEY_LEFT; + s_pVirtualKeyTrans[VK_DOWN] = KEY_DOWN; + s_pVirtualKeyTrans[VK_RIGHT] = KEY_RIGHT; + s_pVirtualKeyTrans[VK_F1] = KEY_F1; + s_pVirtualKeyTrans[VK_F2] = KEY_F2; + s_pVirtualKeyTrans[VK_F3] = KEY_F3; + s_pVirtualKeyTrans[VK_F4] = KEY_F4; + s_pVirtualKeyTrans[VK_F5] = KEY_F5; + s_pVirtualKeyTrans[VK_F6] = KEY_F6; + s_pVirtualKeyTrans[VK_F7] = KEY_F7; + s_pVirtualKeyTrans[VK_F8] = KEY_F8; + s_pVirtualKeyTrans[VK_F9] = KEY_F9; + s_pVirtualKeyTrans[VK_F10] = KEY_F10; + s_pVirtualKeyTrans[VK_F11] = KEY_F11; + s_pVirtualKeyTrans[VK_F12] = KEY_F12; +} + +KeyCode VGUI_MapKey( int keyCode ) +{ + VGUI_InitKeyTranslationTable(); + + if( keyCode < 0 || keyCode >= ARRAYSIZE( s_pVirtualKeyTrans )) + { + Assert( 0 ); + return (KeyCode)-1; + } + else + { + return s_pVirtualKeyTrans[keyCode]; + } +} + +LONG VGUI_SurfaceWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + if( !engSurface ) + return 0; + + switch( uMsg ) + { + case WM_SETCURSOR: + VGUI_ActivateCurrentCursor(); + break; + case WM_MOUSEMOVE: + engApp->internalCursorMoved((short)LOWORD( lParam ), (short)HIWORD( lParam ), engSurface ); + break; + case WM_LBUTTONDOWN: + engApp->internalMousePressed( MOUSE_LEFT, engSurface ); + break; + case WM_RBUTTONDOWN: + engApp->internalMousePressed( MOUSE_RIGHT, engSurface ); + break; + case WM_MBUTTONDOWN: + engApp->internalMousePressed( MOUSE_MIDDLE, engSurface ); + break; + case WM_LBUTTONUP: + engApp->internalMouseReleased( MOUSE_LEFT, engSurface ); + break; + case WM_RBUTTONUP: + engApp->internalMouseReleased( MOUSE_RIGHT, engSurface ); + break; + case WM_MBUTTONUP: + engApp->internalMouseReleased( MOUSE_MIDDLE, engSurface ); + break; + case WM_LBUTTONDBLCLK: + engApp->internalMouseDoublePressed( MOUSE_LEFT, engSurface ); + break; + case WM_RBUTTONDBLCLK: + engApp->internalMouseDoublePressed( MOUSE_RIGHT, engSurface ); + break; + case WM_MBUTTONDBLCLK: + engApp->internalMouseDoublePressed( MOUSE_MIDDLE, engSurface ); + break; + case WM_MOUSEWHEEL: + engApp->internalMouseWheeled(((short)HIWORD( wParam )) / WHEEL_DELTA, engSurface ); + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: + if( !FBitSet( lParam, BIT( 30 ))) + engApp->internalKeyPressed( VGUI_MapKey( wParam ), engSurface ); + engApp->internalKeyTyped( VGUI_MapKey( wParam ), engSurface ); + break; + case WM_CHAR: + case WM_SYSCHAR: + // already handled in Key_Event + break; + case WM_KEYUP: + case WM_SYSKEYUP: + engApp->internalKeyReleased( VGUI_MapKey( wParam ), engSurface ); + break; + } + return 1; +} \ No newline at end of file diff --git a/engine/client/vgui/vgui_int.cpp b/engine/client/vgui/vgui_int.cpp new file mode 100644 index 00000000..7971c441 --- /dev/null +++ b/engine/client/vgui/vgui_int.cpp @@ -0,0 +1,129 @@ +/* +vgui_int.cpp - vgui dll interaction +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "vgui_draw.h" +#include "vgui_main.h" + +Panel *rootPanel = NULL; +CEngineSurface *engSurface = NULL; +CEngineApp staticApp, *engApp; + +void CEngineApp :: setCursorPos( int x, int y ) +{ + POINT pt; + + pt.x = x; + pt.y = y; + + ClientToScreen( (HWND)host.hWnd, &pt ); + + ::SetCursorPos( pt.x, pt.y ); +} + +void CEngineApp :: getCursorPos( int &x,int &y ) +{ + POINT pt; + + // find mouse movement + ::GetCursorPos( &pt ); + ScreenToClient((HWND)host.hWnd, &pt ); + + x = pt.x; + y = pt.y; +} + +void VGui_RunFrame( void ) +{ + if( GetModuleHandle( "fraps32.dll" ) || GetModuleHandle( "fraps64.dll" )) + host.force_draw_version = true; + else host.force_draw_version = false; +} + +void VGui_SetRootPanelSize( void ) +{ + if( rootPanel != NULL ) + rootPanel->setBounds( 0, 0, gameui.globals->scrWidth, gameui.globals->scrHeight ); +} + +void VGui_Startup( void ) +{ + if( engSurface ) return; + + engApp = (CEngineApp *)App::getInstance(); + engApp->reset(); + engApp->setMinimumTickMillisInterval( 0 ); // paint every frame + + rootPanel = new Panel( 0, 0, 320, 240 ); // size will be changed in VGui_SetRootPanelSize + rootPanel->setPaintBorderEnabled( false ); + rootPanel->setPaintBackgroundEnabled( false ); + rootPanel->setPaintEnabled( false ); + rootPanel->setCursor( engApp->getScheme()->getCursor( Scheme::scu_none )); + + engSurface = new CEngineSurface( rootPanel ); + + VGui_SetRootPanelSize (); + VGUI_DrawInit (); +} + +void VGui_Shutdown( void ) +{ + delete rootPanel; + delete engSurface; + engSurface = NULL; + rootPanel = NULL; +} + +void VGui_Paint( int paintAll ) +{ + int extents[4]; + + if( cls.state != ca_active || !rootPanel ) + return; + + VGui_SetRootPanelSize (); + rootPanel->repaint(); + EnableScissor( true ); + + if( cls.key_dest == key_game ) + { + App::getInstance()->externalTick(); + } + + if( paintAll ) + { + // paint everything + rootPanel->paintTraverse(); + } + else + { + rootPanel->getAbsExtents( extents[0], extents[1], extents[2], extents[3] ); + VGui_ViewportPaintBackground( extents ); + } + + EnableScissor( false ); +} + +void VGui_ViewportPaintBackground( int extents[4] ) +{ +// Msg( "Vgui_ViewportPaintBackground( %i, %i, %i, %i )\n", extents[0], extents[1], extents[2], extents[3] ); +} + +void *VGui_GetPanel( void ) +{ + return (void *)rootPanel; +} \ No newline at end of file diff --git a/engine/client/vgui/vgui_main.h b/engine/client/vgui/vgui_main.h new file mode 100644 index 00000000..9d8145ca --- /dev/null +++ b/engine/client/vgui/vgui_main.h @@ -0,0 +1,117 @@ +/* +vgui_main.h - vgui main header +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef VGUI_MAIN_H +#define VGUI_MAIN_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +struct PaintStack +{ + Panel *m_pPanel; + int iTranslateX; + int iTranslateY; + int iScissorLeft; + int iScissorRight; + int iScissorTop; + int iScissorBottom; +}; + +class CEngineSurface : public SurfaceBase +{ +private: + void InitVertex( vpoint_t &vertex, int x, int y, float u, float v ); +public: + CEngineSurface( Panel *embeddedPanel ); + ~CEngineSurface(); +public: + // not used in engine instance + virtual bool setFullscreenMode( int wide, int tall, int bpp ) { return false; } + virtual void setWindowedMode( void ) { } + virtual void setTitle( const char *title ) { } + virtual void createPopup( Panel* embeddedPanel ) { } + virtual bool isWithin( int x, int y ) { return true; } + void SetupPaintState( const PaintStack *paintState ); +#ifdef NEW_VGUI_DLL + virtual void GetMousePos( int &x, int &y ) { } +#endif + virtual bool hasFocus( void ) { return true; } +protected: + virtual int createNewTextureID( void ); + virtual void drawSetColor( int r, int g, int b, int a ); + virtual void drawSetTextColor( int r, int g, int b, int a ); + virtual void drawFilledRect( int x0, int y0, int x1, int y1 ); + virtual void drawOutlinedRect( int x0,int y0,int x1,int y1 ); + virtual void drawSetTextFont( Font *font ); + virtual void drawSetTextPos( int x, int y ); + virtual void drawPrintText( const char* text, int textLen ); + virtual void drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ); + virtual void drawSetTexture( int id ); + virtual void drawTexturedRect( int x0, int y0, int x1, int y1 ); + virtual void drawPrintChar( int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[4] ); + virtual void addCharToBuffer( const vpoint_t *ul, const vpoint_t *lr, int color[4] ); + virtual void setCursor( Cursor* cursor ); + virtual void pushMakeCurrent( Panel* panel, bool useInsets ); + virtual void popMakeCurrent( Panel* panel ); + // not used in engine instance + virtual bool createPlat( void ) { return false; } + virtual bool recreateContext( void ) { return false; } + virtual void enableMouseCapture( bool state ) { } + virtual void invalidate( Panel *panel ) { } + virtual void setAsTopMost( bool state ) { } + virtual void applyChanges( void ) { } + virtual void swapBuffers( void ) { } + virtual void flushBuffer( void ); +protected: + int _drawTextPos[2]; + int _drawColor[4]; + int _drawTextColor[4]; + int _translateX, _translateY; + int _currentTexture; +}; + +// initialize VGUI::App as external (part of engine) +class CEngineApp : public App +{ +public: + virtual void main( int argc, char* argv[] ) { } + virtual void setCursorPos( int x, int y ); // we need to recompute abs position to window + virtual void getCursorPos( int &x,int &y ); +protected: + virtual void platTick(void) { } +}; + +extern Panel *rootPanel; +extern CEngineSurface *engSurface; +extern CEngineApp *engApp; + +// +// vgui_input.cpp +// +void VGUI_InitCursors( void ); +void VGUI_CursorSelect( Cursor *cursor ); +void VGUI_ActivateCurrentCursor( void ); + +#endif//VGUI_MAIN_H \ No newline at end of file diff --git a/engine/client/vgui/vgui_surf.cpp b/engine/client/vgui/vgui_surf.cpp new file mode 100644 index 00000000..9e28a9c8 --- /dev/null +++ b/engine/client/vgui/vgui_surf.cpp @@ -0,0 +1,465 @@ +/* +vgui_surf.cpp - main vgui layer +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "vgui_draw.h" +#include "vgui_main.h" + +#define MAXVERTEXBUFFERS 1024 +#define MAX_PAINT_STACK 8 +#define FONT_SIZE 512 +#define FONT_PAGES 8 + +static char staticRGBA[FONT_SIZE * FONT_SIZE * 4]; +static vpoint_t g_VertexBuffer[MAXVERTEXBUFFERS]; +static int g_iVertexBufferEntriesUsed = 0; +static int staticContextCount = 0; + +struct FontInfo +{ + int id; + int pageCount; + int pageForChar[256]; + int bindIndex[FONT_PAGES]; + float texCoord[256][FONT_PAGES]; + int contextCount; +}; + +static Font* staticFont = NULL; +static FontInfo* staticFontInfo; +static Dar staticFontInfoDar; +static PaintStack paintStack[MAX_PAINT_STACK]; +static staticPaintStackPos = 0; + +CEngineSurface :: CEngineSurface( Panel *embeddedPanel ):SurfaceBase( embeddedPanel ) +{ + _drawTextColor[0] = _drawTextColor[1] = _drawTextColor[2] = _drawTextColor[3] = 255; + _drawColor[0] = _drawColor[1] = _drawColor[2] = _drawColor[3] = 255; + _drawTextPos[0] = _drawTextPos[1] = _currentTexture = 0; + + staticFont = NULL; + staticFontInfo = NULL; + staticFontInfoDar.setCount( 0 ); + staticPaintStackPos = 0; + staticContextCount++; + + VGUI_InitCursors (); +} + +CEngineSurface :: ~CEngineSurface( void ) +{ + VGUI_DrawShutdown (); +} + +void CEngineSurface :: setCursor( Cursor *cursor ) +{ + _currentCursor = cursor; + VGUI_CursorSelect( cursor ); +} + +void CEngineSurface :: SetupPaintState( const PaintStack *paintState ) +{ + _translateX = paintState->iTranslateX; + _translateY = paintState->iTranslateY; + SetScissorRect( paintState->iScissorLeft, paintState->iScissorTop, paintState->iScissorRight, paintState->iScissorBottom ); +} + +void CEngineSurface :: InitVertex( vpoint_t &vertex, int x, int y, float u, float v ) +{ + vertex.point[0] = x + _translateX; + vertex.point[1] = y + _translateY; + vertex.coord[0] = u; + vertex.coord[1] = v; +} + +int CEngineSurface :: createNewTextureID( void ) +{ + return VGUI_GenerateTexture(); +} + +void CEngineSurface :: drawSetColor( int r, int g, int b, int a ) +{ + _drawColor[0] = r; + _drawColor[1] = g; + _drawColor[2] = b; + _drawColor[3] = a; +} + +void CEngineSurface :: drawSetTextColor( int r, int g, int b, int a ) +{ + _drawTextColor[0] = r; + _drawTextColor[1] = g; + _drawTextColor[2] = b; + _drawTextColor[3] = a; +} + +void CEngineSurface :: drawFilledRect( int x0, int y0, int x1, int y1 ) +{ + vpoint_t rect[2]; + vpoint_t clippedRect[2]; + + if( _drawColor[3] >= 255 ) return; + + InitVertex( rect[0], x0, y0, 0, 0 ); + InitVertex( rect[1], x1, y1, 0, 0 ); + + // fully clipped? + if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) + return; + + VGUI_SetupDrawingRect( _drawColor ); + VGUI_EnableTexture( false ); + VGUI_DrawQuad( &clippedRect[0], &clippedRect[1] ); + VGUI_EnableTexture( true ); +} + +void CEngineSurface :: drawOutlinedRect( int x0, int y0, int x1, int y1 ) +{ + if( _drawColor[3] >= 255 ) return; + + drawFilledRect( x0, y0, x1, y0 + 1 ); // top + drawFilledRect( x0, y1 - 1, x1, y1 ); // bottom + drawFilledRect( x0, y0 + 1, x0 + 1, y1 - 1 ); // left + drawFilledRect( x1 - 1, y0 + 1, x1, y1 - 1 ); // right +} + +void CEngineSurface :: drawSetTextFont( Font *font ) +{ + staticFont = font; + + if( font ) + { + bool buildFont = false; + + staticFontInfo = NULL; + + for( int i = 0; i < staticFontInfoDar.getCount(); i++ ) + { + if( staticFontInfoDar[i]->id == font->getId( )) + { + staticFontInfo = staticFontInfoDar[i]; + if( staticFontInfo->contextCount != staticContextCount ) + buildFont = true; + } + } + + if( !staticFontInfo || buildFont ) + { + staticFontInfo = new FontInfo; + staticFontInfo->id = 0; + staticFontInfo->pageCount = 0; + staticFontInfo->bindIndex[0] = 0; + staticFontInfo->bindIndex[1] = 0; + staticFontInfo->bindIndex[2] = 0; + staticFontInfo->bindIndex[3] = 0; + memset( staticFontInfo->pageForChar, 0, sizeof( staticFontInfo->pageForChar )); + staticFontInfo->contextCount = -1; + staticFontInfo->id = staticFont->getId(); + staticFontInfoDar.putElement( staticFontInfo ); + staticFontInfo->contextCount = staticContextCount; + + int currentPage = 0; + int x = 0, y = 0; + + memset( staticRGBA, 0, sizeof( staticRGBA )); + + for( int i = 0; i < 256; i++ ) + { + int abcA, abcB, abcC; + staticFont->getCharABCwide( i, abcA, abcB, abcC ); + + int wide = abcB; + + if( isspace( i )) continue; + + int tall = staticFont->getTall(); + + if( x + wide + 1 > FONT_SIZE ) + { + x = 0; + y += tall + 1; + } + + if( y + tall + 1 > FONT_SIZE ) + { + if( !staticFontInfo->bindIndex[currentPage] ) + { + int bindIndex = createNewTextureID(); + staticFontInfo->bindIndex[currentPage] = bindIndex; + } + + drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); + currentPage++; + + if( currentPage == FONT_PAGES ) + break; + + memset( staticRGBA, 0, sizeof( staticRGBA )); + x = y = 0; + } + + staticFont->getCharRGBA( i, x, y, FONT_SIZE, FONT_SIZE, (byte *)staticRGBA ); + staticFontInfo->pageForChar[i] = currentPage; + staticFontInfo->texCoord[i][0] = (float)((double)x / (double)FONT_SIZE ); + staticFontInfo->texCoord[i][1] = (float)((double)y / (double)FONT_SIZE ); + staticFontInfo->texCoord[i][2] = (float)((double)(x + wide)/(double)FONT_SIZE ); + staticFontInfo->texCoord[i][3] = (float)((double)(y + tall)/(double)FONT_SIZE ); + x += wide + 1; + } + + if( currentPage != FONT_PAGES ) + { + if( !staticFontInfo->bindIndex[currentPage] ) + { + int bindIndex = createNewTextureID(); + staticFontInfo->bindIndex[currentPage] = bindIndex; + } + + drawSetTextureRGBA( staticFontInfo->bindIndex[currentPage], staticRGBA, FONT_SIZE, FONT_SIZE ); + } + staticFontInfo->pageCount = currentPage + 1; + } + } +} + +void CEngineSurface :: drawSetTextPos( int x, int y ) +{ + _drawTextPos[0] = x; + _drawTextPos[1] = y; +} + +void CEngineSurface :: addCharToBuffer( const vpoint_t *ul, const vpoint_t *lr, int color[4] ) +{ + if( g_iVertexBufferEntriesUsed >= MAXVERTEXBUFFERS ) + flushBuffer(); + + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].coord[0] = ul->coord[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].coord[1] = ul->coord[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].point[0] = ul->point[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].point[1] = ul->point[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].color[0] = color[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].color[1] = color[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].color[2] = color[2]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 0].color[3] = 255 - color[3]; + + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].coord[0] = lr->coord[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].coord[1] = ul->coord[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].point[0] = lr->point[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].point[1] = ul->point[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].color[0] = color[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].color[1] = color[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].color[2] = color[2]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 1].color[3] = 255 - color[3]; + + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].coord[0] = lr->coord[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].coord[1] = lr->coord[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].point[0] = lr->point[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].point[1] = lr->point[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].color[0] = color[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].color[1] = color[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].color[2] = color[2]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 2].color[3] = 255 - color[3]; + + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].coord[0] = ul->coord[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].coord[1] = lr->coord[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].point[0] = ul->point[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].point[1] = lr->point[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].color[0] = color[0]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].color[1] = color[1]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].color[2] = color[2]; + g_VertexBuffer[g_iVertexBufferEntriesUsed + 3].color[3] = 255 - color[3]; + + g_iVertexBufferEntriesUsed += 4; +} + +void CEngineSurface :: flushBuffer( void ) +{ + if( g_iVertexBufferEntriesUsed <= 0 ) + return; + + VGUI_DrawBuffer( g_VertexBuffer, g_iVertexBufferEntriesUsed ); + g_iVertexBufferEntriesUsed = 0; +} + +void CEngineSurface :: drawPrintChar( int x, int y, int wide, int tall, float s0, float t0, float s1, float t1, int color[4] ) +{ + vpoint_t ul, lr; + + ul.point[0] = x; + ul.point[1] = y; + lr.point[0] = x + wide; + lr.point[1] = y + tall; + + // gets at the texture coords for this character in its texture page + ul.coord[0] = s0; + ul.coord[1] = t0; + lr.coord[0] = s1; + lr.coord[1] = t1; + + vpoint_t clippedRect[2]; + + if( !ClipRect( ul, lr, &clippedRect[0], &clippedRect[1] )) + return; +#if 1 + // TESTTEST: needs to be more tested + addCharToBuffer( &clippedRect[0], &clippedRect[1], color ); +#else + VGUI_SetupDrawingImage( color ); + VGUI_DrawQuad( &clippedRect[0], &clippedRect[1] ); // draw the letter +#endif +} + +void CEngineSurface :: drawPrintText( const char *text, int textLen ) +{ + static bool hasColor = 0; + static int numColor = 7; + + if( !text || !staticFont || !staticFontInfo ) + return; + + int x = _drawTextPos[0] + _translateX; + int y = _drawTextPos[1] + _translateY; + int tall = staticFont->getTall(); + int curTextColor[4]; + + // HACKHACK: allow color strings in VGUI + if( numColor != 7 && vgui_colorstrings->value ) + { + for( int j = 0; j < 3; j++ ) // grab predefined color + curTextColor[j] = g_color_table[numColor][j]; + } + else + { + for( int j = 0; j < 3; j++ ) // revert default color + curTextColor[j] = _drawTextColor[j]; + } + curTextColor[3] = _drawTextColor[3]; // copy alpha + + if( textLen == 1 && vgui_colorstrings->value ) + { + if( *text == '^' ) + { + hasColor = true; + return; // skip '^' + } + else if( hasColor && isdigit( *text )) + { + numColor = ColorIndex( *text ); + hasColor = false; // handled + return; // skip colornum + } + else hasColor = false; + } + + for( int i = 0; i < textLen; i++ ) + { + int abcA, abcB, abcC; + int curCh = (byte)text[i]; + + staticFont->getCharABCwide( curCh, abcA, abcB, abcC ); + + float s0 = staticFontInfo->texCoord[curCh][0]; + float t0 = staticFontInfo->texCoord[curCh][1]; + float s1 = staticFontInfo->texCoord[curCh][2]; + float t1 = staticFontInfo->texCoord[curCh][3]; + int wide = abcB; + + drawSetTexture( staticFontInfo->bindIndex[staticFontInfo->pageForChar[curCh]] ); + drawPrintChar( x, y, wide, tall, s0, t0, s1, t1, curTextColor ); + x += abcA + abcB + abcC; + } + + _drawTextPos[0] += x; +} + +void CEngineSurface :: drawSetTextureRGBA( int id, const char* rgba, int wide, int tall ) +{ + VGUI_UploadTexture( id, rgba, wide, tall ); + _currentTexture = id; +} + +void CEngineSurface :: drawSetTexture( int id ) +{ + if( _currentTexture != id ) + { + _currentTexture = id; + flushBuffer(); + } + VGUI_BindTexture( id ); +} + +void CEngineSurface :: drawTexturedRect( int x0, int y0, int x1, int y1 ) +{ + vpoint_t rect[2]; + vpoint_t clippedRect[2]; + + InitVertex( rect[0], x0, y0, 0, 0 ); + InitVertex( rect[1], x1, y1, 1, 1 ); + + // fully clipped? + if( !ClipRect( rect[0], rect[1], &clippedRect[0], &clippedRect[1] )) + return; + + VGUI_SetupDrawingImage( _drawColor ); + VGUI_DrawQuad( &clippedRect[0], &clippedRect[1] ); +} + +void CEngineSurface :: pushMakeCurrent( Panel* panel, bool useInsets ) +{ + int insets[4] = { 0, 0, 0, 0 }; + int absExtents[4]; + int clipRect[4]; + + if( useInsets ) + panel->getInset( insets[0], insets[1], insets[2], insets[3] ); + panel->getAbsExtents( absExtents[0], absExtents[1], absExtents[2], absExtents[3] ); + panel->getClipRect( clipRect[0], clipRect[1], clipRect[2], clipRect[3] ); + + PaintStack *paintState = &paintStack[staticPaintStackPos]; + + ASSERT( staticPaintStackPos < MAX_PAINT_STACK ); + + paintState->m_pPanel = panel; + + // determine corrected top left origin + paintState->iTranslateX = insets[0] + absExtents[0]; + paintState->iTranslateY = insets[1] + absExtents[1]; + // setup clipping rectangle for scissoring + paintState->iScissorLeft = clipRect[0]; + paintState->iScissorTop = clipRect[1]; + paintState->iScissorRight = clipRect[2]; + paintState->iScissorBottom = clipRect[3]; + + SetupPaintState( paintState ); + staticPaintStackPos++; +} + +void CEngineSurface :: popMakeCurrent( Panel *panel ) +{ + flushBuffer(); + + int top = staticPaintStackPos - 1; + + // more pops that pushes? + Assert( top >= 0 ); + + // didn't pop in reverse order of push? + Assert( paintStack[top].m_pPanel == panel ); + + staticPaintStackPos--; + + if( top > 0 ) SetupPaintState( &paintStack[top-1] ); +} \ No newline at end of file diff --git a/engine/client/vox.h b/engine/client/vox.h new file mode 100644 index 00000000..d043dd9a --- /dev/null +++ b/engine/client/vox.h @@ -0,0 +1,47 @@ +/* +vox.h - sentences vox private header +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef VOX_H +#define VOX_H + +#define CVOXWORDMAX 64 +#define CVOXZEROSCANMAX 255 // scan up to this many samples for next zero crossing +#define MAX_SENTENCES 2048 +#define SENTENCE_INDEX -99999 // unique sentence index + +typedef struct voxword_s +{ + int volume; // increase percent, ie: 125 = 125% increase + int pitch; // pitch shift up percent + int start; // offset start of wave percent + int end; // offset end of wave percent + int cbtrim; // end of wave after being trimmed to 'end' + int fKeepCached; // 1 if this word was already in cache before sentence referenced it + int samplefrac; // if pitch shifting, this is position into wav * 256 + int timecompress; // % of wave to skip during playback (causes no pitch shift) + sfx_t *sfx; // name and cache pointer +} voxword_t; + + +typedef struct +{ + char *pName; + float length; +} sentence_t; + +void VOX_LoadWord( struct channel_s *pchan ); +void VOX_FreeWord( struct channel_s *pchan ); + +#endif \ No newline at end of file diff --git a/engine/common/avikit.c b/engine/common/avikit.c new file mode 100644 index 00000000..1fb6ead2 --- /dev/null +++ b/engine/common/avikit.c @@ -0,0 +1,712 @@ +/* +avikit.c - playing AVI files (based on original AVIKit code) +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "gl_local.h" +#include // video for windows + +// msvfw32.dll exports +static HDRAWDIB (_stdcall *pDrawDibOpen)( void ); +static BOOL (_stdcall *pDrawDibClose)( HDRAWDIB hdd ); +static BOOL (_stdcall *pDrawDibDraw)( HDRAWDIB, HDC, int, int, int, int, LPBITMAPINFOHEADER, void*, int, int, int, int, uint ); + +static dllfunc_t msvfw_funcs[] = +{ +{ "DrawDibOpen", (void **) &pDrawDibOpen }, +{ "DrawDibDraw", (void **) &pDrawDibDraw }, +{ "DrawDibClose", (void **) &pDrawDibClose }, +{ NULL, NULL } +}; + +dll_info_t msvfw_dll = { "msvfw32.dll", msvfw_funcs, false }; + +// msacm32.dll exports +static MMRESULT (_stdcall *pacmStreamOpen)( LPHACMSTREAM, HACMDRIVER, LPWAVEFORMATEX, LPWAVEFORMATEX, LPWAVEFILTER, DWORD, DWORD, DWORD ); +static MMRESULT (_stdcall *pacmStreamPrepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); +static MMRESULT (_stdcall *pacmStreamUnprepareHeader)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); +static MMRESULT (_stdcall *pacmStreamConvert)( HACMSTREAM, LPACMSTREAMHEADER, DWORD ); +static MMRESULT (_stdcall *pacmStreamSize)( HACMSTREAM, DWORD, LPDWORD, DWORD ); +static MMRESULT (_stdcall *pacmStreamClose)( HACMSTREAM, DWORD ); + +static dllfunc_t msacm_funcs[] = +{ +{ "acmStreamOpen", (void **) &pacmStreamOpen }, +{ "acmStreamPrepareHeader", (void **) &pacmStreamPrepareHeader }, +{ "acmStreamUnprepareHeader", (void **) &pacmStreamUnprepareHeader }, +{ "acmStreamConvert", (void **) &pacmStreamConvert }, +{ "acmStreamSize", (void **) &pacmStreamSize }, +{ "acmStreamClose", (void **) &pacmStreamClose }, +{ NULL, NULL } +}; + +dll_info_t msacm_dll = { "msacm32.dll", msacm_funcs, false }; + +// avifil32.dll exports +static int (_stdcall *pAVIStreamInfo)( PAVISTREAM pavi, AVISTREAMINFO *psi, LONG lSize ); +static int (_stdcall *pAVIStreamRead)( PAVISTREAM pavi, LONG lStart, LONG lSamples, void *lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples ); +static PGETFRAME (_stdcall *pAVIStreamGetFrameOpen)( PAVISTREAM pavi, LPBITMAPINFOHEADER lpbiWanted ); +static void* (_stdcall *pAVIStreamGetFrame)( PGETFRAME pg, LONG lPos ); +static int (_stdcall *pAVIStreamGetFrameClose)( PGETFRAME pg ); +static dword (_stdcall *pAVIStreamRelease)( PAVISTREAM pavi ); +static int (_stdcall *pAVIFileOpen)( PAVIFILE *ppfile, LPCSTR szFile, UINT uMode, LPCLSID lpHandler ); +static int (_stdcall *pAVIFileGetStream)( PAVIFILE pfile, PAVISTREAM *ppavi, DWORD fccType, LONG lParam ); +static int (_stdcall *pAVIStreamReadFormat)( PAVISTREAM pavi, LONG lPos,LPVOID lpFormat, LONG *lpcbFormat ); +static long (_stdcall *pAVIStreamStart)( PAVISTREAM pavi ); +static dword (_stdcall *pAVIFileRelease)( PAVIFILE pfile ); +static void (_stdcall *pAVIFileInit)( void ); +static void (_stdcall *pAVIFileExit)( void ); + +static dllfunc_t avifile_funcs[] = +{ +{ "AVIFileExit", (void **) &pAVIFileExit }, +{ "AVIFileGetStream", (void **) &pAVIFileGetStream }, +{ "AVIFileInit", (void **) &pAVIFileInit }, +{ "AVIFileOpenA", (void **) &pAVIFileOpen }, +{ "AVIFileRelease", (void **) &pAVIFileRelease }, +{ "AVIStreamGetFrame", (void **) &pAVIStreamGetFrame }, +{ "AVIStreamGetFrameClose", (void **) &pAVIStreamGetFrameClose }, +{ "AVIStreamGetFrameOpen", (void **) &pAVIStreamGetFrameOpen }, +{ "AVIStreamInfoA", (void **) &pAVIStreamInfo }, +{ "AVIStreamRead", (void **) &pAVIStreamRead }, +{ "AVIStreamReadFormat", (void **) &pAVIStreamReadFormat }, +{ "AVIStreamRelease", (void **) &pAVIStreamRelease }, +{ "AVIStreamStart", (void **) &pAVIStreamStart }, +{ NULL, NULL } +}; + +dll_info_t avifile_dll = { "avifil32.dll", avifile_funcs, false }; + +typedef struct movie_state_s +{ + qboolean active; + qboolean quiet; // ignore error messages + + PAVIFILE pfile; // avi file pointer + PAVISTREAM video_stream; // video stream pointer + PGETFRAME video_getframe; // pointer to getframe object for video stream + long video_frames; // total frames + long video_xres; // video stream resolution + long video_yres; + float video_fps; // video stream fps + + PAVISTREAM audio_stream; // audio stream pointer + WAVEFORMAT *audio_header; // audio stream header + long audio_header_size; // WAVEFORMAT is returned for PCM data; WAVEFORMATEX for others + long audio_codec; // WAVE_FORMAT_PCM is oldstyle: anything else needs conversion + long audio_length; // in converted samples + long audio_bytes_per_sample; // guess. + + // compressed audio specific data + dword cpa_blockalign; // block size to read + HACMSTREAM cpa_conversion_stream; + ACMSTREAMHEADER cpa_conversion_header; + byte *cpa_srcbuffer; // maintained buffer for raw data + byte *cpa_dstbuffer; + + dword cpa_blocknum; // current block + dword cpa_blockpos; // read position in current block + dword cpa_blockoffset; // corresponding offset in bytes in the output stream + + // for additional unpack Ms-RLE codecs etc + HDC hDC; // compatible DC + HDRAWDIB hDD; // DrawDib handler + HBITMAP hBitmap; // for DIB conversions + byte *pframe_data; // converted framedata +} movie_state_t; + +static qboolean avi_initialized = false; +static movie_state_t avi[2]; + +// Converts a compressed audio stream into uncompressed PCM. +qboolean AVI_ACMConvertAudio( movie_state_t *Avi ) +{ + WAVEFORMATEX dest_header, *sh, *dh; + AVISTREAMINFO stream_info; + dword dest_length; + short bits; + + // WMA codecs, both versions - they simply don't work. + if( Avi->audio_header->wFormatTag == 0x160 || Avi->audio_header->wFormatTag == 0x161 ) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "ACM does not support this audio codec.\n" ); + return false; + } + + // get audio stream info to work with + pAVIStreamInfo( Avi->audio_stream, &stream_info, sizeof( stream_info )); + + if( Avi->audio_header_size < sizeof( WAVEFORMATEX )) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "ACM failed to open conversion stream.\n" ); + return false; + } + + sh = (WAVEFORMATEX *)Avi->audio_header; + bits = 16; // predict state + + // how much of this is actually required? + dest_header.wFormatTag = WAVE_FORMAT_PCM; // yay + dest_header.wBitsPerSample = bits; // 16bit + dest_header.nChannels = sh->nChannels; + dest_header.nSamplesPerSec = sh->nSamplesPerSec; // take straight from the source stream + dest_header.nAvgBytesPerSec = (bits >> 3) * sh->nChannels * sh->nSamplesPerSec; + dest_header.nBlockAlign = (bits >> 3) * sh->nChannels; + dest_header.cbSize = 0; // no more data. + + dh = &dest_header; + + // open the stream + if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) + { + // try with 8 bit destination instead + bits = 8; + + dest_header.wBitsPerSample = bits; // 8bit + dest_header.nAvgBytesPerSec = ( bits >> 3 ) * sh->nChannels * sh->nSamplesPerSec; + dest_header.nBlockAlign = ( bits >> 3 ) * sh->nChannels; // 1 sample at a time + + if( pacmStreamOpen( &Avi->cpa_conversion_stream, NULL, sh, dh, NULL, 0, 0, 0 ) != MMSYSERR_NOERROR ) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "ACM failed to open conversion stream.\n" ); + return false; + } + } + + Avi->cpa_blockalign = sh->nBlockAlign; + dest_length = 0; + + // mp3 specific fix + if( sh->wFormatTag == 0x55 ) + { + LPMPEGLAYER3WAVEFORMAT k; + + k = (LPMPEGLAYER3WAVEFORMAT)sh; + Avi->cpa_blockalign = k->nBlockSize; + } + + // get the size of the output buffer for streaming the compressed audio + if( pacmStreamSize( Avi->cpa_conversion_stream, Avi->cpa_blockalign, &dest_length, ACM_STREAMSIZEF_SOURCE ) != MMSYSERR_NOERROR ) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "Couldn't get ACM conversion stream size.\n" ); + pacmStreamClose( Avi->cpa_conversion_stream, 0 ); + return false; + } + + Avi->cpa_srcbuffer = (byte *)Mem_Alloc( cls.mempool, Avi->cpa_blockalign ); + Avi->cpa_dstbuffer = (byte *)Mem_Alloc( cls.mempool, dest_length ); // maintained buffer for raw data + + // prep the headers! + Avi->cpa_conversion_header.cbStruct = sizeof( ACMSTREAMHEADER ); + Avi->cpa_conversion_header.fdwStatus = 0; + Avi->cpa_conversion_header.dwUser = 0; // no user data + Avi->cpa_conversion_header.pbSrc = Avi->cpa_srcbuffer; // source buffer + Avi->cpa_conversion_header.cbSrcLength = Avi->cpa_blockalign; // source buffer size + Avi->cpa_conversion_header.cbSrcLengthUsed = 0; + Avi->cpa_conversion_header.dwSrcUser = 0; // no user data + Avi->cpa_conversion_header.pbDst = Avi->cpa_dstbuffer; // dest buffer + Avi->cpa_conversion_header.cbDstLength = dest_length; // dest buffer size + Avi->cpa_conversion_header.cbDstLengthUsed = 0; + Avi->cpa_conversion_header.dwDstUser = 0; // no user data + + if( pacmStreamPrepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ) != MMSYSERR_NOERROR ) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "couldn't prep headers.\n" ); + pacmStreamClose( Avi->cpa_conversion_stream, 0 ); + return false; + } + + Avi->cpa_blocknum = 0; // start at 0. + Avi->cpa_blockpos = 0; + Avi->cpa_blockoffset = 0; + + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); + + // convert first chunk twice. it often fails the first time. BLACK MAGIC. + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); + + Avi->audio_bytes_per_sample = (bits >> 3 ) * Avi->audio_header->nChannels; + + return true; +} + +qboolean AVI_GetVideoInfo( movie_state_t *Avi, long *xres, long *yres, float *duration ) +{ + if( !Avi->active ) + return false; + + if( xres != NULL ) + *xres = Avi->video_xres; + + if( yres != NULL ) + *yres = Avi->video_yres; + + if( duration != NULL ) + *duration = (float)Avi->video_frames / Avi->video_fps; + + return true; +} + +// returns a unique frame identifier +long AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ) +{ + if( !Avi->active ) + return 0; + + return (time * Avi->video_fps); +} + +// gets the raw frame data +byte *AVI_GetVideoFrame( movie_state_t *Avi, long frame ) +{ + LPBITMAPINFOHEADER frame_info; + byte *frame_raw; + + if( !Avi->active ) return NULL; + + if( frame >= Avi->video_frames ) + frame = Avi->video_frames - 1; + + frame_info = (LPBITMAPINFOHEADER)pAVIStreamGetFrame( Avi->video_getframe, frame ); + frame_raw = (byte *)frame_info + frame_info->biSize + frame_info->biClrUsed * sizeof( RGBQUAD ); + pDrawDibDraw( Avi->hDD, Avi->hDC, 0, 0, Avi->video_xres, Avi->video_yres, frame_info, frame_raw, 0, 0, Avi->video_xres, Avi->video_yres, 0 ); + + return Avi->pframe_data; +} + +qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ) +{ + if( !Avi->active || Avi->audio_stream == NULL || snd_info == NULL ) + { + return false; + } + + snd_info->rate = Avi->audio_header->nSamplesPerSec; + snd_info->channels = Avi->audio_header->nChannels; + + if( Avi->audio_codec == WAVE_FORMAT_PCM ) // uncompressed audio! + snd_info->width = ( Avi->audio_bytes_per_sample > Avi->audio_header->nChannels ) ? 2 : 1; + else snd_info->width = 2; // assume compressed audio is always 16 bit + + snd_info->size = snd_info->rate * snd_info->width * snd_info->channels; + snd_info->loopStart = 0; // using loopStart as streampos + + return true; +} + +// sync the current audio read to a specific offset +qboolean AVI_SeekPosition( movie_state_t *Avi, dword offset ) +{ + int breaker; + + if( offset < Avi->cpa_blockoffset ) // well, shit. we can't seek backwards... restart + { + if( Avi->cpa_blockoffset - offset < 500000 ) + return false; // don't bother if it's gonna catch up soon + + Avi->cpa_blocknum = 0; // start at 0, eh. + Avi->cpa_blockpos = 0; + Avi->cpa_blockoffset = 0; + + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN|ACM_STREAMCONVERTF_START ); + + // convert first chunk twice. it often fails the first time. BLACK MAGIC. + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); + } + + // now then: seek forwards to the required block + breaker = 30; // maximum zero blocks: anti-freeze protection + + while( Avi->cpa_blockoffset + Avi->cpa_conversion_header.cbDstLengthUsed < offset ) + { + Avi->cpa_blocknum++; + Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; + + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); + + if( Avi->cpa_conversion_header.cbDstLengthUsed == 0 ) + breaker--; + else breaker = 30; + + if( breaker <= 0 ) + return false; + + Avi->cpa_blockpos = 0; + } + + // seek to the right position inside the block + Avi->cpa_blockpos = offset - Avi->cpa_blockoffset; + + return true; +} + +// get a chunk of audio from the stream (in bytes) +long AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, long offset, long length ) +{ + long result = 0; + int i; + + // zero data past the end of the file + if( offset + length > Avi->audio_length ) + { + if( offset <= Avi->audio_length ) + { + long remaining_length = Avi->audio_length - offset; + + AVI_GetAudioChunk( Avi, audiodata, offset, remaining_length ); + + for( i = remaining_length; i < length; i++ ) + audiodata[i] = 0; + } + else + { + for( i = 0; i < length; i++ ) + audiodata[i] = 0; + } + } + + // uncompressed audio! + if( Avi->audio_codec == WAVE_FORMAT_PCM ) + { + // very simple - read straight out + pAVIStreamRead( Avi->audio_stream, offset / Avi->audio_bytes_per_sample, length / Avi->audio_bytes_per_sample, audiodata, length, &result, NULL ); + return result; + } + else + { + // compressed audio! + result = 0; + + // seek to correct chunk and all that stuff + if( !AVI_SeekPosition( Avi, offset )) + return 0; // don't continue if we're waiting for the play pointer to catch up + + while( length > 0 ) + { + long blockread = Avi->cpa_conversion_header.cbDstLengthUsed - Avi->cpa_blockpos; + + if( blockread <= 0 ) // read next + { + Avi->cpa_blocknum++; + Avi->cpa_blockoffset += Avi->cpa_conversion_header.cbDstLengthUsed; + + pAVIStreamRead( Avi->audio_stream, Avi->cpa_blocknum * Avi->cpa_blockalign, Avi->cpa_blockalign, Avi->cpa_srcbuffer, Avi->cpa_blockalign, NULL, NULL ); + pacmStreamConvert( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, ACM_STREAMCONVERTF_BLOCKALIGN ); + + Avi->cpa_blockpos = 0; + continue; + } + + if( blockread > length ) + blockread = length; + + // copy the data + memcpy( audiodata + result, (void *)( Avi->cpa_dstbuffer + Avi->cpa_blockpos ), blockread ); + + Avi->cpa_blockpos += blockread; + result += blockread; + length -= blockread; + } + + return result; + } +} + +void AVI_CloseVideo( movie_state_t *Avi ) +{ + if( Avi->active ) + { + pAVIStreamGetFrameClose( Avi->video_getframe ); + + if( Avi->audio_stream != NULL ) + { + pAVIStreamRelease( Avi->audio_stream ); + Mem_Free( Avi->audio_header ); + + if( Avi->audio_codec != WAVE_FORMAT_PCM ) + { + pacmStreamUnprepareHeader( Avi->cpa_conversion_stream, &Avi->cpa_conversion_header, 0 ); + pacmStreamClose( Avi->cpa_conversion_stream, 0 ); + Mem_Free( Avi->cpa_srcbuffer ); + Mem_Free( Avi->cpa_dstbuffer ); + } + } + + pAVIStreamRelease( Avi->video_stream ); + + DeleteObject( Avi->hBitmap ); + pDrawDibClose( Avi->hDD ); + DeleteDC( Avi->hDC ); + } + + memset( Avi, 0, sizeof( movie_state_t )); +} + +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ) +{ + BITMAPINFOHEADER bmih; + AVISTREAMINFO stream_info; + long opened_streams = 0; + LONG hr; + + // default state: non-working. + Avi->active = false; + Avi->quiet = quiet; + + // can't load Video For Windows :-( + if( !avi_initialized ) return; + + // load the AVI + hr = pAVIFileOpen( &Avi->pfile, filename, OF_SHARE_DENY_WRITE, 0L ); + + if( hr != 0 ) // error opening AVI: + { + switch( hr ) + { + case AVIERR_BADFORMAT: + if( !Avi->quiet ) MsgDev( D_ERROR, "corrupt file or unknown format.\n" ); + break; + case AVIERR_MEMORY: + if( !Avi->quiet ) MsgDev( D_ERROR, "insufficient memory to open file.\n" ); + break; + case AVIERR_FILEREAD: + if( !Avi->quiet ) MsgDev( D_ERROR, "disk error reading file.\n" ); + break; + case AVIERR_FILEOPEN: + if( !Avi->quiet ) MsgDev( D_ERROR, "disk error opening file.\n" ); + break; + case REGDB_E_CLASSNOTREG: + default: + if( !Avi->quiet ) MsgDev( D_ERROR, "no handler found (or file not found).\n" ); + break; + } + return; + } + + Avi->video_stream = Avi->audio_stream = NULL; + + // open the streams until a stream is not available. + while( 1 ) + { + PAVISTREAM stream = NULL; + + if( pAVIFileGetStream( Avi->pfile, &stream, 0L, opened_streams++ ) != AVIERR_OK ) + break; + + if( stream == NULL ) + break; + + pAVIStreamInfo( stream, &stream_info, sizeof( stream_info )); + + if( stream_info.fccType == streamtypeVIDEO && Avi->video_stream == NULL ) + { + Avi->video_stream = stream; + Avi->video_frames = stream_info.dwLength; + Avi->video_xres = stream_info.rcFrame.right - stream_info.rcFrame.left; + Avi->video_yres = stream_info.rcFrame.bottom - stream_info.rcFrame.top; + Avi->video_fps = (float)stream_info.dwRate / (float)stream_info.dwScale; + } + else if( stream_info.fccType == streamtypeAUDIO && Avi->audio_stream == NULL && load_audio ) + { + long size; + + Avi->audio_stream = stream; + + // read the audio header + pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), 0, &size ); + + Avi->audio_header = (WAVEFORMAT *)Mem_Alloc( cls.mempool, size ); + pAVIStreamReadFormat( Avi->audio_stream, pAVIStreamStart( Avi->audio_stream ), Avi->audio_header, &size ); + Avi->audio_header_size = size; + Avi->audio_codec = Avi->audio_header->wFormatTag; + + // length of converted audio in samples + Avi->audio_length = (long)((float)stream_info.dwLength / Avi->audio_header->nAvgBytesPerSec ); + Avi->audio_length *= Avi->audio_header->nSamplesPerSec; + + if( Avi->audio_codec != WAVE_FORMAT_PCM ) + { + if( !AVI_ACMConvertAudio( Avi )) + { + Mem_Free( Avi->audio_header ); + Avi->audio_stream = NULL; + continue; + } + } + else Avi->audio_bytes_per_sample = Avi->audio_header->nBlockAlign; + Avi->audio_length *= Avi->audio_bytes_per_sample; + } + else + { + pAVIStreamRelease( stream ); + } + } + + // display error message-stream not found. + if( Avi->video_stream == NULL ) + { + if( Avi->pfile ) // if file is open, close it + pAVIFileRelease( Avi->pfile ); + if( !Avi->quiet ) MsgDev( D_ERROR, "couldn't find a valid video stream.\n" ); + return; + } + + pAVIFileRelease( Avi->pfile ); // release the file + Avi->video_getframe = pAVIStreamGetFrameOpen( Avi->video_stream, NULL ); // open the frame getter + + if( Avi->video_getframe == NULL ) + { + if( !Avi->quiet ) MsgDev( D_ERROR, "error attempting to read video frames.\n" ); + return; // couldn't open frame getter. + } + + bmih.biSize = sizeof( BITMAPINFOHEADER ); + bmih.biPlanes = 1; + bmih.biBitCount = 32; + bmih.biCompression = BI_RGB; + bmih.biWidth = Avi->video_xres; + bmih.biHeight = -Avi->video_yres; // invert height to flip image upside down + + Avi->hDC = CreateCompatibleDC( 0 ); + Avi->hDD = pDrawDibOpen(); + Avi->hBitmap = CreateDIBSection( Avi->hDC, (BITMAPINFO*)(&bmih), DIB_RGB_COLORS, (void**)(&Avi->pframe_data), NULL, 0 ); + SelectObject( Avi->hDC, Avi->hBitmap ); + + Avi->active = true; // done +} + +qboolean AVI_IsActive( movie_state_t *Avi ) +{ + if( Avi != NULL ) + return Avi->active; + return false; +} + +movie_state_t *AVI_GetState( int num ) +{ + return &avi[num]; +} + +/* +============= +AVIKit user interface + +============= +*/ +movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ) +{ + movie_state_t *Avi; + string path; + const char *fullpath; + + // fast reject + if( !avi_initialized ) + { + MsgDev( D_ERROR, "AVI_LoadVideo: movie support is disabled\n" ); + return NULL; + } + + // open cinematic + Q_snprintf( path, sizeof( path ), "media/%s", filename ); + COM_DefaultExtension( path, ".avi" ); + fullpath = FS_GetDiskPath( path, false ); + + if( FS_FileExists( path, false ) && !fullpath ) + { + MsgDev( D_ERROR, "AVI_LoadVideo: Couldn't load %s from packfile. Please extract it\n", path ); + return NULL; + } + + Avi = Mem_Alloc( cls.mempool, sizeof( movie_state_t )); + AVI_OpenVideo( Avi, fullpath, load_audio, false ); + + if( !AVI_IsActive( Avi )) + { + AVI_FreeVideo( Avi ); // something bad happens + return NULL; + } + + // all done + return Avi; +} + +movie_state_t *AVI_LoadVideoNoSound( const char *filename ) +{ + return AVI_LoadVideo( filename, false ); +} + +void AVI_FreeVideo( movie_state_t *state ) +{ + if( !state ) return; + + if( Mem_IsAllocatedExt( cls.mempool, state )) + { + AVI_CloseVideo( state ); + Mem_Free( state ); + } +} + +qboolean AVI_Initailize( void ) +{ + if( Sys_CheckParm( "-noavi" )) + { + MsgDev( D_INFO, "AVI: Disabled\n" ); + return false; + } + + if( !Sys_LoadLibrary( &avifile_dll )) + { + MsgDev( D_ERROR, "AVI_Initailize: failed\n" ); + return false; + } + + if( !Sys_LoadLibrary( &msvfw_dll )) + { + MsgDev( D_ERROR, "AVI_Initailize: failed\n" ); + Sys_FreeLibrary( &avifile_dll ); + return false; + } + + if( !Sys_LoadLibrary( &msacm_dll )) + { + MsgDev( D_ERROR, "AVI_Initailize: failed\n" ); + Sys_FreeLibrary( &avifile_dll ); + Sys_FreeLibrary( &msvfw_dll ); + return false; + } + + pAVIFileInit(); + avi_initialized = true; + MsgDev( D_NOTE, "AVI_Initailize: done\n" ); + + return true; +} + +void AVI_Shutdown( void ) +{ + if( !avi_initialized ) return; + + pAVIFileExit(); + + Sys_FreeLibrary( &avifile_dll ); + Sys_FreeLibrary( &msvfw_dll ); + Sys_FreeLibrary( &msacm_dll ); + avi_initialized = false; +} \ No newline at end of file diff --git a/engine/common/build.c b/engine/common/build.c new file mode 100644 index 00000000..b7f5df34 --- /dev/null +++ b/engine/common/build.c @@ -0,0 +1,53 @@ +/* +build.c - returns a engine build number +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +static char *date = __DATE__ ; +static char *mon[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; +static char mond[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +// returns days since Feb 13 2007 +int Q_buildnum( void ) +{ +// do not touch this! Only author of Xash3D can increase buildnumbers! +#if 1 + int m = 0, d = 0, y = 0; + static int b = 0; + + if( b != 0 ) return b; + + for( m = 0; m < 11; m++ ) + { + if( !Q_strnicmp( &date[0], mon[m], 3 )) + break; + d += mond[m]; + } + + d += Q_atoi( &date[4] ) - 1; + y = Q_atoi( &date[7] ) - 1900; + b = d + (int)((y - 1) * 365.25f ); + + if((( y % 4 ) == 0 ) && m > 1 ) + { + b += 1; + } + b -= 38752; // Feb 13 2007 + + return b; +#else + return 3847; +#endif +} \ No newline at end of file diff --git a/engine/common/cfgscript.c b/engine/common/cfgscript.c new file mode 100644 index 00000000..92aee5a5 --- /dev/null +++ b/engine/common/cfgscript.c @@ -0,0 +1,341 @@ +/* +cfgscript.c - "Valve script" parsing routines +Copyright (C) 2016 mittorn + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +typedef enum +{ + T_NONE = 0, + T_BOOL, + T_NUMBER, + T_LIST, + T_STRING, + T_COUNT +} cvartype_t; + +char *cvartypes[] = { NULL, "BOOL" , "NUMBER", "LIST", "STRING" }; + +typedef struct parserstate_s +{ + char *buf; + char token[MAX_STRING]; + const char *filename; +} parserstate_t; + +typedef struct scrvardef_s +{ + char name[MAX_STRING]; + char value[MAX_STRING]; + char desc[MAX_STRING]; + float fMin, fMax; + cvartype_t type; + int flags; + qboolean fHandled; +} scrvardef_t; + +/* +=================== +CSCR_ExpectString + +Return true if next token is pExpext and skip it +=================== +*/ +qboolean CSCR_ExpectString( parserstate_t *ps, const char *pExpect, qboolean skip, qboolean error ) +{ + char *tmp = COM_ParseFile( ps->buf, ps->token ); + + if( !Q_stricmp( ps->token, pExpect ) ) + { + ps->buf = tmp; + return true; + } + + if( skip ) ps->buf = tmp; + if( error ) MsgDev( D_ERROR, "Syntax error in %s: got \"%s\" instead of \"%s\"\n", ps->filename, ps->token, pExpect ); + + return false; +} + +/* +=================== +CSCR_ParseType + +Determine script variable type +=================== +*/ +cvartype_t CSCR_ParseType( parserstate_t *ps ) +{ + int i; + + for( i = 1; i < T_COUNT; i++ ) + { + if( CSCR_ExpectString( ps, cvartypes[i], false, false )) + return i; + } + + MsgDev( D_ERROR, "Cannot parse %s: Bad type %s\n", ps->filename, ps->token ); + return T_NONE; +} + + + +/* +========================= +CSCR_ParseSingleCvar +========================= +*/ +qboolean CSCR_ParseSingleCvar( parserstate_t *ps, scrvardef_t *result ) +{ + // read the name + ps->buf = COM_ParseFile( ps->buf, result->name ); + + if( !CSCR_ExpectString( ps, "{", false, true )) + return false; + + // read description + ps->buf = COM_ParseFile( ps->buf, result->desc ); + + if( !CSCR_ExpectString( ps, "{", false, true )) + return false; + + result->type = CSCR_ParseType( ps ); + + switch( result->type ) + { + case T_BOOL: + // bool only has description + if( !CSCR_ExpectString( ps, "}", false, true )) + return false; + break; + case T_NUMBER: + // min + ps->buf = COM_ParseFile( ps->buf, ps->token ); + result->fMin = Q_atof( ps->token ); + + // max + ps->buf = COM_ParseFile( ps->buf, ps->token ); + result->fMax = Q_atof( ps->token ); + + if( !CSCR_ExpectString( ps, "}", false, true )) + return false; + break; + case T_STRING: + if( !CSCR_ExpectString( ps, "}", false, true )) + return false; + break; + case T_LIST: + while( !CSCR_ExpectString( ps, "}", true, false )) + { + // read token for each item here + } + break; + default: + return false; + } + + if( !CSCR_ExpectString( ps, "{", false, true )) + return false; + + // default value + ps->buf = COM_ParseFile( ps->buf, result->value ); + + if( !CSCR_ExpectString( ps, "}", false, true )) + return false; + + if( CSCR_ExpectString( ps, "SetInfo", false, false )) + result->flags |= FCVAR_USERINFO; + + if( !CSCR_ExpectString( ps, "}", false, true )) + return false; + + return true; +} + +/* +====================== +CSCR_ParseHeader + +Check version and seek to first cvar name +====================== +*/ +qboolean CSCR_ParseHeader( parserstate_t *ps ) +{ + if( !CSCR_ExpectString( ps, "VERSION", false, true )) + return false; + + // Parse in the version # + // Get the first token. + ps->buf = COM_ParseFile( ps->buf, ps->token ); + + if( Q_atof( ps->token ) != 1 ) + { + MsgDev( D_ERROR, "File %s has wrong version %s!\n", ps->filename, ps->token ); + return false; + } + + if( !CSCR_ExpectString( ps, "DESCRIPTION", false, true )) + return false; + + ps->buf = COM_ParseFile( ps->buf, ps->token ); + + if( Q_stricmp( ps->token, "INFO_OPTIONS") && Q_stricmp( ps->token, "SERVER_OPTIONS" )) + { + MsgDev( D_ERROR, "DESCRIPTION must be INFO_OPTIONS or SERVER_OPTIONS\n"); + return false; + } + + if( !CSCR_ExpectString( ps, "{", false, true )) + return false; + + return true; +} + +/* +====================== +CSCR_WriteGameCVars + +Print all cvars declared in script to game.cfg file +====================== +*/ +int CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename ) +{ + parserstate_t state = { 0 }; + qboolean success = false; + int count = 0; + long length = 0; + char *start; + + state.filename = scriptfilename; + state.buf = start = (char *)FS_LoadFile( scriptfilename, &length, true ); + + if( !state.buf || !length ) + return 0; + + MsgDev( D_INFO, "Reading config script file %s\n", scriptfilename ); + + if( !CSCR_ParseHeader( &state )) + { + MsgDev( D_ERROR, "Failed to parse header!\n" ); + goto finish; + } + + FS_Printf( cfg, "// declared in %s:\n", scriptfilename ); + + while( !CSCR_ExpectString( &state, "}", false, false )) + { + scrvardef_t var = { 0 }; + + if( CSCR_ParseSingleCvar( &state, &var ) ) + { + convar_t *cvar = Cvar_FindVar( var.name ); + + if( cvar && !FBitSet( cvar->flags, FCVAR_SERVER|FCVAR_ARCHIVE )) + { + // cvars will be placed in game.cfg and restored on map start + if( var.flags & FCVAR_USERINFO ) + FS_Printf( cfg, "// %s ( %s )\nsetu %s \"%s\"\n", var.desc, var.value, var.name, cvar->string ); + else FS_Printf( cfg, "// %s ( %s )\nset %s \"%s\"\n", var.desc, var.value, var.name, cvar->string ); + } + count++; + } + else + { + break; + } + + if( count > 1024 ) + break; + } + + if( COM_ParseFile( state.buf, state.token )) + MsgDev( D_ERROR, "Got extra tokens!\n" ); + else success = true; +finish: + if( !success ) + { + state.token[sizeof( state.token ) - 1] = 0; + + if( start && state.buf ) + MsgDev( D_ERROR, "Parse error in %s, byte %d, token %s\n", scriptfilename, (int)( state.buf - start ), state.token ); + else MsgDev( D_ERROR, "Parse error in %s, token %s\n", scriptfilename, state.token ); + } + + if( start ) Mem_Free( start ); + + return count; +} + +/* +====================== +CSCR_LoadDefaultCVars + +Register all cvars declared in config file and set default values +====================== +*/ +int CSCR_LoadDefaultCVars( const char *scriptfilename ) +{ + parserstate_t state = { 0 }; + qboolean success = false; + int count = 0; + long length = 0; + char *start; + + state.filename = scriptfilename; + state.buf = start = (char *)FS_LoadFile( scriptfilename, &length, true ); + + if( !state.buf || !length ) + return 0; + + MsgDev( D_INFO, "Reading config script file %s\n", scriptfilename ); + + if( !CSCR_ParseHeader( &state )) + { + MsgDev( D_ERROR, "Failed to parse header!\n" ); + goto finish; + } + + while( !CSCR_ExpectString( &state, "}", false, false )) + { + scrvardef_t var = { 0 }; + + // Create a new object + if( CSCR_ParseSingleCvar( &state, &var ) ) + { + Cvar_Get( var.name, var.value, var.flags|FCVAR_TEMPORARY, var.desc ); + count++; + } + else + break; + + if( count > 1024 ) + break; + } + + if( COM_ParseFile( state.buf, state.token )) + MsgDev( D_ERROR, "Got extra tokens!\n" ); + else success = true; +finish: + if( !success ) + { + state.token[sizeof( state.token ) - 1] = 0; + if( start && state.buf ) + MsgDev( D_ERROR, "Parse error in %s, byte %d, token %s\n", scriptfilename, (int)( state.buf - start ), state.token ); + else MsgDev( D_ERROR, "Parse error in %s, token %s\n", scriptfilename, state.token ); + } + + if( start ) Mem_Free( start ); + + return count; +} diff --git a/engine/common/cmd.c b/engine/common/cmd.c new file mode 100644 index 00000000..05677092 --- /dev/null +++ b/engine/common/cmd.c @@ -0,0 +1,1149 @@ +/* +cmd.c - script command processing module +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "server.h" + +#define MAX_CMD_BUFFER 32768 +#define MAX_CMD_LINE 2048 +#define MAX_ALIAS_NAME 32 + +typedef struct cmdalias_s +{ + struct cmdalias_s *next; + char name[MAX_ALIAS_NAME]; + char *value; +} cmdalias_t; + +typedef struct +{ + byte *data; + int cursize; + int maxsize; +} cmdbuf_t; + +qboolean cmd_wait; +cmdbuf_t cmd_text; +byte cmd_text_buf[MAX_CMD_BUFFER]; +cmdalias_t *cmd_alias; +uint cmd_condition; +int cmd_condlevel; + +/* +============================================================================= + + COMMAND BUFFER + +============================================================================= +*/ +/* +============ +Cbuf_Init +============ +*/ +void Cbuf_Init( void ) +{ + cmd_text.data = cmd_text_buf; + cmd_text.maxsize = MAX_CMD_BUFFER; + cmd_text.cursize = 0; +} + +/* +============ +Cbuf_Clear +============ +*/ +void Cbuf_Clear( void ) +{ + memset( cmd_text.data, 0, sizeof( cmd_text_buf )); + cmd_text.cursize = 0; +} + +/* +============ +Cbuf_GetSpace +============ +*/ +void *Cbuf_GetSpace( cmdbuf_t *buf, int length ) +{ + void *data; + + if(( buf->cursize + length ) > buf->maxsize ) + { + buf->cursize = 0; + Host_Error( "Cbuf_GetSpace: overflow\n" ); + } + + data = buf->data + buf->cursize; + buf->cursize += length; + + return data; +} + +/* +============ +Cbuf_AddText + +Adds command text at the end of the buffer +============ +*/ +void Cbuf_AddText( const char *text ) +{ + int l = Q_strlen( text ); + + if(( cmd_text.cursize + l ) >= cmd_text.maxsize ) + { + MsgDev( D_WARN, "Cbuf_AddText: overflow\n" ); + } + else + { + memcpy( Cbuf_GetSpace( &cmd_text, l ), text, l ); + } +} + +/* +============ +Cbuf_InsertText + +Adds command text immediately after the current command +Adds a \n to the text +============ +*/ +void Cbuf_InsertText( const char *text ) +{ + int l = Q_strlen( text ); + + if(( cmd_text.cursize + l ) >= cmd_text.maxsize ) + { + MsgDev( D_WARN, "Cbuf_InsertText: overflow\n" ); + } + else + { + memmove( cmd_text.data + l, cmd_text.data, cmd_text.cursize ); + memcpy( cmd_text.data, text, l ); + cmd_text.cursize += l; + } +} + +/* +============ +Cbuf_Execute +============ +*/ +void Cbuf_Execute( void ) +{ + char *text; + char line[MAX_CMD_LINE]; + int i, quotes; + char *comment; + + while( cmd_text.cursize ) + { + // find a \n or ; line break + text = (char *)cmd_text.data; + + quotes = false; + comment = NULL; + + for( i = 0; i < cmd_text.cursize; i++ ) + { + if( !comment ) + { + if( text[i] == '"' ) quotes = !quotes; + + if( quotes ) + { + // make sure i doesn't get > cursize which causes a negative size in memmove, which is fatal --blub + if( i < ( cmd_text.cursize - 1 ) && ( text[i+0] == '\\' && (text[i+1] == '"' || text[i+1] == '\\'))) + i++; + } + else + { + if( text[i+0] == '/' && text[i+1] == '/' && ( i == 0 || (byte)text[i - 1] <= ' ' )) + comment = &text[i]; + if( text[i] == ';' ) break; // don't break if inside a quoted string or comment + } + } + + if( text[i] == '\n' || text[i] == '\r' ) + break; + } + + if( i >= ( MAX_CMD_LINE - 1 )) + { + MsgDev( D_ERROR, "Cbuf_Execute: command string owerflow\n" ); + line[0] = 0; + } + else + { + memcpy( line, text, comment ? (comment - text) : i ); + line[comment ? (comment - text) : i] = 0; + } + + // delete the text from the command buffer and move remaining commands down + // this is necessary because commands (exec) can insert data at the + // beginning of the text buffer + if( i == cmd_text.cursize ) + { + cmd_text.cursize = 0; + } + else + { + i++; + cmd_text.cursize -= i; + memmove( cmd_text.data, text + i, cmd_text.cursize ); + } + + // execute the command line + Cmd_ExecuteString( line ); + + if( cmd_wait ) + { + // skip out while text still remains in buffer, + // leaving it for next frame + cmd_wait = false; + break; + } + } +} + +/* +=============== +Cbuf_ExecStuffCmds + +execute commandline +=============== +*/ +void Cbuf_ExecStuffCmds( void ) +{ + char build[MAX_CMD_LINE]; // this is for all commandline options combined (and is bounds checked) + int i, j, l = 0; + + // no reason to run the commandline arguments twice + if( !host.stuffcmds_pending ) + return; + build[0] = 0; + + for( i = 0; i < host.argc; i++ ) + { + if( host.argv[i] && host.argv[i][0] == '+' && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' ) && l + Q_strlen( host.argv[i] ) - 1 <= sizeof( build ) - 1 ) + { + j = 1; + + while( host.argv[i][j] ) + build[l++] = host.argv[i][j++]; + + for( i++; i < host.argc; i++ ) + { + if( !host.argv[i] ) continue; + if(( host.argv[i][0] == '+' || host.argv[i][0] == '-' ) && ( host.argv[i][1] < '0' || host.argv[i][1] > '9' )) + break; + if( l + Q_strlen( host.argv[i] ) + 4 > sizeof( build ) - 1 ) + break; + build[l++] = ' '; + + if( Q_strchr( host.argv[i], ' ' )) + build[l++] = '\"'; + + for( j = 0; host.argv[i][j]; j++ ) + build[l++] = host.argv[i][j]; + + if( Q_strchr( host.argv[i], ' ' )) + build[l++] = '\"'; + } + build[l++] = '\n'; + i--; + } + } + + // now terminate the combined string and prepend it to the command buffer + // we already reserved space for the terminator + build[l++] = 0; + Cbuf_InsertText( build ); + Cbuf_Execute(); // apply now + + // this command can be called only from .rc + Cmd_RemoveCommand( "stuffcmds" ); + host.stuffcmds_pending = false; +} + +/* +============================================================================== + + SCRIPT COMMANDS + +============================================================================== +*/ +/* +=============== +Cmd_StuffCmds_f + +Adds command line parameters as script statements +Commands lead with a +, and continue until a - or another + +hl.exe -dev 3 +map c1a0d +hl.exe -nosound -game bshift +=============== +*/ +void Cmd_StuffCmds_f( void ) +{ + host.stuffcmds_pending = true; +} + +/* +============ +Cmd_Wait_f + +Causes execution of the remainder of the command buffer to be delayed until +next frame. This allows commands like: +bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster" +============ +*/ +void Cmd_Wait_f( void ) +{ + cmd_wait = true; +} + +/* +=============== +Cmd_Echo_f + +Just prints the rest of the line to the console +=============== +*/ +void Cmd_Echo_f( void ) +{ + int i; + + for( i = 1; i < Cmd_Argc(); i++ ) + Con_Printf( Cmd_Argv( i )); + Con_Printf( "\n" ); +} + +/* +=============== +Cmd_Alias_f + +Creates a new command that executes a command string (possibly ; seperated) +=============== +*/ +void Cmd_Alias_f( void ) +{ + cmdalias_t *a; + char cmd[MAX_CMD_LINE]; + int i, c; + char *s; + + if( Cmd_Argc() == 1 ) + { + Con_Printf( "Current alias commands:\n" ); + for( a = cmd_alias; a; a = a->next ) + Con_Printf( "^2%s^7 : ^3%s^7\n", a->name, a->value ); + return; + } + + s = Cmd_Argv( 1 ); + + if( Q_strlen( s ) >= MAX_ALIAS_NAME ) + { + Con_Printf( "Alias name is too long\n" ); + return; + } + + // if the alias already exists, reuse it + for( a = cmd_alias; a; a = a->next ) + { + if( !Q_strcmp( s, a->name )) + { + Z_Free( a->value ); + break; + } + } + + if( !a ) + { + cmdalias_t *cur, *prev; + + a = Z_Malloc( sizeof( cmdalias_t )); + + Q_strncpy( a->name, s, sizeof( a->name )); + + // insert it at the right alphanumeric position + for( prev = NULL, cur = cmd_alias; cur && Q_strcmp( cur->name, a->name ) < 0; prev = cur, cur = cur->next ); + + if( prev ) prev->next = a; + else cmd_alias = a; + a->next = cur; + } + + // copy the rest of the command line + cmd[0] = 0; // start out with a null string + + c = Cmd_Argc(); + + for( i = 2; i < c; i++ ) + { + if( i != 2 ) Q_strncat( cmd, " ", sizeof( cmd )); + Q_strncat( cmd, Cmd_Argv( i ), sizeof( cmd )); + } + + Q_strncat( cmd, "\n", sizeof( cmd )); + a->value = copystring( cmd ); +} + +/* +=============== +Cmd_UnAlias_f + +Remove existing aliases. +=============== +*/ +static void Cmd_UnAlias_f ( void ) +{ + cmdalias_t *a, *p; + const char *s; + int i; + + if( Cmd_Argc() == 1 ) + { + Con_Printf( S_USAGE "unalias alias1 [alias2 ...]\n" ); + return; + } + + for( i = 1; i < Cmd_Argc(); i++ ) + { + s = Cmd_Argv( i ); + p = NULL; + + for( a = cmd_alias; a; p = a, a = a->next ) + { + if( !Q_strcmp( s, a->name )) + { + if( a == cmd_alias ) + cmd_alias = a->next; + if( p ) p->next = a->next; + Mem_Free( a->value ); + Mem_Free( a ); + break; + } + } + + if( !a ) Con_Printf( "%s not found\n", s ); + } +} + +/* +============================================================================= + + COMMAND EXECUTION + +============================================================================= +*/ +typedef struct cmd_s +{ + struct cmd_s *next; + char *name; + xcommand_t function; + char *desc; + int flags; +} cmd_t; + +static int cmd_argc; +static char *cmd_args = NULL; +static char *cmd_argv[MAX_CMD_TOKENS]; +static char cmd_tokenized[MAX_CMD_BUFFER]; // will have 0 bytes inserted +static cmd_t *cmd_functions; // possible commands to execute + +/* +============ +Cmd_Argc +============ +*/ +uint Cmd_Argc( void ) +{ + return cmd_argc; +} + +/* +============ +Cmd_Argv +============ +*/ +char *Cmd_Argv( int arg ) +{ + if((uint)arg >= cmd_argc ) + return ""; + return cmd_argv[arg]; +} + +/* +============ +Cmd_Args +============ +*/ +char *Cmd_Args( void ) +{ + return cmd_args; +} + +/* +============ +Cmd_TokenizeString + +Parses the given string into command line tokens. +The text is copied to a seperate buffer and 0 characters +are inserted in the apropriate place, The argv array +will point into this temporary buffer. +============ +*/ +void Cmd_TokenizeString( char *text ) +{ + char cmd_token[MAX_CMD_BUFFER]; + int i; + + // clear the args from the last string + for( i = 0; i < cmd_argc; i++ ) + Z_Free( cmd_argv[i] ); + + cmd_argc = 0; // clear previous args + cmd_args = NULL; + + if( !text ) return; + + while( 1 ) + { + // skip whitespace up to a /n + while( *text && ((byte)*text) <= ' ' && *text != '\r' && *text != '\n' ) + text++; + + if( *text == '\n' || *text == '\r' ) + { + // a newline seperates commands in the buffer + if( *text == '\r' && text[1] == '\n' ) + text++; + text++; + break; + } + + if( !*text ) + return; + + if( cmd_argc == 1 ) + cmd_args = text; + + host.com_ignorebracket = true; + text = COM_ParseFile( text, cmd_token ); + host.com_ignorebracket = false; + + if( !text ) return; + + if( cmd_argc < MAX_CMD_TOKENS ) + { + cmd_argv[cmd_argc] = copystring( cmd_token ); + cmd_argc++; + } + } +} + +/* +============ +Cmd_AddCommand +============ +*/ +void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ) +{ + cmd_t *cmd, *cur, *prev; + + // fail if the command is a variable name + if( Cvar_FindVar( cmd_name )) + { + Con_Printf( S_ERROR "Cmd_AddCommand: %s already defined as a var\n", cmd_name ); + return; + } + + // fail if the command already exists + if( Cmd_Exists( cmd_name )) + { + Con_Printf( S_ERROR "Cmd_AddCommand: %s already defined\n", cmd_name ); + return; + } + + // use a small malloc to avoid zone fragmentation + cmd = Z_Malloc( sizeof( cmd_t )); + cmd->name = copystring( cmd_name ); + cmd->desc = copystring( cmd_desc ); + cmd->function = function; + + // insert it at the right alphanumeric position + for( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next ); + + if( prev ) prev->next = cmd; + else cmd_functions = cmd; + cmd->next = cur; +} + +/* +============ +Cmd_AddServerCommand +============ +*/ +void Cmd_AddServerCommand( const char *cmd_name, xcommand_t function ) +{ + cmd_t *cmd, *cur, *prev; + + if( !cmd_name || !*cmd_name ) + { + MsgDev( D_ERROR, "Cmd_AddServerCommand: NULL name\n" ); + return; + } + + // fail if the command is a variable name + if( Cvar_FindVar( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddServerCommand: %s already defined as a var\n", cmd_name ); + return; + } + + // fail if the command already exists + if( Cmd_Exists( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddServerCommand: %s already defined\n", cmd_name ); + return; + } + + // use a small malloc to avoid zone fragmentation + cmd = Z_Malloc( sizeof( cmd_t )); + cmd->name = copystring( cmd_name ); + cmd->desc = copystring( "server command" ); + cmd->function = function; + cmd->flags = CMD_SERVERDLL; + + // insert it at the right alphanumeric position + for( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next ); + + if( prev ) prev->next = cmd; + else cmd_functions = cmd; + cmd->next = cur; +} + +/* +============ +Cmd_AddClientCommand +============ +*/ +int Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ) +{ + cmd_t *cmd, *cur, *prev; + + if( !cmd_name || !*cmd_name ) + { + MsgDev( D_ERROR, "Cmd_AddClientCommand: NULL name\n" ); + return 0; + } + + // fail if the command is a variable name + if( Cvar_FindVar( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddClientCommand: %s already defined as a var\n", cmd_name ); + return 0; + } + + // fail if the command already exists + if( Cmd_Exists( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddClientCommand: %s already defined\n", cmd_name ); + return 0; + } + + // use a small malloc to avoid zone fragmentation + cmd = Z_Malloc( sizeof( cmd_t )); + cmd->name = copystring( cmd_name ); + cmd->desc = copystring( "client command" ); + cmd->function = function; + cmd->flags = CMD_CLIENTDLL; + + // insert it at the right alphanumeric position + for( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next ); + + if( prev ) prev->next = cmd; + else cmd_functions = cmd; + cmd->next = cur; + + return 1; +} + +/* +============ +Cmd_AddGameUICommand +============ +*/ +int Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ) +{ + cmd_t *cmd, *cur, *prev; + + if( !cmd_name || !*cmd_name ) + { + MsgDev( D_ERROR, "Cmd_AddGameUICommand: NULL name\n" ); + return 0; + } + + // fail if the command is a variable name + if( Cvar_FindVar( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddGameUICommand: %s already defined as a var\n", cmd_name ); + return 0; + } + + // fail if the command already exists + if( Cmd_Exists( cmd_name )) + { + MsgDev( D_ERROR, "Cmd_AddGameUICommand: %s already defined\n", cmd_name ); + return 0; + } + + // use a small malloc to avoid zone fragmentation + cmd = Z_Malloc( sizeof( cmd_t )); + cmd->name = copystring( cmd_name ); + cmd->desc = copystring( "GameUI command" ); + cmd->function = function; + cmd->flags = CMD_GAMEUIDLL; + + // insert it at the right alphanumeric position + for( prev = NULL, cur = cmd_functions; cur && Q_strcmp( cur->name, cmd_name ) < 0; prev = cur, cur = cur->next ); + + if( prev ) prev->next = cmd; + else cmd_functions = cmd; + cmd->next = cur; + + return 1; +} + +/* +============ +Cmd_RemoveCommand +============ +*/ +void Cmd_RemoveCommand( const char *cmd_name ) +{ + cmd_t *cmd, **back; + + if( !cmd_name || !*cmd_name ) + return; + + back = &cmd_functions; + while( 1 ) + { + cmd = *back; + if( !cmd ) return; + + if( !Q_strcmp( cmd_name, cmd->name )) + { + *back = cmd->next; + + if( cmd->name ) + Mem_Free( cmd->name ); + + if( cmd->desc ) + Mem_Free( cmd->desc ); + + Mem_Free( cmd ); + return; + } + back = &cmd->next; + } +} + +/* +============ +Cmd_LookupCmds +============ +*/ +void Cmd_LookupCmds( char *buffer, void *ptr, setpair_t callback ) +{ + cmd_t *cmd; + cmdalias_t *alias; + + // nothing to process ? + if( !callback ) return; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + { + if( !buffer ) callback( cmd->name, (char *)cmd->function, cmd->desc, ptr ); + else callback( cmd->name, (char *)cmd->function, buffer, ptr ); + } + + // lookup an aliases too + for( alias = cmd_alias; alias; alias = alias->next ) + callback( alias->name, alias->value, buffer, ptr ); +} + +/* +============ +Cmd_Exists +============ +*/ +qboolean Cmd_Exists( const char *cmd_name ) +{ + cmd_t *cmd; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + { + if( !Q_strcmp( cmd_name, cmd->name )) + return true; + } + return false; +} + +/* +============ +Cmd_If_f + +Compare and et condition bit if true +============ +*/ +void Cmd_If_f( void ) +{ + // reset bit first + cmd_condition &= ~BIT( cmd_condlevel ); + + // usage + if( cmd_argc == 1 ) + { + Con_Printf( S_USAGE "if [ ]\n"); + Con_Printf( ":\n" ); + Con_Printf( ":\n" ); + Con_Printf( "else\n" ); + Con_Printf( ":\n" ); + Con_Printf( "operands are string or float values\n" ); + Con_Printf( "and substituted cvars like '$cl_lw'\n" ); + Con_Printf( "operator is '='', '==', '>', '<', '>=', '<=' or '!='\n" ); + return; + } + + // one argument - check if nonzero + if( cmd_argc == 2 ) + { + if( Q_atof( cmd_argv[1] )) + cmd_condition |= BIT( cmd_condlevel ); + } + else if( cmd_argc == 4 ) + { + // simple compare + float f1 = Q_atof( cmd_argv[1] ); + float f2 = Q_atof( cmd_argv[3] ); + + if( !cmd_argv[2][0] ) // this is wrong + return; + + if(( cmd_argv[2][0] == '=' ) || ( cmd_argv[2][1] == '=' )) // =, ==, >=, <= + { + if( !Q_strcmp( cmd_argv[1], cmd_argv[3] ) || (( f1 || f2 ) && ( f1 == f2 ))) + cmd_condition |= BIT( cmd_condlevel ); + } + + if( cmd_argv[2][0] == '!' ) // != + { + cmd_condition ^= BIT( cmd_condlevel ); + return; + } + + if(( cmd_argv[2][0] == '>' ) && ( f1 > f2 )) // >, >= + cmd_condition |= BIT( cmd_condlevel ); + + if(( cmd_argv[2][0] == '<' ) && ( f1 < f2 )) // <, <= + cmd_condition |= BIT( cmd_condlevel ); + } +} + +/* +============ +Cmd_Else_f + +Invert condition bit +============ +*/ +void Cmd_Else_f( void ) +{ + cmd_condition ^= BIT( cmd_condlevel ); +} + +/* +============ +Cmd_ExecuteString + +A complete command line has been parsed, so try to execute it +============ +*/ +void Cmd_ExecuteString( char *text ) +{ + cmd_t *cmd; + cmdalias_t *a; + char command[MAX_CMD_LINE]; + char *pcmd = command; + int len = 0; + + cmd_condlevel = 0; + + // cvar value substitution + if( cmd_scripting && cmd_scripting->value ) + { + while( *text ) + { + // check for escape + if(( *text == '\\' || *text == '$' ) && (*( text + 1 ) == '$' )) + { + text ++; + } + else if( *text == '$' ) + { + char token[MAX_CMD_LINE]; + char *ptoken = token; + + // check for correct cvar name + text++; + while(( *text >= '0' && *text <= '9' ) || ( *text >= 'A' && *text <= 'Z' ) || ( *text >= 'a' && *text <= 'z' ) || ( *text == '_' )) + *ptoken++ = *text++; + *ptoken = 0; + + len += Q_strncpy( pcmd, Cvar_VariableString( token ), MAX_CMD_LINE - len ); + pcmd = command + len; + + if( !*text ) break; + } + + *pcmd++ = *text++; + len++; + } + + *pcmd = 0; + text = command; + + while( *text == ':' ) + { + if( !FBitSet( cmd_condition, BIT( cmd_condlevel ))) + return; + cmd_condlevel++; + text++; + } + } + + // execute the command line + Cmd_TokenizeString( text ); + + if( !Cmd_Argc( )) return; // no tokens + + if( !host.apply_game_config ) + { + // check aliases + for( a = cmd_alias; a; a = a->next ) + { + if( !Q_stricmp( cmd_argv[0], a->name )) + { + Cbuf_InsertText( a->value ); + return; + } + } + } + + // special mode for restore game.dll archived cvars + if( !host.apply_game_config || !Q_strcmp( cmd_argv[0], "exec" )) + { + // check functions + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + { + if( !Q_stricmp( cmd_argv[0], cmd->name ) && cmd->function ) + { + cmd->function(); + return; + } + } + } + + // check cvars + if( Cvar_Command( )) return; + + if( host.apply_game_config ) + return; // don't send nothing to server: we is a server! + + // forward the command line to the server, so the entity DLL can parse it + if( host.type == HOST_NORMAL ) + { + if( cls.state >= ca_connected ) + Cmd_ForwardToServer(); + } + else if( text[0] != '@' && host.type == HOST_NORMAL ) + { + // commands with leading '@' are hidden system commands + Con_Printf( S_WARN "Unknown command \"%s\"\n", text ); + } +} + +/* +=================== +Cmd_ForwardToServer + +adds the current command line as a clc_stringcmd to the client message. +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void Cmd_ForwardToServer( void ) +{ + char str[MAX_CMD_BUFFER]; + + if( cls.demoplayback ) + { + if( !Q_stricmp( Cmd_Argv( 0 ), "pause" )) + cl.paused ^= 1; + return; + } + + if( cls.state < ca_connected || cls.state > ca_active ) + { + if( Q_stricmp( Cmd_Argv( 0 ), "setinfo" )) + Con_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 )); + return; // not connected + } + + MSG_BeginClientCmd( &cls.netchan.message, clc_stringcmd ); + + str[0] = 0; + if( Q_stricmp( Cmd_Argv( 0 ), "cmd" )) + { + Q_strcat( str, Cmd_Argv( 0 )); + Q_strcat( str, " " ); + } + + if( Cmd_Argc() > 1 ) + Q_strcat( str, Cmd_Args( )); + else Q_strcat( str, "\n" ); + + MSG_WriteString( &cls.netchan.message, str ); +} + +/* +============ +Cmd_List_f +============ +*/ +void Cmd_List_f( void ) +{ + cmd_t *cmd; + int i = 0; + char *match; + + if( Cmd_Argc() > 1 ) match = Cmd_Argv( 1 ); + else match = NULL; + + for( cmd = cmd_functions; cmd; cmd = cmd->next ) + { + if( cmd->name[0] == '@' ) + continue; // never show system cmds + + if( match && !Q_stricmpext( match, cmd->name )) + continue; + + Con_Printf( " %-*s ^3%s^7\n", 32, cmd->name, cmd->desc ); + i++; + } + + Con_Printf( "%i commands\n", i ); +} + +/* +============ +Cmd_Unlink + +unlink all commands with specified flag +============ +*/ +void Cmd_Unlink( int group ) +{ + cmd_t *cmd; + cmd_t **prev; + int count = 0; + + if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, CMD_SERVERDLL )) + return; + + if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, CMD_CLIENTDLL )) + return; + + if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, CMD_GAMEUIDLL )) + return; + + prev = &cmd_functions; + + while( 1 ) + { + cmd = *prev; + if( !cmd ) break; + + // do filter by specified group + if( group && !FBitSet( cmd->flags, group )) + { + prev = &cmd->next; + continue; + } + + *prev = cmd->next; + + if( cmd->name ) Mem_Free( cmd->name ); + if( cmd->desc ) Mem_Free( cmd->desc ); + + Mem_Free( cmd ); + count++; + } + + Con_Reportf( "unlink %i commands\n", count ); +} + +/* +============ +Cmd_Null_f + +null function for some cmd stubs +============ +*/ +void Cmd_Null_f( void ) +{ +} + +/* +============ +Cmd_Init +============ +*/ +void Cmd_Init( void ) +{ + Cbuf_Init(); + + cmd_functions = NULL; + cmd_condition = 0; + cmd_alias = NULL; + cmd_args = NULL; + cmd_argc = 0; + + // register our commands + Cmd_AddCommand( "echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" ); + Cmd_AddCommand( "wait", Cmd_Wait_f, "make script execution wait for some rendered frames" ); + Cmd_AddCommand( "cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" ); + Cmd_AddCommand( "stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in .rc script)" ); + Cmd_AddCommand( "cmd", Cmd_ForwardToServer, "send a console commandline to the server" ); + Cmd_AddCommand( "alias", Cmd_Alias_f, "create a script function. Without arguments show the list of all alias" ); + Cmd_AddCommand( "unalias", Cmd_UnAlias_f, "remove a script function" ); + Cmd_AddCommand( "if", Cmd_If_f, "compare and set condition bits" ); + Cmd_AddCommand( "else", Cmd_Else_f, "invert condition bit" ); +} \ No newline at end of file diff --git a/engine/common/com_strings.h b/engine/common/com_strings.h new file mode 100644 index 00000000..1817fb8a --- /dev/null +++ b/engine/common/com_strings.h @@ -0,0 +1,60 @@ +/* +com_strings.h - all paths to external resources that hardcoded into engine +Copyright (C) 2018 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef COM_STRINGS_H +#define COM_STRINGS_H + +// default colored message headers +#define S_NOTE "^2Note:^7 " +#define S_WARN "^3Warning:^7 " +#define S_ERROR "^1Error:^7 " +#define S_USAGE "Usage: " + +#define S_OPENGL_NOTE "^2OpenGL Note:^7 " +#define S_OPENGL_WARN "^3OpenGL Warning:^7 " +#define S_OPENGL_ERROR "^3OpenGL Error:^7 " + +// end game final default message +#define DEFAULT_ENDGAME_MESSAGE "The End" + +// path to the hash-pak that contain custom player decals +#define CUSTOM_RES_PATH "custom.hpk" + +// path to default playermodel in GoldSrc +#define DEFAULT_PLAYER_PATH_HALFLIFE "models/player.mdl" + +// path to default playermodel in Quake +#define DEFAULT_PLAYER_PATH_QUAKE "progs/player.mdl" + +// debug beams +#define DEFAULT_LASERBEAM_PATH "sprites/laserbeam.spr" + +// path to folders where placed all sounds +#define DEFAULT_SOUNDPATH "sound/" + +// path store saved games +#define DEFAULT_SAVE_DIRECTORY "save/" + +// fallback to this skybox +#define DEFAULT_SKYBOX_PATH "gfx/env/desert" + +// playlist for startup videos +#define DEFAULT_VIDEOLIST_PATH "media/StartupVids.txt" + +#define CVAR_GLCONFIG_DESCRIPTION "enable or disable %s" + +#define DEFAULT_BSP_BUILD_ERROR "%s can't be loaded in this build. Please rebuild engine with enabled SUPPORT_BSP2_FORMAT\n" + +#endif//COM_STRINGS_H \ No newline at end of file diff --git a/engine/common/common.c b/engine/common/common.c new file mode 100644 index 00000000..45ebba93 --- /dev/null +++ b/engine/common/common.c @@ -0,0 +1,1323 @@ +/* +common.c - misc functions used by dlls' +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "studio.h" +#include "mathlib.h" +#include "const.h" +#include "client.h" +#include "library.h" + +const char *file_exts[10] = +{ + ".cfg", + ".lst", + ".exe", + ".vbs", + ".com", + ".bat", + ".dll", + ".ini", + ".log", + ".sys", +}; + +#ifdef _DEBUG +void DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ) +{ + if( fExpr ) return; + + if( szMessage != NULL ) + MsgDev( at_error, "ASSERT FAILED:\n %s \n(%s@%d)\n%s\n", szExpr, szFile, szLine, szMessage ); + else MsgDev( at_error, "ASSERT FAILED:\n %s \n(%s@%d)\n", szExpr, szFile, szLine ); +} +#endif // DEBUG + +static long idum = 0; + +#define MAX_RANDOM_RANGE 0x7FFFFFFFUL +#define IA 16807 +#define IM 2147483647 +#define IQ 127773 +#define IR 2836 +#define NTAB 32 +#define EPS 1.2e-7 +#define NDIV (1 + (IM - 1) / NTAB) +#define AM (1.0 / IM) +#define RNMX (1.0 - EPS) + +static long lran1( void ) +{ + static long iy = 0; + static long iv[NTAB]; + int j; + long k; + + if( idum <= 0 || !iy ) + { + if( -(idum) < 1 ) idum = 1; + else idum = -(idum); + + for( j = NTAB + 7; j >= 0; j-- ) + { + k = (idum) / IQ; + idum = IA * (idum - k * IQ) - IR * k; + if( idum < 0 ) idum += IM; + if( j < NTAB ) iv[j] = idum; + } + + iy = iv[0]; + } + + k = (idum) / IQ; + idum = IA * (idum - k * IQ) - IR * k; + if( idum < 0 ) idum += IM; + j = iy / NDIV; + iy = iv[j]; + iv[j] = idum; + + return iy; +} + +// fran1 -- return a random floating-point number on the interval [0,1] +static float fran1( void ) +{ + float temp = (float)AM * lran1(); + if( temp > RNMX ) + return (float)RNMX; + return temp; +} + +void COM_SetRandomSeed( long lSeed ) +{ + if( lSeed ) idum = lSeed; + else idum = -time( NULL ); + + if( 1000 < idum ) + idum = -idum; + else if( -1000 < idum ) + idum -= 22261048; +} + +float COM_RandomFloat( float flLow, float flHigh ) +{ + float fl; + + if( idum == 0 ) COM_SetRandomSeed( 0 ); + + fl = fran1(); // float in [0,1] + return (fl * (flHigh - flLow)) + flLow; // float in [low, high) +} + +long COM_RandomLong( long lLow, long lHigh ) +{ + dword maxAcceptable; + dword n, x = lHigh - lLow + 1; + + if( idum == 0 ) COM_SetRandomSeed( 0 ); + + if( x <= 0 || MAX_RANDOM_RANGE < x - 1 ) + return lLow; + + // The following maps a uniform distribution on the interval [0, MAX_RANDOM_RANGE] + // to a smaller, client-specified range of [0,x-1] in a way that doesn't bias + // the uniform distribution unfavorably. Even for a worst case x, the loop is + // guaranteed to be taken no more than half the time, so for that worst case x, + // the average number of times through the loop is 2. For cases where x is + // much smaller than MAX_RANDOM_RANGE, the average number of times through the + // loop is very close to 1. + maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE + 1) % x ); + do + { + n = lran1(); + } while( n > maxAcceptable ); + + return lLow + (n % x); +} + +/* +=============================================================================== + + LZSS Compression + +=============================================================================== +*/ +#define LZSS_ID (('S'<<24)|('S'<<16)|('Z'<<8)|('L')) +#define LZSS_LOOKSHIFT 4 +#define LZSS_WINDOW_SIZE 4096 +#define LZSS_LOOKAHEAD BIT( LZSS_LOOKSHIFT ) + + +typedef struct +{ + unsigned int id; + unsigned int size; +} lzss_header_t; + +// expected to be sixteen bytes +typedef struct lzss_node_s +{ + const byte *data; + struct lzss_node_s *prev; + struct lzss_node_s *next; + char pad[4]; +} lzss_node_t; + +typedef struct +{ + lzss_node_t *start; + lzss_node_t *end; +} lzss_list_t; + +typedef struct +{ + lzss_list_t *hash_table; + lzss_node_t *hash_node; + int window_size; +} lzss_state_t; + +qboolean LZSS_IsCompressed( const byte *source ) +{ + lzss_header_t *phdr = (lzss_header_t *)source; + + if( phdr && phdr->id == LZSS_ID ) + return true; + return false; +} + +uint LZSS_GetActualSize( const byte *source ) +{ + lzss_header_t *phdr = (lzss_header_t *)source; + + if( phdr && phdr->id == LZSS_ID ) + return phdr->size; + return 0; +} + +static void LZSS_BuildHash( lzss_state_t *state, const byte *source ) +{ + lzss_list_t *list; + lzss_node_t *node; + unsigned int targetindex = (uint)source & ( state->window_size - 1 ); + + node = &state->hash_node[targetindex]; + + if( node->data ) + { + list = &state->hash_table[*node->data]; + if( node->prev ) + { + list->end = node->prev; + node->prev->next = NULL; + } + else + { + list->start = NULL; + list->end = NULL; + } + } + + list = &state->hash_table[*source]; + node->data = source; + node->prev = NULL; + node->next = list->start; + if( list->start ) + list->start->prev = node; + else list->end = node; + list->start = node; +} + +byte *LZSS_CompressNoAlloc( lzss_state_t *state, byte *pInput, int input_length, byte *pOutputBuf, uint *pOutputSize ) +{ + byte *pStart = pOutputBuf; // allocate the output buffer, compressed buffer is expected to be less, caller will free + byte *pEnd = pStart + input_length - sizeof( lzss_header_t ) - 8; // prevent compression failure + lzss_header_t *header = (lzss_header_t *)pStart; + byte *pOutput = pStart + sizeof( lzss_header_t ); + const byte *pEncodedPosition = NULL; + byte *pLookAhead = pInput; + byte *pWindow = pInput; + int i, putCmdByte = 0; + byte *pCmdByte = NULL; + + if( input_length <= sizeof( lzss_header_t ) + 8 ) + return NULL; + + // set LZSS header + header->id = LZSS_ID; + header->size = input_length; + + // create the compression work buffers, small enough (~64K) for stack + state->hash_table = (lzss_list_t *)_alloca( 256 * sizeof( lzss_list_t )); + memset( state->hash_table, 0, 256 * sizeof( lzss_list_t )); + state->hash_node = (lzss_node_t *)_alloca( state->window_size * sizeof( lzss_node_t )); + memset( state->hash_node, 0, state->window_size * sizeof( lzss_node_t )); + + while( input_length > 0 ) + { + int lookAheadLength = input_length < LZSS_LOOKAHEAD ? input_length : LZSS_LOOKAHEAD; + lzss_node_t *hash = state->hash_table[pLookAhead[0]].start; + int encoded_length = 0; + + pWindow = pLookAhead - state->window_size; + + if( pWindow < pInput ) + pWindow = pInput; + + if( !putCmdByte ) + { + pCmdByte = pOutput++; + *pCmdByte = 0; + } + + putCmdByte = ( putCmdByte + 1 ) & 0x07; + + while( hash != NULL ) + { + int length = lookAheadLength; + int match_length = 0; + + while( length-- && hash->data[match_length] == pLookAhead[match_length] ) + match_length++; + + if( match_length > encoded_length ) + { + encoded_length = match_length; + pEncodedPosition = hash->data; + } + + if( match_length == lookAheadLength ) + break; + + hash = hash->next; + } + + if ( encoded_length >= 3 ) + { + *pCmdByte = (*pCmdByte >> 1) | 0x80; + *pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) >> LZSS_LOOKSHIFT ); + *pOutput++ = (( pLookAhead - pEncodedPosition - 1 ) << LZSS_LOOKSHIFT ) | ( encoded_length - 1 ); + } + else + { + *pCmdByte = ( *pCmdByte >> 1 ); + *pOutput++ = *pLookAhead; + encoded_length = 1; + } + + for( i = 0; i < encoded_length; i++ ) + { + LZSS_BuildHash( state, pLookAhead++ ); + } + + input_length -= encoded_length; + + if( pOutput >= pEnd ) + { + // compression is worse, abandon + return NULL; + } + } + + if( input_length != 0 ) + { + // unexpected failure + Assert( 0 ); + return NULL; + } + + if( !putCmdByte ) + { + pCmdByte = pOutput++; + *pCmdByte = 0x01; + } + else + { + *pCmdByte = (( *pCmdByte >> 1 ) | 0x80 ) >> ( 7 - putCmdByte ); + } + + // put two ints at end of buffer + *pOutput++ = 0; + *pOutput++ = 0; + + if( pOutputSize ) + *pOutputSize = pOutput - pStart; + + return pStart; +} + +byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize ) +{ + byte *pStart = (byte *)malloc( inputLength ); + byte *pFinal = NULL; + lzss_state_t state; + + memset( &state, 0, sizeof( state )); + state.window_size = LZSS_WINDOW_SIZE; + + pFinal = LZSS_CompressNoAlloc( &state, pInput, inputLength, pStart, pOutputSize ); + + if( !pFinal ) + { + free( pStart ); + return NULL; + } + + return pStart; +} + +uint LZSS_Decompress( const byte *pInput, byte *pOutput ) +{ + uint totalBytes = 0; + int getCmdByte = 0; + int cmdByte = 0; + uint actualSize = LZSS_GetActualSize( pInput ); + + if( !actualSize ) + return 0; + + pInput += sizeof( lzss_header_t ); + + while( 1 ) + { + if( !getCmdByte ) + cmdByte = *pInput++; + getCmdByte = ( getCmdByte + 1 ) & 0x07; + + if( cmdByte & 0x01 ) + { + int position = *pInput++ << LZSS_LOOKSHIFT; + int i, count; + byte *pSource; + + position |= ( *pInput >> LZSS_LOOKSHIFT ); + count = ( *pInput++ & 0x0F ) + 1; + + if( count == 1 ) + break; + + pSource = pOutput - position - 1; + for( i = 0; i < count; i++ ) + *pOutput++ = *pSource++; + totalBytes += count; + } + else + { + *pOutput++ = *pInput++; + totalBytes++; + } + cmdByte = cmdByte >> 1; + } + + if( totalBytes != actualSize ) + { + Assert( 0 ); + return 0; + } + return totalBytes; +} + +/* +============ +COM_FileBase + +Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +============ +*/ +void COM_FileBase( const char *in, char *out ) +{ + int len, start, end; + + len = Q_strlen( in ); + if( !len ) return; + + // scan backward for '.' + end = len - 1; + + while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if( in[end] != '.' ) + end = len-1; // no '.', copy to end + else end--; // found ',', copy to left of '.' + + // scan backward for '/' + start = len - 1; + + while( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) + start = 0; + else start++; + + // length of new sting + len = end - start + 1; + + // Copy partial string + Q_strncpy( out, &in[start], len + 1 ); + out[len] = 0; +} + +/* +============ +COM_FileExtension +============ +*/ +const char *COM_FileExtension( const char *in ) +{ + const char *separator, *backslash, *colon, *dot; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + + if( !separator || separator < backslash ) + separator = backslash; + + colon = Q_strrchr( in, ':' ); + + if( !separator || separator < colon ) + separator = colon; + + dot = Q_strrchr( in, '.' ); + + if( dot == NULL || ( separator && ( dot < separator ))) + return ""; + + return dot + 1; +} + +/* +============ +COM_FileWithoutPath +============ +*/ +const char *COM_FileWithoutPath( const char *in ) +{ + const char *separator, *backslash, *colon; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + + if( !separator || separator < backslash ) + separator = backslash; + + colon = Q_strrchr( in, ':' ); + + if( !separator || separator < colon ) + separator = colon; + + return separator ? separator + 1 : in; +} + +/* +============ +COM_ExtractFilePath +============ +*/ +void COM_ExtractFilePath( const char *path, char *dest ) +{ + const char *src = path + Q_strlen( path ) - 1; + + // back up until a \ or the start + while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) + src--; + + if( src != path ) + { + memcpy( dest, path, src - path ); + dest[src - path - 1] = 0; // cutoff backslash + } + else Q_strcpy( dest, "" ); // file without path +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension( char *path ) +{ + size_t length; + + length = Q_strlen( path ) - 1; + while( length > 0 && path[length] != '.' ) + { + length--; + if( path[length] == '/' || path[length] == '\\' || path[length] == ':' ) + return; // no extension + } + + if( length ) path[length] = 0; +} + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension( char *path, const char *extension ) +{ + const char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + Q_strlen( path ) - 1; + + while( *src != '/' && src != path ) + { + // it has an extension + if( *src == '.' ) return; + src--; + } + + Q_strcat( path, extension ); +} + +/* +================== +COM_ReplaceExtension +================== +*/ +void COM_ReplaceExtension( char *path, const char *extension ) +{ + COM_StripExtension( path ); + COM_DefaultExtension( path, extension ); +} + +/* +============== +COM_IsSingleChar + +interpert this character as single +============== +*/ +static int COM_IsSingleChar( char c ) +{ + if( c == '{' || c == '}' || c == '\'' || c == ',' ) + return true; + + if( !host.com_ignorebracket && ( c == ')' || c == '(' )) + return true; + + if( host.com_handlecolon && c == ':' ) + return true; + + return false; +} + +/* +============== +COM_IsWhiteSpace + +interpret symbol as whitespace +============== +*/ + +static int COM_IsWhiteSpace( char space ) +{ + if( space == ' ' || space == '\t' || space == '\r' || space == '\n' ) + return 1; + return 0; +} + +/* +============== +COM_ParseFile + +text parser +============== +*/ +char *COM_ParseFile( char *data, char *token ) +{ + int c, len; + + if( !token ) + return NULL; + + len = 0; + token[0] = 0; + + if( !data ) + return NULL; +// skip whitespace +skipwhite: + while(( c = ((byte)*data)) <= ' ' ) + { + if( c == 0 ) + return NULL; // end of file; + data++; + } + + // skip // comments + if( c=='/' && data[1] == '/' ) + { + while( *data && *data != '\n' ) + data++; + goto skipwhite; + } + + // handle quoted strings specially + if( c == '\"' ) + { + data++; + while( 1 ) + { + c = (byte)*data++; + if( c == '\"' || !c ) + { + token[len] = 0; + return data; + } + token[len] = c; + len++; + } + } + + // parse single characters + if( COM_IsSingleChar( c )) + { + token[len] = c; + len++; + token[len] = 0; + return data + 1; + } + + // parse a regular word + do + { + token[len] = c; + data++; + len++; + c = ((byte)*data); + + if( COM_IsSingleChar( c )) + break; + } while( c > 32 ); + + token[len] = 0; + + return data; +} + +/* +================ +COM_ParseVector + +================ +*/ +qboolean COM_ParseVector( char **pfile, float *v, size_t size ) +{ + string token; + qboolean bracket = false; + char *saved; + uint i; + + if( v == NULL || size == 0 ) + return false; + + memset( v, 0, sizeof( *v ) * size ); + + if( size == 1 ) + { + *pfile = COM_ParseFile( *pfile, token ); + v[0] = Q_atof( token ); + return true; + } + + saved = *pfile; + + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + return false; + + if( token[0] == '(' ) + bracket = true; + else *pfile = saved; // restore token to right get it again + + for( i = 0; i < size; i++ ) + { + *pfile = COM_ParseFile( *pfile, token ); + v[i] = Q_atof( token ); + } + + if( !bracket ) return true; // done + + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + return false; + + if( token[0] == ')' ) + return true; + return false; +} + +/* +============= +COM_CheckString + +============= +*/ +int COM_CheckString( const char *string ) +{ + if( !string || (byte)*string <= ' ' ) + return 0; + return 1; +} + +/* +============= +COM_FileSize + +============= +*/ +int COM_FileSize( const char *filename ) +{ + return FS_FileSize( filename, false ); +} + +/* +============= +COM_AddAppDirectoryToSearchPath + +============= +*/ +void COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ) +{ + FS_AddGameHierarchy( pszBaseDir, FS_NOWRITE_PATH ); +} + +/* +=========== +COM_ExpandFilename + +Finds the file in the search path, copies over the name with the full path name. +This doesn't search in the pak file. +=========== +*/ +int COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ) +{ + const char *path; + char result[MAX_SYSPATH]; + + if( !COM_CheckString( fileName ) || !nameOutBuffer || nameOutBufferSize <= 0 ) + return 0; + + // filename examples: + // media\sierra.avi - D:\Xash3D\valve\media\sierra.avi + // models\barney.mdl - D:\Xash3D\bshift\models\barney.mdl + if(( path = FS_GetDiskPath( fileName, false )) != NULL ) + { + Q_sprintf( result, "%s/%s", host.rootdir, path ); + + // check for enough room + if( Q_strlen( result ) > nameOutBufferSize ) + return 0; + + Q_strncpy( nameOutBuffer, result, nameOutBufferSize ); + return 1; + } + return 0; +} + +/* +============= +COM_TrimSpace + +trims all whitespace from the front +and end of a string +============= +*/ +void COM_TrimSpace( const char *source, char *dest ) +{ + int start, end, length; + + start = 0; + end = Q_strlen( source ); + + while( source[start] && COM_IsWhiteSpace( source[start] )) + start++; + end--; + + while( end > 0 && COM_IsWhiteSpace( source[end] )) + end--; + end++; + + length = end - start; + + if( length > 0 ) + memcpy( dest, source + start, length ); + else length = 0; + + // terminate the dest string + dest[length] = 0; +} + +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +/* +================== +COM_Nibble + +Returns the 4 bit nibble for a hex character +================== +*/ +byte COM_Nibble( char c ) +{ + if(( c >= '0' ) && ( c <= '9' )) + { + return (byte)(c - '0'); + } + + if(( c >= 'A' ) && ( c <= 'F' )) + { + return (byte)(c - 'A' + 0x0a); + } + + if(( c >= 'a' ) && ( c <= 'f' )) + { + return (byte)(c - 'a' + 0x0a); + } + + return '0'; +} + +/* +================== +COM_HexConvert + +Converts pszInput Hex string to nInputLength/2 binary +================== +*/ +void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ) +{ + const char *pIn; + byte *p = pOutput; + int i; + + + for( i = 0; i < nInputLength; i += 2 ) + { + pIn = &pszInput[i]; + *p = COM_Nibble( pIn[0] ) << 4 | COM_Nibble( pIn[1] ); + p++; + } +} + +/* +============= +COM_MemFgets + +============= +*/ +char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ) +{ + int i, last, stop; + + if( !pMemFile || !pBuffer || !filePos ) + return NULL; + + if( *filePos >= fileSize ) + return NULL; + + i = *filePos; + last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if( last - *filePos > ( bufferSize - 1 )) + last = *filePos + ( bufferSize - 1); + + stop = 0; + + // stop at the next newline (inclusive) or end of buffer + while( i < last && !stop ) + { + if( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + // if we actually advanced the pointer, copy it over + if( i != *filePos ) + { + // we read in size bytes + int size = i - *filePos; + + // copy it out + memcpy( pBuffer, pMemFile + *filePos, size ); + + // If the buffer isn't full, terminate (this is always true) + if( size < bufferSize ) pBuffer[size] = 0; + + // update file pointer + *filePos = i; + return pBuffer; + } + + return NULL; +} + +/* +==================== +Cache_Check + +consistency check +==================== +*/ +void *Cache_Check( byte *mempool, cache_user_t *c ) +{ + if( !c->data ) + return NULL; + + if( !Mem_IsAllocatedExt( mempool, c->data )) + return NULL; + + return c->data; +} + +/* +============= +COM_LoadFileForMe + +============= +*/ +byte* COM_LoadFileForMe( const char *filename, int *pLength ) +{ + string name; + byte *file, *pfile; + int iLength; + + if( !COM_CheckString( filename )) + { + if( pLength ) + *pLength = 0; + return NULL; + } + + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + pfile = FS_LoadFile( name, &iLength, false ); + if( pLength ) *pLength = iLength; + + if( pfile ) + { + file = malloc( iLength + 1 ); + if( file != NULL ) + { + memcpy( file, pfile, iLength ); + file[iLength] = '\0'; + } + Mem_Free( pfile ); + pfile = file; + } + + return pfile; +} + +/* +============= +COM_LoadFile + +============= +*/ +byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ) +{ + return COM_LoadFileForMe( filename, pLength ); +} + +/* +============= +COM_LoadFile + +============= +*/ +int COM_SaveFile( const char *filename, const void *data, long len ) +{ + // check for empty filename + if( !COM_CheckString( filename )) + return false; + + // check for null data + if( !data || len <= 0 ) + return false; + + return FS_WriteFile( filename, data, len ); +} + +/* +============= +COM_FreeFile + +============= +*/ +void COM_FreeFile( void *buffer ) +{ + free( buffer ); +} + +/* +============= +COM_NormalizeAngles + +============= +*/ +void COM_NormalizeAngles( vec3_t angles ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( angles[i] > 180.0f ) + angles[i] -= 360.0f; + else if( angles[i] < -180.0f ) + angles[i] += 360.0f; + } +} + +/* +============= +pfnGetModelType + +============= +*/ +int pfnGetModelType( model_t *mod ) +{ + if( !mod ) return mod_bad; + return mod->type; +} + +/* +============= +pfnGetModelBounds + +============= +*/ +void pfnGetModelBounds( model_t *mod, float *mins, float *maxs ) +{ + if( mod ) + { + if( mins ) VectorCopy( mod->mins, mins ); + if( maxs ) VectorCopy( mod->maxs, maxs ); + } + else + { + if( mins ) VectorClear( mins ); + if( maxs ) VectorClear( maxs ); + } +} + +/* +============= +pfnCvar_RegisterServerVariable + +standard path to register game variable +============= +*/ +void pfnCvar_RegisterServerVariable( cvar_t *variable ) +{ + if( variable != NULL ) + SetBits( variable->flags, FCVAR_EXTDLL ); + Cvar_RegisterVariable( (convar_t *)variable ); +} + +/* +============= +pfnCvar_RegisterEngineVariable + +use with precaution: this cvar will NOT unlinked +after game.dll is unloaded +============= +*/ +void pfnCvar_RegisterEngineVariable( cvar_t *variable ) +{ + Cvar_RegisterVariable( (convar_t *)variable ); +} + +/* +============= +pfnCvar_RegisterVariable + +============= +*/ +cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ) +{ + if( FBitSet( flags, FCVAR_GLCONFIG )) + return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_CLIENTDLL, Cvar_BuildAutoDescription( flags|FCVAR_CLIENTDLL )); +} + +/* +============= +pfnCvar_RegisterVariable + +============= +*/ +cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ) +{ + if( FBitSet( flags, FCVAR_GLCONFIG )) + return (cvar_t *)Cvar_Get( szName, szValue, flags, va( CVAR_GLCONFIG_DESCRIPTION, szName )); + return (cvar_t *)Cvar_Get( szName, szValue, flags|FCVAR_GAMEUIDLL, Cvar_BuildAutoDescription( flags|FCVAR_GAMEUIDLL )); +} + +/* +============= +pfnCVarGetPointer + +can return NULL +============= +*/ +cvar_t *pfnCVarGetPointer( const char *szVarName ) +{ + return (cvar_t *)Cvar_FindVar( szVarName ); +} + +/* +============= +pfnCVarDirectSet + +allow to set cvar directly +============= +*/ +void pfnCVarDirectSet( cvar_t *var, const char *szValue ) +{ + Cvar_DirectSet( (convar_t *)var, szValue ); +} + +/* +============= +COM_CompareFileTime + +============= +*/ +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ) +{ + int bRet = 0; + + *iCompare = 0; + + if( filename1 && filename2 ) + { + long ft1 = FS_FileTime( filename1, false ); + long ft2 = FS_FileTime( filename2, false ); + + // one of files is missing + if( ft1 == -1 || ft2 == -1 ) + return bRet; + + *iCompare = Host_CompareFileTime( ft1, ft2 ); + bRet = 1; + } + + return bRet; +} + +/* +============= +COM_CheckParm + +============= +*/ +int COM_CheckParm( char *parm, char **ppnext ) +{ + static char str[64]; + + if( Sys_GetParmFromCmdLine( parm, str )) + { + // get the pointer on cmdline param + if( ppnext ) *ppnext = str; + return 1; + } + return 0; +} + +/* +============= +pfnTime + +============= +*/ +float pfnTime( void ) +{ + return (float)Sys_DoubleTime(); +} + +/* +============= +pfnGetGameDir + +============= +*/ +void pfnGetGameDir( char *szGetGameDir ) +{ + if( !szGetGameDir ) return; + Q_sprintf( szGetGameDir, "%s/%s", host.rootdir, GI->gamedir ); +} + +qboolean COM_IsSafeFileToDownload( const char *filename ) +{ + char lwrfilename[4096]; + const char *first, *last; + const char *ext; + int i; + + if( !COM_CheckString( filename )) + return false; + + if( !Q_strncmp( filename, "!MD5", 4 )) + return true; + + Q_strnlwr( filename, lwrfilename, sizeof( lwrfilename )); + + if( Q_strstr( lwrfilename, "\\" ) || Q_strstr( lwrfilename, ":" ) || Q_strstr( lwrfilename, ".." ) || Q_strstr( lwrfilename, "~" )) + return false; + + if( lwrfilename[0] == '/' ) + return false; + + first = Q_strchr( lwrfilename, '.' ); + last = Q_strrchr( lwrfilename, '.' ); + + if( first == NULL || last == NULL ) + return false; + + if( first != last ) + return false; + + if( Q_strlen( first ) != 4 ) + return false; + + ext = COM_FileExtension( lwrfilename ); + + for( i = 0; i < ARRAYSIZE( file_exts ); i++ ) + { + if( !Q_stricmp( ext, file_exts[i] )) + return false; + } + + return true; +} \ No newline at end of file diff --git a/engine/common/common.h b/engine/common/common.h new file mode 100644 index 00000000..71f2120c --- /dev/null +++ b/engine/common/common.h @@ -0,0 +1,1013 @@ +/* +common.h - definitions common between client and server +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef COMMON_H +#define COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* +=================================================================================================================================== +Legend: + +INTERNAL RESOURCE - function contain hardcoded path to resource that engine required (optional in most cases) +OBSOLETE, UNUSED - this function no longer used and leaved here for keep binary compatibility +TODO - some functionality not impemented but planned +FIXME - code doesn't working properly in some rare cases +HACKHACK - unexpected behavior on some input params (or something like) +BUGBUG - code doesn't working properly in most cases! +TESTTEST - this code may be unstable and needs to be more tested +g-cont: - notes from engine author +XASH SPECIFIC - sort of hack that works only in Xash3D not in GoldSrc +=================================================================================================================================== +*/ + +// configuration +#define HACKS_RELATED_HLMODS // some HL-mods works differently under Xash and can't be fixed without some hacks at least at current time + +// disable some warnings +#pragma warning(disable : 4244) // MIPS +#pragma warning(disable : 4018) // signed/unsigned mismatch +#pragma warning(disable : 4305) // truncation from const double to float +#pragma warning(disable : 4115) // named type definition in parentheses +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4057) // differs in indirection to slightly different base types +#pragma warning(disable : 4201) // nonstandard extension used +#pragma warning(disable : 4706) // assignment within conditional expression +#pragma warning(disable : 4054) // type cast' : from function pointer +#pragma warning(disable : 4310) // cast truncates constant value + +#define MAX_STRING 256 // generic string +#define MAX_INFO_STRING 256 // infostrings are transmitted across network +#define MAX_SERVERINFO_STRING 512 // server handles too many settings. expand to 1024? +#define MAX_LOCALINFO_STRING 32768 // localinfo used on server and not sended to the clients +#define MAX_SYSPATH 1024 // system filepath +#define MAX_PRINT_MSG 8192 // how many symbols can handle single call of Msg or MsgDev +#define MAX_TOKEN 2048 // parse token length +#define MAX_MODS 512 // environment games that engine can keep visible +#define MAX_USERMSG_LENGTH 2048 // don't modify it's relies on a client-side definitions + +#define EXPORT __declspec( dllexport ) +#define BIT( n ) (1<<( n )) +#define GAMMA ( 2.2 ) // Valve Software gamma +#define INVGAMMA ( 1.0 / 2.2 ) // back to 1.0 +#define TEXGAMMA ( 0.9 ) // compensate dim textures +#define SetBits( iBitVector, bits ) ((iBitVector) = (iBitVector) | (bits)) +#define ClearBits( iBitVector, bits ) ((iBitVector) = (iBitVector) & ~(bits)) +#define FBitSet( iBitVector, bit ) ((iBitVector) & (bit)) + +#ifndef __cplusplus +#define NULL ((void *)0) +#endif + +// color strings +#define IsColorString( p ) ( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' ) +#define ColorIndex( c ) ((( c ) - '0' ) & 7 ) + +typedef unsigned long dword; +typedef unsigned int uint; +typedef char string[MAX_STRING]; +typedef struct file_s file_t; // normal file +typedef struct wfile_s wfile_t; // wad file +typedef struct stream_s stream_t; // sound stream for background music playing + +typedef void (*setpair_t)( const char *key, const char *value, void *buffer, void *numpairs ); + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +enum +{ + DEV_NONE = 0, + DEV_NORMAL, + DEV_EXTENDED +}; + +enum +{ + D_INFO = 1, // "-dev 1", shows various system messages + D_WARN, // "-dev 2", shows not critical system warnings + D_ERROR, // "-dev 3", shows critical warnings + D_REPORT, // "-dev 4", special case for game reports + D_NOTE // "-dev 5", show system notifications for engine developers +}; + +typedef enum +{ + HOST_NORMAL, // listen server, singleplayer + HOST_DEDICATED, +} instance_t; + +#include "system.h" +#include "com_model.h" +#include "com_strings.h" +#include "crtlib.h" +#include "cvar.h" + +#define XASH_VERSION 0.99f // engine current version + +// PERFORMANCE INFO +#define MIN_FPS 20.0 // host minimum fps value for maxfps. +#define MAX_FPS 200.0 // upper limit for maxfps. +#define HOST_FPS 72.0 // multiplayer games typical fps + +#define MAX_FRAMETIME 0.25 +#define MIN_FRAMETIME 0.0001 +#define GAME_FPS 20.0 + +#define MAX_CMD_TOKENS 80 // cmd tokens +#define MAX_ENTNUMBER 99999 // for server and client parsing +#define MAX_HEARTBEAT -99999 // connection time +#define QCHAR_WIDTH 16 // font width + +#define CIN_MAIN 0 +#define CIN_LOGO 1 + +#define MAX_NUM_ARGVS 128 + +// config strings are a general means of communication from +// the server to all connected clients. +// each config string can be at most CS_SIZE characters. +#define MAX_QPATH 64 // max length of a game pathname +#define MAX_OSPATH 260 // max length of a filesystem pathname +#define CS_SIZE 64 // size of one config string +#define CS_TIME 16 // size of time string + +#define MAX_DECALS 512 // touching TE_DECAL messages, etc +#define MAX_STATIC_ENTITIES 3096 // static entities that moved on the client when level is spawn + +// filesystem flags +#define FS_STATIC_PATH 1 // FS_ClearSearchPath will be ignore this path +#define FS_NOWRITE_PATH 2 // default behavior - last added gamedir set as writedir. This flag disables it +#define FS_GAMEDIR_PATH 4 // just a marker for gamedir path + +#define GI SI.GameInfo +#define FS_Gamedir() SI.GameInfo->gamedir +#define FS_Title() SI.GameInfo->title +#define GameState (&host.game) + +#ifdef _DEBUG +void DBG_AssertFunction( qboolean fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ); +#define Assert( f ) DBG_AssertFunction( f, #f, __FILE__, __LINE__, NULL ) +#else +#define Assert( f ) +#endif + +extern convar_t *gl_vsync; +extern convar_t *scr_loading; +extern convar_t *scr_download; +extern convar_t *cmd_scripting; +extern convar_t *sv_maxclients; +extern convar_t *cl_allow_levelshots; +extern convar_t *vid_displayfrequency; +extern convar_t host_developer; +extern convar_t *host_limitlocal; +extern convar_t *host_framerate; +extern convar_t *host_maxfps; + +/* +============================================================== + +HOST INTERFACE + +============================================================== +*/ +/* +======================================================================== + +GAMEINFO stuff + +internal shared gameinfo structure (readonly for engine parts) +======================================================================== +*/ +typedef struct gameinfo_s +{ + // filesystem info + char gamefolder[MAX_QPATH]; // used for change game '-game x' + char basedir[MAX_QPATH]; // base game directory (like 'id1' for Quake or 'valve' for Half-Life) + char gamedir[MAX_QPATH]; // game directory (can be match with basedir, used as game dir and as write path) + char falldir[MAX_QPATH]; // used as second basedir + char startmap[MAX_QPATH];// map to start singleplayer game + char trainmap[MAX_QPATH];// map to start hazard course (if specified) + char title[64]; // Game Main Title + float version; // game version (optional) + + // .dll pathes + char dll_path[MAX_QPATH]; // e.g. "bin" or "cl_dlls" + char game_dll[MAX_QPATH]; // custom path for game.dll + + // .ico path + char iconpath[MAX_QPATH]; // "game.ico" by default + + // about mod info + string game_url; // link to a developer's site + string update_url; // link to updates page + char type[MAX_QPATH]; // single, toolkit, multiplayer etc + char date[MAX_QPATH]; + size_t size; + + int gamemode; + qboolean secure; // prevent to console acess + qboolean nomodels; // don't let player to choose model (use player.mdl always) + + char sp_entity[32]; // e.g. info_player_start + char mp_entity[32]; // e.g. info_player_deathmatch + + char ambientsound[NUM_AMBIENTS][MAX_QPATH]; // quake ambient sounds + + int max_edicts; // min edicts is 600, max edicts is 4096 + int max_tents; // min temp ents is 300, max is 2048 + int max_beams; // min beams is 64, max beams is 512 + int max_particles; // min particles is 4096, max particles is 32768 +} gameinfo_t; + +typedef struct sysinfo_s +{ + string exeName; // exe.filename + string rcName; // .rc script name + string basedirName; // name of base directory + gameinfo_t *GameInfo; // current GameInfo + gameinfo_t *games[MAX_MODS]; // environment games (founded at each engine start) + int numgames; +} sysinfo_t; + +typedef enum +{ + HOST_INIT = 0, // initalize operations + HOST_FRAME, // host running + HOST_SHUTDOWN, // shutdown operations + HOST_ERR_FATAL, // sys error + HOST_SLEEP, // sleeped by different reason, e.g. minimize window + HOST_NOFOCUS, // same as HOST_FRAME, but disable mouse + HOST_CRASHED // an exception handler called +} host_status_t; + +typedef enum +{ + STATE_RUNFRAME = 0, + STATE_LOAD_LEVEL, + STATE_LOAD_GAME, + STATE_CHANGELEVEL, + STATE_GAME_SHUTDOWN, +} host_state_t; + +typedef struct +{ + host_state_t curstate; + host_state_t nextstate; + char levelName[MAX_QPATH]; + char landmarkName[MAX_QPATH]; + qboolean backgroundMap; + qboolean loadGame; + qboolean newGame; // unload the server.dll before start a new map +} game_status_t; + +typedef enum +{ + key_console = 0, + key_game, + key_menu, + key_message +} keydest_t; + +// MD5 Hash +typedef struct +{ + uint buf[4]; + uint bits[2]; + byte in[64]; +} MD5Context_t; + +typedef enum +{ + RD_NONE = 0, + RD_CLIENT, + RD_PACKET +} rdtype_t; + +#include "net_ws.h" + +typedef struct host_redirect_s +{ + rdtype_t target; + char *buffer; + int buffersize; + netadr_t address; + void (*flush)( netadr_t adr, rdtype_t target, char *buffer ); +} host_redirect_t; + +typedef struct +{ + char name[MAX_QPATH]; + short entnum; + vec3_t origin; + float volume; + float attenuation; + qboolean looping; + byte channel; + byte pitch; + byte wordIndex; // current playing word in sentence + double samplePos; + double forcedEnd; +} soundlist_t; + +typedef struct +{ + char model[MAX_QPATH]; // curstate.modelindex = SV_ModelIndex + vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. + short clientIndex; + float fadeSpeed; + float bounceFactor; + byte hitSound; + qboolean high_priority; + float x, y, z; + int flags; + float time; + + // base state + vec3_t velocity; // baseline.origin + vec3_t avelocity; // baseline.angles + int fadeamount; // baseline.renderamt + float sparklife; // baseline.framerate + float thinkTime; // baseline.scale + + // current state + vec3_t origin; // entity.origin + vec3_t angles; // entity.angles + float renderamt; // curstate.renderamt + color24 rendercolor; // curstate.rendercolor + int rendermode; // curstate.rendermode + int renderfx; // curstate.renderfx + float framerate; // curstate.framerate + float frame; // curstate.frame + byte body; // curstate.body + byte skin; // curstate.skin + float scale; // curstate.scale +} tentlist_t; + +typedef struct host_parm_s +{ + HINSTANCE hInst; + HANDLE hMutex; + LPTOP_LEVEL_EXCEPTION_FILTER oldFilter; + + host_status_t status; // global host state + game_status_t game; // game manager + uint type; // running at + jmp_buf abortframe; // abort current frame + dword errorframe; // to prevent multiple host error + byte *mempool; // static mempool for misc allocations + string finalmsg; // server shutdown final message + string downloadfile; // filename to be downloading + int downloadcount; // how many files remain to downloading + host_redirect_t rd; // remote console + + // command line parms + int argc; + const char *argv[MAX_NUM_ARGVS]; + + double realtime; // host.curtime + double frametime; // time between engine frames + double realframetime; // for some system events, e.g. console animations + + uint framecount; // global framecount + + // list of unique decal indexes + char draw_decals[MAX_DECALS][MAX_QPATH]; + + vec3_t player_mins[MAX_MAP_HULLS]; // 4 hulls allowed + vec3_t player_maxs[MAX_MAP_HULLS]; // 4 hulls allowed + + HWND hWnd; // main window + qboolean allow_console; // allow console in dev-mode or multiplayer game + qboolean allow_console_init; // initial value to allow the console + qboolean key_overstrike; // key overstrike mode + qboolean stuffcmds_pending; // should execute stuff commands + qboolean allow_cheats; // this host will allow cheating + qboolean con_showalways; // show console always (developer and dedicated) + qboolean com_handlecolon; // allow COM_ParseFile to handle colon as single char + qboolean com_ignorebracket; // allow COM_ParseFile to ignore () as single char + qboolean change_game; // initialize when game is changed + qboolean mouse_visible; // vgui override cursor control + qboolean shutdown_issued; // engine is shutting down + qboolean force_draw_version; // used when fraps is loaded + qboolean write_to_clipboard; // put image to clipboard instead of disk + qboolean apply_game_config; // when true apply only to game cvars and ignore all other commands + qboolean config_executed; // a bit who indicated was config.cfg already executed e.g. from valve.rc + int sv_cvars_restored; // count of restored server cvars + qboolean crashed; // set to true if crashed + + // some settings were changed and needs to global update + qboolean userinfo_changed; + qboolean movevars_changed; + qboolean renderinfo_changed; + + char rootdir[256]; // member root directory + char gamefolder[MAX_QPATH]; // it's a default gamefolder + byte *imagepool; // imagelib mempool + byte *soundpool; // soundlib mempool + + uint features; // custom features that enables by mod-maker request + + // for IN_MouseMove() easy access + int window_center_x; + int window_center_y; + + struct decallist_s *decalList; // used for keep decals, when renderer is restarted or changed + int numdecals; +} host_parm_t; + +extern host_parm_t host; +extern sysinfo_t SI; + +// +// filesystem.c +// +void FS_Init( void ); +void FS_Path( void ); +void FS_Rescan( void ); +void FS_Shutdown( void ); +void FS_ClearSearchPath( void ); +void FS_AllowDirectPaths( qboolean enable ); +void FS_AddGameDirectory( const char *dir, int flags ); +void FS_AddGameHierarchy( const char *dir, int flags ); +void FS_LoadGameInfo( const char *rootfolder ); +void COM_FileBase( const char *in, char *out ); +const char *COM_FileExtension( const char *in ); +void COM_DefaultExtension( char *path, const char *extension ); +void COM_ReplaceExtension( char *path, const char *extension ); +void COM_ExtractFilePath( const char *path, char *dest ); +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ); +const char *COM_FileWithoutPath( const char *in ); +byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); +void W_Close( wfile_t *wad ); +byte *FS_LoadFile( const char *path, long *filesizeptr, qboolean gamedironly ); +qboolean FS_WriteFile( const char *filename, const void *data, long len ); +qboolean COM_ParseVector( char **pfile, float *v, size_t size ); +void COM_NormalizeAngles( vec3_t angles ); +int COM_FileSize( const char *filename ); +void COM_FixSlashes( char *pname ); +void COM_FreeFile( void *buffer ); +int COM_CheckString( const char *string ); +int COM_CompareFileTime( const char *filename1, const char *filename2, int *iCompare ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ); +long FS_Write( file_t *file, const void *data, size_t datasize ); +long FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_VPrintf( file_t *file, const char *format, va_list ap ); +int FS_Seek( file_t *file, long offset, int whence ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +int FS_Printf( file_t *file, const char *format, ... ); +long FS_FileSize( const char *filename, qboolean gamedironly ); +long FS_FileTime( const char *filename, qboolean gamedironly ); +int FS_Print( file_t *file, const char *msg ); +qboolean FS_Rename( const char *oldname, const char *newname ); +qboolean FS_FileExists( const char *filename, qboolean gamedironly ); +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ); +qboolean FS_Delete( const char *path ); +int FS_UnGetc( file_t *file, byte c ); +void COM_StripExtension( char *path ); +long FS_Tell( file_t *file ); +qboolean FS_Eof( file_t *file ); +int FS_Close( file_t *file ); +int FS_Getc( file_t *file ); +long FS_FileLength( file_t *f ); + +/* +======================================================================== + +internal image format + +typically expanded to rgba buffer +NOTE: number at end of pixelformat name it's a total bitscount e.g. PF_RGB_24 == PF_RGB_888 +======================================================================== +*/ +#define ImageRAW( type ) (type == PF_RGBA_32 || type == PF_BGRA_32 || type == PF_RGB_24 || type == PF_BGR_24) +#define ImageDXT( type ) (type == PF_DXT1 || type == PF_DXT3 || type == PF_DXT5) + +typedef enum +{ + PF_UNKNOWN = 0, + PF_INDEXED_24, // inflated palette (768 bytes) + PF_INDEXED_32, // deflated palette (1024 bytes) + PF_RGBA_32, // normal rgba buffer + PF_BGRA_32, // big endian RGBA (MacOS) + PF_RGB_24, // uncompressed dds or another 24-bit image + PF_BGR_24, // big-endian RGB (MacOS) + PF_DXT1, // s3tc DXT1 format + PF_DXT3, // s3tc DXT3 format + PF_DXT5, // s3tc DXT5 format + PF_TOTALCOUNT, // must be last +} pixformat_t; + +typedef struct bpc_desc_s +{ + int format; // pixelformat + char name[16]; // used for debug + uint glFormat; // RGBA format + int bpp; // channels (e.g. rgb = 3, rgba = 4) +} bpc_desc_t; + +// imagelib global settings +typedef enum +{ + IL_USE_LERPING = BIT(0), // lerping images during resample + IL_KEEP_8BIT = BIT(1), // don't expand paletted images + IL_ALLOW_OVERWRITE = BIT(2), // allow to overwrite stored images + IL_DONTFLIP_TGA = BIT(3), // Steam background completely ignore tga attribute 0x20 (stupid lammers!) + IL_DDS_HARDWARE = BIT(4), // DXT compression is support + IL_LOAD_DECAL = BIT(5), // special mode for load gradient decals + IL_OVERVIEW = BIT(6), // overview required some unque operations +} ilFlags_t; + +// goes into rgbdata_t->encode +#define DXT_ENCODE_DEFAULT 0 // don't use custom encoders +#define DXT_ENCODE_COLOR_YCoCg 0x1A01 // make sure that value dosn't collide with anything +#define DXT_ENCODE_ALPHA_1BIT 0x1A02 // normal 1-bit alpha +#define DXT_ENCODE_ALPHA_8BIT 0x1A03 // normal 8-bit alpha +#define DXT_ENCODE_ALPHA_SDF 0x1A04 // signed distance field +#define DXT_ENCODE_NORMAL_AG_ORTHO 0x1A05 // orthographic projection +#define DXT_ENCODE_NORMAL_AG_STEREO 0x1A06 // stereographic projection +#define DXT_ENCODE_NORMAL_AG_PARABOLOID 0x1A07 // paraboloid projection +#define DXT_ENCODE_NORMAL_AG_QUARTIC 0x1A08 // newton method +#define DXT_ENCODE_NORMAL_AG_AZIMUTHAL 0x1A09 // Lambert Azimuthal Equal-Area + +// rgbdata output flags +typedef enum +{ + // rgbdata->flags + IMAGE_CUBEMAP = BIT(0), // it's 6-sides cubemap buffer + IMAGE_HAS_ALPHA = BIT(1), // image contain alpha-channel + IMAGE_HAS_COLOR = BIT(2), // image contain RGB-channel + IMAGE_COLORINDEX = BIT(3), // all colors in palette is gradients of last color (decals) + IMAGE_HAS_LUMA = BIT(4), // image has luma pixels (q1-style maps) + IMAGE_SKYBOX = BIT(5), // only used by FS_SaveImage - for write right suffixes + IMAGE_QUAKESKY = BIT(6), // it's a quake sky double layered clouds (so keep it as 8 bit) + IMAGE_DDS_FORMAT = BIT(7), // a hint for GL loader + IMAGE_MULTILAYER = BIT(8), // to differentiate from 3D texture + IMAGE_ONEBIT_ALPHA = BIT(9), // binary alpha + IMAGE_QUAKEPAL = BIT(10), // image has quake1 palette + + // Image_Process manipulation flags + IMAGE_FLIP_X = BIT(16), // flip the image by width + IMAGE_FLIP_Y = BIT(17), // flip the image by height + IMAGE_ROT_90 = BIT(18), // flip from upper left corner to down right corner + IMAGE_ROT180 = IMAGE_FLIP_X|IMAGE_FLIP_Y, + IMAGE_ROT270 = IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90, +// reserved + IMAGE_RESAMPLE = BIT(20), // resample image to specified dims +// reserved +// reserved + IMAGE_FORCE_RGBA = BIT(23), // force image to RGBA buffer + IMAGE_MAKE_LUMA = BIT(24), // create luma texture from indexed + IMAGE_QUANTIZE = BIT(25), // make indexed image from 24 or 32- bit image + IMAGE_LIGHTGAMMA = BIT(26), // apply gamma for image + IMAGE_REMAP = BIT(27), // interpret width and height as top and bottom color +} imgFlags_t; + +// ordering is important! +typedef enum +{ + BLUR_FILTER = 0, + BLUR_FILTER2, + EDGE_FILTER, + EMBOSS_FILTER, + NUM_FILTERS, +} pixfilter_t; + +typedef struct rgbdata_s +{ + word width; // image width + word height; // image height + word depth; // image depth + uint type; // compression type + uint flags; // misc image flags + word encode; // DXT may have custom encoder, that will be decoded in GLSL-side + byte numMips; // mipmap count + byte *palette; // palette if present + byte *buffer; // image buffer + rgba_t fogParams; // some water textures in hl1 has info about fog color and alpha + size_t size; // for bounds checking +} rgbdata_t; + +// imgfilter processing flags +typedef enum +{ + FILTER_GRAYSCALE = BIT(0), +} flFlags_t; + +typedef struct imgfilter_s +{ + int filter; // pixfilter_t + float factor; // filter factor value + float bias; // filter bias value + flFlags_t flags; // filter additional flags + uint blendFunc; // blending mode +} imgfilter_t; + +// +// imagelib +// +void Image_Init( void ); +void Image_Shutdown( void ); +void Image_AddCmdFlags( uint flags ); +rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ); +qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ); +rgbdata_t *FS_CopyImage( rgbdata_t *in ); +void FS_FreeImage( rgbdata_t *pack ); +extern const bpc_desc_t PFDesc[]; // image get pixelformat +qboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, imgfilter_t *filter ); +void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int pal_size ); +void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size ); +void Image_SetForceFlags( uint flags ); // set image force flags on loading +size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ); +void Image_ClearForceFlags( void ); + +/* +======================================================================== + +internal sound format + +typically expanded to wav buffer +======================================================================== +*/ +typedef enum +{ + WF_UNKNOWN = 0, + WF_PCMDATA, + WF_MPGDATA, + WF_TOTALCOUNT, // must be last +} sndformat_t; + +// soundlib global settings +typedef enum +{ + SL_USE_LERPING = BIT(0), // lerping sounds during resample + SL_KEEP_8BIT = BIT(1), // don't expand 8bit sounds automatically up to 16 bit + SL_ALLOW_OVERWRITE = BIT(2), // allow to overwrite stored sounds +} slFlags_t; + +// wavdata output flags +typedef enum +{ + // wavdata->flags + SOUND_LOOPED = BIT( 0 ), // this is looped sound (contain cue markers) + SOUND_STREAM = BIT( 1 ), // this is a streaminfo, not a real sound + + // Sound_Process manipulation flags + SOUND_RESAMPLE = BIT(12), // resample sound to specified rate + SOUND_CONVERT16BIT = BIT(13), // change sound resolution from 8 bit to 16 +} sndFlags_t; + +typedef struct +{ + word rate; // num samples per second (e.g. 11025 - 11 khz) + byte width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) + byte channels; // num channels (1 - mono, 2 - stereo) + int loopStart; // offset at this point sound will be looping while playing more than only once + int samples; // total samplecount in wav + uint type; // compression type + uint flags; // misc sound flags + byte *buffer; // sound buffer + size_t size; // for bounds checking +} wavdata_t; + +// +// soundlib +// +void Sound_Init( void ); +void Sound_Shutdown( void ); +wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ); +void FS_FreeSound( wavdata_t *pack ); +stream_t *FS_OpenStream( const char *filename ); +wavdata_t *FS_StreamInfo( stream_t *stream ); +long FS_ReadStream( stream_t *stream, int bytes, void *buffer ); +long FS_SetStreamPos( stream_t *stream, long newpos ); +long FS_GetStreamPos( stream_t *stream ); +void FS_FreeStream( stream_t *stream ); +qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ); + +// +// build.c +// +int Q_buildnum( void ); + +// +// host.c +// +void EXPORT Host_Shutdown( void ); +int Host_CompareFileTime( long ft1, long ft2 ); +void Host_NewInstance( const char *name, const char *finalmsg ); +void Host_EndGame( qboolean abort, const char *message, ... ); +void Host_AbortCurrentFrame( void ); +void Host_WriteServerConfig( const char *name ); +void Host_WriteOpenGLConfig( void ); +void Host_WriteVideoConfig( void ); +void Host_WriteConfig( void ); +qboolean Host_IsLocalGame( void ); +qboolean Host_IsLocalClient( void ); +void Host_ShutdownServer( void ); +void Host_Error( const char *error, ... ); +void Host_PrintEngineFeatures( void ); +void Host_Frame( float time ); +void Host_InitDecals( void ); +void Host_Credits( void ); + +// +// host_state.c +// +void COM_InitHostState( void ); +void COM_NewGame( char const *pMapName ); +void COM_LoadLevel( char const *pMapName, qboolean background ); +void COM_LoadGame( char const *pSaveFileName ); +void COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background ); +void COM_Frame( float time ); + +/* +============================================================== + +CLIENT / SERVER SYSTEMS + +============================================================== +*/ +void CL_Init( void ); +void CL_Shutdown( void ); +void Host_ClientBegin( void ); +void Host_ClientFrame( void ); +qboolean CL_Active( void ); + +void SV_Init( void ); +void SV_Shutdown( const char *finalmsg ); +void Host_ServerFrame( void ); +qboolean SV_Active( void ); + +/* +============================================================== + + SHARED ENGFUNCS + +============================================================== +*/ +void pfnCvar_RegisterServerVariable( cvar_t *variable ); +void pfnCvar_RegisterEngineVariable( cvar_t *variable ); +cvar_t *pfnCvar_RegisterClientVariable( const char *szName, const char *szValue, int flags ); +cvar_t *pfnCvar_RegisterGameUIVariable( const char *szName, const char *szValue, int flags ); +char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ); +void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ); +int COM_SaveFile( const char *filename, const void *data, long len ); +byte* COM_LoadFileForMe( const char *filename, int *pLength ); +qboolean COM_IsSafeFileToDownload( const char *filename ); +cvar_t *pfnCVarGetPointer( const char *szVarName ); +int pfnDrawConsoleString( int x, int y, char *string ); +void pfnDrawSetTextColor( float r, float g, float b ); +void pfnDrawConsoleStringLen( const char *pText, int *length, int *height ); +void *Cache_Check( byte *mempool, struct cache_user_s *c ); +void COM_TrimSpace( const char *source, char *dest ); +edict_t* pfnPEntityOfEntIndex( int iEntIndex ); +void pfnGetModelBounds( model_t *mod, float *mins, float *maxs ); +void pfnCVarDirectSet( cvar_t *var, const char *szValue ); +int COM_CheckParm( char *parm, char **ppnext ); +void pfnGetGameDir( char *szGetGameDir ); +int pfnDecalIndex( const char *m ); +int pfnGetModelType( model_t *mod ); +int pfnIsMapValid( char *filename ); +void Con_Reportf( char *szFmt, ... ); +void Con_DPrintf( char *fmt, ... ); +void Con_Printf( char *szFmt, ... ); +int pfnNumberOfEntities( void ); +int pfnIsInGame( void ); +float pfnTime( void ); + +/* +============================================================== + + MISC COMMON FUNCTIONS + +============================================================== +*/ +#define Z_Malloc( size ) Mem_Alloc( host.mempool, size ) +#define Z_Realloc( ptr, size ) Mem_Realloc( host.mempool, ptr, size ) +#define Z_Free( ptr ) if( ptr != NULL ) Mem_Free( ptr ) + +// +// crclib.c +// +void CRC32_Init( dword *pulCRC ); +byte CRC32_BlockSequence( byte *base, int length, int sequence ); +void CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ); +void CRC32_ProcessByte( dword *pulCRC, byte ch ); +dword CRC32_Final( dword pulCRC ); +qboolean CRC32_File( dword *crcvalue, const char *filename ); +qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer ); +void MD5Init( MD5Context_t *ctx ); +void MD5Update( MD5Context_t *ctx, const byte *buf, uint len ); +void MD5Final( byte digest[16], MD5Context_t *ctx ); +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ); +uint COM_HashKey( const char *string, uint hashSize ); +char *MD5_Print( byte hash[16] ); + +// +// custom.c +// +void COM_ClearCustomizationList( customization_t *pHead, qboolean bCleanDecals ); +qboolean COM_CreateCustomization( customization_t *pHead, resource_t *pRes, int playernum, int flags, customization_t **pCust, int *nLumps ); +int COM_SizeofResourceList( resource_t *pList, resourceinfo_t *ri ); + +// +// cfgscript.c +// +int CSCR_LoadDefaultCVars( const char *scriptfilename ); +int CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename ); + +// +// hpak.c +// +void HPAK_Init( void ); +qboolean HPAK_GetDataPointer( const char *filename, struct resource_s *pRes, byte **buffer, int *size ); +qboolean HPAK_ResourceForHash( const char *filename, byte *hash, struct resource_s *pRes ); +void HPAK_AddLump( qboolean queue, const char *filename, struct resource_s *pRes, byte *data, file_t *f ); +void HPAK_RemoveLump( const char *name, resource_t *resource ); +void HPAK_CheckIntegrity( const char *filename ); +void HPAK_CheckSize( const char *filename ); +void HPAK_FlushHostQueue( void ); + +// +// keys.c +// +qboolean Key_IsDown( int keynum ); +const char *Key_IsBind( int keynum ); +void Key_Event( int key, qboolean down ); +void Key_Init( void ); +void Key_WriteBindings( file_t *f ); +const char *Key_GetBinding( int keynum ); +void Key_SetBinding( int keynum, const char *binding ); +void Key_ClearStates( void ); +const char *Key_KeynumToString( int keynum ); +int Key_StringToKeynum( const char *str ); +int Key_GetKey( const char *binding ); +void Key_EnumCmds_f( void ); +void Key_SetKeyDest( int key_dest ); + +// +// avikit.c +// +typedef struct movie_state_s movie_state_t; +long AVI_GetVideoFrameNumber( movie_state_t *Avi, float time ); +byte *AVI_GetVideoFrame( movie_state_t *Avi, long frame ); +qboolean AVI_GetVideoInfo( movie_state_t *Avi, long *xres, long *yres, float *duration ); +qboolean AVI_GetAudioInfo( movie_state_t *Avi, wavdata_t *snd_info ); +long AVI_GetAudioChunk( movie_state_t *Avi, char *audiodata, long offset, long length ); +void AVI_OpenVideo( movie_state_t *Avi, const char *filename, qboolean load_audio, int quiet ); +movie_state_t *AVI_LoadVideo( const char *filename, qboolean load_audio ); +movie_state_t *AVI_LoadVideoNoSound( const char *filename ); +void AVI_CloseVideo( movie_state_t *Avi ); +qboolean AVI_IsActive( movie_state_t *Avi ); +void AVI_FreeVideo( movie_state_t *Avi ); +movie_state_t *AVI_GetState( int num ); +qboolean AVI_Initailize( void ); +void AVI_Shutdown( void ); + +// shared calls +qboolean CL_IsInGame( void ); +qboolean CL_IsInMenu( void ); +qboolean CL_IsInConsole( void ); +qboolean CL_IsThirdPerson( void ); +qboolean CL_IsIntermission( void ); +qboolean CL_Initialized( void ); +char *CL_Userinfo( void ); +void CL_CharEvent( int key ); +qboolean CL_DisableVisibility( void ); +int CL_PointContents( const vec3_t point ); +char *COM_ParseFile( char *data, char *token ); +byte *COM_LoadFile( const char *filename, int usehunk, int *pLength ); +qboolean CL_GetDemoComment( const char *demoname, char *comment ); +void COM_AddAppDirectoryToSearchPath( const char *pszBaseDir, const char *appName ); +int COM_ExpandFilename( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); +struct pmtrace_s *PM_TraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ); +void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ); +void SV_StartMusic( const char *curtrack, const char *looptrack, long position ); +void SV_CreateDecal( struct sizebuf_s *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ); +void Log_Printf( const char *fmt, ... ); +struct sizebuf_s *SV_GetReliableDatagram( void ); +void SV_BroadcastCommand( const char *fmt, ... ); +qboolean SV_RestoreCustomDecal( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent ); +void SV_BroadcastPrintf( struct sv_client_s *ignore, char *fmt, ... ); +int R_CreateDecalList( struct decallist_s *pList ); +void R_DecalRemoveAll( int texture ); +void R_ClearAllDecals( void ); +void R_ClearStaticEntities( void ); +qboolean S_StreamGetCurrentState( char *currentTrack, char *loopTrack, int *position ); +struct cl_entity_s *CL_GetEntityByIndex( int index ); +struct player_info_s *CL_GetPlayerInfo( int playerIndex ); +void CL_ServerCommand( qboolean reliable, char *fmt, ... ); +void CL_HudMessage( const char *pMessage ); +const char *CL_MsgInfo( int cmd ); +void SV_DrawDebugTriangles( void ); +void SV_DrawOrthoTriangles( void ); +double CL_GetDemoFramerate( void ); +qboolean UI_CreditsActive( void ); +void CL_ExtraUpdate( void ); +int CL_GetMaxClients( void ); +int SV_GetMaxClients( void ); +qboolean CL_IsRecordDemo( void ); +qboolean CL_IsTimeDemo( void ); +qboolean CL_IsPlaybackDemo( void ); +qboolean CL_IsBackgroundDemo( void ); +qboolean CL_IsBackgroundMap( void ); +qboolean SV_Initialized( void ); +qboolean CL_LoadProgs( const char *name ); +qboolean SV_GetSaveComment( const char *savename, char *comment ); +qboolean SV_NewGame( const char *mapName, qboolean loadGame ); +void SV_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); +void CL_ClipPMoveToEntity( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); +void CL_Particle( const vec3_t origin, int color, float life, int zpos, int zvel ); // debug thing +void SV_SysError( const char *error_string ); +void SV_ShutdownGame( void ); +void SV_ExecLoadLevel( void ); +void SV_ExecLoadGame( void ); +void SV_ExecChangeLevel( void ); +void SV_InitGameProgs( void ); +void SV_FreeGameProgs( void ); +void CL_WriteMessageHistory( void ); +void CL_SendCmd( void ); +void CL_Disconnect( void ); +void CL_ClearEdicts( void ); +void CL_Crashed( void ); +qboolean CL_NextDemo( void ); +char *SV_Serverinfo( void ); +void CL_Drop( void ); +void Con_Init( void ); +void SCR_Init( void ); +void SCR_UpdateScreen( void ); +void SCR_BeginLoadingPlaque( qboolean is_background ); +void SCR_CheckStartupVids( void ); +long SCR_GetAudioChunk( char *rawdata, long length ); +wavdata_t *SCR_GetMovieInfo( void ); +void SCR_Shutdown( void ); +void Con_Print( const char *txt ); +void Con_NPrintf( int idx, char *fmt, ... ); +void Con_NXPrintf( struct con_nprint_s *info, char *fmt, ... ); +void UI_NPrintf( int idx, char *fmt, ... ); +void UI_NXPrintf( struct con_nprint_s *info, char *fmt, ... ); +char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemovePrefixedKeys( char *start, char prefix ); +qboolean Info_RemoveKey( char *s, const char *key ); +qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize ); +qboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize ); +qboolean Info_IsValid( const char *s ); +void Info_WriteVars( file_t *f ); +void Info_Print( const char *s ); +void Cmd_WriteVariables( file_t *f ); +qboolean Cmd_CheckMapsList( qboolean fRefresh ); +qboolean Cmd_AutocompleteName( const char *source, char *buffer, size_t bufsize ); +void Cmd_AutoComplete( char *complete_string ); +void COM_SetRandomSeed( long lSeed ); +long COM_RandomLong( long lMin, long lMax ); +float COM_RandomFloat( float fMin, float fMax ); +qboolean LZSS_IsCompressed( const byte *source ); +uint LZSS_GetActualSize( const byte *source ); +byte *LZSS_Compress( byte *pInput, int inputLength, uint *pOutputSize ); +uint LZSS_Decompress( const byte *pInput, byte *pOutput ); +const byte *GL_TextureData( unsigned int texnum ); +void GL_FreeImage( const char *name ); +void VID_InitDefaultResolution( void ); +void UI_SetActiveMenu( qboolean fActive ); +void Cmd_Null_f( void ); + +// soundlib shared exports +qboolean S_Init( void ); +void S_Shutdown( void ); +void S_Activate( qboolean active, void *hInst ); +void S_StopSound( int entnum, int channel, const char *soundname ); +int S_GetCurrentStaticSounds( soundlist_t *pout, int size ); +void S_StopBackgroundTrack( void ); +void S_StopAllSounds( qboolean ambient ); + +// gamma routines +void BuildGammaTable( float gamma, float brightness ); +byte LightToTexGamma( byte b ); +byte TextureToGamma( byte b ); + +#ifdef __cplusplus +} +#endif +#endif//COMMON_H \ No newline at end of file diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c new file mode 100644 index 00000000..61d5dd5f --- /dev/null +++ b/engine/common/con_utils.c @@ -0,0 +1,1062 @@ +/* +con_utils.c - console helpers +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "const.h" +#include "../cl_dll/kbutton.h" + +extern convar_t *con_gamemaps; + +typedef struct autocomplete_list_s +{ + const char *name; + qboolean (*func)( const char *s, char *name, int length ); +} autocomplete_list_t; + +/* +======================================================================= + + FILENAME AUTOCOMPLETION +======================================================================= +*/ +/* +===================================== +Cmd_GetMapList + +Prints or complete map filename +===================================== +*/ +qboolean Cmd_GetMapList( const char *s, char *completedname, int length ) +{ + search_t *t; + file_t *f; + string message; + string compiler; + string generator; + string matchbuf; + byte buf[MAX_SYSPATH]; // 1 kb + int i, nummaps; + + t = FS_Search( va( "maps/%s*.bsp", s ), true, con_gamemaps->value ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for( i = 0, nummaps = 0; i < t->numfilenames; i++ ) + { + char entfilename[MAX_QPATH]; + const char *ext = COM_FileExtension( t->filenames[i] ); + int ver = -1, lumpofs = 0, lumplen = 0; + char *ents = NULL, *pfile; + qboolean validmap = false; + int version = 0; + + if( Q_stricmp( ext, "bsp" )) continue; + Q_strncpy( message, "^1error^7", sizeof( message )); + Q_strncpy( compiler, "", sizeof( compiler )); + Q_strncpy( generator, "", sizeof( generator )); + f = FS_Open( t->filenames[i], "rb", con_gamemaps->value ); + + if( f ) + { + dheader_t *header; + dextrahdr_t *hdrext; + + memset( buf, 0, sizeof( buf )); + FS_Read( f, buf, sizeof( buf )); + header = (dheader_t *)buf; + ver = header->version; + + // check all the lumps and some other errors + if( Mod_TestBmodelLumps( t->filenames[i], buf, true )) + { + lumpofs = header->lumps[LUMP_ENTITIES].fileofs; + lumplen = header->lumps[LUMP_ENTITIES].filelen; + ver = header->version; + } + + hdrext = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t )); + if( hdrext->id == IDEXTRAHEADER ) version = hdrext->version; + + Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); + COM_StripExtension( entfilename ); + COM_DefaultExtension( entfilename, ".ent" ); + ents = FS_LoadFile( entfilename, NULL, true ); + + if( !ents && lumplen >= 10 ) + { + FS_Seek( f, lumpofs, SEEK_SET ); + ents = (char *)Mem_Alloc( host.mempool, lumplen + 1 ); + FS_Read( f, ents, lumplen ); + } + + if( ents ) + { + // if there are entities to parse, a missing message key just + // means there is no title, so clear the message string now + char token[2048]; + + message[0] = 0; // remove 'error' + pfile = ents; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_strcmp( token, "{" )) continue; + else if( !Q_strcmp( token, "}" )) break; + else if( !Q_strcmp( token, "message" )) + { + // get the message contents + pfile = COM_ParseFile( pfile, message ); + } + else if( !Q_strcmp( token, "compiler" ) || !Q_strcmp( token, "_compiler" )) + { + // get the message contents + pfile = COM_ParseFile( pfile, compiler ); + } + else if( !Q_strcmp( token, "generator" ) || !Q_strcmp( token, "_generator" )) + { + // get the message contents + pfile = COM_ParseFile( pfile, generator ); + } + } + Mem_Free( ents ); + } + } + + if( f ) FS_Close(f); + COM_FileBase( t->filenames[i], matchbuf ); + + switch( ver ) + { + case Q1BSP_VERSION: + Q_strncpy( buf, "Quake", sizeof( buf )); + break; + case QBSP2_VERSION: + Q_strncpy( buf, "Darkplaces BSP2", sizeof( buf )); + break; + case HLBSP_VERSION: + switch( version ) + { + case 1: Q_strncpy( buf, "XashXT old format", sizeof( buf )); break; + case 2: Q_strncpy( buf, "Paranoia 2: Savior", sizeof( buf )); break; + case 4: Q_strncpy( buf, "Half-Life extended", sizeof( buf )); break; + default: Q_strncpy( buf, "Half-Life", sizeof( buf )); break; + } + break; + default: Q_strncpy( buf, "??", sizeof( buf )); break; + } + + Con_Printf( "%16s (%s) ^3%s^7 ^2%s %s^7\n", matchbuf, buf, message, compiler, generator ); + nummaps++; + } + + Con_Printf( "\n^3 %i maps found.\n", nummaps ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + return true; +} + +/* +===================================== +Cmd_GetDemoList + +Prints or complete demo filename +===================================== +*/ +qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numdems; + + t = FS_Search( va( "demos/%s*.dem", s ), true, true ); // lookup only in gamedir + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for( i = 0, numdems = 0; i < t->numfilenames; i++ ) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "dem" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numdems++; + } + + Con_Printf( "\n^3 %i demos found.\n", numdems ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +/* +===================================== +Cmd_GetMovieList + +Prints or complete movie filename +===================================== +*/ +qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, nummovies; + + t = FS_Search( va( "media/%s*.avi", s ), true, false ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for(i = 0, nummovies = 0; i < t->numfilenames; i++) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "avi" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + nummovies++; + } + + Con_Printf( "\n^3 %i movies found.\n", nummovies ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + + return true; +} + +/* +===================================== +Cmd_GetMusicList + +Prints or complete background track filename +===================================== +*/ +qboolean Cmd_GetMusicList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numtracks; + + t = FS_Search( va( "media/%s*.*", s ), true, false ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for(i = 0, numtracks = 0; i < t->numfilenames; i++) + { + const char *ext = COM_FileExtension( t->filenames[i] ); + + if( Q_stricmp( ext, "wav" ) && Q_stricmp( ext, "mp3" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numtracks++; + } + + Con_Printf( "\n^3 %i soundtracks found.\n", numtracks ); + Mem_Free(t); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +/* +===================================== +Cmd_GetSavesList + +Prints or complete savegame filename +===================================== +*/ +qboolean Cmd_GetSavesList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numsaves; + + t = FS_Search( va( "%s%s*.sav", DEFAULT_SAVE_DIRECTORY, s ), true, true ); // lookup only in gamedir + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for( i = 0, numsaves = 0; i < t->numfilenames; i++ ) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "sav" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numsaves++; + } + + Con_Printf( "\n^3 %i saves found.\n", numsaves ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + + return true; +} + +/* +===================================== +Cmd_GetConfigList + +Prints or complete .cfg filename +===================================== +*/ +qboolean Cmd_GetConfigList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numconfigs; + + t = FS_Search( va( "%s*.cfg", s ), true, false ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for( i = 0, numconfigs = 0; i < t->numfilenames; i++ ) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "cfg" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numconfigs++; + } + + Con_Printf( "\n^3 %i configs found.\n", numconfigs ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + + return true; +} + +/* +===================================== +Cmd_GetSoundList + +Prints or complete sound filename +===================================== +*/ +qboolean Cmd_GetSoundList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numsounds; + + t = FS_Search( va( "%s%s*.*", DEFAULT_SOUNDPATH, s ), true, false ); + if( !t ) return false; + + Q_strncpy( matchbuf, t->filenames[0] + Q_strlen( DEFAULT_SOUNDPATH ), MAX_STRING ); + COM_StripExtension( matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for(i = 0, numsounds = 0; i < t->numfilenames; i++) + { + const char *ext = COM_FileExtension( t->filenames[i] ); + + if( Q_stricmp( ext, "wav" ) && Q_stricmp( ext, "mp3" )) + continue; + + Q_strncpy( matchbuf, t->filenames[i] + Q_strlen( DEFAULT_SOUNDPATH ), MAX_STRING ); + COM_StripExtension( matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numsounds++; + } + + Con_Printf( "\n^3 %i sounds found.\n", numsounds ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + + return true; +} + +/* +===================================== +Cmd_GetItemsList + +Prints or complete item classname (weapons only) +===================================== +*/ +qboolean Cmd_GetItemsList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numitems; + + if( !clgame.itemspath[0] ) return false; // not in game yet + t = FS_Search( va( "%s/%s*.txt", clgame.itemspath, s ), true, false ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for(i = 0, numitems = 0; i < t->numfilenames; i++) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "txt" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numitems++; + } + + Con_Printf( "\n^3 %i items found.\n", numitems ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +/* +===================================== +Cmd_GetCustomList + +Prints or complete .HPK filenames +===================================== +*/ +qboolean Cmd_GetCustomList( const char *s, char *completedname, int length ) +{ + search_t *t; + string matchbuf; + int i, numitems; + + t = FS_Search( va( "%s*.hpk", s ), true, false ); + if( !t ) return false; + + COM_FileBase( t->filenames[0], matchbuf ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( t->numfilenames == 1 ) return true; + + for(i = 0, numitems = 0; i < t->numfilenames; i++) + { + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "hpk" )) + continue; + + COM_FileBase( t->filenames[i], matchbuf ); + Con_Printf( "%16s\n", matchbuf ); + numitems++; + } + + Con_Printf( "\n^3 %i items found.\n", numitems ); + Mem_Free( t ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +/* +===================================== +Cmd_GetGameList + +Prints or complete gamedir name +===================================== +*/ +qboolean Cmd_GetGamesList( const char *s, char *completedname, int length ) +{ + int i, numgamedirs; + string gamedirs[MAX_MODS]; + string matchbuf; + + // stand-alone games doesn't have cmd "game" + if( !Cmd_Exists( "game" )) + return false; + + // compare gamelist with current keyword + for( i = 0, numgamedirs = 0; i < SI.numgames; i++ ) + { + if(( *s == '*' ) || !Q_strnicmp( SI.games[i]->gamefolder, s, Q_strlen( s ))) + Q_strcpy( gamedirs[numgamedirs++], SI.games[i]->gamefolder ); + } + + if( !numgamedirs ) return false; + Q_strncpy( matchbuf, gamedirs[0], MAX_STRING ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( numgamedirs == 1 ) return true; + + for( i = 0; i < numgamedirs; i++ ) + { + Q_strncpy( matchbuf, gamedirs[i], MAX_STRING ); + Con_Printf( "%16s\n", matchbuf ); + } + + Con_Printf( "\n^3 %i games found.\n", numgamedirs ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +/* +===================================== +Cmd_GetCDList + +Prints or complete CD command name +===================================== +*/ +qboolean Cmd_GetCDList( const char *s, char *completedname, int length ) +{ + int i, numcdcommands; + string cdcommands[8]; + string matchbuf; + + const char *cd_command[] = + { + "info", + "loop", + "off", + "on", + "pause", + "play", + "resume", + "stop", + }; + + // compare CD command list with current keyword + for( i = 0, numcdcommands = 0; i < 8; i++ ) + { + if(( *s == '*' ) || !Q_strnicmp( cd_command[i], s, Q_strlen( s ))) + Q_strcpy( cdcommands[numcdcommands++], cd_command[i] ); + } + + if( !numcdcommands ) return false; + Q_strncpy( matchbuf, cdcommands[0], MAX_STRING ); + if( completedname && length ) + Q_strncpy( completedname, matchbuf, length ); + if( numcdcommands == 1 ) return true; + + for( i = 0; i < numcdcommands; i++ ) + { + Q_strncpy( matchbuf, cdcommands[i], MAX_STRING ); + Con_Printf( "%16s\n", matchbuf ); + } + + Con_Printf( "\n^3 %i commands found.\n", numcdcommands ); + + // cut shortestMatch to the amount common with s + if( completedname && length ) + { + for( i = 0; matchbuf[i]; i++ ) + { + if( Q_tolower( completedname[i] ) != Q_tolower( matchbuf[i] )) + completedname[i] = 0; + } + } + return true; +} + +qboolean Cmd_CheckMapsList_R( qboolean fRefresh, qboolean onlyingamedir ) +{ + byte buf[MAX_SYSPATH]; + char *buffer; + string result; + int i, size; + search_t *t; + file_t *f; + + if( FS_FileSize( "maps.lst", onlyingamedir ) > 0 && !fRefresh ) + { + MsgDev( D_NOTE, "maps.lst is exist: %s\n", onlyingamedir ? "basedir" : "gamedir" ); + return true; // exist + } + + t = FS_Search( "maps/*.bsp", false, onlyingamedir ); + + if( !t ) + { + if( onlyingamedir ) + { + // mod doesn't contain any maps (probably this is a bot) + return Cmd_CheckMapsList_R( fRefresh, false ); + } + return false; + } + + buffer = Mem_Alloc( host.mempool, t->numfilenames * 2 * sizeof( result )); + + for( i = 0; i < t->numfilenames; i++ ) + { + char *ents = NULL, *pfile; + int lumpofs = 0, lumplen = 0; + string mapname, message, entfilename; + + if( Q_stricmp( COM_FileExtension( t->filenames[i] ), "bsp" )) + continue; + + f = FS_Open( t->filenames[i], "rb", onlyingamedir ); + COM_FileBase( t->filenames[i], mapname ); + + if( f ) + { + int num_spawnpoints = 0; + dheader_t *header; + + memset( buf, 0, MAX_SYSPATH ); + FS_Read( f, buf, MAX_SYSPATH ); + header = (dheader_t *)buf; + + // check all the lumps and some other errors + if( !Mod_TestBmodelLumps( t->filenames[i], buf, true )) + { + FS_Close( f ); + continue; + } + + // after call Mod_TestBmodelLumps we gurantee what map is valid + lumpofs = header->lumps[LUMP_ENTITIES].fileofs; + lumplen = header->lumps[LUMP_ENTITIES].filelen; + + Q_strncpy( entfilename, t->filenames[i], sizeof( entfilename )); + COM_StripExtension( entfilename ); + COM_DefaultExtension( entfilename, ".ent" ); + ents = FS_LoadFile( entfilename, NULL, true ); + + if( !ents && lumplen >= 10 ) + { + FS_Seek( f, lumpofs, SEEK_SET ); + ents = Z_Malloc( lumplen + 1 ); + FS_Read( f, ents, lumplen ); + } + + if( ents ) + { + // if there are entities to parse, a missing message key just + // means there is no title, so clear the message string now + char token[MAX_TOKEN]; + qboolean worldspawn = true; + + Q_strncpy( message, "No Title", MAX_STRING ); + pfile = ents; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( token[0] == '}' && worldspawn ) + worldspawn = false; + else if( !Q_strcmp( token, "message" ) && worldspawn ) + { + // get the message contents + pfile = COM_ParseFile( pfile, message ); + } + else if( !Q_strcmp( token, "classname" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_strcmp( token, GI->mp_entity )) + num_spawnpoints++; + } + if( num_spawnpoints ) break; // valid map + } + Mem_Free( ents ); + } + + if( f ) FS_Close( f ); + + if( num_spawnpoints ) + { + // format: mapname "maptitle"\n + Q_sprintf( result, "%s \"%s\"\n", mapname, message ); + Q_strcat( buffer, result ); // add new string + } + } + } + + if( t ) Mem_Free( t ); // free search result + size = Q_strlen( buffer ); + + if( !size ) + { + if( buffer ) Mem_Free( buffer ); + + if( onlyingamedir ) + return Cmd_CheckMapsList_R( fRefresh, false ); + return false; + } + + // write generated maps.lst + if( FS_WriteFile( "maps.lst", buffer, Q_strlen( buffer ))) + { + if( buffer ) Mem_Free( buffer ); + return true; + } + return false; +} + +qboolean Cmd_CheckMapsList( qboolean fRefresh ) +{ + return Cmd_CheckMapsList_R( fRefresh, true ); +} + +autocomplete_list_t cmd_list[] = +{ +{ "map_background", Cmd_GetMapList }, +{ "changelevel", Cmd_GetMapList }, +{ "playdemo", Cmd_GetDemoList, }, +{ "timedemo", Cmd_GetDemoList, }, +{ "playvol", Cmd_GetSoundList }, +{ "hpkval", Cmd_GetCustomList }, +{ "entpatch", Cmd_GetMapList }, +{ "music", Cmd_GetMusicList, }, +{ "movie", Cmd_GetMovieList }, +{ "exec", Cmd_GetConfigList }, +{ "give", Cmd_GetItemsList }, +{ "drop", Cmd_GetItemsList }, +{ "game", Cmd_GetGamesList }, +{ "save", Cmd_GetSavesList }, +{ "load", Cmd_GetSavesList }, +{ "play", Cmd_GetSoundList }, +{ "map", Cmd_GetMapList }, +{ "cd", Cmd_GetCDList }, +{ NULL }, // termiantor +}; + +/* +=============== +Cmd_CheckName + +compare first argument with string +=============== +*/ +static qboolean Cmd_CheckName( const char *name ) +{ + if( !Q_stricmp( Cmd_Argv( 0 ), name )) + return true; + if( !Q_stricmp( Cmd_Argv( 0 ), va( "\\%s", name ))) + return true; + return false; +} + +/* +============ +Cmd_AutocompleteName + +Autocomplete filename +for various cmds +============ +*/ +qboolean Cmd_AutocompleteName( const char *source, char *buffer, size_t bufsize ) +{ + autocomplete_list_t *list; + + for( list = cmd_list; list->name; list++ ) + { + if( Cmd_CheckName( list->name )) + return list->func( source, buffer, bufsize ); + } + + return false; +} + +/* +============ +Cmd_WriteVariables + +Appends lines containing "set variable value" for all variables +with the archive flag set to true. +============ +*/ +static void Cmd_WriteOpenGLCvar( const char *name, const char *string, const char *desc, void *f ) +{ + if( !desc || !*desc ) return; // ignore cvars without description (fantom variables) + FS_Printf( f, "setgl %s \"%s\"\n", name, string ); +} + +static void Cmd_WriteHelp(const char *name, const char *unused, const char *desc, void *f ) +{ + int length; + + if( !desc || !Q_strcmp( desc, "" )) + return; // ignore fantom cmds + if( name[0] == '+' || name[0] == '-' ) + return; // key bindings + + length = 3 - (Q_strlen( name ) / 10); // Asm_Ed default tab stop is 10 + + if( length == 3 ) FS_Printf( f, "%s\t\t\t\"%s\"\n", name, desc ); + if( length == 2 ) FS_Printf( f, "%s\t\t\"%s\"\n", name, desc ); + if( length == 1 ) FS_Printf( f, "%s\t\"%s\"\n", name, desc ); + if( length == 0 ) FS_Printf( f, "%s \"%s\"\n", name, desc ); +} + +void Cmd_WriteOpenGLVariables( file_t *f ) +{ + Cvar_LookupVars( FCVAR_GLCONFIG, NULL, f, Cmd_WriteOpenGLCvar ); +} + +/* +=============== +Host_WriteConfig + +Writes key bindings and archived cvars to config.cfg +=============== +*/ +void Host_WriteConfig( void ) +{ + kbutton_t *mlook = NULL; + kbutton_t *jlook = NULL; + file_t *f; + + if( !clgame.hInstance ) return; + + MsgDev( D_NOTE, "Host_WriteConfig()\n" ); + f = FS_Open( "config.cfg", "w", false ); + if( f ) + { + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\t\t\tCopyright XashXT Group %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\t\tconfig.cfg - archive of cvars\n" ); + FS_Printf( f, "//=======================================================================\n" ); + Key_WriteBindings( f ); + Cvar_WriteVariables( f, FCVAR_ARCHIVE ); + Info_WriteVars( f ); + + if( clgame.hInstance ) + { + mlook = (kbutton_t *)clgame.dllFuncs.KB_Find( "in_mlook" ); + jlook = (kbutton_t *)clgame.dllFuncs.KB_Find( "in_jlook" ); + } + + if( mlook && ( mlook->state & 1 )) + FS_Printf( f, "+mlook\n" ); + + if( jlook && ( jlook->state & 1 )) + FS_Printf( f, "+jlook\n" ); + + FS_Printf( f, "exec userconfig.cfg" ); + + FS_Close( f ); + } + else MsgDev( D_ERROR, "Couldn't write config.cfg.\n" ); +} + +/* +=============== +Host_WriteServerConfig + +save serverinfo variables into server.cfg (using for dedicated server too) +=============== +*/ +void Host_WriteServerConfig( const char *name ) +{ + file_t *f; + + SV_InitGameProgs(); // collect user variables + + // FIXME: move this out until menu parser is done + CSCR_LoadDefaultCVars( "settings.scr" ); + + if(( f = FS_Open( name, "w", false )) != NULL ) + { + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "//\t\t\tCopyright XashXT Group %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\tgame.cfg - multiplayer server temporare config\n" ); + FS_Printf( f, "//=======================================================================\n" ); + Cvar_WriteVariables( f, FCVAR_SERVER ); + CSCR_WriteGameCVars( f, "settings.scr" ); + FS_Close( f ); + } + else MsgDev( D_ERROR, "Couldn't write %s.\n", name ); + + SV_FreeGameProgs(); // release progs with all variables +} + +/* +=============== +Host_WriteOpenGLConfig + +save opengl variables into opengl.cfg +=============== +*/ +void Host_WriteOpenGLConfig( void ) +{ + file_t *f; + + MsgDev( D_NOTE, "Host_WriteGLConfig()\n" ); + f = FS_Open( "opengl.cfg", "w", false ); + if( f ) + { + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "//\t\t\tCopyright XashXT Group %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\t opengl.cfg - archive of opengl extension cvars\n"); + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "\n" ); + Cmd_WriteOpenGLVariables( f ); + FS_Close( f ); + } + else MsgDev( D_ERROR, "can't update opengl.cfg.\n" ); +} + +/* +=============== +Host_WriteVideoConfig + +save render variables into video.cfg +=============== +*/ +void Host_WriteVideoConfig( void ) +{ + file_t *f; + + MsgDev( D_NOTE, "Host_WriteVideoConfig()\n" ); + f = FS_Open( "video.cfg", "w", false ); + if( f ) + { + FS_Printf( f, "//=======================================================================\n" ); + FS_Printf( f, "//\t\t\tCopyright XashXT Group %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\tvideo.cfg - archive of renderer variables\n"); + FS_Printf( f, "//=======================================================================\n" ); + Cvar_WriteVariables( f, FCVAR_RENDERINFO ); + FS_Close( f ); + } + else MsgDev( D_ERROR, "can't update video.cfg.\n" ); +} + +void Key_EnumCmds_f( void ) +{ + file_t *f; + + FS_AllowDirectPaths( true ); + if( FS_FileExists( "../help.txt", false )) + { + Con_Printf( "help.txt already exist\n" ); + FS_AllowDirectPaths( false ); + return; + } + + f = FS_Open( "../help.txt", "w", false ); + if( f ) + { + FS_Printf( f, "//=======================================================================\n"); + FS_Printf( f, "//\t\t\tCopyright XashXT Group %s (C)\n", Q_timestamp( TIME_YEAR_ONLY )); + FS_Printf( f, "//\t\thelp.txt - xash commands and console variables\n"); + FS_Printf( f, "//=======================================================================\n"); + + FS_Printf( f, "\n\n\t\t\tconsole variables\n\n"); + Cvar_LookupVars( 0, NULL, f, Cmd_WriteHelp ); + FS_Printf( f, "\n\n\t\t\tconsole commands\n\n"); + Cmd_LookupCmds( NULL, f, Cmd_WriteHelp ); + FS_Printf( f, "\n\n"); + FS_Close( f ); + Con_Printf( "help.txt created\n" ); + } + else Con_Printf( S_ERROR "couldn't write help.txt.\n"); + FS_AllowDirectPaths( false ); +} \ No newline at end of file diff --git a/engine/common/console.c b/engine/common/console.c new file mode 100644 index 00000000..0383ae0d --- /dev/null +++ b/engine/common/console.c @@ -0,0 +1,2477 @@ +/* +console.c - developer console +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" +#include "keydefs.h" +#include "protocol.h" // get the protocol version +#include "con_nprint.h" +#include "gl_local.h" +#include "qfont.h" +#include "wadfile.h" + +convar_t *con_notifytime; +convar_t *scr_conspeed; +convar_t *con_fontsize; + +#define CON_TIMES 4 // notify lines +#define CON_MAX_TIMES 64 // notify max lines +#define COLOR_DEFAULT '7' +#define CON_HISTORY 64 +#define MAX_DBG_NOTIFY 128 +#define CON_MAXCMDS 4096 // auto-complete intermediate list +#define CON_NUMFONTS 3 // maxfonts + +#define CON_LINES( i ) (con.lines[(con.lines_first + (i)) % con.maxlines]) +#define CON_LINES_COUNT con.lines_count +#define CON_LINES_LAST() CON_LINES( CON_LINES_COUNT - 1 ) + +#define CON_TEXTSIZE 1048576 // max scrollback buffer characters in console (1 Mb) +#define CON_MAXLINES 16384 // max scrollback buffer lines in console + +// console color typeing +rgba_t g_color_table[8] = +{ +{ 0, 0, 0, 255 }, // black +{ 255, 0, 0, 255 }, // red +{ 0, 255, 0, 255 }, // green +{ 255, 255, 0, 255 }, // yellow +{ 0, 0, 255, 255 }, // blue +{ 0, 255, 255, 255 }, // cyan +{ 255, 0, 255, 255 }, // magenta +{ 240, 180, 24, 255 }, // default color (can be changed by user) +}; + +typedef struct +{ + string buffer; + int cursor; + int scroll; + int widthInChars; +} field_t; + +typedef struct +{ + string szNotify; + float expire; + rgba_t color; + int key_dest; +} notify_t; + +typedef struct con_lineinfo_s +{ + char *start; + size_t length; + double addtime; // notify stuff +} con_lineinfo_t; + +typedef struct +{ + qboolean initialized; + + // conbuffer + char *buffer; // common buffer for all console lines + int bufsize; // CON_TEXSIZE + con_lineinfo_t *lines; // console lines + int maxlines; // CON_MAXLINES + + int lines_first; // cyclic buffer + int lines_count; + int num_times; // overlay lines count + + // console scroll + int backscroll; // lines up from bottom to display + int linewidth; // characters across screen + + // console animation + float showlines; // how many lines we should display + float vislines; // in scanlines + + // console images + int background; // console background + + // console fonts + cl_font_t chars[CON_NUMFONTS];// fonts.wad/font1.fnt + cl_font_t *curFont, *lastUsedFont; + + // console input + field_t input; + + // chatfiled + field_t chat; + string chat_cmd; // can be overrieded by user + + // console history + field_t historyLines[CON_HISTORY]; + int historyLine; // the line being displayed from history buffer will be <= nextHistoryLine + int nextHistoryLine; // the last line in the history buffer, not masked + + notify_t notify[MAX_DBG_NOTIFY]; // for Con_NXPrintf + qboolean draw_notify; // true if we have NXPrint message + + // console auto-complete + string shortestMatch; + field_t *completionField; // con.input or dedicated server fake field-line + char *completionString; + char *completionBuffer; + char *cmds[CON_MAXCMDS]; + int matchCount; +} console_t; + +static console_t con; + +void Field_CharEvent( field_t *edit, int ch ); + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f( void ) +{ + con.lines_count = 0; + con.backscroll = 0; // go to end +} + +/* +================ +Con_SetColor_f +================ +*/ +void Con_SetColor_f( void ) +{ + vec3_t color; + + switch( Cmd_Argc() ) + { + case 1: + Con_Printf( "\"con_color\" is %i %i %i\n", g_color_table[7][0], g_color_table[7][1], g_color_table[7][2] ); + break; + case 2: + VectorSet( color, g_color_table[7][0], g_color_table[7][1], g_color_table[7][2] ); + Q_atov( color, Cmd_Argv( 1 ), 3 ); + Con_DefaultColor( color[0], color[1], color[2] ); + break; + case 4: + VectorSet( color, Q_atof( Cmd_Argv( 1 )), Q_atof( Cmd_Argv( 2 )), Q_atof( Cmd_Argv( 3 ))); + Con_DefaultColor( color[0], color[1], color[2] ); + break; + default: + Con_Printf( S_USAGE "con_color \"r g b\"\n" ); + break; + } +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) +{ + int i; + + for( i = 0; i < CON_LINES_COUNT; i++ ) + CON_LINES( i ).addtime = 0.0; +} + +/* +================ +Con_ClearField +================ +*/ +void Con_ClearField( field_t *edit ) +{ + memset( edit->buffer, 0, MAX_STRING ); + edit->cursor = 0; + edit->scroll = 0; +} + +/* +================ +Con_ClearTyping +================ +*/ +void Con_ClearTyping( void ) +{ + int i; + + Con_ClearField( &con.input ); + con.input.widthInChars = con.linewidth; + + // free the old autocomplete list + for( i = 0; i < con.matchCount; i++ ) + { + freestring( con.cmds[i] ); + } + + con.matchCount = 0; +} + +/* +============ +Con_StringLength + +skipped color prefixes +============ +*/ +int Con_StringLength( const char *string ) +{ + int len; + const char *p; + + if( !string ) return 0; + + len = 0; + p = string; + + while( *p ) + { + if( IsColorString( p )) + { + p += 2; + continue; + } + len++; + p++; + } + + return len; +} + +/* +================ +Con_MessageMode_f +================ +*/ +void Con_MessageMode_f( void ) +{ + if( Cmd_Argc() == 2 ) + Q_strncpy( con.chat_cmd, Cmd_Argv( 1 ), sizeof( con.chat_cmd )); + else Q_strncpy( con.chat_cmd, "say", sizeof( con.chat_cmd )); + + Key_SetKeyDest( key_message ); +} + +/* +================ +Con_MessageMode2_f +================ +*/ +void Con_MessageMode2_f( void ) +{ + Q_strncpy( con.chat_cmd, "say_team", sizeof( con.chat_cmd )); + Key_SetKeyDest( key_message ); +} + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f( void ) +{ + if( !host.allow_console || UI_CreditsActive( )) + return; // disabled + + // show console only in game or by special call from menu + if( cls.state != ca_active || cls.key_dest == key_menu ) + return; + + Con_ClearTyping(); + Con_ClearNotify(); + + if( cls.key_dest == key_console ) + { + if( Cvar_VariableInteger( "sv_background" ) || Cvar_VariableInteger( "cl_background" )) + UI_SetActiveMenu( true ); + else UI_SetActiveMenu( false ); + } + else + { + UI_SetActiveMenu( false ); + Key_SetKeyDest( key_console ); + } +} + +/* +================ +Con_SetTimes_f +================ +*/ +void Con_SetTimes_f( void ) +{ + int newtimes; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "contimes \n" ); + return; + } + + newtimes = Q_atoi( Cmd_Argv( 1 ) ); + con.num_times = bound( CON_TIMES, newtimes, CON_MAX_TIMES ); +} + +/* +================ +Con_FixTimes + +Notifies the console code about the current time +(and shifts back times of other entries when the time +went backwards) +================ +*/ +void Con_FixTimes( void ) +{ + double diff; + int i; + + if( con.lines_count <= 0 ) return; + + diff = cl.time - CON_LINES_LAST().addtime; + if( diff >= 0.0 ) return; // nothing to fix + + for( i = 0; i < con.lines_count; i++ ) + CON_LINES( i ).addtime += diff; +} + +/* +================ +Con_DeleteLine + +Deletes the first line from the console history. +================ +*/ +void Con_DeleteLine( void ) +{ + if( con.lines_count == 0 ) + return; + con.lines_count--; + con.lines_first = (con.lines_first + 1) % con.maxlines; +} + +/* +================ +Con_DeleteLastLine + +Deletes the last line from the console history. +================ +*/ +void Con_DeleteLastLine( void ) +{ + if( con.lines_count == 0 ) + return; + con.lines_count--; +} + +/* +================ +Con_BytesLeft + +Checks if there is space for a line of the given length, and if yes, returns a +pointer to the start of such a space, and NULL otherwise. +================ +*/ +static char *Con_BytesLeft( int length ) +{ + if( length > con.bufsize ) + return NULL; + + if( con.lines_count == 0 ) + { + return con.buffer; + } + else + { + char *firstline_start = con.lines[con.lines_first].start; + char *lastline_onepastend = CON_LINES_LAST().start + CON_LINES_LAST().length; + + // the buffer is cyclic, so we first have two cases... + if( firstline_start < lastline_onepastend ) // buffer is contiguous + { + // put at end? + if( length <= con.buffer + con.bufsize - lastline_onepastend ) + return lastline_onepastend; + // put at beginning? + else if( length <= firstline_start - con.buffer ) + return con.buffer; + + return NULL; + } + else + { + // buffer has a contiguous hole + if( length <= firstline_start - lastline_onepastend ) + return lastline_onepastend; + + return NULL; + } + } +} + +/* +================ +Con_AddLine + +Appends a given string as a new line to the console. +================ +*/ +void Con_AddLine( const char *line, int length ) +{ + byte *putpos; + con_lineinfo_t *p; + + if( !con.initialized || !con.buffer ) + return; + + Con_FixTimes(); + length++; // reserve space for term + + ASSERT( length < CON_TEXTSIZE ); + + while( !( putpos = Con_BytesLeft( length )) || con.lines_count >= con.maxlines ) + Con_DeleteLine(); + + memcpy( putpos, line, length ); + putpos[length - 1] = '\0'; + con.lines_count++; + + p = &CON_LINES_LAST(); + p->start = putpos; + p->length = length; + p->addtime = cl.time; +} + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize( void ) +{ + int charWidth = 8; + int i, width; + + if( con.curFont && con.curFont->hFontTexture ) + charWidth = con.curFont->charWidths['M'] - 1; + + width = ( glState.width / charWidth ) - 2; + if( !glw_state.initialized ) width = (640 / 5); + + if( width == con.linewidth ) + return; + + Con_ClearNotify(); + con.linewidth = width; + con.backscroll = 0; + + con.input.widthInChars = con.linewidth; + + for( i = 0; i < CON_HISTORY; i++ ) + con.historyLines[i].widthInChars = con.linewidth; +} + +/* +================ +Con_PageUp +================ +*/ +void Con_PageUp( int lines ) +{ + con.backscroll += abs( lines ); +} + +/* +================ +Con_PageDown +================ +*/ +void Con_PageDown( int lines ) +{ + con.backscroll -= abs( lines ); +} + +/* +================ +Con_Top +================ +*/ +void Con_Top( void ) +{ + con.backscroll = CON_MAXLINES; +} + +/* +================ +Con_Bottom +================ +*/ +void Con_Bottom( void ) +{ + con.backscroll = 0; +} + +/* +================ +Con_Visible +================ +*/ +qboolean Con_Visible( void ) +{ + return (con.vislines > 0); +} + +/* +================ +Con_FixedFont +================ +*/ +qboolean Con_FixedFont( void ) +{ + if( con.curFont && con.curFont->valid && con.curFont->type == FONT_FIXED ) + return true; + return false; +} + +static qboolean Con_LoadFixedWidthFont( const char *fontname, cl_font_t *font ) +{ + int i, fontWidth; + + if( font->valid ) + return true; // already loaded + + if( !FS_FileExists( fontname, false )) + return false; + + // keep source to print directly into conback image + font->hFontTexture = GL_LoadTexture( fontname, NULL, 0, TF_FONT|TF_KEEP_SOURCE, NULL ); + R_GetTextureParms( &fontWidth, NULL, font->hFontTexture ); + + if( font->hFontTexture && fontWidth != 0 ) + { + font->charHeight = fontWidth / 16; + font->type = FONT_FIXED; + + // build fixed rectangles + for( i = 0; i < 256; i++ ) + { + font->fontRc[i].left = (i * (fontWidth / 16)) % fontWidth; + font->fontRc[i].right = font->fontRc[i].left + fontWidth / 16; + font->fontRc[i].top = (i / 16) * (fontWidth / 16); + font->fontRc[i].bottom = font->fontRc[i].top + fontWidth / 16; + font->charWidths[i] = fontWidth / 16; + } + font->valid = true; + } + + return true; +} + +static qboolean Con_LoadVariableWidthFont( const char *fontname, cl_font_t *font ) +{ + int i, fontWidth; + byte *buffer; + size_t length; + qfont_t *src; + + if( font->valid ) + return true; // already loaded + + if( !FS_FileExists( fontname, false )) + return false; + + font->hFontTexture = GL_LoadTexture( fontname, NULL, 0, TF_FONT|TF_NEAREST, NULL ); + R_GetTextureParms( &fontWidth, NULL, font->hFontTexture ); + + // setup consolefont + if( font->hFontTexture && fontWidth != 0 ) + { + // half-life font with variable chars witdh + buffer = FS_LoadFile( fontname, &length, false ); + + if( buffer && length >= sizeof( qfont_t )) + { + src = (qfont_t *)buffer; + font->charHeight = src->rowheight; + font->type = FONT_VARIABLE; + + // build rectangles + for( i = 0; i < 256; i++ ) + { + font->fontRc[i].left = (word)src->fontinfo[i].startoffset % fontWidth; + font->fontRc[i].right = font->fontRc[i].left + src->fontinfo[i].charwidth; + font->fontRc[i].top = (word)src->fontinfo[i].startoffset / fontWidth; + font->fontRc[i].bottom = font->fontRc[i].top + src->rowheight; + font->charWidths[i] = src->fontinfo[i].charwidth; + } + font->valid = true; + } + if( buffer ) Mem_Free( buffer ); + } + + return true; +} + +/* +================ +Con_LoadConsoleFont + +INTERNAL RESOURCE +================ +*/ +static void Con_LoadConsoleFont( int fontNumber, cl_font_t *font ) +{ + if( font->valid ) return; // already loaded + + // loading conchars + if( Sys_CheckParm( "-oldfont" )) + Con_LoadVariableWidthFont( "gfx.wad/conchars.fnt", font ); + else Con_LoadVariableWidthFont( va( "fonts.wad/font%i", fontNumber ), font ); + + // quake fixed font as fallback + if( !font->valid ) Con_LoadFixedWidthFont( "gfx/conchars", font ); +} + +/* +================ +Con_LoadConchars +================ +*/ +static void Con_LoadConchars( void ) +{ + int i, fontSize; + + // load all the console fonts + for( i = 0; i < 3; i++ ) + Con_LoadConsoleFont( i, con.chars + i ); + + // select properly fontsize + if( glState.width <= 640 ) + fontSize = 0; + else if( glState.width >= 1280 ) + fontSize = 2; + else fontSize = 1; + + // sets the current font + con.lastUsedFont = con.curFont = &con.chars[fontSize]; + +} + +static void Con_DrawCharToConback( int num, byte *conchars, byte *dest ) +{ + int row, col; + byte *source; + int drawline; + int x; + + row = num >> 4; + col = num & 15; + source = conchars + (row << 10) + (col << 3); + + drawline = 8; + + while( drawline-- ) + { + for( x = 0; x < 8; x++ ) + if( source[x] != 255 ) + dest[x] = 0x60 + source[x]; + source += 128; + dest += 320; + } + +} + +/* +==================== +Con_TextAdjustSize + +draw charcters routine +==================== +*/ +static void Con_TextAdjustSize( int *x, int *y, int *w, int *h ) +{ + float xscale, yscale; + + if( !x && !y && !w && !h ) return; + + // scale for screen sizes + xscale = (float)glState.width / (float)clgame.scrInfo.iWidth; + yscale = (float)glState.height / (float)clgame.scrInfo.iHeight; + + if( x ) *x *= xscale; + if( y ) *y *= yscale; + if( w ) *w *= xscale; + if( h ) *h *= yscale; +} + +/* +==================== +Con_DrawGenericChar + +draw console single character +==================== +*/ +static int Con_DrawGenericChar( int x, int y, int number, rgba_t color ) +{ + int width, height; + float s1, t1, s2, t2; + wrect_t *rc; + + number &= 255; + + if( !con.curFont || !con.curFont->valid ) + return 0; + +// if( number < 32 ) return 0; + if( y < -con.curFont->charHeight ) + return 0; + + rc = &con.curFont->fontRc[number]; + + pglColor4ubv( color ); + R_GetTextureParms( &width, &height, con.curFont->hFontTexture ); + + // calc rectangle + s1 = (float)rc->left / width; + t1 = (float)rc->top / height; + s2 = (float)rc->right / width; + t2 = (float)rc->bottom / height; + width = rc->right - rc->left; + height = rc->bottom - rc->top; + + if( clgame.ds.adjust_size ) + Con_TextAdjustSize( &x, &y, &width, &height ); + R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, con.curFont->hFontTexture ); + pglColor4ub( 255, 255, 255, 255 ); // don't forget reset color + + return con.curFont->charWidths[number]; +} + +/* +==================== +Con_SetFont + +choose font size +==================== +*/ +void Con_SetFont( int fontNum ) +{ + fontNum = bound( 0, fontNum, 2 ); + con.curFont = &con.chars[fontNum]; +} + +/* +==================== +Con_RestoreFont + +restore auto-selected console font +(that based on screen resolution) +==================== +*/ +void Con_RestoreFont( void ) +{ + con.curFont = con.lastUsedFont; +} + +/* +==================== +Con_DrawCharacter + +client version of routine +==================== +*/ +int Con_DrawCharacter( int x, int y, int number, rgba_t color ) +{ + GL_SetRenderMode( kRenderTransTexture ); + return Con_DrawGenericChar( x, y, number, color ); +} + +/* +==================== +Con_DrawCharacterLen + +returns character sizes in screen pixels +==================== +*/ +void Con_DrawCharacterLen( int number, int *width, int *height ) +{ + if( width && con.curFont ) *width = con.curFont->charWidths[number]; + if( height && con.curFont ) *height = con.curFont->charHeight; +} + +/* +==================== +Con_DrawStringLen + +compute string width and height in screen pixels +==================== +*/ +void Con_DrawStringLen( const char *pText, int *length, int *height ) +{ + int curLength = 0; + + if( !con.curFont ) return; + + if( height ) *height = con.curFont->charHeight; + if( !length ) return; + + *length = 0; + + while( *pText ) + { + byte c = *pText; + + if( *pText == '\n' ) + { + pText++; + curLength = 0; + } + + // skip color strings they are not drawing + if( IsColorString( pText )) + { + pText += 2; + continue; + } + + curLength += con.curFont->charWidths[c]; + pText++; + + if( curLength > *length ) + *length = curLength; + } +} + +/* +================== +Con_DrawString + +Draws a multi-colored string, optionally forcing +to a fixed color. +================== +*/ +int Con_DrawGenericString( int x, int y, const char *string, rgba_t setColor, qboolean forceColor, int hideChar ) +{ + rgba_t color; + int drawLen = 0; + int numDraws = 0; + const char *s; + + if( !con.curFont ) return 0; // no font set + + // draw the colored text + *(uint *)color = *(uint *)setColor; + s = string; + + while( *s ) + { + if( *s == '\n' ) + { + s++; + if( !*s ) break; // at end the string + drawLen = 0; // begin new row + y += con.curFont->charHeight; + } + + if( IsColorString( s )) + { + if( !forceColor ) + { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color )); + color[3] = setColor[3]; + } + + s += 2; + numDraws++; + continue; + } + + // hide char for overstrike mode + if( hideChar == numDraws ) + drawLen += con.curFont->charWidths[*s]; + else drawLen += Con_DrawCharacter( x + drawLen, y, *s, color ); + + numDraws++; + s++; + } + + pglColor4ub( 255, 255, 255, 255 ); + return drawLen; +} + +/* +==================== +Con_DrawString + +client version of routine +==================== +*/ +int Con_DrawString( int x, int y, const char *string, rgba_t setColor ) +{ + return Con_DrawGenericString( x, y, string, setColor, false, -1 ); +} + +/* +================ +Con_Init +================ +*/ +void Con_Init( void ) +{ + int i; + + if( host.type == HOST_DEDICATED ) + return; // dedicated server already have console + + // must be init before startup video subsystem + scr_conspeed = Cvar_Get( "scr_conspeed", "600", FCVAR_ARCHIVE, "console moving speed" ); + con_notifytime = Cvar_Get( "con_notifytime", "3", FCVAR_ARCHIVE, "notify time to live" ); + con_fontsize = Cvar_Get( "con_fontsize", "1", FCVAR_ARCHIVE, "console font number (0, 1 or 2)" ); + + // init the console buffer + con.bufsize = CON_TEXTSIZE; + con.buffer = (char *)Z_Malloc( con.bufsize ); + con.maxlines = CON_MAXLINES; + con.lines = (con_lineinfo_t *)Z_Malloc( con.maxlines * sizeof( *con.lines )); + con.lines_first = con.lines_count = 0; + con.num_times = CON_TIMES; // default as 4 + + Con_CheckResize(); + + Con_ClearField( &con.input ); + con.input.widthInChars = con.linewidth; + + Con_ClearField( &con.chat ); + con.chat.widthInChars = con.linewidth; + + for( i = 0; i < CON_HISTORY; i++ ) + { + Con_ClearField( &con.historyLines[i] ); + con.historyLines[i].widthInChars = con.linewidth; + } + + Cmd_AddCommand( "toggleconsole", Con_ToggleConsole_f, "opens or closes the console" ); + Cmd_AddCommand( "con_color", Con_SetColor_f, "set a custom console color" ); + Cmd_AddCommand( "clear", Con_Clear_f, "clear console history" ); + Cmd_AddCommand( "messagemode", Con_MessageMode_f, "enable message mode \"say\"" ); + Cmd_AddCommand( "messagemode2", Con_MessageMode2_f, "enable message mode \"say_team\"" ); + Cmd_AddCommand( "contimes", Con_SetTimes_f, "change number of console overlay lines (4-64)" ); + con.initialized = true; + + MsgDev( D_INFO, "Console initialized.\n" ); +} + +/* +================ +Con_Shutdown +================ +*/ +void Con_Shutdown( void ) +{ + con.initialized = false; + + if( con.buffer ) + Mem_Free( con.buffer ); + + if( con.lines ) + Mem_Free( con.lines ); + + con.buffer = NULL; + con.lines = NULL; +} + +/* +================ +Con_Print + +Handles cursor positioning, line wrapping, etc +All console printing must go through this in order to be displayed +If no console is visible, the notify window will pop up. +================ +*/ +void Con_Print( const char *txt ) +{ + static int cr_pending = 0; + static char buf[MAX_PRINT_MSG]; + static qboolean inupdate; + static int bufpos = 0; + int c, mask = 0; + + // client not running + if( !con.initialized || !con.buffer ) + return; + + if( txt[0] == 2 ) + { + // go to colored text + if( Con_FixedFont( )) + mask = 128; + txt++; + } + + for( ; *txt; txt++ ) + { + if( cr_pending ) + { + Con_DeleteLastLine(); + cr_pending = 0; + } + + c = *txt; + + switch( c ) + { + case '\0': + break; + case '\r': + Con_AddLine( buf, bufpos ); + cr_pending = 1; + bufpos = 0; + break; + case '\n': + Con_AddLine( buf, bufpos ); + bufpos = 0; + break; + default: + buf[bufpos++] = c | mask; + if(( bufpos >= sizeof( buf ) - 1 ) || bufpos >= ( con.linewidth - 1 )) + { + Con_AddLine( buf, bufpos ); + bufpos = 0; + } + break; + } + } + + if( cls.state != ca_disconnected && cls.state < ca_active && !cl.video_prepped && !cls.disable_screen ) + { + if( !inupdate ) + { + inupdate = true; + SCR_UpdateScreen (); + inupdate = false; + } + } +} + +/* +============= +Con_Printf + +============= +*/ +void Con_Printf( char *szFmt, ... ) +{ + static char buffer[MAX_PRINT_MSG]; + va_list args; + + if( !host.allow_console ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + + Sys_Print( buffer ); +} + +/* +============= +Con_DPrintf + +============= +*/ +void Con_DPrintf( char *szFmt, ... ) +{ + static char buffer[MAX_PRINT_MSG]; + va_list args; + + if( host_developer.value < DEV_NORMAL ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + + if( buffer[0] == '0' && buffer[1] == '\n' && buffer[2] == '\0' ) + return; // hlrally spam + + Sys_Print( buffer ); +} + +/* +============= +Con_Reportf + +============= +*/ +void Con_Reportf( char *szFmt, ... ) +{ + static char buffer[MAX_PRINT_MSG]; + va_list args; + + if( host_developer.value < DEV_EXTENDED ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + + Sys_Print( buffer ); +} + +/* +================ +Con_NPrint + +Draw a single debug line with specified height +================ +*/ +void Con_NPrintf( int idx, char *fmt, ... ) +{ + va_list args; + + if( idx < 0 || idx >= MAX_DBG_NOTIFY ) + return; + + memset( con.notify[idx].szNotify, 0, MAX_STRING ); + + va_start( args, fmt ); + Q_vsnprintf( con.notify[idx].szNotify, MAX_STRING, fmt, args ); + va_end( args ); + + // reset values + con.notify[idx].key_dest = key_game; + con.notify[idx].expire = host.realtime + 4.0f; + MakeRGBA( con.notify[idx].color, 255, 255, 255, 255 ); + con.draw_notify = true; +} + +/* +================ +Con_NXPrint + +Draw a single debug line with specified height, color and time to live +================ +*/ +void Con_NXPrintf( con_nprint_t *info, char *fmt, ... ) +{ + va_list args; + + if( !info ) return; + + if( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) + return; + + memset( con.notify[info->index].szNotify, 0, MAX_STRING ); + + va_start( args, fmt ); + Q_vsnprintf( con.notify[info->index].szNotify, MAX_STRING, fmt, args ); + va_end( args ); + + // setup values + con.notify[info->index].key_dest = key_game; + con.notify[info->index].expire = host.realtime + info->time_to_live; + MakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 ); + con.draw_notify = true; +} + +/* +================ +UI_NPrint + +Draw a single debug line with specified height (menu version) +================ +*/ +void UI_NPrintf( int idx, char *fmt, ... ) +{ + va_list args; + + if( idx < 0 || idx >= MAX_DBG_NOTIFY ) + return; + + memset( con.notify[idx].szNotify, 0, MAX_STRING ); + + va_start( args, fmt ); + Q_vsnprintf( con.notify[idx].szNotify, MAX_STRING, fmt, args ); + va_end( args ); + + // reset values + con.notify[idx].key_dest = key_menu; + con.notify[idx].expire = host.realtime + 4.0f; + MakeRGBA( con.notify[idx].color, 255, 255, 255, 255 ); + con.draw_notify = true; +} + +/* +================ +UI_NXPrint + +Draw a single debug line with specified height, color and time to live (menu version) +================ +*/ +void UI_NXPrintf( con_nprint_t *info, char *fmt, ... ) +{ + va_list args; + + if( !info ) return; + + if( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) + return; + + memset( con.notify[info->index].szNotify, 0, MAX_STRING ); + + va_start( args, fmt ); + Q_vsnprintf( con.notify[info->index].szNotify, MAX_STRING, fmt, args ); + va_end( args ); + + // setup values + con.notify[info->index].key_dest = key_menu; + con.notify[info->index].expire = host.realtime + info->time_to_live; + MakeRGBA( con.notify[info->index].color, (byte)(info->color[0] * 255), (byte)(info->color[1] * 255), (byte)(info->color[2] * 255), 255 ); + con.draw_notify = true; +} + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ +/* +=============== +Con_AddCommandToList + +=============== +*/ +static void Con_AddCommandToList( const char *s, const char *unused1, const char *unused2, void *unused3 ) +{ + if( *s == '@' ) return; // never show system cvars or cmds + if( con.matchCount >= CON_MAXCMDS ) return; // list is full + + if( Q_strnicmp( s, con.completionString, Q_strlen( con.completionString ))) + return; // no match + + con.cmds[con.matchCount++] = copystring( s ); +} + +/* +================= +Con_SortCmds +================= +*/ +static int Con_SortCmds( const char **arg1, const char **arg2 ) +{ + return Q_stricmp( *arg1, *arg2 ); +} + +/* +=============== +Con_PrintCmdMatches +=============== +*/ +static void Con_PrintCmdMatches( const char *s, const char *unused1, const char *m, void *unused2 ) +{ + if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ))) + { + if( COM_CheckString( m )) Con_Printf( " %s ^3\"%s\"\n", s, m ); + else Con_Printf( " %s\n", s ); // variable or command without description + } +} + +/* +=============== +Con_PrintCvarMatches +=============== +*/ +static void Con_PrintCvarMatches( const char *s, const char *value, const char *m, void *unused2 ) +{ + if( !Q_strnicmp( s, con.shortestMatch, Q_strlen( con.shortestMatch ))) + { + if( COM_CheckString( m )) Con_Printf( " %s (%s) ^3\"%s\"\n", s, value, m ); + else Con_Printf( " %s (%s)\n", s, value ); // variable or command without description + } +} + +/* +=============== +Con_ConcatRemaining +=============== +*/ +static void Con_ConcatRemaining( const char *src, const char *start ) +{ + char *arg; + int i; + + arg = Q_strstr( src, start ); + + if( !arg ) + { + for( i = 1; i < Cmd_Argc(); i++ ) + { + Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer )); + arg = Cmd_Argv( i ); + while( *arg ) + { + if( *arg == ' ' ) + { + Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer )); + break; + } + arg++; + } + + Q_strncat( con.completionField->buffer, Cmd_Argv( i ), sizeof( con.completionField->buffer )); + if( *arg == ' ' ) Q_strncat( con.completionField->buffer, "\"", sizeof( con.completionField->buffer )); + } + return; + } + + arg += Q_strlen( start ); + Q_strncat( con.completionField->buffer, arg, sizeof( con.completionField->buffer )); +} + +/* +=============== +Con_CompleteCommand + +perform Tab expansion +=============== +*/ +void Con_CompleteCommand( field_t *field ) +{ + field_t temp; + string filename; + qboolean nextcmd; + int i; + + // setup the completion field + con.completionField = field; + + // only look at the first token for completion purposes + Cmd_TokenizeString( con.completionField->buffer ); + + nextcmd = ( con.completionField->buffer[Q_strlen( con.completionField->buffer ) - 1] == ' ' ) ? true : false; + + con.completionString = Cmd_Argv( 0 ); + con.completionBuffer = Cmd_Argv( 1 ); + + // skip backslash + while( *con.completionString && ( *con.completionString == '\\' || *con.completionString == '/' )) + con.completionString++; + + // skip backslash + while( *con.completionBuffer && ( *con.completionBuffer == '\\' || *con.completionBuffer == '/' )) + con.completionBuffer++; + + if( !Q_strlen( con.completionString )) + return; + + // free the old autocomplete list + for( i = 0; i < con.matchCount; i++ ) + { + if( con.cmds[i] != NULL ) + { + Mem_Free( con.cmds[i] ); + con.cmds[i] = NULL; + } + } + + con.matchCount = 0; + con.shortestMatch[0] = 0; + + // find matching commands and variables + Cmd_LookupCmds( NULL, NULL, Con_AddCommandToList ); + Cvar_LookupVars( 0, NULL, NULL, Con_AddCommandToList ); + + if( !con.matchCount ) return; // no matches + + memcpy( &temp, con.completionField, sizeof( field_t )); + + // autocomplete second arg + if(( Cmd_Argc() == 2 ) || (( Cmd_Argc() == 1 ) && nextcmd )) + { + if( !Q_strlen( con.completionBuffer )) + return; + + if( Cmd_AutocompleteName( con.completionBuffer, filename, sizeof( filename ))) + { + Q_sprintf( con.completionField->buffer, "%s %s", Cmd_Argv( 0 ), filename ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + } + + // don't adjusting cursor pos if we nothing found + return; + } + else if( Cmd_Argc() >= 3 ) + { + // disable autocomplete for all next args + return; + } + + if( con.matchCount == 1 ) + { + Q_sprintf( con.completionField->buffer, "\\%s", con.cmds[0] ); + if( Cmd_Argc() == 1 ) Q_strncat( con.completionField->buffer, " ", sizeof( con.completionField->buffer )); + else Con_ConcatRemaining( temp.buffer, con.completionString ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + } + else + { + char *first, *last; + int len = 0; + + qsort( con.cmds, con.matchCount, sizeof( char* ), Con_SortCmds ); + + // find the number of matching characters between the first and + // the last element in the list and copy it + first = con.cmds[0]; + last = con.cmds[con.matchCount-1]; + + while( *first && *last && Q_tolower( *first ) == Q_tolower( *last )) + { + first++; + last++; + + con.shortestMatch[len] = con.cmds[0][len]; + len++; + } + con.shortestMatch[len] = 0; + + // multiple matches, complete to shortest + Q_sprintf( con.completionField->buffer, "\\%s", con.shortestMatch ); + con.completionField->cursor = Q_strlen( con.completionField->buffer ); + Con_ConcatRemaining( temp.buffer, con.completionString ); + + Con_Printf( "]%s\n", con.completionField->buffer ); + + // run through again, printing matches + Cmd_LookupCmds( NULL, NULL, Con_PrintCmdMatches ); + Cvar_LookupVars( 0, NULL, NULL, Con_PrintCvarMatches ); + } +} + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) +{ + char *cbd; + int i, pasteLen; + + cbd = Sys_GetClipboardData(); + if( !cbd ) return; + + // send as if typed, so insert / overstrike works properly + pasteLen = Q_strlen( cbd ); + for( i = 0; i < pasteLen; i++ ) + Field_CharEvent( edit, cbd[i] ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) +{ + int len; + + // shift-insert is paste + if((( key == K_INS ) || ( key == K_KP_INS )) && Key_IsDown( K_SHIFT )) + { + Field_Paste( edit ); + return; + } + + len = Q_strlen( edit->buffer ); + + if( key == K_DEL ) + { + if( edit->cursor < len ) + memmove( edit->buffer + edit->cursor, edit->buffer + edit->cursor + 1, len - edit->cursor ); + return; + } + + if( key == K_BACKSPACE ) + { + if( edit->cursor > 0 ) + { + memmove( edit->buffer + edit->cursor - 1, edit->buffer + edit->cursor, len - edit->cursor + 1 ); + edit->cursor--; + if( edit->scroll ) edit->scroll--; + } + return; + } + + if( key == K_RIGHTARROW ) + { + if( edit->cursor < len ) edit->cursor++; + if( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) + edit->scroll++; + return; + } + + if( key == K_LEFTARROW ) + { + if( edit->cursor > 0 ) edit->cursor--; + if( edit->cursor < edit->scroll ) edit->scroll--; + return; + } + + if( key == K_HOME || ( Q_tolower(key) == 'a' && Key_IsDown( K_CTRL ))) + { + edit->cursor = 0; + return; + } + + if( key == K_END || ( Q_tolower(key) == 'e' && Key_IsDown( K_CTRL ))) + { + edit->cursor = len; + return; + } + + if( key == K_INS ) + { + host.key_overstrike = !host.key_overstrike; + return; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) +{ + int len; + + if( ch == 'v' - 'a' + 1 ) + { + // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if( ch == 'c' - 'a' + 1 ) + { + // ctrl-c clears the field + Con_ClearField( edit ); + return; + } + + len = Q_strlen( edit->buffer ); + + if( ch == 'a' - 'a' + 1 ) + { + // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if( ch == 'e' - 'a' + 1 ) + { + // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // ignore any other non printable chars + if( ch < 32 ) return; + + if( host.key_overstrike ) + { + if ( edit->cursor == MAX_STRING - 1 ) return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + else + { + // insert mode + if ( len == MAX_STRING - 1 ) return; // all full + memmove( edit->buffer + edit->cursor + 1, edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + if( edit->cursor >= edit->widthInChars ) edit->scroll++; + if( edit->cursor == len + 1 ) edit->buffer[edit->cursor] = 0; +} + +/* +================== +Field_DrawInputLine +================== +*/ +void Field_DrawInputLine( int x, int y, field_t *edit ) +{ + int len, cursorChar; + int drawLen, hideChar = -1; + int prestep, curPos; + char str[MAX_SYSPATH]; + byte *colorDefault; + + drawLen = edit->widthInChars; + len = Q_strlen( edit->buffer ) + 1; + colorDefault = g_color_table[ColorIndex( COLOR_DEFAULT )]; + + // guarantee that cursor will be visible + if( len <= drawLen ) + { + prestep = 0; + } + else + { + if( edit->scroll + drawLen > len ) + { + edit->scroll = len - drawLen; + if( edit->scroll < 0 ) edit->scroll = 0; + } + + prestep = edit->scroll; + } + + if( prestep + drawLen > len ) + drawLen = len - prestep; + + // extract characters from the field at + drawLen = Q_min( drawLen, MAX_SYSPATH - 1 ); + + memcpy( str, edit->buffer + prestep, drawLen ); + str[drawLen] = 0; + + // save char for overstrike + cursorChar = str[edit->cursor - prestep]; + + if( host.key_overstrike && cursorChar && !((int)( host.realtime * 4 ) & 1 )) + hideChar = edit->cursor - prestep; // skip this char + + // draw it + Con_DrawGenericString( x, y, str, colorDefault, false, hideChar ); + + // draw the cursor + if((int)( host.realtime * 4 ) & 1 ) return; // off blink + + // calc cursor position + str[edit->cursor - prestep] = 0; + Con_DrawStringLen( str, &curPos, NULL ); + + if( host.key_overstrike && cursorChar ) + { + // overstrike cursor + pglEnable( GL_BLEND ); + pglDisable( GL_ALPHA_TEST ); + pglBlendFunc( GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + Con_DrawGenericChar( x + curPos, y, cursorChar, colorDefault ); + } + else Con_DrawCharacter( x + curPos, y, '_', colorDefault ); +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ +/* +==================== +Key_Console + +Handles history and console scrollback +==================== +*/ +void Key_Console( int key ) +{ + // ctrl-L clears screen + if( key == 'l' && Key_IsDown( K_CTRL )) + { + Cbuf_AddText( "clear\n" ); + return; + } + + // enter finishes the line + if( key == K_ENTER || key == K_KP_ENTER ) + { + // if not in the game explicitly prepent a slash if needed + if( cls.state != ca_active && con.input.buffer[0] != '\\' && con.input.buffer[0] != '/' ) + { + char temp[MAX_SYSPATH]; + + Q_strncpy( temp, con.input.buffer, sizeof( temp )); + Q_sprintf( con.input.buffer, "\\%s", temp ); + con.input.cursor++; + } + + // backslash text are commands, else chat + if( con.input.buffer[0] == '\\' || con.input.buffer[0] == '/' ) + Cbuf_AddText( con.input.buffer + 1 ); // skip backslash + else Cbuf_AddText( con.input.buffer ); // valid command + Cbuf_AddText( "\n" ); + + // echo to console + Con_Printf( ">%s\n", con.input.buffer ); + + // copy line to history buffer + con.historyLines[con.nextHistoryLine % CON_HISTORY] = con.input; + con.nextHistoryLine++; + con.historyLine = con.nextHistoryLine; + + Con_ClearField( &con.input ); + con.input.widthInChars = con.linewidth; + + if( cls.state == ca_disconnected ) + { + // force an update, because the command may take some time + SCR_UpdateScreen (); + } + return; + } + + // command completion + if( key == K_TAB ) + { + Con_CompleteCommand( &con.input ); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + if(( key == K_MWHEELUP && Key_IsDown( K_SHIFT )) || ( key == K_UPARROW ) || (( Q_tolower(key) == 'p' ) && Key_IsDown( K_CTRL ))) + { + if( con.nextHistoryLine - con.historyLine < CON_HISTORY && con.historyLine > 0 ) + con.historyLine--; + con.input = con.historyLines[con.historyLine % CON_HISTORY]; + return; + } + + if(( key == K_MWHEELDOWN && Key_IsDown( K_SHIFT )) || ( key == K_DOWNARROW ) || (( Q_tolower(key) == 'n' ) && Key_IsDown( K_CTRL ))) + { + if( con.historyLine == con.nextHistoryLine ) return; + con.historyLine++; + con.input = con.historyLines[con.historyLine % CON_HISTORY]; + return; + } + + // console scrolling + if( key == K_PGUP ) + { + Con_PageUp( 1 ); + return; + } + + if( key == K_PGDN ) + { + Con_PageDown( 1 ); + return; + } + + if( key == K_MWHEELUP ) + { + if( Key_IsDown( K_CTRL )) + Con_PageUp( 8 ); + else Con_PageUp( 2 ); + return; + } + + if( key == K_MWHEELDOWN ) + { + if( Key_IsDown( K_CTRL )) + Con_PageDown( 8 ); + else Con_PageDown( 2 ); + return; + } + + // ctrl-home = top of console + if( key == K_HOME && Key_IsDown( K_CTRL )) + { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if( key == K_END && Key_IsDown( K_CTRL )) + { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &con.input, key ); +} + +/* +================ +Key_Message + +In game talk message +================ +*/ +void Key_Message( int key ) +{ + char buffer[MAX_SYSPATH]; + + if( key == K_ESCAPE ) + { + Key_SetKeyDest( key_game ); + Con_ClearField( &con.chat ); + return; + } + + if( key == K_ENTER || key == K_KP_ENTER ) + { + if( con.chat.buffer[0] && cls.state == ca_active ) + { + Q_snprintf( buffer, sizeof( buffer ), "%s \"%s\"\n", con.chat_cmd, con.chat.buffer ); + Cbuf_AddText( buffer ); + } + + Key_SetKeyDest( key_game ); + Con_ClearField( &con.chat ); + return; + } + + Field_KeyDownEvent( &con.chat, key ); +} + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ +/* +================ +Con_DrawInput + +The input line scrolls horizontally if typing goes beyond the right edge +================ +*/ +void Con_DrawInput( int lines ) +{ + int y; + + // don't draw anything (always draw if not active) + if( cls.key_dest != key_console || !con.curFont ) + return; + + y = lines - ( con.curFont->charHeight * 2 ); + Con_DrawCharacter( 8, y, ']', g_color_table[7] ); + Field_DrawInputLine( 16, y, &con.input ); +} + +/* +================ +Con_DrawDebugLines + +Custom debug messages +================ +*/ +int Con_DrawDebugLines( void ) +{ + int i, count = 0; + int defaultX; + int y = 20; + + defaultX = glState.width / 4; + + for( i = 0; i < MAX_DBG_NOTIFY; i++ ) + { + if( host.realtime < con.notify[i].expire && con.notify[i].key_dest == cls.key_dest ) + { + int x, len; + int fontTall; + + Con_DrawStringLen( con.notify[i].szNotify, &len, &fontTall ); + x = glState.width - Q_max( defaultX, len ) - 10; + fontTall += 1; + + if( y + fontTall > glState.height - 20 ) + return count; + + count++; + y = 20 + fontTall * i; + Con_DrawString( x, y, con.notify[i].szNotify, con.notify[i].color ); + } + } + + return count; +} + +/* +================ +Con_DrawDebug + +Draws the debug messages (not passed to console history) +================ +*/ +void Con_DrawDebug( void ) +{ + static double timeStart; + string dlstring; + int x, y; + + if( scr_download->value != -1.0f ) + { + Q_snprintf( dlstring, sizeof( dlstring ), "Downloading [%d remaining]: ^2%s^7 %5.1f%%", + host.downloadcount, host.downloadfile, scr_download->value, Sys_DoubleTime() - timeStart ); + x = glState.width - 400; + y = con.curFont->charHeight * 1.05f; + Con_DrawString( x, y, dlstring, g_color_table[7] ); + } + else + { + timeStart = Sys_DoubleTime(); + } + + if( !host_developer.value || Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) + return; + + if( con.draw_notify && !Con_Visible( )) + { + if( Con_DrawDebugLines() == 0 ) + con.draw_notify = false; + } +} + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify( void ) +{ + double time = cl.time; + int i, x, y = 0; + + if( !con.curFont ) return; + + x = con.curFont->charWidths[' ']; // offset one space at left screen side + + if( host_developer.value && ( !Cvar_VariableInteger( "cl_background" ) && !Cvar_VariableInteger( "sv_background" ))) + { + for( i = CON_LINES_COUNT - con.num_times; i < CON_LINES_COUNT; i++ ) + { + con_lineinfo_t *l = &CON_LINES( i ); + + if( l->addtime < ( time - con_notifytime->value )) + continue; + + Con_DrawString( x, y, l->start, g_color_table[7] ); + y += con.curFont->charHeight; + } + } + + if( cls.key_dest == key_message ) + { + string buf; + int len; + + // update chatline position from client.dll + if( clgame.dllFuncs.pfnChatInputPosition ) + clgame.dllFuncs.pfnChatInputPosition( &x, &y ); + + Q_snprintf( buf, sizeof( buf ), "%s: ", con.chat_cmd ); + + Con_DrawStringLen( buf, &len, NULL ); + Con_DrawString( x, y, buf, g_color_table[7] ); + + Field_DrawInputLine( x + len, y, &con.chat ); + } + + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +================ +Con_DrawConsoleLine + +Draws a line of the console; returns its height in lines. +If alpha is 0, the line is not drawn, but still wrapped and its height +returned. +================ +*/ +int Con_DrawConsoleLine( int y, int lineno ) +{ + con_lineinfo_t *li = &CON_LINES( lineno ); + + if( *li->start == '\1' ) + return 0; // this string will be shown only at notify + + if( y >= con.curFont->charHeight ) + Con_DrawGenericString( con.curFont->charWidths[' '], y, li->start, g_color_table[7], false, -1 ); + + return con.curFont->charHeight; +} + +/* +================ +Con_LastVisibleLine + +Calculates the last visible line index and how much to show +of it based on con.backscroll. +================ +*/ +static void Con_LastVisibleLine( int *lastline ) +{ + int i, lines_seen = 0; + + con.backscroll = Q_max( 0, con.backscroll ); + *lastline = 0; + + // now count until we saw con_backscroll actual lines + for( i = CON_LINES_COUNT - 1; i >= 0; i-- ) + { + // line is the last visible line? + *lastline = i; + + if( lines_seen + 1 > con.backscroll && lines_seen <= con.backscroll ) + return; + + lines_seen += 1; + } + + // if we get here, no line was on screen - scroll so that one line is visible then. + con.backscroll = lines_seen - 1; +} + +/* +================ +Con_DrawConsole + +Draws the console with the solid background +================ +*/ +void Con_DrawSolidConsole( int lines ) +{ + int i, x, y; + float fraction; + int start; + + if( lines <= 0 ) return; + + // draw the background + GL_SetRenderMode( kRenderNormal ); + pglColor4ub( 255, 255, 255, 255 ); // to prevent grab color from screenfade + R_DrawStretchPic( 0, lines - glState.height, glState.width, glState.height, 0, 0, 1, 1, con.background ); + + if( !con.curFont || !host.allow_console ) + return; // nothing to draw + + if( host.allow_console ) + { + // draw current version + int stringLen, width = 0, charH; + string curbuild; + byte color[4]; + + memcpy( color, g_color_table[7], sizeof( color )); + + Q_snprintf( curbuild, MAX_STRING, "Xash3D %i/%g (hw build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildnum( )); + Con_DrawStringLen( curbuild, &stringLen, &charH ); + start = glState.width - stringLen; + stringLen = Con_StringLength( curbuild ); + + fraction = lines / (float)glState.height; + color[3] = Q_min( fraction * 2.0f, 1.0f ) * 255; // fadeout version number + + for( i = 0; i < stringLen; i++ ) + width += Con_DrawCharacter( start + width, 0, curbuild[i], color ); + } + + // draw the text + if( CON_LINES_COUNT > 0 ) + { + int ymax = lines - (con.curFont->charHeight * 2.0f); + int lastline; + + Con_LastVisibleLine( &lastline ); + y = ymax - con.curFont->charHeight; + + if( con.backscroll ) + { + start = con.curFont->charWidths[' ']; // offset one space at left screen side + + // draw red arrows to show the buffer is backscrolled + for( x = 0; x < con.linewidth; x += 4 ) + Con_DrawCharacter(( x + 1 ) * start, y, '^', g_color_table[1] ); + y -= con.curFont->charHeight; + } + x = lastline; + + while( 1 ) + { + y -= Con_DrawConsoleLine( y, x ); + + // top of console buffer or console window + if( x == 0 || y < con.curFont->charHeight ) + break; + x--; + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput( lines ); + + y = lines - ( con.curFont->charHeight * 1.2f ); + SCR_DrawFPS( max( y, 4 )); // to avoid to hide fps counter + + pglColor4ub( 255, 255, 255, 255 ); +} + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) +{ + // never draw console when changelevel in-progress + if( cls.state != ca_disconnected && ( cls.changelevel || cls.changedemo )) + return; + + // check for console width changes from a vid mode change + Con_CheckResize (); + + if( cls.state == ca_connecting || cls.state == ca_connected ) + { + if( !cl_allow_levelshots->value ) + { + if(( Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) && cls.key_dest != key_console ) + con.vislines = con.showlines = 0; + else con.vislines = con.showlines = glState.height; + } + else + { + con.showlines = 0; + + if( host_developer.value >= DEV_EXTENDED ) + Con_DrawNotify(); // draw notify lines + } + } + + // if disconnected, render console full screen + switch( cls.state ) + { + case ca_disconnected: + if( cls.key_dest != key_menu ) + { + Con_DrawSolidConsole( glState.height ); + Key_SetKeyDest( key_console ); + } + break; + case ca_connecting: + case ca_connected: + case ca_validate: + // force to show console always for -dev 3 and higher + Con_DrawSolidConsole( con.vislines ); + break; + case ca_active: + case ca_cinematic: + if( Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) + { + if( cls.key_dest == key_console ) + Con_DrawSolidConsole( glState.height ); + } + else + { + if( con.vislines ) + Con_DrawSolidConsole( con.vislines ); + else if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message )) + Con_DrawNotify(); // draw notify lines + } + break; + } + + if( !Con_Visible( )) SCR_DrawFPS( 4 ); +} + +/* +================== +Con_DrawVersion + +Used by menu +================== +*/ +void Con_DrawVersion( void ) +{ + // draws the current build + byte *color = g_color_table[7]; + int i, stringLen, width = 0, charH; + int start, height = glState.height; + qboolean draw_version = false; + string curbuild; + + switch( cls.scrshot_action ) + { + case scrshot_normal: + case scrshot_snapshot: + draw_version = true; + break; + } + + if( !host.force_draw_version ) + { + if(( cls.key_dest != key_menu && !draw_version ) || CL_IsDevOverviewMode() == 2 || net_graph->value ) + return; + } + + if( host.force_draw_version || draw_version ) + Q_snprintf( curbuild, MAX_STRING, "Xash3D v%i/%g (build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildnum( )); + else Q_snprintf( curbuild, MAX_STRING, "v%i/%g (build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildnum( )); + Con_DrawStringLen( curbuild, &stringLen, &charH ); + start = glState.width - stringLen * 1.05f; + stringLen = Con_StringLength( curbuild ); + height -= charH * 1.05f; + + for( i = 0; i < stringLen; i++ ) + width += Con_DrawCharacter( start + width, height, curbuild[i], color ); +} + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole( void ) +{ + float lines_per_frame; + + // decide on the destination height of the console + if( host.allow_console && cls.key_dest == key_console ) + { + if( cls.state < ca_active || cl.first_frame ) + con.showlines = glState.height; // full screen + else con.showlines = (glState.height >> 1); // half screen + } + else con.showlines = 0; // none visible + + lines_per_frame = fabs( scr_conspeed->value ) * host.realframetime; + + if( con.showlines < con.vislines ) + { + con.vislines -= lines_per_frame; + if( con.showlines > con.vislines ) + con.vislines = con.showlines; + } + else if( con.showlines > con.vislines ) + { + con.vislines += lines_per_frame; + if( con.showlines < con.vislines ) + con.vislines = con.showlines; + } +} + +/* +============================================================================== + +CONSOLE INTERFACE + +============================================================================== +*/ +/* +================ +Con_CharEvent + +Console input +================ +*/ +void Con_CharEvent( int key ) +{ + // distribute the key down event to the apropriate handler + if( cls.key_dest == key_console ) + { + Field_CharEvent( &con.input, key ); + } + else if( cls.key_dest == key_message ) + { + Field_CharEvent( &con.chat, key ); + } +} + +/* +========= +Con_VidInit + +INTERNAL RESOURCE +========= +*/ +void Con_VidInit( void ) +{ + Con_CheckResize(); + + Con_LoadConchars(); + + // loading console image + if( host.allow_console ) + { + // trying to load truecolor image first + if( FS_FileExists( "gfx/shell/conback.bmp", false ) || FS_FileExists( "gfx/shell/conback.tga", false )) + con.background = GL_LoadTexture( "gfx/shell/conback", NULL, 0, TF_IMAGE, NULL ); + + if( !con.background ) + { + if( FS_FileExists( "cached/conback640", false )) + con.background = GL_LoadTexture( "cached/conback640", NULL, 0, TF_IMAGE, NULL ); + else if( FS_FileExists( "cached/conback", false )) + con.background = GL_LoadTexture( "cached/conback", NULL, 0, TF_IMAGE, NULL ); + } + } + else + { + // trying to load truecolor image first + if( FS_FileExists( "gfx/shell/loading.bmp", false ) || FS_FileExists( "gfx/shell/loading.tga", false )) + con.background = GL_LoadTexture( "gfx/shell/loading", NULL, 0, TF_IMAGE, NULL ); + + if( !con.background ) + { + if( FS_FileExists( "cached/loading640", false )) + con.background = GL_LoadTexture( "cached/loading640", NULL, 0, TF_IMAGE, NULL ); + else if( FS_FileExists( "cached/loading", false )) + con.background = GL_LoadTexture( "cached/loading", NULL, 0, TF_IMAGE, NULL ); + } + } + + + if( !con.background ) // last chance - quake conback image + { + qboolean draw_to_console = false; + int length = 0; + gltexture_t *chars; + + // NOTE: only these games want to draw build number into console background + if( !Q_stricmp( FS_Gamedir(), "id1" )) + draw_to_console = true; + + if( !Q_stricmp( FS_Gamedir(), "hipnotic" )) + draw_to_console = true; + + if( !Q_stricmp( FS_Gamedir(), "rogue" )) + draw_to_console = true; + + if( draw_to_console && con.curFont && ( chars = R_GetTexture( con.curFont->hFontTexture )) != NULL && chars->original ) + { + lmp_t *cb = (lmp_t *)FS_LoadFile( "gfx/conback.lmp", &length, false ); + char ver[64]; + byte *dest; + int x, y; + + if( cb && cb->width == 320 && cb->height == 200 ) + { + Q_snprintf( ver, 64, "%i", Q_buildnum( )); // can store only buildnum + dest = (byte *)(cb + 1) + 320 * 186 + 320 - 11 - 8 * Q_strlen( ver ); + y = Q_strlen( ver ); + for( x = 0; x < y; x++ ) + Con_DrawCharToConback( ver[x], chars->original->buffer, dest + (x << 3)); + con.background = GL_LoadTexture( "#gfx/conback.lmp", (byte *)cb, length, TF_IMAGE, NULL ); + } + if( cb ) Mem_Free( cb ); + } + + if( !con.background ) // trying the load unmodified conback + con.background = GL_LoadTexture( "gfx/conback.lmp", NULL, 0, TF_IMAGE, NULL ); + } + + // missed console image will be replaced as gray background like X-Ray or Crysis + if( con.background == tr.defaultTexture || con.background == 0 ) + con.background = tr.grayTexture; +} + +/* +========= +Con_InvalidateFonts + +========= +*/ +void Con_InvalidateFonts( void ) +{ + memset( con.chars, 0, sizeof( con.chars )); + con.curFont = con.lastUsedFont = NULL; +} + +/* +========= +Cmd_AutoComplete + +NOTE: input string must be equal or longer than MAX_STRING +========= +*/ +void Cmd_AutoComplete( char *complete_string ) +{ + field_t input; + + if( !complete_string || !*complete_string ) + return; + + // setup input + Q_strncpy( input.buffer, complete_string, sizeof( input.buffer )); + input.cursor = input.scroll = 0; + + Con_CompleteCommand( &input ); + + // setup output + if( input.buffer[0] == '\\' || input.buffer[0] == '/' ) + Q_strncpy( complete_string, input.buffer + 1, sizeof( input.buffer )); + else Q_strncpy( complete_string, input.buffer, sizeof( input.buffer )); +} + +/* +========= +Con_FastClose + +immediately close the console +========= +*/ +void Con_FastClose( void ) +{ + Con_ClearField( &con.input ); + Con_ClearNotify(); + con.showlines = 0; + con.vislines = 0; +} + +/* +========= +Con_DefaultColor + +called from MainUI +========= +*/ +void Con_DefaultColor( int r, int g, int b ) +{ + r = bound( 0, r, 255 ); + g = bound( 0, g, 255 ); + b = bound( 0, b, 255 ); + MakeRGBA( g_color_table[7], r, g, b, 255 ); +} \ No newline at end of file diff --git a/engine/common/crclib.c b/engine/common/crclib.c new file mode 100644 index 00000000..165708bc --- /dev/null +++ b/engine/common/crclib.c @@ -0,0 +1,604 @@ +/* +crclib.c - generate crc stuff +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" + +#define NUM_BYTES 256 +#define CRC32_INIT_VALUE 0xFFFFFFFFUL +#define CRC32_XOR_VALUE 0xFFFFFFFFUL + +static const dword crc32table[NUM_BYTES] = +{ +0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, +0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, +0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, +0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, +0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, +0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, +0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, +0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, +0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, +0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, +0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, +0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, +0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, +0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, +0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, +0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, +0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, +0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, +0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, +0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, +0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, +0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, +0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, +0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, +0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, +0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, +0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, +0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, +0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, +0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, +0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, +0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, +0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, +0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, +0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, +0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, +0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, +0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, +0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, +0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, +0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, +0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, +0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, +0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, +0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, +0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, +0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, +0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, +0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, +0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, +0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, +0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, +0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, +0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, +0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, +0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, +0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, +0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, +0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, +0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, +0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, +0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, +0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, +0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +void CRC32_Init( dword *pulCRC ) +{ + *pulCRC = CRC32_INIT_VALUE; +} + +dword CRC32_Final( dword pulCRC ) +{ + return pulCRC ^ CRC32_XOR_VALUE; +} + +void CRC32_ProcessByte( dword *pulCRC, byte ch ) +{ + dword ulCrc = *pulCRC; + + ulCrc ^= ch; + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; +} + +void CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ) +{ + dword ulCrc = *pulCRC; + byte *pb = (byte *)pBuffer; + uint nFront; + int nMain; +JustAfew: + switch( nBuffer ) + { + case 7: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 6: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 5: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 4: + ulCrc ^= *(dword *)pb; // warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; + return; + case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 0: *pulCRC = ulCrc; + return; + } + + // We may need to do some alignment work up front, and at the end, so that + // the main loop is aligned and only has to worry about 8 byte at a time. + // The low-order two bits of pb and nBuffer in total control the + // upfront work. + nFront = ((uint)pb) & 3; + nBuffer -= nFront; + + switch( nFront ) + { + case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + } + + nMain = nBuffer >> 3; + while( nMain-- ) + { + ulCrc ^= *(dword *)pb; // warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc ^= *(dword *)(pb + 4);// warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + pb += 8; + } + + nBuffer &= 7; + goto JustAfew; +} + +/* +==================== +CRC32_BlockSequence + +For proxy protecting +==================== +*/ +byte CRC32_BlockSequence( byte *base, int length, int sequence ) +{ + dword CRC; + char *ptr; + char buffer[64]; + + if( sequence < 0 ) sequence = abs( sequence ); + ptr = (char *)crc32table + (sequence % 0x3FC); + + if( length > 60 ) length = 60; + memcpy( buffer, base, length ); + + buffer[length+0] = ptr[0]; + buffer[length+1] = ptr[1]; + buffer[length+2] = ptr[2]; + buffer[length+3] = ptr[3]; + + length += 4; + + CRC32_Init( &CRC ); + CRC32_ProcessBuffer( &CRC, buffer, length ); + CRC = CRC32_Final( CRC ); + + return (byte)CRC; +} + +qboolean CRC32_File( dword *crcvalue, const char *filename ) +{ + char buffer[1024]; + int num_bytes; + file_t *f; + + f = FS_Open( filename, "rb", false ); + if( !f ) return false; + + Assert( crcvalue != NULL ); + CRC32_Init( crcvalue ); + + while( 1 ) + { + num_bytes = FS_Read( f, buffer, sizeof( buffer )); + + if( num_bytes > 0 ) + CRC32_ProcessBuffer( crcvalue, buffer, num_bytes ); + + if( FS_Eof( f )) break; + } + + FS_Close( f ); + return true; +} + +qboolean CRC32_MapFile( dword *crcvalue, const char *filename, qboolean multiplayer ) +{ + char headbuf[256], buffer[1024]; + int i, num_bytes, lumplen; + int version, hdr_size; + dheader_t *header; + file_t *f; + + if( !crcvalue ) return false; + + // always calc same checksum for singleplayer + if( multiplayer == false ) + { + *crcvalue = (('H'<<24)+('S'<<16)+('A'<<8)+'X'); + return true; + } + + f = FS_Open( filename, "rb", false ); + if( !f ) return false; + + // read version number + FS_Read( f, &version, sizeof( int )); + FS_Seek( f, 0, SEEK_SET ); + + hdr_size = sizeof( int ) + sizeof( dlump_t ) * HEADER_LUMPS; + num_bytes = FS_Read( f, headbuf, hdr_size ); + + // corrupted map ? + if( num_bytes != hdr_size ) + { + FS_Close( f ); + return false; + } + + header = (dheader_t *)headbuf; + + // invalid version ? + switch( header->version ) + { + case Q1BSP_VERSION: + case HLBSP_VERSION: + case QBSP2_VERSION: + break; + default: + FS_Close( f ); + return false; + } + + CRC32_Init( crcvalue ); + + for( i = LUMP_PLANES; i < HEADER_LUMPS; i++ ) + { + lumplen = header->lumps[i].filelen; + FS_Seek( f, header->lumps[i].fileofs, SEEK_SET ); + + while( lumplen > 0 ) + { + if( lumplen >= sizeof( buffer )) + num_bytes = FS_Read( f, buffer, sizeof( buffer )); + else num_bytes = FS_Read( f, buffer, lumplen ); + + if( num_bytes > 0 ) + { + lumplen -= num_bytes; + CRC32_ProcessBuffer( crcvalue, buffer, num_bytes ); + } + + // file unexpected end ? + if( FS_Eof( f )) break; + } + } + + FS_Close( f ); + + return 1; +} + +void MD5Transform( uint buf[4], const uint in[16] ); + +/* +================== +MD5Init + +Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. +================== +*/ +void MD5Init( MD5Context_t *ctx ) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; +} + +/* +=================== +MD5Update + +Update context to reflect the concatenation of another buffer full of bytes. +=================== +*/ +void MD5Update( MD5Context_t *ctx, const byte *buf, uint len ) +{ + uint t; + + // update bitcount + t = ctx->bits[0]; + + if(( ctx->bits[0] = t + ((uint) len << 3 )) < t ) + ctx->bits[1]++; // carry from low to high + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; // bytes already in shsInfo->data + + // handle any leading odd-sized chunks + if( t ) + { + byte *p = (byte *)ctx->in + t; + + t = 64 - t; + if( len < t ) + { + memcpy( p, buf, len ); + return; + } + + memcpy( p, buf, t ); + MD5Transform( ctx->buf, (uint *)ctx->in ); + buf += t; + len -= t; + } + + // process data in 64-byte chunks + while( len >= 64 ) + { + memcpy( ctx->in, buf, 64 ); + MD5Transform( ctx->buf, (uint *)ctx->in ); + buf += 64; + len -= 64; + } + + // handle any remaining bytes of data. + memcpy( ctx->in, buf, len ); +} + +/* +=============== +MD5Final + +Final wrapup - pad to 64-byte boundary with the bit pattern +1 0* (64-bit count of bits processed, MSB-first) +=============== +*/ +void MD5Final( byte digest[16], MD5Context_t *ctx ) +{ + uint count; + byte *p; + + // compute number of bytes mod 64 + count = ( ctx->bits[0] >> 3 ) & 0x3F; + + // set the first char of padding to 0x80. + // this is safe since there is always at least one byte free + p = ctx->in + count; + *p++ = 0x80; + + // bytes of padding needed to make 64 bytes + count = 64 - 1 - count; + + // pad out to 56 mod 64 + if( count < 8 ) + { + + // two lots of padding: pad the first block to 64 bytes + memset( p, 0, count ); + MD5Transform( ctx->buf, (uint *)ctx->in ); + + // now fill the next block with 56 bytes + memset( ctx->in, 0, 56 ); + } + else + { + // pad block to 56 bytes + memset( p, 0, count - 8 ); + } + + // append length in bits and transform + ((uint *)ctx->in)[14] = ctx->bits[0]; + ((uint *)ctx->in)[15] = ctx->bits[1]; + + MD5Transform( ctx->buf, (uint *)ctx->in ); + memcpy( digest, ctx->buf, 16 ); + memset( ctx, 0, sizeof( ctx )); // in case it's sensitive +} + +// The four core functions +#define F1( x, y, z ) ( z ^ ( x & ( y ^ z ))) +#define F2( x, y, z ) F1( z, x, y ) +#define F3( x, y, z ) ( x ^ y ^ z ) +#define F4( x, y, z ) ( y ^ ( x | ~z )) + +// this is the central step in the MD5 algorithm. +#define MD5STEP( f, w, x, y, z, data, s ) ( w += f( x, y, z ) + data, w = w << s|w >> (32 - s), w += x ) + +/* +================= +MD5Transform + +The core of the MD5 algorithm, this alters an existing MD5 hash to +reflect the addition of 16 longwords of new data. MD5Update blocks +the data and converts bytes into longwords for this routine. +================= +*/ +void MD5Transform( uint buf[4], const uint in[16] ) +{ + register uint a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP( F1, a, b, c, d, in[0] + 0xd76aa478, 7 ); + MD5STEP( F1, d, a, b, c, in[1] + 0xe8c7b756, 12 ); + MD5STEP( F1, c, d, a, b, in[2] + 0x242070db, 17 ); + MD5STEP( F1, b, c, d, a, in[3] + 0xc1bdceee, 22 ); + MD5STEP( F1, a, b, c, d, in[4] + 0xf57c0faf, 7 ); + MD5STEP( F1, d, a, b, c, in[5] + 0x4787c62a, 12 ); + MD5STEP( F1, c, d, a, b, in[6] + 0xa8304613, 17 ); + MD5STEP( F1, b, c, d, a, in[7] + 0xfd469501, 22 ); + MD5STEP( F1, a, b, c, d, in[8] + 0x698098d8, 7 ); + MD5STEP( F1, d, a, b, c, in[9] + 0x8b44f7af, 12 ); + MD5STEP( F1, c, d, a, b, in[10] + 0xffff5bb1, 17 ); + MD5STEP( F1, b, c, d, a, in[11] + 0x895cd7be, 22 ); + MD5STEP( F1, a, b, c, d, in[12] + 0x6b901122, 7 ); + MD5STEP( F1, d, a, b, c, in[13] + 0xfd987193, 12 ); + MD5STEP( F1, c, d, a, b, in[14] + 0xa679438e, 17 ); + MD5STEP( F1, b, c, d, a, in[15] + 0x49b40821, 22 ); + + MD5STEP( F2, a, b, c, d, in[1] + 0xf61e2562, 5 ); + MD5STEP( F2, d, a, b, c, in[6] + 0xc040b340, 9 ); + MD5STEP( F2, c, d, a, b, in[11] + 0x265e5a51, 14 ); + MD5STEP( F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20 ); + MD5STEP( F2, a, b, c, d, in[5] + 0xd62f105d, 5 ); + MD5STEP( F2, d, a, b, c, in[10] + 0x02441453, 9 ); + MD5STEP( F2, c, d, a, b, in[15] + 0xd8a1e681, 14 ); + MD5STEP( F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20 ); + MD5STEP( F2, a, b, c, d, in[9] + 0x21e1cde6, 5 ); + MD5STEP( F2, d, a, b, c, in[14] + 0xc33707d6, 9 ); + MD5STEP( F2, c, d, a, b, in[3] + 0xf4d50d87, 14 ); + MD5STEP( F2, b, c, d, a, in[8] + 0x455a14ed, 20 ); + MD5STEP( F2, a, b, c, d, in[13] + 0xa9e3e905, 5 ); + MD5STEP( F2, d, a, b, c, in[2] + 0xfcefa3f8, 9 ); + MD5STEP( F2, c, d, a, b, in[7] + 0x676f02d9, 14 ); + MD5STEP( F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20 ); + + MD5STEP( F3, a, b, c, d, in[5] + 0xfffa3942, 4 ); + MD5STEP( F3, d, a, b, c, in[8] + 0x8771f681, 11 ); + MD5STEP( F3, c, d, a, b, in[11] + 0x6d9d6122, 16 ); + MD5STEP( F3, b, c, d, a, in[14] + 0xfde5380c, 23 ); + MD5STEP( F3, a, b, c, d, in[1] + 0xa4beea44, 4 ); + MD5STEP( F3, d, a, b, c, in[4] + 0x4bdecfa9, 11 ); + MD5STEP( F3, c, d, a, b, in[7] + 0xf6bb4b60, 16 ); + MD5STEP( F3, b, c, d, a, in[10] + 0xbebfbc70, 23 ); + MD5STEP( F3, a, b, c, d, in[13] + 0x289b7ec6, 4 ); + MD5STEP( F3, d, a, b, c, in[0] + 0xeaa127fa, 11 ); + MD5STEP( F3, c, d, a, b, in[3] + 0xd4ef3085, 16 ); + MD5STEP( F3, b, c, d, a, in[6] + 0x04881d05, 23 ); + MD5STEP( F3, a, b, c, d, in[9] + 0xd9d4d039, 4 ); + MD5STEP( F3, d, a, b, c, in[12] + 0xe6db99e5, 11 ); + MD5STEP( F3, c, d, a, b, in[15] + 0x1fa27cf8, 16 ); + MD5STEP( F3, b, c, d, a, in[2] + 0xc4ac5665, 23 ); + + MD5STEP( F4, a, b, c, d, in[0] + 0xf4292244, 6 ); + MD5STEP( F4, d, a, b, c, in[7] + 0x432aff97, 10 ); + MD5STEP( F4, c, d, a, b, in[14] + 0xab9423a7, 15 ); + MD5STEP( F4, b, c, d, a, in[5] + 0xfc93a039, 21 ); + MD5STEP( F4, a, b, c, d, in[12] + 0x655b59c3, 6 ); + MD5STEP( F4, d, a, b, c, in[3] + 0x8f0ccc92, 10 ); + MD5STEP( F4, c, d, a, b, in[10] + 0xffeff47d, 15 ); + MD5STEP( F4, b, c, d, a, in[1] + 0x85845dd1, 21 ); + MD5STEP( F4, a, b, c, d, in[8] + 0x6fa87e4f, 6 ); + MD5STEP( F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10 ); + MD5STEP( F4, c, d, a, b, in[6] + 0xa3014314, 15 ); + MD5STEP( F4, b, c, d, a, in[13] + 0x4e0811a1, 21 ); + MD5STEP( F4, a, b, c, d, in[4] + 0xf7537e82, 6 ); + MD5STEP( F4, d, a, b, c, in[11] + 0xbd3af235, 10 ); + MD5STEP( F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15 ); + MD5STEP( F4, b, c, d, a, in[9] + 0xeb86d391, 21 ); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +qboolean MD5_HashFile( byte digest[16], const char *pszFileName, uint seed[4] ) +{ + file_t *file; + char buffer[1024]; + MD5Context_t MD5_Hash; + int bytes; + + if(( file = FS_Open( pszFileName, "rb", false )) == NULL ) + return false; + + memset( &MD5_Hash, 0, sizeof( MD5Context_t )); + + MD5Init( &MD5_Hash ); + + if( seed ) + { + MD5Update( &MD5_Hash, (const byte *)seed, 16 ); + } + + while( 1 ) + { + bytes = FS_Read( file, buffer, sizeof( buffer )); + + if( bytes > 0 ) + MD5Update( &MD5_Hash, buffer, bytes ); + + if( FS_Eof( file )) + break; + } + + FS_Close( file ); + MD5Final( digest, &MD5_Hash ); + + return true; +} + +/* +================= +MD5_Print + +transform hash to hexadecimal printable symbols +================= +*/ +char *MD5_Print( byte hash[16] ) +{ + static char szReturn[64]; + byte szChunk[10]; + int i; + + memset( szReturn, 0, 64 ); + + for( i = 0; i < 16; i++ ) + { + Q_snprintf( szChunk, sizeof( szChunk ), "%02X", hash[i] ); + Q_strncat( szReturn, szChunk, sizeof( szReturn )); + } + + return szReturn; +} + +/* +================= +COM_HashKey + +returns hash key for string +================= +*/ +uint COM_HashKey( const char *string, uint hashSize ) +{ + uint i, hashKey = 0; + + for( i = 0; string[i]; i++ ) + hashKey = (hashKey + i) * 37 + Q_tolower( string[i] ); + + return (hashKey % hashSize); +} \ No newline at end of file diff --git a/engine/common/crtlib.c b/engine/common/crtlib.c new file mode 100644 index 00000000..e7a68cda --- /dev/null +++ b/engine/common/crtlib.c @@ -0,0 +1,722 @@ +/* +crtlib.c - internal stdlib +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include +#include "common.h" + +void Q_strnupr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'a' && *in <= 'z' ) + *out++ = *in++ + 'A' - 'a'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +void Q_strnlwr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'A' && *in <= 'Z' ) + *out++ = *in++ + 'a' - 'A'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +qboolean Q_isdigit( const char *str ) +{ + if( str && *str ) + { + while( isdigit( *str )) str++; + if( !*str ) return true; + } + return false; +} + +int Q_strlen( const char *string ) +{ + int len; + const char *p; + + if( !string ) return 0; + + len = 0; + p = string; + while( *p ) + { + p++; + len++; + } + return len; +} + +int Q_colorstr( const char *string ) +{ + int len; + const char *p; + + if( !string ) return 0; + + len = 0; + p = string; + while( *p ) + { + if( IsColorString( p )) + { + len += 2; + p += 2; + continue; + } + p++; + } + + return len; +} + +char Q_toupper( const char in ) +{ + char out; + + if( in >= 'a' && in <= 'z' ) + out = in + 'A' - 'a'; + else out = in; + + return out; +} + +char Q_tolower( const char in ) +{ + char out; + + if( in >= 'A' && in <= 'Z' ) + out = in + 'a' - 'A'; + else out = in; + + return out; +} + +size_t Q_strncat( char *dst, const char *src, size_t size ) +{ + register char *d = dst; + register const char *s = src; + register size_t n = size; + size_t dlen; + + if( !dst || !src || !size ) + return 0; + + // find the end of dst and adjust bytes left but don't go past end + while( n-- != 0 && *d != '\0' ) d++; + dlen = d - dst; + n = size - dlen; + + if( n == 0 ) return( dlen + Q_strlen( s )); + + while( *s != '\0' ) + { + if( n != 1 ) + { + *d++ = *s; + n--; + } + s++; + } + + *d = '\0'; + return( dlen + ( s - src )); // count does not include NULL +} + +size_t Q_strncpy( char *dst, const char *src, size_t size ) +{ + register char *d = dst; + register const char *s = src; + register size_t n = size; + + if( !dst || !src || !size ) + return 0; + + // copy as many bytes as will fit + if( n != 0 && --n != 0 ) + { + do + { + if(( *d++ = *s++ ) == 0 ) + break; + } while( --n != 0 ); + } + + // not enough room in dst, add NULL and traverse rest of src + if( n == 0 ) + { + if( size != 0 ) + *d = '\0'; // NULL-terminate dst + while( *s++ ); + } + return ( s - src - 1 ); // count does not include NULL +} + +char *_copystring( byte *mempool, const char *s, const char *filename, int fileline ) +{ + char *b; + + if( !s ) return NULL; + if( !mempool ) mempool = host.mempool; + + b = _Mem_Alloc( mempool, Q_strlen( s ) + 1, filename, fileline ); + Q_strcpy( b, s ); + + return b; +} + +int Q_atoi( const char *str ) +{ + int val = 0; + int c, sign; + + if( !str ) return 0; + + // check for empty charachters in string + while( str && *str == ' ' ) + str++; + + if( !str ) return 0; + + if( *str == '-' ) + { + sign = -1; + str++; + } + else sign = 1; + + // check for hex + if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) + { + str += 2; + while( 1 ) + { + c = *str++; + if( c >= '0' && c <= '9' ) val = (val<<4) + c - '0'; + else if( c >= 'a' && c <= 'f' ) val = (val<<4) + c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) val = (val<<4) + c - 'A' + 10; + else return val * sign; + } + } + + // check for character + if( str[0] == '\'' ) + return sign * str[1]; + + // assume decimal + while( 1 ) + { + c = *str++; + if( c < '0' || c > '9' ) + return val * sign; + val = val * 10 + c - '0'; + } + return 0; +} + +float Q_atof( const char *str ) +{ + double val = 0; + int c, sign, decimal, total; + + if( !str ) return 0.0f; + + // check for empty charachters in string + while( str && *str == ' ' ) + str++; + + if( !str ) return 0.0f; + + if( *str == '-' ) + { + sign = -1; + str++; + } + else sign = 1; + + // check for hex + if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) + { + str += 2; + while( 1 ) + { + c = *str++; + if( c >= '0' && c <= '9' ) val = (val * 16) + c - '0'; + else if( c >= 'a' && c <= 'f' ) val = (val * 16) + c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) val = (val * 16) + c - 'A' + 10; + else return val * sign; + } + } + + // check for character + if( str[0] == '\'' ) return sign * str[1]; + + // assume decimal + decimal = -1; + total = 0; + + while( 1 ) + { + c = *str++; + if( c == '.' ) + { + decimal = total; + continue; + } + + if( c < '0' || c > '9' ) + break; + val = val * 10 + c - '0'; + total++; + } + + if( decimal == -1 ) + return val * sign; + + while( total > decimal ) + { + val /= 10; + total--; + } + + return val * sign; +} + +void Q_atov( float *vec, const char *str, size_t siz ) +{ + string buffer; + char *pstr, *pfront; + int j; + + Q_strncpy( buffer, str, sizeof( buffer )); + memset( vec, 0, sizeof( vec_t ) * siz ); + pstr = pfront = buffer; + + for( j = 0; j < siz; j++ ) + { + vec[j] = Q_atof( pfront ); + + // valid separator is space + while( *pstr && *pstr != ' ' ) + pstr++; + + if( !*pstr ) break; + pstr++; + pfront = pstr; + } +} + +char *Q_strchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + while( len-- ) + { + if( *++s == c ) + return (char *)s; + } + return 0; +} + +char *Q_strrchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + s += len; + + while( len-- ) + { + if( *--s == c ) + return (char *)s; + } + return 0; +} + +int Q_strnicmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) + return 0; + else return -1; + } + else if( s2 == NULL ) + { + return 1; + } + + do { + c1 = *s1++; + c2 = *s2++; + + if( !n-- ) return 0; // strings are equal until end point + + if( c1 != c2 ) + { + if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); + if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + } + } while( c1 ); + + // strings are equal + return 0; +} + +int Q_strncmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) + return 0; + else return -1; + } + else if( s2 == NULL ) + { + return 1; + } + + do { + c1 = *s1++; + c2 = *s2++; + + // strings are equal until end point + if( !n-- ) return 0; + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + + } while( c1 ); + + // strings are equal + return 0; +} + +static qboolean Q_starcmp( const char *pattern, const char *text ) +{ + char c, c1; + const char *p = pattern, *t = text; + + while(( c = *p++ ) == '?' || c == '*' ) + { + if( c == '?' && *t++ == '\0' ) + return false; + } + + if( c == '\0' ) return true; + + for( c1 = (( c == '\\' ) ? *p : c ); ; ) + { + if( Q_tolower( *t ) == c1 && Q_stricmpext( p - 1, t )) + return true; + if( *t++ == '\0' ) return false; + } +} + +qboolean Q_stricmpext( const char *pattern, const char *text ) +{ + char c; + + while(( c = *pattern++ ) != '\0' ) + { + switch( c ) + { + case '?': + if( *text++ == '\0' ) + return false; + break; + case '\\': + if( Q_tolower( *pattern++ ) != Q_tolower( *text++ )) + return false; + break; + case '*': + return Q_starcmp( pattern, text ); + default: + if( Q_tolower( c ) != Q_tolower( *text++ )) + return false; + } + } + return ( *text == '\0' ); +} + +const char* Q_timestamp( int format ) +{ + static string timestamp; + time_t crt_time; + const struct tm *crt_tm; + string timestring; + + time( &crt_time ); + crt_tm = localtime( &crt_time ); + + switch( format ) + { + case TIME_FULL: + // Build the full timestamp (ex: "Apr03 2007 [23:31.55]"); + strftime( timestring, sizeof( timestring ), "%b%d %Y [%H:%M.%S]", crt_tm ); + break; + case TIME_DATE_ONLY: + // Build the date stamp only (ex: "Apr03 2007"); + strftime( timestring, sizeof( timestring ), "%b%d %Y", crt_tm ); + break; + case TIME_TIME_ONLY: + // Build the time stamp only (ex: "23:31.55"); + strftime( timestring, sizeof( timestring ), "%H:%M.%S", crt_tm ); + break; + case TIME_NO_SECONDS: + // Build the time stamp exclude seconds (ex: "13:46"); + strftime( timestring, sizeof( timestring ), "%H:%M", crt_tm ); + break; + case TIME_YEAR_ONLY: + // Build the date stamp year only (ex: "2006"); + strftime( timestring, sizeof( timestring ), "%Y", crt_tm ); + break; + case TIME_FILENAME: + // Build a timestamp that can use for filename (ex: "Nov2006-26 (19.14.28)"); + strftime( timestring, sizeof( timestring ), "%b%Y-%d_%H.%M.%S", crt_tm ); + break; + default: return NULL; + } + + Q_strncpy( timestamp, timestring, sizeof( timestamp )); + + return timestamp; +} + +char *Q_strstr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = *string2; + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && *string != c; string++ ); + + if( *string ) + { + if( !Q_strncmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +char *Q_stristr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = Q_tolower( *string2 ); + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && Q_tolower( *string ) != c; string++ ); + + if( *string ) + { + if( !Q_strnicmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) +{ + size_t result; + + __try + { + result = _vsnprintf( buffer, buffersize, format, args ); + } + + // to prevent crash while output + __except( EXCEPTION_EXECUTE_HANDLER ) + { + memset( buffer, 0, buffersize ); + result = -1; + } + + if( result < 0 || result >= buffersize ) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + return result; +} + +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, buffersize, format, args ); + va_end( args ); + + return result; +} + +int Q_sprintf( char *buffer, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, 99999, format, args ); + va_end( args ); + + return result; +} + +uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ) +{ + uint i, hashKey = 0; + + if( caseinsensitive ) + { + for( i = 0; string[i]; i++) + hashKey += (i * 119) * Q_tolower( string[i] ); + } + else + { + for( i = 0; string[i]; i++ ) + hashKey += (i + 119) * (int)string[i]; + } + + hashKey = ((hashKey ^ (hashKey >> 10)) ^ (hashKey >> 20)) & (hashSize - 1); + + return hashKey; +} + +char *Q_pretifymem( float value, int digitsafterdecimal ) +{ + static char output[8][32]; + static int current; + float onekb = 1024.0f; + float onemb = onekb * onekb; + char suffix[8]; + char *out = output[current]; + char val[32], *i, *o, *dot; + int pos; + + current = ( current + 1 ) & ( 8 - 1 ); + + // first figure out which bin to use + if( value > onemb ) + { + value /= onemb; + Q_sprintf( suffix, " Mb" ); + } + else if( value > onekb ) + { + value /= onekb; + Q_sprintf( suffix, " Kb" ); + } + else Q_sprintf( suffix, " bytes" ); + + // clamp to >= 0 + digitsafterdecimal = max( digitsafterdecimal, 0 ); + + // if it's basically integral, don't do any decimals + if( fabs( value - (int)value ) < 0.00001 ) + { + Q_sprintf( val, "%i%s", (int)value, suffix ); + } + else + { + char fmt[32]; + + // otherwise, create a format string for the decimals + Q_sprintf( fmt, "%%.%if%s", digitsafterdecimal, suffix ); + Q_sprintf( val, fmt, value ); + } + + // copy from in to out + i = val; + o = out; + + // search for decimal or if it was integral, find the space after the raw number + dot = Q_strstr( i, "." ); + if( !dot ) dot = Q_strstr( i, " " ); + + pos = dot - i; // compute position of dot + pos -= 3; // don't put a comma if it's <= 3 long + + while( *i ) + { + // if pos is still valid then insert a comma every third digit, except if we would be + // putting one in the first spot + if( pos >= 0 && !( pos % 3 )) + { + // never in first spot + if( o != out ) *o++ = ','; + } + + pos--; // count down comma position + *o++ = *i++; // copy rest of data as normal + } + *o = 0; // terminate + + return out; +} + +/* +============ +va + +does a varargs printf into a temp buffer, +so I don't need to have varargs versions +of all text functions. +============ +*/ +char *va( const char *format, ... ) +{ + va_list argptr; + static char string[256][1024], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 255; + va_start( argptr, format ); + Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); + va_end( argptr ); + + return s; +} \ No newline at end of file diff --git a/engine/common/crtlib.h b/engine/common/crtlib.h new file mode 100644 index 00000000..f622010c --- /dev/null +++ b/engine/common/crtlib.h @@ -0,0 +1,130 @@ +/* +crtlib.h - internal stdlib +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef STDLIB_H +#define STDLIB_H + +// timestamp modes +enum +{ + TIME_FULL = 0, + TIME_DATE_ONLY, + TIME_TIME_ONLY, + TIME_NO_SECONDS, + TIME_YEAR_ONLY, + TIME_FILENAME, +}; + +#define CMD_SERVERDLL BIT( 0 ) // added by server.dll +#define CMD_CLIENTDLL BIT( 1 ) // added by client.dll +#define CMD_GAMEUIDLL BIT( 2 ) // added by GameUI.dll + +typedef void (*xcommand_t)( void ); + +// +// cmd.c +// +void Cbuf_Init( void ); +void Cbuf_Clear( void ); +void Cbuf_AddText( const char *text ); +void Cbuf_InsertText( const char *text ); +void Cbuf_ExecStuffCmds( void ); +void Cbuf_Execute (void); +uint Cmd_Argc( void ); +char *Cmd_Args( void ); +char *Cmd_Argv( int arg ); +void Cmd_Init( void ); +void Cmd_Unlink( int group ); +void Cmd_AddCommand( const char *cmd_name, xcommand_t function, const char *cmd_desc ); +void Cmd_AddServerCommand( const char *cmd_name, xcommand_t function ); +int Cmd_AddClientCommand( const char *cmd_name, xcommand_t function ); +int Cmd_AddGameUICommand( const char *cmd_name, xcommand_t function ); +void Cmd_RemoveCommand( const char *cmd_name ); +qboolean Cmd_Exists( const char *cmd_name ); +void Cmd_LookupCmds( char *buffer, void *ptr, setpair_t callback ); +qboolean Cmd_GetMapList( const char *s, char *completedname, int length ); +qboolean Cmd_GetDemoList( const char *s, char *completedname, int length ); +qboolean Cmd_GetMovieList( const char *s, char *completedname, int length ); +void Cmd_TokenizeString( char *text ); +void Cmd_ExecuteString( char *text ); +void Cmd_ForwardToServer( void ); + +// +// crtlib.c +// +#define Q_strupr( in, out ) Q_strnupr( in, out, 99999 ) +void Q_strnupr( const char *in, char *out, size_t size_out ); +#define Q_strlwr( in, out ) Q_strnlwr( in, out, 99999 ) +void Q_strnlwr( const char *in, char *out, size_t size_out ); +int Q_strlen( const char *string ); +int Q_colorstr( const char *string ); +char Q_toupper( const char in ); +char Q_tolower( const char in ); +#define Q_strcat( dst, src ) Q_strncat( dst, src, 99999 ) +size_t Q_strncat( char *dst, const char *src, size_t siz ); +#define Q_strcpy( dst, src ) Q_strncpy( dst, src, 99999 ) +size_t Q_strncpy( char *dst, const char *src, size_t siz ); +#define copystring( s ) _copystring( host.mempool, s, __FILE__, __LINE__ ) +#define SV_CopyString( s ) _copystring( svgame.stringspool, s, __FILE__, __LINE__ ) +#define freestring( s ) if( s != NULL ) { Mem_Free( s ); s = NULL; } +char *_copystring( byte *mempool, const char *s, const char *filename, int fileline ); +uint Q_hashkey( const char *string, uint hashSize, qboolean caseinsensitive ); +qboolean Q_isdigit( const char *str ); +int Q_atoi( const char *str ); +float Q_atof( const char *str ); +void Q_atov( float *vec, const char *str, size_t siz ); +char *Q_strchr( const char *s, char c ); +char *Q_strrchr( const char *s, char c ); +#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) +int Q_strnicmp( const char *s1, const char *s2, int n ); +#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) +int Q_strncmp( const char *s1, const char *s2, int n ); +qboolean Q_stricmpext( const char *s1, const char *s2 ); +const char *Q_timestamp( int format ); +char *Q_stristr( const char *string, const char *string2 ); +char *Q_strstr( const char *string, const char *string2 ); +#define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ); +int Q_sprintf( char *buffer, const char *format, ... ); +#define Q_memprint( val ) Q_pretifymem( val, 2 ) +char *Q_pretifymem( float value, int digitsafterdecimal ); +char *va( const char *format, ... ); + +// +// zone.c +// +void Memory_Init( void ); +void *_Mem_Realloc( byte *poolptr, void *memptr, size_t size, const char *filename, int fileline ); +void *_Mem_Alloc( byte *poolptr, size_t size, const char *filename, int fileline ); +byte *_Mem_AllocPool( const char *name, const char *filename, int fileline ); +void _Mem_FreePool( byte **poolptr, const char *filename, int fileline ); +void _Mem_EmptyPool( byte *poolptr, const char *filename, int fileline ); +void _Mem_Free( void *data, const char *filename, int fileline ); +void _Mem_Check( const char *filename, int fileline ); +qboolean Mem_IsAllocatedExt( byte *poolptr, void *data ); +void Mem_PrintList( size_t minallocationsize ); +void Mem_PrintStats( void ); + +#define Mem_Alloc( pool, size ) _Mem_Alloc( pool, size, __FILE__, __LINE__ ) +#define Mem_Realloc( pool, ptr, size ) _Mem_Realloc( pool, ptr, size, __FILE__, __LINE__ ) +#define Mem_Free( mem ) _Mem_Free( mem, __FILE__, __LINE__ ) +#define Mem_AllocPool( name ) _Mem_AllocPool( name, __FILE__, __LINE__ ) +#define Mem_FreePool( pool ) _Mem_FreePool( pool, __FILE__, __LINE__ ) +#define Mem_EmptyPool( pool ) _Mem_EmptyPool( pool, __FILE__, __LINE__ ) +#define Mem_IsAllocated( mem ) Mem_IsAllocatedExt( NULL, mem ) +#define Mem_Check() _Mem_Check( __FILE__, __LINE__ ) + +#endif//STDLIB_H \ No newline at end of file diff --git a/engine/common/custom.c b/engine/common/custom.c new file mode 100644 index 00000000..7b4b15c5 --- /dev/null +++ b/engine/common/custom.c @@ -0,0 +1,150 @@ +/* +custom.c - customization routines +Copyright (C) 2018 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "custom.h" + +qboolean CustomDecal_Validate( void *raw, int nFileSize ) +{ + rgbdata_t *test = FS_LoadImage( "#logo.bmp", raw, nFileSize ); + + if( test ) + { + // all's ok, logo is valid + FS_FreeImage( test ); + return true; + } + + return false; +} + +void COM_ClearCustomizationList( customization_t *pHead, qboolean bCleanDecals ) +{ + customization_t *pCurrent; + customization_t *pNext; + + for( pCurrent = pHead->pNext; pCurrent != NULL; pCurrent = pNext ) + { + pNext = pCurrent->pNext; + + if( pCurrent->bInUse && pCurrent->pBuffer ) + Mem_Free( pCurrent->pBuffer ); + + if( pCurrent->bInUse && pCurrent->pInfo ) + { + if( pCurrent->resource.type == t_decal ) + { + if( bCleanDecals && CL_Active( )) + R_DecalRemoveAll( pCurrent->nUserData1 ); + } + + FS_FreeImage( pCurrent->pInfo ); + } + Mem_Free( pCurrent ); + } + + pHead->pNext = NULL; +} + +qboolean COM_CreateCustomization( customization_t *pListHead, resource_t *pResource, int playernumber, int flags, customization_t **pOut, int *nLumps ) +{ + qboolean bError = false; + int checksize = 0; + customization_t *pCust; + + if( pOut ) *pOut = NULL; + + pCust = Z_Malloc( sizeof( customization_t )); + pCust->resource = *pResource; + + if( pResource->nDownloadSize <= 0 ) + goto CustomizationError; + + pCust->bInUse = true; + + if( FBitSet( flags, FCUST_FROMHPAK )) + { + if( !HPAK_GetDataPointer( CUSTOM_RES_PATH, pResource, (byte **)&pCust->pBuffer, NULL )) + bError = true; + } + else + { + + pCust->pBuffer = FS_LoadFile( pResource->szFileName, &checksize, true ); + if( checksize != pCust->resource.nDownloadSize ) + bError = true; + } + + if( bError ) + goto CustomizationError; + + if( FBitSet( pCust->resource.ucFlags, RES_CUSTOM ) && pCust->resource.type == t_decal ) + { + pCust->resource.playernum = playernumber; + + if( CustomDecal_Validate( pCust->pBuffer, pResource->nDownloadSize )) + { + if( !FBitSet( flags, FCUST_IGNOREINIT )) + { + if( pResource->nDownloadSize >= (1 * 1024) && pResource->nDownloadSize <= ( 16 * 1024 )) + { + pCust->bTranslated = true; + pCust->nUserData1 = 0; + pCust->nUserData2 = 1; + + if( !FBitSet( flags, FCUST_WIPEDATA )) + pCust->pInfo = FS_LoadImage( "#logo.bmp", pCust->pBuffer, pCust->resource.nDownloadSize ); + else pCust->pInfo = NULL; + if( nLumps ) *nLumps = 1; + } + } + } + } + + if( pOut ) *pOut = pCust; + pCust->pNext = pListHead->pNext; + pListHead->pNext = pCust; + + return true; + +CustomizationError: + if( pCust->pBuffer ) + Mem_Free( pCust->pBuffer ); + + if( pCust->pInfo ) + Mem_Free( pCust->pInfo ); + Mem_Free( pCust ); + + return false; +} + +int COM_SizeofResourceList( resource_t *pList, resourceinfo_t *ri ) +{ + int nSize = 0; + resource_t *p; + + memset( ri, 0, sizeof( *ri )); + + for( p = pList->pNext; p != pList; p = p->pNext ) + { + nSize += p->nDownloadSize; + + if( p->type == t_model && p->nIndex == 1 ) + ri->info[t_world].size += p->nDownloadSize; + else ri->info[p->type].size += p->nDownloadSize; + } + + return nSize; +} \ No newline at end of file diff --git a/engine/common/cvar.c b/engine/common/cvar.c new file mode 100644 index 00000000..23a1010d --- /dev/null +++ b/engine/common/cvar.c @@ -0,0 +1,890 @@ +/* +cvar.c - dynamic variable tracking +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "math.h" // fabs... + +convar_t *cvar_vars = NULL; // head of list +convar_t *cmd_scripting; + +/* +============ +Cvar_FindVar + +find the specified variable by name +============ +*/ +convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ) +{ + convar_t *var; + + if( !var_name ) + return NULL; + + for( var = cvar_vars; var; var = var->next ) + { + if( ignore_group && FBitSet( ignore_group, var->flags )) + continue; + + if( !Q_stricmp( var_name, var->name )) + return var; + } + + return NULL; +} + +/* +============ +Cvar_BuildAutoDescription + +build cvar auto description that based on the setup flags +============ +*/ +const char *Cvar_BuildAutoDescription( int flags ) +{ + static char desc[128]; + + desc[0] = '\0'; + + if( FBitSet( flags, FCVAR_EXTDLL )) + Q_strncpy( desc, "game ", sizeof( desc )); + else if( FBitSet( flags, FCVAR_CLIENTDLL )) + Q_strncpy( desc, "client ", sizeof( desc )); + else if( FBitSet( flags, FCVAR_GAMEUIDLL )) + Q_strncpy( desc, "GameUI ", sizeof( desc )); + + if( FBitSet( flags, FCVAR_SERVER )) + Q_strncat( desc, "server ", sizeof( desc )); + + if( FBitSet( flags, FCVAR_USERINFO )) + Q_strncat( desc, "user ", sizeof( desc )); + + if( FBitSet( flags, FCVAR_ARCHIVE )) + Q_strncat( desc, "archived ", sizeof( desc )); + + Q_strncat( desc, "cvar", sizeof( desc )); + + return desc; +} + +/* +============ +Cvar_UpdateInfo + +deal with userinfo etc +============ +*/ +static qboolean Cvar_UpdateInfo( convar_t *var, const char *value, qboolean notify ) +{ + if( FBitSet( var->flags, FCVAR_USERINFO )) + { + if ( host.type == HOST_DEDICATED ) + { + // g-cont. this is a very strange behavior... + Info_SetValueForKey( SV_Serverinfo(), var->name, value, MAX_SERVERINFO_STRING ), + SV_BroadcastCommand( "fullserverinfo \"%s\"\n", SV_Serverinfo( )); + } + else + { + if( !Info_SetValueForKey( CL_Userinfo(), var->name, value, MAX_INFO_STRING )) + return false; // failed to change value + + // time to update server copy of userinfo + CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", var->name, value ); + } + } + + if( FBitSet( var->flags, FCVAR_SERVER ) && notify ) + { + if( !FBitSet( var->flags, FCVAR_UNLOGGED )) + { + if( FBitSet( var->flags, FCVAR_PROTECTED )) + { + Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var->name, "***PROTECTED***" ); + SV_BroadcastPrintf( NULL, "\"%s\" changed to \"%s\"\n", var->name, "***PROTECTED***" ); + } + else + { + Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var->name, value ); + SV_BroadcastPrintf( NULL, "\"%s\" changed to \"%s\"\n", var->name, value ); + } + } + } + + return true; +} + +/* +============ +Cvar_ValidateString + +deal with userinfo etc +============ +*/ +const char *Cvar_ValidateString( convar_t *var, const char *value ) +{ + const char *pszValue; + static char szNew[MAX_STRING]; + + pszValue = value; + szNew[0] = 0; + + // this cvar's string must only contain printable characters. + // strip out any other crap. we'll fill in "empty" if nothing is left + if( FBitSet( var->flags, FCVAR_PRINTABLEONLY )) + { + char *szVal = szNew; + int len = 0; + + // step through the string, only copying back in characters that are printable + while( *pszValue && len < MAX_STRING ) + { + if( ((byte)*pszValue) < 32 ) + { + pszValue++; + continue; + } + *szVal++ = *pszValue++; + len++; + } + + *szVal = '\0'; + pszValue = szNew; + + // g-cont. is this even need? + if( !Q_strlen( szNew )) Q_strncpy( szNew, "empty", sizeof( szNew )); + } + + if( FBitSet( var->flags, FCVAR_NOEXTRAWHITEPACE )) + { + char *szVal = szNew; + int len = 0; + + // step through the string, only copying back in characters that are printable + while( *pszValue && len < MAX_STRING ) + { + if( *pszValue == ' ' ) + { + pszValue++; + continue; + } + *szVal++ = *pszValue++; + len++; + } + + *szVal = '\0'; + pszValue = szNew; + } + + return pszValue; +} + +/* +============ +Cvar_UnlinkVar + +unlink the variable +============ +*/ +int Cvar_UnlinkVar( const char *var_name, int group ) +{ + int count = 0; + convar_t **prev; + convar_t *var; + + prev = &cvar_vars; + + while( 1 ) + { + var = *prev; + if( !var ) break; + + // do filter by name + if( var_name && Q_strcmp( var->name, var_name )) + { + prev = &var->next; + continue; + } + + // do filter by specified group + if( group && !FBitSet( var->flags, group )) + { + prev = &var->next; + continue; + } + + // unlink variable from list + freestring( var->string ); + *prev = var->next; + + // only allocated cvars can throw these fields + if( FBitSet( var->flags, FCVAR_ALLOCATED )) + { + freestring( var->name ); + freestring( var->def_string ); + freestring( var->desc ); + Mem_Free( var ); + } + count++; + } + + return count; +} + +/* +============ +Cvar_Changed + +Tell the engine parts about cvar changing +============ +*/ +static void Cvar_Changed( convar_t *var ) +{ + Assert( var != NULL ); + + // tell about changes + SetBits( var->flags, FCVAR_CHANGED ); + + // tell the engine parts with global state + if( FBitSet( var->flags, FCVAR_USERINFO )) + host.userinfo_changed = true; + + if( FBitSet( var->flags, FCVAR_MOVEVARS )) + host.movevars_changed = true; + + if( FBitSet( var->flags, FCVAR_VIDRESTART )) + host.renderinfo_changed = true; + + if( !Q_strcmp( var->name, "sv_cheats" )) + host.allow_cheats = Q_atoi( var->string ); +} + +/* +============ +Cvar_LookupVars +============ +*/ +void Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback ) +{ + convar_t *var; + + // nothing to process ? + if( !callback ) return; + + // force checkbit to 0 for lookup all cvars + for( var = cvar_vars; var; var = var->next ) + { + if( checkbit && !FBitSet( var->flags, checkbit )) + continue; + + if( buffer ) + { + callback( var->name, var->string, buffer, ptr ); + } + else + { + // NOTE: dlls cvars doesn't have description + if( FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) + callback( var->name, var->string, var->desc, ptr ); + else callback( var->name, var->string, "", ptr ); + } + } +} + +/* +============ +Cvar_Get + +If the variable already exists, the value will not be set +The flags will be or'ed in if the variable exists. +============ +*/ +convar_t *Cvar_Get( const char *name, const char *value, int flags, const char *var_desc ) +{ + convar_t *cur, *find, *var; + + ASSERT( name && *name ); + + // check for command coexisting + if( Cmd_Exists( name )) + { + MsgDev( D_ERROR, "can't register variable '%s', is already defined as command\n", name ); + return NULL; + } + + var = Cvar_FindVar( name ); + + if( var ) + { + // already existed? + if( FBitSet( flags, FCVAR_GLCONFIG )) + { + // NOTE: cvars without description produced by Cvar_FullSet + // which executed from the config file. So we don't need to + // change value here: we *already* have actual value from config. + // in other cases we need to rewrite them + if( Q_strcmp( var->desc, "" )) + { + // directly set value + freestring( var->string ); + var->string = copystring( value ); + var->value = Q_atof( var->string ); + SetBits( var->flags, flags ); + + // tell engine about changes + Cvar_Changed( var ); + } + } + else + { + SetBits( var->flags, flags ); + Cvar_DirectSet( var, value ); + } + + if( FBitSet( var->flags, FCVAR_ALLOCATED ) && Q_strcmp( var_desc, var->desc )) + { + if( !FBitSet( flags, FCVAR_GLCONFIG )) + MsgDev( D_REPORT, "%s change description from %s to %s\n", var->name, var->desc, var_desc ); + // update description if needs + freestring( var->desc ); + var->desc = copystring( var_desc ); + } + + return var; + } + + // allocate a new cvar + var = Z_Malloc( sizeof( *var )); + var->name = copystring( name ); + var->string = copystring( value ); + var->def_string = copystring( value ); + var->desc = copystring( var_desc ); + var->value = Q_atof( var->string ); + var->flags = flags|FCVAR_ALLOCATED; + + // link the variable in alphanumerical order + for( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next ); + + if( cur ) cur->next = var; + else cvar_vars = var; + var->next = find; + + // fill it cls.userinfo, svs.serverinfo + Cvar_UpdateInfo( var, var->string, false ); + + // tell engine about changes + Cvar_Changed( var ); + + return var; +} + +/* +============ +Cvar_RegisterVariable + +Adds a freestanding variable to the variable list. +============ +*/ +void Cvar_RegisterVariable( convar_t *var ) +{ + convar_t *cur, *find, *dup; + + ASSERT( var != NULL ); + + // first check to see if it has allready been defined + dup = Cvar_FindVar( var->name ); + + if( dup ) + { + if( !FBitSet( dup->flags, FCVAR_TEMPORARY )) + { + MsgDev( D_ERROR, "can't register variable '%s', is already defined\n", var->name ); + return; + } + + // time to replace temp variable with real + Cvar_UnlinkVar( var->name, FCVAR_TEMPORARY ); + } + + // check for overlap with a command + if( Cmd_Exists( var->name )) + { + MsgDev( D_ERROR, "can't register variable '%s', is already defined as command\n", var->name ); + return; + } + + // NOTE: all the 'long' engine cvars have an special setntinel on static declaration + // (all the engine cvars should be declared through CVAR_DEFINE macros or they shouldn't working properly anyway) + // so we can determine long version 'convar_t' and short version 'cvar_t' more reliable than by FCVAR_EXTDLL flag + if( CVAR_CHECK_SENTINEL( var )) SetBits( var->flags, FCVAR_EXTENDED ); + + // copy the value off, because future sets will free it + if( FBitSet( var->flags, FCVAR_EXTENDED )) + var->def_string = var->string; // just swap pointers + + var->string = copystring( var->string ); + var->value = Q_atof( var->string ); + + // find the supposed position in chain (alphanumerical order) + for( cur = NULL, find = cvar_vars; find && Q_strcmp( find->name, var->name ) < 0; cur = find, find = find->next ); + + // now link variable + if( cur ) cur->next = var; + else cvar_vars = var; + var->next = find; + + // fill it cls.userinfo, svs.serverinfo + Cvar_UpdateInfo( var, var->string, false ); + + // tell engine about changes + Cvar_Changed( var ); +} + +/* +============ +Cvar_DirectSet + +way to change value for many cvars +============ +*/ +void Cvar_DirectSet( convar_t *var, const char *value ) +{ + const char *pszValue; + + if( !var ) return; // ??? + + // lookup for registration + if( CVAR_CHECK_SENTINEL( var ) || ( var->next == NULL && !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED ))) + { + // need to registering cvar fisrt + MsgDev( D_WARN, "Cvar_DirectSet: called for unregistered cvar '%s'\n", var->name ); + Cvar_RegisterVariable( var ); // ok, register it + } + + // lookup for registration again + if( var != Cvar_FindVar( var->name )) + { + MsgDev( D_ERROR, "Cvar_DirectSet: couldn't find cvar '%s' in linked list\n", var->name ); + return; + } + + if( FBitSet( var->flags, FCVAR_READ_ONLY|FCVAR_GLCONFIG )) + { + Con_Printf( "%s is read-only.\n", var->name ); + return; + } + + if( FBitSet( var->flags, FCVAR_CHEAT ) && !host.allow_cheats ) + { + Con_Printf( "%s is cheat protected.\n", var->name ); + return; + } + + // just tell user about deferred changes + if( FBitSet( var->flags, FCVAR_LATCH ) && ( SV_Active() || CL_Active( ))) + Con_Printf( "%s will be changed upon restarting.\n", var->name ); + + // check value + if( !value ) + { + if( !FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) + { + Con_Printf( "%s has no default value and can't be reset.\n", var->name ); + return; + } + + value = var->def_string; // reset to default value + } + + pszValue = Cvar_ValidateString( var, value ); + + // nothing to change + if( !Q_strcmp( pszValue, var->string )) + return; + + // fill it cls.userinfo, svs.serverinfo + if( !Cvar_UpdateInfo( var, pszValue, true )) + return; + + // and finally changed the cvar itself + freestring( var->string ); + var->string = copystring( pszValue ); + var->value = Q_atof( var->string ); + + // tell engine about changes + Cvar_Changed( var ); +} + +/* +============ +Cvar_FullSet + +can set any protected cvars +============ +*/ +void Cvar_FullSet( const char *var_name, const char *value, int flags ) +{ + convar_t *var = Cvar_FindVar( var_name ); + + if( !var ) + { + Cvar_Get( var_name, value, flags, "" ); + return; + } + + freestring( var->string ); + var->string = copystring( value ); + var->value = Q_atof( var->string ); + SetBits( var->flags, flags ); + + // tell engine about changes + Cvar_Changed( var ); +} + +/* +============ +Cvar_Set +============ +*/ +void Cvar_Set( const char *var_name, const char *value ) +{ + convar_t *var = Cvar_FindVar( var_name ); + + if( !var ) + { + // there is an error in C code if this happens + if( host.type != HOST_DEDICATED ) + MsgDev( D_ERROR, "Cvar_Set: variable '%s' not found\n", var_name ); + return; + } + + Cvar_DirectSet( var, value ); +} + +/* +============ +Cvar_SetValue +============ +*/ +void Cvar_SetValue( const char *var_name, float value ) +{ + char val[32]; + + if( fabs( value - (int)value ) < 0.000001 ) + Q_snprintf( val, sizeof( val ), "%d", (int)value ); + else Q_snprintf( val, sizeof( val ), "%f", value ); + + Cvar_Set( var_name, val ); +} + +/* +============ +Cvar_Reset +============ +*/ +void Cvar_Reset( const char *var_name ) +{ + Cvar_Set( var_name, NULL ); +} + +/* +============ +Cvar_VariableValue +============ +*/ +float Cvar_VariableValue( const char *var_name ) +{ + convar_t *var; + + var = Cvar_FindVar( var_name ); + if( !var ) return 0.0f; + + return Q_atof( var->string ); +} + +/* +============ +Cvar_VariableInteger +============ +*/ +int Cvar_VariableInteger( const char *var_name ) +{ + convar_t *var; + + var = Cvar_FindVar( var_name ); + if( !var ) return 0; + + return Q_atoi( var->string ); +} + +/* +============ +Cvar_VariableString +============ +*/ +char *Cvar_VariableString( const char *var_name ) +{ + convar_t *var; + + var = Cvar_FindVar( var_name ); + if( !var ) return ""; + + return var->string; +} + +/* +============ +Cvar_SetCheatState + +Any testing variables will be reset to the safe values +============ +*/ +void Cvar_SetCheatState( void ) +{ + convar_t *var; + + // set all default vars to the safe value + for( var = cvar_vars; var; var = var->next ) + { + // can't process dll cvars - missed def_string + if( !FBitSet( var->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) + continue; + + if( FBitSet( var->flags, FCVAR_CHEAT )) + { + if( Q_strcmp( var->def_string, var->string )) + Cvar_DirectSet( var, var->def_string ); + } + } +} + +/* +============ +Cvar_Command + +Handles variable inspection and changing from the console +============ +*/ +qboolean Cvar_Command( void ) +{ + convar_t *v; + + // check variables + v = Cvar_FindVar( Cmd_Argv( 0 )); + if( !v ) return false; + + // perform a variable print or set + if( Cmd_Argc() == 1 ) + { + if( FBitSet( v->flags, FCVAR_ALLOCATED|FCVAR_EXTENDED )) + Con_Printf( "\"%s\" is \"%s\" ( ^3\"%s\"^7 )\n", v->name, v->string, v->def_string ); + else Con_Printf( "\"%s\" is \"%s\"\n", v->name, v->string ); + + return true; + } + + if( host.apply_game_config ) + { + if( !FBitSet( v->flags, FCVAR_EXTDLL )) + return true; // only game.dll cvars passed + } + + if( FBitSet( v->flags, FCVAR_SPONLY ) && CL_GetMaxClients() > 1 ) + { + Con_Printf( "can't set \"%s\" in multiplayer\n", v->name ); + return false; + } + else + { + Cvar_DirectSet( v, Cmd_Argv( 1 )); + if( host.apply_game_config ) + host.sv_cvars_restored++; + return true; + } +} + +/* +============ +Cvar_WriteVariables + +Writes lines containing "variable value" for all variables +with the specified flag set to true. +============ +*/ +void Cvar_WriteVariables( file_t *f, int group ) +{ + convar_t *var; + + for( var = cvar_vars; var; var = var->next ) + { + if( FBitSet( var->flags, group )) + FS_Printf( f, "%s \"%s\"\n", var->name, var->string ); + } +} + +/* +============ +Cvar_Toggle_f + +Toggles a cvar for easy single key binding +============ +*/ +void Cvar_Toggle_f( void ) +{ + int v; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "toggle \n" ); + return; + } + + v = !Cvar_VariableInteger( Cmd_Argv( 1 )); + + Cvar_Set( Cmd_Argv( 1 ), va( "%i", v )); +} + +/* +============ +Cvar_SetR_f + +keep for legacy configs +============ +*/ +void Cvar_SetR_f( void ) +{ + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "setr \n" ); + return; + } + + Cvar_Set( Cmd_Argv( 1 ), Cmd_Argv( 2 )); +} + +/* +============ +Cvar_SetGL_f + +As Cvar_Set, but also flags it as glconfig +============ +*/ +void Cvar_SetGL_f( void ) +{ + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "setgl \n" ); + return; + } + + Cvar_FullSet( Cmd_Argv( 1 ), Cmd_Argv( 2 ), FCVAR_GLCONFIG ); +} + +/* +============ +Cvar_Reset_f +============ +*/ +void Cvar_Reset_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "reset \n" ); + return; + } + + Cvar_Reset( Cmd_Argv( 1 )); +} + +/* +============ +Cvar_List_f +============ +*/ +void Cvar_List_f( void ) +{ + convar_t *var; + char *match = NULL; + char *value; + int count = 0; + + if( Cmd_Argc() > 1 ) + match = Cmd_Argv( 1 ); + + for( var = cvar_vars; var; var = var->next ) + { + if( var->name[0] == '@' ) + continue; // never shows system cvars + + if( match && !Q_stricmpext( match, var->name )) + continue; + + if( Q_colorstr( var->string )) + value = va( "\"%s\"", var->string ); + else value = va( "\"^2%s^7\"", var->string ); + + if( FBitSet( var->flags, FCVAR_EXTENDED|FCVAR_ALLOCATED )) + Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, var->desc ); + else Con_Printf( " %-*s %s ^3%s^7\n", 32, var->name, value, Cvar_BuildAutoDescription( var->flags )); + + count++; + } + + Con_Printf( "\n%i cvars\n", count ); +} + +/* +============ +Cvar_Unlink + +unlink all cvars with specified flag +============ +*/ +void Cvar_Unlink( int group ) +{ + int count; + + if( Cvar_VariableInteger( "host_gameloaded" ) && FBitSet( group, FCVAR_EXTDLL )) + return; + + if( Cvar_VariableInteger( "host_clientloaded" ) && FBitSet( group, FCVAR_CLIENTDLL )) + return; + + if( Cvar_VariableInteger( "host_gameuiloaded" ) && FBitSet( group, FCVAR_GAMEUIDLL )) + return; + + count = Cvar_UnlinkVar( NULL, group ); + Con_Reportf( "unlink %i cvars\n", count ); +} + +/* +============ +Cvar_Init + +Reads in all archived cvars +============ +*/ +void Cvar_Init( void ) +{ + cvar_vars = NULL; + cmd_scripting = Cvar_Get( "cmd_scripting", "0", FCVAR_ARCHIVE, "enable simple condition checking and variable operations" ); + Cvar_RegisterVariable (&host_developer); // early registering for dev + + Cmd_AddCommand( "setr", Cvar_SetR_f, "create or change the value of a renderinfo variable" ); + Cmd_AddCommand( "setgl", Cvar_SetGL_f, "create or change the value of a opengl variable" ); + Cmd_AddCommand( "toggle", Cvar_Toggle_f, "toggles a console variable's values (use for more info)" ); + Cmd_AddCommand( "reset", Cvar_Reset_f, "reset any type variable to initial value" ); + Cmd_AddCommand( "cvarlist", Cvar_List_f, "display all console variables beginning with the specified prefix" ); +} \ No newline at end of file diff --git a/engine/common/cvar.h b/engine/common/cvar.h new file mode 100644 index 00000000..e190b109 --- /dev/null +++ b/engine/common/cvar.h @@ -0,0 +1,71 @@ +/* +cvar.h - dynamic variable tracking +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef CVAR_H +#define CVAR_H + +#include "cvardef.h" + +#define CVAR_SENTINEL 0xDEADBEEF +#define CVAR_CHECK_SENTINEL( cv ) ((uint)(cv)->next == CVAR_SENTINEL) + +// NOTE: if this is changed, it must be changed in cvardef.h too +typedef struct convar_s +{ + // this part shared with cvar_t + char *name; + char *string; + int flags; + float value; + struct convar_s *next; + + // this part unique for convar_t + char *desc; // variable descrition info + char *def_string; // keep pointer to initial value +} convar_t; + +// cvar internal flags +#define FCVAR_RENDERINFO (1<<16) // save to a seperate config called video.cfg +#define FCVAR_READ_ONLY (1<<17) // cannot be set by user at all, and can't be requested by CvarGetPointer from game dlls +#define FCVAR_EXTENDED (1<<18) // this is convar_t (sets on registration) +#define FCVAR_ALLOCATED (1<<19) // this convar_t is fully dynamic allocated (include description) +#define FCVAR_VIDRESTART (1<<20) // recreate the window is cvar with this flag was changed +#define FCVAR_TEMPORARY (1<<21) // these cvars holds their values and can be unlink in any time + +#define CVAR_DEFINE( cv, cvname, cvstr, cvflags, cvdesc ) convar_t cv = { cvname, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } +#define CVAR_DEFINE_AUTO( cv, cvstr, cvflags, cvdesc ) convar_t cv = { #cv, cvstr, cvflags, 0.0f, (void *)CVAR_SENTINEL, cvdesc } +#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) + +#define Cvar_FindVar( name ) Cvar_FindVarExt( name, 0 ) +convar_t *Cvar_FindVarExt( const char *var_name, int ignore_group ); +void Cvar_RegisterVariable( convar_t *var ); +convar_t *Cvar_Get( const char *var_name, const char *value, int flags, const char *description ); +void Cvar_LookupVars( int checkbit, void *buffer, void *ptr, setpair_t callback ); +void Cvar_FullSet( const char *var_name, const char *value, int flags ); +void Cvar_DirectSet( convar_t *var, const char *value ); +void Cvar_Set( const char *var_name, const char *value ); +void Cvar_SetValue( const char *var_name, float value ); +const char *Cvar_BuildAutoDescription( int flags ); +float Cvar_VariableValue( const char *var_name ); +int Cvar_VariableInteger( const char *var_name ); +char *Cvar_VariableString( const char *var_name ); +void Cvar_WriteVariables( file_t *f, int group ); +void Cvar_Reset( const char *var_name ); +void Cvar_SetCheatState( void ); +qboolean Cvar_Command( void ); +void Cvar_Init( void ); +void Cvar_Unlink( int group ); + +#endif//CVAR_H \ No newline at end of file diff --git a/engine/common/filesystem.c b/engine/common/filesystem.c new file mode 100644 index 00000000..2093b981 --- /dev/null +++ b/engine/common/filesystem.c @@ -0,0 +1,3203 @@ +/* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include +#include +#include +#include +#include +#include "common.h" +#include "wadfile.h" +#include "filesystem.h" +#include "library.h" +#include "mathlib.h" +#include "protocol.h" + +#define FILE_COPY_SIZE (1024 * 1024) +#define FILE_BUFF_SIZE (65535) + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct wadtype_s +{ + char *ext; + char type; +} wadtype_t; + +typedef struct file_s +{ + int handle; // file descriptor + long real_length; // uncompressed file size (for files opened in "read" mode) + long position; // current position in the file + long offset; // offset into the package (0 if external file) + int ungetc; // single stored character from ungetc, cleared to EOF when read + time_t filetime; // pak, wad or real filetime + // contents buffer + long buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +}; + +typedef struct wfile_s +{ + string filename; + int infotableofs; + byte *mempool; // W_ReadLump temp buffers + int numlumps; + file_t *handle; + dlumpinfo_t *lumps; + time_t filetime; +}; + +typedef struct pack_s +{ + string filename; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +} pack_t; + +typedef struct searchpath_s +{ + string filename; + pack_t *pack; + wfile_t *wad; + int flags; + struct searchpath_s *next; +} searchpath_t; + +byte *fs_mempool; +searchpath_t *fs_searchpaths = NULL; // chain +searchpath_t fs_directpath; // static direct path +char fs_basedir[MAX_SYSPATH]; // base game directory +char fs_gamedir[MAX_SYSPATH]; // game current directory +char fs_writedir[MAX_SYSPATH]; // path that game allows to overwrite, delete and rename files (and create new of course) +qboolean fs_ext_path = false; // attempt to read\write from ./ or ../ pathes +static const wadtype_t wad_hints[10]; + +static void FS_InitMemory( void ); +static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ); +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const char matchtype ); +static dpackfile_t *FS_AddFileToPack( const char* name, pack_t *pack, long offset, long size ); +static byte *W_LoadFile( const char *path, long *filesizeptr, qboolean gamedironly ); +static wfile_t *W_Open( const char *filename, int *errorcode ); +static qboolean FS_SysFileExists( const char *path ); +static qboolean FS_SysFolderExists( const char *path ); +static long FS_SysFileTime( const char *filename ); +static char W_TypeFromExt( const char *lumpname ); +static const char *W_ExtFromType( char lumptype ); +static void FS_Purge( file_t* file ); + +/* +============================================================================= + +FILEMATCH COMMON SYSTEM + +============================================================================= +*/ +static int matchpattern( const char *str, const char *cmp, qboolean caseinsensitive ) +{ + int c1, c2; + + while( *cmp ) + { + switch( *cmp ) + { + case 0: return 1; // end of pattern + case '?': // match any single character + if( *str == 0 || *str == '/' || *str == '\\' || *str == ':' ) + return 0; // no match + str++; + cmp++; + break; + case '*': // match anything until following string + if( !*str ) return 1; // match + cmp++; + while( *str ) + { + if( *str == '/' || *str == '\\' || *str == ':' ) + break; + // see if pattern matches at this offset + if( matchpattern( str, cmp, caseinsensitive )) + return 1; + // nope, advance to next offset + str++; + } + break; + default: + if( *str != *cmp ) + { + if( !caseinsensitive ) + return 0; // no match + c1 = Q_tolower( *str ); + c2 = Q_tolower( *cmp ); + if( c1 != c2 ) return 0; // no match + } + + str++; + cmp++; + break; + } + } + + // reached end of pattern but not end of input? + return (*str) ? 0 : 1; +} + +static void stringlistinit( stringlist_t *list ) +{ + memset( list, 0, sizeof( *list )); +} + +static void stringlistfreecontents( stringlist_t *list ) +{ + int i; + + for( i = 0; i < list->numstrings; i++ ) + { + if( list->strings[i] ) + Mem_Free( list->strings[i] ); + list->strings[i] = NULL; + } + + if( list->strings ) + Mem_Free( list->strings ); + + list->numstrings = 0; + list->maxstrings = 0; + list->strings = NULL; +} + +static void stringlistappend( stringlist_t *list, char *text ) +{ + size_t textlen; + + if( !Q_stricmp( text, "." ) || !Q_stricmp( text, ".." )) + return; // ignore the virtual directories + + if( list->numstrings >= list->maxstrings ) + { + list->maxstrings += 4096; + list->strings = Mem_Realloc( fs_mempool, list->strings, list->maxstrings * sizeof( *list->strings )); + } + + textlen = Q_strlen( text ) + 1; + list->strings[list->numstrings] = Mem_Alloc( fs_mempool, textlen ); + memcpy( list->strings[list->numstrings], text, textlen ); + list->numstrings++; +} + +static void stringlistsort( stringlist_t *list ) +{ + char *temp; + int i, j; + + // this is a selection sort (finds the best entry for each slot) + for( i = 0; i < list->numstrings - 1; i++ ) + { + for( j = i + 1; j < list->numstrings; j++ ) + { + if( Q_strcmp( list->strings[i], list->strings[j] ) > 0 ) + { + temp = list->strings[i]; + list->strings[i] = list->strings[j]; + list->strings[j] = temp; + } + } + } +} + +// convert names to lowercase because windows doesn't care, but pattern matching code often does +static void listlowercase( stringlist_t *list ) +{ + char *c; + int i; + + for( i = 0; i < list->numstrings; i++ ) + { + for( c = list->strings[i]; *c; c++ ) + *c = Q_tolower( *c ); + } +} + +static void listdirectory( stringlist_t *list, const char *path ) +{ + char pattern[4096]; + struct _finddata_t n_file; + long hFile; + + Q_strncpy( pattern, path, sizeof( pattern )); + Q_strncat( pattern, "*", sizeof( pattern )); + + // ask for the directory listing handle + hFile = _findfirst( pattern, &n_file ); + if( hFile == -1 ) return; + + // start a new chain with the the first name + stringlistappend( list, n_file.name ); + + // iterate through the directory + while( _findnext( hFile, &n_file ) == 0 ) + stringlistappend( list, n_file.name ); + _findclose( hFile ); + + // g-cont. disabled for some reasons +// listlowercase( list ); +} + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, long offset, long size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) MsgDev( D_WARN, "package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +============ +FS_CreatePath + +Only used for FS_Open. +============ +*/ +void FS_CreatePath( char *path ) +{ + char *ofs, save; + + for( ofs = path + 1; *ofs; ofs++ ) + { + if( *ofs == '/' || *ofs == '\\' ) + { + // create the directory + save = *ofs; + *ofs = 0; + _mkdir( path ); + *ofs = save; + } + } +} + +/* +============ +FS_Path_f + +debug info +============ +*/ +void FS_Path_f( void ) +{ + searchpath_t *s; + + Con_Printf( "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + if( s->pack ) Con_Printf( "%s (%i files)", s->pack->filename, s->pack->numfiles ); + else if( s->wad ) Con_Printf( "%s (%i files)", s->wad->filename, s->wad->numlumps ); + else Con_Printf( "%s", s->filename ); + + if( FBitSet( s->flags, FS_GAMEDIR_PATH )) + Con_Printf( " ^2gamedir^7\n" ); + else Con_Printf( "\n" ); + } +} + +/* +============ +FS_ClearPath_f + +only for debug targets +============ +*/ +void FS_ClearPaths_f( void ) +{ + FS_ClearSearchPath(); +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + + if( packhandle < 0 ) + { + MsgDev( D_NOTE, "%s couldn't open\n", packfile ); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + read( packhandle, (void *)&header, sizeof( header )); + + if( header.ident != IDPACKV1HEADER ) + { + MsgDev( D_NOTE, "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + MsgDev( D_ERROR, "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + MsgDev( D_ERROR, "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + MsgDev( D_NOTE, "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Alloc( fs_mempool, sizeof( *info ) * numpackfiles ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + MsgDev( D_NOTE, "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info ); + return NULL; + } + + pack = (pack_t *)Mem_Alloc( fs_mempool, sizeof( pack_t )); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Alloc( fs_mempool, numpackfiles * sizeof( dpackfile_t )); + pack->filetime = FS_SysFileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info ); + + return pack; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +static qboolean FS_AddWad_Fullpath( const char *wadfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + if( !Q_stricmp( ext, "wad" )) wad = W_Open( wadfile, &errorcode ); + else MsgDev( D_ERROR, "\"%s\" doesn't have a wad extension\n", wadfile ); + + if( wad ) + { + search = (searchpath_t *)Mem_Alloc( fs_mempool, sizeof( searchpath_t )); + search->wad = wad; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + MsgDev( D_REPORT, "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + MsgDev( D_ERROR, "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +static qboolean FS_AddPak_Fullpath( const char *pakfile, qboolean *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) pak = FS_LoadPackPAK( pakfile, &errorcode ); + else MsgDev( D_ERROR, "\"%s\" does not have a pack extension\n", pakfile ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Alloc( fs_mempool, sizeof( searchpath_t )); + search->pack = pak; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + MsgDev( D_REPORT, "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_sprintf( fullpath, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + MsgDev( D_ERROR, "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +/* +================ +FS_AddGameDirectory + +Sets fs_writedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ +*/ +void FS_AddGameDirectory( const char *dir, int flags ) +{ + stringlist_t list; + searchpath_t *search; + string fullpath; + int i; + + if( !FBitSet( flags, FS_NOWRITE_PATH )) + Q_strncpy( fs_writedir, dir, sizeof( fs_writedir )); + + stringlistinit( &list ); + listdirectory( &list, dir ); + stringlistsort( &list ); + + // add any PAK package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddPak_Fullpath( fullpath, NULL, flags ); + } + } + + FS_AllowDirectPaths( true ); + + // add any WAD package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + stringlistfreecontents( &list ); + FS_AllowDirectPaths( false ); + + // add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Alloc( fs_mempool, sizeof( searchpath_t )); + Q_strncpy( search->filename, dir, sizeof ( search->filename )); + search->next = fs_searchpaths; + search->flags = flags; + fs_searchpaths = search; +} + +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, int flags ) +{ + // Add the common game directory + if( COM_CheckString( dir )) + FS_AddGameDirectory( va( "%s/", dir ), flags ); +} + +/* +================ +FS_ClearSearchPath +================ +*/ +void FS_ClearSearchPath( void ) +{ + while( fs_searchpaths ) + { + searchpath_t *search = fs_searchpaths; + + if( !search ) break; + + if( FBitSet( search->flags, FS_STATIC_PATH )) + { + // skip read-only pathes + if( search->next ) + fs_searchpaths = search->next->next; + else break; + } + else fs_searchpaths = search->next; + + if( search->pack ) + { + if( search->pack->files ) + Mem_Free( search->pack->files ); + Mem_Free( search->pack ); + } + + if( search->wad ) + { + W_Close( search->wad ); + } + + Mem_Free( search ); + } +} + +/* +==================== +FS_CheckNastyPath + +Return true if the path should be rejected due to one of the following: +1: path elements that are non-portable +2: path elements that would allow access to files outside the game directory, + or are just not a good idea for a mod to be using. +==================== +*/ +int FS_CheckNastyPath( const char *path, qboolean isgamedir ) +{ + // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless + if( !COM_CheckString( path )) return 2; + + if( fs_ext_path ) return 0; // allow any path + + // Mac: don't allow Mac-only filenames - : is a directory separator + // instead of /, but we rely on / working already, so there's no reason to + // support a Mac-only path + // Amiga and Windows: : tries to go to root of drive + if( Q_strstr( path, ":" )) return 1; // non-portable attempt to go to root of drive + + // Amiga: // is parent directory + if( Q_strstr( path, "//" )) return 1; // non-portable attempt to go to parent directory + + // all: don't allow going to parent directory (../ or /../) + if( Q_strstr( path, ".." )) return 2; // attempt to go outside the game directory + + // Windows and UNIXes: don't allow absolute paths + if( path[0] == '/' ) return 2; // attempt to go outside the game directory + + // all: forbid trailing slash on gamedir + if( isgamedir && path[Q_strlen( path )-1] == '/' ) return 2; + + // all: forbid leading dot on any filename for any reason + if( Q_strstr( path, "/." )) return 2; // attempt to go outside the game directory + + // after all these checks we're pretty sure it's a / separated filename + // and won't do much if any harm + return 0; +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + MsgDev( D_NOTE, "FS_Rescan( %s )\n", GI->title ); + + FS_ClearSearchPath(); + + if( Q_stricmp( GI->basedir, GI->gamedir )) + FS_AddGameHierarchy( GI->basedir, 0 ); + if( Q_stricmp( GI->basedir, GI->falldir ) && Q_stricmp( GI->gamedir, GI->falldir )) + FS_AddGameHierarchy( GI->falldir, 0 ); + FS_AddGameHierarchy( GI->gamedir, FS_GAMEDIR_PATH ); + + if( FS_FileExists( va( "%s.rc", SI.basedirName ), false )) + Q_strncpy( SI.rcName, SI.basedirName, sizeof( SI.rcName )); // e.g. valve.rc + else Q_strncpy( SI.rcName, SI.exeName, sizeof( SI.rcName )); // e.g. quake.rc +} + +/* +================ +FS_Rescan_f +================ +*/ +void FS_Rescan_f( void ) +{ + FS_Rescan(); +} + +/* +================ +FS_WriteGameInfo + +assume GameInfo is valid +================ +*/ +static void FS_WriteGameInfo( const char *filepath, gameinfo_t *GameInfo ) +{ + file_t *f = FS_Open( filepath, "w", false ); // we in binary-mode + if( !f ) Sys_Error( "FS_WriteGameInfo: can't write %s\n", filepath ); // may be disk-space is out? + + FS_Print( f, "// generated by Xash3D\n\n\n" ); + + if( Q_strlen( GameInfo->basedir )) + FS_Printf( f, "basedir\t\t\"%s\"\n", GameInfo->basedir ); + + if( Q_strlen( GameInfo->gamedir )) + FS_Printf( f, "gamedir\t\t\"%s\"\n", GameInfo->gamedir ); + + if( Q_strlen( GameInfo->falldir )) + FS_Printf( f, "fallback_dir\t\"%s\"\n", GameInfo->falldir ); + + if( Q_strlen( GameInfo->title )) + FS_Printf( f, "title\t\t\"%s\"\n", GameInfo->title ); + + if( Q_strlen( GameInfo->startmap )) + FS_Printf( f, "startmap\t\t\"%s\"\n", GameInfo->startmap ); + + if( Q_strlen( GameInfo->trainmap )) + FS_Printf( f, "trainmap\t\t\"%s\"\n", GameInfo->trainmap ); + + if( GameInfo->version != 0.0f ) + FS_Printf( f, "version\t\t%g\n", GameInfo->version ); + + if( GameInfo->size != 0 ) + FS_Printf( f, "size\t\t%i\n", GameInfo->size ); + + if( Q_strlen( GameInfo->game_url )) + FS_Printf( f, "url_info\t\t\"%s\"\n", GameInfo->game_url ); + + if( Q_strlen( GameInfo->update_url )) + FS_Printf( f, "url_update\t\t\"%s\"\n", GameInfo->update_url ); + + if( Q_strlen( GameInfo->type )) + FS_Printf( f, "type\t\t\"%s\"\n", GameInfo->type ); + + if( Q_strlen( GameInfo->date )) + FS_Printf( f, "date\t\t\"%s\"\n", GameInfo->date ); + + if( Q_strlen( GameInfo->dll_path )) + FS_Printf( f, "dllpath\t\t\"%s\"\n", GameInfo->dll_path ); + if( Q_strlen( GameInfo->game_dll )) + FS_Printf( f, "gamedll\t\t\"%s\"\n", GameInfo->game_dll ); + + if( Q_strlen( GameInfo->iconpath )) + FS_Printf( f, "icon\t\t\"%s\"\n", GameInfo->iconpath ); + + switch( GameInfo->gamemode ) + { + case 1: FS_Print( f, "gamemode\t\t\"singleplayer_only\"\n" ); break; + case 2: FS_Print( f, "gamemode\t\t\"multiplayer_only\"\n" ); break; + } + + if( Q_strlen( GameInfo->sp_entity )) + FS_Printf( f, "sp_entity\t\t\"%s\"\n", GameInfo->sp_entity ); + if( Q_strlen( GameInfo->mp_entity )) + FS_Printf( f, "mp_entity\t\t\"%s\"\n", GameInfo->mp_entity ); + + if( GameInfo->secure ) + FS_Printf( f, "secure\t\t\"%i\"\n", GameInfo->secure ); + + if( GameInfo->nomodels ) + FS_Printf( f, "nomodels\t\t\"%i\"\n", GameInfo->nomodels ); + + if( GameInfo->max_edicts > 0 ) + FS_Printf( f, "max_edicts\t%i\n", GameInfo->max_edicts ); + if( GameInfo->max_tents > 0 ) + FS_Printf( f, "max_tempents\t%i\n", GameInfo->max_tents ); + if( GameInfo->max_beams > 0 ) + FS_Printf( f, "max_beams\t\t%i\n", GameInfo->max_beams ); + if( GameInfo->max_particles > 0 ) + FS_Printf( f, "max_particles\t%i\n", GameInfo->max_particles ); + + FS_Print( f, "\n\n\n" ); + FS_Close( f ); // all done +} + +/* +================ +FS_CreateDefaultGameInfo +================ +*/ +void FS_CreateDefaultGameInfo( const char *filename ) +{ + gameinfo_t defGI; + + memset( &defGI, 0, sizeof( defGI )); + + // setup default values + defGI.max_edicts = 900; // default value if not specified + defGI.max_tents = 500; + defGI.max_beams = 128; + defGI.max_particles = 4096; + defGI.version = 1.0; + defGI.falldir[0] = '\0'; + + Q_strncpy( defGI.title, "New Game", sizeof( defGI.title )); + Q_strncpy( defGI.gamedir, fs_gamedir, sizeof( defGI.gamedir )); + Q_strncpy( defGI.basedir, fs_basedir, sizeof( defGI.basedir )); + Q_strncpy( defGI.sp_entity, "info_player_start", sizeof( defGI.sp_entity )); + Q_strncpy( defGI.mp_entity, "info_player_deathmatch", sizeof( defGI.mp_entity )); + Q_strncpy( defGI.dll_path, "cl_dlls", sizeof( defGI.dll_path )); + Q_strncpy( defGI.game_dll, "dlls/hl.dll", sizeof( defGI.game_dll )); + Q_strncpy( defGI.startmap, "newmap", sizeof( defGI.startmap )); + Q_strncpy( defGI.iconpath, "game.ico", sizeof( defGI.iconpath )); + + // make simple gameinfo.txt + FS_WriteGameInfo( filename, &defGI ); +} + +/* +================ +FS_ParseLiblistGam +================ +*/ +static qboolean FS_ParseLiblistGam( const char *filename, const char *gamedir, gameinfo_t *GameInfo ) +{ + char *afile, *pfile; + string token; + + if( !GameInfo ) return false; + afile = FS_LoadFile( filename, NULL, false ); + if( !afile ) return false; + + // setup default values + GameInfo->max_edicts = 900; // default value if not specified + GameInfo->max_tents = 500; + GameInfo->max_beams = 128; + GameInfo->max_particles = 4096; + GameInfo->version = 1.0f; + GameInfo->falldir[0] = '\0'; + + Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + Q_strncpy( GameInfo->gamedir, gamedir, sizeof( GameInfo->gamedir )); + Q_strncpy( GameInfo->basedir, fs_basedir, sizeof( GameInfo->basedir )); + Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity )); + Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity )); + Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->startmap, "newmap", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_stricmp( token, "game" )) + { + pfile = COM_ParseFile( pfile, GameInfo->title ); + } + if( !Q_stricmp( token, "gamedir" )) + { + pfile = COM_ParseFile( pfile, GameInfo->gamedir ); + } + if( !Q_stricmp( token, "fallback_dir" )) + { + pfile = COM_ParseFile( pfile, GameInfo->falldir ); + } + else if( !Q_stricmp( token, "startmap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->startmap ); + COM_StripExtension( GameInfo->startmap ); // HQ2:Amen has extension .bsp + } + else if( !Q_stricmp( token, "trainmap" ) || !Q_stricmp( token, "trainingmap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->trainmap ); + COM_StripExtension( GameInfo->trainmap ); // HQ2:Amen has extension .bsp + } + else if( !Q_stricmp( token, "url_info" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_url ); + } + else if( !Q_stricmp( token, "url_dl" )) + { + pfile = COM_ParseFile( pfile, GameInfo->update_url ); + } + else if( !Q_stricmp( token, "gamedll" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll ); + COM_FixSlashes( GameInfo->game_dll ); + } + else if( !Q_stricmp( token, "icon" )) + { + pfile = COM_ParseFile( pfile, GameInfo->iconpath ); + COM_FixSlashes( GameInfo->iconpath ); + COM_DefaultExtension( GameInfo->iconpath, ".ico" ); + } + else if( !Q_stricmp( token, "type" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "singleplayer_only" )) + { + GameInfo->gamemode = 1; + Q_strncpy( GameInfo->type, "Single", sizeof( GameInfo->type )); + } + else if( !Q_stricmp( token, "multiplayer_only" )) + { + GameInfo->gamemode = 2; + Q_strncpy( GameInfo->type, "Multiplayer", sizeof( GameInfo->type )); + } + else + { + // pass type without changes + GameInfo->gamemode = 0; + Q_strncpy( GameInfo->type, token, sizeof( GameInfo->type )); + } + } + else if( !Q_stricmp( token, "version" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->version = Q_atof( token ); + } + else if( !Q_stricmp( token, "size" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->size = Q_atoi( token ); + } + else if( !Q_stricmp( token, "mpentity" )) + { + pfile = COM_ParseFile( pfile, GameInfo->mp_entity ); + } + else if( !Q_stricmp( token, "secure" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->secure = Q_atoi( token ); + } + else if( !Q_stricmp( token, "nomodels" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->nomodels = Q_atoi( token ); + } + } + + if( !FS_SysFolderExists( va( "%s\\%s", host.rootdir, GameInfo->gamedir ))) + Q_strncpy( GameInfo->gamedir, gamedir, sizeof( GameInfo->gamedir )); + + if( !FS_SysFolderExists( va( "%s\\%s", host.rootdir, GameInfo->falldir ))) + GameInfo->falldir[0] = '\0'; + + Mem_Free( afile ); + + return true; +} + +/* +================ +FS_ConvertGameInfo +================ +*/ +void FS_ConvertGameInfo( const char *gamedir, const char *gameinfo_path, const char *liblist_path ) +{ + gameinfo_t GameInfo; + + memset( &GameInfo, 0, sizeof( GameInfo )); + + if( FS_ParseLiblistGam( liblist_path, gamedir, &GameInfo )) + { + Con_DPrintf( "Convert %s to %s\n", liblist_path, gameinfo_path ); + FS_WriteGameInfo( gameinfo_path, &GameInfo ); + } +} + +/* +================ +FS_ReadGameInfo +================ +*/ +static qboolean FS_ReadGameInfo( const char *filepath, const char *gamedir, gameinfo_t *GameInfo ) +{ + char *afile, *pfile; + char token[1204]; + string fs_path; + + afile = FS_LoadFile( filepath, NULL, false ); + if( !afile ) return false; + + // setup default values + Q_strncpy( GameInfo->gamefolder, gamedir, sizeof( GameInfo->gamefolder )); + GameInfo->max_edicts = 900; // default value if not specified + GameInfo->max_tents = 500; + GameInfo->max_beams = 128; + GameInfo->max_particles = 4096; + GameInfo->version = 1.0f; + GameInfo->falldir[0] = '\0'; + + Q_strncpy( GameInfo->title, "New Game", sizeof( GameInfo->title )); + Q_strncpy( GameInfo->sp_entity, "info_player_start", sizeof( GameInfo->sp_entity )); + Q_strncpy( GameInfo->mp_entity, "info_player_deathmatch", sizeof( GameInfo->mp_entity )); + Q_strncpy( GameInfo->dll_path, "cl_dlls", sizeof( GameInfo->dll_path )); + Q_strncpy( GameInfo->game_dll, "dlls/hl.dll", sizeof( GameInfo->game_dll )); + Q_strncpy( GameInfo->startmap, "", sizeof( GameInfo->startmap )); + Q_strncpy( GameInfo->iconpath, "game.ico", sizeof( GameInfo->iconpath )); + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_stricmp( token, "basedir" )) + { + pfile = COM_ParseFile( pfile, fs_path ); + if( Q_stricmp( fs_path, GameInfo->basedir ) || Q_stricmp( fs_path, GameInfo->gamedir )) + Q_strncpy( GameInfo->basedir, fs_path, sizeof( GameInfo->basedir )); + } + else if( !Q_stricmp( token, "fallback_dir" )) + { + pfile = COM_ParseFile( pfile, fs_path ); + if( Q_stricmp( fs_path, GameInfo->basedir ) || Q_stricmp( fs_path, GameInfo->falldir )) + Q_strncpy( GameInfo->falldir, fs_path, sizeof( GameInfo->falldir )); + } + else if( !Q_stricmp( token, "gamedir" )) + { + pfile = COM_ParseFile( pfile, fs_path ); + if( Q_stricmp( fs_path, GameInfo->basedir ) || Q_stricmp( fs_path, GameInfo->gamedir )) + Q_strncpy( GameInfo->gamedir, fs_path, sizeof( GameInfo->gamedir )); + } + else if( !Q_stricmp( token, "title" )) + { + pfile = COM_ParseFile( pfile, GameInfo->title ); + } + else if( !Q_stricmp( token, "sp_entity" )) + { + pfile = COM_ParseFile( pfile, GameInfo->sp_entity ); + } + else if( !Q_stricmp( token, "mp_entity" )) + { + pfile = COM_ParseFile( pfile, GameInfo->mp_entity ); + } + else if( !Q_stricmp( token, "gamedll" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_dll ); + } + else if( !Q_stricmp( token, "dllpath" )) + { + pfile = COM_ParseFile( pfile, GameInfo->dll_path ); + } + else if( !Q_stricmp( token, "startmap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->startmap ); + COM_StripExtension( GameInfo->startmap ); // HQ2:Amen has extension .bsp + } + else if( !Q_stricmp( token, "trainmap" )) + { + pfile = COM_ParseFile( pfile, GameInfo->trainmap ); + COM_StripExtension( GameInfo->trainmap ); // HQ2:Amen has extension .bsp + } + else if( !Q_stricmp( token, "icon" )) + { + pfile = COM_ParseFile( pfile, GameInfo->iconpath ); + COM_DefaultExtension( GameInfo->iconpath, ".ico" ); + } + else if( !Q_stricmp( token, "url_info" )) + { + pfile = COM_ParseFile( pfile, GameInfo->game_url ); + } + else if( !Q_stricmp( token, "url_update" )) + { + pfile = COM_ParseFile( pfile, GameInfo->update_url ); + } + else if( !Q_stricmp( token, "date" )) + { + pfile = COM_ParseFile( pfile, GameInfo->date ); + } + else if( !Q_stricmp( token, "type" )) + { + pfile = COM_ParseFile( pfile, GameInfo->type ); + } + else if( !Q_stricmp( token, "version" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->version = Q_atof( token ); + } + else if( !Q_stricmp( token, "size" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->size = Q_atoi( token ); + } + else if( !Q_stricmp( token, "max_edicts" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_edicts = bound( 600, Q_atoi( token ), MAX_EDICTS ); + } + else if( !Q_stricmp( token, "max_tempents" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_tents = bound( 300, Q_atoi( token ), 2048 ); + } + else if( !Q_stricmp( token, "max_beams" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_beams = bound( 64, Q_atoi( token ), 512 ); + } + else if( !Q_stricmp( token, "max_particles" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->max_particles = bound( 1024, Q_atoi( token ), 131072 ); + } + else if( !Q_stricmp( token, "gamemode" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "singleplayer_only" )) + GameInfo->gamemode = 1; + else if( !Q_stricmp( token, "multiplayer_only" )) + GameInfo->gamemode = 2; + } + else if( !Q_stricmp( token, "secure" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->secure = Q_atoi( token ); + } + else if( !Q_stricmp( token, "nomodels" )) + { + pfile = COM_ParseFile( pfile, token ); + GameInfo->nomodels = Q_atoi( token ); + } + else if( !Q_strnicmp( token, "ambient", 7 )) + { + int ambientNum = Q_atoi( token + 7 ); + + if( ambientNum < 0 || ambientNum > ( NUM_AMBIENTS - 1 )) + { + MsgDev( D_ERROR, "FS_ReadGameInfo: Invalid ambient number %i. Ignored.\n", ambientNum ); + } + else + { + pfile = COM_ParseFile( pfile, GameInfo->ambientsound[ambientNum] ); + } + } + } + + // make sure what gamedir is really exist + if( !FS_SysFolderExists( va( "%s\\%s", host.rootdir, GameInfo->gamedir ))) + Q_strncpy( GameInfo->gamedir, gamedir, sizeof( GameInfo->gamedir )); + + // make sure what fallback_dir is really exist + if( !FS_SysFolderExists( va( "%s\\%s", host.rootdir, GameInfo->falldir ))) + GameInfo->falldir[0] = '\0'; + + if( afile != NULL ) + Mem_Free( afile ); + + return true; +} + +/* +================ +FS_CheckForGameDir +================ +*/ +static qboolean FS_CheckForGameDir( const char *gamedir ) +{ + // if directory contain config.cfg it's 100% gamedir + if( FS_FileExists( va( "%s/config.cfg", gamedir ), false )) + return true; + + // if directory contain progs.dat it's 100% gamedir + if( FS_FileExists( va( "%s/progs.dat", gamedir ), false )) + return true; + + // quake mods probably always archived but can missed config.cfg before first running + if( FS_FileExists( va( "%s/pak0.pak", gamedir ), false )) + return true; + + // NOTE; adds here some additional checks if you wished + + return false; +} + +/* +================ +FS_ParseGameInfo +================ +*/ +static qboolean FS_ParseGameInfo( const char *gamedir, gameinfo_t *GameInfo ) +{ + string liblist_path, gameinfo_path; + string default_gameinfo_path; + gameinfo_t tmpGameInfo; + + Q_snprintf( default_gameinfo_path, sizeof( default_gameinfo_path ), "%s/gameinfo.txt", fs_basedir ); + Q_snprintf( gameinfo_path, sizeof( gameinfo_path ), "%s/gameinfo.txt", gamedir ); + Q_snprintf( liblist_path, sizeof( liblist_path ), "%s/liblist.gam", gamedir ); + + // if user change liblist.gam update the gameinfo.txt + if( FS_FileTime( liblist_path, false ) > FS_FileTime( gameinfo_path, false )) + FS_ConvertGameInfo( gamedir, gameinfo_path, liblist_path ); + + // force to create gameinfo for specified game if missing + if(( FS_CheckForGameDir( gamedir ) || !Q_stricmp( fs_gamedir, gamedir )) && !FS_FileExists( gameinfo_path, false )) + { + memset( &tmpGameInfo, 0, sizeof( tmpGameInfo )); + + if( FS_ReadGameInfo( default_gameinfo_path, gamedir, &tmpGameInfo )) + { + // now we have copy of game info from basedir but needs to change gamedir + Con_DPrintf( "Convert %s to %s\n", default_gameinfo_path, gameinfo_path ); + Q_strncpy( tmpGameInfo.gamedir, gamedir, sizeof( tmpGameInfo.gamedir )); + FS_WriteGameInfo( gameinfo_path, &tmpGameInfo ); + } + else FS_CreateDefaultGameInfo( gameinfo_path ); + } + + if( !GameInfo || !FS_FileExists( gameinfo_path, false )) + return false; // no dest + + if( FS_ReadGameInfo( gameinfo_path, gamedir, GameInfo )) + return true; + return false; +} + +/* +================ +FS_LoadGameInfo + +can be passed null arg +================ +*/ +void FS_LoadGameInfo( const char *rootfolder ) +{ + int i; + + // lock uplevel of gamedir for read\write + fs_ext_path = false; + + if( rootfolder ) Q_strcpy( fs_gamedir, rootfolder ); + MsgDev( D_NOTE, "FS_LoadGameInfo( %s )\n", fs_gamedir ); + + // clear any old pathes + FS_ClearSearchPath(); + + // validate gamedir + for( i = 0; i < SI.numgames; i++ ) + { + if( !Q_stricmp( SI.games[i]->gamefolder, fs_gamedir )) + break; + } + + if( i == SI.numgames ) + Sys_Error( "Couldn't find game directory '%s'\n", fs_gamedir ); + + SI.GameInfo = SI.games[i]; + FS_Rescan(); // create new filesystem + + Host_InitDecals (); // reload decals +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( void ) +{ + stringlist_t dirs; + qboolean hasBaseDir = false; + qboolean hasGameDir = false; + int i; + + FS_InitMemory(); + + Cmd_AddCommand( "fs_rescan", FS_Rescan_f, "rescan filesystem search pathes" ); + Cmd_AddCommand( "fs_path", FS_Path_f, "show filesystem search pathes" ); + Cmd_AddCommand( "fs_clearpaths", FS_ClearPaths_f, "clear filesystem search pathes" ); + + // ignore commandlineoption "-game" for other stuff + if( host.type == HOST_NORMAL || host.type == HOST_DEDICATED ) + { + stringlistinit( &dirs ); + listdirectory( &dirs, "./" ); + stringlistsort( &dirs ); + SI.numgames = 0; + + Q_strncpy( fs_basedir, SI.basedirName, sizeof( fs_basedir )); // default dir + + if( !Sys_GetParmFromCmdLine( "-game", fs_gamedir )) + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // gamedir == basedir + + if( FS_CheckNastyPath( fs_basedir, true )) + { + // this is completely fatal... + Sys_Error( "FS_Init: invalid base directory \"%s\"\n", fs_basedir ); + } + + if( FS_CheckNastyPath( fs_gamedir, true )) + { + MsgDev( D_ERROR, "FS_Init: invalid game directory \"%s\"\n", fs_gamedir ); + Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); // default dir + } + + // validate directories + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !Q_stricmp( fs_basedir, dirs.strings[i] )) + hasBaseDir = true; + + if( !Q_stricmp( fs_gamedir, dirs.strings[i] )) + hasGameDir = true; + } + + if( !hasGameDir ) + { + MsgDev( D_ERROR, "FS_Init: game directory \"%s\" not exist\n", fs_gamedir ); + if( hasBaseDir ) Q_strncpy( fs_gamedir, fs_basedir, sizeof( fs_gamedir )); + } + + // build list of game directories here + FS_AddGameDirectory( "./", 0 ); + + for( i = 0; i < dirs.numstrings; i++ ) + { + if( !FS_SysFolderExists( dirs.strings[i] ) || ( !Q_stricmp( dirs.strings[i], ".." ) && !fs_ext_path )) + continue; + + if( SI.games[SI.numgames] == NULL ) + SI.games[SI.numgames] = (gameinfo_t *)Mem_Alloc( fs_mempool, sizeof( gameinfo_t )); + + if( FS_ParseGameInfo( dirs.strings[i], SI.games[SI.numgames] )) + SI.numgames++; // added + } + + stringlistfreecontents( &dirs ); + } + + MsgDev( D_NOTE, "FS_Init: done\n" ); +} + +void FS_AllowDirectPaths( qboolean enable ) +{ + fs_ext_path = enable; +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + int i; + + // release gamedirs + for( i = 0; i < SI.numgames; i++ ) + if( SI.games[i] ) Mem_Free( SI.games[i] ); + + memset( &SI, 0, sizeof( sysinfo_t )); + + FS_ClearSearchPath(); // release all wad files too + Mem_FreePool( &fs_mempool ); +} + +/* +==================== +FS_SysFileTime + +Internal function used to determine filetime +==================== +*/ +static long FS_SysFileTime( const char *filename ) +{ + struct stat buf; + + if( stat( filename, &buf ) == -1 ) + return -1; + + return buf.st_mtime; +} + +/* +==================== +FS_SysOpen + +Internal function used to create a file_t and open the relevant non-packed file on disk +==================== +*/ +static file_t *FS_SysOpen( const char *filepath, const char *mode ) +{ + file_t *file; + int mod, opt; + uint ind; + + // Parse the mode string + switch( mode[0] ) + { + case 'r': // read + mod = O_RDONLY; + opt = 0; + break; + case 'w': // write + mod = O_WRONLY; + opt = O_CREAT | O_TRUNC; + break; + case 'a': // append + mod = O_WRONLY; + opt = O_CREAT | O_APPEND; + break; + case 'e': // edit + mod = O_WRONLY; + opt = O_CREAT; + break; + default: + MsgDev( D_ERROR, "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode ); + return NULL; + } + + for( ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + MsgDev( D_ERROR, "FS_SysOpen: %s: unknown char in mode (%c)\n", filepath, mode, mode[ind] ); + break; + } + } + + file = (file_t *)Mem_Alloc( fs_mempool, sizeof( *file )); + file->filetime = FS_SysFileTime( filepath ); + file->ungetc = EOF; + + file->handle = open( filepath, mod|opt, 0666 ); + + if( file->handle < 0 ) + { + Mem_Free( file ); + return NULL; + } + + file->real_length = lseek( file->handle, 0, SEEK_END ); + + // For files opened in append mode, we start at the end of the file + if( mod & O_APPEND ) file->position = file->real_length; + else lseek( file->handle, 0, SEEK_SET ); + + return file; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + int dup_handle; + file_t *file; + + pfile = &pack->files[pack_ind]; + + if( lseek( pack->handle, pfile->filepos, SEEK_SET ) == -1 ) + return NULL; + + dup_handle = dup( pack->handle ); + + if( dup_handle < 0 ) + return NULL; + + file = (file_t *)Mem_Alloc( fs_mempool, sizeof( *file )); + file->handle = dup_handle; + file->real_length = pfile->filelen; + file->offset = pfile->filepos; + file->position = 0; + file->ungetc = EOF; + + return file; +} + +/* +================== +FS_SysFileExists + +Look for a file in the filesystem only +================== +*/ +qboolean FS_SysFileExists( const char *path ) +{ + int desc; + + if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 ) + return false; + + close( desc ); + return true; +} + +/* +================== +FS_SysFolderExists + +Look for a existing folder +================== +*/ +qboolean FS_SysFolderExists( const char *path ) +{ + DWORD dwFlags = GetFileAttributes( path ); + + return ( dwFlags != -1 ) && FBitSet( dwFlags, FILE_ATTRIBUTE_DIRECTORY ); +} + +/* +==================== +FS_FindFile + +Look for a file in the packages and in the filesystem + +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== +*/ +static searchpath_t *FS_FindFile( const char *name, int *index, qboolean gamedironly ) +{ + searchpath_t *search; + char *pEnvPath; + + // search through the path, one element at a time + for( search = fs_searchpaths; search; search = search->next ) + { + if( gamedironly & !FBitSet( search->flags, FS_GAMEDIR_PATH )) + continue; + + // is the element a pak file? + if( search->pack ) + { + int left, right, middle; + pack_t *pak; + + pak = search->pack; + + // look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pak->files[middle].name, name ); + + // Found it + if( !diff ) + { + if( index ) *index = middle; + return search; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + } + else if( search->wad ) + { + dlumpinfo_t *lump; + char type = W_TypeFromExt( name ); + qboolean anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( search->wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + continue; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( search->wad, shortname, type ); + + if( lump ) + { + if( index ) + *index = lump - search->wad->lumps; + return search; + } + } + else + { + char netpath[MAX_SYSPATH]; + + Q_sprintf( netpath, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath )) + { + if( index != NULL ) *index = -1; + return search; + } + } + } + + if( fs_ext_path && ( pEnvPath = getenv( "Path" ))) + { + char netpath[MAX_SYSPATH]; + + // clear searchpath + search = &fs_directpath; + memset( search, 0, sizeof( searchpath_t )); + + // root folder has a more priority than netpath + Q_strncpy( search->filename, host.rootdir, sizeof( search->filename )); + Q_strcat( search->filename, "\\" ); + Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath )) + { + if( index != NULL ) + *index = -1; + return search; + } + + // search for environment path + while( pEnvPath ) + { + char *end = Q_strchr( pEnvPath, ';' ); + if( !end ) break; + Q_strncpy( search->filename, pEnvPath, (end - pEnvPath) + 1 ); + Q_strcat( search->filename, "\\" ); + Q_snprintf( netpath, MAX_SYSPATH, "%s%s", search->filename, name ); + + if( FS_SysFileExists( netpath )) + { + if( index != NULL ) + *index = -1; + return search; + } + pEnvPath += (end - pEnvPath) + 1; // move pointer + } + } + + if( index != NULL ) + *index = -1; + + return NULL; +} + + +/* +=========== +FS_OpenReadFile + +Look for a file in the search paths and open it in read-only mode +=========== +*/ +file_t *FS_OpenReadFile( const char *filename, const char *mode, qboolean gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + + // not found? + if( search == NULL ) + return NULL; + + if( search->pack ) + return FS_OpenPackedFile( search->pack, pack_ind ); + else if( search->wad ) + return NULL; // let W_LoadFile get lump correctly + else if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; + + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } + + return NULL; +} + +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +==================== +FS_Open + +Open a file. The syntax is the same as fopen +==================== +*/ +file_t *FS_Open( const char *filepath, const char *mode, qboolean gamedironly ) +{ + // some stupid mappers used leading '/' or '\' in path to models or sounds + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + if( FS_CheckNastyPath( filepath, false )) + return NULL; + + // if the file is opened in "write", "append", or "read/write" mode + if( mode[0] == 'w' || mode[0] == 'a'|| mode[0] == 'e' || Q_strchr( mode, '+' )) + { + char real_path[MAX_SYSPATH]; + + // open the file on disk directly + Q_sprintf( real_path, "%s/%s", fs_writedir, filepath ); + FS_CreatePath( real_path );// Create directories up to the file + return FS_SysOpen( real_path, mode ); + } + + // else, we look at the various search paths and open the file in read-only mode + return FS_OpenReadFile( filepath, mode, gamedironly ); +} + +/* +==================== +FS_Close + +Close a file +==================== +*/ +int FS_Close( file_t *file ) +{ + if( !file ) return 0; + + if( close( file->handle )) + return EOF; + + Mem_Free( file ); + return 0; +} + +/* +==================== +FS_Write + +Write "datasize" bytes into a file +==================== +*/ +long FS_Write( file_t *file, const void *data, size_t datasize ) +{ + long result; + + if( !file ) return 0; + + // if necessary, seek to the exact file position we're supposed to be + if( file->buff_ind != file->buff_len ) + lseek( file->handle, file->buff_ind - file->buff_len, SEEK_CUR ); + + // purge cached data + FS_Purge( file ); + + // write the buffer and update the position + result = write( file->handle, data, (long)datasize ); + file->position = lseek( file->handle, 0, SEEK_CUR ); + + if( file->real_length < file->position ) + file->real_length = file->position; + + if( result < 0 ) + return 0; + return result; +} + +/* +==================== +FS_Read + +Read up to "buffersize" bytes from a file +==================== +*/ +long FS_Read( file_t *file, void *buffer, size_t buffersize ) +{ + long count, done; + long nb; + + // nothing to copy + if( buffersize == 0 ) return 1; + + // Get rid of the ungetc character + if( file->ungetc != EOF ) + { + ((char*)buffer)[0] = file->ungetc; + buffersize--; + file->ungetc = EOF; + done = 1; + } + else done = 0; + + // first, we copy as many bytes as we can from "buff" + if( file->buff_ind < file->buff_len ) + { + count = file->buff_len - file->buff_ind; + + done += ((long)buffersize > count ) ? count : (long)buffersize; + memcpy( buffer, &file->buff[file->buff_ind], done ); + file->buff_ind += done; + + buffersize -= done; + if( buffersize == 0 ) + return done; + } + + // NOTE: at this point, the read buffer is always empty + + // we must take care to not read after the end of the file + count = file->real_length - file->position; + + // if we have a lot of data to get, put them directly into "buffer" + if( buffersize > sizeof( file->buff ) / 2 ) + { + if( count > (long)buffersize ) + count = (long)buffersize; + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read (file->handle, &((byte *)buffer)[done], count ); + + if( nb > 0 ) + { + done += nb; + file->position += nb; + // purge cached data + FS_Purge( file ); + } + } + else + { + if( count > (long)sizeof( file->buff )) + count = (long)sizeof( file->buff ); + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read( file->handle, file->buff, count ); + + if( nb > 0 ) + { + file->buff_len = nb; + file->position += nb; + + // copy the requested data in "buffer" (as much as we can) + count = (long)buffersize > file->buff_len ? file->buff_len : (long)buffersize; + memcpy( &((byte *)buffer)[done], file->buff, count ); + file->buff_ind = count; + done += count; + } + } + + return done; +} + +/* +==================== +FS_Print + +Print a string into a file +==================== +*/ +int FS_Print( file_t *file, const char *msg ) +{ + return FS_Write( file, msg, Q_strlen( msg )); +} + +/* +==================== +FS_Printf + +Print a string into a file +==================== +*/ +int FS_Printf( file_t *file, const char *format, ... ) +{ + int result; + va_list args; + + va_start( args, format ); + result = FS_VPrintf( file, format, args ); + va_end( args ); + + return result; +} + +/* +==================== +FS_VPrintf + +Print a string into a file +==================== +*/ +int FS_VPrintf( file_t *file, const char *format, va_list ap ) +{ + int len; + long buff_size = MAX_SYSPATH; + char *tempbuff; + + if( !file ) return 0; + + while( 1 ) + { + tempbuff = (char *)Mem_Alloc( fs_mempool, buff_size ); + len = Q_vsprintf( tempbuff, format, ap ); + + if( len >= 0 && len < buff_size ) + break; + + Mem_Free( tempbuff ); + buff_size *= 2; + } + + len = write( file->handle, tempbuff, len ); + Mem_Free( tempbuff ); + + return len; +} + +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc( file_t *file ) +{ + char c; + + if( FS_Read( file, &c, 1 ) != 1 ) + return EOF; + + return c; +} + +/* +==================== +FS_UnGetc + +Put a character back into the read buffer (only supports one character!) +==================== +*/ +int FS_UnGetc( file_t *file, byte c ) +{ + // If there's already a character waiting to be read + if( file->ungetc != EOF ) + return EOF; + + file->ungetc = c; + return c; +} + +/* +==================== +FS_Gets + +Same as fgets +==================== +*/ +int FS_Gets( file_t *file, byte *string, size_t bufsize ) +{ + int c, end = 0; + + while( 1 ) + { + c = FS_Getc( file ); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < bufsize - 1 ) + string[end++] = c; + } + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = FS_Getc( file ); + + if( c != '\n' ) + FS_UnGetc( file, (byte)c ); + } + + return c; +} + +/* +==================== +FS_Seek + +Move the position index in a file +==================== +*/ +int FS_Seek( file_t *file, long offset, int whence ) +{ + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += file->position - file->buff_len + file->buff_ind; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += file->real_length; + break; + default: + return -1; + } + + if( offset < 0 || offset > file->real_length ) + return -1; + + // if we have the data in our read buffer, we don't need to actually seek + if( file->position - file->buff_len <= offset && offset <= file->position ) + { + file->buff_ind = offset + file->buff_len - file->position; + return 0; + } + + // Purge cached data + FS_Purge( file ); + + if( lseek( file->handle, file->offset + offset, SEEK_SET ) == -1 ) + return -1; + file->position = offset; + + return 0; +} + +/* +==================== +FS_Tell + +Give the current position in a file +==================== +*/ +long FS_Tell( file_t *file ) +{ + if( !file ) return 0; + return file->position - file->buff_len + file->buff_ind; +} + +/* +==================== +FS_Eof + +indicates at reached end of file +==================== +*/ +qboolean FS_Eof( file_t *file ) +{ + if( !file ) return true; + return (( file->position - file->buff_len + file->buff_ind ) == file->real_length ) ? true : false; +} + +/* +==================== +FS_Purge + +Erases any buffered input or output data +==================== +*/ +void FS_Purge( file_t *file ) +{ + file->buff_len = 0; + file->buff_ind = 0; + file->ungetc = EOF; +} + +/* +============ +FS_LoadFile + +Filename are relative to the xash directory. +Always appends a 0 byte. +============ +*/ +byte *FS_LoadFile( const char *path, long *filesizeptr, qboolean gamedironly ) +{ + file_t *file; + byte *buf = NULL; + long filesize = 0; + + file = FS_Open( path, "rb", gamedironly ); + + if( file ) + { + filesize = file->real_length; + buf = (byte *)Mem_Alloc( fs_mempool, filesize + 1 ); + buf[filesize] = '\0'; + FS_Read( file, buf, filesize ); + FS_Close( file ); + } + else + { + buf = W_LoadFile( path, &filesize, gamedironly ); + } + + if( filesizeptr ) + *filesizeptr = filesize; + + return buf; +} + +/* +============ +FS_WriteFile + +The filename will be prefixed by the current game directory +============ +*/ +qboolean FS_WriteFile( const char *filename, const void *data, long len ) +{ + file_t *file; + + file = FS_Open( filename, "wb", false ); + + if( !file ) + { + MsgDev( D_ERROR, "FS_WriteFile: failed on %s\n", filename); + return false; + } + + FS_Write( file, data, len ); + FS_Close( file ); + + return true; +} + +/* +============================================================================= + +OTHERS PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ +qboolean FS_FileExists( const char *filename, qboolean gamedironly ) +{ + if( FS_FindFile( filename, NULL, gamedironly )) + return true; + return false; +} + +/* +================== +FS_GetDiskPath + +Build direct path for file in the filesystem +return NULL for file in pack +================== +*/ +const char *FS_GetDiskPath( const char *name, qboolean gamedironly ) +{ + int index; + searchpath_t *search; + + search = FS_FindFile( name, &index, gamedironly ); + + if( search ) + { + if( index != -1 ) // file in pack or wad + return NULL; + return va( "%s%s", search->filename, name ); + } + + return NULL; +} + +/* +================== +FS_CheckForCrypt + +return true if library is crypted +================== +*/ +qboolean FS_CheckForCrypt( const char *dllname ) +{ + file_t *f; + int key; + + f = FS_Open( dllname, "rb", false ); + if( !f ) return false; + + FS_Seek( f, 64, SEEK_SET ); // skip first 64 bytes + FS_Read( f, &key, sizeof( key )); + FS_Close( f ); + + return ( key == 0x12345678 ) ? true : false; +} + +/* +================== +FS_FindLibrary + +search for library, assume index is valid +only for internal use +================== +*/ +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ) +{ + string dllpath; + searchpath_t *search; + dll_user_t *hInst; + int i, index; + int start = 0; + + // check for bad exports + if( !COM_CheckString( dllname )) + return NULL; + + fs_ext_path = directpath; + + // HACKHACK remove absoulte path to valve folder + if( !Q_strnicmp( dllname, "..\\valve\\", 9 ) || !Q_strnicmp( dllname, "../valve/", 9 )) + start += 9; + + // replace all backward slashes + for( i = 0; i < Q_strlen( dllname ); i++ ) + { + if( dllname[i+start] == '\\' ) dllpath[i] = '/'; + else dllpath[i] = Q_tolower( dllname[i+start] ); + } + dllpath[i] = '\0'; + + COM_DefaultExtension( dllpath, ".dll" ); // apply ext if forget + search = FS_FindFile( dllpath, &index, false ); + + if( !search ) + { + fs_ext_path = false; + if( directpath ) return NULL; // direct paths fails here + + // trying check also 'bin' folder for indirect paths + Q_strncpy( dllpath, dllname, sizeof( dllpath )); + search = FS_FindFile( dllpath, &index, false ); + if( !search ) return NULL; // unable to find + } + + // all done, create dll_user_t struct + hInst = Mem_Alloc( host.mempool, sizeof( dll_user_t )); + + // save dllname for debug purposes + Q_strncpy( hInst->dllName, dllname, sizeof( hInst->dllName )); + + // shortPath is used for LibraryLoadSymbols only + Q_strncpy( hInst->shortPath, dllpath, sizeof( hInst->shortPath )); + + hInst->encrypted = FS_CheckForCrypt( dllpath ); + + if( index < 0 && !hInst->encrypted ) + { + Q_snprintf( hInst->fullPath, sizeof( hInst->fullPath ), "%s%s", search->filename, dllpath ); + hInst->custom_loader = false; // we can loading from disk and use normal debugging + } + else + { + Q_strncpy( hInst->fullPath, dllpath, sizeof( hInst->fullPath )); + hInst->custom_loader = true; // loading from pack or wad - for release, debug don't working + } + fs_ext_path = false; // always reset direct paths + + return hInst; +} + +/* +================== +FS_FileSize + +return size of file in bytes +================== +*/ +long FS_FileSize( const char *filename, qboolean gamedironly ) +{ + int length = -1; // in case file was missed + file_t *fp; + + fp = FS_Open( filename, "rb", gamedironly ); + + if( fp ) + { + // it exists + FS_Seek( fp, 0, SEEK_END ); + length = FS_Tell( fp ); + FS_Close( fp ); + } + + return length; +} + +/* +================== +FS_FileLength + +return size of file in bytes +================== +*/ +long FS_FileLength( file_t *f ) +{ + if( !f ) return 0; + return f->real_length; +} + +/* +================== +FS_FileTime + +return time of creation file in seconds +================== +*/ +long FS_FileTime( const char *filename, qboolean gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + if( !search ) return -1; // doesn't exist + + if( search->pack ) // grab pack filetime + return search->pack->filetime; + else if( search->wad ) // grab wad filetime + return search->wad->filetime; + else if( pack_ind < 0 ) + { + // found in the filesystem? + char path [MAX_SYSPATH]; + + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysFileTime( path ); + } + + return -1; // doesn't exist +} + +/* +================== +FS_Rename + +rename specified file from gamefolder +================== +*/ +qboolean FS_Rename( const char *oldname, const char *newname ) +{ + char oldpath[MAX_SYSPATH], newpath[MAX_SYSPATH]; + qboolean iRet; + + if( !oldname || !newname || !*oldname || !*newname ) + return false; + + Q_snprintf( oldpath, sizeof( oldpath ), "%s%s", fs_writedir, oldname ); + Q_snprintf( newpath, sizeof( newpath ), "%s%s", fs_writedir, newname ); + + COM_FixSlashes( oldpath ); + COM_FixSlashes( newpath ); + + iRet = rename( oldpath, newpath ); + + return (iRet == 0); +} + +/* +================== +FS_Delete + +delete specified file from gamefolder +================== +*/ +qboolean FS_Delete( const char *path ) +{ + char real_path[MAX_SYSPATH]; + qboolean iRet; + + if( !path || !*path ) + return false; + + Q_snprintf( real_path, sizeof( real_path ), "%s%s", fs_writedir, path ); + COM_FixSlashes( real_path ); + iRet = remove( real_path ); + + return (iRet == 0); +} + +/* +================== +FS_FileCopy + +================== +*/ +qboolean FS_FileCopy( file_t *pOutput, file_t *pInput, int fileSize ) +{ + char *buf = Mem_Alloc( fs_mempool, FILE_COPY_SIZE ); + int size, readSize; + qboolean done = true; + + while( fileSize > 0 ) + { + if( fileSize > FILE_COPY_SIZE ) + size = FILE_COPY_SIZE; + else size = fileSize; + + if(( readSize = FS_Read( pInput, buf, size )) < size ) + { + MsgDev( D_ERROR, "FS_FileCopy: unexpected end of input file (%d < %d)\n", readSize, size ); + fileSize = 0; + done = false; + break; + } + + FS_Write( pOutput, buf, readSize ); + fileSize -= size; + } + + Mem_Free( buf ); + return done; +} + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) +{ + search_t *search = NULL; + searchpath_t *searchpath; + pack_t *pak; + wfile_t *wad; + int i, basepathlength, numfiles, numchars; + int resultlistindex, dirlistindex; + const char *slash, *backslash, *colon, *separator; + string netpath, temp; + stringlist_t resultlist; + stringlist_t dirlist; + char *basepath; + + if( pattern[0] == '.' || pattern[0] == ':' || pattern[0] == '/' || pattern[0] == '\\' ) + return NULL; // punctuation issues + + stringlistinit( &resultlist ); + stringlistinit( &dirlist ); + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + separator = max( slash, backslash ); + separator = max( separator, colon ); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = Mem_Alloc( fs_mempool, basepathlength + 1 ); + if( basepathlength ) memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next ) + { + if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIR_PATH )) + continue; + + // is the element a pak file? + if( searchpath->pack ) + { + // look through all the pak file elements + pak = searchpath->pack; + for( i = 0; i < pak->numfiles; i++ ) + { + Q_strncpy( temp, pak->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else if( searchpath->wad ) + { + string wadpattern, wadname, temp2; + char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( searchpath->wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + continue; + + // look through all the wad file elements + wad = searchpath->wad; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_snprintf( temp, sizeof( temp ), "%s%s", wad->lumps[i].name, wad_hints[wad->lumps[i].img_type].ext ); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( &resultlist, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + // get a directory listing and look at each name + Q_sprintf( netpath, "%s%s", searchpath->filename, basepath ); + stringlistinit( &dirlist ); + listdirectory( &dirlist, netpath ); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] ); + + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + } + + stringlistfreecontents( &dirlist ); + } + } + + if( resultlist.numstrings ) + { + stringlistsort( &resultlist ); + numfiles = resultlist.numstrings; + numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1; + search = Mem_Alloc( fs_mempool, sizeof(search_t) + numchars + numfiles * sizeof( char* )); + search->filenames = (char **)((char *)search + sizeof( search_t )); + search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* )); + search->numfilenames = (int)numfiles; + numfiles = numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + size_t textlen; + + search->filenames[numfiles] = search->filenamesbuffer + numchars; + textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1; + memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen ); + numfiles++; + numchars += (int)textlen; + } + } + + stringlistfreecontents( &resultlist ); + + Mem_Free( basepath ); + + return search; +} + +void FS_InitMemory( void ) +{ + fs_mempool = Mem_AllocPool( "FileSystem Pool" ); + fs_searchpaths = NULL; +} + +/* +============================================================================= + +WADSYSTEM PRIVATE ROUTINES + +============================================================================= +*/ +// associate extension with wad type +static const wadtype_t wad_types[7] = +{ +{ "pal", TYP_PALETTE }, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 mip +{ "txt", TYP_SCRIPT }, // scripts +{ NULL, TYP_NONE } +}; + +// suffix converts to img_type and back +static const wadtype_t wad_hints[10] = +{ +{ "", IMG_DIFFUSE }, // no suffix +{ "_mask", IMG_ALPHAMASK }, // alpha-channel stored to another lump +{ "_norm", IMG_NORMALMAP }, // indexed normalmap +{ "_spec", IMG_GLOSSMAP }, // grayscale\color specular +{ "_gpow", IMG_GLOSSPOWER }, // grayscale gloss power +{ "_hmap", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_luma", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_adec", IMG_DECAL_ALPHA }, // classic HL-decal (with alpha-channel) +{ "_cdec", IMG_DECAL_COLOR }, // paranoia decal (base 127 127 127) +{ NULL, 0 } // terminator +}; + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +static char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +static const char *W_ExtFromType( char lumptype ) +{ + const wadtype_t *type; + + // we not known aboyt filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + return ""; +} + +/* +=========== +W_HintFromSuf + +Convert name suffix into image type +=========== +*/ +char W_HintFromSuf( const char *lumpname ) +{ + char barename[64]; + char suffix[8]; + size_t namelen; + const wadtype_t *hint; + + // trying to extract hint from the name + COM_FileBase( lumpname, barename ); + namelen = Q_strlen( barename ); + + if( namelen <= HINT_NAMELEN ) + return IMG_DIFFUSE; + + Q_strncpy( suffix, barename + namelen - HINT_NAMELEN, sizeof( suffix )); + + // we not known about filetype, so match only by filename + for( hint = wad_hints; hint->ext; hint++ ) + { + if( !Q_stricmp( suffix, hint->ext )) + return hint->type; + } + + // no any special type was found + return IMG_DIFFUSE; +} + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +static dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const char matchtype ) +{ + char img_type = IMG_DIFFUSE; + char barename[64], suffix[8]; + int left, right; + size_t namelen; + const wadtype_t *hint; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // trying to extract hint from the name + COM_FileBase( name, barename ); + namelen = Q_strlen( barename ); + + if( namelen > HINT_NAMELEN ) + { + Q_strncpy( suffix, barename + namelen - HINT_NAMELEN, sizeof( suffix )); + + // we not known about filetype, so match only by filename + for( hint = wad_hints; hint->ext; hint++ ) + { + if( !Q_stricmp( suffix, hint->ext )) + { + img_type = hint->type; + break; + } + } + + if( img_type != IMG_DIFFUSE ) + barename[namelen - HINT_NAMELEN] = '\0'; // kill the suffix + } + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, barename ); + + if( !diff ) + { + if( wad->lumps[middle].img_type > img_type ) + diff = 1; + else if( wad->lumps[middle].img_type < img_type ) + diff = -1; + else if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].img_type > newlump->img_type ) + diff = 1; + else if( wad->lumps[middle].img_type < newlump->img_type ) + diff = -1; + else if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else MsgDev( D_WARN, "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, long *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Alloc( wad->mempool, lump->disksize ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + MsgDev( D_WARN, "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +============================================================================= + +WADSYSTEM PUBLIC BASE FUNCTIONS + +============================================================================= +*/ +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +wfile_t *W_Open( const char *filename, int *error ) +{ + wfile_t *wad = (wfile_t *)Mem_Alloc( fs_mempool, sizeof( wfile_t )); + int i, lumpcount; + dlumpinfo_t *srclumps; + size_t lat_size; + dwadinfo_t header; + + // NOTE: FS_Open is load wad file from the first pak in the list (while fs_ext_path is false) + if( fs_ext_path ) wad->handle = FS_Open( filename, "rb", false ); + else wad->handle = FS_Open( COM_FileWithoutPath( filename ), "rb", false ); + + if( wad->handle == NULL ) + { + MsgDev( D_ERROR, "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + W_Close( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = FS_SysFileTime( filename ); + wad->mempool = Mem_AllocPool( filename ); + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + MsgDev( D_ERROR, "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + W_Close( wad ); + return NULL; + } + + if( header.ident != IDWAD2HEADER && header.ident != IDWAD3HEADER ) + { + MsgDev( D_ERROR, "W_Open: %s is not a WAD2 or WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + W_Close( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD ) + { + MsgDev( D_WARN, "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + } + else if( lumpcount <= 0 ) + { + MsgDev( D_ERROR, "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + W_Close( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + W_Close( wad ); + return NULL; + } + + lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + srclumps = (dlumpinfo_t *)Mem_Alloc( wad->mempool, lat_size ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + MsgDev( D_ERROR, "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps ); + W_Close( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Alloc( wad->mempool, lat_size ); + wad->numlumps = 0; + + // sort lumps for binary search + for( i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strnlwr( srclumps[i].name, name, sizeof( srclumps[i].name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; + + // check for Quake 'conchars' issues (only lmp loader really allows to read this lame pic) + if( srclumps[i].type == 68 && !Q_stricmp( srclumps[i].name, "conchars" )) + srclumps[i].type = TYP_GFXPIC; + + // fixups bad image types (some quake wads) + if( srclumps[i].img_type < 0 || srclumps[i].img_type > IMG_DECAL_COLOR ) + srclumps[i].img_type = IMG_DIFFUSE; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps ); + + // and leave the file open + return wad; +} + +/* +=========== +W_Close + +finalize wad or just close +=========== +*/ +void W_Close( wfile_t *wad ) +{ + if( !wad ) return; + + Mem_FreePool( &wad->mempool ); + if( wad->handle != NULL ) + FS_Close( wad->handle ); + Mem_Free( wad ); // free himself +} + +/* +============================================================================= + +FILESYSTEM IMPLEMENTATION + +============================================================================= +*/ +/* +=========== +W_LoadFile + +loading lump into the tmp buffer +=========== +*/ +static byte *W_LoadFile( const char *path, long *lumpsizeptr, qboolean gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->wad ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} \ No newline at end of file diff --git a/engine/common/filesystem.h b/engine/common/filesystem.h new file mode 100644 index 00000000..b08f62b1 --- /dev/null +++ b/engine/common/filesystem.h @@ -0,0 +1,132 @@ +/* +filesystem.h - engine FS +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" + +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +// hidden virtual lump types +#define TYP_ANY -1 // any type can be accepted +#define TYP_NONE 0 // unknown lump type + +#include "const.h" + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + char type; // TYP_* + char attribs; // file attribs + char img_type; // IMG_* + char pad; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +#include "custom.h" + +/* +======================================================================== +.HPK archive format (Hash PAK - HPK) + +List of compressed files, that can be identify only by TYPE_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDHPAKHEADER (('K'<<24)+('A'<<16)+('P'<<8)+'H') // little-endian "HPAK" +#define IDHPAK_VERSION 1 + +typedef struct +{ + int ident; // should be equal HPAK + int version; + int infotableofs; +} hpak_header_t; + +typedef struct +{ + resource_t resource; + int filepos; + int disksize; +} hpak_lump_t; + +typedef struct +{ + int count; + hpak_lump_t *entries; // variable sized. +} hpak_info_t; + +#endif//FILESYSTEM_H \ No newline at end of file diff --git a/engine/common/gamma.c b/engine/common/gamma.c new file mode 100644 index 00000000..e4ccabaa --- /dev/null +++ b/engine/common/gamma.c @@ -0,0 +1,88 @@ +/* +gamma.c - gamma routines +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include +#include "gl_local.h" + +//----------------------------------------------------------------------------- +// Gamma conversion support +//----------------------------------------------------------------------------- +static byte texgammatable[256]; // palette is sent through this to convert to screen gamma +static byte lightgammatable[256]; +static int lineargammatable[1024]; +static int screengammatable[1024]; + +void BuildGammaTable( float lightgamma, float brightness ) +{ + int i, inf; + float f, g, g1, g3; + + lightgamma = bound( 1.8f, lightgamma, 3.0f ); + brightness = bound( 0.0f, brightness, 10.0f ); + + if( brightness <= 0.0f ) + g3 = 0.125f; + else if( brightness > 1.0f ) + g3 = 0.05f; + else g3 = 0.125f - (brightness * brightness) * 0.075f; + + g = 1.0f / lightgamma; + g1 = GAMMA * g; + + for( i = 0; i < 256; i++ ) + { + f = pow( i / 255.f, GAMMA ); + + // scale up + if( brightness > 1.0f ) + f = f * brightness; + + // shift up + if( f <= g3 ) f = (f / g3) * 0.125f; + else f = 0.125f + ((f - g3) / (1.0f - g3)) * 0.875f; + + // convert linear space to desired gamma space + inf = (int)( 255.0 * pow( f, g )); + + lightgammatable[i] = bound( 0, inf, 255 ); + } + + for( i = 0; i < 256; i++ ) + { + f = 255.0 * pow(( float )i / 255.0f, TEXGAMMA ); + inf = (int)(f + 0.5f); + texgammatable[i] = bound( 0, inf, 255 ); + } + + for( i = 0; i < 1024; i++ ) + { + // convert from screen gamma space to linear space + lineargammatable[i] = 1023 * pow( i / 1023.0, g1 ); + + // convert from linear gamma space to screen space + screengammatable[i] = 1023 * pow( i / 1023.0, 1.0 / g1 ); + } +} + +byte LightToTexGamma( byte b ) +{ + return lightgammatable[b]; +} + +byte TextureToGamma( byte b ) +{ + return texgammatable[b]; +} \ No newline at end of file diff --git a/engine/common/host.c b/engine/common/host.c new file mode 100644 index 00000000..76b4365a --- /dev/null +++ b/engine/common/host.c @@ -0,0 +1,909 @@ +/* +host.c - dedicated and normal host +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "netchan.h" +#include "protocol.h" +#include "mod_local.h" +#include "mathlib.h" +#include "input.h" +#include "features.h" +#include "render_api.h" // decallist_t + +typedef void (*pfnChangeGame)( const char *progname ); + +pfnChangeGame pChangeGame = NULL; +HINSTANCE hCurrent; // hinstance of current .dll +host_parm_t host; // host parms +sysinfo_t SI; + +CVAR_DEFINE( host_developer, "developer", "0", 0, "engine is in development-mode" ); +convar_t *host_gameloaded; +convar_t *host_clientloaded; +convar_t *host_limitlocal; +convar_t host_developer; +convar_t *host_maxfps; +convar_t *host_framerate; +convar_t *con_gamemaps; +convar_t *build, *ver; + +int Host_CompareFileTime( long ft1, long ft2 ) +{ + if( ft1 < ft2 ) + { + return -1; + } + else if( ft1 > ft2 ) + { + return 1; + } + return 0; +} + +void Host_ShutdownServer( void ) +{ + SV_Shutdown( "Server was killed\n" ); +} + +/* +================ +Host_PrintEngineFeatures +================ +*/ +void Host_PrintEngineFeatures( void ) +{ + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) + MsgDev( D_REPORT, "^3EXT:^7 big world support enabled\n" ); + + if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) + MsgDev( D_REPORT, "^3EXT:^7 deluxemap support enabled\n" ); + + if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) + MsgDev( D_REPORT, "^3EXT:^7 Improved MOVETYPE_PUSH is used\n" ); + + if( FBitSet( host.features, ENGINE_LARGE_LIGHTMAPS )) + MsgDev( D_REPORT, "^3EXT:^7 Large lightmaps enabled\n" ); + + if( FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + MsgDev( D_REPORT, "^3EXT:^7 Compensate quake bug enabled\n" ); + + if( FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + MsgDev( D_REPORT, "^3EXT:^7 runnung server at constant fps\n" ); +} + +/* +================ +Host_EndGame +================ +*/ +void Host_EndGame( qboolean abort, const char *message, ... ) +{ + va_list argptr; + static char string[MAX_SYSPATH]; + + va_start( argptr, message ); + Q_vsnprintf( string, sizeof( string ), message, argptr ); + va_end( argptr ); + + MsgDev( D_INFO, "Host_EndGame: %s\n", string ); + + SV_Shutdown( "\n" ); + CL_Disconnect(); + + // recreate world if needs + CL_ClearEdicts (); + + // release all models + Mod_FreeAll(); + + if( abort ) Host_AbortCurrentFrame (); +} + +/* +================ +Host_AbortCurrentFrame + +aborts the current host frame and goes on with the next one +================ +*/ +void Host_AbortCurrentFrame( void ) +{ + longjmp( host.abortframe, 1 ); +} + +/* +================== +Host_CheckSleep +================== +*/ +void Host_CheckSleep( void ) +{ + if( host.type == HOST_DEDICATED ) + { + // let the dedicated server some sleep + Sys_Sleep( 1 ); + } + else + { + if( host.status == HOST_NOFOCUS ) + { + if( SV_Active() && CL_IsInGame( )) + Sys_Sleep( 1 ); // listenserver + else Sys_Sleep( 20 ); // sleep 20 ms otherwise + } + else if( host.status == HOST_SLEEP ) + { + // completely sleep in minimized state + Sys_Sleep( 20 ); + } + } +} + +void Host_NewInstance( const char *name, const char *finalmsg ) +{ + if( !pChangeGame ) return; + + host.change_game = true; + Q_strncpy( host.finalmsg, finalmsg, sizeof( host.finalmsg )); + pChangeGame( name ); // call from hl.exe +} + +/* +================= +Host_ChangeGame_f + +Change game modification +================= +*/ +void Host_ChangeGame_f( void ) +{ + int i; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "game \n" ); + return; + } + + // validate gamedir + for( i = 0; i < SI.numgames; i++ ) + { + if( !Q_stricmp( SI.games[i]->gamefolder, Cmd_Argv( 1 ))) + break; + } + + if( i == SI.numgames ) + { + Con_Printf( "%s not exist\n", Cmd_Argv( 1 )); + } + else if( !Q_stricmp( GI->gamefolder, Cmd_Argv( 1 ))) + { + Con_Printf( "%s already active\n", Cmd_Argv( 1 )); + } + else + { + const char *arg1 = va( "%s%s", (host.type == HOST_NORMAL) ? "" : "#", Cmd_Argv( 1 )); + const char *arg2 = va( "change game to '%s'", SI.games[i]->title ); + + Host_NewInstance( arg1, arg2 ); + } +} + +/* +=============== +Host_Exec_f +=============== +*/ +void Host_Exec_f( void ) +{ + string cfgpath; + char *f, *txt; + size_t len; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "exec \n" ); + return; + } + + if( !Q_stricmp( "game.cfg", Cmd_Argv( 1 ))) + { + // don't execute game.cfg in singleplayer + if( SV_GetMaxClients() == 1 ) + return; + } + + Q_strncpy( cfgpath, Cmd_Argv( 1 ), sizeof( cfgpath )); + COM_DefaultExtension( cfgpath, ".cfg" ); // append as default + + f = FS_LoadFile( cfgpath, &len, false ); + if( !f ) + { + MsgDev( D_NOTE, "couldn't exec %s\n", Cmd_Argv( 1 )); + return; + } + + if( !Q_stricmp( "config.cfg", Cmd_Argv( 1 ))) + host.config_executed = true; + + // adds \n\0 at end of the file + txt = Z_Malloc( len + 2 ); + memcpy( txt, f, len ); + Q_strncat( txt, "\n", len + 2 ); + Mem_Free( f ); + + if( !host.apply_game_config ) + MsgDev( D_INFO, "execing %s\n", Cmd_Argv( 1 )); + Cbuf_InsertText( txt ); + Mem_Free( txt ); +} + +/* +=============== +Host_MemStats_f +=============== +*/ +void Host_MemStats_f( void ) +{ + switch( Cmd_Argc( )) + { + case 1: + Mem_PrintList( 1<<30 ); + Mem_PrintStats(); + break; + case 2: + Mem_PrintList( Q_atoi( Cmd_Argv( 1 )) * 1024 ); + Mem_PrintStats(); + break; + default: + Con_Printf( S_USAGE "memlist \n" ); + break; + } +} + +void Host_Minimize_f( void ) +{ + if( host.hWnd ) ShowWindow( host.hWnd, SW_MINIMIZE ); +} + +/* +================= +Host_IsLocalGame + +singleplayer game detect +================= +*/ +qboolean Host_IsLocalGame( void ) +{ + if( SV_Active( )) + { + return ( SV_GetMaxClients() == 1 ) ? true : false; + } + else + { + return ( CL_GetMaxClients() == 1 ) ? true : false; + } +} + +qboolean Host_IsLocalClient( void ) +{ + // only the local client have the active server + if( CL_Initialized( ) && SV_Initialized( )) + return true; + return false; +} + +/* +================= +Host_RegisterDecal +================= +*/ +qboolean Host_RegisterDecal( const char *name, int *count ) +{ + char shortname[MAX_QPATH]; + int i; + + if( !COM_CheckString( name )) + return 0; + + COM_FileBase( name, shortname ); + + for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) + { + if( !Q_stricmp( host.draw_decals[i], shortname )) + return true; + } + + if( i == MAX_DECALS ) + { + MsgDev( D_ERROR, "MAX_DECALS limit exceeded (%d)\n", MAX_DECALS ); + return false; + } + + // register new decal + Q_strncpy( host.draw_decals[i], shortname, sizeof( host.draw_decals[i] )); + *count += 1; + + return true; +} + +/* +================= +Host_InitDecals +================= +*/ +void Host_InitDecals( void ) +{ + int i, num_decals = 0; + search_t *t; + + memset( host.draw_decals, 0, sizeof( host.draw_decals )); + + // lookup all the decals in decals.wad (basedir, gamedir, falldir) + t = FS_Search( "decals.wad/*.*", true, false ); + + for( i = 0; t && i < t->numfilenames; i++ ) + { + if( !Host_RegisterDecal( t->filenames[i], &num_decals )) + break; + } + + if( t ) Mem_Free( t ); + Con_DPrintf( "InitDecals: %i decals\n", num_decals ); +} + +/* +=================== +Host_GetCommands + +Add them exactly as if they had been typed at the console +=================== +*/ +void Host_GetCommands( void ) +{ + char *cmd; + + if( host.type != HOST_DEDICATED ) + return; + + cmd = Con_Input(); + if( cmd ) Cbuf_AddText( cmd ); + Cbuf_Execute (); +} + +/* +=================== +Host_CalcFPS + +compute actual FPS for various modes +=================== +*/ +double Host_CalcFPS( void ) +{ + double fps = 0.0; + + // NOTE: we should play demos with same fps as it was recorded + if( CL_IsPlaybackDemo() || CL_IsRecordDemo( )) + fps = CL_GetDemoFramerate(); + else if( Host_IsLocalGame( )) + fps = host_maxfps->value; + else + { + fps = host_maxfps->value; + if( fps == 0.0 ) fps = HOST_FPS; // default for multiplayer + fps = bound( MIN_FPS, fps, MAX_FPS ); + } + + // probably left part of this condition is redundant :-) + if( host.type != HOST_DEDICATED && Host_IsLocalGame( ) && !CL_IsTimeDemo( )) + { + // ajdust fps for vertical synchronization + if( gl_vsync != NULL && gl_vsync->value ) + { + if( vid_displayfrequency->value != 0.0f ) + fps = vid_displayfrequency->value; + else fps = 60.0; // default + } + } + + return fps; +} + +/* +=================== +Host_FilterTime + +Returns false if the time is too short to run a frame +=================== +*/ +qboolean Host_FilterTime( float time ) +{ + static double oldtime; + double fps; + + host.realtime += time; + fps = Host_CalcFPS( ); + + // clamp the fps in multiplayer games + if( fps != 0.0 ) + { + // limit fps to withing tolerable range + fps = bound( MIN_FPS, fps, MAX_FPS ); + + if(( host.realtime - oldtime ) < ( 1.0 / fps )) + return false; + } + + host.frametime = host.realtime - oldtime; + host.realframetime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); + oldtime = host.realtime; + + // NOTE: allow only in singleplayer while demos are not active + if( host_framerate->value > 0.0f && Host_IsLocalGame() && !CL_IsPlaybackDemo() && !CL_IsRecordDemo( )) + host.frametime = bound( MIN_FRAMETIME, host_framerate->value, MAX_FRAMETIME ); + else host.frametime = bound( MIN_FRAMETIME, host.frametime, MAX_FRAMETIME ); + + return true; +} + +/* +================= +Host_Frame +================= +*/ +void Host_Frame( float time ) +{ + Host_CheckSleep(); + + // decide the simulation time + if( !Host_FilterTime( time )) + return; + + Host_InputFrame (); // input frame + Host_ClientBegin (); // begin client + Host_GetCommands (); // dedicated in + Host_ServerFrame (); // server frame + Host_ClientFrame (); // client frame + + host.framecount++; +} + +/* +================= +Host_Error +================= +*/ +void Host_Error( const char *error, ... ) +{ + static char hosterror1[MAX_SYSPATH]; + static char hosterror2[MAX_SYSPATH]; + static qboolean recursive = false; + va_list argptr; + + if( host.mouse_visible && !CL_IsInMenu( )) + { + // hide VGUI mouse + while( ShowCursor( false ) >= 0 ); + host.mouse_visible = false; + } + + va_start( argptr, error ); + Q_vsprintf( hosterror1, error, argptr ); + va_end( argptr ); + + CL_WriteMessageHistory (); // before Q_error call + + if( host.framecount < 3 ) + { + Sys_Error( "Host_InitError: %s", hosterror1 ); + } + else if( host.framecount == host.errorframe ) + { + Sys_Error( "Host_MultiError: %s", hosterror2 ); + return; + } + else + { + if( host.allow_console ) + { + UI_SetActiveMenu( false ); + Key_SetKeyDest( key_console ); + Con_Printf( "Host_Error: %s", hosterror1 ); + } + else MSGBOX2( hosterror1 ); + } + + // host is shutting down. don't invoke infinite loop + if( host.status == HOST_SHUTDOWN ) return; + + if( recursive ) + { + Con_Printf( "Host_RecursiveError: %s", hosterror2 ); + Sys_Error( hosterror1 ); + return; // don't multiple executes + } + + recursive = true; + Q_strncpy( hosterror2, hosterror1, MAX_SYSPATH ); + host.errorframe = host.framecount; // to avoid multply calls per frame + Q_sprintf( host.finalmsg, "Server crashed: %s", hosterror1 ); + + // clearing cmd buffer to prevent execute any commands + COM_InitHostState(); + Cbuf_Clear(); + + Host_ShutdownServer(); + CL_Drop(); // drop clients + + // recreate world if needs + CL_ClearEdicts (); + + // release all models + Mod_FreeAll(); + + recursive = false; + Host_AbortCurrentFrame(); +} + +void Host_Error_f( void ) +{ + const char *error = Cmd_Argv( 1 ); + + if( !*error ) error = "Invoked host error"; + Host_Error( "%s\n", error ); +} + +void Sys_Error_f( void ) +{ + const char *error = Cmd_Argv( 1 ); + + if( !*error ) error = "Invoked sys error"; + Sys_Error( "%s\n", error ); +} + +/* +================= +Host_Crash_f +================= +*/ +static void Host_Crash_f( void ) +{ + *(int *)0 = 0xffffffff; +} + +/* +================= +Host_InitCommon +================= +*/ +void Host_InitCommon( const char *hostname, qboolean bChangeGame ) +{ + MEMORYSTATUS lpBuffer; + char dev_level[4]; + char progname[128]; + char cmdline[128]; + qboolean parse_cmdline = false; + char szTemp[MAX_SYSPATH]; + int developer = 0; + string szRootPath; + char *in, *out; + + lpBuffer.dwLength = sizeof( MEMORYSTATUS ); + GlobalMemoryStatus( &lpBuffer ); + + if( !GetCurrentDirectory( sizeof( host.rootdir ), host.rootdir )) + Sys_Error( "couldn't determine current directory\n" ); + + if( host.rootdir[Q_strlen( host.rootdir ) - 1] == '/' ) + host.rootdir[Q_strlen( host.rootdir ) - 1] = 0; + + host.oldFilter = SetUnhandledExceptionFilter( Sys_Crash ); + host.hInst = GetModuleHandle( NULL ); + host.change_game = bChangeGame; + host.config_executed = false; + host.status = HOST_INIT; // initialzation started + + Memory_Init(); // init memory subsystem + + progname[0] = cmdline[0] = '\0'; + in = (char *)hostname; + out = progname; + + while( *in != '\0' ) + { + if( parse_cmdline ) + { + *out++ = *in++; + } + else + { + if( *in == ' ' ) + { + parse_cmdline = true; + *out++ = '\0'; + out = cmdline; + } + else *out++ = *in++; + } + } + *out = '\0'; // write terminator + + Sys_ParseCommandLine( GetCommandLine( ), false ); + SetErrorMode( SEM_FAILCRITICALERRORS ); // no abort/retry/fail errors + + host.mempool = Mem_AllocPool( "Zone Engine" ); + + if( Sys_CheckParm( "-console" )) + host.allow_console = true; + + if( Sys_CheckParm( "-dev" )) + { + host.allow_console = true; + developer = DEV_NORMAL; + + if( Sys_GetParmFromCmdLine( "-dev", dev_level )) + { + if( Q_isdigit( dev_level )) + developer = bound( DEV_NONE, abs( Q_atoi( dev_level )), DEV_EXTENDED ); + } + } + + host.type = HOST_NORMAL; // predict state + host.con_showalways = true; + + // we can specified custom name, from Sys_NewInstance + if( GetModuleFileName( NULL, szTemp, sizeof( szTemp )) && !host.change_game ) + COM_FileBase( szTemp, SI.exeName ); + + COM_ExtractFilePath( szTemp, szRootPath ); + if( Q_stricmp( host.rootdir, szRootPath )) + { + Q_strncpy( host.rootdir, szRootPath, sizeof( host.rootdir )); + SetCurrentDirectory( host.rootdir ); + } + + if( SI.exeName[0] == '#' ) host.type = HOST_DEDICATED; + + // determine host type + if( progname[0] == '#' ) + { + Q_strncpy( SI.basedirName, progname + 1, sizeof( SI.basedirName )); + host.type = HOST_DEDICATED; + } + else Q_strncpy( SI.basedirName, progname, sizeof( SI.basedirName )); + + if( Sys_CheckParm( "-dedicated" )) + host.type = HOST_DEDICATED; + + if( host.type == HOST_DEDICATED ) + { + // check for duplicate dedicated server + host.hMutex = CreateMutex( NULL, 0, "Xash Dedicated Server" ); + + if( !host.hMutex ) + { + MSGBOX( "Dedicated server already running" ); + Sys_Quit(); + return; + } + + Sys_MergeCommandLine( cmdline ); + + CloseHandle( host.hMutex ); + host.hMutex = CreateSemaphore( NULL, 0, 1, "Xash Dedicated Server" ); + host.allow_console = true; + } + else + { + // don't show console as default + if( developer <= DEV_NORMAL ) + host.con_showalways = false; + } + + // member console allowing + host.allow_console_init = host.allow_console; + + Con_CreateConsole(); // system console used by dedicated server or show fatal errors + + // NOTE: this message couldn't be passed into game console but it doesn't matter + MsgDev( D_NOTE, "Sys_LoadLibrary: Loading xash.dll - ok\n" ); + + // get default screen res + VID_InitDefaultResolution(); + + // init host state machine + COM_InitHostState(); + + // startup cmds and cvars subsystem + Cmd_Init(); + Cvar_Init(); + + // share developer level across all dlls + Q_snprintf( dev_level, sizeof( dev_level ), "%i", developer ); + Cvar_DirectSet( &host_developer, dev_level ); + + Con_Init(); // early console running to catch all the messages + Cmd_AddCommand( "exec", Host_Exec_f, "execute a script file" ); + Cmd_AddCommand( "memlist", Host_MemStats_f, "prints memory pool information" ); + + FS_Init(); + Image_Init(); + Sound_Init(); + + FS_LoadGameInfo( NULL ); + Q_strncpy( host.gamefolder, GI->gamefolder, sizeof( host.gamefolder )); + + if( GI->secure ) + { + // clear all developer levels when game is protected + Cvar_DirectSet( &host_developer, "0" ); + host.allow_console_init = false; + host.con_showalways = false; + host.allow_console = false; + } + HPAK_Init(); + + IN_Init(); + Key_Init(); +} + +void Host_FreeCommon( void ) +{ + Image_Shutdown(); + Sound_Shutdown(); + Netchan_Shutdown(); + HPAK_FlushHostQueue(); + FS_Shutdown(); +} + +/* +================= +Host_Main +================= +*/ +int EXPORT Host_Main( const char *progname, int bChangeGame, pfnChangeGame func ) +{ + static double oldtime, newtime; + + pChangeGame = func; // may be NULL + + Host_InitCommon( progname, bChangeGame ); + + // init commands and vars + if( host_developer.value >= DEV_EXTENDED ) + { + Cmd_AddCommand ( "sys_error", Sys_Error_f, "just throw a fatal error to test shutdown procedures"); + Cmd_AddCommand ( "host_error", Host_Error_f, "just throw a host error to test shutdown procedures"); + Cmd_AddCommand ( "crash", Host_Crash_f, "a way to force a bus error for development reasons"); + } + + host_maxfps = Cvar_Get( "fps_max", "72", FCVAR_ARCHIVE, "host fps upper limit" ); + host_framerate = Cvar_Get( "host_framerate", "0", 0, "locks frame timing to this value in seconds" ); + host_gameloaded = Cvar_Get( "host_gameloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded game.dll" ); + host_clientloaded = Cvar_Get( "host_clientloaded", "0", FCVAR_READ_ONLY, "inidcates a loaded client.dll" ); + host_limitlocal = Cvar_Get( "host_limitlocal", "0", 0, "apply cl_cmdrate and rate to loopback connection" ); + con_gamemaps = Cvar_Get( "con_mapfilter", "1", FCVAR_ARCHIVE, "when true show only maps in game folder" ); + build = Cvar_Get( "build", va( "%i", Q_buildnum()), FCVAR_READ_ONLY, "returns a current build number" ); + ver = Cvar_Get( "ver", va( "%i/%g (hw build %i)", PROTOCOL_VERSION, XASH_VERSION, Q_buildnum()), FCVAR_READ_ONLY, "shows an engine version" ); + + Mod_Init(); + NET_Init(); + Netchan_Init(); + + // allow to change game from the console + if( pChangeGame != NULL ) + { + Cmd_AddCommand( "game", Host_ChangeGame_f, "change game" ); + Cvar_Get( "host_allow_changegame", "1", FCVAR_READ_ONLY, "allows to change games" ); + } + else + { + Cvar_Get( "host_allow_changegame", "0", FCVAR_READ_ONLY, "allows to change games" ); + } + + SV_Init(); + CL_Init(); + + if( host.type == HOST_DEDICATED ) + { + Con_InitConsoleCommands (); + + Cmd_AddCommand( "quit", Sys_Quit, "quit the game" ); + Cmd_AddCommand( "exit", Sys_Quit, "quit the game" ); + } + else Cmd_AddCommand( "minimize", Host_Minimize_f, "minimize main window to tray" ); + + host.errorframe = 0; + + // post initializations + switch( host.type ) + { + case HOST_NORMAL: + Con_ShowConsole( false ); // hide console + // execute startup config and cmdline + Cbuf_AddText( va( "exec %s.rc\n", SI.rcName )); + Cbuf_Execute(); + if( !host.config_executed ) + { + Cbuf_AddText( "exec config.cfg\n" ); + Cbuf_Execute(); + } + break; + case HOST_DEDICATED: + // allways parse commandline in dedicated-mode + host.stuffcmds_pending = true; + break; + } + + host.change_game = false; // done + Cmd_RemoveCommand( "setr" ); // remove potentially backdoor for change render settings + Cmd_RemoveCommand( "setgl" ); + Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline) + SCR_CheckStartupVids(); // must be last + + oldtime = Sys_DoubleTime() - 0.1; + + if( host.type == HOST_DEDICATED && GameState->nextstate == STATE_RUNFRAME ) + Con_Printf( "type 'map ' to run server... (TAB-autocomplete is working too)\n" ); + + // main window message loop + while( !host.crashed ) + { + newtime = Sys_DoubleTime (); + COM_Frame( newtime - oldtime ); + oldtime = newtime; + } + + // never reached + return 0; +} + +/* +================= +Host_Shutdown +================= +*/ +void EXPORT Host_Shutdown( void ) +{ + if( host.shutdown_issued ) return; + host.shutdown_issued = true; + + if( host.status != HOST_ERR_FATAL ) host.status = HOST_SHUTDOWN; // prepare host to normal shutdown + if( !host.change_game ) Q_strncpy( host.finalmsg, "Server shutdown", sizeof( host.finalmsg )); + + if( host.type == HOST_NORMAL ) + Host_WriteConfig(); + + SV_Shutdown( "" ); + CL_Shutdown(); + + Mod_Shutdown(); + NET_Shutdown(); + Host_FreeCommon(); + Con_DestroyConsole(); + + // must be last, console uses this + Mem_FreePool( &host.mempool ); + + // restore filter + if( host.oldFilter ) SetUnhandledExceptionFilter( host.oldFilter ); +} + +// main DLL entry point +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) +{ + hCurrent = hinstDLL; + return TRUE; +} \ No newline at end of file diff --git a/engine/common/host_state.c b/engine/common/host_state.c new file mode 100644 index 00000000..dfbc7ed2 --- /dev/null +++ b/engine/common/host_state.c @@ -0,0 +1,181 @@ +/* +host_cmd.c - dedicated and normal host +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +void COM_InitHostState( void ) +{ + memset( GameState, 0, sizeof( game_status_t )); +} + +static void Host_SetState( host_state_t newState, qboolean clearNext ) +{ + if( clearNext ) + GameState->nextstate = newState; + GameState->curstate = newState; +} + +static void Host_SetNextState( host_state_t nextState ) +{ + ASSERT( GameState->curstate == STATE_RUNFRAME ); + GameState->nextstate = nextState; +} + +void COM_NewGame( char const *pMapName ) +{ + if( GameState->nextstate != STATE_RUNFRAME ) + return; + + Q_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName )); + Host_SetNextState( STATE_LOAD_LEVEL ); + + GameState->backgroundMap = false; + GameState->landmarkName[0] = 0; + GameState->loadGame = false; + GameState->newGame = true; +} + +void COM_LoadLevel( char const *pMapName, qboolean background ) +{ + if( GameState->nextstate != STATE_RUNFRAME ) + return; + + Q_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName )); + Host_SetNextState( STATE_LOAD_LEVEL ); + + GameState->backgroundMap = background; + GameState->landmarkName[0] = 0; + GameState->loadGame = false; + GameState->newGame = false; +} + +void COM_LoadGame( char const *pMapName ) +{ + if( GameState->nextstate != STATE_RUNFRAME ) + return; + + Q_strncpy( GameState->levelName, pMapName, sizeof( GameState->levelName )); + Host_SetNextState( STATE_LOAD_GAME ); + GameState->backgroundMap = false; + GameState->newGame = false; + GameState->loadGame = true; +} + +void COM_ChangeLevel( char const *pNewLevel, char const *pLandmarkName, qboolean background ) +{ + if( GameState->nextstate != STATE_RUNFRAME ) + return; + + Q_strncpy( GameState->levelName, pNewLevel, sizeof( GameState->levelName )); + GameState->backgroundMap = background; + + if( COM_CheckString( pLandmarkName )) + { + Q_strncpy( GameState->landmarkName, pLandmarkName, sizeof( GameState->landmarkName )); + GameState->loadGame = true; + } + else + { + GameState->landmarkName[0] = 0; + GameState->loadGame = false; + } + + Host_SetNextState( STATE_CHANGELEVEL ); + GameState->newGame = false; +} + +void Host_ShutdownGame( void ) +{ + SV_ShutdownGame(); + + switch( GameState->nextstate ) + { + case STATE_LOAD_GAME: + case STATE_LOAD_LEVEL: + Host_SetState( GameState->nextstate, true ); + break; + default: + Host_SetState( STATE_RUNFRAME, true ); + break; + } +} + +void Host_RunFrame( float time ) +{ + // engine main frame + Host_Frame( time ); + + switch( GameState->nextstate ) + { + case STATE_RUNFRAME: + break; + case STATE_LOAD_GAME: + case STATE_LOAD_LEVEL: + SCR_BeginLoadingPlaque( GameState->backgroundMap ); + // intentionally fallthrough + case STATE_GAME_SHUTDOWN: + Host_SetState( STATE_GAME_SHUTDOWN, false ); + break; + case STATE_CHANGELEVEL: + SCR_BeginLoadingPlaque( GameState->backgroundMap ); + Host_SetState( GameState->nextstate, true ); + break; + default: + Host_SetState( STATE_RUNFRAME, true ); + break; + } +} + +void COM_Frame( float time ) +{ + int loopCount = 0; + + if( setjmp( host.abortframe )) + return; + + while( 1 ) + { + int oldState = GameState->curstate; + + // execute the current state (and transition to the next state if not in HS_RUN) + switch( GameState->curstate ) + { + case STATE_LOAD_LEVEL: + SV_ExecLoadLevel(); + Host_SetState( STATE_RUNFRAME, true ); + break; + case STATE_LOAD_GAME: + SV_ExecLoadGame(); + Host_SetState( STATE_RUNFRAME, true ); + break; + case STATE_CHANGELEVEL: + SV_ExecChangeLevel(); + Host_SetState( STATE_RUNFRAME, true ); + break; + case STATE_RUNFRAME: + Host_RunFrame( time ); + break; + case STATE_GAME_SHUTDOWN: + Host_ShutdownGame(); + break; + } + + if( oldState == STATE_RUNFRAME ) + break; + + if(( GameState->curstate == oldState ) || ( ++loopCount > 8 )) + Sys_Error( "state infinity loop!\n" ); + } +} \ No newline at end of file diff --git a/engine/common/hpak.c b/engine/common/hpak.c new file mode 100644 index 00000000..a83ed0e4 --- /dev/null +++ b/engine/common/hpak.c @@ -0,0 +1,1088 @@ +/* +hpak.c - custom user package to send other clients +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "filesystem.h" + +#define HPAK_MAX_ENTRIES 0x8000 +#define HPAK_MIN_SIZE (1 * 1024) +#define HPAK_MAX_SIZE (128 * 1024) + +typedef struct hash_pack_queue_s +{ + char *name; + resource_t resource; + size_t size; + void *data; + struct hash_pack_queue_s *next; +} hash_pack_queue_t; + +convar_t *hpk_maxsize; +hash_pack_queue_t *gp_hpak_queue = NULL; +hpak_header_t hash_pack_header; +hpak_info_t hash_pack_info; + +const char *HPAK_TypeFromIndex( int type ) +{ + switch( type ) + { + case t_sound: return "decal"; + case t_skin: return "skin"; + case t_model: return "model"; + case t_decal: return "decal"; + case t_generic: return "generic"; + case t_eventscript: return "event"; + case t_world: return "map"; + } + return "?"; +} + +static void HPAK_AddToQueue( const char *name, resource_t *pResource, void *data, file_t *f ) +{ + hash_pack_queue_t *p; + + p = Z_Malloc( sizeof( hash_pack_queue_t )); + p->name = copystring( name ); + p->resource = *pResource; + p->size = pResource->nDownloadSize; + p->data = Z_Malloc( p->size ); + + if( data != NULL ) memcpy( p->data, data, p->size ); + else if( f != NULL ) FS_Read( f, p->data, p->size ); + else Host_Error( "HPAK_AddToQueue: data == NULL.\n" ); + + p->next = gp_hpak_queue; + gp_hpak_queue = p; +} + +void HPAK_FlushHostQueue( void ) +{ + hash_pack_queue_t *p; + + for( p = gp_hpak_queue; p != NULL; p = gp_hpak_queue ) + { + gp_hpak_queue = p->next; + HPAK_AddLump( false, p->name, &p->resource, p->data, NULL ); + freestring( p->name ); + Mem_Free( p->data ); + Mem_Free( p ); + } + gp_hpak_queue = NULL; +} + +void HPAK_CreatePak( const char *filename, resource_t *pResource, byte *pData, file_t *fin ) +{ + int filelocation; + string pakname; + char md5[16]; + char *temp; + file_t *fout; + MD5Context_t ctx; + + if( !COM_CheckString( filename )) + return; + + if(( fin != NULL && pData != NULL ) || ( fin == NULL && pData == NULL )) + { + MsgDev( D_ERROR, "HPAK_CreatePak, must specify one of pData or fpSource\n" ); + return; + } + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + MsgDev( D_INFO, "creating HPAK %s.\n", pakname ); + + fout = FS_Open( pakname, "wb", false ); + if( !fout ) + { + MsgDev( D_ERROR, "HPAK_CreatePak: can't write %s.\n", pakname ); + return; + } + + // let's hash it. + memset( &ctx, 0, sizeof( MD5Context_t )); + MD5Init( &ctx ); + + if( pData == NULL ) + { + // there are better ways + filelocation = FS_Tell( fin ); + temp = Z_Malloc( pResource->nDownloadSize ); + FS_Read( fin, temp, pResource->nDownloadSize ); + FS_Seek( fin, filelocation, SEEK_SET ); + MD5Update( &ctx, temp, pResource->nDownloadSize ); + Mem_Free( temp ); + } + else + { + MD5Update( &ctx, pData, pResource->nDownloadSize ); + } + + MD5Final( md5, &ctx ); + + if( memcmp( md5, pResource->rgucMD5_hash, 16 )) + { + MsgDev( D_ERROR, "HPAK_CreatePak: bad checksum for %s. Ignored\n", pakname ); + return; + } + + hash_pack_header.ident = IDHPAKHEADER; + hash_pack_header.version = IDHPAK_VERSION; + hash_pack_header.infotableofs = 0; + + FS_Write( fout, &hash_pack_header, sizeof( hash_pack_header )); + + hash_pack_info.count = 1; + hash_pack_info.entries = Z_Malloc( sizeof( hpak_lump_t )); + hash_pack_info.entries[0].resource = *pResource; + hash_pack_info.entries[0].filepos = FS_Tell( fout ); + hash_pack_info.entries[0].disksize = pResource->nDownloadSize; + + if( pData == NULL ) + { + FS_FileCopy( fout, fin, hash_pack_info.entries[0].disksize ); + } + else + { + FS_Write( fout, pData, hash_pack_info.entries[0].disksize ); + } + + filelocation = FS_Tell( fout ); + FS_Write( fout, &hash_pack_info.count, sizeof( hash_pack_info.count )); + FS_Write( fout, &hash_pack_info.entries[0], sizeof( hpak_lump_t )); + + if( hash_pack_info.entries ) + Mem_Free( hash_pack_info.entries ); + memset( &hash_pack_info, 0, sizeof( hpak_info_t )); + + hash_pack_header.infotableofs = filelocation; + FS_Seek( fout, 0, SEEK_SET ); + FS_Write( fout, &hash_pack_header, sizeof( hpak_header_t )); + FS_Close( fout ); +} + +static qboolean HPAK_FindResource( hpak_info_t *hpk, byte *hash, resource_t *pResource ) +{ + int i; + + for( i = 0; i < hpk->count; i++ ) + { + if( !memcmp( hpk->entries[i].resource.rgucMD5_hash, hash, 16 )) + { + if( pResource ) + *pResource = hpk->entries[i].resource; + return true; + } + } + + return false; +} + +void HPAK_AddLump( qboolean bUseQueue, const char *name, resource_t *pResource, byte *pData, file_t *pFile ) +{ + int i, j, position, length; + hpak_lump_t *pCurrentEntry = NULL; + string srcname, dstname; + hpak_info_t srcpak, dstpak; + file_t *file_src; + file_t *file_dst; + char md5[16]; + byte *temp; + MD5Context_t ctx; + + if( pData == NULL && pFile == NULL ) + { + MsgDev( D_ERROR, "HPAK_AddLump: no data\n" ); + return; + } + + if( pResource->nDownloadSize < HPAK_MIN_SIZE || pResource->nDownloadSize > HPAK_MAX_SIZE ) + { + Con_Printf( S_ERROR "%s: invalid size %s\n", name, Q_pretifymem( pResource->nDownloadSize, 2 )); + return; + } + + // hash it + memset( &ctx, 0, sizeof( MD5Context_t )); + MD5Init( &ctx ); + + if( pData == NULL ) + { + // there are better ways + position = FS_Tell( pFile ); + temp = Z_Malloc( pResource->nDownloadSize ); + FS_Read( pFile, temp, pResource->nDownloadSize ); + FS_Seek( pFile, position, SEEK_SET ); + MD5Update( &ctx, temp, pResource->nDownloadSize ); + Mem_Free( temp ); + } + else + { + MD5Update( &ctx, pData, pResource->nDownloadSize ); + } + + MD5Final( md5, &ctx ); + + if( memcmp( md5, pResource->rgucMD5_hash, 16 )) + { + MsgDev( D_ERROR, "HPAK_AddLump: bad checksum for %s. Ignored\n", pResource->szFileName ); + return; + } + + if( bUseQueue ) + { + HPAK_AddToQueue( name, pResource, pData, pFile ); + return; + } + + Q_strncpy( srcname, name, sizeof( srcname )); + COM_ReplaceExtension( srcname, ".hpk" ); + + file_src = FS_Open( srcname, "rb", false ); + + if( !file_src ) + { + // just create new pack + HPAK_CreatePak( name, pResource, pData, pFile ); + return; + } + + Q_strncpy( dstname, srcname, sizeof( dstname )); + COM_ReplaceExtension( dstname, ".hp2" ); + + file_dst = FS_Open( dstname, "wb", false ); + + if( !file_dst ) + { + MsgDev( D_ERROR, "HPAK_AddLump: couldn't open %s.\n", srcname ); + FS_Close( file_src ); + return; + } + + // load headers + FS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t )); + + if( hash_pack_header.version != IDHPAK_VERSION ) + { + // we don't check the HPAK bit for some reason. + MsgDev( D_ERROR, "HPAK_AddLump: %s does not have a valid header.\n", srcname ); + FS_Close( file_src ); + FS_Close( file_dst ); + } + + length = FS_FileLength( file_src ); + FS_Seek( file_src, 0, SEEK_SET ); // rewind to start of file + FS_FileCopy( file_dst, file_src, length ); + + FS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET ); + FS_Read( file_src, &srcpak.count, sizeof( srcpak.count )); + + if( srcpak.count < 1 || srcpak.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "HPAK_AddLump: %s contain too many lumps.\n", srcname ); + FS_Close( file_src ); + FS_Close( file_dst ); + return; + } + + // load the data + srcpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * srcpak.count ); + FS_Read( file_src, srcpak.entries, sizeof( hpak_lump_t ) * srcpak.count ); + FS_Close( file_src ); + + // check if already exists + if( HPAK_FindResource( &srcpak, pResource->rgucMD5_hash, NULL )) + { + Z_Free( srcpak.entries ); + FS_Close( file_dst ); + FS_Delete( dstname ); + return; + } + + // make a new container + dstpak.count = srcpak.count + 1; + dstpak.entries = Z_Malloc( sizeof( hpak_lump_t ) * dstpak.count ); + memcpy( dstpak.entries, srcpak.entries, srcpak.count ); + + for( i = 0; i < srcpak.count; i++ ) + { + if( memcmp( md5, srcpak.entries[i].resource.rgucMD5_hash, 16 )) + { + pCurrentEntry = &dstpak.entries[i]; + + for( j = i; j < srcpak.count; j++ ) + dstpak.entries[j + 1] = srcpak.entries[j]; + } + } + + if( !pCurrentEntry ) + pCurrentEntry = &dstpak.entries[dstpak.count-1]; + + memset( pCurrentEntry, 0, sizeof( hpak_lump_t )); + FS_Seek( file_dst, hash_pack_header.infotableofs, SEEK_SET ); + pCurrentEntry->resource = *pResource; + pCurrentEntry->filepos = FS_Tell( file_dst ); + pCurrentEntry->disksize = pResource->nDownloadSize; + + if( !pData ) FS_FileCopy( file_dst, file_src, pCurrentEntry->disksize ); + else FS_Write( file_dst, pData, pCurrentEntry->disksize ); + + hash_pack_header.infotableofs = FS_Tell( file_dst ); + FS_Write( file_dst, &dstpak.count, sizeof( dstpak.count )); + + for( i = 0; i < dstpak.count; i++ ) + { + FS_Write( file_dst, &dstpak.entries[i], sizeof( hpak_lump_t )); + } + + // finalize + if( srcpak.entries ) + Mem_Free( srcpak.entries ); + if( dstpak.entries ) + Mem_Free( dstpak.entries ); + + FS_Seek( file_dst, 0, SEEK_SET ); + FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); + FS_Close( file_dst ); + + FS_Delete( srcname ); + FS_Rename( dstname, srcname ); +} + +static qboolean HPAK_Validate( const char *filename, qboolean quiet ) +{ + file_t *f; + hpak_lump_t *dataDir; + hpak_header_t hdr; + byte *dataPak; + int i, num_lumps; + MD5Context_t MD5_Hash; + string pakname; + resource_t *pRes; + char md5[16]; + + if( quiet ) HPAK_FlushHostQueue(); + + // not an error - just flush queue + if( !filename || !*filename ) + return true; + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) + { + MsgDev( D_INFO, "Couldn't find %s.\n", pakname ); + return true; + } + + if( !quiet ) MsgDev( D_INFO, "Validating %s\n", pakname ); + + FS_Read( f, &hdr, sizeof( hdr )); + if( hdr.ident != IDHPAKHEADER || hdr.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "HPAK_ValidatePak: %s does not have a valid HPAK header.\n", pakname ); + FS_Close( f ); + return false; + } + + FS_Seek( f, hdr.infotableofs, SEEK_SET ); + FS_Read( f, &num_lumps, sizeof( num_lumps )); + + if( num_lumps < 1 || num_lumps > MAX_FILES_IN_WAD ) + { + MsgDev( D_ERROR, "HPAK_ValidatePak: %s has too many lumps %u.\n", pakname, num_lumps ); + FS_Close( f ); + return false; + } + + if( !quiet ) MsgDev( D_INFO, "# of Entries: %i\n", num_lumps ); + + dataDir = Z_Malloc( sizeof( hpak_lump_t ) * num_lumps ); + FS_Read( f, dataDir, sizeof( hpak_lump_t ) * num_lumps ); + + if( !quiet ) MsgDev( D_INFO, "# Type Size FileName : MD5 Hash\n" ); + + for( i = 0; i < num_lumps; i++ ) + { + if( dataDir[i].disksize < 1 || dataDir[i].disksize > 131071 ) + { + // odd max size + MsgDev( D_ERROR, "HPAK_ValidatePak: lump %i has invalid size %s\n", i, Q_pretifymem( dataDir[i].disksize, 2 )); + Mem_Free( dataDir ); + FS_Close(f); + return false; + } + + dataPak = Z_Malloc( dataDir[i].disksize ); + FS_Seek( f, dataDir[i].filepos, SEEK_SET ); + FS_Read( f, dataPak, dataDir[i].disksize ); + + memset( &MD5_Hash, 0, sizeof( MD5Context_t )); + MD5Init( &MD5_Hash ); + MD5Update( &MD5_Hash, dataPak, dataDir[i].disksize ); + MD5Final( md5, &MD5_Hash ); + + pRes = &dataDir[i].resource; + + MsgDev( D_INFO, "%i: %s %s %s: ", i, HPAK_TypeFromIndex( pRes->type ), + Q_pretifymem( pRes->nDownloadSize, 2 ), pRes->szFileName ); + + if( memcmp( md5, pRes->rgucMD5_hash, 0x10 )) + { + if( quiet ) + { + MsgDev( D_ERROR, "HPAK_ValidatePak: %s has invalid checksum.\n", pakname ); + Mem_Free( dataPak ); + Mem_Free( dataDir ); + FS_Close( f ); + return false; + } + else MsgDev( D_INFO, "failed\n" ); + } + else + { + if( !quiet ) MsgDev( D_INFO, "OK\n" ); + } + + // at this point, it's passed our checks. + Mem_Free( dataPak ); + } + + Mem_Free( dataDir ); + FS_Close( f ); + return true; +} + +void HPAK_ValidatePak( const char *filename ) +{ + HPAK_Validate( filename, true ); +} + +void HPAK_CheckIntegrity( const char *filename ) +{ + string pakname; + + if( !filename || !filename[0] ) + return; + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + HPAK_ValidatePak( pakname ); +} + +void HPAK_CheckSize( const char *filename ) +{ + string pakname; + int maxsize; + + maxsize = hpk_maxsize->value; + if( maxsize <= 0 ) return; + + if( !filename || !filename[0] ) + return; + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + if( FS_FileSize( pakname, false ) > ( maxsize * 1000000 )) + { + Con_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); + Log_Printf( "Server: Size of %s > %f MB, deleting.\n", filename, hpk_maxsize->value ); + FS_Delete( filename ); + } +} + +qboolean HPAK_ResourceForHash( const char *filename, byte *hash, resource_t *pResource ) +{ + hpak_info_t directory; + hpak_header_t header; + string pakname; + qboolean bFound; + file_t *f; + hash_pack_queue_t *p; + + if( !COM_CheckString( filename )) + return false; + + for( p = gp_hpak_queue; p != NULL; p = p->next ) + { + if( !Q_stricmp( p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, hash, 16 )) + { + if( pResource != NULL ) + *pResource = p->resource; + return true; + } + } + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) return false; + + FS_Read( f, &header, sizeof( header )); + + if( header.ident != IDHPAKHEADER ) + { + FS_Close( f ); + return false; + } + + if( header.version != IDHPAK_VERSION ) + { + FS_Close( f ); + return false; + } + + FS_Seek( f, header.infotableofs, SEEK_SET ); + FS_Read( f, &directory.count, sizeof( directory.count )); + + if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) + { + FS_Close( f ); + return false; + } + + directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); + FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); + bFound = HPAK_FindResource( &directory, hash, pResource ); + Mem_Free( directory.entries ); + FS_Close( f ); + + return bFound; +} + +static qboolean HPAK_ResourceForIndex( const char *filename, int index, resource_t *pResource ) +{ + hpak_header_t header; + hpak_info_t directory; + string pakname; + file_t *f; + + if( !filename || !filename[0] ) + return false; + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) + { + MsgDev( D_ERROR, "couldn't open %s.\n", pakname ); + return false; + } + + FS_Read( f, &header, sizeof( header )); + if( header.ident != IDHPAKHEADER ) + { + MsgDev( D_ERROR, "%s is not an HPAK file\n", pakname ); + FS_Close( f ); + return false; + } + + if( header.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); + FS_Close( f ); + return false; + } + + FS_Seek( f, header.infotableofs, SEEK_SET ); + FS_Read( f, &directory.count, sizeof( directory.count )); + + if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "%s has too many lumps %u.\n", pakname, directory.count ); + FS_Close( f ); + return false; + } + + if( index < 1 || index > directory.count ) + { + MsgDev( D_ERROR, "%s, lump with index %i doesn't exist.\n", pakname, index ); + FS_Close( f ); + return false; + } + + directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); + FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); + *pResource = directory.entries[index-1].resource; + Z_Free( directory.entries ); + FS_Close( f ); + + return true; +} + +qboolean HPAK_GetDataPointer( const char *filename, resource_t *pResource, byte **buffer, int *bufsize ) +{ + byte *tmpbuf; + string pakname; + hpak_header_t header; + hpak_info_t directory; + hpak_lump_t *entry; + hash_pack_queue_t *p; + file_t *f; + int i; + + if( !COM_CheckString( filename )) + return false; + + if( buffer ) *buffer = NULL; + if( bufsize ) *bufsize = 0; + + for( p = gp_hpak_queue; p != NULL; p = p->next ) + { + if( !Q_stricmp(p->name, filename ) && !memcmp( p->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + { + if( buffer ) + { + tmpbuf = Z_Malloc( p->size ); + memcpy( tmpbuf, p->data, p->size ); + *buffer = tmpbuf; + } + + if( bufsize ) + *bufsize = p->size; + + return true; + } + } + + Q_strncpy( pakname, filename, sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) return false; + + FS_Read( f, &header, sizeof( header )); + + if( header.ident != IDHPAKHEADER ) + { + MsgDev( D_ERROR, "%s it's not a HPK file.\n", pakname ); + FS_Close( f ); + return false; + } + + if( header.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); + FS_Close( f ); + return false; + } + + FS_Seek( f, header.infotableofs, SEEK_SET ); + FS_Read( f, &directory.count, sizeof( directory.count )); + + if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "HPAK_GetDataPointer: %s has too many lumps %u.\n", filename, directory.count ); + FS_Close( f ); + return false; + } + + directory.entries = Z_Malloc( sizeof( hpak_lump_t ) * directory.count ); + FS_Read( f, directory.entries, sizeof( hpak_lump_t ) * directory.count ); + + for( i = 0; i < directory.count; i++ ) + { + entry = &directory.entries[i]; + + if( !memcmp( entry->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + { + FS_Seek( f, entry->filepos, SEEK_SET ); + + if( buffer && entry->disksize > 0 ) + { + tmpbuf = Z_Malloc( entry->disksize ); + FS_Read( f, tmpbuf, entry->disksize ); + *buffer = tmpbuf; + } + + if( bufsize ) + *bufsize = entry->disksize; + + Mem_Free( directory.entries ); + FS_Close( f ); + + return true; + } + } + + Mem_Free( directory.entries ); + FS_Close( f ); + + return false; +} + +void HPAK_RemoveLump( const char *name, resource_t *pResource ) +{ + string read_path; + string save_path; + file_t *file_src; + file_t *file_dst; + hpak_info_t hpak_read; + hpak_info_t hpak_save; + int i, j; + + if( !COM_CheckString( name ) || !pResource ) + return; + + HPAK_FlushHostQueue(); + + Q_strncpy( read_path, name, sizeof( read_path )); + COM_ReplaceExtension( read_path, ".hpk" ); + + file_src = FS_Open( read_path, "rb", false ); + if( !file_src ) + { + MsgDev( D_ERROR, "%s couldn't open.\n", read_path ); + return; + } + + Q_strncpy( save_path, read_path, sizeof( save_path )); + COM_ReplaceExtension( save_path, ".hp2" ); + file_dst = FS_Open( save_path, "wb", false ); + + if( !file_dst ) + { + MsgDev( D_ERROR, "%s couldn't open.\n", save_path ); + FS_Close( file_src ); + return; + } + + FS_Seek( file_src, 0, SEEK_SET ); + FS_Seek( file_dst, 0, SEEK_SET ); + + // header copy + FS_Read( file_src, &hash_pack_header, sizeof( hpak_header_t )); + FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); + + if( hash_pack_header.ident != IDHPAKHEADER || hash_pack_header.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "%s has invalid header.\n", read_path ); + FS_Close( file_src ); + FS_Close( file_dst ); + FS_Delete( save_path ); // delete temp file + return; + } + + FS_Seek( file_src, hash_pack_header.infotableofs, SEEK_SET ); + FS_Read( file_src, &hpak_read.count, sizeof( hpak_read.count )); + + if( hpak_read.count < 1 || hpak_read.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "%s has invalid number of lumps.\n", read_path ); + FS_Close( file_src ); + FS_Close( file_dst ); + FS_Delete( save_path ); // delete temp file + return; + } + + if( hpak_read.count == 1 ) + { + MsgDev( D_WARN, "%s only has one element, so HPAK will be removed\n", read_path ); + FS_Close( file_src ); + FS_Close( file_dst ); + FS_Delete( read_path ); + FS_Delete( save_path ); + return; + } + + hpak_save.count = hpak_read.count - 1; + hpak_read.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_read.count ); + hpak_save.entries = Z_Malloc( sizeof( hpak_lump_t ) * hpak_save.count ); + + FS_Read( file_src, hpak_read.entries, sizeof( hpak_lump_t ) * hpak_read.count ); + + if( !HPAK_FindResource( &hpak_read, pResource->rgucMD5_hash, NULL )) + { + MsgDev( D_ERROR, "HPAK doesn't contain specified lump: %s\n", pResource->szFileName, read_path ); + Mem_Free( hpak_read.entries ); + Mem_Free( hpak_save.entries ); + FS_Close( file_src ); + FS_Close( file_dst ); + FS_Delete( save_path ); + return; + } + + MsgDev( D_INFO, "Removing %s from HPAK %s.\n", pResource->szFileName, read_path ); + + // If there's a collision, we've just corrupted this hpak. + for( i = 0, j = 0; i < hpak_read.count; i++ ) + { + if( !memcmp( hpak_read.entries[i].resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + continue; + + hpak_save.entries[j] = hpak_read.entries[i]; + hpak_save.entries[j].filepos = FS_Tell( file_dst ); + FS_Seek( file_src, hpak_read.entries[j].filepos, SEEK_SET ); + FS_FileCopy( file_dst, file_src, hpak_save.entries[j].disksize ); + j++; + } + + hash_pack_header.infotableofs = FS_Tell( file_dst ); + FS_Write( file_dst, &hpak_save.count, sizeof( hpak_save.count )); + + for( i = 0; i < hpak_save.count; i++ ) + FS_Write( file_dst, &hpak_save.entries[i], sizeof( hpak_lump_t )); + + FS_Seek( file_dst, 0, SEEK_SET ); + FS_Write( file_dst, &hash_pack_header, sizeof( hpak_header_t )); + + Mem_Free( hpak_read.entries ); + Mem_Free( hpak_save.entries ); + FS_Close( file_src ); + FS_Close( file_dst ); + + FS_Delete( read_path ); + FS_Rename( save_path, read_path ); +} + +void HPAK_List_f( void ) +{ + int nCurrent; + hpak_header_t header; + hpak_info_t directory; + hpak_lump_t *entry; + string lumpname; + string pakname; + const char *type; + const char *size; + file_t *f; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "hpklist \n" ); + return; + } + + HPAK_FlushHostQueue(); + + Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + Con_Printf( "Contents for %s.\n", pakname ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) + { + MsgDev( D_ERROR, "couldn't open %s.\n", pakname ); + return; + } + + FS_Read( f, &header, sizeof( hpak_header_t )); + + if( header.ident != IDHPAKHEADER ) + { + MsgDev( D_ERROR, "%s is not an HPAK file\n", pakname ); + FS_Close( f ); + return; + } + + if( header.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); + FS_Close( f ); + return; + } + + FS_Seek( f, header.infotableofs, SEEK_SET ); + FS_Read( f, &directory.count, sizeof( directory.count )); + + if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "%s has too many lumps %u.\n", pakname, directory.count ); + FS_Close( f ); + return; + } + + Con_Printf( "# of Entries: %i\n", directory.count ); + Con_Printf( "# Type Size FileName : MD5 Hash\n" ); + + directory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t )); + FS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t )); + + for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) + { + entry = &directory.entries[nCurrent]; + COM_FileBase( entry->resource.szFileName, lumpname ); + type = HPAK_TypeFromIndex( entry->resource.type ); + size = Q_memprint( entry->resource.nDownloadSize ); + + Con_Printf( "%i: %10s %s %s\n : %s\n", nCurrent + 1, type, size, lumpname, MD5_Print( entry->resource.rgucMD5_hash )); + } + + if( directory.entries ) + Mem_Free( directory.entries ); + FS_Close( f ); +} + +void HPAK_Extract_f( void ) +{ + int nCurrent; + hpak_header_t header; + hpak_info_t directory; + hpak_lump_t *entry; + string lumpname; + string pakname; + string szFileOut; + int nIndex; + byte *pData; + int nDataSize; + const char *type; + const char *size; + file_t *f; + + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "hpkextract hpkname [all | single index]\n" ); + return; + } + + if( !Q_stricmp( Cmd_Argv( 2 ), "all" )) + { + nIndex = -1; + } + else + { + nIndex = Q_atoi( Cmd_Argv( 2 ) ); + } + + HPAK_FlushHostQueue(); + + Q_strncpy( pakname, Cmd_Argv( 1 ), sizeof( pakname )); + COM_ReplaceExtension( pakname, ".hpk" ); + Con_Printf( "Contents for %s.\n", pakname ); + + f = FS_Open( pakname, "rb", false ); + if( !f ) + { + MsgDev( D_ERROR, "couldn't open %s.\n", pakname ); + return; + } + + FS_Read( f, &header, sizeof( hpak_header_t )); + + if( header.ident != IDHPAKHEADER ) + { + MsgDev( D_ERROR, "%s is not an HPAK file\n", pakname ); + FS_Close( f ); + return; + } + + if( header.version != IDHPAK_VERSION ) + { + MsgDev( D_ERROR, "%s has invalid version (%i should be %i).\n", pakname, header.version, IDHPAK_VERSION ); + FS_Close( f ); + return; + } + + FS_Seek( f, header.infotableofs, SEEK_SET ); + FS_Read( f, &directory.count, sizeof( directory.count )); + + if( directory.count < 1 || directory.count > HPAK_MAX_ENTRIES ) + { + MsgDev( D_ERROR, "%s has too many lumps %u.\n", pakname, directory.count ); + FS_Close( f ); + return; + } + + if( nIndex == -1 ) Con_Printf( "Extracting all lumps from %s.\n", pakname ); + else Con_Printf( "Extracting lump %i from %s\n", nIndex, pakname ); + + directory.entries = Z_Malloc( directory.count * sizeof( hpak_lump_t )); + FS_Read( f, directory.entries, directory.count * sizeof( hpak_lump_t )); + + for( nCurrent = 0; nCurrent < directory.count; nCurrent++ ) + { + entry = &directory.entries[nCurrent]; + + if( nIndex != -1 && nIndex != nCurrent ) + continue; + + COM_FileBase( entry->resource.szFileName, lumpname ); + type = HPAK_TypeFromIndex( entry->resource.type ); + size = Q_memprint( entry->resource.nDownloadSize ); + + Con_Printf( "Extracting %i: %10s %s %s\n", nCurrent + 1, type, size, lumpname ); + + if( entry->disksize <= 0 || entry->disksize >= HPAK_MAX_SIZE ) + { + MsgDev( D_WARN, "Unable to extract data, size invalid: %s\n", nDataSize ); + continue; + } + + nDataSize = entry->disksize; + pData = Z_Malloc( nDataSize + 1 ); + FS_Seek( f, entry->filepos, SEEK_SET ); + FS_Read( f, pData, nDataSize ); + + Q_snprintf( szFileOut, sizeof( szFileOut ), "hpklmps\\lmp%04i.bmp", nCurrent ); + FS_WriteFile( szFileOut, pData, nDataSize ); + if( pData ) Mem_Free( pData ); + } + + if( directory.entries ) + Mem_Free( directory.entries ); + + FS_Close( f ); +} + +void HPAK_Remove_f( void ) +{ + resource_t resource; + + HPAK_FlushHostQueue(); + + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "hpkremove \n" ); + return; + } + + if( HPAK_ResourceForIndex( Cmd_Argv( 1 ), Q_atoi( Cmd_Argv( 2 )), &resource )) + { + HPAK_RemoveLump( Cmd_Argv( 1 ), &resource ); + } + else + { + MsgDev( D_ERROR, "Could not locate resource %i in %s\n", Q_atoi( Cmd_Argv( 2 )), Cmd_Argv( 1 )); + } +} + +void HPAK_Validate_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "hpkval \n" ); + return; + } + + HPAK_Validate( Cmd_Argv( 1 ), false ); +} + +void HPAK_Init( void ) +{ + Cmd_AddCommand( "hpklist", HPAK_List_f, "list all files in specified HPK-file" ); + Cmd_AddCommand( "hpkremove", HPAK_Remove_f, "remove specified file from HPK-file" ); + Cmd_AddCommand( "hpkval", HPAK_Validate_f, "validate specified HPK-file" ); + Cmd_AddCommand( "hpkextract", HPAK_Extract_f, "extract all lumps from specified HPK-file" ); + hpk_maxsize = Cvar_Get( "hpk_maxsize", "0", FCVAR_ARCHIVE, "set limit by size for all HPK-files ( 0 - unlimited )" ); + + gp_hpak_queue = NULL; +} \ No newline at end of file diff --git a/engine/common/imagelib/imagelib.h b/engine/common/imagelib/imagelib.h new file mode 100644 index 00000000..951103ce --- /dev/null +++ b/engine/common/imagelib/imagelib.h @@ -0,0 +1,330 @@ +/* +imagelib.h - engine image lib +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef IMAGELIB_H +#define IMAGELIB_H + +#include "common.h" + +// skyorder_q2[6] = { 2, 3, 1, 0, 4, 5, }; // Quake, Half-Life skybox ordering +// skyorder_ms[6] = { 4, 5, 1, 0, 2, 3 }; // Microsoft DDS ordering (reverse) + +// cubemap hints +typedef enum +{ + CB_HINT_NO = 0, + + // dds cubemap hints ( Microsoft sides order ) + CB_HINT_POSX, + CB_HINT_NEGX, + CB_HINT_POSZ, + CB_HINT_NEGZ, + CB_HINT_POSY, + CB_HINT_NEGY, + CB_FACECOUNT, +} side_hint_t; + +typedef enum +{ + IL_HINT_NO = 0, + IL_HINT_Q1, // palette choosing + IL_HINT_HL, +} image_hint_t; + +typedef struct loadformat_s +{ + const char *formatstring; + const char *ext; + qboolean (*loadfunc)( const char *name, const byte *buffer, size_t filesize ); + image_hint_t hint; +} loadpixformat_t; + +typedef struct saveformat_s +{ + const char *formatstring; + const char *ext; + qboolean (*savefunc)( const char *name, rgbdata_t *pix ); +} savepixformat_t; + +typedef struct imglib_s +{ + const loadpixformat_t *loadformats; + const savepixformat_t *saveformats; + + // current 2d image state + word width; + word height; + word depth; + byte num_mips; // mipmap count + word encode; // custom encode type + uint type; // main type switcher + uint flags; // additional image flags + size_t size; // image rgba size (for bounds checking) + uint ptr; // safe image pointer + int bpp; // PFDesc[type].bpp + byte *rgba; // image pointer (see image_type for details) + + // current cubemap state + int source_width; // locked cubemap dims (all wrong sides will be automatically resampled) + int source_height; + uint source_type; // shared image type for all mipmaps or cubemap sides + int num_sides; // how much sides is loaded + byte *cubemap; // cubemap pack + + // indexed images state + uint *d_currentpal; // installed version of internal palette + int d_rendermode; // palette rendermode + byte *palette; // palette pointer + + // global parms + rgba_t fogParams; // some water textures has info about underwater fog + + image_hint_t hint; // hint for some loaders + byte *tempbuffer; // for convert operations + int cmd_flags; // global imglib flags + int force_flags; // override cmd_flags +} imglib_t; + +/* +======================================================================== + +.BMP image format + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct +{ + char id[2]; // bmfh.bfType + dword fileSize; // bmfh.bfSize + dword reserved0; // bmfh.bfReserved1 + bmfh.bfReserved2 + dword bitmapDataOffset; // bmfh.bfOffBits + dword bitmapHeaderSize; // bmih.biSize + int width; // bmih.biWidth + int height; // bmih.biHeight + word planes; // bmih.biPlanes + word bitsPerPixel; // bmih.biBitCount + dword compression; // bmih.biCompression + dword bitmapDataSize; // bmih.biSizeImage + dword hRes; // bmih.biXPelsPerMeter + dword vRes; // bmih.biYPelsPerMeter + dword colors; // bmih.biClrUsed + dword importantColors; // bmih.biClrImportant +} bmp_t; +#pragma pack( ) + +/* +======================================================================== + +.TGA image format (Truevision Targa) + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct tga_s +{ + byte id_length; + byte colormap_type; + byte image_type; + word colormap_index; + word colormap_length; + byte colormap_size; + word x_origin; + word y_origin; + word width; + word height; + byte pixel_size; + byte attributes; +} tga_t; +#pragma pack( ) + +/* +======================================================================== + +.DDS image format + +======================================================================== +*/ +#define DDSHEADER ((' '<<24)+('S'<<16)+('D'<<8)+'D') // little-endian "DDS " + +// various four-cc types +#define TYPE_DXT1 (('1'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT1" +#define TYPE_DXT2 (('2'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT2" +#define TYPE_DXT3 (('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT3" +#define TYPE_DXT4 (('4'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT4" +#define TYPE_DXT5 (('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT5" +#define TYPE_ATI1 (('1'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI1" +#define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" +#define TYPE_RXGB (('B'<<24)+('G'<<16)+('X'<<8)+'R') // little-endian "RXGB" doom3 normalmaps +#define TYPE_$ (('\0'<<24)+('\0'<<16)+('\0'<<8)+'$') // little-endian "$" +#define TYPE_o (('\0'<<24)+('\0'<<16)+('\0'<<8)+'o') // little-endian "o" +#define TYPE_p (('\0'<<24)+('\0'<<16)+('\0'<<8)+'p') // little-endian "p" +#define TYPE_q (('\0'<<24)+('\0'<<16)+('\0'<<8)+'q') // little-endian "q" +#define TYPE_r (('\0'<<24)+('\0'<<16)+('\0'<<8)+'r') // little-endian "r" +#define TYPE_s (('\0'<<24)+('\0'<<16)+('\0'<<8)+'s') // little-endian "s" +#define TYPE_t (('\0'<<24)+('\0'<<16)+('\0'<<8)+'t') // little-endian "t" + +// dwFlags1 +#define DDS_CAPS 0x00000001L +#define DDS_HEIGHT 0x00000002L +#define DDS_WIDTH 0x00000004L +#define DDS_PITCH 0x00000008L +#define DDS_PIXELFORMAT 0x00001000L +#define DDS_MIPMAPCOUNT 0x00020000L +#define DDS_LINEARSIZE 0x00080000L +#define DDS_DEPTH 0x00800000L + +// dwFlags2 +#define DDS_ALPHAPIXELS 0x00000001L +#define DDS_ALPHA 0x00000002L +#define DDS_FOURCC 0x00000004L +#define DDS_RGB 0x00000040L +#define DDS_RGBA 0x00000041L // (DDS_RGB|DDS_ALPHAPIXELS) +#define DDS_LUMINANCE 0x00020000L +#define DDS_DUDV 0x00080000L + +// dwCaps1 +#define DDS_COMPLEX 0x00000008L +#define DDS_TEXTURE 0x00001000L +#define DDS_MIPMAP 0x00400000L + +// dwCaps2 +#define DDS_CUBEMAP 0x00000200L +#define DDS_CUBEMAP_POSITIVEX 0x00000400L +#define DDS_CUBEMAP_NEGATIVEX 0x00000800L +#define DDS_CUBEMAP_POSITIVEY 0x00001000L +#define DDS_CUBEMAP_NEGATIVEY 0x00002000L +#define DDS_CUBEMAP_POSITIVEZ 0x00004000L +#define DDS_CUBEMAP_NEGATIVEZ 0x00008000L +#define DDS_CUBEMAP_ALL_SIDES 0x0000FC00L +#define DDS_VOLUME 0x00200000L + +typedef struct dds_pf_s +{ + uint dwSize; + uint dwFlags; + uint dwFourCC; + uint dwRGBBitCount; + uint dwRBitMask; + uint dwGBitMask; + uint dwBBitMask; + uint dwABitMask; +} dds_pixf_t; + +// DDCAPS2 +typedef struct dds_caps_s +{ + uint dwCaps1; + uint dwCaps2; + uint dwCaps3; // currently unused + uint dwCaps4; // currently unused +} dds_caps_t; + +typedef struct dds_s +{ + uint dwIdent; // must matched with DDSHEADER + uint dwSize; + uint dwFlags; // determines what fields are valid + uint dwHeight; + uint dwWidth; + uint dwLinearSize; // Formless late-allocated optimized surface size + uint dwDepth; // depth if a volume texture + uint dwMipMapCount; // number of mip-map levels requested + uint dwAlphaBitDepth; // depth of alpha buffer requested + uint dwReserved1[10]; // reserved for future expansions + dds_pixf_t dsPixelFormat; + dds_caps_t dsCaps; + uint dwTextureStage; +} dds_t; + +// imagelib definitions +#define IMAGE_MAXWIDTH 8192 +#define IMAGE_MAXHEIGHT 8192 +#define LUMP_MAXWIDTH 1024 // WorldCraft limits +#define LUMP_MAXHEIGHT 1024 + +enum +{ + LUMP_NORMAL = 0, // no alpha + LUMP_MASKED, // 1-bit alpha channel masked texture + LUMP_GRADIENT, // gradient image (decals) + LUMP_EXTENDED, // bmp images have extened palette with alpha-channel + LUMP_HALFLIFE, // get predefined half-life palette + LUMP_QUAKE1 // get predefined quake palette +}; + +enum +{ + PAL_INVALID = -1, + PAL_CUSTOM = 0, + PAL_QUAKE1, + PAL_HALFLIFE +}; + +extern imglib_t image; + +byte *Image_ResampleInternal( const void *indata, int in_w, int in_h, int out_w, int out_h, int intype, qboolean *done ); +byte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int type, int flags ); +rgbdata_t *Image_Load(const char *filename, const byte *buffer, size_t buffsize ); +qboolean Image_Copy8bitRGBA( const byte *in, byte *out, int pixels ); +qboolean Image_AddIndexedImageToPack( const byte *in, int width, int height ); +qboolean Image_AddRGBAImageToPack( uint imageSize, const void* data ); +void Image_Save( const char *filename, rgbdata_t *pix ); +void Image_GetPaletteLMP( const byte *pal, int rendermode ); +void Image_GetPaletteBMP( const byte *pal ); +int Image_ComparePalette( const byte *pal ); +void Image_FreeImage( rgbdata_t *pack ); +void Image_CopyPalette24bit( void ); +void Image_CopyPalette32bit( void ); +void Image_SetPixelFormat( void ); +void Image_GetPaletteQ1( void ); +void Image_GetPaletteHL( void ); + +// +// formats load +// +qboolean Image_LoadMIP( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadMDL( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadSPR( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadDDS( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadFNT( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadLMP( const char *name, const byte *buffer, size_t filesize ); +qboolean Image_LoadPAL( const char *name, const byte *buffer, size_t filesize ); + +// +// formats save +// +qboolean Image_SaveTGA( const char *name, rgbdata_t *pix ); +qboolean Image_SaveBMP( const char *name, rgbdata_t *pix ); + +// +// img_quant.c +// +rgbdata_t *Image_Quantize( rgbdata_t *pic ); + +// +// img_utils.c +// +void Image_Reset( void ); +rgbdata_t *ImagePack( void ); +byte *Image_Copy( size_t size ); +void Image_CopyParms( rgbdata_t *src ); +qboolean Image_ValidSize( const char *name ); +qboolean Image_LumpValidSize( const char *name ); +qboolean Image_CheckFlag( int bit ); + +#endif//IMAGELIB_H \ No newline at end of file diff --git a/engine/common/imagelib/img_bmp.c b/engine/common/imagelib/img_bmp.c new file mode 100644 index 00000000..6524c90e --- /dev/null +++ b/engine/common/imagelib/img_bmp.c @@ -0,0 +1,484 @@ +/* +img_bmp.c - bmp format load & save +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" +#include "mathlib.h" + +/* +============= +Image_LoadBMP +============= +*/ +qboolean Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ) +{ + byte *buf_p, *pixbuf; + byte palette[256][4]; + int i, columns, column, rows, row, bpp = 1; + int cbPalBytes = 0, padSize = 0, bps = 0; + int reflectivity[3] = { 0, 0, 0 }; + qboolean load_qfont = false; + bmp_t bhdr; + + if( filesize < sizeof( bhdr )) return false; + + buf_p = (byte *)buffer; + bhdr.id[0] = *buf_p++; + bhdr.id[1] = *buf_p++; // move pointer + bhdr.fileSize = *(long *)buf_p; buf_p += 4; + bhdr.reserved0 = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4; + bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4; + bhdr.width = *(long *)buf_p; buf_p += 4; + bhdr.height = *(long *)buf_p; buf_p += 4; + bhdr.planes = *(short *)buf_p; buf_p += 2; + bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2; + bhdr.compression = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4; + bhdr.hRes = *(long *)buf_p; buf_p += 4; + bhdr.vRes = *(long *)buf_p; buf_p += 4; + bhdr.colors = *(long *)buf_p; buf_p += 4; + bhdr.importantColors = *(long *)buf_p; buf_p += 4; + + // bogus file header check + if( bhdr.reserved0 != 0 ) return false; + if( bhdr.planes != 1 ) return false; + + if( memcmp( bhdr.id, "BM", 2 )) + { + MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + return false; + } + + if( bhdr.bitmapHeaderSize != 0x28 ) + { + MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + return false; + } + + // bogus info header check + if( bhdr.fileSize != filesize ) + { + // Sweet Half-Life issues. splash.bmp have bogus filesize + MsgDev( D_REPORT, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize ); + } + + // bogus compression? Only non-compressed supported. + if( bhdr.compression != BI_RGB ) + { + MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + return false; + } + + image.width = columns = bhdr.width; + image.height = rows = abs( bhdr.height ); + + if( !Image_ValidSize( name )) + return false; + + // special case for loading qfont (menu font) + if( !Q_strncmp( name, "#XASH_SYSTEMFONT_001", 20 )) + { + // NOTE: same as system font we can use 4-bit bmps only + // step1: move main layer into alpha-channel (give grayscale from RED channel) + // step2: fill main layer with 255 255 255 color (white) + // step3: ???? + // step4: PROFIT!!! (economy up to 150 kb for menu.dll final size) + image.flags |= IMAGE_HAS_ALPHA; + load_qfont = true; + } + + if( bhdr.bitsPerPixel <= 8 ) + { + // figure out how many entries are actually in the table + if( bhdr.colors == 0 ) + { + bhdr.colors = 256; + cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD ); + } + else cbPalBytes = bhdr.colors * sizeof( RGBQUAD ); + } + + memcpy( palette, buf_p, cbPalBytes ); + + // setup gradient alpha for player decal + if( !Q_strncmp( name, "#logo", 5 )) + { + for( i = 0; i < bhdr.colors; i++ ) + palette[i][3] = i; + image.flags |= IMAGE_HAS_ALPHA; + } + + if( Image_CheckFlag( IL_OVERVIEW ) && bhdr.bitsPerPixel == 8 ) + { + // convert green background into alpha-layer, make opacity for all other entries + for( i = 0; i < bhdr.colors; i++ ) + { + if( palette[i][0] == 0 && palette[i][1] == 255 && palette[i][2] == 0 ) + { + palette[i][0] = palette[i][1] = palette[i][2] = palette[i][3] = 0; + image.flags |= IMAGE_HAS_ALPHA; + } + else palette[i][3] = 255; + } + } + + if( Image_CheckFlag( IL_KEEP_8BIT ) && bhdr.bitsPerPixel == 8 ) + { + pixbuf = image.palette = Mem_Alloc( host.imagepool, 1024 ); + + // bmp have a reversed palette colors + for( i = 0; i < bhdr.colors; i++ ) + { + *pixbuf++ = palette[i][2]; + *pixbuf++ = palette[i][1]; + *pixbuf++ = palette[i][0]; + *pixbuf++ = palette[i][3]; + } + image.type = PF_INDEXED_32; // 32 bit palette + } + else + { + image.palette = NULL; + image.type = PF_RGBA_32; + bpp = 4; + } + + buf_p += cbPalBytes; + image.size = image.width * image.height * bpp; + image.rgba = Mem_Alloc( host.imagepool, image.size ); + bps = image.width * (bhdr.bitsPerPixel >> 3); + + switch( bhdr.bitsPerPixel ) + { + case 1: + padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4; + break; + case 4: + padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4; + break; + case 16: + padSize = ( 4 - ( image.width * 2 % 4 )) % 4; + break; + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = image.rgba + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + word shortPixel; + int c, k, palIndex; + + switch( bhdr.bitsPerPixel ) + { + case 1: + alpha = *buf_p++; + column--; // ingnore main iterations + for( c = 0, k = 128; c < 8; c++, k >>= 1 ) + { + red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0x00; + if( ++column == columns ) + break; + } + break; + case 4: + alpha = *buf_p++; + palIndex = alpha >> 4; + if( load_qfont ) + { + *pixbuf++ = red = 255; + *pixbuf++ = green = 255; + *pixbuf++ = blue = 255; + *pixbuf++ = palette[palIndex][2]; + } + else + { + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + } + if( ++column == columns ) break; + palIndex = alpha & 0x0F; + if( load_qfont ) + { + *pixbuf++ = red = 255; + *pixbuf++ = green = 255; + *pixbuf++ = blue = 255; + *pixbuf++ = palette[palIndex][2]; + } + else + { + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + } + break; + case 8: + palIndex = *buf_p++; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( Image_CheckFlag( IL_KEEP_8BIT )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 16: + shortPixel = *(word *)buf_p, buf_p += 2; + *pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7; + *pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2; + *pixbuf++ = red = (shortPixel & ( 31 )) << 3; + *pixbuf++ = 0xff; + break; + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0xFF; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + if( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA; + break; + default: + MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name ); + Mem_Free( image.palette ); + Mem_Free( image.rgba ); + return false; + } + + if( red != green || green != blue ) + image.flags |= IMAGE_HAS_COLOR; + + reflectivity[0] += red; + reflectivity[1] += green; + reflectivity[2] += blue; + } + buf_p += padSize; // actual only for 4-bit bmps + } + + VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams ); + if( image.palette ) Image_GetPaletteBMP( image.palette ); + image.depth = 1; + + return true; +} + +qboolean Image_SaveBMP( const char *name, rgbdata_t *pix ) +{ + file_t *pfile = NULL; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + size_t total_size, cur_size; + RGBQUAD rgrgbPalette[256]; + dword cbBmpBits; + byte *clipbuf = NULL; + byte *pb, *pbBmpBits; + dword cbPalBytes = 0; + dword biTrueWidth; + int pixel_size; + int i, x, y; + + if( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE ) && !host.write_to_clipboard ) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + // get image description + switch( pix->type ) + { + case PF_INDEXED_24: + case PF_INDEXED_32: + pixel_size = 1; + break; + case PF_RGB_24: + pixel_size = 3; + break; + case PF_RGBA_32: + pixel_size = 4; + break; + default: + MsgDev( D_ERROR, "Image_SaveBMP: unsupported image type %s\n", PFDesc[pix->type].name ); + return false; + } + + if( !host.write_to_clipboard ) + { + pfile = FS_Open( name, "wb", false ); + if( !pfile ) return false; + } + + // NOTE: align transparency column will sucessfully removed + // after create sprite or lump image, it's just standard requiriments + biTrueWidth = ((pix->width + 3) & ~3); + cbBmpBits = biTrueWidth * pix->height * pixel_size; + if( pixel_size == 1 ) cbPalBytes = 256 * sizeof( RGBQUAD ); + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof( bmfh ) + sizeof( bmih ) + cbBmpBits + cbPalBytes; + bmfh.bfReserved1 = 0; + bmfh.bfReserved2 = 0; + bmfh.bfOffBits = sizeof( bmfh ) + sizeof( bmih ) + cbPalBytes; + + if( host.write_to_clipboard ) + { + // NOTE: the cbPalBytes may be 0 + total_size = sizeof( bmih ) + cbPalBytes + cbBmpBits; + clipbuf = Z_Malloc( total_size ); + cur_size = 0; + } + else + { + // write header + FS_Write( pfile, &bmfh, sizeof( bmfh )); + } + + // size of structure + bmih.biSize = sizeof( bmih ); + bmih.biWidth = biTrueWidth; + bmih.biHeight = pix->height; + bmih.biPlanes = 1; + bmih.biBitCount = pixel_size * 8; + bmih.biCompression = BI_RGB; + bmih.biSizeImage = cbBmpBits; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = ( pixel_size == 1 ) ? 256 : 0; + bmih.biClrImportant = 0; + + if( host.write_to_clipboard ) + { + memcpy( clipbuf + cur_size, &bmih, sizeof( bmih )); + cur_size += sizeof( bmih ); + } + else + { + // Write info header + FS_Write( pfile, &bmih, sizeof( bmih )); + } + + pbBmpBits = Mem_Alloc( host.imagepool, cbBmpBits ); + + if( pixel_size == 1 ) + { + pb = pix->palette; + + // copy over used entries + for( i = 0; i < (int)bmih.biClrUsed; i++ ) + { + rgrgbPalette[i].rgbRed = *pb++; + rgrgbPalette[i].rgbGreen = *pb++; + rgrgbPalette[i].rgbBlue = *pb++; + + // bmp feature - can store 32-bit palette if present + // some viewers e.g. fimg.exe can show alpha-chanell for it + if( pix->type == PF_INDEXED_32 ) + rgrgbPalette[i].rgbReserved = *pb++; + else rgrgbPalette[i].rgbReserved = 0; + } + + if( host.write_to_clipboard ) + { + memcpy( clipbuf + cur_size, rgrgbPalette, cbPalBytes ); + cur_size += cbPalBytes; + } + else + { + // write palette + FS_Write( pfile, rgrgbPalette, cbPalBytes ); + } + } + + pb = pix->buffer; + + for( y = 0; y < bmih.biHeight; y++ ) + { + i = (bmih.biHeight - 1 - y ) * (bmih.biWidth); + + for( x = 0; x < pix->width; x++ ) + { + if( pixel_size == 1 ) + { + // 8-bit + pbBmpBits[i] = pb[x]; + } + else + { + // 24 bit + pbBmpBits[i*pixel_size+0] = pb[x*pixel_size+2]; + pbBmpBits[i*pixel_size+1] = pb[x*pixel_size+1]; + pbBmpBits[i*pixel_size+2] = pb[x*pixel_size+0]; + } + + if( pixel_size == 4 ) // write alpha channel + pbBmpBits[i*pixel_size+3] = pb[x*pixel_size+3]; + i++; + } + + pb += pix->width * pixel_size; + } + + if( host.write_to_clipboard ) + { + memcpy( clipbuf + cur_size, pbBmpBits, cbBmpBits ); + cur_size += cbBmpBits; + Sys_SetClipboardData( clipbuf, total_size ); + Z_Free( clipbuf ); + } + else + { + // write bitmap bits (remainder of file) + FS_Write( pfile, pbBmpBits, cbBmpBits ); + FS_Close( pfile ); + } + + Mem_Free( pbBmpBits ); + + return true; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_dds.c b/engine/common/imagelib/img_dds.c new file mode 100644 index 00000000..79f5ed74 --- /dev/null +++ b/engine/common/imagelib/img_dds.c @@ -0,0 +1,330 @@ +/* +img_dds.c - dds format load +Copyright (C) 2015 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" +#include "mathlib.h" + +qboolean Image_CheckDXT3Alpha( dds_t *hdr, byte *fin ) +{ + uint bitmask; + word sAlpha; + byte *alpha; + int x, y, i, j; + + for( y = 0; y < hdr->dwHeight; y += 4 ) + { + for( x = 0; x < hdr->dwWidth; x += 4 ) + { + alpha = fin; + fin += 8; + bitmask = ((uint *)fin)[1]; + fin += 8; + + for( j = 0; j < 4; j++ ) + { + sAlpha = alpha[2*j] + 256 * alpha[2*j+1]; + + for( i = 0; i < 4; i++ ) + { + if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight )) + { + if( sAlpha == 0 ) + return true; + } + sAlpha >>= 4; + } + } + } + } + + return false; +} + +qboolean Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) +{ + uint bits, bitmask; + byte *alphamask; + int x, y, i, j; + + for( y = 0; y < hdr->dwHeight; y += 4 ) + { + for( x = 0; x < hdr->dwWidth; x += 4 ) + { + if( y >= hdr->dwHeight || x >= hdr->dwWidth ) + break; + + alphamask = fin + 2; + fin += 8; + + bitmask = ((uint *)fin)[1]; + fin += 8; + + // last three bytes + bits = (alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16); + + for( j = 2; j < 4; j++ ) + { + for( i = 0; i < 4; i++ ) + { + // only put pixels out < width or height + if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight )) + { + if( bits & 0x07 ) + return true; + } + bits >>= 3; + } + } + } + } + + return false; +} + +void Image_DXTGetPixelFormat( dds_t *hdr ) +{ + uint bits = hdr->dsPixelFormat.dwRGBBitCount; + + if( !FBitSet( hdr->dsCaps.dwCaps2, DDS_VOLUME )) + hdr->dwDepth = 1; + + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC )) + { + switch( hdr->dsPixelFormat.dwFourCC ) + { + case TYPE_DXT1: + image.type = PF_DXT1; + break; + case TYPE_DXT2: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + case TYPE_DXT3: + image.type = PF_DXT3; + break; + case TYPE_DXT4: + image.flags &= ~IMAGE_HAS_ALPHA; // alpha is already premultiplied by color + case TYPE_DXT5: + image.type = PF_DXT5; + break; + default: + image.type = PF_UNKNOWN; // assume error + break; + } + } + else + { + // this dds texture isn't compressed so write out ARGB or luminance format + if( hdr->dsPixelFormat.dwFlags & DDS_DUDV ) + { + image.type = PF_UNKNOWN; // assume error + } + else if( hdr->dsPixelFormat.dwFlags & DDS_LUMINANCE ) + { + image.type = PF_UNKNOWN; // assume error + } + else + { + if( bits == 32 ) + image.type = PF_BGRA_32; + else if( bits == 24 ) + image.type = PF_BGR_24; + else image.type = PF_UNKNOWN; // assume error; + } + } + + // setup additional flags + if( hdr->dsCaps.dwCaps1 & DDS_COMPLEX && hdr->dsCaps.dwCaps2 & DDS_CUBEMAP ) + image.flags |= IMAGE_CUBEMAP; + + if( hdr->dwFlags & DDS_MIPMAPCOUNT ) + image.num_mips = hdr->dwMipMapCount; // get actual mip count +} + +size_t Image_DXTGetLinearSize( int type, int width, int height, int depth ) +{ + switch( type ) + { + case PF_DXT1: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 8 ); + case PF_DXT3: + case PF_DXT5: return ((( width + 3 ) / 4 ) * (( height + 3 ) / 4 ) * depth * 16 ); + case PF_BGR_24: + case PF_RGB_24: return (width * height * depth * 3); + case PF_BGRA_32: + case PF_RGBA_32: return (width * height * depth * 4); + } + + return 0; +} + +size_t Image_DXTCalcMipmapSize( dds_t *hdr ) +{ + size_t buffsize = 0; + int i, width, height; + + // now correct buffer size + for( i = 0; i < Q_max( 1, ( hdr->dwMipMapCount )); i++ ) + { + width = Q_max( 1, ( hdr->dwWidth >> i )); + height = Q_max( 1, ( hdr->dwHeight >> i )); + buffsize += Image_DXTGetLinearSize( image.type, width, height, image.depth ); + } + + return buffsize; +} + +uint Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) +{ + size_t buffsize = 0; + int w = image.width; + int h = image.height; + int d = image.depth; + + if( hdr->dsCaps.dwCaps2 & DDS_CUBEMAP ) + { + // cubemap w*h always match for all sides + buffsize = Image_DXTCalcMipmapSize( hdr ) * 6; + } + else if( hdr->dwFlags & DDS_MIPMAPCOUNT ) + { + // if mipcount > 1 + buffsize = Image_DXTCalcMipmapSize( hdr ); + } + else if( hdr->dwFlags & ( DDS_LINEARSIZE|DDS_PITCH )) + { + // just in case (no need, really) + buffsize = hdr->dwLinearSize; + } + else + { + // pretty solution for microsoft bug + buffsize = Image_DXTCalcMipmapSize( hdr ); + } + + if( filesize != buffsize ) // main check + { + MsgDev( D_WARN, "Image_LoadDDS: (%s) probably corrupted(%i should be %i)\n", name, buffsize, filesize ); + return false; + } + + return buffsize; +} + +void Image_DXTAdjustVolume( dds_t *hdr ) +{ + if( hdr->dwDepth <= 1 ) + return; + + hdr->dwLinearSize = Image_DXTGetLinearSize( image.type, hdr->dwWidth, hdr->dwHeight, hdr->dwDepth ); + hdr->dwFlags |= DDS_LINEARSIZE; +} + +/* +============= +Image_LoadDDS +============= +*/ +qboolean Image_LoadDDS( const char *name, const byte *buffer, size_t filesize ) +{ + dds_t header; + byte *fin; + + if( filesize < sizeof( dds_t )) + { + MsgDev( D_ERROR, "Image_LoadDDS: file (%s) have invalid size\n", name ); + return false; + } + + memcpy( &header, buffer, sizeof( dds_t )); + + if( header.dwIdent != DDSHEADER ) + return false; // it's not a dds file, just skip it + + if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum) + { + MsgDev( D_ERROR, "Image_LoadDDS: (%s) have corrupted header\n", name ); + return false; + } + + if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure + { + MsgDev( D_ERROR, "Image_LoadDDS: (%s) have corrupt pixelformat header\n", name ); + return false; + } + + image.width = header.dwWidth; + image.height = header.dwHeight; + + if( header.dwFlags & DDS_DEPTH ) + image.depth = header.dwDepth; + else image.depth = 1; + + if( !Image_ValidSize( name )) return false; + + Image_DXTGetPixelFormat( &header ); // and image type too :) + Image_DXTAdjustVolume( &header ); + + if( !Image_CheckFlag( IL_DDS_HARDWARE ) && ( image.type == PF_DXT1 || image.type == PF_DXT3 || image.type == PF_DXT5 )) + return false; // silently rejected + + if( image.type == PF_UNKNOWN ) + { + MsgDev( D_WARN, "Image_LoadDDS: (%s) has unrecognized type\n", name ); + return false; + } + + image.size = Image_DXTCalcSize( name, &header, filesize - 128 ); + if( image.size == 0 ) return false; // just in case + fin = (byte *)(buffer + sizeof( dds_t )); + + // copy an encode method + image.encode = (word)header.dwReserved1[0]; + + switch( image.encode ) + { + case DXT_ENCODE_COLOR_YCoCg: + SetBits( image.flags, IMAGE_HAS_COLOR ); + break; + case DXT_ENCODE_NORMAL_AG_ORTHO: + case DXT_ENCODE_NORMAL_AG_STEREO: + case DXT_ENCODE_NORMAL_AG_PARABOLOID: + case DXT_ENCODE_NORMAL_AG_QUARTIC: + case DXT_ENCODE_NORMAL_AG_AZIMUTHAL: + SetBits( image.flags, IMAGE_HAS_COLOR ); + break; + default: // check for real alpha-pixels + if( image.type == PF_DXT3 && Image_CheckDXT3Alpha( &header, fin )) + SetBits( image.flags, IMAGE_HAS_ALPHA ); + else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) + SetBits( image.flags, IMAGE_HAS_ALPHA ); + if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) + SetBits( image.flags, IMAGE_HAS_COLOR ); + break; + } + + if( header.dwReserved1[1] != 0 ) + { + // store texture reflectivity + image.fogParams[0] = ((header.dwReserved1[1] & 0x000000FF) >> 0 ); + image.fogParams[1] = ((header.dwReserved1[1] & 0x0000FF00) >> 8 ); + image.fogParams[2] = ((header.dwReserved1[1] & 0x00FF0000) >> 16); + image.fogParams[3] = ((header.dwReserved1[1] & 0xFF000000) >> 24); + } + + // dds files will be uncompressed on a render. requires minimal of info for set this + image.rgba = Mem_Alloc( host.imagepool, image.size ); + memcpy( image.rgba, fin, image.size ); + image.flags |= IMAGE_DDS_FORMAT; + + return true; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_main.c b/engine/common/imagelib/img_main.c new file mode 100644 index 00000000..69c48f97 --- /dev/null +++ b/engine/common/imagelib/img_main.c @@ -0,0 +1,508 @@ +/* +img_main.c - load & save various image formats +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" + +// global image variables +imglib_t image; + +typedef struct suffix_s +{ + const char *suf; + uint flags; + side_hint_t hint; +} suffix_t; + +static const suffix_t skybox_qv1[6] = +{ +{ "ft", IMAGE_FLIP_X, CB_HINT_POSX }, +{ "bk", IMAGE_FLIP_Y, CB_HINT_NEGX }, +{ "up", IMAGE_ROT_90, CB_HINT_POSZ }, +{ "dn", IMAGE_ROT_90, CB_HINT_NEGZ }, +{ "rt", IMAGE_ROT_90, CB_HINT_POSY }, +{ "lf", IMAGE_ROT270, CB_HINT_NEGY }, +}; + +static const suffix_t skybox_qv2[6] = +{ +{ "_ft", IMAGE_FLIP_X, CB_HINT_POSX }, +{ "_bk", IMAGE_FLIP_Y, CB_HINT_NEGX }, +{ "_up", IMAGE_ROT_90, CB_HINT_POSZ }, +{ "_dn", IMAGE_ROT_90, CB_HINT_NEGZ }, +{ "_rt", IMAGE_ROT_90, CB_HINT_POSY }, +{ "_lf", IMAGE_ROT270, CB_HINT_NEGY }, +}; + +static const suffix_t cubemap_v1[6] = +{ +{ "posx", 0, CB_HINT_POSX }, +{ "negx", 0, CB_HINT_NEGX }, +{ "posy", 0, CB_HINT_POSY }, +{ "negy", 0, CB_HINT_NEGY }, +{ "posz", 0, CB_HINT_POSZ }, +{ "negz", 0, CB_HINT_NEGZ }, +}; + +static const suffix_t cubemap_v2[6] = +{ +{ "px", 0, CB_HINT_POSX }, +{ "nx", 0, CB_HINT_NEGX }, +{ "py", 0, CB_HINT_POSY }, +{ "ny", 0, CB_HINT_NEGY }, +{ "pz", 0, CB_HINT_POSZ }, +{ "nz", 0, CB_HINT_NEGZ }, +}; + +typedef struct cubepack_s +{ + const char *name; // just for debug + const suffix_t *type; +} cubepack_t; + +static const cubepack_t load_cubemap[] = +{ +{ "3Ds Sky1", skybox_qv1 }, +{ "3Ds Sky2", skybox_qv2 }, +{ "3Ds Cube", cubemap_v2 }, +{ "Tenebrae", cubemap_v1 }, +{ NULL, NULL }, +}; + +// soul of ImageLib - table of image format constants +const bpc_desc_t PFDesc[] = +{ +{PF_UNKNOWN, "raw", 0x1908, 0 }, +{PF_INDEXED_24, "pal 24", 0x1908, 1 }, +{PF_INDEXED_32, "pal 32", 0x1908, 1 }, +{PF_RGBA_32, "RGBA 32",0x1908, 4 }, +{PF_BGRA_32, "BGRA 32",0x80E1, 4 }, +{PF_RGB_24, "RGB 24", 0x1908, 3 }, +{PF_BGR_24, "BGR 24", 0x80E0, 3 }, +{PF_DXT1, "DXT 1", 0x83F1, 4 }, +{PF_DXT3, "DXT 3", 0x83F2, 4 }, +{PF_DXT5, "DXT 5", 0x83F3, 4 }, +}; + +void Image_Reset( void ) +{ + // reset global variables + image.width = image.height = image.depth = 0; + image.source_width = image.source_height = 0; + image.source_type = image.num_mips = 0; + image.num_sides = image.flags = 0; + image.encode = DXT_ENCODE_DEFAULT; + image.type = PF_UNKNOWN; + image.fogParams[0] = 0; + image.fogParams[1] = 0; + image.fogParams[2] = 0; + image.fogParams[3] = 0; + + // pointers will be saved with prevoius picture struct + // don't care about it + image.palette = NULL; + image.cubemap = NULL; + image.rgba = NULL; + image.ptr = 0; + image.size = 0; +} + +rgbdata_t *ImagePack( void ) +{ + rgbdata_t *pack = Mem_Alloc( host.imagepool, sizeof( rgbdata_t )); + + // clear any force flags + image.force_flags = 0; + + if( image.cubemap && image.num_sides != 6 ) + { + // this never be happens, just in case + MsgDev( D_NOTE, "ImagePack: inconsistent cubemap pack %d\n", image.num_sides ); + FS_FreeImage( pack ); + return NULL; + } + + if( image.cubemap ) + { + image.flags |= IMAGE_CUBEMAP; + pack->buffer = image.cubemap; + pack->width = image.source_width; + pack->height = image.source_height; + pack->type = image.source_type; + pack->size = image.size * image.num_sides; + } + else + { + pack->buffer = image.rgba; + pack->width = image.width; + pack->height = image.height; + pack->depth = image.depth; + pack->type = image.type; + pack->size = image.size; + } + + // copy fog params + pack->fogParams[0] = image.fogParams[0]; + pack->fogParams[1] = image.fogParams[1]; + pack->fogParams[2] = image.fogParams[2]; + pack->fogParams[3] = image.fogParams[3]; + + pack->flags = image.flags; + pack->numMips = image.num_mips; + pack->palette = image.palette; + pack->encode = image.encode; + + return pack; +} + +/* +================ +FS_AddSideToPack + +================ +*/ +qboolean FS_AddSideToPack( const char *name, int adjust_flags ) +{ + byte *out, *flipped; + qboolean resampled = false; + + // first side set average size for all cubemap sides! + if( !image.cubemap ) + { + image.source_width = image.width; + image.source_height = image.height; + image.source_type = image.type; + } + + // keep constant size, render.dll expecting it + image.size = image.source_width * image.source_height * 4; + + // mixing dds format with any existing ? + if( image.type != image.source_type ) + return false; + + // flip image if needed + flipped = Image_FlipInternal( image.rgba, &image.width, &image.height, image.source_type, adjust_flags ); + if( !flipped ) return false; // try to reasmple dxt? + if( flipped != image.rgba ) image.rgba = Image_Copy( image.size ); + + // resampling image if needed + out = Image_ResampleInternal((uint *)image.rgba, image.width, image.height, image.source_width, image.source_height, image.source_type, &resampled ); + if( !out ) return false; // try to reasmple dxt? + if( resampled ) image.rgba = Image_Copy( image.size ); + + image.cubemap = Mem_Realloc( host.imagepool, image.cubemap, image.ptr + image.size ); + memcpy( image.cubemap + image.ptr, image.rgba, image.size ); // add new side + + Mem_Free( image.rgba ); // release source buffer + image.ptr += image.size; // move to next + image.num_sides++; // bump sides count + + return true; +} + +/* +================ +FS_LoadImage + +loading and unpack to rgba any known image +================ +*/ +rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) +{ + const char *ext = COM_FileExtension( filename ); + string path, loadname, sidename; + qboolean anyformat = true; + int i, filesize = 0; + const loadpixformat_t *format; + const cubepack_t *cmap; + byte *f; + + Image_Reset(); // clear old image + Q_strncpy( loadname, filename, sizeof( loadname )); + + if( Q_stricmp( ext, "" )) + { + // we needs to compare file extension with list of supported formats + // and be sure what is real extension, not a filename with dot + for( format = image.loadformats; format && format->formatstring; format++ ) + { + if( !Q_stricmp( format->ext, ext )) + { + COM_StripExtension( loadname ); + anyformat = false; + break; + } + } + } + + // special mode: skip any checks, load file from buffer + if( filename[0] == '#' && buffer && size ) + goto load_internal; + + // now try all the formats in the selected list + for( format = image.loadformats; format && format->formatstring; format++) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + image.hint = format->hint; + f = FS_LoadFile( path, &filesize, false ); + if( f && filesize > 0 ) + { + if( format->loadfunc( path, f, filesize )) + { + Mem_Free( f ); // release buffer + return ImagePack(); // loaded + } + else Mem_Free(f); // release buffer + } + } + } + + // check all cubemap sides with package suffix + for( cmap = load_cubemap; cmap && cmap->type; cmap++ ) + { + for( i = 0; i < 6; i++ ) + { + // for support mixed cubemaps e.g. sky_ft.bmp, sky_rt.tga + // NOTE: all loaders must keep sides in one format for all + for( format = image.loadformats; format && format->formatstring; format++ ) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, loadname, cmap->type[i].suf, format->ext ); + image.hint = cmap->type[i].hint; // side hint + + f = FS_LoadFile( path, &filesize, false ); + if( f && filesize > 0 ) + { + // this name will be used only for tell user about problems + if( format->loadfunc( path, f, filesize )) + { + Q_snprintf( sidename, sizeof( sidename ), "%s%s.%s", loadname, cmap->type[i].suf, format->ext ); + if( FS_AddSideToPack( sidename, cmap->type[i].flags )) // process flags to flip some sides + { + Mem_Free( f ); + break; // loaded + } + } + Mem_Free( f ); + } + } + } + + if( image.num_sides != i + 1 ) // check side + { + // first side not found, probably it's not cubemap + // it contain info about image_type and dimensions, don't generate black cubemaps + if( !image.cubemap ) break; + MsgDev( D_ERROR, "FS_LoadImage: couldn't load (%s%s), create black image\n", loadname, cmap->type[i].suf ); + + // Mem_Alloc already filled memblock with 0x00, no need to do it again + image.cubemap = Mem_Realloc( host.imagepool, image.cubemap, image.ptr + image.size ); + image.ptr += image.size; // move to next + image.num_sides++; // merge counter + } + } + + // make sure what all sides is loaded + if( image.num_sides != 6 ) + { + // unexpected errors ? + if( image.cubemap ) + Mem_Free( image.cubemap ); + Image_Reset(); + } + else break; + } + + if( image.cubemap ) + return ImagePack(); // all done + +load_internal: + for( format = image.loadformats; format && format->formatstring; format++ ) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + image.hint = format->hint; + if( buffer && size > 0 ) + { + if( format->loadfunc( loadname, buffer, size )) + return ImagePack(); // loaded + } + } + } + + if( !image.loadformats || image.loadformats->ext == NULL ) + MsgDev( D_NOTE, "FS_LoadImage: imagelib offline\n" ); + else if( filename[0] != '#' ) + MsgDev( D_WARN, "FS_LoadImage: couldn't load \"%s\"\n", loadname ); + + // clear any force flags + image.force_flags = 0; + + return NULL; +} + +/* +================ +Image_Save + +writes image as any known format +================ +*/ +qboolean FS_SaveImage( const char *filename, rgbdata_t *pix ) +{ + const char *ext = COM_FileExtension( filename ); + qboolean anyformat = !Q_stricmp( ext, "" ) ? true : false; + string path, savename; + const savepixformat_t *format; + + if( !pix || !pix->buffer || anyformat ) + { + // clear any force flags + image.force_flags = 0; + return false; + } + + Q_strncpy( savename, filename, sizeof( savename )); + COM_StripExtension( savename ); // remove extension if needed + + if( pix->flags & (IMAGE_CUBEMAP|IMAGE_SKYBOX)) + { + size_t realSize = pix->size; // keep real pic size + byte *picBuffer; // to avoid corrupt memory on free data + const suffix_t *box; + int i; + + if( pix->flags & IMAGE_SKYBOX ) + box = skybox_qv1; + else if( pix->flags & IMAGE_CUBEMAP ) + box = cubemap_v2; + else + { + // clear any force flags + image.force_flags = 0; + return false; // do not happens + } + + pix->size /= 6; // now set as side size + picBuffer = pix->buffer; + + // save all sides seperately + for( format = image.saveformats; format && format->formatstring; format++ ) + { + if( !Q_stricmp( ext, format->ext )) + { + for( i = 0; i < 6; i++ ) + { + Q_sprintf( path, format->formatstring, savename, box[i].suf, format->ext ); + if( !format->savefunc( path, pix )) break; // there were errors + pix->buffer += pix->size; // move pointer + } + + // restore pointers + pix->size = realSize; + pix->buffer = picBuffer; + + // clear any force flags + image.force_flags = 0; + + return ( i == 6 ); + } + } + } + else + { + for( format = image.saveformats; format && format->formatstring; format++ ) + { + if( !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, savename, "", format->ext ); + if( format->savefunc( path, pix )) + { + // clear any force flags + image.force_flags = 0; + return true; // saved + } + } + } + } + + // clear any force flags + image.force_flags = 0; + + return false; +} + +/* +================ +Image_FreeImage + +free RGBA buffer +================ +*/ +void FS_FreeImage( rgbdata_t *pack ) +{ + if( pack ) + { + if( pack->buffer ) Mem_Free( pack->buffer ); + if( pack->palette ) Mem_Free( pack->palette ); + Mem_Free( pack ); + } + else MsgDev( D_WARN, "FS_FreeImage: trying to free NULL image\n" ); +} + +/* +================ +FS_CopyImage + +make an image copy +================ +*/ +rgbdata_t *FS_CopyImage( rgbdata_t *in ) +{ + rgbdata_t *out; + int palSize = 0; + + if( !in ) return NULL; + + out = Mem_Alloc( host.imagepool, sizeof( rgbdata_t )); + *out = *in; + + switch( in->type ) + { + case PF_INDEXED_24: + palSize = 768; + break; + case PF_INDEXED_32: + palSize = 1024; + break; + } + + if( palSize ) + { + out->palette = Mem_Alloc( host.imagepool, palSize ); + memcpy( out->palette, in->palette, palSize ); + } + + if( in->size ) + { + out->buffer = Mem_Alloc( host.imagepool, in->size ); + memcpy( out->buffer, in->buffer, in->size ); + } + + return out; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_quant.c b/engine/common/imagelib/img_quant.c new file mode 100644 index 00000000..2ad8f7df --- /dev/null +++ b/engine/common/imagelib/img_quant.c @@ -0,0 +1,472 @@ +/* +img_quant.c - image quantizer. based on Antony Dekker original code +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" + + +#define netsize 256 // number of colours used +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 + +#define minpicturebytes (3*prime4) // minimum size for input image + +#define maxnetpos (netsize-1) +#define netbiasshift 4 // bias for colour values +#define ncycles 100 // no. of learning cycles + +// defs for freq and bias +#define intbiasshift 16 // bias for fractions +#define intbias (1<>betashift) // beta = 1 / 1024 +#define betagamma (intbias<<(gammashift - betashift)) + +// defs for decreasing radius factor +#define initrad (netsize>>3) // for 256 cols, radius starts +#define radiusbiasshift 6 // at 32.0 biased by 6 bits +#define radiusbias (1<>= netbiasshift; + // Fix based on bug report by Juergen Weigert jw@suse.de + temp = (network[i][j] + (1 << (netbiasshift - 1))) >> netbiasshift; + if( temp > 255 ) temp = 255; + network[i][j] = temp; + } + + network[i][3] = i; // record colour num + } +} + +// Insertion sort of network and building of netindex[0..255] (to do after unbias) +void inxbuild( void ) +{ + register int *p, *q; + register int i, j, smallpos, smallval; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + + for( i = 0; i < netsize; i++ ) + { + p = network[i]; + smallpos = i; + smallval = p[1]; // index on g + + // find smallest in i..netsize-1 + for( j = i + 1; j < netsize; j++ ) + { + q = network[j]; + if( q[1] < smallval ) + { + // index on g + smallpos = j; + smallval = q[1]; // index on g + } + } + + q = network[smallpos]; + + // swap p (i) and q (smallpos) entries + if( i != smallpos ) + { + j = q[0]; q[0] = p[0]; p[0] = j; + j = q[1]; q[1] = p[1]; p[1] = j; + j = q[2]; q[2] = p[2]; p[2] = j; + j = q[3]; q[3] = p[3]; p[3] = j; + } + + // smallval entry is now in position i + if( smallval != previouscol ) + { + netindex[previouscol] = (startpos+i) >> 1; + + for( j = previouscol + 1; j < smallval; j++ ) + netindex[j] = i; + + previouscol = smallval; + startpos = i; + } + } + + netindex[previouscol] = (startpos + maxnetpos)>>1; + + for( j = previouscol + 1; j < 256; j++ ) + netindex[j] = maxnetpos; // really 256 +} + + +// Search for BGR values 0..255 (after net is unbiased) and return colour index +int inxsearch( int r, int g, int b ) +{ + register int i, j, dist, a, bestd; + register int *p; + int best; + + bestd = 1000; // biggest possible dist is 256 * 3 + best = -1; + i = netindex[g]; // index on g + j = i - 1; // start at netindex[g] and work outwards + + while(( i < netsize ) || ( j >= 0 )) + { + if( i < netsize ) + { + p = network[i]; + dist = p[1] - g; // inx key + + if( dist >= bestd ) + { + i = netsize; // stop iter + } + else + { + i++; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + + if( j >= 0 ) + { + p = network[j]; + dist = g - p[1]; // inx key - reverse dif + + if( dist >= bestd ) + { + j = -1; // stop iter + } + else + { + j--; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + } + + return best; +} + +// Search for biased BGR values +int contest( int r, int g, int b ) +{ + // finds closest neuron (min dist) and updates freq + // finds best neuron (min dist-bias) and returns position + // for frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma * ((1 / netsize) - freq[i]) + + register int *p, *f, *n; + register int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + + bestd = ~(1<<31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + p = bias; + f = freq; + + for( i = 0; i < netsize; i++ ) + { + n = network[i]; + dist = n[2] - b; + if( dist < 0 ) dist = -dist; + a = n[1] - g; + if( a < 0 ) a = -a; + dist += a; + a = n[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + bestpos = i; + } + + biasdist = dist - ((*p) >> (intbiasshift - netbiasshift)); + + if( biasdist < bestbiasd ) + { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = (*f >> betashift); + *f++ -= betafreq; + *p++ += (betafreq << gammashift); + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + + return bestbiaspos; +} + +// Move neuron i towards biased (b,g,r) by factor alpha +void altersingle( int alpha, int i, int r, int g, int b ) +{ + register int *n; + + n = network[i]; // alter hit neuron + *n -= (alpha * (*n - r)) / initalpha; + n++; + *n -= (alpha * (*n - g)) / initalpha; + n++; + *n -= (alpha * (*n - b)) / initalpha; +} + +// Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] +void alterneigh( int rad, int i, int r, int g, int b ) +{ + register int j, k, lo, hi, a; + register int *p, *q; + + lo = i - rad; + if( lo < -1 ) lo = -1; + hi = i + rad; + if( hi > netsize ) hi = netsize; + + j = i + 1; + k = i - 1; + q = radpower; + + while(( j < hi ) || ( k > lo )) + { + a = (*(++q)); + + if( j < hi ) + { + p = network[j]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + j++; + } + + if( k > lo ) + { + p = network[k]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + k--; + } + } +} + +// Main Learning Loop +void learn( void ) +{ + register byte *p; + register int i, j, r, g, b; + int radius, rad, alpha, step; + int delta, samplepixels; + byte *lim; + + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + lim = thepicture + lengthcount; + samplepixels = lengthcount / (image.bpp * samplefac); + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( i = 0; i < rad; i++ ) + radpower[i] = alpha * ((( rad * rad - i * i ) * radbias ) / ( rad * rad )); + + if( delta <= 0 ) return; + + if(( lengthcount % prime1 ) != 0 ) + { + step = prime1 * image.bpp; + } + else if(( lengthcount % prime2 ) != 0 ) + { + step = prime2 * image.bpp; + } + else if(( lengthcount % prime3 ) != 0 ) + { + step = prime3 * image.bpp; + } + else + { + step = prime4 * image.bpp; + } + + i = 0; + + while( i < samplepixels ) + { + r = p[0] << netbiasshift; + g = p[1] << netbiasshift; + b = p[2] << netbiasshift; + j = contest( r, g, b ); + + altersingle( alpha, j, r, g, b ); + if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours + + p += step; + if( p >= lim ) p -= lengthcount; + + i++; + + if( i % delta == 0 ) + { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( j = 0; j < rad; j++ ) + radpower[j] = alpha * ((( rad * rad - j * j ) * radbias ) / ( rad * rad )); + } + } +} + +// returns the actual number of palette entries. +rgbdata_t *Image_Quantize( rgbdata_t *pic ) +{ + int i; + + // quick case to reject unneeded conversions + if( pic->type == PF_INDEXED_24 || pic->type == PF_INDEXED_32 ) + return pic; + + Image_CopyParms( pic ); + image.size = image.width * image.height; + image.bpp = PFDesc[pic->type].bpp; + image.ptr = 0; + + // allocate 8-bit buffer + image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, image.size ); + + initnet( pic->buffer, pic->size, 10 ); + learn(); + unbiasnet(); + + pic->palette = Mem_Alloc( host.imagepool, netsize * 3 ); + + for( i = 0; i < netsize; i++ ) + { + pic->palette[i*3+0] = network[i][0]; // red + pic->palette[i*3+1] = network[i][1]; // green + pic->palette[i*3+2] = network[i][2]; // blue + } + + inxbuild(); + + for( i = 0; i < image.width * image.height; i++ ) + { + image.tempbuffer[i] = inxsearch( pic->buffer[i*image.bpp+0], pic->buffer[i*image.bpp+1], pic->buffer[i*image.bpp+2] ); + } + + pic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size ); + memcpy( pic->buffer, image.tempbuffer, image.size ); + pic->type = PF_INDEXED_24; + pic->size = image.size; + + return pic; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_tga.c b/engine/common/imagelib/img_tga.c new file mode 100644 index 00000000..ec5511b7 --- /dev/null +++ b/engine/common/imagelib/img_tga.c @@ -0,0 +1,306 @@ +/* +img_tga.c - tga format load & save +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" +#include "mathlib.h" + +/* +============= +Image_LoadTGA +============= +*/ +qboolean Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ) +{ + int i, columns, rows, row_inc, row, col; + byte *buf_p, *pixbuf, *targa_rgba; + byte palette[256][4], red = 0, green = 0, blue = 0, alpha = 0; + int readpixelcount, pixelcount; + int reflectivity[3] = { 0, 0, 0 }; + qboolean compressed; + tga_t targa_header; + + if( filesize < sizeof( tga_t )) + return false; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = buf_p[0] + buf_p[1] * 256; buf_p += 2; + targa_header.colormap_length = buf_p[0] + buf_p[1] * 256; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = image.width = *(short *)buf_p; buf_p += 2; + targa_header.height = image.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + if( targa_header.id_length != 0 ) buf_p += targa_header.id_length; // skip TARGA image comment + + // check for tga file + if( !Image_ValidSize( name )) return false; + + image.type = PF_RGBA_32; // always exctracted to 32-bit buffer + + if( targa_header.image_type == 1 || targa_header.image_type == 9 ) + { + // uncompressed colormapped image + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + return false; + } + if( targa_header.colormap_length != 256 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + return false; + } + if( targa_header.colormap_index ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + return false; + } + if( targa_header.colormap_size == 24 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = 255; + } + } + else if( targa_header.colormap_size == 32 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = *buf_p++; + } + } + else + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + return false; + } + } + else if( targa_header.image_type == 2 || targa_header.image_type == 10 ) + { + // uncompressed or RLE compressed RGB + if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + return false; + } + } + else if( targa_header.image_type == 3 || targa_header.image_type == 11 ) + { + // uncompressed greyscale + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + return false; + } + } + + columns = targa_header.width; + rows = targa_header.height; + + image.size = image.width * image.height * 4; + targa_rgba = image.rgba = Mem_Alloc( host.imagepool, image.size ); + + // if bit 5 of attributes isn't set, the image has been stored from bottom to top + if( !Image_CheckFlag( IL_DONTFLIP_TGA ) && targa_header.attributes & 0x20 ) + { + pixbuf = targa_rgba; + row_inc = 0; + } + else + { + pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; + row_inc = -columns * 4 * 2; + } + + compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); + for( row = col = 0; row < rows; ) + { + pixelcount = 0x10000; + readpixelcount = 0x10000; + + if( compressed ) + { + pixelcount = *buf_p++; + if( pixelcount & 0x80 ) // run-length packet + readpixelcount = 1; + pixelcount = 1 + ( pixelcount & 0x7f ); + } + + while( pixelcount-- && ( row < rows ) ) + { + if( readpixelcount-- > 0 ) + { + switch( targa_header.image_type ) + { + case 1: + case 9: + // colormapped image + blue = *buf_p++; + red = palette[blue][0]; + green = palette[blue][1]; + alpha = palette[blue][3]; + blue = palette[blue][2]; + if( alpha != 255 ) image.flags |= IMAGE_HAS_ALPHA; + break; + case 2: + case 10: + // 24 or 32 bit image + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = 255; + if( targa_header.pixel_size == 32 ) + { + alpha = *buf_p++; + if( alpha != 255 ) + image.flags |= IMAGE_HAS_ALPHA; + } + break; + case 3: + case 11: + // greyscale image + blue = green = red = *buf_p++; + alpha = 255; + break; + } + } + + if( red != green || green != blue ) + image.flags |= IMAGE_HAS_COLOR; + + reflectivity[0] += red; + reflectivity[1] += green; + reflectivity[2] += blue; + + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + if( ++col == columns ) + { + // run spans across rows + row++; + col = 0; + pixbuf += row_inc; + } + } + } + + VectorDivide( reflectivity, ( image.width * image.height ), image.fogParams ); + image.depth = 1; + + return true; +} + +/* +============= +Image_SaveTGA +============= +*/ +qboolean Image_SaveTGA( const char *name, rgbdata_t *pix ) +{ + int y, outsize, pixel_size; + const byte *bufend, *in; + byte *buffer, *out; + const char *comment = "Generated by Xash ImageLib\0"; + + if( FS_FileExists( name, false ) && !Image_CheckFlag( IL_ALLOW_OVERWRITE )) + return false; // already existed + + if( pix->flags & IMAGE_HAS_ALPHA ) + outsize = pix->width * pix->height * 4 + 18 + Q_strlen( comment ); + else outsize = pix->width * pix->height * 3 + 18 + Q_strlen( comment ); + + buffer = (byte *)Mem_Alloc( host.imagepool, outsize ); + + // prepare header + buffer[0] = Q_strlen( comment ); // tga comment length + buffer[2] = 2; // uncompressed type + buffer[12] = (pix->width >> 0) & 0xFF; + buffer[13] = (pix->width >> 8) & 0xFF; + buffer[14] = (pix->height >> 0) & 0xFF; + buffer[15] = (pix->height >> 8) & 0xFF; + buffer[16] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 32 : 24; + buffer[17] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 8 : 0; // 8 bits of alpha + Q_strncpy( buffer + 18, comment, Q_strlen( comment )); + out = buffer + 18 + Q_strlen( comment ); + + // get image description + switch( pix->type ) + { + case PF_RGB_24: + case PF_BGR_24: pixel_size = 3; break; + case PF_RGBA_32: + case PF_BGRA_32: pixel_size = 4; break; + default: + MsgDev( D_ERROR, "Image_SaveTGA: unsupported image type %s\n", PFDesc[pix->type].name ); + Mem_Free( buffer ); + return false; + } + + switch( pix->type ) + { + case PF_RGB_24: + case PF_RGBA_32: + // swap rgba to bgra and flip upside down + for( y = pix->height - 1; y >= 0; y-- ) + { + in = pix->buffer + y * pix->width * pixel_size; + bufend = in + pix->width * pixel_size; + for( ; in < bufend; in += pixel_size ) + { + *out++ = in[2]; + *out++ = in[1]; + *out++ = in[0]; + if( pix->flags & IMAGE_HAS_ALPHA ) + *out++ = in[3]; + } + } + break; + case PF_BGR_24: + case PF_BGRA_32: + // flip upside down + for( y = pix->height - 1; y >= 0; y-- ) + { + in = pix->buffer + y * pix->width * pixel_size; + bufend = in + pix->width * pixel_size; + for( ; in < bufend; in += pixel_size ) + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + if( pix->flags & IMAGE_HAS_ALPHA ) + *out++ = in[3]; + } + } + break; + } + FS_WriteFile( name, buffer, outsize ); + + Mem_Free( buffer ); + return true; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c new file mode 100644 index 00000000..a8a2a7ba --- /dev/null +++ b/engine/common/imagelib/img_utils.c @@ -0,0 +1,1565 @@ +/* +img_utils.c - image common tools +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" +#include "mathlib.h" +#include "mod_local.h" +#include "gl_export.h" + +#define LERPBYTE( i ) r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r ) +#define FILTER_SIZE 5 + +uint d_8toQ1table[256]; +uint d_8toHLtable[256]; +uint d_8to24table[256]; + +qboolean q1palette_init = false; +qboolean hlpalette_init = false; + +static byte palette_q1[768] = +{ +0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171, +171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63, +47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27, +27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123, +123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71, +7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79, +0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7, +95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19, +87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255, +243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123, +95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119, +83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107, +143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35, +19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83, +107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107, +95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255, +243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55, +0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47, +47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23, +7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123, +59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255, +243,147,255,247,199,255,255,255,159,91,83 +}; + +// this is used only for particle colors +static byte palette_hl[768] = +{ +0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171, +171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63, +47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27, +27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123, +123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71, +7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79, +0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7, +95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19, +87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255, +243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123, +95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119, +83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107, +143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35, +19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83, +107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107, +95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255, +243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55, +0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47, +47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23, +7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123, +59,183,155,55,199,195,55,231,227,87,0,255,0,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243, +147,255,247,199,255,255,255,159,91,83 +}; + +static float FILTER[NUM_FILTERS][FILTER_SIZE][FILTER_SIZE] = +{ +{ // regular blur +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, +{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, +{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +}, +{ // light blur +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, +{ 0.0f, 1.0f, 4.0f, 1.0f, 0.0f }, +{ 0.0f, 1.0f, 1.0f, 1.0f, 0.0f }, +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +}, +{ // find edges +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +{ 0.0f, -1.0f, -1.0f, -1.0f, 0.0f }, +{ 0.0f, -1.0f, 8.0f, -1.0f, 0.0f }, +{ 0.0f, -1.0f, -1.0f, -1.0f, 0.0f }, +{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }, +}, +{ // emboss +{-0.7f, -0.7f, -0.7f, -0.7f, 0.0f }, +{-0.7f, -0.7f, -0.7f, 0.0f, 0.7f }, +{-0.7f, -0.7f, 0.0f, 0.7f, 0.7f }, +{-0.7f, 0.0f, 0.7f, 0.7f, 0.7f }, +{ 0.0f, 0.7f, 0.7f, 0.7f, 0.7f }, +} +}; + +/* +============================================================================= + + XASH3D LOAD IMAGE FORMATS + +============================================================================= +*/ +// stub +static const loadpixformat_t load_null[] = +{ +{ NULL, NULL, NULL, IL_HINT_NO } +}; + +static const loadpixformat_t load_game[] = +{ +{ "%s%s.%s", "dds", Image_LoadDDS, IL_HINT_NO }, // dds for world and studio models +{ "%s%s.%s", "tga", Image_LoadTGA, IL_HINT_NO }, // hl vgui menus +{ "%s%s.%s", "bmp", Image_LoadBMP, IL_HINT_NO }, // WON menu images +{ "%s%s.%s", "mip", Image_LoadMIP, IL_HINT_NO }, // hl textures from wad or buffer +{ "%s%s.%s", "mdl", Image_LoadMDL, IL_HINT_HL }, // hl studio model skins +{ "%s%s.%s", "spr", Image_LoadSPR, IL_HINT_HL }, // hl sprite frames +{ "%s%s.%s", "lmp", Image_LoadLMP, IL_HINT_NO }, // hl menu images (cached.wad etc) +{ "%s%s.%s", "fnt", Image_LoadFNT, IL_HINT_HL }, // hl console font (fonts.wad etc) +{ "%s%s.%s", "pal", Image_LoadPAL, IL_HINT_NO }, // install studio\sprite palette +{ NULL, NULL, NULL, IL_HINT_NO } +}; + +/* +============================================================================= + + XASH3D SAVE IMAGE FORMATS + +============================================================================= +*/ +// stub +static const savepixformat_t save_null[] = +{ +{ NULL, NULL, NULL } +}; + +// Xash3D normal instance +static const savepixformat_t save_game[] = +{ +{ "%s%s.%s", "tga", Image_SaveTGA }, // tga screenshots +{ "%s%s.%s", "bmp", Image_SaveBMP }, // bmp levelshots or screenshots +{ NULL, NULL, NULL } +}; + +void Image_Init( void ) +{ + // init pools + host.imagepool = Mem_AllocPool( "ImageLib Pool" ); + + // install image formats (can be re-install later by Image_Setup) + switch( host.type ) + { + case HOST_NORMAL: + image.cmd_flags = IL_USE_LERPING|IL_ALLOW_OVERWRITE; + image.loadformats = load_game; + image.saveformats = save_game; + break; + default: // all other instances not using imagelib + image.cmd_flags = 0; + image.loadformats = load_game; + image.saveformats = save_null; + break; + } + + image.tempbuffer = NULL; +} + +void Image_Shutdown( void ) +{ + Mem_Check(); // check for leaks + Mem_FreePool( &host.imagepool ); +} + +byte *Image_Copy( size_t size ) +{ + byte *out; + + out = Mem_Alloc( host.imagepool, size ); + memcpy( out, image.tempbuffer, size ); + + return out; +} + +/* +================= +Image_CheckFlag +================= +*/ +qboolean Image_CheckFlag( int bit ) +{ + if( FBitSet( image.force_flags, bit )) + return true; + + if( FBitSet( image.cmd_flags, bit )) + return true; + + return false; +} + +/* +================= +Image_SetForceFlags +================= +*/ +void Image_SetForceFlags( uint flags ) +{ + SetBits( image.force_flags, flags ); +} + +/* +================= +Image_ClearForceFlags +================= +*/ +void Image_ClearForceFlags( void ) +{ + image.force_flags = 0; +} + +/* +================= +Image_AddCmdFlags +================= +*/ +void Image_AddCmdFlags( uint flags ) +{ + SetBits( image.cmd_flags, flags ); +} + +qboolean Image_ValidSize( const char *name ) +{ + if( image.width > IMAGE_MAXWIDTH || image.height > IMAGE_MAXHEIGHT || image.width <= 0 || image.height <= 0 ) + { + MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, image.width, image.height ); + return false; + } + return true; +} + +qboolean Image_LumpValidSize( const char *name ) +{ + if( image.width > LUMP_MAXWIDTH || image.height > LUMP_MAXHEIGHT || image.width <= 0 || image.height <= 0 ) + { + MsgDev(D_WARN, "Image_LumpValidSize: (%s) dims out of range[%dx%d]\n", name, image.width,image.height ); + return false; + } + return true; +} + +/* +============= +Image_ComparePalette +============= +*/ +int Image_ComparePalette( const byte *pal ) +{ + if( pal == NULL ) + return PAL_INVALID; + else if( !memcmp( palette_q1, pal, 765 )) // last color was changed + return PAL_QUAKE1; + else if( !memcmp( palette_hl, pal, 765 )) + return PAL_HALFLIFE; + return PAL_CUSTOM; +} + +void Image_SetPalette( const byte *pal, uint *d_table ) +{ + int i; + byte rgba[4]; + + // setup palette + switch( image.d_rendermode ) + { + case LUMP_NORMAL: + for( i = 0; i < 256; i++ ) + { + rgba[0] = pal[i*3+0]; + rgba[1] = pal[i*3+1]; + rgba[2] = pal[i*3+2]; + rgba[3] = 0xFF; + d_table[i] = *(uint *)rgba; + } + break; + case LUMP_GRADIENT: + for( i = 0; i < 256; i++ ) + { + rgba[0] = pal[765]; + rgba[1] = pal[766]; + rgba[2] = pal[767]; + rgba[3] = i; + d_table[i] = *(uint *)rgba; + } + break; + case LUMP_MASKED: + for( i = 0; i < 255; i++ ) + { + rgba[0] = pal[i*3+0]; + rgba[1] = pal[i*3+1]; + rgba[2] = pal[i*3+2]; + rgba[3] = 0xFF; + d_table[i] = *(uint *)rgba; + } + d_table[255] = 0; + break; + case LUMP_EXTENDED: + for( i = 0; i < 256; i++ ) + { + rgba[0] = pal[i*4+0]; + rgba[1] = pal[i*4+1]; + rgba[2] = pal[i*4+2]; + rgba[3] = pal[i*4+3]; + d_table[i] = *(uint *)rgba; + } + break; + } +} + +void Image_GetPaletteQ1( void ) +{ + if( !q1palette_init ) + { + image.d_rendermode = LUMP_NORMAL; + Image_SetPalette( palette_q1, d_8toQ1table ); + d_8toQ1table[255] = 0; // 255 is transparent + q1palette_init = true; + } + + image.d_rendermode = LUMP_QUAKE1; + image.d_currentpal = d_8toQ1table; +} + +void Image_GetPaletteHL( void ) +{ + if( !hlpalette_init ) + { + image.d_rendermode = LUMP_NORMAL; + Image_SetPalette( palette_hl, d_8toHLtable ); + hlpalette_init = true; + } + + image.d_rendermode = LUMP_HALFLIFE; + image.d_currentpal = d_8toHLtable; +} + +void Image_GetPaletteBMP( const byte *pal ) +{ + image.d_rendermode = LUMP_EXTENDED; + + if( pal ) + { + Image_SetPalette( pal, d_8to24table ); + image.d_currentpal = d_8to24table; + } +} + +void Image_GetPaletteLMP( const byte *pal, int rendermode ) +{ + image.d_rendermode = rendermode; + + if( pal ) + { + Image_SetPalette( pal, d_8to24table ); + image.d_currentpal = d_8to24table; + } + else + { + switch( rendermode ) + { + case LUMP_QUAKE1: + Image_GetPaletteQ1(); + break; + case LUMP_HALFLIFE: + Image_GetPaletteHL(); // default half-life palette + break; + default: + MsgDev( D_ERROR, "Image_GetPaletteLMP: invalid palette specified\n" ); + Image_GetPaletteHL(); // defaulting to half-life palette + break; + } + } +} + +static void Image_ConvertPalTo24bit( rgbdata_t *pic ) +{ + byte *pal32, *pal24; + byte *converted; + int i; + + if( pic->type == PF_INDEXED_24 ) + return; // does nothing + + pal24 = converted = Mem_Alloc( host.imagepool, 768 ); + pal32 = pic->palette; + + for( i = 0; i < 256; i++, pal24 += 3, pal32 += 4 ) + { + pal24[0] = pal32[0]; + pal24[1] = pal32[1]; + pal24[2] = pal32[2]; + } + + Mem_Free( pic->palette ); + pic->palette = converted; + pic->type = PF_INDEXED_24; +} + +void Image_CopyPalette32bit( void ) +{ + if( image.palette ) return; // already created ? + image.palette = Mem_Alloc( host.imagepool, 1024 ); + memcpy( image.palette, image.d_currentpal, 1024 ); +} + +void Image_PaletteHueReplace( byte *palSrc, int newHue, int start, int end, int pal_size ) +{ + float r, g, b; + float maxcol, mincol; + float hue, val, sat; + int i; + + hue = (float)(newHue * ( 360.0f / 255 )); + pal_size = bound( 3, pal_size, 4 ); + + for( i = start; i <= end; i++ ) + { + r = palSrc[i*pal_size+0]; + g = palSrc[i*pal_size+1]; + b = palSrc[i*pal_size+2]; + + maxcol = max( max( r, g ), b ) / 255.0f; + mincol = min( min( r, g ), b ) / 255.0f; + + if( maxcol == 0 ) continue; + + val = maxcol; + sat = (maxcol - mincol) / maxcol; + + mincol = val * (1.0f - sat); + + if( hue <= 120.0f ) + { + b = mincol; + if( hue < 60 ) + { + r = val; + g = mincol + hue * (val - mincol) / (120.0f - hue); + } + else + { + g = val; + r = mincol + (120.0f - hue) * (val - mincol) / hue; + } + } + else if( hue <= 240.0f ) + { + r = mincol; + if( hue < 180.0f ) + { + g = val; + b = mincol + (hue - 120.0f) * (val - mincol) / (240.0f - hue); + } + else + { + b = val; + g = mincol + (240.0f - hue) * (val - mincol) / (hue - 120.0f); + } + } + else + { + g = mincol; + if( hue < 300.0f ) + { + b = val; + r = mincol + (hue - 240.0f) * (val - mincol) / (360.0f - hue); + } + else + { + r = val; + b = mincol + (360.0f - hue) * (val - mincol) / (hue - 240.0f); + } + } + + palSrc[i*pal_size+0] = (byte)(r * 255); + palSrc[i*pal_size+1] = (byte)(g * 255); + palSrc[i*pal_size+2] = (byte)(b * 255); + } +} + +void Image_PaletteTranslate( byte *palSrc, int top, int bottom, int pal_size ) +{ + byte dst[256], src[256]; + int i; + + pal_size = bound( 3, pal_size, 4 ); + for( i = 0; i < 256; i++ ) + src[i] = i; + memcpy( dst, src, 256 ); + + if( top < 128 ) + { + // the artists made some backwards ranges. sigh. + memcpy( dst + SHIRT_HUE_START, src + top, 16 ); + } + else + { + for( i = 0; i < 16; i++ ) + dst[SHIRT_HUE_START+i] = src[top + 15 - i]; + } + + if( bottom < 128 ) + { + memcpy( dst + PANTS_HUE_START, src + bottom, 16 ); + } + else + { + for( i = 0; i < 16; i++ ) + dst[PANTS_HUE_START + i] = src[bottom + 15 - i]; + } + + // last color isn't changed + for( i = 0; i < 255; i++ ) + { + palSrc[i*pal_size+0] = palette_q1[dst[i]*3+0]; + palSrc[i*pal_size+1] = palette_q1[dst[i]*3+1]; + palSrc[i*pal_size+2] = palette_q1[dst[i]*3+2]; + } +} + +void Image_CopyParms( rgbdata_t *src ) +{ + Image_Reset(); + + image.width = src->width; + image.height = src->height; + image.type = src->type; + image.flags = src->flags; + image.size = src->size; + image.palette = src->palette; // may be NULL + + memcpy( image.fogParams, src->fogParams, sizeof( image.fogParams )); +} + +/* +============ +Image_Copy8bitRGBA + +NOTE: must call Image_GetPaletteXXX before used +============ +*/ +qboolean Image_Copy8bitRGBA( const byte *in, byte *out, int pixels ) +{ + int *iout = (int *)out; + byte *fin = (byte *)in; + byte *col; + int i; + + if( !image.d_currentpal ) + { + MsgDev( D_ERROR, "Image_Copy8bitRGBA: no palette set\n" ); + return false; + } + + if( !in ) + { + MsgDev( D_ERROR, "Image_Copy8bitRGBA: no input image\n" ); + return false; + } + + // this is a base image with luma - clear luma pixels + if( image.flags & IMAGE_HAS_LUMA ) + { + for( i = 0; i < image.width * image.height; i++ ) + fin[i] = fin[i] < 224 ? fin[i] : 0; + } + + // check for color + for( i = 0; i < 256; i++ ) + { + col = (byte *)&image.d_currentpal[i]; + if( col[0] != col[1] || col[1] != col[2] ) + { + image.flags |= IMAGE_HAS_COLOR; + break; + } + } + + while( pixels >= 8 ) + { + iout[0] = image.d_currentpal[in[0]]; + iout[1] = image.d_currentpal[in[1]]; + iout[2] = image.d_currentpal[in[2]]; + iout[3] = image.d_currentpal[in[3]]; + iout[4] = image.d_currentpal[in[4]]; + iout[5] = image.d_currentpal[in[5]]; + iout[6] = image.d_currentpal[in[6]]; + iout[7] = image.d_currentpal[in[7]]; + + in += 8; + iout += 8; + pixels -= 8; + } + + if( pixels & 4 ) + { + iout[0] = image.d_currentpal[in[0]]; + iout[1] = image.d_currentpal[in[1]]; + iout[2] = image.d_currentpal[in[2]]; + iout[3] = image.d_currentpal[in[3]]; + in += 4; + iout += 4; + } + + if( pixels & 2 ) + { + iout[0] = image.d_currentpal[in[0]]; + iout[1] = image.d_currentpal[in[1]]; + in += 2; + iout += 2; + } + + if( pixels & 1 ) // last byte + iout[0] = image.d_currentpal[in[0]]; + image.type = PF_RGBA_32; // update image type; + + return true; +} + +static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + if( xi != oldx ) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]); + *out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +static void Image_Resample24LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + + if( xi != oldx ) + { + in += (xi - oldx) * 3; + oldx = xi; + } + + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[3] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[4] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[5] - in[2]) * lerp)>>16) + in[2]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + } + } +} + +void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); + int inwidth4 = inwidth * 4; + int outwidth4 = outwidth * 4; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( host.imagepool, outwidth * 4 * 2); + resamplerow2 = resamplerow1 + outwidth * 4; + + inrow = (const byte *)indata; + + Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f>>16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if (yi == oldy+1) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4*yi; + if( yi == oldy + 1 ) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth4 ); + } + } + + Mem_Free( resamplerow1 ); +} + +void Image_Resample32Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + int i, j; + uint frac, fracstep; + int *inrow, *out = (int *)outdata; // relies on int being 4 bytes + + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++) + { + inrow = (int *)indata + inwidth * (i * inheight / outheight); + frac = fracstep>>1; + j = outwidth - 4; + + while( j >= 0 ) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out[2] = inrow[frac >> 16];frac += fracstep; + out[3] = inrow[frac >> 16];frac += fracstep; + out += 4; + j -= 4; + } + + if( j & 2 ) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out[1] = inrow[frac >> 16];frac += fracstep; + out += 2; + } + + if( j & 1 ) + { + out[0] = inrow[frac >> 16];frac += fracstep; + out += 1; + } + } +} + +void Image_Resample24Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight - 1); + int inwidth3 = inwidth * 3; + int outwidth3 = outwidth * 3; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( host.imagepool, outwidth * 3 * 2 ); + resamplerow2 = resamplerow1 + outwidth*3; + + inrow = (const byte *)indata; + oldy = 0; + Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f>>16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth3 * yi; + if( yi == oldy + 1) memcpy( resamplerow1, resamplerow2, outwidth3 ); + else Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample24LerpLine( inrow + inwidth3, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + out += 12; + resamplerow1 += 12; + resamplerow2 += 12; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + out += 6; + resamplerow1 += 6; + resamplerow2 += 6; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + out += 3; + resamplerow1 += 3; + resamplerow2 += 3; + } + + resamplerow1 -= outwidth3; + resamplerow2 -= outwidth3; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth3*yi; + if( yi == oldy + 1) memcpy( resamplerow1, resamplerow2, outwidth3 ); + else Image_Resample24LerpLine( inrow, resamplerow1, inwidth, outwidth ); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth3 ); + } + } + + Mem_Free( resamplerow1 ); +} + +void Image_Resample24Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + uint frac, fracstep; + int i, j, f, inwidth3 = inwidth * 3; + byte *inrow, *out = (byte *)outdata; + + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++) + { + inrow = (byte *)indata + inwidth3 * (i * inheight / outheight); + frac = fracstep>>1; + j = outwidth - 4; + + while( j >= 0 ) + { + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + j -= 4; + } + + if( j & 2 ) + { + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + out += 2; + } + + if( j & 1 ) + { + f = (frac >> 16)*3; + *out++ = inrow[f+0]; + *out++ = inrow[f+1]; + *out++ = inrow[f+2]; + frac += fracstep; + out += 1; + } + } +} + +void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + int i, j; + byte *in, *inrow; + uint frac, fracstep; + byte *out = (byte *)outdata; + + in = (byte *)indata; + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++, out += outwidth ) + { + inrow = in + inwidth*(i*inheight/outheight); + frac = fracstep>>1; + + for( j = 0; j < outwidth; j++ ) + { + out[j] = inrow[frac>>16]; + frac += fracstep; + } + } +} + +/* +================ +Image_Resample +================ +*/ +byte *Image_ResampleInternal( const void *indata, int inwidth, int inheight, int outwidth, int outheight, int type, qboolean *resampled ) +{ + qboolean quality = Image_CheckFlag( IL_USE_LERPING ); + + // nothing to resample ? + if( inwidth == outwidth && inheight == outheight ) + { + *resampled = false; + return (byte *)indata; + } + + // alloc new buffer + switch( type ) + { + case PF_INDEXED_24: + case PF_INDEXED_32: + image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight ); + Image_Resample8Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight ); + break; + case PF_RGB_24: + case PF_BGR_24: + image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 3 ); + if( quality ) Image_Resample24Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight ); + else Image_Resample24Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight ); + break; + case PF_RGBA_32: + case PF_BGRA_32: + image.tempbuffer = (byte *)Mem_Realloc( host.imagepool, image.tempbuffer, outwidth * outheight * 4 ); + if( quality ) Image_Resample32Lerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight ); + else Image_Resample32Nolerp( indata, inwidth, inheight, image.tempbuffer, outwidth, outheight ); + break; + default: + MsgDev( D_WARN, "Image_Resample: unsupported format %s\n", PFDesc[type].name ); + *resampled = false; + return (byte *)indata; + } + + *resampled = true; + return image.tempbuffer; +} + +/* +================ +Image_Flip +================ +*/ +byte *Image_FlipInternal( const byte *in, word *srcwidth, word *srcheight, int type, int flags ) +{ + int i, x, y; + word width = *srcwidth; + word height = *srcheight; + int samples = PFDesc[type].bpp; + qboolean flip_x = ( flags & IMAGE_FLIP_X ) ? true : false; + qboolean flip_y = ( flags & IMAGE_FLIP_Y ) ? true : false; + qboolean flip_i = ( flags & IMAGE_ROT_90 ) ? true : false; + int row_inc = ( flip_y ? -samples : samples ) * width; + int col_inc = ( flip_x ? -samples : samples ); + int row_ofs = ( flip_y ? ( height - 1 ) * width * samples : 0 ); + int col_ofs = ( flip_x ? ( width - 1 ) * samples : 0 ); + const byte *p, *line; + byte *out; + + // nothing to process + if( !FBitSet( flags, IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90 )) + return (byte *)in; + + switch( type ) + { + case PF_INDEXED_24: + case PF_INDEXED_32: + case PF_RGB_24: + case PF_BGR_24: + case PF_RGBA_32: + case PF_BGRA_32: + image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height * samples ); + break; + default: + MsgDev( D_WARN, "Image_Flip: unsupported format %s\n", PFDesc[type].name ); + return (byte *)in; + } + + out = image.tempbuffer; + + if( flip_i ) + { + for( x = 0, line = in + col_ofs; x < width; x++, line += col_inc ) + for( y = 0, p = line + row_ofs; y < height; y++, p += row_inc, out += samples ) + for( i = 0; i < samples; i++ ) + out[i] = p[i]; + } + else + { + for( y = 0, line = in + row_ofs; y < height; y++, line += row_inc ) + for( x = 0, p = line + col_ofs; x < width; x++, p += col_inc, out += samples ) + for( i = 0; i < samples; i++ ) + out[i] = p[i]; + } + + // update dims + if( FBitSet( flags, IMAGE_ROT_90 )) + { + *srcwidth = height; + *srcheight = width; + } + else + { + *srcwidth = width; + *srcheight = height; + } + + return image.tempbuffer; +} + +byte *Image_CreateLumaInternal( byte *fin, int width, int height, int type, int flags ) +{ + byte *out; + int i; + + if( !FBitSet( flags, IMAGE_HAS_LUMA )) + return (byte *)fin; + + switch( type ) + { + case PF_INDEXED_24: + case PF_INDEXED_32: + out = image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, width * height ); + for( i = 0; i < width * height; i++ ) + *out++ = fin[i] >= 224 ? fin[i] : 0; + break; + default: + // another formats does ugly result :( + Con_Printf( S_ERROR "Image_MakeLuma: unsupported format %s\n", PFDesc[type].name ); + return (byte *)fin; + } + + return image.tempbuffer; +} + +qboolean Image_AddIndexedImageToPack( const byte *in, int width, int height ) +{ + int mipsize = width * height; + qboolean expand_to_rgba = true; + + if( Image_CheckFlag( IL_KEEP_8BIT )) + expand_to_rgba = false; + else if( FBitSet( image.flags, IMAGE_HAS_LUMA|IMAGE_QUAKESKY )) + expand_to_rgba = false; + + image.size = mipsize; + + if( expand_to_rgba ) image.size *= 4; + else Image_CopyPalette32bit(); + + // reallocate image buffer + image.rgba = Mem_Alloc( host.imagepool, image.size ); + if( !expand_to_rgba ) memcpy( image.rgba, in, image.size ); + else if( !Image_Copy8bitRGBA( in, image.rgba, mipsize )) + return false; // probably pallette not installed + + return true; +} + +/* +============= +Image_Decompress + +force to unpack any image to 32-bit buffer +============= +*/ +qboolean Image_Decompress( const byte *data ) +{ + byte *fin, *fout; + int i, size; + + if( !data ) return false; + fin = (byte *)data; + + size = image.width * image.height * 4; + image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, size ); + fout = image.tempbuffer; + + switch( PFDesc[image.type].format ) + { + case PF_INDEXED_24: + if( image.flags & IMAGE_HAS_ALPHA ) + { + if( image.flags & IMAGE_COLORINDEX ) + Image_GetPaletteLMP( image.palette, LUMP_GRADIENT ); + else Image_GetPaletteLMP( image.palette, LUMP_MASKED ); + } + else Image_GetPaletteLMP( image.palette, LUMP_NORMAL ); + // intentional falltrough + case PF_INDEXED_32: + if( !image.d_currentpal ) image.d_currentpal = (uint *)image.palette; + if( !Image_Copy8bitRGBA( fin, fout, image.width * image.height )) + return false; + break; + case PF_BGR_24: + for (i = 0; i < image.width * image.height; i++ ) + { + fout[(i<<2)+0] = fin[i*3+2]; + fout[(i<<2)+1] = fin[i*3+1]; + fout[(i<<2)+2] = fin[i*3+0]; + fout[(i<<2)+3] = 255; + } + break; + case PF_RGB_24: + for (i = 0; i < image.width * image.height; i++ ) + { + fout[(i<<2)+0] = fin[i*3+0]; + fout[(i<<2)+1] = fin[i*3+1]; + fout[(i<<2)+2] = fin[i*3+2]; + fout[(i<<2)+3] = 255; + } + break; + case PF_BGRA_32: + for( i = 0; i < image.width * image.height; i++ ) + { + fout[i*4+0] = fin[i*4+2]; + fout[i*4+1] = fin[i*4+1]; + fout[i*4+2] = fin[i*4+0]; + fout[i*4+3] = fin[i*4+3]; + } + break; + case PF_RGBA_32: + // fast default case + memcpy( fout, fin, size ); + break; + default: return false; + } + + // set new size + image.size = size; + + return true; +} + +rgbdata_t *Image_DecompressInternal( rgbdata_t *pic ) +{ + // quick case to reject unneeded conversions + if( pic->type == PF_RGBA_32 ) + return pic; + + Image_CopyParms( pic ); + image.size = image.ptr = 0; + + Image_Decompress( pic->buffer ); + + // now we can change type to RGBA + pic->type = PF_RGBA_32; + + pic->buffer = Mem_Realloc( host.imagepool, pic->buffer, image.size ); + memcpy( pic->buffer, image.tempbuffer, image.size ); + if( pic->palette ) Mem_Free( pic->palette ); + pic->flags = image.flags; + pic->palette = NULL; + + return pic; +} + +rgbdata_t *Image_LightGamma( rgbdata_t *pic ) +{ + byte *in = (byte *)pic->buffer; + int i; + + if( pic->type != PF_RGBA_32 ) + return pic; + + for( i = 0; i < pic->width * pic->height; i++, in += 4 ) + { + in[0] = LightToTexGamma( in[0] ); + in[1] = LightToTexGamma( in[1] ); + in[2] = LightToTexGamma( in[2] ); + } + + return pic; +} + +qboolean Image_RemapInternal( rgbdata_t *pic, int topColor, int bottomColor ) +{ + if( !pic->palette ) + { + MsgDev( D_ERROR, "Image_Remap: palette is missed\n" ); + return false; + } + + switch( pic->type ) + { + case PF_INDEXED_24: + break; + case PF_INDEXED_32: + Image_ConvertPalTo24bit( pic ); + break; + default: + MsgDev( D_ERROR, "Image_Remap: unsupported format %s\n", PFDesc[pic->type].name ); + return false; + } + + if( Image_ComparePalette( pic->palette ) == PAL_QUAKE1 ) + { + Image_PaletteTranslate( pic->palette, topColor * 16, bottomColor * 16, 3 ); + } + else + { + // g-cont. preview images has a swapped top and bottom colors. I don't know why. + Image_PaletteHueReplace( pic->palette, topColor, SUIT_HUE_START, SUIT_HUE_END, 3 ); + Image_PaletteHueReplace( pic->palette, bottomColor, PLATE_HUE_START, PLATE_HUE_END, 3 ); + } + + return true; +} + +/* +================== +Image_ApplyFilter + +Applies a 5 x 5 filtering matrix to the texture, then runs it through a simulated OpenGL texture environment +blend with the original data to derive a new texture. Freaky, funky, and *f--king* *fantastic*. You can do +reasonable enough "fake bumpmapping" with this baby... + +Filtering algorithm from http://www.student.kuleuven.ac.be/~m0216922/CG/filtering.html +All credit due +================== +*/ +qboolean Image_ApplyFilter( rgbdata_t *pic, int filter, float factor, float bias, flFlags_t flags, GLenum blendFunc ) +{ + int i, x, y; + uint *fin, *fout; + size_t size; + + // first expand the image into 32-bit buffer + pic = Image_DecompressInternal( pic ); + + size = image.width * image.height * 4; + image.tempbuffer = Mem_Realloc( host.imagepool, image.tempbuffer, size ); + fout = (uint *)image.tempbuffer; + fin = (uint *)pic->buffer; + + for( x = 0; x < image.width; x++ ) + { + for( y = 0; y < image.height; y++ ) + { + vec3_t vout = { 0.0f, 0.0f, 0.0f }; + int pos_x, pos_y; + + for( pos_x = 0; pos_x < FILTER_SIZE; pos_x++ ) + { + for( pos_y = 0; pos_y < FILTER_SIZE; pos_y++ ) + { + int img_x = (x - (FILTER_SIZE / 2) + pos_x + image.width) % image.width; + int img_y = (y - (FILTER_SIZE / 2) + pos_y + image.height) % image.height; + + // casting's a unary operation anyway, so the othermost set of brackets in the left part + // of the rvalue should not be necessary... but i'm paranoid when it comes to C... + vout[0] += ((float)((byte *)&fin[img_y * image.width + img_x])[0]) * FILTER[filter][pos_x][pos_y]; + vout[1] += ((float)((byte *)&fin[img_y * image.width + img_x])[1]) * FILTER[filter][pos_x][pos_y]; + vout[2] += ((float)((byte *)&fin[img_y * image.width + img_x])[2]) * FILTER[filter][pos_x][pos_y]; + } + } + + // multiply by factor, add bias, and clamp + for( i = 0; i < 3; i++ ) + { + vout[i] *= factor; + vout[i] += bias; + vout[i] = bound( 0.0f, vout[i], 255.0f ); + } + + if( flags & FILTER_GRAYSCALE ) + { + // NTSC greyscale conversion standard + float avg = (vout[0] * 30.0f + vout[1] * 59.0f + vout[2] * 11.0f) / 100.0f; + + // divide by 255 so GL operations work as expected + vout[0] = avg / 255.0f; + vout[1] = avg / 255.0f; + vout[2] = avg / 255.0f; + } + + // write to temp - first, write data in (to get the alpha channel quickly and + // easily, which will be left well alone by this particular operation...!) + fout[y * image.width + x] = fin[y * image.width + x]; + + // now write in each element, applying the blend operator. blend + // operators are based on standard OpenGL TexEnv modes, and the + // formulas are derived from the OpenGL specs (http://www.opengl.org). + for( i = 0; i < 3; i++ ) + { + // divide by 255 so GL operations work as expected + float src = ((float)((byte *)&fin[y * image.width + x])[i]) / 255.0f; + float tmp; + + switch( blendFunc ) + { + case GL_ADD: + tmp = vout[i] + src; + break; + case GL_BLEND: + // default is FUNC_ADD here + // CsS + CdD works out as Src * Dst * 2 + tmp = vout[i] * src * 2.0f; + break; + case GL_DECAL: + // same as GL_REPLACE unless there's alpha, which we ignore for this + case GL_REPLACE: + tmp = vout[i]; + break; + case GL_ADD_SIGNED: + tmp = (vout[i] + src) - 0.5f; + break; + case GL_MODULATE: + default: // same as default + tmp = vout[i] * src; + break; + } + + // multiply back by 255 to get the proper byte scale + tmp *= 255.0f; + + // bound the temp target again now, cos the operation may have thrown it out + tmp = bound( 0.0f, tmp, 255.0f ); + // and copy it in + ((byte *)&fout[y * image.width + x])[i] = (byte)tmp; + } + } + } + + // copy result back + memcpy( fin, fout, size ); + + return true; +} + +qboolean Image_Process( rgbdata_t **pix, int width, int height, uint flags, imgfilter_t *filter ) +{ + rgbdata_t *pic = *pix; + qboolean result = true; + byte *out; + + // check for buffers + if( !pic || !pic->buffer ) + { + MsgDev( D_WARN, "Image_Process: NULL image\n" ); + image.force_flags = 0; + return false; + } + + if( !flags && !filter ) + { + // clear any force flags + image.force_flags = 0; + return false; // no operation specfied + } + + if( FBitSet( flags, IMAGE_MAKE_LUMA )) + { + out = Image_CreateLumaInternal( pic->buffer, pic->width, pic->height, pic->type, pic->flags ); + if( pic->buffer != out ) memcpy( pic->buffer, image.tempbuffer, pic->size ); + ClearBits( pic->flags, IMAGE_HAS_LUMA ); + } + + if( flags & IMAGE_REMAP ) + { + // NOTE: user should keep copy of indexed image manually for new changes + if( Image_RemapInternal( pic, width, height )) + pic = Image_DecompressInternal( pic ); + } + + // update format to RGBA if any + if( flags & IMAGE_FORCE_RGBA ) pic = Image_DecompressInternal( pic ); + if( flags & IMAGE_LIGHTGAMMA ) pic = Image_LightGamma( pic ); + + if( filter ) Image_ApplyFilter( pic, filter->filter, filter->factor, filter->bias, filter->flags, filter->blendFunc ); + + out = Image_FlipInternal( pic->buffer, &pic->width, &pic->height, pic->type, flags ); + if( pic->buffer != out ) memcpy( pic->buffer, image.tempbuffer, pic->size ); + + if( FBitSet( flags, IMAGE_RESAMPLE ) && width > 0 && height > 0 ) + { + int w = bound( 1, width, IMAGE_MAXWIDTH ); // 1 - 4096 + int h = bound( 1, height, IMAGE_MAXHEIGHT); // 1 - 4096 + qboolean resampled = false; + + out = Image_ResampleInternal((uint *)pic->buffer, pic->width, pic->height, w, h, pic->type, &resampled ); + + if( resampled ) // resampled or filled + { + MsgDev( D_NOTE, "Image_Resample: from[%d x %d] to [%d x %d]\n", pic->width, pic->height, w, h ); + pic->width = w, pic->height = h; + pic->size = w * h * PFDesc[pic->type].bpp; + Mem_Free( pic->buffer ); // free original image buffer + pic->buffer = Image_Copy( pic->size ); // unzone buffer (don't touch image.tempbuffer) + } + else + { + // not a resampled or filled + result = false; + } + } + + // quantize image + if( FBitSet( flags, IMAGE_QUANTIZE )) + pic = Image_Quantize( pic ); + + *pix = pic; + + // clear any force flags + image.force_flags = 0; + + return result; +} \ No newline at end of file diff --git a/engine/common/imagelib/img_wad.c b/engine/common/imagelib/img_wad.c new file mode 100644 index 00000000..95896b4e --- /dev/null +++ b/engine/common/imagelib/img_wad.c @@ -0,0 +1,522 @@ +/* +img_mip.c - hl1 and q1 image mips +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "imagelib.h" +#include "mathlib.h" +#include "wadfile.h" +#include "studio.h" +#include "sprite.h" +#include "qfont.h" + +/* +============ +Image_LoadPAL +============ +*/ +qboolean Image_LoadPAL( const char *name, const byte *buffer, size_t filesize ) +{ + int rendermode = LUMP_NORMAL; + + if( filesize != 768 ) + { + MsgDev( D_ERROR, "Image_LoadPAL: (%s) have invalid size (%d should be %d)\n", name, filesize, 768 ); + return false; + } + + if( name[0] == '#' ) + { + // using palette name as rendermode + if( Q_stristr( name, "normal" )) + rendermode = LUMP_NORMAL; + else if( Q_stristr( name, "masked" )) + rendermode = LUMP_MASKED; + else if( Q_stristr( name, "gradient" )) + rendermode = LUMP_GRADIENT; + else if( Q_stristr( name, "valve" )) + { + rendermode = LUMP_HALFLIFE; + buffer = NULL; // force to get HL palette + } + else if( Q_stristr( name, "id" )) + { + rendermode = LUMP_QUAKE1; + buffer = NULL; // force to get Q1 palette + } + } + + // NOTE: image.d_currentpal not cleared with Image_Reset() + // and stay valid any time before new call of Image_SetPalette + Image_GetPaletteLMP( buffer, rendermode ); + Image_CopyPalette32bit(); + + image.rgba = NULL; // only palette, not real image + image.size = 1024; // expanded palette + image.width = image.height = 0; + image.depth = 1; + + return true; +} + +/* +============ +Image_LoadFNT +============ +*/ +qboolean Image_LoadFNT( const char *name, const byte *buffer, size_t filesize ) +{ + qfont_t font; + const byte *pal, *fin; + size_t size; + int numcolors; + + if( image.hint == IL_HINT_Q1 ) + return false; // Quake1 doesn't have qfonts + + if( filesize < sizeof( font )) + return false; + + memcpy( &font, buffer, sizeof( font )); + + // last sixty four bytes - what the hell ???? + size = sizeof( qfont_t ) - 4 + ( font.height * font.width * QCHAR_WIDTH ) + sizeof( short ) + 768 + 64; + + if( size != filesize ) + { + // oldstyle font: "conchars" or "creditsfont" + image.width = 256; // hardcoded + image.height = font.height; + } + else + { + // Half-Life 1.1.0.0 font style (qfont_t) + image.width = font.width * QCHAR_WIDTH; + image.height = font.height; + } + + if( !Image_LumpValidSize( name )) + return false; + + fin = buffer + sizeof( font ) - 4; + pal = fin + (image.width * image.height); + numcolors = *(short *)pal, pal += sizeof( short ); + + if( numcolors == 768 || numcolors == 256 ) + { + // g-cont. make sure that is didn't hit anything + Image_GetPaletteLMP( pal, LUMP_MASKED ); + image.flags |= IMAGE_HAS_ALPHA; // fonts always have transparency + } + else + { + if( image.hint == IL_HINT_NO ) + MsgDev( D_ERROR, "Image_LoadFNT: (%s) have invalid palette size %d\n", name, numcolors ); + return false; + } + + image.type = PF_INDEXED_32; // 32-bit palette + image.depth = 1; + + return Image_AddIndexedImageToPack( fin, image.width, image.height ); +} + +/* +============ +Image_LoadMDL +============ +*/ +qboolean Image_LoadMDL( const char *name, const byte *buffer, size_t filesize ) +{ + byte *fin; + size_t pixels; + mstudiotexture_t *pin; + int flags; + + pin = (mstudiotexture_t *)buffer; + flags = pin->flags; + + image.width = pin->width; + image.height = pin->height; + pixels = image.width * image.height; + fin = (byte *)pin->index; // setup buffer + + if( !Image_ValidSize( name )) return false; + + if( image.hint == IL_HINT_HL ) + { + if( filesize < ( sizeof( *pin ) + pixels + 768 )) + return false; + + if( FBitSet( flags, STUDIO_NF_MASKED )) + { + byte *pal = fin + pixels; + + Image_GetPaletteLMP( pal, LUMP_MASKED ); + image.flags |= IMAGE_HAS_ALPHA|IMAGE_ONEBIT_ALPHA; + } + else Image_GetPaletteLMP( fin + pixels, LUMP_NORMAL ); + } + else + { + if( image.hint == IL_HINT_NO ) + MsgDev( D_ERROR, "Image_LoadMDL: lump (%s) is corrupted\n", name ); + return false; // unknown or unsupported mode rejected + } + + image.type = PF_INDEXED_32; // 32-bit palete + image.depth = 1; + + return Image_AddIndexedImageToPack( fin, image.width, image.height ); +} + +/* +============ +Image_LoadSPR +============ +*/ +qboolean Image_LoadSPR( const char *name, const byte *buffer, size_t filesize ) +{ + dspriteframe_t *pin; // identical for q1\hl sprites + qboolean truecolor = false; + + if( image.hint == IL_HINT_HL ) + { + if( !image.d_currentpal ) + { + MsgDev( D_ERROR, "Image_LoadSPR: (%s) palette not installed\n", name ); + return false; + } + } + else if( image.hint == IL_HINT_Q1 ) + { + Image_GetPaletteQ1(); + } + else + { + // unknown mode rejected + return false; + } + + pin = (dspriteframe_t *)buffer; + image.width = pin->width; + image.height = pin->height; + + if( filesize < image.width * image.height ) + { + MsgDev( D_ERROR, "Image_LoadSPR: file (%s) have invalid size\n", name ); + return false; + } + + if( filesize == ( image.width * image.height * 4 )) + truecolor = true; + + // sorry, can't validate palette rendermode + if( !Image_LumpValidSize( name )) return false; + image.type = (truecolor) ? PF_RGBA_32 : PF_INDEXED_32; // 32-bit palete + image.depth = 1; + + // detect alpha-channel by palette type + switch( image.d_rendermode ) + { + case LUMP_MASKED: + SetBits( image.flags, IMAGE_ONEBIT_ALPHA ); + case LUMP_GRADIENT: + case LUMP_QUAKE1: + SetBits( image.flags, IMAGE_HAS_ALPHA ); + break; + } + + if( truecolor ) + { + // spr32 support + image.size = image.width * image.height * 4; + image.rgba = Mem_Alloc( host.imagepool, image.size ); + memcpy( image.rgba, (byte *)(pin + 1), image.size ); + SetBits( image.flags, IMAGE_HAS_COLOR ); // Color. True Color! + return true; + } + + return Image_AddIndexedImageToPack( (byte *)(pin + 1), image.width, image.height ); +} + +/* +============ +Image_LoadLMP +============ +*/ +qboolean Image_LoadLMP( const char *name, const byte *buffer, size_t filesize ) +{ + lmp_t lmp; + byte *fin, *pal; + int rendermode; + int i, pixels; + + if( filesize < sizeof( lmp )) + { + MsgDev( D_ERROR, "Image_LoadLMP: file (%s) have invalid size\n", name ); + return false; + } + + // valve software trick (particle palette) + if( Q_stristr( name, "palette.lmp" )) + return Image_LoadPAL( name, buffer, filesize ); + + // id software trick (image without header) + if( image.hint != IL_HINT_HL && Q_stristr( name, "conchars" )) + { + image.width = image.height = 128; + rendermode = LUMP_QUAKE1; + filesize += sizeof( lmp ); + fin = (byte *)buffer; + + // need to remap transparent color from first to last entry + for( i = 0; i < 16384; i++ ) if( !fin[i] ) fin[i] = 0xFF; + } + else + { + fin = (byte *)buffer; + memcpy( &lmp, fin, sizeof( lmp )); + image.width = lmp.width; + image.height = lmp.height; + rendermode = LUMP_NORMAL; + fin += sizeof( lmp ); + } + + pixels = image.width * image.height; + + if( filesize < sizeof( lmp ) + pixels ) + { + MsgDev( D_ERROR, "Image_LoadLMP: file (%s) have invalid size %d\n", name, filesize ); + return false; + } + + if( !Image_ValidSize( name )) + return false; + + if( image.hint != IL_HINT_Q1 && filesize > (int)sizeof(lmp) + pixels ) + { + int numcolors; + + if( fin[0] == 255 ) + { + image.flags |= IMAGE_HAS_ALPHA; + rendermode = LUMP_MASKED; + } + pal = fin + pixels; + numcolors = *(short *)pal; + if( numcolors != 256 ) pal = NULL; // corrupted lump ? + else pal += sizeof( short ); + } + else if( image.hint != IL_HINT_HL ) + { + image.flags |= IMAGE_HAS_ALPHA; + rendermode = LUMP_QUAKE1; + pal = NULL; + } + else + { + // unknown mode rejected + return false; + } + + Image_GetPaletteLMP( pal, rendermode ); + image.type = PF_INDEXED_32; // 32-bit palete + image.depth = 1; + + return Image_AddIndexedImageToPack( fin, image.width, image.height ); +} + +/* +============= +Image_LoadMIP +============= +*/ +qboolean Image_LoadMIP( const char *name, const byte *buffer, size_t filesize ) +{ + mip_t mip; + qboolean hl_texture; + byte *fin, *pal; + int ofs[4], rendermode; + int i, pixels, numcolors; + int reflectivity[3] = { 0, 0, 0 }; + + if( filesize < sizeof( mip )) + { + MsgDev( D_ERROR, "Image_LoadMIP: file (%s) have invalid size\n", name ); + return false; + } + + memcpy( &mip, buffer, sizeof( mip )); + image.width = mip.width; + image.height = mip.height; + + if( !Image_ValidSize( name )) + return false; + + memcpy( ofs, mip.offsets, sizeof( ofs )); + pixels = image.width * image.height; + + if( image.hint != IL_HINT_Q1 && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6) + sizeof(short) + 768) + { + // half-life 1.0.0.1 mip version with palette + fin = (byte *)buffer + mip.offsets[0]; + pal = (byte *)buffer + mip.offsets[0] + (((image.width * image.height) * 85)>>6); + numcolors = *(short *)pal; + if( numcolors != 256 ) pal = NULL; // corrupted mip ? + else pal += sizeof( short ); // skip colorsize + + hl_texture = true; + + // setup rendermode + if( Q_strrchr( name, '{' )) + { + // NOTE: decals with 'blue base' can be interpret as colored decals + if( !Image_CheckFlag( IL_LOAD_DECAL ) || ( pal[765] == 0 && pal[766] == 0 && pal[767] == 255 )) + { + rendermode = LUMP_MASKED; + image.flags |= IMAGE_ONEBIT_ALPHA; + } + else + { + // classic gradient decals + image.flags |= IMAGE_COLORINDEX; + rendermode = LUMP_GRADIENT; + } + + SetBits( image.flags, IMAGE_HAS_ALPHA ); + } + else + { + int pal_type; + + // NOTE: we can have luma-pixels if quake1 texture + // converted into the hl texture but palette leave unchanged + // this is a good reason for using fullbright pixels + pal_type = Image_ComparePalette( pal ); + + // check for luma pixels (but ignore liquid textures, this a Xash3D limitation) + if( mip.name[0] != '!' && pal_type == PAL_QUAKE1 ) + { + for( i = 0; i < image.width * image.height; i++ ) + { + if( fin[i] > 224 ) + { + image.flags |= IMAGE_HAS_LUMA; + break; + } + } + } + + if( pal_type == PAL_QUAKE1 ) + SetBits( image.flags, IMAGE_QUAKEPAL ); + rendermode = LUMP_NORMAL; + } + + Image_GetPaletteLMP( pal, rendermode ); + image.d_currentpal[255] &= 0xFFFFFF; + } + else if( image.hint != IL_HINT_HL && filesize >= (int)sizeof(mip) + ((pixels * 85)>>6)) + { + // quake1 1.01 mip version without palette + fin = (byte *)buffer + mip.offsets[0]; + pal = NULL; // clear palette + rendermode = LUMP_NORMAL; + + hl_texture = false; + + // check for luma and alpha pixels + for( i = 0; i < image.width * image.height; i++ ) + { + if( fin[i] > 224 && fin[i] != 255 ) + { + // don't apply luma to water surfaces because + // we use glpoly->next for store luma chain each frame + // and can't modify glpoly_t because many-many HL mods + // expected unmodified glpoly_t and can crashes on changed struct + // water surfaces uses glpoly->next as pointer to subdivided surfaces (as q1) + if( mip.name[0] != '*' && mip.name[0] != '!' ) + image.flags |= IMAGE_HAS_LUMA; + break; + } + } + + // Arcane Dimensions has the transparent textures + if( Q_strrchr( name, '{' )) + { + for( i = 0; i < image.width * image.height; i++ ) + { + if( fin[i] == 255 ) + { + // don't set ONEBIT_ALPHA flag for some reasons + image.flags |= IMAGE_HAS_ALPHA; + break; + } + } + } + + SetBits( image.flags, IMAGE_QUAKEPAL ); + Image_GetPaletteQ1(); + } + else + { + if( image.hint == IL_HINT_NO ) + MsgDev( D_ERROR, "Image_LoadMIP: lump (%s) is corrupted\n", name ); + return false; // unknown or unsupported mode rejected + } + + // check for quake-sky texture + if( !Q_strncmp( mip.name, "sky", 3 ) && image.width == ( image.height * 2 )) + { + // g-cont: we need to run additional checks for palette type and colors ? + image.flags |= IMAGE_QUAKESKY; + } + + // check for half-life water texture + if( hl_texture && ( mip.name[0] == '!' || !Q_strnicmp( mip.name, "water", 5 ))) + { + // grab the fog color + image.fogParams[0] = pal[3*3+0]; + image.fogParams[1] = pal[3*3+1]; + image.fogParams[2] = pal[3*3+2]; + + // grab the fog density + image.fogParams[3] = pal[4*3+0]; + } + else if( hl_texture && ( rendermode == LUMP_GRADIENT )) + { + // grab the decal color + image.fogParams[0] = pal[255*3+0]; + image.fogParams[1] = pal[255*3+1]; + image.fogParams[2] = pal[255*3+2]; + + // calc the decal reflectivity + image.fogParams[3] = VectorAvg( image.fogParams ); + } + else if( pal != NULL )// calc texture reflectivity + { + for( i = 0; i < 256; i++ ) + { + reflectivity[0] += pal[i*3+0]; + reflectivity[1] += pal[i*3+1]; + reflectivity[2] += pal[i*3+2]; + } + + VectorDivide( reflectivity, 256, image.fogParams ); + } + + image.type = PF_INDEXED_32; // 32-bit palete + image.depth = 1; + + return Image_AddIndexedImageToPack( fin, image.width, image.height ); +} \ No newline at end of file diff --git a/engine/common/infostring.c b/engine/common/infostring.c new file mode 100644 index 00000000..fd299ac1 --- /dev/null +++ b/engine/common/infostring.c @@ -0,0 +1,500 @@ +/* +infostring.c - network info strings +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +#define MAX_KV_SIZE 128 + +/* +======================================================================= + + INFOSTRING STUFF +======================================================================= +*/ +/* +=============== +Info_Print + +printing current key-value pair +=============== +*/ +void Info_Print( const char *s ) +{ + char key[MAX_KV_SIZE]; + char value[MAX_KV_SIZE]; + int l, count; + char *o; + + if( *s == '\\' ) s++; + + while( *s ) + { + count = 0; + o = key; + + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + + l = o - key; + if( l < 20 ) + { + memset( o, ' ', 20 - l ); + key[20] = 0; + } + else *o = 0; + + Con_Printf( "%s", key ); + + if( !*s ) + { + Con_Printf( "(null)\n" ); + return; + } + + count = 0; + o = value; + s++; + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + *o = 0; + + if( *s ) s++; + Con_Printf( "%s\n", value ); + } +} + +/* +============== +Info_IsValid + +check infostring for potential problems +============== +*/ +qboolean Info_IsValid( const char *s ) +{ + char key[MAX_KV_SIZE]; + char value[MAX_KV_SIZE]; + int count; + char *o; + + if( *s == '\\' ) s++; + + while( *s ) + { + count = 0; + o = key; + + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + *o = 0; + + if( !*s ) return false; + + count = 0; + o = value; + s++; + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + *o = 0; + + if( !Q_strlen( value )) + return false; + + if( *s ) s++; + } + + return true; +} + +/* +============== +Info_WriteVars + +============== +*/ +void Info_WriteVars( file_t *f ) +{ + char *s = CL_Userinfo(); + char pkey[MAX_SERVERINFO_STRING]; + static char value[4][MAX_SERVERINFO_STRING]; // use two buffers so compares work without stomping on each other + static int valueindex; + convar_t *pcvar; + char *o; + + valueindex = (valueindex + 1) % 4; + if( *s == '\\' ) s++; + + while( 1 ) + { + o = pkey; + while( *s != '\\' ) + { + if( !*s ) return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while( *s != '\\' && *s ) + { + if( !*s ) return; + *o++ = *s++; + } + *o = 0; + + pcvar = Cvar_FindVar( pkey ); + + if( !pcvar && pkey[0] != '*' ) // don't store out star keys + FS_Printf( f, "setinfo \"%s\" \"%s\"\n", pkey, value[valueindex] ); + + if( !*s ) return; + s++; + } +} + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) +{ + char pkey[MAX_KV_SIZE]; + static char value[4][MAX_KV_SIZE]; // use two buffers so compares work without stomping on each other + static int valueindex; + int count; + char *o; + + valueindex = (valueindex + 1) % 4; + if( *s == '\\' ) s++; + + while( 1 ) + { + count = 0; + o = pkey; + + while( count < (MAX_KV_SIZE - 1) && *s != '\\' ) + { + if( !*s ) return ""; + *o++ = *s++; + count++; + } + + *o = 0; + s++; + + o = value[valueindex]; + count = 0; + + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + if( !*s ) return ""; + *o++ = *s++; + count++; + } + *o = 0; + + if( !Q_strcmp( key, pkey )) + return value[valueindex]; + if( !*s ) return ""; + s++; + } +} + +qboolean Info_RemoveKey( char *s, const char *key ) +{ + char *start; + char pkey[MAX_KV_SIZE]; + char value[MAX_KV_SIZE]; + int cmpsize = Q_strlen( key ); + int count; + char *o; + + if( cmpsize > ( MAX_KV_SIZE - 1 )) + cmpsize = MAX_KV_SIZE - 1; + + if( Q_strstr( key, "\\" )) + return false; + + while( 1 ) + { + start = s; + if( *s == '\\' ) s++; + count = 0; + o = pkey; + + while( count < (MAX_KV_SIZE - 1) && *s != '\\' ) + { + if( !*s ) return false; + *o++ = *s++; + count++; + } + *o = 0; + s++; + + count = 0; + o = value; + + while( count < (MAX_KV_SIZE - 1) && *s != '\\' && *s ) + { + if( !*s ) return false; + *o++ = *s++; + count++; + } + *o = 0; + + if( !Q_strncmp( key, pkey, cmpsize )) + { + Q_strcpy( start, s ); // remove this part + return true; + } + + if( !*s ) return false; + } +} + +void Info_RemovePrefixedKeys( char *start, char prefix ) +{ + char *s, *o; + char pkey[MAX_KV_SIZE]; + char value[MAX_KV_SIZE]; + int count; + + s = start; + + while( 1 ) + { + if( *s == '\\' ) s++; + + count = 0; + o = pkey; + + while( count < (MAX_KV_SIZE - 1) && *s != '\\' ) + { + if( !*s ) return; + *o++ = *s++; + count++; + } + *o = 0; + s++; + + count = 0; + o = value; + + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + if( !*s ) return; + *o++ = *s++; + count++; + } + *o = 0; + + if( pkey[0] == prefix ) + { + Info_RemoveKey( start, pkey ); + s = start; + } + + if( !*s ) return; + } +} + +qboolean Info_IsKeyImportant( const char *key ) +{ + if( key[0] == '*' ) + return true; + if( !Q_strcmp( key, "name" )) + return true; + if( !Q_strcmp( key, "model" )) + return true; + if( !Q_strcmp( key, "rate" )) + return true; + if( !Q_strcmp( key, "topcolor" )) + return true; + if( !Q_strcmp( key, "bottomcolor" )) + return true; + if( !Q_strcmp( key, "cl_updaterate" )) + return true; + if( !Q_strcmp( key, "cl_lw" )) + return true; + if( !Q_strcmp( key, "cl_lc" )) + return true; + if( !Q_strcmp( key, "cl_nopred" )) + return true; + return false; +} + +char *Info_FindLargestKey( char *s ) +{ + char key[MAX_KV_SIZE]; + char value[MAX_KV_SIZE]; + static char largest_key[128]; + int largest_size = 0; + int l, count; + char *o; + + *largest_key = 0; + + if( *s == '\\' ) s++; + + while( *s ) + { + int size = 0; + + count = 0; + o = key; + + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + + l = o - key; + *o = 0; + size = Q_strlen( key ); + + if( !*s ) return largest_key; + + count = 0; + o = value; + s++; + while( count < (MAX_KV_SIZE - 1) && *s && *s != '\\' ) + { + *o++ = *s++; + count++; + } + *o = 0; + + if( *s ) s++; + + size += Q_strlen( value ); + + if(( size > largest_size ) && !Info_IsKeyImportant( key )) + { + Q_strncpy( largest_key, key, sizeof( largest_key )); + largest_size = size; + } + } + + return largest_key; +} + +qboolean Info_SetValueForStarKey( char *s, const char *key, const char *value, int maxsize ) +{ + char new[1024], *v; + int c, team; + + if( Q_strstr( key, "\\" ) || Q_strstr( value, "\\" )) + { + MsgDev( D_ERROR, "SetValueForKey: can't use keys or values with a \\\n" ); + return false; + } + + if( Q_strstr( key, ".." ) || Q_strstr( value, ".." )) + return false; + + if( Q_strstr( key, "\"" ) || Q_strstr( value, "\"" )) + { + MsgDev( D_ERROR, "SetValueForKey: can't use keys or values with a \"\n" ); + return false; + } + + if( Q_strlen( key ) > ( MAX_KV_SIZE - 1 ) || Q_strlen( value ) > ( MAX_KV_SIZE - 1 )) + { + MsgDev( D_ERROR, "SetValueForKey: keys and values must be < %i characters.\n", MAX_KV_SIZE ); + return false; + } + + Info_RemoveKey( s, key ); + + if( !value || !Q_strlen( value )) + return true; // just clear variable + + Q_snprintf( new, sizeof( new ), "\\%s\\%s", key, value ); + if( Q_strlen( new ) + Q_strlen( s ) > maxsize ) + { + // no more room in buffer to add key/value + if( Info_IsKeyImportant( key )) + { + // keep removing the largest key/values until we have room + char *largekey; + + do + { + largekey = Info_FindLargestKey( s ); + Info_RemoveKey( s, largekey ); + } while((( Q_strlen( new ) + Q_strlen( s )) >= maxsize ) && *largekey != 0 ); + + if( largekey[0] == 0 ) + { + // no room to add setting + MsgDev( D_ERROR, "SetValueForKey: info string length exceeded\n" ); + return true; // info changed, new value can't saved + } + } + else + { + // no room to add setting + MsgDev( D_ERROR, "SetValueForKey: info string length exceeded\n" ); + return true; // info changed, new value can't saved + } + } + + // only copy ascii values + s += Q_strlen( s ); + v = new; + + team = ( Q_stricmp( key, "team" ) == 0 ) ? true : false; + + while( *v ) + { + c = (byte)*v++; + if( team ) c = Q_tolower( c ); + if( c > 13 ) *s++ = c; + } + *s = 0; + + // all done + return true; +} + +qboolean Info_SetValueForKey( char *s, const char *key, const char *value, int maxsize ) +{ + if( key[0] == '*' ) + { + MsgDev( D_ERROR, "Can't set *keys\n" ); + return false; + } + + return Info_SetValueForStarKey( s, key, value, maxsize ); +} \ No newline at end of file diff --git a/engine/common/input.c b/engine/common/input.c new file mode 100644 index 00000000..c4a246cd --- /dev/null +++ b/engine/common/input.c @@ -0,0 +1,587 @@ +/* +input.c - win32 input devices +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "input.h" +#include "client.h" +#include "vgui_draw.h" + +#define PRINTSCREEN_ID 1 +#define WND_HEADSIZE wnd_caption // some offset +#define WND_BORDER 3 // sentinel border in pixels + +HICON in_mousecursor; +qboolean in_mouseactive; // false when not focus app +qboolean in_restore_spi; +qboolean in_mouseinitialized; +int in_mouse_oldbuttonstate; +qboolean in_mouse_suspended; +qboolean in_mouse_savedpos; +int in_mouse_buttons; +RECT window_rect, real_rect; +POINT in_lastvalidpos; +uint in_mouse_wheel; +int wnd_caption; + +static byte scan_to_key[128] = +{ + 0,27,'1','2','3','4','5','6','7','8','9','0','-','=',K_BACKSPACE,9, + 'q','w','e','r','t','y','u','i','o','p','[',']', 13 , K_CTRL, + 'a','s','d','f','g','h','j','k','l',';','\'','`', + K_SHIFT,'\\','z','x','c','v','b','n','m',',','.','/',K_SHIFT, + '*',K_ALT,' ',K_CAPSLOCK, + K_F1,K_F2,K_F3,K_F4,K_F5,K_F6,K_F7,K_F8,K_F9,K_F10, + K_PAUSE,0,K_HOME,K_UPARROW,K_PGUP,K_KP_MINUS,K_LEFTARROW,K_KP_5, + K_RIGHTARROW,K_KP_PLUS,K_END,K_DOWNARROW,K_PGDN,K_INS,K_DEL, + 0,0,0,K_F11,K_F12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 +}; + +// extra mouse buttons +static int mouse_buttons[] = +{ + MK_LBUTTON, + MK_RBUTTON, + MK_MBUTTON, + MK_XBUTTON1, + MK_XBUTTON2, + MK_XBUTTON3, + MK_XBUTTON4, + MK_XBUTTON5 +}; + +/* +======= +Host_MapKey + +Map from windows to engine keynums +======= +*/ +static int Host_MapKey( int key ) +{ + int result, modified; + qboolean is_extended = false; + + modified = ( key >> 16 ) & 255; + if( modified > 127 ) return 0; + + if( key & ( 1 << 24 )) + is_extended = true; + + result = scan_to_key[modified]; + + if( !is_extended ) + { + switch( result ) + { + case K_HOME: return K_KP_HOME; + case K_UPARROW: return K_KP_UPARROW; + case K_PGUP: return K_KP_PGUP; + case K_LEFTARROW: return K_KP_LEFTARROW; + case K_RIGHTARROW: return K_KP_RIGHTARROW; + case K_END: return K_KP_END; + case K_DOWNARROW: return K_KP_DOWNARROW; + case K_PGDN: return K_KP_PGDN; + case K_INS: return K_KP_INS; + case K_DEL: return K_KP_DEL; + default: return result; + } + } + else + { + switch( result ) + { + case K_PAUSE: return K_KP_NUMLOCK; + case 0x0D: return K_KP_ENTER; + case 0x2F: return K_KP_SLASH; + case 0xAF: return K_KP_PLUS; + default: return result; + } + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse( void ) +{ + if( host.type == HOST_DEDICATED ) return; + if( Sys_CheckParm( "-nomouse" )) return; + + in_mouse_buttons = 8; + in_mouseinitialized = true; + in_mouse_wheel = RegisterWindowMessage( "MSWHEEL_ROLLMSG" ); +} + +static qboolean IN_CursorInRect( void ) +{ + POINT curpos; + + if( !in_mouseinitialized || !in_mouseactive ) + return false; + + // find mouse movement + GetCursorPos( &curpos ); + + if( curpos.x < real_rect.left + WND_BORDER ) + return false; + if( curpos.x > real_rect.right - WND_BORDER * 3 ) + return false; + if( curpos.y < real_rect.top + WND_HEADSIZE + WND_BORDER ) + return false; + if( curpos.y > real_rect.bottom - WND_BORDER * 3 ) + return false; + return true; +} + +static void IN_ActivateCursor( void ) +{ + if( cls.key_dest == key_menu ) + { + SetCursor( in_mousecursor ); + } +} + +void IN_SetCursor( HICON hCursor ) +{ + in_mousecursor = hCursor; + + IN_ActivateCursor(); +} + +/* +=========== +IN_MouseSavePos + +Save mouse pos before state change e.g. changelevel +=========== +*/ +void IN_MouseSavePos( void ) +{ + if( !in_mouseactive ) + return; + + GetCursorPos( &in_lastvalidpos ); + in_mouse_savedpos = true; +} + +/* +=========== +IN_MouseRestorePos + +Restore right position for background +=========== +*/ +void IN_MouseRestorePos( void ) +{ + if( !in_mouse_savedpos ) + return; + + SetCursorPos( in_lastvalidpos.x, in_lastvalidpos.y ); + in_mouse_savedpos = false; +} + +/* +=========== +IN_ToggleClientMouse + +Called when key_dest is changed +=========== +*/ +void IN_ToggleClientMouse( int newstate, int oldstate ) +{ + if( newstate == oldstate ) return; + + if( oldstate == key_game ) + { + clgame.dllFuncs.IN_DeactivateMouse(); + } + else if( newstate == key_game ) + { + // reset mouse pos, so cancel effect in game + SetCursorPos( host.window_center_x, host.window_center_y ); + clgame.dllFuncs.IN_ActivateMouse(); + } + + if( newstate == key_menu && ( !CL_IsBackgroundMap() || CL_IsBackgroundDemo( ))) + { + in_mouseactive = false; + ClipCursor( NULL ); + ReleaseCapture(); + while( ShowCursor( true ) < 0 ); + } +} + +/* +=========== +IN_ActivateMouse + +Called when the window gains focus or changes in some way +=========== +*/ +void IN_ActivateMouse( qboolean force ) +{ + int width, height; + static int oldstate; + + if( !in_mouseinitialized ) + return; + + if( CL_Active() && host.mouse_visible && !force ) + return; // VGUI controls + + if( cls.key_dest == key_menu && !Cvar_VariableInteger( "fullscreen" )) + { + // check for mouse leave-entering + if( !in_mouse_suspended && !UI_MouseInRect( )) + in_mouse_suspended = true; + + if( oldstate != in_mouse_suspended ) + { + if( in_mouse_suspended ) + { + ClipCursor( NULL ); + ReleaseCapture(); + while( ShowCursor( true ) < 0 ); + UI_ShowCursor( false ); + } + } + + oldstate = in_mouse_suspended; + + if( in_mouse_suspended && IN_CursorInRect( )) + { + in_mouse_suspended = false; + in_mouseactive = false; // re-initialize mouse + UI_ShowCursor( true ); + } + } + + if( in_mouseactive ) return; + in_mouseactive = true; + + if( UI_IsVisible( )) return; + + if( cls.key_dest == key_game ) + { + clgame.dllFuncs.IN_ActivateMouse(); + } + + width = GetSystemMetrics( SM_CXSCREEN ); + height = GetSystemMetrics( SM_CYSCREEN ); + + GetWindowRect( host.hWnd, &window_rect ); + if( window_rect.left < 0 ) window_rect.left = 0; + if( window_rect.top < 0 ) window_rect.top = 0; + if( window_rect.right >= width ) window_rect.right = width - 1; + if( window_rect.bottom >= height - 1 ) window_rect.bottom = height - 1; + + host.window_center_x = (window_rect.right + window_rect.left) / 2; + host.window_center_y = (window_rect.top + window_rect.bottom) / 2; + SetCursorPos( host.window_center_x, host.window_center_y ); + + SetCapture( host.hWnd ); + ClipCursor( &window_rect ); + while( ShowCursor( false ) >= 0 ); +} + +/* +=========== +IN_DeactivateMouse + +Called when the window loses focus +=========== +*/ +void IN_DeactivateMouse( void ) +{ + if( !in_mouseinitialized || !in_mouseactive ) + return; + + if( cls.key_dest == key_game ) + { + clgame.dllFuncs.IN_DeactivateMouse(); + } + + in_mouseactive = false; + ClipCursor( NULL ); + ReleaseCapture(); + while( ShowCursor( true ) < 0 ); +} + +/* +================ +IN_MouseMove +================ +*/ +void IN_MouseMove( void ) +{ + POINT current_pos; + + if( !in_mouseinitialized || !in_mouseactive || !UI_IsVisible( )) + return; + + // find mouse movement + GetCursorPos( ¤t_pos ); + ScreenToClient( host.hWnd, ¤t_pos ); + + // if the menu is visible, move the menu cursor + UI_MouseMove( current_pos.x, current_pos.y ); + + IN_ActivateCursor(); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void IN_MouseEvent( int mstate ) +{ + int i; + + if( !in_mouseinitialized || !in_mouseactive ) + return; + + if( cls.key_dest == key_game ) + { + clgame.dllFuncs.IN_MouseEvent( mstate ); + return; + } + + // perform button actions + for( i = 0; i < in_mouse_buttons; i++ ) + { + if( FBitSet( mstate, BIT( i )) && !FBitSet( in_mouse_oldbuttonstate, BIT( i ))) + { + Key_Event( K_MOUSE1 + i, true ); + } + + if( !FBitSet( mstate, BIT( i )) && FBitSet( in_mouse_oldbuttonstate, BIT( i ))) + { + Key_Event( K_MOUSE1 + i, false ); + } + } + + in_mouse_oldbuttonstate = mstate; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown( void ) +{ + IN_DeactivateMouse( ); +} + + +/* +=========== +IN_Init +=========== +*/ +void IN_Init( void ) +{ + IN_StartupMouse( ); +} + +/* +================== +Host_InputFrame + +Called every frame, even if not generating commands +================== +*/ +void Host_InputFrame( void ) +{ + qboolean shutdownMouse = false; + + Sys_SendKeyEvents (); + + if( !in_mouseinitialized ) + return; + + if( host.status != HOST_FRAME ) + { + IN_DeactivateMouse(); + return; + } + + // release mouse during pause or console typeing + if( cl.paused && cls.key_dest == key_game ) + shutdownMouse = true; + + if( shutdownMouse && !Cvar_VariableInteger( "fullscreen" )) + { + IN_DeactivateMouse(); + return; + } + + IN_ActivateMouse( false ); + IN_MouseMove(); +} + +/* +==================== +IN_WndProc + +main window procedure +==================== +*/ +LONG IN_WndProc( HWND hWnd, UINT uMsg, UINT wParam, LONG lParam ) +{ + int i, temp = 0; + qboolean fActivate; + + if( uMsg == in_mouse_wheel ) + uMsg = WM_MOUSEWHEEL; + + VGUI_SurfaceWndProc( hWnd, uMsg, wParam, lParam ); + + switch( uMsg ) + { + case WM_KILLFOCUS: + if( Cvar_VariableInteger( "fullscreen" )) + ShowWindow( host.hWnd, SW_SHOWMINNOACTIVE ); + break; + case WM_SETCURSOR: + IN_ActivateCursor(); + break; + case WM_MOUSEWHEEL: + if( !in_mouseactive ) + break; + if(( short )HIWORD( wParam ) > 0 ) + { + Key_Event( K_MWHEELUP, true ); + Key_Event( K_MWHEELUP, false ); + } + else + { + Key_Event( K_MWHEELDOWN, true ); + Key_Event( K_MWHEELDOWN, false ); + } + break; + case WM_CREATE: + host.hWnd = hWnd; + GetWindowRect( host.hWnd, &real_rect ); + RegisterHotKey( host.hWnd, PRINTSCREEN_ID, 0, VK_SNAPSHOT ); + break; + case WM_CLOSE: + Sys_Quit(); + break; + case WM_ACTIVATE: + if( host.status == HOST_SHUTDOWN ) + break; // no need to activate + if( HIWORD( wParam )) + host.status = HOST_SLEEP; + else if( LOWORD( wParam ) == WA_INACTIVE ) + host.status = HOST_NOFOCUS; + else host.status = HOST_FRAME; + fActivate = (host.status == HOST_FRAME) ? true : false; + wnd_caption = GetSystemMetrics( SM_CYCAPTION ) + WND_BORDER; + + S_Activate( fActivate, host.hWnd ); + IN_ActivateMouse( fActivate ); + Key_ClearStates(); + + if( host.status == HOST_FRAME ) + { + SetForegroundWindow( hWnd ); + ShowWindow( hWnd, SW_RESTORE ); + } + else if( Cvar_VariableInteger( "fullscreen" )) + { + ShowWindow( hWnd, SW_MINIMIZE ); + } + break; + case WM_MOVE: + if( !Cvar_VariableInteger( "fullscreen" )) + { + RECT rect; + int xPos, yPos, style; + + xPos = (short)LOWORD( lParam ); // horizontal position + yPos = (short)HIWORD( lParam ); // vertical position + + rect.left = rect.top = 0; + rect.right = rect.bottom = 1; + style = GetWindowLong( hWnd, GWL_STYLE ); + AdjustWindowRect( &rect, style, FALSE ); + + Cvar_SetValue( "_window_xpos", xPos + rect.left ); + Cvar_SetValue( "_window_ypos", yPos + rect.top ); + GetWindowRect( host.hWnd, &real_rect ); + } + break; + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_XBUTTONDOWN: + case WM_XBUTTONUP: + case WM_MOUSEMOVE: + for( i = 0; i < in_mouse_buttons; i++ ) + { + if( wParam & mouse_buttons[i] ) + temp |= (1< 1, it is autorepeating + const char *binding; +} key_t; + +typedef struct keyname_s +{ + char *name; // key name + int keynum; // key number + const char *binding; // default bind +} keyname_t; + +key_t keys[256]; + +keyname_t keynames[] = +{ +{"TAB", K_TAB, "" }, +{"ENTER", K_ENTER, "" }, +{"ESCAPE", K_ESCAPE, "escape" }, // hardcoded +{"SPACE", K_SPACE, "+jump" }, +{"BACKSPACE", K_BACKSPACE, "" }, +{"UPARROW", K_UPARROW, "+forward" }, +{"DOWNARROW", K_DOWNARROW, "+back" }, +{"LEFTARROW", K_LEFTARROW, "+left" }, +{"RIGHTARROW", K_RIGHTARROW, "+right" }, +{"ALT", K_ALT, "+strafe" }, +{"CTRL", K_CTRL, "+attack" }, +{"SHIFT", K_SHIFT, "+speed" }, +{"CAPSLOCK", K_CAPSLOCK, "" }, +{"F1", K_F1, "cmd help" }, +{"F2", K_F2, "menu_savegame" }, +{"F3", K_F3, "menu_loadgame" }, +{"F4", K_F4, "menu_controls" }, +{"F5", K_F5, "menu_creategame" }, +{"F6", K_F6, "savequick" }, +{"F7", K_F7, "loadquick" }, +{"F8", K_F8, "stop" }, +{"F9", K_F9, "" }, +{"F10", K_F10, "menu_main" }, +{"F11", K_F11, "" }, +{"F12", K_F12, "snapshot" }, +{"INS", K_INS, "" }, +{"DEL", K_DEL, "+lookdown" }, +{"PGDN", K_PGDN, "+lookup" }, +{"PGUP", K_PGUP, "" }, +{"HOME", K_HOME, "" }, +{"END", K_END, "centerview" }, + +// mouse buttouns +{"MOUSE1", K_MOUSE1, "+attack" }, +{"MOUSE2", K_MOUSE2, "+attack2" }, +{"MOUSE3", K_MOUSE3, "" }, +{"MOUSE4", K_MOUSE4, "" }, +{"MOUSE5", K_MOUSE5, "" }, +{"MWHEELUP", K_MWHEELUP, "" }, +{"MWHEELDOWN", K_MWHEELDOWN, "" }, + +// digital keyboard +{"KP_HOME", K_KP_HOME, "" }, +{"KP_UPARROW", K_KP_UPARROW, "+forward" }, +{"KP_PGUP", K_KP_PGUP, "" }, +{"KP_LEFTARROW", K_KP_LEFTARROW, "+left" }, +{"KP_5", K_KP_5, "" }, +{"KP_RIGHTARROW", K_KP_RIGHTARROW, "+right" }, +{"KP_END", K_KP_END, "centerview" }, +{"KP_DOWNARROW", K_KP_DOWNARROW, "+back" }, +{"KP_PGDN", K_KP_PGDN, "+lookup" }, +{"KP_ENTER", K_KP_ENTER, "" }, +{"KP_INS", K_KP_INS, "" }, +{"KP_DEL", K_KP_DEL, "+lookdown" }, +{"KP_SLASH", K_KP_SLASH, "" }, +{"KP_MINUS", K_KP_MINUS, "" }, +{"KP_PLUS", K_KP_PLUS, "" }, +{"PAUSE", K_PAUSE, "pause" }, + +// raw semicolon seperates commands +{"SEMICOLON", ';', "" }, +{NULL, 0, NULL }, +}; + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) +{ + if( keynum == -1 ) + return false; + return keys[keynum].down; +} + +/* +=================== +Key_GetBind +=================== +*/ +const char *Key_IsBind( int keynum ) +{ + if( keynum == -1 || !keys[keynum].binding ) + return NULL; + return keys[keynum].binding; +} + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( const char *str ) +{ + keyname_t *kn; + + if( !str || !str[0] ) return -1; + if( !str[1] ) return str[0]; + + // check for hex code + if( str[0] == '0' && str[1] == 'x' && Q_strlen( str ) == 4 ) + { + int n1, n2; + + n1 = str[2]; + if( n1 >= '0' && n1 <= '9' ) + { + n1 -= '0'; + } + else if( n1 >= 'a' && n1 <= 'f' ) + { + n1 = n1 - 'a' + 10; + } + else n1 = 0; + + n2 = str[3]; + if( n2 >= '0' && n2 <= '9' ) + { + n2 -= '0'; + } + else if( n2 >= 'a' && n2 <= 'f' ) + { + n2 = n2 - 'a' + 10; + } + else n2 = 0; + + return n1 * 16 + n2; + } + + // scan for a text match + for( kn = keynames; kn->name; kn++ ) + { + if( !Q_stricmp( str, kn->name )) + return kn->keynum; + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +const char *Key_KeynumToString( int keynum ) +{ + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) return ""; + if ( keynum < 0 || keynum > 255 ) return ""; + + // check for printable ascii (don't use quote) + if( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) + { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for( kn = keynames; kn->name; kn++ ) + { + if( keynum == kn->keynum ) + return kn->name; + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) +{ + if( keynum == -1 ) return; + + // free old bindings + if( keys[keynum].binding ) + { + Mem_Free((char *)keys[keynum].binding ); + keys[keynum].binding = NULL; + } + + // allocate memory for new binding + keys[keynum].binding = copystring( binding ); +} + + +/* +=================== +Key_GetBinding +=================== +*/ +const char *Key_GetBinding( int keynum ) +{ + if( keynum == -1 ) return NULL; + return keys[keynum].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ +int Key_GetKey( const char *binding ) +{ + int i; + + if( !binding ) return -1; + + for( i = 0; i < 256; i++ ) + { + if( keys[i].binding && !Q_stricmp( binding, keys[i].binding )) + return i; + } + + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f( void ) +{ + int b; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "unbind : remove commands from a key\n" ); + return; + } + + b = Key_StringToKeynum( Cmd_Argv( 1 )); + + if( b == -1 ) + { + Con_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 )); + return; + } + + Key_SetBinding( b, "" ); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f( void ) +{ + int i; + + for( i = 0; i < 256; i++ ) + { + if( keys[i].binding ) + Key_SetBinding( i, "" ); + } +} + +/* +=================== +Key_Reset_f +=================== +*/ +void Key_Reset_f( void ) +{ + keyname_t *kn; + int i; + + // clear all keys first + for( i = 0; i < 256; i++ ) + { + if( keys[i].binding ) + Key_SetBinding( i, "" ); + } + + // apply default values + for( kn = keynames; kn->name; kn++ ) + Key_SetBinding( kn->keynum, kn->binding ); +} + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f( void ) +{ + char cmd[1024]; + int i, c, b; + + c = Cmd_Argc(); + + if( c < 2 ) + { + Con_Printf( S_USAGE "bind [command] : attach a command to a key\n" ); + return; + } + + b = Key_StringToKeynum( Cmd_Argv( 1 )); + + if( b == -1 ) + { + Con_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 )); + return; + } + + if( c == 2 ) + { + if( keys[b].binding ) + Con_Printf( "\"%s\" = \"%s\"\n", Cmd_Argv( 1 ), keys[b].binding ); + else Con_Printf( "\"%s\" is not bound\n", Cmd_Argv( 1 )); + return; + } + + // copy the rest of the command line + cmd[0] = 0; // start out with a null string + + for( i = 2; i < c; i++ ) + { + Q_strcat( cmd, Cmd_Argv( i )); + if( i != ( c - 1 )) Q_strcat( cmd, " " ); + } + + Key_SetBinding( b, cmd ); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( file_t *f ) +{ + int i; + + if( !f ) return; + + FS_Printf( f, "unbindall\n" ); + + for( i = 0; i < 256; i++ ) + { + if( keys[i].binding && keys[i].binding[0] ) + FS_Printf( f, "bind %s \"%s\"\n", Key_KeynumToString( i ), keys[i].binding ); + } +} + +/* +============ +Key_Bindlist_f + +============ +*/ +void Key_Bindlist_f( void ) +{ + int i; + + for( i = 0; i < 256; i++ ) + { + if( keys[i].binding && keys[i].binding[0] ) + Con_Printf( "%s \"%s\"\n", Key_KeynumToString( i ), keys[i].binding ); + } +} + +/* +============================================================================== + + LINE TYPING INTO THE CONSOLE + +============================================================================== +*/ +/* +=================== +Key_Init +=================== +*/ +void Key_Init( void ) +{ + keyname_t *kn; + + // register our functions + Cmd_AddCommand( "bind", Key_Bind_f, "binds a command to the specified key in bindmap" ); + Cmd_AddCommand( "unbind", Key_Unbind_f, "removes a command on the specified key in bindmap" ); + Cmd_AddCommand( "unbindall", Key_Unbindall_f, "removes all commands from all keys in bindmap" ); + Cmd_AddCommand( "resetkeys", Key_Reset_f, "reset all keys to their default values" ); + Cmd_AddCommand( "bindlist", Key_Bindlist_f, "display current key bindings" ); + Cmd_AddCommand( "makehelp", Key_EnumCmds_f, "write help.txt that contains all console cvars and cmds" ); + + // setup default binding. "unbindall" from config.cfg will be reset it + for( kn = keynames; kn->name; kn++ ) Key_SetBinding( kn->keynum, kn->binding ); +} + +/* +=================== +Key_AddKeyUpCommands +=================== +*/ +void Key_AddKeyUpCommands( int key, const char *kb ) +{ + int i; + char button[1024], *buttonPtr; + char cmd[1024]; + qboolean keyevent; + + if( !kb ) return; + keyevent = false; + buttonPtr = button; + + for( i = 0; ; i++ ) + { + if( kb[i] == ';' || !kb[i] ) + { + *buttonPtr = '\0'; + if( button[0] == '+' ) + { + // button commands add keynum as a parm + Q_sprintf( cmd, "-%s %i\n", button+1, key ); + Cbuf_AddText( cmd ); + keyevent = true; + } + else + { + if( keyevent ) + { + // down-only command + Cbuf_AddText( button ); + Cbuf_AddText( "\n" ); + } + } + + buttonPtr = button; + while(( kb[i] <= ' ' || kb[i] == ';' ) && kb[i] != 0 ) + i++; + } + + *buttonPtr++ = kb[i]; + if( !kb[i] ) break; + } +} + +/* +=================== +Key_Event + +Called by the system for both key up and key down events +=================== +*/ +void Key_Event( int key, qboolean down ) +{ + const char *kb; + char cmd[1024]; + + // key was pressed before engine was run + if( !keys[key].down && !down ) + return; + + // update auto-repeat status and BUTTON_ANY status + keys[key].down = down; + + if( down ) + { + keys[key].repeats++; + + if( key != K_BACKSPACE && key != K_PAUSE && keys[key].repeats > 1 ) + { + if( cls.key_dest == key_game ) + { + // ignore most autorepeats + return; + } + } + } + else + { + keys[key].repeats = 0; + } + + // console key is hardcoded, so the user can never unbind it + if( key == '`' || key == '~' ) + { + // we are in typing mode. So don't switch to console + if( (word)GetKeyboardLayout( 0 ) == (word)0x419 ) + { + if( cls.key_dest != key_game ) + return; + } + + if( !down ) return; + Con_ToggleConsole_f(); + return; + } + + // escape is always handled special + if( key == K_ESCAPE && down ) + { + switch( cls.key_dest ) + { + case key_game: + if( gl_showtextures->value ) + { + // close texture atlas + Cvar_SetValue( "r_showtextures", 0.0f ); + return; + } + if( host.mouse_visible && cls.state != ca_cinematic ) + { + clgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding ); + return; // handled in client.dll + } + break; + case key_message: + Key_Message( key ); + return; + case key_console: + if( cls.state == ca_active && !cl.background ) + Key_SetKeyDest( key_game ); + else UI_SetActiveMenu( true ); + return; + case key_menu: + UI_KeyEvent( key, true ); + return; + default: + MsgDev( D_ERROR, "Key_Event: bad cls.key_dest\n" ); + return; + } + } + + if( cls.key_dest == key_menu ) + { + // only non printable keys passed + UI_KeyEvent( key, down ); + return; + } + + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + if( !down ) + { + kb = keys[key].binding; + + if( cls.key_dest == key_game && ( key != K_ESCAPE )) + clgame.dllFuncs.pfnKey_Event( down, key, kb ); + + Key_AddKeyUpCommands( key, kb ); + return; + } + + // distribute the key down event to the apropriate handler + if( cls.key_dest == key_game ) + { + if( cls.state == ca_cinematic && ( key != K_ESCAPE || !down )) + { + // only escape passed when cinematic is playing + // HLFX 0.6 bug: crash in vgui3.dll while press +attack during movie playback + return; + } + + // send the bound action + kb = keys[key].binding; + + if( !clgame.dllFuncs.pfnKey_Event( down, key, keys[key].binding )) + { + // handled in client.dll + } + else if( kb != NULL ) + { + if( kb[0] == '+' ) + { + int i; + char button[1024], *buttonPtr; + + for( i = 0, buttonPtr = button; ; i++ ) + { + if( kb[i] == ';' || !kb[i] ) + { + *buttonPtr = '\0'; + if( button[0] == '+' ) + { + Q_sprintf( cmd, "%s %i\n", button, key ); + Cbuf_AddText( cmd ); + } + else + { + // down-only command + Cbuf_AddText( button ); + Cbuf_AddText( "\n" ); + } + + buttonPtr = button; + while (( kb[i] <= ' ' || kb[i] == ';' ) && kb[i] != 0 ) + i++; + } + + *buttonPtr++ = kb[i]; + if( !kb[i] ) break; + } + } + else + { + // down-only command + Cbuf_AddText( kb ); + Cbuf_AddText( "\n" ); + } + } + } + else if( cls.key_dest == key_console ) + { + Key_Console( key ); + } + else if( cls.key_dest == key_message ) + { + Key_Message( key ); + } +} + +/* +========= +Key_SetKeyDest +========= +*/ +void Key_SetKeyDest( int key_dest ) +{ + IN_ToggleClientMouse( key_dest, cls.key_dest ); + + switch( key_dest ) + { + case key_game: + cls.key_dest = key_game; + break; + case key_menu: + cls.key_dest = key_menu; + break; + case key_console: + cls.key_dest = key_console; + break; + case key_message: + cls.key_dest = key_message; + break; + default: + Host_Error( "Key_SetKeyDest: wrong destination (%i)\n", key_dest ); + break; + } +} + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates( void ) +{ + int i; + + // don't clear keys during changelevel + if( cls.changelevel ) return; + + for( i = 0; i < 256; i++ ) + { + if( keys[i].down ) + Key_Event( i, false ); + + keys[i].down = 0; + keys[i].repeats = 0; + } + + if( clgame.hInstance ) + clgame.dllFuncs.IN_ClearStates(); +} + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) +{ + // the console key should never be used as a char + if( key == '`' || key == '~' ) return; + + if( cls.key_dest == key_console && !Con_Visible( )) + { + if((char)key == ',' || (char)key == '?' ) + return; // don't pass ',' when we open the console + } + + // distribute the key down event to the apropriate handler + if( cls.key_dest == key_console || cls.key_dest == key_message ) + { + Con_CharEvent( key ); + } + else if( cls.key_dest == key_menu ) + { + UI_CharEvent( key ); + } +} \ No newline at end of file diff --git a/engine/common/library.c b/engine/common/library.c new file mode 100644 index 00000000..410d5103 --- /dev/null +++ b/engine/common/library.c @@ -0,0 +1,891 @@ +/* +library.c - custom dlls loader +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "library.h" + +/* +--------------------------------------------------------------- + + Custom dlls loader + +--------------------------------------------------------------- +*/ + +typedef struct +{ + PIMAGE_NT_HEADERS headers; + byte *codeBase; + void **modules; + int numModules; + int initialized; +} MEMORYMODULE, *PMEMORYMODULE; + +// Protection flags for memory pages (Executable, Readable, Writeable) +static int ProtectionFlags[2][2][2] = +{ +{ +{ PAGE_NOACCESS, PAGE_WRITECOPY }, // not executable +{ PAGE_READONLY, PAGE_READWRITE }, +}, +{ +{ PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY }, // executable +{ PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE }, +}, +}; + +typedef BOOL (WINAPI *DllEntryProc)( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved ); + +#define GET_HEADER_DICTIONARY( module, idx ) &(module)->headers->OptionalHeader.DataDirectory[idx] +#define CALCULATE_ADDRESS( base, offset ) (((DWORD)(base)) + (offset)) + +static void CopySections( const byte *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + byte *codeBase = module->codeBase; + int i, size; + byte *dest; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + // section doesn't contain data in the dll itself, but may define + // uninitialized data + size = old_headers->OptionalHeader.SectionAlignment; + + if( size > 0 ) + { + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), size, MEM_COMMIT, PAGE_READWRITE ); + section->Misc.PhysicalAddress = (DWORD)dest; + memset( dest, 0, size ); + } + // section is empty + continue; + } + + // commit memory block and copy data from dll + dest = (byte *)VirtualAlloc((byte *)CALCULATE_ADDRESS(codeBase, section->VirtualAddress), section->SizeOfRawData, MEM_COMMIT, PAGE_READWRITE ); + memcpy( dest, (byte *)CALCULATE_ADDRESS(data, section->PointerToRawData), section->SizeOfRawData ); + section->Misc.PhysicalAddress = (DWORD)dest; + } +} + +static void FreeSections( PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers); + byte *codeBase = module->codeBase; + int i, size; + + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + if( section->SizeOfRawData == 0 ) + { + size = old_headers->OptionalHeader.SectionAlignment; + if( size > 0 ) + { + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), size, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } + continue; + } + + VirtualFree((byte *)CALCULATE_ADDRESS( codeBase, section->VirtualAddress ), section->SizeOfRawData, MEM_DECOMMIT ); + section->Misc.PhysicalAddress = 0; + } +} + +static void FinalizeSections( MEMORYMODULE *module ) +{ + PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION( module->headers ); + int i; + + // loop through all sections and change access flags + for( i = 0; i < module->headers->FileHeader.NumberOfSections; i++, section++ ) + { + DWORD protect, oldProtect, size; + int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0; + int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0; + int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0; + + if( section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE ) + { + // section is not needed any more and can safely be freed + VirtualFree((LPVOID)section->Misc.PhysicalAddress, section->SizeOfRawData, MEM_DECOMMIT); + continue; + } + + // determine protection flags based on characteristics + protect = ProtectionFlags[executable][readable][writeable]; + if( section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED ) + protect |= PAGE_NOCACHE; + + // determine size of region + size = section->SizeOfRawData; + + if( size == 0 ) + { + if( section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfInitializedData; + else if( section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA ) + size = module->headers->OptionalHeader.SizeOfUninitializedData; + } + + if( size > 0 ) + { + // change memory access flags + if( !VirtualProtect((LPVOID)section->Misc.PhysicalAddress, size, protect, &oldProtect )) + Sys_Error( "error protecting memory page\n" ); + } + } +} + +static void PerformBaseRelocation( MEMORYMODULE *module, DWORD delta ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_BASERELOC ); + byte *codeBase = module->codeBase; + DWORD i; + + if( directory->Size > 0 ) + { + PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + for( ; relocation->VirtualAddress > 0; ) + { + byte *dest = (byte *)CALCULATE_ADDRESS( codeBase, relocation->VirtualAddress ); + word *relInfo = (word *)((byte *)relocation + IMAGE_SIZEOF_BASE_RELOCATION ); + + for( i = 0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++ ) + { + DWORD *patchAddrHL; + int type, offset; + + // the upper 4 bits define the type of relocation + type = *relInfo >> 12; + // the lower 12 bits define the offset + offset = *relInfo & 0xfff; + + switch( type ) + { + case IMAGE_REL_BASED_ABSOLUTE: + // skip relocation + break; + case IMAGE_REL_BASED_HIGHLOW: + // change complete 32 bit address + patchAddrHL = (DWORD *)CALCULATE_ADDRESS( dest, offset ); + *patchAddrHL += delta; + break; + default: + MsgDev( D_ERROR, "PerformBaseRelocation: unknown relocation: %d\n", type ); + break; + } + } + + // advance to next relocation block + relocation = (PIMAGE_BASE_RELOCATION)CALCULATE_ADDRESS( relocation, relocation->SizeOfBlock ); + } + } +} + +static FARPROC MemoryGetProcAddress( void *module, const char *name ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((MEMORYMODULE *)module, IMAGE_DIRECTORY_ENTRY_EXPORT ); + byte *codeBase = ((PMEMORYMODULE)module)->codeBase; + PIMAGE_EXPORT_DIRECTORY exports; + int idx = -1; + DWORD i, *nameRef; + WORD *ordinal; + + if( directory->Size == 0 ) + { + // no export table found + return NULL; + } + + exports = (PIMAGE_EXPORT_DIRECTORY)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + if( exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0 ) + { + // DLL doesn't export anything + return NULL; + } + + // search function name in list of exported names + nameRef = (DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNames ); + ordinal = (WORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfNameOrdinals ); + + for( i = 0; i < exports->NumberOfNames; i++, nameRef++, ordinal++ ) + { + // GetProcAddress case insensative ????? + if( !Q_stricmp( name, (const char *)CALCULATE_ADDRESS( codeBase, *nameRef ))) + { + idx = *ordinal; + break; + } + } + + if( idx == -1 ) + { + // exported symbol not found + return NULL; + } + + if((DWORD)idx > exports->NumberOfFunctions ) + { + // name <-> ordinal number don't match + return NULL; + } + + // addressOfFunctions contains the RVAs to the "real" functions + return (FARPROC)CALCULATE_ADDRESS( codeBase, *(DWORD *)CALCULATE_ADDRESS( codeBase, exports->AddressOfFunctions + (idx * 4))); +} + +static int BuildImportTable( MEMORYMODULE *module ) +{ + PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY( module, IMAGE_DIRECTORY_ENTRY_IMPORT ); + byte *codeBase = module->codeBase; + int result = 1; + + if( directory->Size > 0 ) + { + PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)CALCULATE_ADDRESS( codeBase, directory->VirtualAddress ); + + for( ; !IsBadReadPtr( importDesc, sizeof( IMAGE_IMPORT_DESCRIPTOR )) && importDesc->Name; importDesc++ ) + { + DWORD *thunkRef, *funcRef; + LPCSTR libname; + void *handle; + + libname = (LPCSTR)CALCULATE_ADDRESS( codeBase, importDesc->Name ); + handle = COM_LoadLibrary( libname, false, true ); + + if( handle == NULL ) + { + MsgDev( D_ERROR, "couldn't load library %s\n", libname ); + result = 0; + break; + } + + module->modules = (void *)Mem_Realloc( host.mempool, module->modules, (module->numModules + 1) * (sizeof( void* ))); + module->modules[module->numModules++] = handle; + + if( importDesc->OriginalFirstThunk ) + { + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->OriginalFirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + else + { + // no hint table + thunkRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + funcRef = (DWORD *)CALCULATE_ADDRESS( codeBase, importDesc->FirstThunk ); + } + + for( ; *thunkRef; thunkRef++, funcRef++ ) + { + if( IMAGE_SNAP_BY_ORDINAL( *thunkRef )) + { + LPCSTR funcName = (LPCSTR)IMAGE_ORDINAL( *thunkRef ); + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + else + { + PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME)CALCULATE_ADDRESS( codeBase, *thunkRef ); + LPCSTR funcName = (LPCSTR)&thunkData->Name; + *funcRef = (DWORD)COM_GetProcAddress( handle, funcName ); + } + + if( *funcRef == 0 ) + { + result = 0; + break; + } + } + if( !result ) break; + } + } + return result; +} + +static void MemoryFreeLibrary( void *hInstance ) +{ + MEMORYMODULE *module = (MEMORYMODULE *)hInstance; + + if( module != NULL ) + { + int i; + + if( module->initialized != 0 ) + { + // notify library about detaching from process + DllEntryProc DllEntry = (DllEntryProc)CALCULATE_ADDRESS( module->codeBase, module->headers->OptionalHeader.AddressOfEntryPoint ); + (*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0 ); + module->initialized = 0; + } + + if( module->modules != NULL ) + { + // free previously opened libraries + for( i = 0; i < module->numModules; i++ ) + { + if( module->modules[i] != NULL ) + COM_FreeLibrary( module->modules[i] ); + } + Mem_Free( module->modules ); // Mem_Realloc end + } + + FreeSections( module->headers, module ); + + if( module->codeBase != NULL ) + { + // release memory of library + VirtualFree( module->codeBase, 0, MEM_RELEASE ); + } + + HeapFree( GetProcessHeap(), 0, module ); + } +} + +void *MemoryLoadLibrary( const char *name ) +{ + MEMORYMODULE *result = NULL; + PIMAGE_DOS_HEADER dos_header; + PIMAGE_NT_HEADERS old_header; + byte *code, *headers; + DWORD locationDelta; + DllEntryProc DllEntry; + string errorstring; + qboolean successfull; + void *data = NULL; + + data = FS_LoadFile( name, NULL, false ); + + if( !data ) + { + Q_sprintf( errorstring, "couldn't load %s", name ); + goto library_error; + } + + dos_header = (PIMAGE_DOS_HEADER)data; + if( dos_header->e_magic != IMAGE_DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s it's not a valid executable file", name ); + goto library_error; + } + + old_header = (PIMAGE_NT_HEADERS)&((const byte *)(data))[dos_header->e_lfanew]; + if( old_header->Signature != IMAGE_NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s missing PE header", name ); + goto library_error; + } + + // reserve memory for image of library + code = (byte *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase), old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + + if( code == NULL ) + { + // try to allocate memory at arbitrary position + code = (byte *)VirtualAlloc( NULL, old_header->OptionalHeader.SizeOfImage, MEM_RESERVE, PAGE_READWRITE ); + } + + if( code == NULL ) + { + Q_sprintf( errorstring, "%s can't reserve memory", name ); + goto library_error; + } + + result = (MEMORYMODULE *)HeapAlloc( GetProcessHeap(), 0, sizeof( MEMORYMODULE )); + result->codeBase = code; + result->numModules = 0; + result->modules = NULL; + result->initialized = 0; + + // XXX: is it correct to commit the complete memory region at once? + // calling DllEntry raises an exception if we don't... + VirtualAlloc( code, old_header->OptionalHeader.SizeOfImage, MEM_COMMIT, PAGE_READWRITE ); + + // commit memory for headers + headers = (byte *)VirtualAlloc( code, old_header->OptionalHeader.SizeOfHeaders, MEM_COMMIT, PAGE_READWRITE ); + + // copy PE header to code + memcpy( headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders ); + result->headers = (PIMAGE_NT_HEADERS)&((const byte *)(headers))[dos_header->e_lfanew]; + + // update position + result->headers->OptionalHeader.ImageBase = (DWORD)code; + + // copy sections from DLL file block to new memory location + CopySections( data, old_header, result ); + + // adjust base address of imported data + locationDelta = (DWORD)(code - old_header->OptionalHeader.ImageBase); + if( locationDelta != 0 ) PerformBaseRelocation( result, locationDelta ); + + // load required dlls and adjust function table of imports + if( !BuildImportTable( result )) + { + Q_sprintf( errorstring, "%s failed to build import table", name ); + goto library_error; + } + + // mark memory pages depending on section headers and release + // sections that are marked as "discardable" + FinalizeSections( result ); + + // get entry point of loaded library + if( result->headers->OptionalHeader.AddressOfEntryPoint != 0 ) + { + DllEntry = (DllEntryProc)CALCULATE_ADDRESS( code, result->headers->OptionalHeader.AddressOfEntryPoint ); + if( DllEntry == 0 ) + { + Q_sprintf( errorstring, "%s has no entry point", name ); + goto library_error; + } + + // notify library about attaching to process + successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0 ); + if( !successfull ) + { + Q_sprintf( errorstring, "can't attach library %s", name ); + goto library_error; + } + result->initialized = 1; + } + + Mem_Free( data ); // release memory + return (void *)result; +library_error: + // cleanup + if( data ) Mem_Free( data ); + MemoryFreeLibrary( result ); + MsgDev( D_ERROR, "LoadLibrary: %s\n", errorstring ); + + return NULL; +} + +/* +--------------------------------------------------------------- + + Name for function stuff + +--------------------------------------------------------------- +*/ +static void FsGetString( file_t *f, char *str ) +{ + char ch; + + while(( ch = FS_Getc( f )) != EOF ) + { + *str++ = ch; + if( !ch ) break; + } +} + +static void FreeNameFuncGlobals( dll_user_t *hInst ) +{ + int i; + + if( !hInst ) return; + + if( hInst->ordinals ) Mem_Free( hInst->ordinals ); + if( hInst->funcs ) Mem_Free( hInst->funcs ); + + for( i = 0; i < hInst->num_ordinals; i++ ) + { + if( hInst->names[i] ) + Mem_Free( hInst->names[i] ); + } + + hInst->num_ordinals = 0; + hInst->ordinals = NULL; + hInst->funcs = NULL; +} + +char *GetMSVCName( const char *in_name ) +{ + static string out_name; + char *pos; + + if( in_name[0] == '?' ) // is this a MSVC C++ mangled name? + { + if(( pos = Q_strstr( in_name, "@@" )) != NULL ) + { + int len = pos - in_name; + + // strip off the leading '?' + Q_strncpy( out_name, in_name + 1, sizeof( out_name )); + out_name[len-1] = 0; // terminate string at the "@@" + return out_name; + } + } + + Q_strncpy( out_name, in_name, sizeof( out_name )); + + return out_name; +} + +qboolean LibraryLoadSymbols( dll_user_t *hInst ) +{ + file_t *f; + string errorstring; + DOS_HEADER dos_header; + LONG nt_signature; + PE_HEADER pe_header; + SECTION_HEADER section_header; + qboolean rdata_found; + OPTIONAL_HEADER optional_header; + long rdata_delta = 0; + EXPORT_DIRECTORY export_directory; + long name_offset; + long exports_offset; + long ordinal_offset; + long function_offset; + string function_name; + dword *p_Names = NULL; + int i, index; + + // can only be done for loaded libraries + if( !hInst ) return false; + + for( i = 0; i < hInst->num_ordinals; i++ ) + hInst->names[i] = NULL; + + f = FS_Open( hInst->shortPath, "rb", false ); + if( !f ) + { + Q_sprintf( errorstring, "couldn't load %s", hInst->shortPath ); + goto table_error; + } + + if( FS_Read( f, &dos_header, sizeof( dos_header )) != sizeof( dos_header )) + { + Q_sprintf( errorstring, "%s has corrupted EXE header", hInst->shortPath ); + goto table_error; + } + + if( dos_header.e_magic != DOS_SIGNATURE ) + { + Q_sprintf( errorstring, "%s does not have a valid dll signature", hInst->shortPath ); + goto table_error; + } + + if( FS_Seek( f, dos_header.e_lfanew, SEEK_SET ) == -1 ) + { + Q_sprintf( errorstring, "%s error seeking for new exe header", hInst->shortPath ); + goto table_error; + } + + if( FS_Read( f, &nt_signature, sizeof( nt_signature )) != sizeof( nt_signature )) + { + Q_sprintf( errorstring, "%s has corrupted NT header", hInst->shortPath ); + goto table_error; + } + + if( nt_signature != NT_SIGNATURE ) + { + Q_sprintf( errorstring, "%s does not have a valid NT signature", hInst->shortPath ); + goto table_error; + } + + if( FS_Read( f, &pe_header, sizeof( pe_header )) != sizeof( pe_header )) + { + Q_sprintf( errorstring, "%s does not have a valid PE header", hInst->shortPath ); + goto table_error; + } + + if( !pe_header.SizeOfOptionalHeader ) + { + Q_sprintf( errorstring, "%s does not have an optional header", hInst->shortPath ); + goto table_error; + } + + if( FS_Read( f, &optional_header, sizeof( optional_header )) != sizeof( optional_header )) + { + Q_sprintf( errorstring, "%s optional header probably corrupted", hInst->shortPath ); + goto table_error; + } + + rdata_found = false; + + for( i = 0; i < pe_header.NumberOfSections; i++ ) + { + if( FS_Read( f, §ion_header, sizeof( section_header )) != sizeof( section_header )) + { + Q_sprintf( errorstring, "%s error during reading section header", hInst->shortPath ); + goto table_error; + } + + if((( optional_header.DataDirectory[0].VirtualAddress >= section_header.VirtualAddress ) && + (optional_header.DataDirectory[0].VirtualAddress < (section_header.VirtualAddress + section_header.Misc.VirtualSize)))) + { + rdata_found = true; + break; + } + } + + if( rdata_found ) + { + rdata_delta = section_header.VirtualAddress - section_header.PointerToRawData; + } + + exports_offset = optional_header.DataDirectory[0].VirtualAddress - rdata_delta; + + if( FS_Seek( f, exports_offset, SEEK_SET ) == -1 ) + { + Q_sprintf( errorstring, "%s does not have a valid exports section", hInst->shortPath ); + goto table_error; + } + + if( FS_Read( f, &export_directory, sizeof( export_directory )) != sizeof( export_directory )) + { + Q_sprintf( errorstring, "%s does not have a valid optional header", hInst->shortPath ); + goto table_error; + } + + hInst->num_ordinals = export_directory.NumberOfNames; // also number of ordinals + + if( hInst->num_ordinals > MAX_LIBRARY_EXPORTS ) + { + Q_sprintf( errorstring, "%s too many exports %i", hInst->shortPath, hInst->num_ordinals ); + hInst->num_ordinals = 0; + goto table_error; + } + + ordinal_offset = export_directory.AddressOfNameOrdinals - rdata_delta; + + if( FS_Seek( f, ordinal_offset, SEEK_SET ) == -1 ) + { + Q_sprintf( errorstring, "%s does not have a valid ordinals section", hInst->shortPath ); + goto table_error; + } + + hInst->ordinals = Mem_Alloc( host.mempool, hInst->num_ordinals * sizeof( word )); + + if( FS_Read( f, hInst->ordinals, hInst->num_ordinals * sizeof( word )) != (hInst->num_ordinals * sizeof( word ))) + { + Q_sprintf( errorstring, "%s error during reading ordinals table", hInst->shortPath ); + goto table_error; + } + + function_offset = export_directory.AddressOfFunctions - rdata_delta; + + if( FS_Seek( f, function_offset, SEEK_SET ) == -1 ) + { + Q_sprintf( errorstring, "%s does not have a valid export address section", hInst->shortPath ); + goto table_error; + } + + hInst->funcs = Mem_Alloc( host.mempool, hInst->num_ordinals * sizeof( dword )); + + if( FS_Read( f, hInst->funcs, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) + { + Q_sprintf( errorstring, "%s error during reading export address section", hInst->shortPath ); + goto table_error; + } + + name_offset = export_directory.AddressOfNames - rdata_delta; + + if( FS_Seek( f, name_offset, SEEK_SET ) == -1 ) + { + Q_sprintf( errorstring, "%s file does not have a valid names section", hInst->shortPath ); + goto table_error; + } + + p_Names = Mem_Alloc( host.mempool, hInst->num_ordinals * sizeof( dword )); + + if( FS_Read( f, p_Names, hInst->num_ordinals * sizeof( dword )) != (hInst->num_ordinals * sizeof( dword ))) + { + Q_sprintf( errorstring, "%s error during reading names table", hInst->shortPath ); + goto table_error; + } + + for( i = 0; i < hInst->num_ordinals; i++ ) + { + name_offset = p_Names[i] - rdata_delta; + + if( name_offset != 0 ) + { + if( FS_Seek( f, name_offset, SEEK_SET ) != -1 ) + { + FsGetString( f, function_name ); + hInst->names[i] = copystring( GetMSVCName( function_name )); + } + else break; + } + } + + if( i != hInst->num_ordinals ) + { + Q_sprintf( errorstring, "%s error during loading names section", hInst->shortPath ); + goto table_error; + } + FS_Close( f ); + + for( i = 0; i < hInst->num_ordinals; i++ ) + { + if( !Q_strcmp( "GiveFnptrsToDll", hInst->names[i] )) // main entry point for user dlls + { + void *fn_offset; + + index = hInst->ordinals[i]; + fn_offset = (void *)COM_GetProcAddress( hInst, "GiveFnptrsToDll" ); + hInst->funcBase = (dword)(fn_offset) - hInst->funcs[index]; + break; + } + } + + if( p_Names ) Mem_Free( p_Names ); + return true; +table_error: + // cleanup + if( f ) FS_Close( f ); + if( p_Names ) Mem_Free( p_Names ); + FreeNameFuncGlobals( hInst ); + Con_Printf( S_ERROR "LoadLibrary: %s\n", errorstring ); + + return false; +} + +/* +================ +COM_LoadLibrary + +smart dll loader - can loading dlls from pack or wad files +================ +*/ +void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ) +{ + dll_user_t *hInst; + + hInst = FS_FindLibrary( dllname, directpath ); + if( !hInst ) return NULL; // nothing to load + + if( hInst->custom_loader ) + { + if( hInst->encrypted ) + { + Con_Printf( S_ERROR "LoadLibrary: couldn't load encrypted library %s\n", dllname ); + return NULL; + } + + hInst->hInstance = MemoryLoadLibrary( hInst->fullPath ); + } + else hInst->hInstance = LoadLibrary( hInst->fullPath ); + + if( !hInst->hInstance ) + { + Con_DPrintf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_FreeLibrary( hInst ); + return NULL; + } + + // if not set - FunctionFromName and NameForFunction will not working + if( build_ordinals_table ) + { + if( !LibraryLoadSymbols( hInst )) + { + Con_DPrintf( "LoadLibrary: Loading %s - failed\n", dllname ); + COM_FreeLibrary( hInst ); + return NULL; + } + } + + Con_DPrintf( "LoadLibrary: Loading %s - ok\n", dllname ); + + return hInst; +} + +void *COM_GetProcAddress( void *hInstance, const char *name ) +{ + dll_user_t *hInst = (dll_user_t *)hInstance; + + if( !hInst || !hInst->hInstance ) + return NULL; + + if( hInst->custom_loader ) + return (void *)MemoryGetProcAddress( hInst->hInstance, name ); + return (void *)GetProcAddress( hInst->hInstance, GetMSVCName( name )); +} + +void COM_FreeLibrary( void *hInstance ) +{ + dll_user_t *hInst = (dll_user_t *)hInstance; + + if( !hInst || !hInst->hInstance ) + return; // already freed + + if( host.status == HOST_CRASHED ) + { + // we need to hold down all modules, while MSVC can find error + MsgDev( D_NOTE, "Sys_FreeLibrary: hold %s for debugging\n", hInst->dllName ); + return; + } + else MsgDev( D_NOTE, "Sys_FreeLibrary: Unloading %s\n", hInst->dllName ); + + if( hInst->custom_loader ) + MemoryFreeLibrary( hInst->hInstance ); + else FreeLibrary( hInst->hInstance ); + + hInst->hInstance = NULL; + + if( hInst->num_ordinals ) + FreeNameFuncGlobals( hInst ); + Mem_Free( hInst ); // done +} + +dword COM_FunctionFromName( void *hInstance, const char *pName ) +{ + dll_user_t *hInst = (dll_user_t *)hInstance; + int i, index; + + if( !hInst || !hInst->hInstance ) + return 0; + + for( i = 0; i < hInst->num_ordinals; i++ ) + { + if( !Q_strcmp( pName, hInst->names[i] )) + { + index = hInst->ordinals[i]; + return hInst->funcs[index] + hInst->funcBase; + } + } + + // couldn't find the function name to return address + Con_Printf( "Can't find proc: %s\n", pName ); + + return 0; +} + +const char *COM_NameForFunction( void *hInstance, dword function ) +{ + dll_user_t *hInst = (dll_user_t *)hInstance; + int i, index; + + if( !hInst || !hInst->hInstance ) + return NULL; + + for( i = 0; i < hInst->num_ordinals; i++ ) + { + index = hInst->ordinals[i]; + + if(( function - hInst->funcBase ) == hInst->funcs[index] ) + return hInst->names[i]; + } + + // couldn't find the function address to return name + Con_Printf( "Can't find address: %08lx\n", function ); + + return NULL; +} \ No newline at end of file diff --git a/engine/common/library.h b/engine/common/library.h new file mode 100644 index 00000000..c6eec583 --- /dev/null +++ b/engine/common/library.h @@ -0,0 +1,160 @@ +/* +library.h - custom dlls loader +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef LIBRARY_H +#define LIBRARY_H + +#define DOS_SIGNATURE 0x5A4D // MZ +#define NT_SIGNATURE 0x00004550 // PE00 +#define NUMBER_OF_DIRECTORY_ENTRIES 16 +#define MAX_LIBRARY_EXPORTS 4096 + +typedef struct +{ + // dos .exe header + word e_magic; // magic number + word e_cblp; // bytes on last page of file + word e_cp; // pages in file + word e_crlc; // relocations + word e_cparhdr; // size of header in paragraphs + word e_minalloc; // minimum extra paragraphs needed + word e_maxalloc; // maximum extra paragraphs needed + word e_ss; // initial (relative) SS value + word e_sp; // initial SP value + word e_csum; // checksum + word e_ip; // initial IP value + word e_cs; // initial (relative) CS value + word e_lfarlc; // file address of relocation table + word e_ovno; // overlay number + word e_res[4]; // reserved words + word e_oemid; // OEM identifier (for e_oeminfo) + word e_oeminfo; // OEM information; e_oemid specific + word e_res2[10]; // reserved words + long e_lfanew; // file address of new exe header +} DOS_HEADER; + +typedef struct +{ + // win .exe header + word Machine; + word NumberOfSections; + dword TimeDateStamp; + dword PointerToSymbolTable; + dword NumberOfSymbols; + word SizeOfOptionalHeader; + word Characteristics; +} PE_HEADER; + +typedef struct +{ + byte Name[8]; // dos name length + + union + { + dword PhysicalAddress; + dword VirtualSize; + } Misc; + + dword VirtualAddress; + dword SizeOfRawData; + dword PointerToRawData; + dword PointerToRelocations; + dword PointerToLinenumbers; + word NumberOfRelocations; + word NumberOfLinenumbers; + dword Characteristics; +} SECTION_HEADER; + +typedef struct +{ + dword VirtualAddress; + dword Size; +} DATA_DIRECTORY; + +typedef struct +{ + word Magic; + byte MajorLinkerVersion; + byte MinorLinkerVersion; + dword SizeOfCode; + dword SizeOfInitializedData; + dword SizeOfUninitializedData; + dword AddressOfEntryPoint; + dword BaseOfCode; + dword BaseOfData; + dword ImageBase; + dword SectionAlignment; + dword FileAlignment; + word MajorOperatingSystemVersion; + word MinorOperatingSystemVersion; + word MajorImageVersion; + word MinorImageVersion; + word MajorSubsystemVersion; + word MinorSubsystemVersion; + dword Win32VersionValue; + dword SizeOfImage; + dword SizeOfHeaders; + dword CheckSum; + word Subsystem; + word DllCharacteristics; + dword SizeOfStackReserve; + dword SizeOfStackCommit; + dword SizeOfHeapReserve; + dword SizeOfHeapCommit; + dword LoaderFlags; + dword NumberOfRvaAndSizes; + + DATA_DIRECTORY DataDirectory[NUMBER_OF_DIRECTORY_ENTRIES]; +} OPTIONAL_HEADER; + +typedef struct +{ + dword Characteristics; + dword TimeDateStamp; + word MajorVersion; + word MinorVersion; + dword Name; + dword Base; + dword NumberOfFunctions; + dword NumberOfNames; + dword AddressOfFunctions; // RVA from base of image + dword AddressOfNames; // RVA from base of image + dword AddressOfNameOrdinals; // RVA from base of image +} EXPORT_DIRECTORY; + +typedef struct dll_user_s +{ + void *hInstance; // instance handle + qboolean custom_loader; // a bit who indicated loader type + qboolean encrypted; // dll is crypted (some client.dll in HL, CS etc) + char dllName[32]; // for debug messages + string fullPath, shortPath; // actual dll paths + + // ordinals stuff + word *ordinals; + dword *funcs; + char *names[MAX_LIBRARY_EXPORTS]; // max 4096 exports supported + int num_ordinals; // actual exports count + dword funcBase; // base offset +} dll_user_t; + +dll_user_t *FS_FindLibrary( const char *dllname, qboolean directpath ); +void *COM_LoadLibrary( const char *dllname, int build_ordinals_table, qboolean directpath ); +void *COM_GetProcAddress( void *hInstance, const char *name ); +const char *COM_NameForFunction( void *hInstance, dword function ); +dword COM_FunctionFromName( void *hInstance, const char *pName ); +void COM_FreeLibrary( void *hInstance ); + +#endif//LIBRARY_H \ No newline at end of file diff --git a/engine/common/mathlib.c b/engine/common/mathlib.c new file mode 100644 index 00000000..84e02d95 --- /dev/null +++ b/engine/common/mathlib.c @@ -0,0 +1,738 @@ +/* +mathlib.c - internal mathlib +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" +#include "eiface.h" + +#define NUM_HULL_ROUNDS ARRAYSIZE( hull_table ) +#define HULL_PRECISION 4 + +vec3_t vec3_origin = { 0, 0, 0 }; + +static word hull_table[] = { 2, 4, 6, 8, 12, 16, 18, 24, 28, 32, 36, 40, 48, 54, 56, 60, 64, 72, 80, 112, 120, 128, 140, 176 }; + +int boxpnt[6][4] = +{ +{ 0, 4, 6, 2 }, // +X +{ 0, 1, 5, 4 }, // +Y +{ 0, 2, 3, 1 }, // +Z +{ 7, 5, 1, 3 }, // -X +{ 7, 3, 2, 6 }, // -Y +{ 7, 6, 4, 5 }, // -Z +}; + +// pre-quantized table normals from Quake1 +const float m_bytenormals[NUMVERTEXNORMALS][3] = +{ +#include "anorms.h" +}; + +/* +================= +anglemod +================= +*/ +float anglemod( float a ) +{ + a = (360.0 / 65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +/* +================= +SimpleSpline + +NOTE: ripped from hl2 source +hermite basis function for smooth interpolation +Similar to Gain() above, but very cheap to call +value should be between 0 & 1 inclusive +================= +*/ +float SimpleSpline( float value ) +{ + float valueSquared = value * value; + + // nice little ease-in, ease-out spline-like curve + return (3.0f * valueSquared - 2.0f * valueSquared * value); +} + +word FloatToHalf( float v ) +{ + unsigned int i = *((unsigned int *)&v); + unsigned int e = (i >> 23) & 0x00ff; + unsigned int m = i & 0x007fffff; + unsigned short h; + + if( e <= 127 - 15 ) + h = ((m | 0x00800000) >> (127 - 14 - e)) >> 13; + else h = (i >> 13) & 0x3fff; + + h |= (i >> 16) & 0xc000; + + return h; +} + +float HalfToFloat( word h ) +{ + unsigned int f = (h << 16) & 0x80000000; + unsigned int em = h & 0x7fff; + + if( em > 0x03ff ) + { + f |= (em << 13) + ((127 - 15) << 23); + } + else + { + unsigned int m = em & 0x03ff; + + if( m != 0 ) + { + unsigned int e = (em >> 10) & 0x1f; + + while(( m & 0x0400 ) == 0 ) + { + m <<= 1; + e--; + } + + m &= 0x3ff; + f |= ((e + (127 - 14)) << 23) | (m << 13); + } + } + + return *((float *)&f); +} + +/* +================= +RoundUpHullSize + +round the hullsize to nearest 'right' value +================= +*/ +void RoundUpHullSize( vec3_t size ) +{ + int i, j; + + for( i = 0; i < 3; i++) + { + qboolean negative = false; + float result, value; + + value = size[i]; + if( value < 0.0f ) negative = true; + value = Q_ceil( fabs( value )); + + // lookup hull table to find nearest supposed value + for( j = 0; j < NUM_HULL_ROUNDS; j++ ) + { + if( value > hull_table[j] ) + continue; // ceil only + + if( negative ) + { + result = ( value - hull_table[j] ); + if( result <= HULL_PRECISION ) + { + result = -hull_table[j]; + break; + } + } + else + { + result = ( value - hull_table[j] ); + if( result <= HULL_PRECISION ) + { + result = hull_table[j]; + break; + } + } + } + + size[i] = result; + } +} + +/* +================= +SignbitsForPlane + +fast box on planeside test +================= +*/ +int SignbitsForPlane( const vec3_t normal ) +{ + int bits, i; + + for( bits = i = 0; i < 3; i++ ) + if( normal[i] < 0.0f ) bits |= 1<normal, n1 ); + VectorNormalize2( plane2->normal, n2 ); + VectorNormalize2( plane3->normal, n3 ); + + CrossProduct( n1, n2, n1n2 ); + CrossProduct( n2, n3, n2n3 ); + CrossProduct( n3, n1, n3n1 ); + + denom = DotProduct( n1, n2n3 ); + VectorClear( out ); + + // check if the denominator is zero (which would mean that no intersection is to be found + if( denom == 0.0f ) + { + // no intersection could be found, return <0,0,0> + return false; + } + + // compute intersection point +#if 0 + VectorMAMAM( plane1->dist, n2n3, plane2->dist, n3n1, plane3->dist, n1n2, out ); +#else + VectorMA( out, plane1->dist, n2n3, out ); + VectorMA( out, plane2->dist, n3n1, out ); + VectorMA( out, plane3->dist, n1n2, out ); +#endif + VectorScale( out, ( 1.0f / denom ), out ); + + return true; +} + +/* +================= +NearestPOW +================= +*/ +int NearestPOW( int value, qboolean roundDown ) +{ + int n = 1; + + if( value <= 0 ) return 1; + while( n < value ) n <<= 1; + + if( roundDown ) + { + if( n > value ) n >>= 1; + } + return n; +} + +// remap a value in the range [A,B] to [C,D]. +float RemapVal( float val, float A, float B, float C, float D ) +{ + return C + (D - C) * (val - A) / (B - A); +} + +float ApproachVal( float target, float value, float speed ) +{ + float delta = target - value; + + if( delta > speed ) + value += speed; + else if( delta < -speed ) + value -= speed; + else value = target; + + return value; +} + +/* +================= +rsqrt +================= +*/ +float rsqrt( float number ) +{ + int i; + float x, y; + + if( number == 0.0f ) + return 0.0f; + + x = number * 0.5f; + i = *(int *)&number; // evil floating point bit level hacking + i = 0x5f3759df - (i >> 1); // what the fuck? + y = *(float *)&i; + y = y * (1.5f - (x * y * y)); // first iteration + + return y; +} + +/* +================= +SinCos +================= +*/ +void SinCos( float radians, float *sine, float *cosine ) +{ + _asm + { + fld dword ptr [radians] + fsincos + + mov edx, dword ptr [cosine] + mov eax, dword ptr [sine] + + fstp dword ptr [edx] + fstp dword ptr [eax] + } +} + +float VectorNormalizeLength2( const vec3_t v, vec3_t out ) +{ + float length, ilength; + + length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + length = sqrt( length ); + + if( length ) + { + ilength = 1.0f / length; + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; + } + + return length; +} + +void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ) +{ + float d; + + right[0] = forward[2]; + right[1] = -forward[0]; + right[2] = forward[1]; + + d = DotProduct( forward, right ); + VectorMA( right, -d, forward, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + +/* +================= +AngleVectors + +================= +*/ +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); + + if( forward ) + { + forward[0] = cp * cy; + forward[1] = cp * sy; + forward[2] = -sp; + } + + if( right ) + { + right[0] = (-1.0f * sr * sp * cy + -1.0f * cr * -sy ); + right[1] = (-1.0f * sr * sp * sy + -1.0f * cr * cy ); + right[2] = (-1.0f * sr * cp); + } + + if( up ) + { + up[0] = (cr * sp * cy + -sr * -sy ); + up[1] = (cr * sp * sy + -sr * cy ); + up[2] = (cr * cp); + } +} + +/* +================= +VectorAngles + +================= +*/ +void VectorAngles( const float *forward, float *angles ) +{ + float tmp, yaw, pitch; + + if( !forward || !angles ) + { + if( angles ) VectorClear( angles ); + return; + } + + if( forward[1] == 0 && forward[0] == 0 ) + { + // fast case + yaw = 0; + if( forward[2] > 0 ) + pitch = 90.0f; + else pitch = 270.0f; + } + else + { + yaw = ( atan2( forward[1], forward[0] ) * 180 / M_PI ); + if( yaw < 0 ) yaw += 360; + + tmp = sqrt( forward[0] * forward[0] + forward[1] * forward[1] ); + pitch = ( atan2( forward[2], tmp ) * 180 / M_PI ); + if( pitch < 0 ) pitch += 360; + } + + VectorSet( angles, pitch, yaw, 0 ); +} + +/* +================= +VectorsAngles + +================= +*/ +void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles ) +{ + float pitch, cpitch, yaw, roll; + + pitch = -asin( forward[2] ); + cpitch = cos( pitch ); + + if( fabs( cpitch ) > EQUAL_EPSILON ) // gimball lock? + { + cpitch = 1.0f / cpitch; + pitch = RAD2DEG( pitch ); + yaw = RAD2DEG( atan2( forward[1] * cpitch, forward[0] * cpitch )); + roll = RAD2DEG( atan2( -right[2] * cpitch, up[2] * cpitch )); + } + else + { + pitch = forward[2] > 0 ? -90.0f : 90.0f; + yaw = RAD2DEG( atan2( right[0], -right[1] )); + roll = 180.0f; + } + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = roll; +} + +// +// bounds operations +// +/* +================= +ClearBounds +================= +*/ +void ClearBounds( vec3_t mins, vec3_t maxs ) +{ + // make bogus range + mins[0] = mins[1] = mins[2] = 999999.0f; + maxs[0] = maxs[1] = maxs[2] = -999999.0f; +} + +/* +================= +AddPointToBounds +================= +*/ +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) +{ + float val; + int i; + + for( i = 0; i < 3; i++ ) + { + val = v[i]; + if( val < mins[i] ) mins[i] = val; + if( val > maxs[i] ) maxs[i] = val; + } +} + +/* +================= +ExpandBounds +================= +*/ +void ExpandBounds( vec3_t mins, vec3_t maxs, float offset ) +{ + mins[0] -= offset; + mins[1] -= offset; + mins[2] -= offset; + maxs[0] += offset; + maxs[1] += offset; + maxs[2] += offset; +} + +/* +================= +BoundsIntersect +================= +*/ +qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 ) +{ + if( mins1[0] > maxs2[0] || mins1[1] > maxs2[1] || mins1[2] > maxs2[2] ) + return false; + if( maxs1[0] < mins2[0] || maxs1[1] < mins2[1] || maxs1[2] < mins2[2] ) + return false; + return true; +} + +/* +================= +BoundsAndSphereIntersect +================= +*/ +qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius ) +{ + if( mins[0] > origin[0] + radius || mins[1] > origin[1] + radius || mins[2] > origin[2] + radius ) + return false; + if( maxs[0] < origin[0] - radius || maxs[1] < origin[1] - radius || maxs[2] < origin[2] - radius ) + return false; + return true; +} + +/* +================= +SphereIntersect +================= +*/ +qboolean SphereIntersect( const vec3_t vSphereCenter, float fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir ) +{ + float a, b, c, insideSqr; + vec3_t p; + + // translate sphere to origin. + VectorSubtract( vLinePt, vSphereCenter, p ); + + a = DotProduct( vLineDir, vLineDir ); + b = 2.0f * DotProduct( p, vLineDir ); + c = DotProduct( p, p ) - fSphereRadiusSquared; + + insideSqr = b * b - 4.0f * a * c; + if( insideSqr <= 0.000001f ) + return false; + return true; +} + +/* +================= +PlaneIntersect + +find point where ray +was intersect with plane +================= +*/ +void PlaneIntersect( const mplane_t *plane, const vec3_t p0, const vec3_t p1, vec3_t out ) +{ + float distToPlane = PlaneDiff( p0, plane ); + float planeDotRay = DotProduct( plane->normal, p1 ); + float sect = -(distToPlane) / planeDotRay; + + VectorMA( p0, sect, p1, out ); +} + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) +{ + vec3_t corner; + int i; + + for( i = 0; i < 3; i++ ) + { + corner[i] = fabs( mins[i] ) > fabs( maxs[i] ) ? fabs( mins[i] ) : fabs( maxs[i] ); + } + return VectorLength( corner ); +} + +// +// studio utils +// +/* +==================== +AngleQuaternion + +==================== +*/ +void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ) +{ + float sr, sp, sy, cr, cp, cy; + + if( studio ) + { + SinCos( angles[ROLL] * 0.5f, &sy, &cy ); + SinCos( angles[YAW] * 0.5f, &sp, &cp ); + SinCos( angles[PITCH] * 0.5f, &sr, &cr ); + } + else + { + SinCos( DEG2RAD( angles[YAW] ) * 0.5f, &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ) * 0.5f, &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ) * 0.5f, &sr, &cr ); + } + + q[0] = sr * cp * cy - cr * sp * sy; // X + q[1] = cr * sp * cy + sr * cp * sy; // Y + q[2] = cr * cp * sy - sr * sp * cy; // Z + q[3] = cr * cp * cy + sr * sp * sy; // W +} + +/* +==================== +QuaternionAngle + +==================== +*/ +void QuaternionAngle( const vec4_t q, vec3_t angles ) +{ + matrix3x4 mat; + Matrix3x4_FromOriginQuat( mat, q, vec3_origin ); + Matrix3x4_AnglesFromMatrix( mat, angles ); +} + +/* +==================== +QuaternionAlign + +make sure quaternions are within 180 degrees of one another, +if not, reverse q +==================== +*/ +void QuaternionAlign( const vec4_t p, const vec4_t q, vec4_t qt ) +{ + // decide if one of the quaternions is backwards + float a = 0.0f; + float b = 0.0f; + int i; + + for( i = 0; i < 4; i++ ) + { + a += (p[i] - q[i]) * (p[i] - q[i]); + b += (p[i] + q[i]) * (p[i] + q[i]); + } + + if( a > b ) + { + for( i = 0; i < 4; i++ ) + qt[i] = -q[i]; + } + else + { + for( i = 0; i < 4; i++ ) + qt[i] = q[i]; + } +} + +/* +==================== +QuaternionSlerpNoAlign +==================== +*/ +void QuaternionSlerpNoAlign( const vec4_t p, const vec4_t q, float t, vec4_t qt ) +{ + float omega, cosom, sinom, sclp, sclq; + int i; + + // 0.0 returns p, 1.0 return q. + cosom = p[0] * q[0] + p[1] * q[1] + p[2] * q[2] + p[3] * q[3]; + + if(( 1.0f + cosom ) > 0.000001f ) + { + if(( 1.0f - cosom ) > 0.000001f ) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0f - t) * omega) / sinom; + sclq = sin( t * omega ) / sinom; + } + else + { + sclp = 1.0f - t; + sclq = t; + } + + for( i = 0; i < 4; i++ ) + { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin(( 1.0f - t ) * ( 0.5f * M_PI )); + sclq = sin( t * ( 0.5f * M_PI )); + + for( i = 0; i < 3; i++ ) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionSlerp + +Quaternion sphereical linear interpolation +==================== +*/ +void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ) +{ + vec4_t q2; + + // 0.0 returns p, 1.0 return q. + // decide if one of the quaternions is backwards + QuaternionAlign( p, q, q2 ); + + QuaternionSlerpNoAlign( p, q2, t, qt ); +} \ No newline at end of file diff --git a/engine/common/mathlib.h b/engine/common/mathlib.h new file mode 100644 index 00000000..09a09f31 --- /dev/null +++ b/engine/common/mathlib.h @@ -0,0 +1,197 @@ +/* +mathlib.h - base math functions +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef MATHLIB_H +#define MATHLIB_H + +#include + +#pragma warning(disable : 4201) // nonstandard extension used + +// euler angle order +#define PITCH 0 +#define YAW 1 +#define ROLL 2 + +#ifndef M_PI +#define M_PI (float)3.14159265358979323846 +#endif + +#ifndef M_PI2 +#define M_PI2 (float)6.28318530717958647692 +#endif + +#define M_PI_F ((float)(M_PI)) +#define M_PI2_F ((float)(M_PI2)) + +#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI)) +#define DEG2RAD( x ) ((float)(x) * (float)(M_PI / 180.f)) + +#define NUMVERTEXNORMALS 162 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS -2 + +#define PLANE_X 0 // 0 - 2 are axial planes +#define PLANE_Y 1 // 3 needs alternate calc +#define PLANE_Z 2 +#define PLANE_NONAXIAL 3 + +#define EQUAL_EPSILON 0.001f +#define STOP_EPSILON 0.1f +#define ON_EPSILON 0.1f + +#define RAD_TO_STUDIO (32768.0 / M_PI) +#define STUDIO_TO_RAD (M_PI / 32768.0) + +#define INV127F ( 1.0f / 127.0f ) +#define INV255F ( 1.0f / 255.0f ) +#define MAKE_SIGNED( x ) ((( x ) * INV127F ) - 1.0f ) + +#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) +#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) +#define Q_recip( a ) ((float)(1.0f / (float)(a))) +#define Q_floor( a ) ((float)(long)(a)) +#define Q_ceil( a ) ((float)(long)((a) + 1)) + +#define Q_rint(x) ((x) < 0 ? ((int)((x)-0.5f)) : ((int)((x)+0.5f))) +#define IS_NAN(x) (((*(int *)&x) & (255<<23)) == (255<<23)) + +#define ALIGN( x, a ) ((( x ) + (( size_t )( a ) - 1 )) & ~(( size_t )( a ) - 1 )) + +#define VectorIsNAN(v) (IS_NAN(v[0]) || IS_NAN(v[1]) || IS_NAN(v[2])) +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define DotProductAbs(x,y) (abs((x)[0]*(y)[0])+abs((x)[1]*(y)[1])+abs((x)[2]*(y)[2])) +#define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2])) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define Vector2Subtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define Vector2Add(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorAddScalar(a,b,c) ((c)[0]=(a)[0]+(b),(c)[1]=(a)[1]+(b),(c)[2]=(a)[2]+(b)) +#define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) +#define VectorCompare(v1,v2) ((v1)[0]==(v2)[0] && (v1)[1]==(v2)[1] && (v1)[2]==(v2)[2]) +#define VectorDivide( in, d, out ) VectorScale( in, (1.0f / (d)), out ) +#define VectorMax(a) ( max((a)[0], max((a)[1], (a)[2])) ) +#define VectorAvg(a) ( ((a)[0] + (a)[1] + (a)[2]) / 3 ) +#define VectorLength(a) ( sqrt( DotProduct( a, a ))) +#define VectorLength2(a) (DotProduct( a, a )) +#define VectorDistance(a, b) (sqrt( VectorDistance2( a, b ))) +#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) +#define Vector2Average(a,b,o) ((o)[0]=((a)[0]+(b)[0])*0.5,(o)[1]=((a)[1]+(b)[1])*0.5) +#define VectorAverage(a,b,o) ((o)[0]=((a)[0]+(b)[0])*0.5,(o)[1]=((a)[1]+(b)[1])*0.5,(o)[2]=((a)[2]+(b)[2])*0.5) +#define Vector2Set(v, x, y) ((v)[0]=(x),(v)[1]=(y)) +#define VectorSet(v, x, y, z) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z)) +#define Vector4Set(v, a, b, c, d) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c),(v)[3] = (d)) +#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0) +#define Vector2Lerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1])) +#define VectorLerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2])) +#define VectorNormalize( v ) { float ilength = (float)sqrt(DotProduct(v, v));if (ilength) ilength = 1.0f / ilength;v[0] *= ilength;v[1] *= ilength;v[2] *= ilength; } +#define VectorNormalize2( v, dest ) {float ilength = (float)sqrt(DotProduct(v,v));if (ilength) ilength = 1.0f / ilength;dest[0] = v[0] * ilength;dest[1] = v[1] * ilength;dest[2] = v[2] * ilength; } +#define VectorNormalizeFast( v ) {float ilength = (float)rsqrt(DotProduct(v,v)); v[0] *= ilength; v[1] *= ilength; v[2] *= ilength; } +#define VectorNormalizeLength( v ) VectorNormalizeLength2((v), (v)) +#define VectorNegate(x, y) ((y)[0] = -(x)[0], (y)[1] = -(x)[1], (y)[2] = -(x)[2]) +#define VectorM(scale1, b1, c) ((c)[0] = (scale1) * (b1)[0],(c)[1] = (scale1) * (b1)[1],(c)[2] = (scale1) * (b1)[2]) +#define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2]) +#define VectorMAM(scale1, b1, scale2, b2, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2]) +#define VectorMAMAM(scale1, b1, scale2, b2, scale3, b3, c) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0] + (scale3) * (b3)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1] + (scale3) * (b3)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2] + (scale3) * (b3)[2]) +#define VectorIsNull( v ) ((v)[0] == 0.0f && (v)[1] == 0.0f && (v)[2] == 0.0f) +#define MakeRGBA( out, x, y, z, w ) Vector4Set( out, x, y, z, w ) +#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) +#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) + +float rsqrt( float number ); +float anglemod( float a ); +word FloatToHalf( float v ); +float HalfToFloat( word h ); +float SimpleSpline( float value ); +void RoundUpHullSize( vec3_t size ); +int SignbitsForPlane( const vec3_t normal ); +int PlaneTypeForNormal( const vec3_t normal ); +int NearestPOW( int value, qboolean roundDown ); +void SinCos( float radians, float *sine, float *cosine ); +float VectorNormalizeLength2( const vec3_t v, vec3_t out ); +void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ); +void VectorAngles( const float *forward, float *angles ); +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up ); +void VectorsAngles( const vec3_t forward, const vec3_t right, const vec3_t up, vec3_t angles ); +qboolean PlanesGetIntersectionPoint( const struct mplane_s *plane1, const struct mplane_s *plane2, const struct mplane_s *plane3, vec3_t out ); +void PlaneIntersect( const struct mplane_s *plane, const vec3_t p0, const vec3_t p1, vec3_t out ); + +void ClearBounds( vec3_t mins, vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); +qboolean BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2 ); +qboolean BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, float radius ); +qboolean SphereIntersect( const vec3_t vSphereCenter, float fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir ); +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); +void ExpandBounds( vec3_t mins, vec3_t maxs, float offset ); + +void AngleQuaternion( const vec3_t angles, vec4_t q, qboolean studio ); +void QuaternionAngle( const vec4_t q, vec3_t angles ); +void QuaternionSlerp( const vec4_t p, const vec4_t q, float t, vec4_t qt ); +float RemapVal( float val, float A, float B, float C, float D ); +float ApproachVal( float target, float value, float speed ); + +// +// matrixlib.c +// +#define Matrix3x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, matrix3x4_identity ) +#define Matrix3x4_Copy( out, in ) memcpy( out, in, sizeof( matrix3x4 )) + +void Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] ); +void Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] ); +void Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] ); +void Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] ); +void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ); +void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ); +void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale ); +void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); +void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z ); +void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ); +void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out ); +void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ); + +#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, matrix4x4_identity ) +#define Matrix4x4_Copy( out, in ) memcpy( out, in, sizeof( matrix4x4 )) + +void Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] ); +void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] ); +void Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] ); +void Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] ); +void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); +void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ); +void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ); +void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); +void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); +void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin ); +void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ); +void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ); +void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ); +void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ); +qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ); + +extern vec3_t vec3_origin; +extern int boxpnt[6][4]; +extern const matrix3x4 matrix3x4_identity; +extern const matrix4x4 matrix4x4_identity; +extern const float m_bytenormals[NUMVERTEXNORMALS][3]; + +#endif//MATHLIB_H \ No newline at end of file diff --git a/engine/common/matrixlib.c b/engine/common/matrixlib.c new file mode 100644 index 00000000..fdeea667 --- /dev/null +++ b/engine/common/matrixlib.c @@ -0,0 +1,807 @@ +/* +matrixlib.c - internal matrixlib +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" + +const matrix3x4 matrix3x4_identity = +{ +{ 1, 0, 0, 0 }, // PITCH [forward], org[0] +{ 0, 1, 0, 0 }, // YAW [right] , org[1] +{ 0, 0, 1, 0 }, // ROLL [up] , org[2] +}; + +/* +======================================================================== + + Matrix3x4 operations + +======================================================================== +*/ +void Matrix3x4_VectorTransform( const matrix3x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3]; + out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3]; + out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3]; +} + +void Matrix3x4_VectorITransform( const matrix3x4 in, const float v[3], float out[3] ) +{ + vec3_t dir; + + dir[0] = v[0] - in[0][3]; + dir[1] = v[1] - in[1][3]; + dir[2] = v[2] - in[2][3]; + + out[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0]; + out[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1]; + out[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2]; +} + +void Matrix3x4_VectorRotate( const matrix3x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2]; + out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2]; + out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2]; +} + +void Matrix3x4_VectorIRotate( const matrix3x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0]; + out[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1]; + out[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2]; +} + +void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; +} + +void Matrix3x4_SetOrigin( matrix3x4 out, float x, float y, float z ) +{ + out[0][3] = x; + out[1][3] = y; + out[2][3] = z; +} + +void Matrix3x4_OriginFromMatrix( const matrix3x4 in, float *out ) +{ + out[0] = in[0][3]; + out[1] = in[1][3]; + out[2] = in[2][3]; +} + +void Matrix3x4_AnglesFromMatrix( const matrix3x4 in, vec3_t out ) +{ + float xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] ); + + if( xyDist > 0.001f ) + { + // enough here to get angles? + out[0] = RAD2DEG( atan2( -in[2][0], xyDist )); + out[1] = RAD2DEG( atan2( in[1][0], in[0][0] )); + out[2] = RAD2DEG( atan2( in[2][1], in[2][2] )); + } + else + { + // forward is mostly Z, gimbal lock + out[0] = RAD2DEG( atan2( -in[2][0], xyDist )); + out[1] = RAD2DEG( atan2( -in[0][1], in[1][1] )); + out[2] = 0.0f; + } +} + +void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ) +{ + out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; + out[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; + out[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; + + out[0][1] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2]; + out[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2]; + out[2][1] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0]; + + out[0][2] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1]; + out[1][2] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0]; + out[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1]; + + out[0][3] = origin[0]; + out[1][3] = origin[1]; + out[2][3] = origin[2]; +} + +void Matrix3x4_CreateFromEntity( matrix3x4 out, const vec3_t angles, const vec3_t origin, float scale ) +{ + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI2 / 360.0f); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI2 / 360.0f); + SinCos( angle, &sr, &cr ); + + out[0][0] = (cp*cy) * scale; + out[0][1] = (sr*sp*cy+cr*-sy) * scale; + out[0][2] = (cr*sp*cy+-sr*-sy) * scale; + out[0][3] = origin[0]; + out[1][0] = (cp*sy) * scale; + out[1][1] = (sr*sp*sy+cr*cy) * scale; + out[1][2] = (cr*sp*sy+-sr*cy) * scale; + out[1][3] = origin[1]; + out[2][0] = (-sp) * scale; + out[2][1] = (sr*cp) * scale; + out[2][2] = (cr*cp) * scale; + out[2][3] = origin[2]; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI2 / 360.0f); + SinCos( angle, &sp, &cp ); + + out[0][0] = (cp*cy) * scale; + out[0][1] = (-sy) * scale; + out[0][2] = (sp*cy) * scale; + out[0][3] = origin[0]; + out[1][0] = (cp*sy) * scale; + out[1][1] = (cy) * scale; + out[1][2] = (sp*sy) * scale; + out[1][3] = origin[1]; + out[2][0] = (-sp) * scale; + out[2][1] = 0.0f; + out[2][2] = (cp) * scale; + out[2][3] = origin[2]; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + + out[0][0] = (cy) * scale; + out[0][1] = (-sy) * scale; + out[0][2] = 0.0f; + out[0][3] = origin[0]; + out[1][0] = (sy) * scale; + out[1][1] = (cy) * scale; + out[1][2] = 0.0f; + out[1][3] = origin[1]; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = scale; + out[2][3] = origin[2]; + } + else + { + out[0][0] = scale; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = origin[0]; + out[1][0] = 0.0f; + out[1][1] = scale; + out[1][2] = 0.0f; + out[1][3] = origin[1]; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = scale; + out[2][3] = origin[2]; + } +} + +void Matrix3x4_TransformPositivePlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) +{ + float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); + float iscale = 1.0f / scale; + + out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; + out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; + out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; + *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); +} + +void Matrix3x4_Invert_Simple( matrix3x4 out, const matrix3x4 in1 ) +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) + float scale = 1.0f / (in1[0][0] * in1[0][0] + in1[0][1] * in1[0][1] + in1[0][2] * in1[0][2]); + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out[0][0] = in1[0][0] * scale; + out[0][1] = in1[1][0] * scale; + out[0][2] = in1[2][0] * scale; + out[1][0] = in1[0][1] * scale; + out[1][1] = in1[1][1] * scale; + out[1][2] = in1[2][1] * scale; + out[2][0] = in1[0][2] * scale; + out[2][1] = in1[1][2] * scale; + out[2][2] = in1[2][2] * scale; + + // invert the translate + out[0][3] = -(in1[0][3] * out[0][0] + in1[1][3] * out[0][1] + in1[2][3] * out[0][2]); + out[1][3] = -(in1[0][3] * out[1][0] + in1[1][3] * out[1][1] + in1[2][3] * out[1][2]); + out[2][3] = -(in1[0][3] * out[2][0] + in1[1][3] * out[2][1] + in1[2][3] * out[2][2]); +} + +const matrix4x4 matrix4x4_identity = +{ +{ 1, 0, 0, 0 }, // PITCH +{ 0, 1, 0, 0 }, // YAW +{ 0, 0, 1, 0 }, // ROLL +{ 0, 0, 0, 1 }, // ORIGIN +}; + +/* +======================================================================== + + Matrix4x4 operations + +======================================================================== +*/ +void Matrix4x4_VectorTransform( const matrix4x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2] + in[0][3]; + out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2] + in[1][3]; + out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2] + in[2][3]; +} + +void Matrix4x4_VectorITransform( const matrix4x4 in, const float v[3], float out[3] ) +{ + vec3_t dir; + + dir[0] = v[0] - in[0][3]; + dir[1] = v[1] - in[1][3]; + dir[2] = v[2] - in[2][3]; + + out[0] = dir[0] * in[0][0] + dir[1] * in[1][0] + dir[2] * in[2][0]; + out[1] = dir[0] * in[0][1] + dir[1] * in[1][1] + dir[2] * in[2][1]; + out[2] = dir[0] * in[0][2] + dir[1] * in[1][2] + dir[2] * in[2][2]; +} + +void Matrix4x4_VectorRotate( const matrix4x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[0][1] + v[2] * in[0][2]; + out[1] = v[0] * in[1][0] + v[1] * in[1][1] + v[2] * in[1][2]; + out[2] = v[0] * in[2][0] + v[1] * in[2][1] + v[2] * in[2][2]; +} + +void Matrix4x4_VectorIRotate( const matrix4x4 in, const float v[3], float out[3] ) +{ + out[0] = v[0] * in[0][0] + v[1] * in[1][0] + v[2] * in[2][0]; + out[1] = v[0] * in[0][1] + v[1] * in[1][1] + v[2] * in[2][1]; + out[2] = v[0] * in[0][2] + v[1] * in[1][2] + v[2] * in[2][2]; +} + +void Matrix4x4_ConcatTransforms( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[0][1] * in2[1][0] + in1[0][2] * in2[2][0]; + out[0][1] = in1[0][0] * in2[0][1] + in1[0][1] * in2[1][1] + in1[0][2] * in2[2][1]; + out[0][2] = in1[0][0] * in2[0][2] + in1[0][1] * in2[1][2] + in1[0][2] * in2[2][2]; + out[0][3] = in1[0][0] * in2[0][3] + in1[0][1] * in2[1][3] + in1[0][2] * in2[2][3] + in1[0][3]; + out[1][0] = in1[1][0] * in2[0][0] + in1[1][1] * in2[1][0] + in1[1][2] * in2[2][0]; + out[1][1] = in1[1][0] * in2[0][1] + in1[1][1] * in2[1][1] + in1[1][2] * in2[2][1]; + out[1][2] = in1[1][0] * in2[0][2] + in1[1][1] * in2[1][2] + in1[1][2] * in2[2][2]; + out[1][3] = in1[1][0] * in2[0][3] + in1[1][1] * in2[1][3] + in1[1][2] * in2[2][3] + in1[1][3]; + out[2][0] = in1[2][0] * in2[0][0] + in1[2][1] * in2[1][0] + in1[2][2] * in2[2][0]; + out[2][1] = in1[2][0] * in2[0][1] + in1[2][1] * in2[1][1] + in1[2][2] * in2[2][1]; + out[2][2] = in1[2][0] * in2[0][2] + in1[2][1] * in2[1][2] + in1[2][2] * in2[2][2]; + out[2][3] = in1[2][0] * in2[0][3] + in1[2][1] * in2[1][3] + in1[2][2] * in2[2][3] + in1[2][3]; +} + +void Matrix4x4_SetOrigin( matrix4x4 out, float x, float y, float z ) +{ + out[0][3] = x; + out[1][3] = y; + out[2][3] = z; +} + +void Matrix4x4_OriginFromMatrix( const matrix4x4 in, float *out ) +{ + out[0] = in[0][3]; + out[1] = in[1][3]; + out[2] = in[2][3]; +} + +void Matrix4x4_FromOriginQuat( matrix4x4 out, const vec4_t quaternion, const vec3_t origin ) +{ + out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; + out[1][0] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; + out[2][0] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; + out[0][3] = origin[0]; + out[0][1] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2]; + out[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2]; + out[2][1] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0]; + out[1][3] = origin[1]; + out[0][2] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1]; + out[1][2] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0]; + out[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1]; + out[2][3] = origin[2]; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_CreateFromEntity( matrix4x4 out, const vec3_t angles, const vec3_t origin, float scale ) +{ + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI2 / 360.0f); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI2 / 360.0f); + SinCos( angle, &sr, &cr ); + + out[0][0] = (cp*cy) * scale; + out[0][1] = (sr*sp*cy+cr*-sy) * scale; + out[0][2] = (cr*sp*cy+-sr*-sy) * scale; + out[0][3] = origin[0]; + out[1][0] = (cp*sy) * scale; + out[1][1] = (sr*sp*sy+cr*cy) * scale; + out[1][2] = (cr*sp*sy+-sr*cy) * scale; + out[1][3] = origin[1]; + out[2][0] = (-sp) * scale; + out[2][1] = (sr*cp) * scale; + out[2][2] = (cr*cp) * scale; + out[2][3] = origin[2]; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI2 / 360.0f); + SinCos( angle, &sp, &cp ); + + out[0][0] = (cp*cy) * scale; + out[0][1] = (-sy) * scale; + out[0][2] = (sp*cy) * scale; + out[0][3] = origin[0]; + out[1][0] = (cp*sy) * scale; + out[1][1] = (cy) * scale; + out[1][2] = (sp*sy) * scale; + out[1][3] = origin[1]; + out[2][0] = (-sp) * scale; + out[2][1] = 0.0f; + out[2][2] = (cp) * scale; + out[2][3] = origin[2]; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI2 / 360.0f); + SinCos( angle, &sy, &cy ); + + out[0][0] = (cy) * scale; + out[0][1] = (-sy) * scale; + out[0][2] = 0.0f; + out[0][3] = origin[0]; + out[1][0] = (sy) * scale; + out[1][1] = (cy) * scale; + out[1][2] = 0.0f; + out[1][3] = origin[1]; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = scale; + out[2][3] = origin[2]; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; + } + else + { + out[0][0] = scale; + out[0][1] = 0.0f; + out[0][2] = 0.0f; + out[0][3] = origin[0]; + out[1][0] = 0.0f; + out[1][1] = scale; + out[1][2] = 0.0f; + out[1][3] = origin[1]; + out[2][0] = 0.0f; + out[2][1] = 0.0f; + out[2][2] = scale; + out[2][3] = origin[2]; + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; + } +} + +void Matrix4x4_ConvertToEntity( const matrix4x4 in, vec3_t angles, vec3_t origin ) +{ + float xyDist = sqrt( in[0][0] * in[0][0] + in[1][0] * in[1][0] ); + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles[0] = RAD2DEG( atan2( -in[2][0], xyDist )); + angles[1] = RAD2DEG( atan2( in[1][0], in[0][0] )); + angles[2] = RAD2DEG( atan2( in[2][1], in[2][2] )); + } + else // forward is mostly Z, gimbal lock + { + angles[0] = RAD2DEG( atan2( -in[2][0], xyDist )); + angles[1] = RAD2DEG( atan2( -in[0][1], in[1][1] )); + angles[2] = 0.0f; + } + + origin[0] = in[0][3]; + origin[1] = in[1][3]; + origin[2] = in[2][3]; +} + +void Matrix4x4_TransformPositivePlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) +{ + float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); + float iscale = 1.0f / scale; + + out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; + out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; + out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; + *dist = d * scale + ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); +} + +void Matrix4x4_TransformStandardPlane( const matrix4x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) +{ + float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); + float iscale = 1.0f / scale; + + out[0] = (normal[0] * in[0][0] + normal[1] * in[0][1] + normal[2] * in[0][2]) * iscale; + out[1] = (normal[0] * in[1][0] + normal[1] * in[1][1] + normal[2] * in[1][2]) * iscale; + out[2] = (normal[0] * in[2][0] + normal[1] * in[2][1] + normal[2] * in[2][2]) * iscale; + *dist = d * scale - ( out[0] * in[0][3] + out[1] * in[1][3] + out[2] * in[2][3] ); +} + +void Matrix4x4_Invert_Simple( matrix4x4 out, const matrix4x4 in1 ) +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) + float scale = 1.0f / (in1[0][0] * in1[0][0] + in1[0][1] * in1[0][1] + in1[0][2] * in1[0][2]); + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out[0][0] = in1[0][0] * scale; + out[0][1] = in1[1][0] * scale; + out[0][2] = in1[2][0] * scale; + out[1][0] = in1[0][1] * scale; + out[1][1] = in1[1][1] * scale; + out[1][2] = in1[2][1] * scale; + out[2][0] = in1[0][2] * scale; + out[2][1] = in1[1][2] * scale; + out[2][2] = in1[2][2] * scale; + + // invert the translate + out[0][3] = -(in1[0][3] * out[0][0] + in1[1][3] * out[0][1] + in1[2][3] * out[0][2]); + out[1][3] = -(in1[0][3] * out[1][0] + in1[1][3] * out[1][1] + in1[2][3] * out[1][2]); + out[2][3] = -(in1[0][3] * out[2][0] + in1[1][3] * out[2][1] + in1[2][3] * out[2][2]); + + // don't know if there's anything worth doing here + out[3][0] = 0.0f; + out[3][1] = 0.0f; + out[3][2] = 0.0f; + out[3][3] = 1.0f; +} + +void Matrix4x4_Transpose( matrix4x4 out, const matrix4x4 in1 ) +{ + out[0][0] = in1[0][0]; + out[0][1] = in1[1][0]; + out[0][2] = in1[2][0]; + out[0][3] = in1[3][0]; + out[1][0] = in1[0][1]; + out[1][1] = in1[1][1]; + out[1][2] = in1[2][1]; + out[1][3] = in1[3][1]; + out[2][0] = in1[0][2]; + out[2][1] = in1[1][2]; + out[2][2] = in1[2][2]; + out[2][3] = in1[3][2]; + out[3][0] = in1[0][3]; + out[3][1] = in1[1][3]; + out[3][2] = in1[2][3]; + out[3][3] = in1[3][3]; +} + +qboolean Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ) +{ + float *temp; + float *r[4]; + float rtemp[4][8]; + float m[4]; + float s; + + r[0] = rtemp[0]; + r[1] = rtemp[1]; + r[2] = rtemp[2]; + r[3] = rtemp[3]; + + r[0][0] = in1[0][0]; + r[0][1] = in1[0][1]; + r[0][2] = in1[0][2]; + r[0][3] = in1[0][3]; + r[0][4] = 1.0f; + r[0][5] = 0.0f; + r[0][6] = 0.0f; + r[0][7] = 0.0f; + + r[1][0] = in1[1][0]; + r[1][1] = in1[1][1]; + r[1][2] = in1[1][2]; + r[1][3] = in1[1][3]; + r[1][5] = 1.0f; + r[1][4] = 0.0f; + r[1][6] = 0.0f; + r[1][7] = 0.0f; + + r[2][0] = in1[2][0]; + r[2][1] = in1[2][1]; + r[2][2] = in1[2][2]; + r[2][3] = in1[2][3]; + r[2][6] = 1.0f; + r[2][4] = 0.0f; + r[2][5] = 0.0f; + r[2][7] = 0.0f; + + r[3][0] = in1[3][0]; + r[3][1] = in1[3][1]; + r[3][2] = in1[3][2]; + r[3][3] = in1[3][3]; + r[3][4] = 0.0f; + r[3][5] = 0.0f; + r[3][6] = 0.0f; + r[3][7] = 1.0f; + + if( fabs( r[3][0] ) > fabs( r[2][0] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( fabs( r[2][0] ) > fabs( r[1][0] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( fabs( r[1][0] ) > fabs( r[0][0] )) + { + temp = r[1]; + r[1] = r[0]; + r[0] = temp; + } + + if( r[0][0] ) + { + m[1] = r[1][0] / r[0][0]; + m[2] = r[2][0] / r[0][0]; + m[3] = r[3][0] / r[0][0]; + + s = r[0][1]; + r[1][1] -= m[1] * s; + r[2][1] -= m[2] * s; + r[3][1] -= m[3] * s; + + s = r[0][2]; + r[1][2] -= m[1] * s; + r[2][2] -= m[2] * s; + r[3][2] -= m[3] * s; + + s = r[0][3]; + r[1][3] -= m[1] * s; + r[2][3] -= m[2] * s; + r[3][3] -= m[3] * s; + + s = r[0][4]; + if( s ) + { + r[1][4] -= m[1] * s; + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[0][5]; + if( s ) + { + r[1][5] -= m[1] * s; + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[0][6]; + if( s ) + { + r[1][6] -= m[1] * s; + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[0][7]; + if( s ) + { + r[1][7] -= m[1] * s; + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][1] ) > fabs( r[2][1] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( fabs( r[2][1] ) > fabs( r[1][1] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( r[1][1] ) + { + m[2] = r[2][1] / r[1][1]; + m[3] = r[3][1] / r[1][1]; + r[2][2] -= m[2] * r[1][2]; + r[3][2] -= m[3] * r[1][2]; + r[2][3] -= m[2] * r[1][3]; + r[3][3] -= m[3] * r[1][3]; + + s = r[1][4]; + if( s ) + { + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[1][5]; + if( s ) + { + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[1][6]; + if( s ) + { + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[1][7]; + if( s ) + { + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][2] ) > fabs( r[2][2] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( r[2][2] ) + { + m[3] = r[3][2] / r[2][2]; + r[3][3] -= m[3] * r[2][3]; + r[3][4] -= m[3] * r[2][4]; + r[3][5] -= m[3] * r[2][5]; + r[3][6] -= m[3] * r[2][6]; + r[3][7] -= m[3] * r[2][7]; + + if( r[3][3] ) + { + s = 1.0f / r[3][3]; + r[3][4] *= s; + r[3][5] *= s; + r[3][6] *= s; + r[3][7] *= s; + + m[2] = r[2][3]; + s = 1.0f / r[2][2]; + r[2][4] = s * (r[2][4] - r[3][4] * m[2]); + r[2][5] = s * (r[2][5] - r[3][5] * m[2]); + r[2][6] = s * (r[2][6] - r[3][6] * m[2]); + r[2][7] = s * (r[2][7] - r[3][7] * m[2]); + + m[1] = r[1][3]; + r[1][4] -= r[3][4] * m[1]; + r[1][5] -= r[3][5] * m[1]; + r[1][6] -= r[3][6] * m[1]; + r[1][7] -= r[3][7] * m[1]; + + m[0] = r[0][3]; + r[0][4] -= r[3][4] * m[0]; + r[0][5] -= r[3][5] * m[0]; + r[0][6] -= r[3][6] * m[0]; + r[0][7] -= r[3][7] * m[0]; + + m[1] = r[1][2]; + s = 1.0f / r[1][1]; + r[1][4] = s * (r[1][4] - r[2][4] * m[1]); + r[1][5] = s * (r[1][5] - r[2][5] * m[1]); + r[1][6] = s * (r[1][6] - r[2][6] * m[1]); + r[1][7] = s * (r[1][7] - r[2][7] * m[1]); + + m[0] = r[0][2]; + r[0][4] -= r[2][4] * m[0]; + r[0][5] -= r[2][5] * m[0]; + r[0][6] -= r[2][6] * m[0]; + r[0][7] -= r[2][7] * m[0]; + + m[0] = r[0][1]; + s = 1.0f / r[0][0]; + r[0][4] = s * (r[0][4] - r[1][4] * m[0]); + r[0][5] = s * (r[0][5] - r[1][5] * m[0]); + r[0][6] = s * (r[0][6] - r[1][6] * m[0]); + r[0][7] = s * (r[0][7] - r[1][7] * m[0]); + + out[0][0] = r[0][4]; + out[0][1] = r[0][5]; + out[0][2] = r[0][6]; + out[0][3] = r[0][7]; + out[1][0] = r[1][4]; + out[1][1] = r[1][5]; + out[1][2] = r[1][6]; + out[1][3] = r[1][7]; + out[2][0] = r[2][4]; + out[2][1] = r[2][5]; + out[2][2] = r[2][6]; + out[2][3] = r[2][7]; + out[3][0] = r[3][4]; + out[3][1] = r[3][5]; + out[3][2] = r[3][6]; + out[3][3] = r[3][7]; + + return true; + } + } + } + } + return false; +} \ No newline at end of file diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c new file mode 100644 index 00000000..f7ca6fb6 --- /dev/null +++ b/engine/common/mod_bmodel.c @@ -0,0 +1,3030 @@ +/* +mod_bmodel.c - loading & handling world and brushmodels +Copyright (C) 2016 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "mod_local.h" +#include "sprite.h" +#include "mathlib.h" +#include "alias.h" +#include "studio.h" +#include "wadfile.h" +#include "world.h" +#include "gl_local.h" +#include "features.h" +#include "client.h" +#include "server.h" // LUMP_ error codes + +typedef struct wadlist_s +{ + char wadnames[MAX_MAP_WADS][32]; + int count; +} wadlist_t; + +typedef struct leaflist_s +{ + int count; + int maxcount; + qboolean overflowed; + short *list; + vec3_t mins, maxs; + int topnode; // for overflows where each leaf can't be stored individually +} leaflist_t; + +typedef struct +{ + // generic lumps + dmodel_t *submodels; + size_t numsubmodels; + + dvertex_t *vertexes; + size_t numvertexes; + + dplane_t *planes; + size_t numplanes; + + union + { + dnode_t *nodes; + dnode32_t *nodes32; + }; + size_t numnodes; + + union + { + dleaf_t *leafs; + dleaf32_t *leafs32; + }; + int numleafs; + + union + { + dclipnode_t *clipnodes; + dclipnode32_t *clipnodes32; + }; + int numclipnodes; + + dtexinfo_t *texinfo; + size_t numtexinfo; + + union + { + dmarkface_t *markfaces; + dmarkface32_t *markfaces32; + }; + size_t nummarkfaces; + + dsurfedge_t *surfedges; + size_t numsurfedges; + + union + { + dedge_t *edges; + dedge32_t *edges32; + }; + size_t numedges; + + union + { + dface_t *surfaces; + dface32_t *surfaces32; + }; + size_t numsurfaces; + + dfaceinfo_t *faceinfo; + size_t numfaceinfo; + + // array lumps + byte *visdata; + size_t visdatasize; + + byte *lightdata; + size_t lightdatasize; + + byte *deluxdata; + size_t deluxdatasize; + + byte *shadowdata; + size_t shadowdatasize; + + byte *entdata; + size_t entdatasize; + + // lumps that required personal handler + dmiptexlump_t *textures; + size_t texdatasize; + + // intermediate arrays (pointers will lost after loading, but keep the data) + color24 *deluxedata_out; // deluxemap data pointer + byte *shadowdata_out; // occlusion data pointer + dclipnode32_t *clipnodes_out; // temporary 32-bit array to hold clipnodes + + // misc stuff + wadlist_t wadlist; + int lightmap_samples; // samples per lightmap (1 or 3) + int version; // model version + qboolean isworld; + qboolean vis_errors; // don't spam about vis decompression errors +} dbspmodel_t; + +typedef struct +{ + const char *lumpname; + size_t entrysize; + size_t maxcount; + size_t count; +} mlumpstat_t; + +typedef struct +{ + char name[64]; // just for debug + + // count errors and warnings + int numerrors; + int numwarnings; +} loadstat_t; + +#define CHECK_OVERFLOW BIT( 0 ) // if some of lumps will be overflowed this non fatal for us. But some lumps are critical. mark them +#define USE_EXTRAHEADER BIT( 1 ) + +#define LUMP_SAVESTATS BIT( 0 ) +#define LUMP_TESTONLY BIT( 1 ) +#define LUMP_SILENT BIT( 2 ) + +typedef struct +{ + const int lumpnumber; + const size_t mincount; + const size_t maxcount; + const int entrysize; + const int entrysize32; // alternative (-1 by default) + const char *loadname; + int flags; + const void **dataptr; + size_t *count; +} mlumpinfo_t; + +world_static_t world; +static dbspmodel_t srcmodel; +static loadstat_t loadstat; +static model_t *worldmodel; +static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS]; +static mlumpinfo_t srclumps[HEADER_LUMPS] = +{ +{ LUMP_ENTITIES, 32, MAX_MAP_ENTSTRING, sizeof( byte ), -1, "entities", 0, (void **)&srcmodel.entdata, &srcmodel.entdatasize }, +{ LUMP_PLANES, 1, MAX_MAP_PLANES, sizeof( dplane_t ), -1, "planes", 0, (void **)&srcmodel.planes, &srcmodel.numplanes }, +{ LUMP_TEXTURES, 1, MAX_MAP_MIPTEX, sizeof( byte ), -1, "textures", 0, (void **)&srcmodel.textures, &srcmodel.texdatasize }, +{ LUMP_VERTEXES, 0, MAX_MAP_VERTS, sizeof( dvertex_t ), -1, "vertexes", 0, (void **)&srcmodel.vertexes, &srcmodel.numvertexes }, +{ LUMP_VISIBILITY, 0, MAX_MAP_VISIBILITY, sizeof( byte ), -1, "visibility", 0, (void **)&srcmodel.visdata, &srcmodel.visdatasize }, +{ LUMP_NODES, 1, MAX_MAP_NODES, sizeof( dnode_t ), sizeof( dnode32_t ), "nodes", CHECK_OVERFLOW, (void **)&srcmodel.nodes, &srcmodel.numnodes }, +{ LUMP_TEXINFO, 0, MAX_MAP_TEXINFO, sizeof( dtexinfo_t ), -1, "texinfo", CHECK_OVERFLOW, (void **)&srcmodel.texinfo, &srcmodel.numtexinfo }, +{ LUMP_FACES, 0, MAX_MAP_FACES, sizeof( dface_t ), sizeof( dface32_t ), "faces", CHECK_OVERFLOW, (void **)&srcmodel.surfaces, &srcmodel.numsurfaces }, +{ LUMP_LIGHTING, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "lightmaps", 0, (void **)&srcmodel.lightdata, &srcmodel.lightdatasize }, +{ LUMP_CLIPNODES, 0, MAX_MAP_CLIPNODES, sizeof( dclipnode_t ), sizeof( dclipnode32_t ), "clipnodes", 0, (void **)&srcmodel.clipnodes, &srcmodel.numclipnodes }, +{ LUMP_LEAFS, 1, MAX_MAP_LEAFS, sizeof( dleaf_t ), sizeof( dleaf32_t ), "leafs", CHECK_OVERFLOW, (void **)&srcmodel.leafs, &srcmodel.numleafs }, +{ LUMP_MARKSURFACES, 0, MAX_MAP_MARKSURFACES, sizeof( dmarkface_t ), sizeof( dmarkface32_t ), "markfaces", 0, (void **)&srcmodel.markfaces, &srcmodel.nummarkfaces }, +{ LUMP_EDGES, 0, MAX_MAP_EDGES, sizeof( dedge_t ), sizeof( dedge32_t ), "edges", 0, (void **)&srcmodel.edges, &srcmodel.numedges }, +{ LUMP_SURFEDGES, 0, MAX_MAP_SURFEDGES, sizeof( dsurfedge_t ), -1, "surfedges", 0, (void **)&srcmodel.surfedges, &srcmodel.numsurfedges }, +{ LUMP_MODELS, 1, MAX_MAP_MODELS, sizeof( dmodel_t ), -1, "models", CHECK_OVERFLOW, (void **)&srcmodel.submodels, &srcmodel.numsubmodels }, +}; + +static mlumpinfo_t extlumps[EXTRA_LUMPS] = +{ +{ LUMP_LIGHTVECS, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "deluxmaps", USE_EXTRAHEADER, (void **)&srcmodel.deluxdata, &srcmodel.deluxdatasize }, +{ LUMP_FACEINFO, 0, MAX_MAP_FACEINFO, sizeof( dfaceinfo_t ), -1, "faceinfos", CHECK_OVERFLOW|USE_EXTRAHEADER, (void **)&srcmodel.faceinfo, &srcmodel.numfaceinfo }, +{ LUMP_SHADOWMAP, 0, MAX_MAP_LIGHTING / 3, sizeof( byte ), -1, "shadowmap", USE_EXTRAHEADER, (void **)&srcmodel.shadowdata, &srcmodel.shadowdatasize }, +}; + +/* +=============================================================================== + + MAP PROCESSING + +=============================================================================== +*/ +/* +================= +Mod_LoadLump + +generic loader +================= +*/ +static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, int flags ) +{ + int version = ((dheader_t *)in)->version; + size_t numelems, real_entrysize; + char msg1[32], msg2[32]; + dlump_t *l = NULL; + + if( FBitSet( info->flags, USE_EXTRAHEADER )) + { + dextrahdr_t *header = (dextrahdr_t *)((byte *)in + sizeof( dheader_t )); + if( header->id != IDEXTRAHEADER || header->version != EXTRA_VERSION ) + return; + l = &header->lumps[info->lumpnumber]; + } + else + { + dheader_t *header = (dheader_t *)in; + l = &header->lumps[info->lumpnumber]; + } + + // lump is unused by engine for some reasons ? + if( !l || info->entrysize <= 0 || info->maxcount <= 0 ) + return; + + real_entrysize = info->entrysize; // default + + // analyze real entrysize + if( version == QBSP2_VERSION && info->entrysize32 > 0 ) + { + // always use alternate entrysize for BSP2 + real_entrysize = info->entrysize32; + } + else if( info->lumpnumber == LUMP_CLIPNODES && version != Q1BSP_VERSION ) + { + // never run this check for BSP29 because Arguire QBSP 'broken' clipnodes! + if(( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize ) >= MAX_MAP_CLIPNODES ) + { + real_entrysize = info->entrysize32; + SetBits( flags, LUMP_SILENT ); // shut up warning + } + } + + // bmodels not required the visibility + if( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY ) + SetBits( flags, LUMP_SILENT ); // shut up warning + + // fill the stats for world + if( FBitSet( flags, LUMP_SAVESTATS )) + { + stat->lumpname = info->loadname; + stat->entrysize = real_entrysize; + stat->maxcount = info->maxcount; + if( real_entrysize != 0 ) + stat->count = l->filelen / real_entrysize; + } + + Q_strncpy( msg1, info->loadname, sizeof( msg1 )); + Q_strncpy( msg2, info->loadname, sizeof( msg2 )); + msg2[0] = Q_toupper( msg2[0] ); // first letter in cap + + // lump is not present + if( l->filelen <= 0 ) + { + // don't warn about extra lumps - it's optional + if( !FBitSet( info->flags, USE_EXTRAHEADER )) + { + // some data array that may be optional + if( real_entrysize == sizeof( byte )) + { + if( !FBitSet( flags, LUMP_SILENT )) + { + MsgDev( D_WARN, "map ^2%s^7 has no %s\n", loadstat.name, msg1 ); + loadstat.numwarnings++; + } + } + else if( info->mincount > 0 ) + { + // it has the mincount and the lump is completely missed! + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_ERROR, "map ^2%s^7 has no %s\n", loadstat.name, msg1 ); + loadstat.numerrors++; + } + } + return; + } + + if( l->filelen % real_entrysize ) + { + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_ERROR, "Mod_Load%s: funny lump size\n", msg2 ); + loadstat.numerrors++; + return; + } + + numelems = l->filelen / real_entrysize; + + if( numelems < info->mincount ) + { + // it has the mincount and it's smaller than this limit + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_ERROR, "map ^2%s^7 has no %s\n", loadstat.name, msg1 ); + loadstat.numerrors++; + return; + } + + if( numelems > info->maxcount ) + { + // it has the maxcount and it's overflowed + if( FBitSet( info->flags, CHECK_OVERFLOW )) + { + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_ERROR, "map ^2%s^7 has too many %s\n", loadstat.name, msg1 ); + loadstat.numerrors++; + return; + } + else + { + // just throw warning + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_WARN, "map ^2%s^7 has too many %s\n", loadstat.name, msg1 ); + loadstat.numwarnings++; + } + } + + if( FBitSet( flags, LUMP_TESTONLY )) + return; // don't fill the intermediate struct + + // all checks are passed, store pointers + if( info->dataptr ) *info->dataptr = (void *)(in + l->fileofs); + if( info->count ) *info->count = numelems; +} + +/* +================ +Mod_ArrayUsage +================ +*/ +static int Mod_ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? (items * 100.0f / maxitems) : 0.0f; + + Con_Printf( "%-12s %7i/%-7i %8i/%-8i (%4.1f%%) ", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + + if( percentage > 99.9f ) + Con_Printf( "^1SIZE OVERFLOW!!!^7\n" ); + else if( percentage > 95.0f ) + Con_Printf( "^3SIZE DANGER!^7\n" ); + else if( percentage > 80.0f ) + Con_Printf( "^2VERY FULL!^7\n" ); + else Con_Printf( "\n" ); + + return items * itemsize; +} + +/* +================ +Mod_GlobUsage +================ +*/ +static int Mod_GlobUsage( const char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? (itemstorage * 100.0f / maxstorage) : 0.0f; + + Con_Printf( "%-15s %-12s %8i/%-8i (%4.1f%%) ", szItem, "[variable]", itemstorage, maxstorage, percentage ); + + if( percentage > 99.9f ) + Con_Printf( "^1SIZE OVERFLOW!!!^7\n" ); + else if( percentage > 95.0f ) + Con_Printf( "^3SIZE DANGER!^7\n" ); + else if( percentage > 80.0f ) + Con_Printf( "^2VERY FULL!^7\n" ); + else Con_Printf( "\n" ); + + return itemstorage; +} + +/* +============= +Mod_PrintWorldStats_f + +Dumps info about world +============= +*/ +void Mod_PrintWorldStats_f( void ) +{ + int i, totalmemory = 0; + model_t *w = worldmodel; + + if( !w || !w->numsubmodels ) + { + Con_Printf( "No map loaded\n" ); + return; + } + + Con_Printf( "\n" ); + Con_Printf( "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + Con_Printf( "------------ --------------- --------------- --------\n" ); + + for( i = 0; i < ARRAYSIZE( worldstats ); i++ ) + { + mlumpstat_t *stat = &worldstats[i]; + + if( !stat->lumpname || !stat->maxcount || !stat->count ) + continue; // unused or lump is empty + + if( stat->entrysize == sizeof( byte )) + totalmemory += Mod_GlobUsage( stat->lumpname, stat->count, stat->maxcount ); + else totalmemory += Mod_ArrayUsage( stat->lumpname, stat->count, stat->maxcount, stat->entrysize ); + } + + Con_Printf( "=== Total BSP file data space used: %s ===\n", Q_memprint( totalmemory )); + Con_Printf( "World size ( %g %g %g ) units\n", world.size[0], world.size[1], world.size[2] ); + Con_Printf( "Supports transparency world water: %s\n", FBitSet( world.flags, FWORLD_WATERALPHA ) ? "Yes" : "No" ); + Con_Printf( "Lighting: %s\n", FBitSet( w->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" ); + Con_Printf( "World total leafs: %d\n", worldmodel->numleafs + 1 ); + Con_Printf( "original name: ^1%s\n", worldmodel->name ); + Con_Printf( "internal name: %s\n", (world.message[0]) ? va( "^2%s", world.message ) : "none" ); + Con_Printf( "map compiler: %s\n", (world.compiler[0]) ? va( "^3%s", world.compiler ) : "unknown" ); + Con_Printf( "map editor: %s\n", (world.generator[0]) ? va( "^2%s", world.generator ) : "unknown" ); +} + +/* +=============================================================================== + + COMMON ROUTINES + +=============================================================================== +*/ +/* +=================== +Mod_DecompressVis +=================== +*/ +static void Mod_DecompressVis( dbspmodel_t *bmod, const byte *in, const byte *inend, byte *out, byte *outend ) +{ + byte *outstart = out; + int c; + + while( out < outend ) + { + if( in == inend ) + { + if( !bmod->vis_errors ) + { + MsgDev( D_WARN, "Mod_DecompressVis: input underrun (decompressed %i of %i output bytes)\n", + (int)(out - outstart), (int)(outend - outstart)); + bmod->vis_errors = true; + } + return; + } + + c = *in++; + + if( c ) + { + *out++ = c; + } + else + { + if( in == inend ) + { + if( !bmod->vis_errors ) + { + MsgDev( D_NOTE, "Mod_DecompressVis: input underrun (during zero-run) (decompressed %i of %i output bytes)\n", + (int)(out - outstart), (int)(outend - outstart)); + bmod->vis_errors = true; + } + return; + } + + for( c = *in++; c > 0; c-- ) + { + if( out == outend ) + { + if( !bmod->vis_errors ) + { + MsgDev( D_NOTE, "Mod_DecompressVis: output overrun (decompressed %i of %i output bytes)\n", + (int)(out - outstart), (int)(outend - outstart)); + bmod->vis_errors = true; + } + return; + } + *out++ = 0; + } + } + } +} + +/* +================== +Mod_PointInLeaf + +================== +*/ +mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ) +{ + Assert( node != NULL ); + + while( 1 ) + { + if( node->contents < 0 ) + return (mleaf_t *)node; + node = node->children[PlaneDiff( p, node->plane ) <= 0]; + } + + // never reached + return NULL; +} + +/* +================== +Mod_GetPVSForPoint + +Returns PVS data for a given point +NOTE: can return NULL +================== +*/ +byte *Mod_GetPVSForPoint( const vec3_t p ) +{ + mnode_t *node; + mleaf_t *leaf = NULL; + + ASSERT( worldmodel != NULL ); + + node = worldmodel->nodes; + + while( 1 ) + { + if( node->contents < 0 ) + { + leaf = (mleaf_t *)node; + break; // we found a leaf + } + node = node->children[PlaneDiff( p, node->plane ) <= 0]; + } + + if( leaf && leaf->cluster >= 0 ) + return world.visdata + leaf->cluster * world.visbytes; + return NULL; +} + +/* +================== +Mod_FatPVS_RecursiveBSPNode + +================== +*/ +static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node ) +{ + int i; + + while( node->contents >= 0 ) + { + float d = PlaneDiff( org, node->plane ); + + if( d > radius ) + node = node->children[0]; + else if( d < -radius ) + node = node->children[1]; + else + { + // go down both sides + Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0] ); + node = node->children[1]; + } + } + + // if this leaf is in a cluster, accumulate the vis bits + if(((mleaf_t *)node)->cluster >= 0 ) + { + byte *vis = world.visdata + ((mleaf_t *)node)->cluster * world.visbytes; + + for( i = 0; i < visbytes; i++ ) + visbuffer[i] |= vis[i]; + } +} + +/* +================== +Mod_FatPVS_RecursiveBSPNode + +Calculates a PVS that is the inclusive or of all leafs +within radius pixels of the given point. +================== +*/ +int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis ) +{ + int bytes = world.visbytes; + mleaf_t *leaf = NULL; + + ASSERT( worldmodel != NULL ); + + leaf = Mod_PointInLeaf( org, worldmodel->nodes ); + bytes = Q_min( bytes, visbytes ); + + // enable full visibility for some reasons + if( fullvis || !world.visclusters || !leaf || leaf->cluster < 0 ) + { + memset( visbuffer, 0xFF, bytes ); + return bytes; + } + + if( !merge ) memset( visbuffer, 0x00, bytes ); + + Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes ); + + return bytes; +} + +/* +====================================================================== + +LEAF LISTING + +====================================================================== +*/ +static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node ) +{ + int sides; + + while( 1 ) + { + if( node->contents == CONTENTS_SOLID ) + return; + + if( node->contents < 0 ) + { + mleaf_t *leaf = (mleaf_t *)node; + + // it's a leaf! + if( ll->count >= ll->maxcount ) + { + ll->overflowed = true; + return; + } + + ll->list[ll->count++] = leaf->cluster; + return; + } + + sides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane ); + + if( sides == 1 ) + { + node = node->children[0]; + } + else if( sides == 2 ) + { + node = node->children[1]; + } + else + { + // go down both + if( ll->topnode == -1 ) + ll->topnode = node - worldmodel->nodes; + Mod_BoxLeafnums_r( ll, node->children[0] ); + node = node->children[1]; + } + } +} + +/* +================== +Mod_BoxLeafnums +================== +*/ +static int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, short *list, int listsize, int *topnode ) +{ + leaflist_t ll; + + if( !worldmodel ) return 0; + + VectorCopy( mins, ll.mins ); + VectorCopy( maxs, ll.maxs ); + + ll.maxcount = listsize; + ll.overflowed = false; + ll.topnode = -1; + ll.list = list; + ll.count = 0; + + Mod_BoxLeafnums_r( &ll, worldmodel->nodes ); + + if( topnode ) *topnode = ll.topnode; + return ll.count; +} + +/* +============= +Mod_BoxVisible + +Returns true if any leaf in boxspace +is potentially visible +============= +*/ +qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits ) +{ + short leafList[MAX_BOX_LEAFS]; + int i, count; + + if( !visbits || !mins || !maxs ) + return true; + + count = Mod_BoxLeafnums( mins, maxs, leafList, MAX_BOX_LEAFS, NULL ); + + for( i = 0; i < count; i++ ) + { + if( CHECKVISBIT( visbits, leafList[i] )) + return true; + } + return false; +} + +/* +============= +Mod_HeadnodeVisible +============= +*/ +qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf ) +{ + if( !node || node->contents == CONTENTS_SOLID ) + return false; + + if( node->contents < 0 ) + { + if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster )) + return false; + + if( lastleaf ) + *lastleaf = ((mleaf_t *)node)->cluster; + return true; + } + + if( Mod_HeadnodeVisible( node->children[0], visbits, lastleaf )) + return true; + + if( Mod_HeadnodeVisible( node->children[1], visbits, lastleaf )) + return true; + + return false; +} + +/* +================== +Mod_AmbientLevels + +grab the ambient sound levels for current point +================== +*/ +void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ) +{ + mleaf_t *leaf; + + if( !worldmodel || !p || !pvolumes ) + return; + + leaf = Mod_PointInLeaf( p, worldmodel->nodes ); + *(int *)pvolumes = *(int *)leaf->ambient_sound_level; +} + +/* +================= +Mod_FindModelOrigin + +routine to detect bmodels with origin-brush +================= +*/ +static void Mod_FindModelOrigin( const char *entities, const char *modelname, vec3_t origin ) +{ + char *pfile; + string keyname; + char token[2048]; + qboolean model_found; + qboolean origin_found; + + if( !entities || !modelname || !*modelname ) + return; + + if( !origin || !VectorIsNull( origin )) + return; + + pfile = (char *)entities; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( token[0] != '{' ) + Host_Error( "Mod_FindModelOrigin: found %s when expecting {\n", token ); + + model_found = origin_found = false; + VectorClear( origin ); + + while( 1 ) + { + // parse key + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) + Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" ); + if( token[0] == '}' ) break; // end of desc + + Q_strncpy( keyname, token, sizeof( keyname )); + + // parse value + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) + Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" ); + + if( token[0] == '}' ) + Host_Error( "Mod_FindModelOrigin: closing brace without data\n" ); + + if( !Q_stricmp( keyname, "model" ) && !Q_stricmp( modelname, token )) + model_found = true; + + if( !Q_stricmp( keyname, "origin" )) + { + Q_atov( origin, token, 3 ); + origin_found = true; + } + } + + if( model_found ) break; + } +} + +/* +================== +Mod_CheckWaterAlphaSupport + +converted maps potential may don't +support water transparency +================== +*/ +static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod ) +{ + mleaf_t *leaf; + int i, j; + const byte *pvs; + + if( bmod->visdatasize <= 0 ) + return true; + + // check all liquid leafs to see if they can see into empty leafs, if any + // can we can assume this map supports r_wateralpha + for( i = 0, leaf = loadmodel->leafs; i < loadmodel->numleafs; i++, leaf++ ) + { + if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 ) + { + pvs = world.visdata + leaf->cluster * world.visbytes; + + for( j = 0; j < loadmodel->numleafs; j++ ) + { + if( CHECKVISBIT( pvs, loadmodel->leafs[j].cluster ) && loadmodel->leafs[j].contents == CONTENTS_EMPTY ) + return true; + } + } + } + + return false; +} + +/* +================== +Mod_SampleSizeForFace + +return the current lightmap resolution per face +================== +*/ +int Mod_SampleSizeForFace( msurface_t *surf ) +{ + if( !surf || !surf->texinfo ) + return LM_SAMPLE_SIZE; + + // world luxels has more priority + if( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS )) + return 1; + + if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) + return LM_SAMPLE_EXTRASIZE; + + if( surf->texinfo->faceinfo ) + return surf->texinfo->faceinfo->texture_step; + + return LM_SAMPLE_SIZE; +} + +/* +================== +Mod_MakeNormalAxial + +remove jitter from near-axial normals +================== +*/ +static void Mod_MakeNormalAxial( vec3_t normal ) +{ + int i, type; + + for( type = 0; type < 3; type++ ) + { + if( fabs( normal[type] ) > 0.9999f ) + break; + } + + // make positive and pure axial + for( i = 0; i < 3 && type != 3; i++ ) + { + if( i == type ) + normal[i] = 1.0f; + else normal[i] = 0.0f; + } +} + +/* +================== +Mod_LightMatrixFromTexMatrix + +compute lightmap matrix based on texture matrix +================== +*/ +static void Mod_LightMatrixFromTexMatrix( const mtexinfo_t *tx, float lmvecs[2][4] ) +{ + float lmscale = LM_SAMPLE_SIZE; + int i, j; + + // this is can't be possible but who knews + if( FBitSet( tx->flags, TEX_EXTRA_LIGHTMAP )) + lmscale = LM_SAMPLE_EXTRASIZE; + + if( tx->faceinfo ) + lmscale = tx->faceinfo->texture_step; + + // copy texmatrix into lightmap matrix fisrt + for( i = 0; i < 2; i++ ) + { + for( j = 0; j < 4; j++ ) + { + lmvecs[i][j] = tx->vecs[i][j]; + } + } + + if( !FBitSet( tx->flags, TEX_WORLD_LUXELS )) + return; // just use texmatrix + + VectorNormalize( lmvecs[0] ); + VectorNormalize( lmvecs[1] ); + + if( FBitSet( tx->flags, TEX_AXIAL_LUXELS )) + { + Mod_MakeNormalAxial( lmvecs[0] ); + Mod_MakeNormalAxial( lmvecs[1] ); + } + + // put the lighting origin at center the of poly + VectorScale( lmvecs[0], (1.0 / lmscale), lmvecs[0] ); + VectorScale( lmvecs[1], -(1.0 / lmscale), lmvecs[1] ); + + lmvecs[0][3] = lmscale * 0.5; + lmvecs[1][3] = -lmscale * 0.5; +} + +/* +================= +Mod_CalcSurfaceExtents + +Fills in surf->texturemins[] and surf->extents[] +================= +*/ +static void Mod_CalcSurfaceExtents( msurface_t *surf ) +{ + float mins[2], maxs[2], val; + float lmmins[2], lmmaxs[2]; + int bmins[2], bmaxs[2]; + int i, j, e, sample_size; + mextrasurf_t *info = surf->info; + int facenum = surf - loadmodel->surfaces; + mtexinfo_t *tex; + mvertex_t *v; + + sample_size = Mod_SampleSizeForFace( surf ); + tex = surf->texinfo; + + Mod_LightMatrixFromTexMatrix( tex, info->lmvecs ); + + mins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999; + maxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[1] =-999999; + + for( i = 0; i < surf->numedges; i++ ) + { + e = loadmodel->surfedges[surf->firstedge + i]; + + if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) + Host_Error( "Mod_CalcSurfaceExtents: bad edge\n" ); + + if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + + for( j = 0; j < 2; j++ ) + { + val = DotProduct( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3]; + mins[j] = Q_min( val, mins[j] ); + maxs[j] = Q_max( val, maxs[j] ); + } + + for( j = 0; j < 2; j++ ) + { + val = DotProduct( v->position, info->lmvecs[j] ) + info->lmvecs[j][3]; + lmmins[j] = Q_min( val, lmmins[j] ); + lmmaxs[j] = Q_max( val, lmmaxs[j] ); + } + } + + for( i = 0; i < 2; i++ ) + { + bmins[i] = floor( mins[i] / sample_size ); + bmaxs[i] = ceil( maxs[i] / sample_size ); + + surf->texturemins[i] = bmins[i] * sample_size; + surf->extents[i] = (bmaxs[i] - bmins[i]) * sample_size; + + if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) + { + lmmins[i] = floor( lmmins[i] ); + lmmaxs[i] = ceil( lmmaxs[i] ); + + info->lightmapmins[i] = lmmins[i]; + info->lightextents[i] = (lmmaxs[i] - lmmins[i]); + } + else + { + // just copy texturemins + info->lightmapmins[i] = surf->texturemins[i]; + info->lightextents[i] = surf->extents[i]; + } + + if( !FBitSet( tex->flags, TEX_SPECIAL ) && surf->extents[i] > 4096 ) + MsgDev( D_ERROR, "Bad surface extents %i\n", surf->extents[i] ); + } +} + +/* +================= +Mod_CalcSurfaceBounds + +fills in surf->mins and surf->maxs +================= +*/ +static void Mod_CalcSurfaceBounds( msurface_t *surf ) +{ + int i, e; + mvertex_t *v; + + ClearBounds( surf->info->mins, surf->info->maxs ); + + for( i = 0; i < surf->numedges; i++ ) + { + e = loadmodel->surfedges[surf->firstedge + i]; + + if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) + Host_Error( "Mod_CalcSurfaceBounds: bad edge\n" ); + + if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; + else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + AddPointToBounds( v->position, surf->info->mins, surf->info->maxs ); + } + + VectorAverage( surf->info->mins, surf->info->maxs, surf->info->origin ); +} + +/* +================= +Mod_SetParent +================= +*/ +static void Mod_SetParent( mnode_t *node, mnode_t *parent ) +{ + node->parent = parent; + + if( node->contents < 0 ) return; // it's leaf + Mod_SetParent( node->children[0], node ); + Mod_SetParent( node->children[1], node ); +} + +/* +================== +CountClipNodes_r +================== +*/ +static void CountClipNodes_r( dclipnode32_t *src, hull_t *hull, int nodenum ) +{ + // leaf? + if( nodenum < 0 ) return; + + if( hull->lastclipnode == MAX_MAP_CLIPNODES ) + Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" ); + hull->lastclipnode++; + + CountClipNodes_r( src, hull, src[nodenum].children[0] ); + CountClipNodes_r( src, hull, src[nodenum].children[1] ); +} + +/* +================== +RemapClipNodes_r +================== +*/ +static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum ) +{ + dclipnode32_t *src; + mclipnode_t *out; + int i, c; + + // leaf? + if( nodenum < 0 ) + return nodenum; + + // emit a clipnode + if( hull->lastclipnode == MAX_MAP_CLIPNODES ) + Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" ); + src = srcnodes + nodenum; + + c = hull->lastclipnode; + out = &hull->clipnodes[c]; + hull->lastclipnode++; + + out->planenum = src->planenum; + + for( i = 0; i < 2; i++ ) + out->children[i] = RemapClipNodes_r( srcnodes, hull, src->children[i] ); + + return c; +} + +/* +================= +Mod_MakeHull0 + +Duplicate the drawing hull structure as a clipping hull +================= +*/ +static void Mod_MakeHull0( void ) +{ + mnode_t *in, *child; + mclipnode_t *out; + hull_t *hull; + int i, j; + + hull = &loadmodel->hulls[0]; + hull->clipnodes = out = Mem_Alloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out )); + in = loadmodel->nodes; + + hull->firstclipnode = 0; + hull->lastclipnode = loadmodel->numnodes - 1; + hull->planes = loadmodel->planes; + + for( i = 0; i < loadmodel->numnodes; i++, out++, in++ ) + { + out->planenum = in->plane - loadmodel->planes; + + for( j = 0; j < 2; j++ ) + { + child = in->children[j]; + + if( child->contents < 0 ) + out->children[j] = child->contents; + else out->children[j] = child - loadmodel->nodes; + } + } +} + +/* +================= +Mod_SetupHull +================= +*/ +static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, byte *mempool, int headnode, int hullnum ) +{ + hull_t *hull = &mod->hulls[hullnum]; + int count; + + // assume no hull + hull->firstclipnode = hull->lastclipnode = 0; + hull->planes = NULL; // hull is missed + + if(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 )) + return; // hull missed + + if( headnode >= mod->numclipnodes ) + return; // ZHLT weird empty hulls + + switch( hullnum ) + { + case 1: + VectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull + VectorCopy( host.player_maxs[0], hull->clip_maxs ); + break; + case 2: + VectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull + VectorCopy( host.player_maxs[3], hull->clip_maxs ); + break; + case 3: + VectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull + VectorCopy( host.player_maxs[1], hull->clip_maxs ); + break; + default: + Host_Error( "Mod_SetupHull: bad hull number %i\n", hullnum ); + break; + } + + if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs )) + return; // no hull specified + + CountClipNodes_r( bmod->clipnodes_out, hull, headnode ); + count = hull->lastclipnode; + + // fit array to real count + hull->clipnodes = (mclipnode_t *)Mem_Alloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode ); + hull->planes = mod->planes; // share planes + hull->lastclipnode = 0; // restart counting + + // remap clipnodes to 16-bit indexes + RemapClipNodes_r( bmod->clipnodes_out, hull, headnode ); +} + +/* +================= +Mod_LoadColoredLighting +================= +*/ +static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) +{ + char modelname[64]; + char path[64]; + int iCompare; + size_t litdatasize; + byte *in; + + COM_FileBase( loadmodel->name, modelname ); + Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname ); + + // make sure what deluxemap is actual + if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) + return false; + + if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg + MsgDev( D_WARN, "%s probably is out of date\n", path ); + + in = FS_LoadFile( path, &litdatasize, false ); + + Assert( in != NULL ); + + if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION ) + { + Mem_Free( in ); + return false; + } + + // skip header bytes + litdatasize -= 8; + + if( litdatasize != ( bmod->lightdatasize * 3 )) + { + Con_Printf( S_ERROR "%s has mismatched size (%i should be %i)\n", path, litdatasize, bmod->lightdatasize * 3 ); + Mem_Free( in ); + return false; + } + + loadmodel->lightdata = Mem_Alloc( loadmodel->mempool, litdatasize ); + memcpy( loadmodel->lightdata, in + 8, litdatasize ); + SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); + bmod->lightdatasize = litdatasize; + Mem_Free( in ); + + return true; +} + +/* +================= +Mod_LoadDeluxemap +================= +*/ +static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) +{ + char modelname[64]; + size_t deluxdatasize; + char path[64]; + int iCompare; + byte *in; + + if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) + return; + + COM_FileBase( loadmodel->name, modelname ); + Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname ); + + // make sure what deluxemap is actual + if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) + return; + + if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg + MsgDev( D_WARN, "%s probably is out of date\n", path ); + + in = FS_LoadFile( path, &deluxdatasize, false ); + + Assert( in != NULL ); + + if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION ) + { + Mem_Free( in ); + return; + } + + // skip header bytes + deluxdatasize -= 8; + + if( deluxdatasize != bmod->lightdatasize ) + { + MsgDev( D_ERROR, "%s has mismatched size (%i should be %i)\n", path, deluxdatasize, bmod->lightdatasize ); + Mem_Free( in ); + return; + } + + bmod->deluxedata_out = Mem_Alloc( loadmodel->mempool, deluxdatasize ); + memcpy( bmod->deluxedata_out, in + 8, deluxdatasize ); + bmod->deluxdatasize = deluxdatasize; + Mem_Free( in ); +} + +/* +================= +Mod_SetupSubmodels + +duplicate the basic information +for embedded submodels +================= +*/ +static void Mod_SetupSubmodels( dbspmodel_t *bmod ) +{ + byte *mempool; + char *ents; + model_t *mod; + dmodel_t *bm; + int i, j; + + ents = loadmodel->entities; + mempool = loadmodel->mempool; + mod = loadmodel; + + loadmodel->numframes = 2; // regular and alternate animation + + // set up the submodels + for( i = 0; i < mod->numsubmodels; i++ ) + { + bm = &mod->submodels[i]; + + // hull 0 is just shared across all bmodels + mod->hulls[0].firstclipnode = bm->headnode[0]; + + // but hulls1-3 is build individually for a each given submodel + for( j = 1; j < MAX_MAP_HULLS; j++ ) + Mod_SetupHull( bmod, mod, mempool, bm->headnode[j], j ); + + mod->firstmodelsurface = bm->firstface; + mod->nummodelsurfaces = bm->numfaces; + + VectorCopy( bm->mins, mod->mins ); + VectorCopy( bm->maxs, mod->maxs ); + + mod->radius = RadiusFromBounds( mod->mins, mod->maxs ); + mod->numleafs = bm->visleafs; +// mod->flags = 0; + + if( i != 0 ) + { + Mod_FindModelOrigin( ents, va( "*%i", i ), bm->origin ); + + // mark models that have origin brushes + if( !VectorIsNull( bm->origin )) + SetBits( mod->flags, MODEL_HAS_ORIGIN ); +#ifdef HACKS_RELATED_HLMODS + // c2a1 doesn't have origin brush it's just placed at center of the level + if( !Q_stricmp( loadmodel->name, "maps/c2a1.bsp" ) && ( i == 11 )) + SetBits( mod->flags, MODEL_HAS_ORIGIN ); +#endif + } + + // sets the model flags + for( j = 0; i != 0 && j < mod->nummodelsurfaces; j++ ) + { + msurface_t *surf = mod->surfaces + mod->firstmodelsurface + j; + + if( FBitSet( surf->flags, SURF_CONVEYOR )) + SetBits( mod->flags, MODEL_CONVEYOR ); + + if( FBitSet( surf->flags, SURF_TRANSPARENT )) + SetBits( mod->flags, MODEL_TRANSPARENT ); + + if( FBitSet( surf->flags, SURF_DRAWTURB )) + SetBits( mod->flags, MODEL_LIQUID ); + } + + if( i < mod->numsubmodels - 1 ) + { + char name[8]; + + // duplicate the basic information + Q_snprintf( name, sizeof( name ), "*%i", i + 1 ); + loadmodel = Mod_FindName( name, true ); + *loadmodel = *mod; + Q_strncpy( loadmodel->name, name, sizeof( loadmodel->name )); + loadmodel->mempool = NULL; + mod = loadmodel; + } + } + + Mem_Free( bmod->clipnodes_out ); +} + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ +/* +================= +Mod_LoadSubmodels +================= +*/ +static void Mod_LoadSubmodels( dbspmodel_t *bmod ) +{ + dmodel_t *in, *out; + int oldmaxfaces; + int i, j; + + // allocate extradata for each dmodel_t + out = Mem_Alloc( loadmodel->mempool, bmod->numsubmodels * sizeof( *out )); + + loadmodel->numsubmodels = bmod->numsubmodels; + loadmodel->submodels = out; + in = bmod->submodels; + + if( bmod->isworld ) + world.max_surfaces = 0; + oldmaxfaces = world.max_surfaces; + + for( i = 0; i < bmod->numsubmodels; i++, in++, out++ ) + { + for( j = 0; j < 3; j++ ) + { + // spread the mins / maxs by a unit + out->mins[j] = in->mins[j] - 1.0f; + out->maxs[j] = in->maxs[j] + 1.0f; + out->origin[j] = in->origin[j]; + } + + for( j = 0; j < MAX_MAP_HULLS; j++ ) + out->headnode[j] = in->headnode[j]; + + out->visleafs = in->visleafs; + out->firstface = in->firstface; + out->numfaces = in->numfaces; + + if( i == 0 && bmod->isworld ) + continue; // skip the world to save mem + oldmaxfaces = Q_max( oldmaxfaces, out->numfaces ); + } + + // these array used to sort translucent faces in bmodels + if( oldmaxfaces > world.max_surfaces ) + { + world.draw_surfaces = (sortedface_t *)Z_Realloc( world.draw_surfaces, oldmaxfaces * sizeof( sortedface_t )); + world.max_surfaces = oldmaxfaces; + } +} + +/* +================= +Mod_LoadEntities +================= +*/ +static void Mod_LoadEntities( dbspmodel_t *bmod ) +{ + byte *entpatch = NULL; + char token[MAX_TOKEN]; + char wadstring[2048]; + string keyname; + char *pfile; + + if( bmod->isworld ) + { + char entfilename[MAX_QPATH]; + long entpatchsize; + size_t ft1, ft2; + + // world is check for entfile too + Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename )); + COM_ReplaceExtension( entfilename, ".ent" ); + + // make sure what entity patch is never than bsp + ft1 = FS_FileTime( loadmodel->name, false ); + ft2 = FS_FileTime( entfilename, true ); + + if( ft2 != -1 ) + { + if( ft1 > ft2 ) + { + Con_Printf( S_WARN "Entity patch is older than bsp. Ignored.\n", entfilename ); + } + else if(( entpatch = FS_LoadFile( entfilename, &entpatchsize, true )) != NULL ) + { + Con_Printf( "^2Read entity patch:^7 %s\n", entfilename ); + bmod->entdatasize = entpatchsize; + bmod->entdata = entpatch; + } + } + } + + // make sure what we really has terminator + loadmodel->entities = Mem_Alloc( loadmodel->mempool, bmod->entdatasize + 1 ); + memcpy( loadmodel->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool + if( entpatch ) Mem_Free( entpatch ); // release entpatch if present + if( !bmod->isworld ) return; + + pfile = (char *)loadmodel->entities; + world.generator[0] = '\0'; + world.compiler[0] = '\0'; + world.message[0] = '\0'; + bmod->wadlist.count = 0; + + // parse all the wads for loading textures in right ordering + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( token[0] != '{' ) + Host_Error( "Mod_LoadEntities: found %s when expecting {\n", token ); + + while( 1 ) + { + // parse key + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) + Host_Error( "Mod_LoadEntities: EOF without closing brace\n" ); + if( token[0] == '}' ) break; // end of desc + + Q_strncpy( keyname, token, sizeof( keyname )); + + // parse value + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) + Host_Error( "Mod_LoadEntities: EOF without closing brace\n" ); + + if( token[0] == '}' ) + Host_Error( "Mod_LoadEntities: closing brace without data\n" ); + + if( !Q_stricmp( keyname, "wad" )) + { + char *pszWadFile; + + Q_strncpy( wadstring, token, MAX_TOKEN - 2 ); + wadstring[MAX_TOKEN - 2] = 0; + + if( !Q_strchr( wadstring, ';' )) + Q_strcat( wadstring, ";" ); + + // parse wad pathes + for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" )) + { + COM_FixSlashes( pszWadFile ); + COM_FileBase( pszWadFile, token ); + + // make sure what wad is really exist + if( FS_FileExists( va( "%s.wad", token ), false )) + { + int num = bmod->wadlist.count++; + Q_strncpy( bmod->wadlist.wadnames[num], token, sizeof( bmod->wadlist.wadnames[0] )); + } + + if( bmod->wadlist.count >= MAX_MAP_WADS ) + break; // too many wads... + } + } + else if( !Q_stricmp( keyname, "message" )) + Q_strncpy( world.message, token, sizeof( world.message )); + else if( !Q_stricmp( keyname, "compiler" ) || !Q_stricmp( keyname, "_compiler" )) + Q_strncpy( world.compiler, token, sizeof( world.compiler )); + else if( !Q_stricmp( keyname, "generator" ) || !Q_stricmp( keyname, "_generator" )) + Q_strncpy( world.generator, token, sizeof( world.generator )); + } + return; // all done + } +} + +/* +================= +Mod_LoadPlanes +================= +*/ +static void Mod_LoadPlanes( dbspmodel_t *bmod ) +{ + dplane_t *in; + mplane_t *out; + int i, j; + + in = bmod->planes; + loadmodel->planes = out = Mem_Alloc( loadmodel->mempool, bmod->numplanes * sizeof( *out )); + loadmodel->numplanes = bmod->numplanes; + + for( i = 0; i < bmod->numplanes; i++, in++, out++ ) + { + for( j = 0; j < 3; j++ ) + { + out->normal[j] = in->normal[j]; + + if( out->normal[j] < 0.0f ) + SetBits( out->signbits, BIT( j )); + } + + if( VectorLength( out->normal ) < 0.5f ) + Con_Printf( S_ERROR "bad normal for plane #%i\n", i ); + + out->dist = in->dist; + out->type = in->type; + } +} + +/* +================= +Mod_LoadVertexes +================= +*/ +static void Mod_LoadVertexes( dbspmodel_t *bmod ) +{ + dvertex_t *in; + mvertex_t *out; + int i; + + in = bmod->vertexes; + out = loadmodel->vertexes = Mem_Alloc( loadmodel->mempool, bmod->numvertexes * sizeof( mvertex_t )); + loadmodel->numvertexes = bmod->numvertexes; + + if( bmod->isworld ) ClearBounds( world.mins, world.maxs ); + + for( i = 0; i < bmod->numvertexes; i++, in++, out++ ) + { + if( bmod->isworld ) + AddPointToBounds( in->point, world.mins, world.maxs ); + VectorCopy( in->point, out->position ); + } + + if( !bmod->isworld ) return; + + VectorSubtract( world.maxs, world.mins, world.size ); + + for( i = 0; i < 3; i++ ) + { + // spread the mins / maxs by a pixel + world.mins[i] -= 1.0f; + world.maxs[i] += 1.0f; + } +} + +/* +================= +Mod_LoadEdges +================= +*/ +static void Mod_LoadEdges( dbspmodel_t *bmod ) +{ + medge_t *out; + int i; + + loadmodel->edges = out = Mem_Alloc( loadmodel->mempool, bmod->numedges * sizeof( medge_t )); + loadmodel->numedges = bmod->numedges; + + if( bmod->version == QBSP2_VERSION ) + { + dedge32_t *in = (dedge32_t *)bmod->edges32; + + for( i = 0; i < bmod->numedges; i++, in++, out++ ) + { + out->v[0] = in->v[0]; + out->v[1] = in->v[1]; + } + } + else + { + dedge_t *in = (dedge_t *)bmod->edges; + + for( i = 0; i < bmod->numedges; i++, in++, out++ ) + { + out->v[0] = (word)in->v[0]; + out->v[1] = (word)in->v[1]; + } + } +} + +/* +================= +Mod_LoadSurfEdges +================= +*/ +static void Mod_LoadSurfEdges( dbspmodel_t *bmod ) +{ + loadmodel->surfedges = Mem_Alloc( loadmodel->mempool, bmod->numsurfedges * sizeof( dsurfedge_t )); + memcpy( loadmodel->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t )); + loadmodel->numsurfedges = bmod->numsurfedges; +} + +/* +================= +Mod_LoadMarkSurfaces +================= +*/ +static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) +{ + msurface_t **out; + int i; + + loadmodel->marksurfaces = out = Mem_Alloc( loadmodel->mempool, bmod->nummarkfaces * sizeof( *out )); + loadmodel->nummarksurfaces = bmod->nummarkfaces; + + if( bmod->version == QBSP2_VERSION ) + { + dmarkface32_t *in = bmod->markfaces32; + + for( i = 0; i < bmod->nummarkfaces; i++, in++ ) + { + if( *in < 0 || *in >= loadmodel->numsurfaces ) + Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); + out[i] = loadmodel->surfaces + *in; + } + } + else + { + dmarkface_t *in = bmod->markfaces; + + for( i = 0; i < bmod->nummarkfaces; i++, in++ ) + { + if( *in < 0 || *in >= loadmodel->numsurfaces ) + Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); + out[i] = loadmodel->surfaces + *in; + } + } +} + +/* +================= +Mod_LoadTextures +================= +*/ +static void Mod_LoadTextures( dbspmodel_t *bmod ) +{ + dmiptexlump_t *in; + texture_t *tx, *tx2; + texture_t *anims[10]; + texture_t *altanims[10]; + int num, max, altmax; + qboolean custom_palette; + char texname[64]; + imgfilter_t *filter; + mip_t *mt; + int i, j; + + if( bmod->isworld ) + { + // release old sky layers first + GL_FreeTexture( tr.solidskyTexture ); + GL_FreeTexture( tr.alphaskyTexture ); + tr.solidskyTexture = 0; + tr.alphaskyTexture = 0; + } + + if( !bmod->texdatasize ) + { + // no textures + loadmodel->textures = NULL; + return; + } + + in = bmod->textures; + loadmodel->textures = (texture_t **)Mem_Alloc( loadmodel->mempool, in->nummiptex * sizeof( texture_t* )); + loadmodel->numtextures = in->nummiptex; + + for( i = 0; i < loadmodel->numtextures; i++ ) + { + if( in->dataofs[i] == -1 ) + { + // create default texture (some mods requires this) + tx = Mem_Alloc( loadmodel->mempool, sizeof( *tx )); + loadmodel->textures[i] = tx; + + Q_strncpy( tx->name, "*default", sizeof( tx->name )); + tx->gl_texturenum = tr.defaultTexture; + tx->width = tx->height = 16; + continue; // missed + } + + mt = (mip_t *)((byte *)in + in->dataofs[i] ); + + if( !mt->name[0] ) + { + MsgDev( D_WARN, "unnamed texture in %s\n", loadstat.name ); + Q_snprintf( mt->name, sizeof( mt->name ), "miptex_%i", i ); + } + + tx = Mem_Alloc( loadmodel->mempool, sizeof( *tx )); + loadmodel->textures[i] = tx; + + // convert to lowercase + Q_strncpy( tx->name, mt->name, sizeof( tx->name )); + Q_strnlwr( tx->name, tx->name, sizeof( tx->name )); + filter = R_FindTexFilter( tx->name ); // grab texture filter + custom_palette = false; + + tx->width = mt->width; + tx->height = mt->height; + + if( mt->offsets[0] > 0 ) + { + int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); + int next_dataofs, remaining; + + // compute next dataofset to determine allocated miptex sapce + for( j = i + 1; j < loadmodel->numtextures; j++ ) + { + next_dataofs = in->dataofs[j]; + if( next_dataofs != -1 ) break; + } + + if( j == loadmodel->numtextures ) + next_dataofs = bmod->texdatasize; + + // NOTE: imagelib detect miptex version by size + // 770 additional bytes is indicated custom palette + remaining = next_dataofs - (in->dataofs[i] + size); + if( remaining >= 770 ) custom_palette = true; + } + + // check for multi-layered sky texture (quake1 specific) + if( bmod->isworld && !Q_strncmp( mt->name, "sky", 3 ) && (( mt->width / mt->height ) == 2 )) + { + R_InitSkyClouds( mt, tx, custom_palette ); // load quake sky + + if( tr.solidskyTexture && tr.alphaskyTexture ) + SetBits( world.flags, FWORLD_SKYSPHERE ); + continue; + } + + // texture loading order: + // 1. from wad + // 2. internal from map + + // trying wad texture (force while r_wadtextures is 1) + if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || ( mt->offsets[0] <= 0 )) + { + Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name ); + + // check wads in reverse order + for( j = bmod->wadlist.count - 1; j >= 0; j-- ) + { + char *texpath = va( "%s.wad/%s", bmod->wadlist.wadnames[j], texname ); + + if( FS_FileExists( texpath, false )) + { + tx->gl_texturenum = GL_LoadTexture( texpath, NULL, 0, 0, filter ); + break; + } + } + } + + // wad failed, so use internal texture (if present) + if( mt->offsets[0] > 0 && !tx->gl_texturenum ) + { + // NOTE: imagelib detect miptex version by size + // 770 additional bytes is indicated custom palette + int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); + + if( custom_palette ) size += sizeof( short ) + 768; + Q_snprintf( texname, sizeof( texname ), "#%s:%s.mip", loadstat.name, mt->name ); + tx->gl_texturenum = GL_LoadTexture( texname, (byte *)mt, size, 0, filter ); + } + + // if texture is completely missed + if( !tx->gl_texturenum ) + { + if( host.type != HOST_DEDICATED ) + MsgDev( D_ERROR, "couldn't load %s.mip\n", mt->name ); + tx->gl_texturenum = tr.defaultTexture; + } + + // check for luma texture + if( FBitSet( R_GetTexture( tx->gl_texturenum )->flags, TF_HAS_LUMA )) + { + Q_snprintf( texname, sizeof( texname ), "#%s:%s_luma.mip", loadstat.name, mt->name ); + + if( mt->offsets[0] > 0 ) + { + // NOTE: imagelib detect miptex version by size + // 770 additional bytes is indicated custom palette + int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6); + + if( custom_palette ) size += sizeof( short ) + 768; + tx->fb_texturenum = GL_LoadTexture( texname, (byte *)mt, size, TF_MAKELUMA, NULL ); + } + else + { + size_t srcSize = 0; + byte *src = NULL; + + // NOTE: we can't loading it from wad as normal because _luma texture doesn't exist + // and not be loaded. But original texture is already loaded and can't be modified + // So load original texture manually and convert it to luma + + // check wads in reverse order + for( j = bmod->wadlist.count - 1; j >= 0; j-- ) + { + char *texpath = va( "%s.wad/%s.mip", bmod->wadlist.wadnames[j], tx->name ); + + if( FS_FileExists( texpath, false )) + { + src = FS_LoadFile( texpath, &srcSize, false ); + break; + } + } + + // okay, loading it from wad or hi-res version + tx->fb_texturenum = GL_LoadTexture( texname, src, srcSize, TF_MAKELUMA, NULL ); + if( src ) Mem_Free( src ); + } + } + } + + // sequence the animations and detail textures + for( i = 0; i < loadmodel->numtextures; i++ ) + { + tx = loadmodel->textures[i]; + + if( !tx || ( tx->name[0] != '-' && tx->name[0] != '+' )) + continue; + + if( tx->anim_next ) + continue; // already sequenced + + // find the number of frames in the animation + memset( anims, 0, sizeof( anims )); + memset( altanims, 0, sizeof( altanims )); + + max = tx->name[1]; + altmax = 0; + + if( max >= '0' && max <= '9' ) + { + max -= '0'; + altmax = 0; + anims[max] = tx; + max++; + } + else if( max >= 'a' && max <= 'j' ) + { + altmax = max - 'a'; + max = 0; + altanims[altmax] = tx; + altmax++; + } + else MsgDev( D_ERROR, "Mod_LoadTextures: bad animating texture %s\n", tx->name ); + + for( j = i + 1; j < loadmodel->numtextures; j++ ) + { + tx2 = loadmodel->textures[j]; + + if( !tx2 || ( tx2->name[0] != '-' && tx2->name[0] != '+' )) + continue; + + if( Q_strcmp( tx2->name + 2, tx->name + 2 )) + continue; + + num = tx2->name[1]; + + if( num >= '0' && num <= '9' ) + { + num -= '0'; + anims[num] = tx2; + if( num + 1 > max ) + max = num + 1; + } + else if( num >= 'a' && num <= 'j' ) + { + num = num - 'a'; + altanims[num] = tx2; + if( num + 1 > altmax ) + altmax = num + 1; + } + else MsgDev( D_ERROR, "Mod_LoadTextures: bad animating texture %s\n", tx->name ); + } + + // link them all together + for( j = 0; j < max; j++ ) + { + tx2 = anims[j]; + + if( !tx2 ) + { + MsgDev( D_ERROR, "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); + tx->anim_total = 0; + break; + } + + tx2->anim_total = max * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j + 1) * ANIM_CYCLE; + tx2->anim_next = anims[(j + 1) % max]; + if( altmax ) tx2->alternate_anims = altanims[0]; + } + + for( j = 0; j < altmax; j++ ) + { + tx2 = altanims[j]; + + if( !tx2 ) + { + MsgDev( D_ERROR, "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name ); + tx->anim_total = 0; + break; + } + + tx2->anim_total = altmax * ANIM_CYCLE; + tx2->anim_min = j * ANIM_CYCLE; + tx2->anim_max = (j+1) * ANIM_CYCLE; + tx2->anim_next = altanims[(j + 1) % altmax]; + if( max ) tx2->alternate_anims = anims[0]; + } + } +} + +/* +================= +Mod_LoadTexInfo +================= +*/ +static void Mod_LoadTexInfo( dbspmodel_t *bmod ) +{ + mfaceinfo_t *fout, *faceinfo; + int i, j, miptex; + dfaceinfo_t *fin; + mtexinfo_t *out; + dtexinfo_t *in; + + // trying to load faceinfo + faceinfo = fout = Mem_Alloc( loadmodel->mempool, bmod->numfaceinfo * sizeof( *fout )); + fin = bmod->faceinfo; + + for( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ ) + { + Q_strncpy( fout->landname, fin->landname, sizeof( fout->landname )); + fout->texture_step = fin->texture_step; + fout->max_extent = fin->max_extent; + fout->groupid = fin->groupid; + } + + loadmodel->texinfo = out = Mem_Alloc( loadmodel->mempool, bmod->numtexinfo * sizeof( *out )); + loadmodel->numtexinfo = bmod->numtexinfo; + in = bmod->texinfo; + + for( i = 0; i < bmod->numtexinfo; i++, in++, out++ ) + { + for( j = 0; j < 8; j++ ) + out->vecs[0][j] = in->vecs[0][j]; + + miptex = in->miptex; + if( miptex < 0 || miptex > loadmodel->numtextures ) + { + MsgDev( D_WARN, "Mod_LoadTexInfo: bad miptex number %i in '%s'\n", miptex, loadmodel->name ); + miptex = 0; + } + + out->texture = loadmodel->textures[miptex]; + out->flags = in->flags; + + // make sure what faceinfo is really exist + if( faceinfo != NULL && in->faceinfo != -1 && in->faceinfo < bmod->numfaceinfo ) + out->faceinfo = &faceinfo[in->faceinfo]; + } +} + +/* +================= +Mod_LoadSurfaces +================= +*/ +static void Mod_LoadSurfaces( dbspmodel_t *bmod ) +{ + int test_lightsize = -1; + int next_lightofs = -1; + int prev_lightofs = -1; + int i, j, lightofs; + mextrasurf_t *info; + msurface_t *out; + + loadmodel->surfaces = out = Mem_Alloc( loadmodel->mempool, bmod->numsurfaces * sizeof( msurface_t )); + info = Mem_Alloc( loadmodel->mempool, bmod->numsurfaces * sizeof( mextrasurf_t )); + loadmodel->numsurfaces = bmod->numsurfaces; + + // predict samplecount based on bspversion + if( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION ) + bmod->lightmap_samples = 1; + else bmod->lightmap_samples = 3; + + for( i = 0; i < bmod->numsurfaces; i++, out++, info++ ) + { + texture_t *tex; + + // setup crosslinks between two parts of msurface_t + out->info = info; + info->surf = out; + + if( bmod->version == QBSP2_VERSION ) + { + dface32_t *in = &bmod->surfaces32[i]; + + if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + { + MsgDev( D_ERROR, "bad surface %i from %i\n", i, bmod->numsurfaces ); + continue; + } + + out->firstedge = in->firstedge; + out->numedges = in->numedges; + if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); + out->plane = loadmodel->planes + in->planenum; + out->texinfo = loadmodel->texinfo + in->texinfo; + + for( j = 0; j < MAXLIGHTMAPS; j++ ) + out->styles[j] = in->styles[j]; + lightofs = in->lightofs; + } + else + { + dface_t *in = &bmod->surfaces[i]; + + if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + { + MsgDev( D_ERROR, "bad surface %i from %i\n", i, bmod->numsurfaces ); + continue; + } + + out->firstedge = in->firstedge; + out->numedges = in->numedges; + if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); + out->plane = loadmodel->planes + in->planenum; + out->texinfo = loadmodel->texinfo + in->texinfo; + + for( j = 0; j < MAXLIGHTMAPS; j++ ) + out->styles[j] = in->styles[j]; + lightofs = in->lightofs; + } + + tex = out->texinfo->texture; + + if( !Q_strncmp( tex->name, "sky", 3 )) + SetBits( out->flags, SURF_DRAWTILED|SURF_DRAWSKY ); + + if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' ) + SetBits( out->flags, SURF_DRAWTURB|SURF_DRAWTILED ); + + if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + { + if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 )) + SetBits( out->flags, SURF_DRAWTURB|SURF_DRAWTILED ); + } + + if( !Q_strncmp( tex->name, "scroll", 6 )) + SetBits( out->flags, SURF_CONVEYOR ); + + // g-cont. added a combined conveyor-transparent + if( !Q_strncmp( tex->name, "{scroll", 7 )) + SetBits( out->flags, SURF_CONVEYOR|SURF_TRANSPARENT ); + + if( tex->name[0] == '{' ) + SetBits( out->flags, SURF_TRANSPARENT ); + + if( FBitSet( out->texinfo->flags, TEX_SPECIAL )) + SetBits( out->flags, SURF_DRAWTILED ); + + Mod_CalcSurfaceBounds( out ); + Mod_CalcSurfaceExtents( out ); + + // grab the second sample to detect colored lighting + if( test_lightsize > 0 && lightofs != -1 ) + { + if( lightofs > prev_lightofs && lightofs < next_lightofs ) + next_lightofs = lightofs; + } + + // grab the first sample to determine lightmap size + if( lightofs != -1 && test_lightsize == -1 ) + { + int sample_size = Mod_SampleSizeForFace( out ); + int smax = (info->lightextents[0] / sample_size) + 1; + int tmax = (info->lightextents[1] / sample_size) + 1; + int lightstyles = 0; + + test_lightsize = smax * tmax; + // count styles to right compute test_lightsize + for( j = 0; j < MAXLIGHTMAPS && out->styles[j] != 255; j++ ) + lightstyles++; + + test_lightsize *= lightstyles; + prev_lightofs = lightofs; + next_lightofs = 99999999; + } + + if( FBitSet( out->flags, SURF_DRAWTURB )) + GL_SubdivideSurface( out ); // cut up polygon for warps + } + + // now we have enough data to trying determine samplecount per lightmap pixel + if( test_lightsize > 0 && prev_lightofs != -1 && next_lightofs != -1 && next_lightofs != 99999999 ) + { + float samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize; + + if( samples != (int)samples ) + { + test_lightsize = (test_lightsize + 3) & ~3; // align datasize and try again + samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize; + } + + if( samples == 1 || samples == 3 ) + { + bmod->lightmap_samples = (int)samples; + MsgDev( D_REPORT, "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" ); + bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero + } + else MsgDev( D_WARN, "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples ); + } +} + +/* +================= +Mod_LoadNodes +================= +*/ +static void Mod_LoadNodes( dbspmodel_t *bmod ) +{ + mnode_t *out; + int i, j, p; + + loadmodel->nodes = out = (mnode_t *)Mem_Alloc( loadmodel->mempool, bmod->numnodes * sizeof( *out )); + loadmodel->numnodes = bmod->numnodes; + + for( i = 0; i < loadmodel->numnodes; i++, out++ ) + { + if( bmod->version == QBSP2_VERSION ) + { + dnode32_t *in = &bmod->nodes32[i]; + + for( j = 0; j < 3; j++ ) + { + out->minmaxs[j+0] = in->mins[j]; + out->minmaxs[j+3] = in->maxs[j]; + } + + p = in->planenum; + out->plane = loadmodel->planes + p; + out->firstsurface = in->firstface; + out->numsurfaces = in->numfaces; + + for( j = 0; j < 2; j++ ) + { + p = in->children[j]; + if( p >= 0 ) out->children[j] = loadmodel->nodes + p; + else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); + } + } + else + { + dnode_t *in = &bmod->nodes[i]; + + for( j = 0; j < 3; j++ ) + { + out->minmaxs[j+0] = in->mins[j]; + out->minmaxs[j+3] = in->maxs[j]; + } + + p = in->planenum; + out->plane = loadmodel->planes + p; + out->firstsurface = in->firstface; + out->numsurfaces = in->numfaces; + + for( j = 0; j < 2; j++ ) + { + p = in->children[j]; + if( p >= 0 ) out->children[j] = loadmodel->nodes + p; + else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); + } + } + } + + // sets nodes and leafs + Mod_SetParent( loadmodel->nodes, NULL ); +} + +/* +================= +Mod_LoadLeafs +================= +*/ +static void Mod_LoadLeafs( dbspmodel_t *bmod ) +{ + mleaf_t *out; + int i, j, p; + + loadmodel->leafs = out = (mleaf_t *)Mem_Alloc( loadmodel->mempool, bmod->numleafs * sizeof( *out )); + loadmodel->numleafs = bmod->numleafs; + + if( bmod->isworld ) + { + // get visleafs from the submodel data + world.visclusters = loadmodel->submodels[0].visleafs; + world.visbytes = (world.visclusters + 7) >> 3; + world.visdata = (byte *)Mem_Alloc( loadmodel->mempool, world.visclusters * world.visbytes ); + world.fatbytes = (world.visclusters + 31) >> 3; + + // enable full visibility as default + memset( world.visdata, 0xFF, world.visclusters * world.visbytes ); + } + + for( i = 0; i < bmod->numleafs; i++, out++ ) + { + if( bmod->version == QBSP2_VERSION ) + { + dleaf32_t *in = &bmod->leafs32[i]; + + for( j = 0; j < 3; j++ ) + { + out->minmaxs[j+0] = in->mins[j]; + out->minmaxs[j+3] = in->maxs[j]; + } + + out->contents = in->contents; + p = in->visofs; + + for( j = 0; j < 4; j++ ) + out->ambient_sound_level[j] = in->ambient_level[j]; + + out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->nummarksurfaces = in->nummarksurfaces; + } + else + { + dleaf_t *in = &bmod->leafs[i]; + + for( j = 0; j < 3; j++ ) + { + out->minmaxs[j+0] = in->mins[j]; + out->minmaxs[j+3] = in->maxs[j]; + } + + out->contents = in->contents; + p = in->visofs; + + for( j = 0; j < 4; j++ ) + out->ambient_sound_level[j] = in->ambient_level[j]; + + out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->nummarksurfaces = in->nummarksurfaces; + } + + if( bmod->isworld ) + { + out->cluster = ( i - 1 ); // solid leaf 0 has no visdata + + if( out->cluster >= world.visclusters ) + out->cluster = -1; + + // ignore visofs errors on leaf 0 (solid) + if( p >= 0 && out->cluster >= 0 && loadmodel->visdata ) + { + if( p < bmod->visdatasize ) + { + byte *inrow = loadmodel->visdata + p; + byte *inrowend = loadmodel->visdata + bmod->visdatasize; + byte *outrow = world.visdata + out->cluster * world.visbytes; + byte *outrowend = world.visdata + (out->cluster + 1) * world.visbytes; + + Mod_DecompressVis( bmod, inrow, inrowend, outrow, outrowend ); + } + else MsgDev( D_WARN, "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i ); + } + } + else out->cluster = -1; // no visclusters on bmodels + + if( p == -1 ) out->compressed_vis = NULL; + else out->compressed_vis = loadmodel->visdata + p; + + // gl underwater warp + if( out->contents != CONTENTS_EMPTY ) + { + for( j = 0; j < out->nummarksurfaces; j++ ) + { + // mark underwater surfaces + SetBits( out->firstmarksurface[j]->flags, SURF_UNDERWATER ); + } + } + } + + if( bmod->isworld && loadmodel->leafs[0].contents != CONTENTS_SOLID ) + Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", loadmodel->name ); + + // do some final things for world + if( bmod->isworld && Mod_CheckWaterAlphaSupport( bmod )) + SetBits( world.flags, FWORLD_WATERALPHA ); +} + +/* +================= +Mod_LoadClipnodes +================= +*/ +static void Mod_LoadClipnodes( dbspmodel_t *bmod ) +{ + dclipnode32_t *out; + int i; + + bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Alloc( loadmodel->mempool, bmod->numclipnodes * sizeof( *out )); + + if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->numclipnodes >= MAX_MAP_CLIPNODES )) + { + dclipnode32_t *in = bmod->clipnodes32; + + for( i = 0; i < bmod->numclipnodes; i++, out++, in++ ) + { + out->planenum = in->planenum; + out->children[0] = in->children[0]; + out->children[1] = in->children[1]; + } + } + else + { + dclipnode_t *in = bmod->clipnodes; + + for( i = 0; i < bmod->numclipnodes; i++, out++, in++ ) + { + out->planenum = in->planenum; + + out->children[0] = (unsigned short)in->children[0]; + out->children[1] = (unsigned short)in->children[1]; + + // Arguire QBSP 'broken' clipnodes + if( out->children[0] >= bmod->numclipnodes ) + out->children[0] -= 65536; + if( out->children[1] >= bmod->numclipnodes ) + out->children[1] -= 65536; + } + } + + // FIXME: fill loadmodel->clipnodes? + loadmodel->numclipnodes = bmod->numclipnodes; +} + +/* +================= +Mod_LoadVisibility +================= +*/ +static void Mod_LoadVisibility( dbspmodel_t *bmod ) +{ + loadmodel->visdata = Mem_Alloc( loadmodel->mempool, bmod->visdatasize ); + memcpy( loadmodel->visdata, bmod->visdata, bmod->visdatasize ); +} + +/* +================= +Mod_LoadLightVecs +================= +*/ +static void Mod_LoadLightVecs( dbspmodel_t *bmod ) +{ + if( bmod->deluxdatasize != bmod->lightdatasize ) + { + if( bmod->deluxdatasize > 0 ) + MsgDev( D_ERROR, "Mod_LoadLightVecs: has mismatched size (%i should be %i)\n", bmod->deluxdatasize, bmod->lightdatasize ); + else Mod_LoadDeluxemap( bmod ); // old method + return; + } + + bmod->deluxedata_out = Mem_Alloc( loadmodel->mempool, bmod->deluxdatasize ); + memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize ); +} + +/* +================= +Mod_LoadShadowmap +================= +*/ +static void Mod_LoadShadowmap( dbspmodel_t *bmod ) +{ + if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 )) + { + if( bmod->shadowdatasize > 0 ) + MsgDev( D_ERROR, "Mod_LoadShadowmap: has mismatched size (%i should be %i)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 ); + return; + } + + bmod->shadowdata_out = Mem_Alloc( loadmodel->mempool, bmod->shadowdatasize ); + memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize ); +} + +/* +================= +Mod_LoadLighting +================= +*/ +static void Mod_LoadLighting( dbspmodel_t *bmod ) +{ + int i, lightofs; + msurface_t *surf; + color24 *out; + byte *in; + + if( !bmod->lightdatasize ) + return; + + switch( bmod->lightmap_samples ) + { + case 1: + if( !Mod_LoadColoredLighting( bmod )) + { + loadmodel->lightdata = out = (color24 *)Mem_Alloc( loadmodel->mempool, bmod->lightdatasize * sizeof( color24 )); + in = bmod->lightdata; + + // expand the white lighting data + for( i = 0; i < bmod->lightdatasize; i++, out++ ) + out->r = out->g = out->b = *in++; + } + break; + case 3: // load colored lighting + loadmodel->lightdata = Mem_Alloc( loadmodel->mempool, bmod->lightdatasize ); + memcpy( loadmodel->lightdata, bmod->lightdata, bmod->lightdatasize ); + SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); + break; + default: + Host_Error( "Mod_LoadLighting: bad lightmap sample count %i\n", bmod->lightmap_samples ); + break; + } + + // not supposed to be load ? + if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) + { + Mod_LoadLightVecs( bmod ); + Mod_LoadShadowmap( bmod ); + + if( bmod->isworld && bmod->deluxdatasize ) + SetBits( world.flags, FWORLD_HAS_DELUXEMAP ); + } + + surf = loadmodel->surfaces; + + // setup lightdata pointers + for( i = 0; i < loadmodel->numsurfaces; i++, surf++ ) + { + if( bmod->version == QBSP2_VERSION ) + lightofs = bmod->surfaces32[i].lightofs; + else lightofs = bmod->surfaces[i].lightofs; + + if( loadmodel->lightdata && lightofs != -1 ) + { + int offset = (lightofs / bmod->lightmap_samples); + + // NOTE: we divide offset by three because lighting and deluxemap keep their pointers + // into three-bytes structs and shadowmap just monochrome + surf->samples = loadmodel->lightdata + offset; + + // if deluxemap is present setup it too + if( bmod->deluxedata_out ) + surf->info->deluxemap = bmod->deluxedata_out + offset; + + // will be used by mods + if( bmod->shadowdata_out ) + surf->info->shadowmap = bmod->shadowdata_out + offset; + } + } +} + +/* +================= +Mod_LoadBmodelLumps + +loading and processing bmodel +================= +*/ +qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) +{ + dheader_t *header = (dheader_t *)mod_base; + dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)mod_base + sizeof( dheader_t )); + dbspmodel_t *bmod = &srcmodel; + char wadvalue[2048]; + int i; + + // always reset the intermediate struct + memset( bmod, 0, sizeof( dbspmodel_t )); + memset( &loadstat, 0, sizeof( loadstat_t )); + + Q_strncpy( loadstat.name, loadmodel->name, sizeof( loadstat.name )); + wadvalue[0] = '\0'; + +#ifndef SUPPORT_BSP2_FORMAT + if( header->version == QBSP2_VERSION ) + { + Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, loadmodel->name ); + return false; + } +#endif + switch( header->version ) + { + case Q1BSP_VERSION: + case HLBSP_VERSION: + case QBSP2_VERSION: + break; + default: + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", loadmodel->name, header->version, HLBSP_VERSION ); + loadstat.numerrors++; + return false; + } + + bmod->version = header->version; // share up global + if( isworld ) world.flags = 0; // clear world settings + bmod->isworld = isworld; + + // loading base lumps + for( i = 0; i < ARRAYSIZE( srclumps ); i++ ) + Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 ); + + // loading extralumps + for( i = 0; i < ARRAYSIZE( extlumps ); i++ ) + Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 ); + + if( !bmod->isworld && loadstat.numerrors ) + { + Con_DPrintf( "Mod_Load%s: %i error(s), %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numerrors, loadstat.numwarnings ); + return false; // there were errors, we can't load this map + } + else if( !bmod->isworld && loadstat.numwarnings ) + Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings ); + + // load into heap + Mod_LoadEntities( bmod ); + Mod_LoadPlanes( bmod ); + Mod_LoadSubmodels( bmod ); + Mod_LoadVertexes( bmod ); + Mod_LoadEdges( bmod ); + Mod_LoadSurfEdges( bmod ); + Mod_LoadTextures( bmod ); + Mod_LoadVisibility( bmod ); + Mod_LoadTexInfo( bmod ); + Mod_LoadSurfaces( bmod ); + Mod_LoadLighting( bmod ); + Mod_LoadMarkSurfaces( bmod ); + Mod_LoadLeafs( bmod ); + Mod_LoadNodes( bmod ); + Mod_LoadClipnodes( bmod ); + + // preform some post-initalization + Mod_MakeHull0 (); + Mod_SetupSubmodels( bmod ); + + for( i = 0; i < bmod->wadlist.count; i++ ) + Q_strncat( wadvalue, va( "%s.wad; ", bmod->wadlist.wadnames[i] ), sizeof( wadvalue )); + + if( COM_CheckString( wadvalue )) + { + wadvalue[Q_strlen( wadvalue ) - 2] = '\0'; + Con_DPrintf( "Wad files required to run the map: \"%s\"\n", wadvalue ); + } + + return true; +} + +/* +================= +Mod_TestBmodelLumps + +check for possible errors +================= +*/ +qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean silent ) +{ + dheader_t *header = (dheader_t *)mod_base; + int i, flags = LUMP_TESTONLY; + + // always reset the intermediate struct + memset( &loadstat, 0, sizeof( loadstat_t )); + + // store the name to correct show errors and warnings + Q_strncpy( loadstat.name, name, sizeof( loadstat.name )); + if( silent ) SetBits( flags, LUMP_SILENT ); + +#ifndef SUPPORT_BSP2_FORMAT + if( header->version == QBSP2_VERSION ) + { + if( !FBitSet( flags, LUMP_SILENT )) + Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, name ); + return false; + } +#endif + switch( header->version ) + { + case Q1BSP_VERSION: + case HLBSP_VERSION: + case QBSP2_VERSION: + break; + default: + // don't early out: let me analyze errors + if( !FBitSet( flags, LUMP_SILENT )) + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", name, header->version, HLBSP_VERSION ); + loadstat.numerrors++; + break; + } + + // loading base lumps + for( i = 0; i < ARRAYSIZE( srclumps ); i++ ) + Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags ); + + // loading extralumps + for( i = 0; i < ARRAYSIZE( extlumps ); i++ ) + Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags ); + + if( loadstat.numerrors ) + { + if( !FBitSet( flags, LUMP_SILENT )) + Con_Printf( "Mod_LoadWorld: %i error(s), %i warning(s)\n", loadstat.numerrors, loadstat.numwarnings ); + return false; // there were errors, we can't load this map + } + else if( loadstat.numwarnings ) + { + if( !FBitSet( flags, LUMP_SILENT )) + Con_Printf( "Mod_LoadWorld: %i warning(s)\n", loadstat.numwarnings ); + } + + return true; +} + +/* +================= +Mod_LoadBrushModel +================= +*/ +void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ) +{ + if( loaded ) *loaded = false; + + loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); + loadmodel->type = mod_brush; + + // loading all the lumps into heap + if( !Mod_LoadBmodelLumps( buffer, world.loading )) + return; // there were errors + + if( world.loading ) worldmodel = mod; + + if( loaded ) *loaded = true; // all done +} + +/* +================= +Mod_UnloadBrushModel + +Release all uploaded textures +================= +*/ +void Mod_UnloadBrushModel( model_t *mod ) +{ + texture_t *tx; + int i; + + Assert( mod != NULL ); + + if( mod->type != mod_brush ) + return; // not a bmodel + + if( mod->name[0] != '*' ) + { + for( i = 0; i < mod->numtextures; i++ ) + { + tx = mod->textures[i]; + if( !tx || tx->gl_texturenum == tr.defaultTexture ) + continue; // free slot + + GL_FreeTexture( tx->gl_texturenum ); // main texture + GL_FreeTexture( tx->fb_texturenum ); // luma texture + } + Mem_FreePool( &mod->mempool ); + } + + memset( mod, 0, sizeof( *mod )); +} + +/* +================== +Mod_CheckLump + +check lump for existing +================== +*/ +int Mod_CheckLump( const char *filename, const int lump, int *lumpsize ) +{ + file_t *f = FS_Open( filename, "rb", true ); + byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; + size_t prefetch_size = sizeof( buffer ); + dextrahdr_t *extrahdr; + dheader_t *header; + + if( !f ) return LUMP_LOAD_COULDNT_OPEN; + + if( FS_Read( f, buffer, prefetch_size ) != prefetch_size ) + { + FS_Close( f ); + return LUMP_LOAD_BAD_HEADER; + } + + header = (dheader_t *)buffer; + + if( header->version != HLBSP_VERSION ) + { + FS_Close( f ); + return LUMP_LOAD_BAD_VERSION; + } + + extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t )); + + if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION ) + { + FS_Close( f ); + return LUMP_LOAD_NO_EXTRADATA; + } + + if( lump < 0 || lump >= EXTRA_LUMPS ) + { + FS_Close( f ); + return LUMP_LOAD_INVALID_NUM; + } + + if( extrahdr->lumps[lump].filelen <= 0 ) + { + FS_Close( f ); + return LUMP_LOAD_NOT_EXIST; + } + + if( lumpsize ) + *lumpsize = extrahdr->lumps[lump].filelen; + + FS_Close( f ); + + return LUMP_LOAD_OK; +} + +/* +================== +Mod_ReadLump + +reading random lump by user request +================== +*/ +int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ) +{ + file_t *f = FS_Open( filename, "rb", true ); + byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; + size_t prefetch_size = sizeof( buffer ); + dextrahdr_t *extrahdr; + dheader_t *header; + byte *data; + int length; + + if( !f ) return LUMP_LOAD_COULDNT_OPEN; + + if( FS_Read( f, buffer, prefetch_size ) != prefetch_size ) + { + FS_Close( f ); + return LUMP_LOAD_BAD_HEADER; + } + + header = (dheader_t *)buffer; + + if( header->version != HLBSP_VERSION ) + { + FS_Close( f ); + return LUMP_LOAD_BAD_VERSION; + } + + extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t )); + + if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION ) + { + FS_Close( f ); + return LUMP_LOAD_NO_EXTRADATA; + } + + if( lump < 0 || lump >= EXTRA_LUMPS ) + { + FS_Close( f ); + return LUMP_LOAD_INVALID_NUM; + } + + if( extrahdr->lumps[lump].filelen <= 0 ) + { + FS_Close( f ); + return LUMP_LOAD_NOT_EXIST; + } + + data = malloc( extrahdr->lumps[lump].filelen + 1 ); + length = extrahdr->lumps[lump].filelen; + + if( !data ) + { + FS_Close( f ); + return LUMP_LOAD_MEM_FAILED; + } + + FS_Seek( f, extrahdr->lumps[lump].fileofs, SEEK_SET ); + + if( FS_Read( f, data, length ) != length ) + { + free( data ); + FS_Close( f ); + return LUMP_LOAD_CORRUPTED; + } + + data[length] = 0; // write term + FS_Close( f ); + + if( lumpsize ) + *lumpsize = length; + *lumpdata = data; + + return LUMP_LOAD_OK; +} + +/* +================== +Mod_SaveLump + +writing lump by user request +only empty lumps is allows +================== +*/ +int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ) +{ + file_t *f = FS_Open( filename, "e+b", true ); + byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )]; + size_t prefetch_size = sizeof( buffer ); + dextrahdr_t *extrahdr; + dheader_t *header; + + if( !f ) return LUMP_SAVE_COULDNT_OPEN; + + if( !lumpdata || lumpsize <= 0 ) + return LUMP_SAVE_NO_DATA; + + if( FS_Read( f, buffer, prefetch_size ) != prefetch_size ) + { + FS_Close( f ); + return LUMP_SAVE_BAD_HEADER; + } + + header = (dheader_t *)buffer; + + if( header->version != HLBSP_VERSION ) + { + FS_Close( f ); + return LUMP_SAVE_BAD_VERSION; + } + + extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t )); + + if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION ) + { + FS_Close( f ); + return LUMP_SAVE_NO_EXTRADATA; + } + + if( lump < 0 || lump >= EXTRA_LUMPS ) + { + FS_Close( f ); + return LUMP_SAVE_INVALID_NUM; + } + + if( extrahdr->lumps[lump].filelen != 0 ) + { + FS_Close( f ); + return LUMP_SAVE_ALREADY_EXIST; + } + + FS_Seek( f, 0, SEEK_END ); + + // will be saved later + extrahdr->lumps[lump].fileofs = FS_Tell( f ); + extrahdr->lumps[lump].filelen = lumpsize; + + if( FS_Write( f, lumpdata, lumpsize ) != lumpsize ) + { + FS_Close( f ); + return LUMP_SAVE_CORRUPTED; + } + + // update the header + FS_Seek( f, sizeof( dheader_t ), SEEK_SET ); + + if( FS_Write( f, extrahdr, sizeof( dextrahdr_t )) != sizeof( dextrahdr_t )) + { + FS_Close( f ); + return LUMP_SAVE_CORRUPTED; + } + + FS_Close( f ); + return LUMP_SAVE_OK; +} \ No newline at end of file diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h new file mode 100644 index 00000000..9f1ebeb1 --- /dev/null +++ b/engine/common/mod_local.h @@ -0,0 +1,196 @@ +/* +mod_local.h - model loader +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef MOD_LOCAL_H +#define MOD_LOCAL_H + +#include "common.h" +#include "edict.h" +#include "eiface.h" + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (1.0f / 32.0f) +#define FRAC_EPSILON (1.0f / 1024.0f) +#define BACKFACE_EPSILON 0.01f +#define MAX_BOX_LEAFS 256 +#define ANIM_CYCLE 2 +#define MOD_FRAMES 20 + +// remapping info +#define SUIT_HUE_START 192 +#define SUIT_HUE_END 223 +#define PLATE_HUE_START 160 +#define PLATE_HUE_END 191 + +#define SHIRT_HUE_START 16 +#define SHIRT_HUE_END 32 +#define PANTS_HUE_START 96 +#define PANTS_HUE_END 112 + +#define LM_SAMPLE_SIZE 16 +#define LM_SAMPLE_EXTRASIZE 8 + +#define MAX_MAP_WADS 256 // max wads that can be referenced per one map + +#define CHECKVISBIT( vis, b ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] & (1 << ((b) & 7))) : (byte)false ) +#define SETVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] |= (1 << ((b) & 7))) : (byte)false ) +#define CLEARVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] &= ~(1 << ((b) & 7))) : (byte)false ) + +#define REFPVS_RADIUS 2.0f // radius for rendering +#define FATPVS_RADIUS 8.0f // FatPVS use radius smaller than the FatPHS +#define FATPHS_RADIUS 16.0f + +#define WORLD_INDEX (1) // world index is always 1 + +// model flags (stored in model_t->flags) +#define MODEL_CONVEYOR BIT( 0 ) +#define MODEL_HAS_ORIGIN BIT( 1 ) +#define MODEL_LIQUID BIT( 2 ) // model has only point hull +#define MODEL_TRANSPARENT BIT( 3 ) // have transparent surfaces +#define MODEL_COLORED_LIGHTING BIT( 4 ) // lightmaps stored as RGB + +#define MODEL_WORLD BIT( 29 ) // it's a worldmodel +#define MODEL_CLIENT BIT( 30 ) // client sprite + +// goes into world.flags +#define FWORLD_SKYSPHERE BIT( 0 ) +#define FWORLD_CUSTOM_SKYBOX BIT( 1 ) +#define FWORLD_WATERALPHA BIT( 2 ) +#define FWORLD_HAS_DELUXEMAP BIT( 3 ) + +typedef struct consistency_s +{ + const char *filename; + int orig_index; + int check_type; + qboolean issound; + int value; + vec3_t mins; + vec3_t maxs; +} consistency_t; + +#define FCRC_SHOULD_CHECKSUM BIT( 0 ) +#define FCRC_CHECKSUM_DONE BIT( 1 ) + +typedef struct +{ + int flags; + CRC32_t initialCRC; +} model_info_t; + +// values for model_t's needload +#define NL_UNREFERENCED 0 // this model can be freed after sequence precaching is done +#define NL_NEEDS_LOADED 1 +#define NL_PRESENT 2 + +typedef struct +{ + msurface_t *surf; + int cull; +} sortedface_t; + +typedef struct +{ + qboolean loading; // true if worldmodel is loading + int flags; // misc flags + + // mapstats info + char message[2048]; // just for debug + char compiler[256]; // map compiler + char generator[256]; // map editor + + // translucent sorted array + sortedface_t *draw_surfaces; // used for sorting translucent surfaces + int max_surfaces; // max surfaces per submodel (for all models) + + // visibility info + byte *visdata; // uncompressed visdata + size_t visbytes; // cluster size + size_t fatbytes; // fatpvs size + int visclusters; // num visclusters + + // world bounds + vec3_t mins; // real accuracy world bounds + vec3_t maxs; + vec3_t size; +} world_static_t; + +extern world_static_t world; +extern byte *com_studiocache; +extern model_t *loadmodel; +extern convar_t *mod_studiocache; +extern convar_t *r_wadtextures; + +// +// model.c +// +void Mod_Init( void ); +void Mod_FreeAll( void ); +void Mod_Shutdown( void ); +void Mod_ClearUserData( void ); +model_t *Mod_LoadWorld( const char *name, qboolean preload ); +void *Mod_Calloc( int number, size_t size ); +void *Mod_CacheCheck( struct cache_user_s *c ); +void Mod_LoadCacheFile( const char *path, struct cache_user_s *cu ); +void *Mod_AliasExtradata( model_t *mod ); +void *Mod_StudioExtradata( model_t *mod ); +model_t *Mod_FindName( const char *name, qboolean trackCRC ); +model_t *Mod_LoadModel( model_t *mod, qboolean crash ); +model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ); +qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ); +void Mod_NeedCRC( const char *name, qboolean needCRC ); +void Mod_PurgeStudioCache( void ); +void Mod_FreeUnused( void ); + +// +// mod_bmodel.c +// +void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ); +qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean silent ); +qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, int *lastleaf ); +int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis ); +qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits ); +int Mod_CheckLump( const char *filename, const int lump, int *lumpsize ); +int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize ); +int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize ); +mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node ); +void Mod_AmbientLevels( const vec3_t p, byte *pvolumes ); +int Mod_SampleSizeForFace( msurface_t *surf ); +byte *Mod_GetPVSForPoint( const vec3_t p ); +void Mod_UnloadBrushModel( model_t *mod ); +void Mod_PrintWorldStats_f( void ); + +// +// mod_studio.c +// +void Mod_InitStudioAPI( void ); +void Mod_InitStudioHull( void ); +void Mod_ResetStudioAPI( void ); +const char *Mod_StudioTexName( const char *modname ); +qboolean Mod_GetStudioBounds( const char *name, vec3_t mins, vec3_t maxs ); +void Mod_StudioGetAttachment( const edict_t *e, int iAttachment, float *org, float *ang ); +void Mod_GetBonePosition( const edict_t *e, int iBone, float *org, float *ang ); +hull_t *Mod_HullForStudio( model_t *m, float frame, int seq, vec3_t ang, vec3_t org, vec3_t size, byte *pcnt, byte *pbl, int *hitboxes, edict_t *ed ); +void R_StudioSlerpBones( int numbones, vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ); +void R_StudioCalcBoneQuaternion( int frame, float s, void *pbone, void *panim, float *adj, vec4_t q ); +void R_StudioCalcBonePosition( int frame, float s, void *pbone, void *panim, vec3_t adj, vec3_t pos ); +void *R_StudioGetAnim( void *m_pStudioHeader, void *m_pSubModel, void *pseqdesc ); +void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean ignore_sequences ); +void Mod_StudioLoadTextures( model_t *mod, void *data ); +void Mod_StudioUnloadTextures( void *data ); +int Mod_HitgroupForStudioHull( int index ); +void Mod_ClearStudioCache( void ); + +#endif//MOD_LOCAL_H \ No newline at end of file diff --git a/engine/common/mod_studio.c b/engine/common/mod_studio.c new file mode 100644 index 00000000..37bd8e86 --- /dev/null +++ b/engine/common/mod_studio.c @@ -0,0 +1,954 @@ +/* +sv_studio.c - server studio utilities +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "studio.h" +#include "r_studioint.h" +#include "library.h" + +typedef int (*STUDIOAPI)( int, sv_blending_interface_t**, server_studio_api_t*, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] ); + +typedef struct mstudiocache_s +{ + float frame; + int sequence; + vec3_t angles; + vec3_t origin; + vec3_t size; + byte controller[4]; + byte blending[2]; + model_t *model; + uint current_hull; + uint current_plane; + uint numhitboxes; +} mstudiocache_t; + +#define STUDIO_CACHESIZE 16 +#define STUDIO_CACHEMASK (STUDIO_CACHESIZE - 1) + +// trace global variables +static sv_blending_interface_t *pBlendAPI = NULL; +static studiohdr_t *mod_studiohdr; +static matrix3x4 studio_transform; +static hull_t cache_hull[MAXSTUDIOBONES]; +static hull_t studio_hull[MAXSTUDIOBONES]; +static matrix3x4 studio_bones[MAXSTUDIOBONES]; +static uint studio_hull_hitgroup[MAXSTUDIOBONES]; +static uint cache_hull_hitgroup[MAXSTUDIOBONES]; +static mstudiocache_t cache_studio[STUDIO_CACHESIZE]; +static mclipnode_t studio_clipnodes[6]; +static mplane_t studio_planes[768]; +static mplane_t cache_planes[768]; + +// current cache state +static int cache_current; +static int cache_current_hull; +static int cache_current_plane; + +/* +==================== +Mod_InitStudioHull +==================== +*/ +void Mod_InitStudioHull( void ) +{ + int i, side; + + if( studio_hull[0].planes != NULL ) + return; // already initailized + + for( i = 0; i < 6; i++ ) + { + studio_clipnodes[i].planenum = i; + + side = i & 1; + + studio_clipnodes[i].children[side] = CONTENTS_EMPTY; + if( i != 5 ) studio_clipnodes[i].children[side^1] = i + 1; + else studio_clipnodes[i].children[side^1] = CONTENTS_SOLID; + } + + for( i = 0; i < MAXSTUDIOBONES; i++ ) + { + studio_hull[i].clipnodes = studio_clipnodes; + studio_hull[i].planes = &studio_planes[i*6]; + studio_hull[i].firstclipnode = 0; + studio_hull[i].lastclipnode = 5; + } +} + +/* +=============================================================================== + + STUDIO MODELS CACHE + +=============================================================================== +*/ +/* +==================== +ClearStudioCache +==================== +*/ +void Mod_ClearStudioCache( void ) +{ + memset( cache_studio, 0, sizeof( cache_studio )); + cache_current_hull = cache_current_plane = 0; + + cache_current = 0; +} + +/* +==================== +AddToStudioCache +==================== +*/ +void Mod_AddToStudioCache( float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *pcontroller, byte *pblending, model_t *model, hull_t *hull, int numhitboxes ) +{ + mstudiocache_t *pCache; + + if( numhitboxes + cache_current_hull >= MAXSTUDIOBONES ) + Mod_ClearStudioCache(); + + cache_current++; + pCache = &cache_studio[cache_current & STUDIO_CACHEMASK]; + + pCache->frame = frame; + pCache->sequence = sequence; + VectorCopy( angles, pCache->angles ); + VectorCopy( origin, pCache->origin ); + VectorCopy( size, pCache->size ); + + memcpy( pCache->controller, pcontroller, 4 ); + memcpy( pCache->blending, pblending, 2 ); + + pCache->model = model; + pCache->current_hull = cache_current_hull; + pCache->current_plane = cache_current_plane; + + memcpy( &cache_hull[cache_current_hull], hull, numhitboxes * sizeof( hull_t )); + memcpy( &cache_planes[cache_current_plane], studio_planes, numhitboxes * sizeof( mplane_t ) * 6 ); + memcpy( &cache_hull_hitgroup[cache_current_hull], studio_hull_hitgroup, numhitboxes * sizeof( uint )); + + cache_current_hull += numhitboxes; + cache_current_plane += numhitboxes * 6; + pCache->numhitboxes = numhitboxes; +} + +/* +==================== +CheckStudioCache +==================== +*/ +mstudiocache_t *Mod_CheckStudioCache( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *controller, byte *blending ) +{ + mstudiocache_t *pCached; + int i; + + for( i = 0; i < STUDIO_CACHESIZE; i++ ) + { + pCached = &cache_studio[(cache_current - i) & STUDIO_CACHEMASK]; + + if( pCached->model != model ) + continue; + + if( pCached->frame != frame ) + continue; + + if( pCached->sequence != sequence ) + continue; + + if( !VectorCompare( pCached->angles, angles )) + continue; + + if( !VectorCompare( pCached->origin, origin )) + continue; + + if( !VectorCompare( pCached->size, size )) + continue; + + if( memcmp( pCached->controller, controller, 4 ) != 0 ) + continue; + + if( memcmp( pCached->blending, blending, 2 ) != 0 ) + continue; + + return pCached; + } + + return NULL; +} + +/* +=============================================================================== + + STUDIO MODELS TRACING + +=============================================================================== +*/ +/* +==================== +SetStudioHullPlane +==================== +*/ +void Mod_SetStudioHullPlane( int planenum, int bone, int axis, float offset, const vec3_t size ) +{ + mplane_t *pl = &studio_planes[planenum]; + + pl->type = 5; + + pl->normal[0] = studio_bones[bone][0][axis]; + pl->normal[1] = studio_bones[bone][1][axis]; + pl->normal[2] = studio_bones[bone][2][axis]; + + pl->dist = (pl->normal[0] * studio_bones[bone][0][3]) + (pl->normal[1] * studio_bones[bone][1][3]) + (pl->normal[2] * studio_bones[bone][2][3]) + offset; + + if( planenum & 1 ) pl->dist -= DotProductFabs( pl->normal, size ); + else pl->dist += DotProductFabs( pl->normal, size ); + +} + +/* +==================== +HullForStudio + +NOTE: pEdict may be NULL +==================== +*/ +hull_t *Mod_HullForStudio( model_t *model, float frame, int sequence, vec3_t angles, vec3_t origin, vec3_t size, byte *pcontroller, byte *pblending, int *numhitboxes, edict_t *pEdict ) +{ + vec3_t angles2; + mstudiocache_t *bonecache; + mstudiobbox_t *phitbox; + qboolean bSkipShield; + int i, j; + + bSkipShield = false; + *numhitboxes = 0; // assume error + + if( mod_studiocache->value ) + { + bonecache = Mod_CheckStudioCache( model, frame, sequence, angles, origin, size, pcontroller, pblending ); + + if( bonecache != NULL ) + { + memcpy( studio_planes, &cache_planes[bonecache->current_plane], bonecache->numhitboxes * sizeof( mplane_t ) * 6 ); + memcpy( studio_hull_hitgroup, &cache_hull_hitgroup[bonecache->current_hull], bonecache->numhitboxes * sizeof( uint )); + memcpy( studio_hull, &cache_hull[bonecache->current_hull], bonecache->numhitboxes * sizeof( hull_t )); + + *numhitboxes = bonecache->numhitboxes; + return studio_hull; + } + } + + mod_studiohdr = Mod_StudioExtradata( model ); + if( !mod_studiohdr ) return NULL; // probably not a studiomodel + + VectorCopy( angles, angles2 ); + + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + angles2[PITCH] = -angles2[PITCH]; // stupid quake bug + + pBlendAPI->SV_StudioSetupBones( model, frame, sequence, angles2, origin, pcontroller, pblending, -1, pEdict ); + phitbox = (mstudiobbox_t *)((byte *)mod_studiohdr + mod_studiohdr->hitboxindex); + + if( SV_IsValidEdict( pEdict ) && pEdict->v.gamestate == 1 ) + bSkipShield = 1; + + for( i = j = 0; i < mod_studiohdr->numhitboxes; i++, j += 6 ) + { + if( bSkipShield && i == 21 ) + continue; // CS stuff + + studio_hull_hitgroup[i] = phitbox[i].group; + + Mod_SetStudioHullPlane( j + 0, phitbox[i].bone, 0, phitbox[i].bbmax[0], size ); + Mod_SetStudioHullPlane( j + 1, phitbox[i].bone, 0, phitbox[i].bbmin[0], size ); + Mod_SetStudioHullPlane( j + 2, phitbox[i].bone, 1, phitbox[i].bbmax[1], size ); + Mod_SetStudioHullPlane( j + 3, phitbox[i].bone, 1, phitbox[i].bbmin[1], size ); + Mod_SetStudioHullPlane( j + 4, phitbox[i].bone, 2, phitbox[i].bbmax[2], size ); + Mod_SetStudioHullPlane( j + 5, phitbox[i].bone, 2, phitbox[i].bbmin[2], size ); + } + + // tell trace code about hitbox count + *numhitboxes = (bSkipShield) ? (mod_studiohdr->numhitboxes - 1) : (mod_studiohdr->numhitboxes); + + if( mod_studiocache->value ) + Mod_AddToStudioCache( frame, sequence, angles, origin, size, pcontroller, pblending, model, studio_hull, *numhitboxes ); + + return studio_hull; +} + +/* +=============================================================================== + + STUDIO MODELS SETUP BONES + +=============================================================================== +*/ +/* +==================== +StudioCalcBoneAdj + +==================== +*/ +static void Mod_StudioCalcBoneAdj( float *adj, const byte *pcontroller ) +{ + int i, j; + float value; + mstudiobonecontroller_t *pbonecontroller; + + pbonecontroller = (mstudiobonecontroller_t *)((byte *)mod_studiohdr + mod_studiohdr->bonecontrollerindex); + + for( j = 0; j < mod_studiohdr->numbonecontrollers; j++ ) + { + i = pbonecontroller[j].index; + + if( i == STUDIO_MOUTH ) + continue; // ignore mouth + + if( i <= MAXSTUDIOCONTROLLERS ) + { + // check for 360% wrapping + if( pbonecontroller[j].type & STUDIO_RLOOP ) + { + value = pcontroller[i] * (360.0f / 256.0f) + pbonecontroller[j].start; + } + else + { + value = pcontroller[i] / 255.0f; + value = bound( 0.0f, value, 1.0f ); + value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; + } + } + + switch( pbonecontroller[j].type & STUDIO_TYPES ) + { + case STUDIO_XR: + case STUDIO_YR: + case STUDIO_ZR: + adj[j] = value * (M_PI / 180.0f); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } +} + +/* +==================== +StudioCalcRotations + +==================== +*/ +static void Mod_StudioCalcRotations( int boneused[], int numbones, const byte *pcontroller, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) +{ + int i, j, frame; + mstudiobone_t *pbone; + float adj[MAXSTUDIOCONTROLLERS]; + float s; + + // bah, fix this bug with changing sequences too fast + if( f > pseqdesc->numframes - 1 ) + { + f = 0.0f; + } + else if( f < -0.01f ) + { + // BUG ( somewhere else ) but this code should validate this data. + // This could cause a crash if the frame # is negative, so we'll go ahead + // and clamp it here + f = -0.01f; + } + + frame = (int)f; + s = (f - frame); + + // add in programtic controllers + pbone = (mstudiobone_t *)((byte *)mod_studiohdr + mod_studiohdr->boneindex); + + Mod_StudioCalcBoneAdj( adj, pcontroller ); + + for( j = numbones - 1; j >= 0; j-- ) + { + i = boneused[j]; + R_StudioCalcBoneQuaternion( frame, s, &pbone[i], &panim[i], adj, q[i] ); + R_StudioCalcBonePosition( frame, s, &pbone[i], &panim[i], adj, pos[i] ); + } + + if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f; + if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f; +} + +/* +==================== +StudioSetupBones + +NOTE: pEdict is unused +==================== +*/ +static void SV_StudioSetupBones( model_t *pModel, float frame, int sequence, const vec3_t angles, const vec3_t origin, + const byte *pcontroller, const byte *pblending, int iBone, const edict_t *pEdict ) +{ + int i, j, numbones = 0; + int boneused[MAXSTUDIOBONES]; + float f = 0.0; + + mstudiobone_t *pbones; + mstudioseqdesc_t *pseqdesc; + mstudioanim_t *panim; + + static float pos[MAXSTUDIOBONES][3]; + static vec4_t q[MAXSTUDIOBONES]; + matrix3x4 bonematrix; + + static float pos2[MAXSTUDIOBONES][3]; + static vec4_t q2[MAXSTUDIOBONES]; + static float pos3[MAXSTUDIOBONES][3]; + static vec4_t q3[MAXSTUDIOBONES]; + static float pos4[MAXSTUDIOBONES][3]; + static vec4_t q4[MAXSTUDIOBONES]; + + if( sequence < 0 || sequence >= mod_studiohdr->numseq ) + { + // only show warn if sequence that out of range was specified intentionally + if( sequence > mod_studiohdr->numseq ) + MsgDev( D_WARN, "SV_StudioSetupBones: sequence %i/%i out of range for model %s\n", sequence, mod_studiohdr->numseq, mod_studiohdr->name ); + sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)mod_studiohdr + mod_studiohdr->seqindex) + sequence; + pbones = (mstudiobone_t *)((byte *)mod_studiohdr + mod_studiohdr->boneindex); + panim = R_StudioGetAnim( mod_studiohdr, pModel, pseqdesc ); + + if( iBone < -1 || iBone >= mod_studiohdr->numbones ) + iBone = 0; + + if( iBone == -1 ) + { + numbones = mod_studiohdr->numbones; + for( i = 0; i < mod_studiohdr->numbones; i++ ) + boneused[(numbones - i) - 1] = i; + } + else + { + // only the parent bones + for( i = iBone; i != -1; i = pbones[i].parent ) + boneused[numbones++] = i; + } + + if( pseqdesc->numframes > 1 ) + f = ( frame * ( pseqdesc->numframes - 1 )) / 256.0f; + + Mod_StudioCalcRotations( boneused, numbones, pcontroller, pos, q, pseqdesc, panim, f ); + + if( pseqdesc->numblends > 1 ) + { + float s; + + panim += mod_studiohdr->numbones; + Mod_StudioCalcRotations( boneused, numbones, pcontroller, pos2, q2, pseqdesc, panim, f ); + + s = (float)pblending[0] / 255.0f; + + R_StudioSlerpBones( mod_studiohdr->numbones, q, pos, q2, pos2, s ); + + if( pseqdesc->numblends == 4 ) + { + panim += mod_studiohdr->numbones; + Mod_StudioCalcRotations( boneused, numbones, pcontroller, pos3, q3, pseqdesc, panim, f ); + + panim += mod_studiohdr->numbones; + Mod_StudioCalcRotations( boneused, numbones, pcontroller, pos4, q4, pseqdesc, panim, f ); + + s = (float)pblending[0] / 255.0f; + R_StudioSlerpBones( mod_studiohdr->numbones, q3, pos3, q4, pos4, s ); + + s = (float)pblending[1] / 255.0f; + R_StudioSlerpBones( mod_studiohdr->numbones, q, pos, q3, pos3, s ); + } + } + + Matrix3x4_CreateFromEntity( studio_transform, angles, origin, 1.0f ); + + for( j = numbones - 1; j >= 0; j-- ) + { + i = boneused[j]; + + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + if( pbones[i].parent == -1 ) + Matrix3x4_ConcatTransforms( studio_bones[i], studio_transform, bonematrix ); + else Matrix3x4_ConcatTransforms( studio_bones[i], studio_bones[pbones[i].parent], bonematrix ); + } +} + +/* +==================== +StudioGetAttachment +==================== +*/ +void Mod_StudioGetAttachment( const edict_t *e, int iAtt, float *origin, float *angles ) +{ + mstudioattachment_t *pAtt; + vec3_t angles2; + model_t *mod; + + mod = SV_ModelHandle( e->v.modelindex ); + mod_studiohdr = (studiohdr_t *)Mod_StudioExtradata( mod ); + if( !mod_studiohdr ) return; + + if( mod_studiohdr->numattachments <= 0 ) + { + if( origin ) VectorCopy( e->v.origin, origin ); + + if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ) && angles ) + VectorCopy( e->v.angles, angles ); + return; + } + + iAtt = bound( 0, iAtt, mod_studiohdr->numattachments - 1 ); + + // calculate attachment origin and angles + pAtt = (mstudioattachment_t *)((byte *)mod_studiohdr + mod_studiohdr->attachmentindex) + iAtt; + + VectorCopy( e->v.angles, angles2 ); + + if( !FBitSet( host.features, ENGINE_COMPENSATE_QUAKE_BUG )) + angles2[PITCH] = -angles2[PITCH]; + + pBlendAPI->SV_StudioSetupBones( mod, e->v.frame, e->v.sequence, angles2, e->v.origin, e->v.controller, e->v.blending, pAtt->bone, e ); + + // compute pos and angles + if( origin != NULL ) + Matrix3x4_VectorTransform( studio_bones[pAtt->bone], pAtt->org, origin ); + + if( FBitSet( host.features, ENGINE_COMPUTE_STUDIO_LERP ) && origin != NULL && angles != NULL ) + { + vec3_t forward, bonepos; + + Matrix3x4_OriginFromMatrix( studio_bones[pAtt->bone], bonepos ); + VectorSubtract( origin, bonepos, forward ); // make forward + VectorNormalizeFast( forward ); + VectorAngles( forward, angles ); + } +} + +/* +==================== +GetBonePosition +==================== +*/ +void Mod_GetBonePosition( const edict_t *e, int iBone, float *origin, float *angles ) +{ + model_t *mod; + + mod = SV_ModelHandle( e->v.modelindex ); + mod_studiohdr = (studiohdr_t *)Mod_StudioExtradata( mod ); + if( !mod_studiohdr ) return; + + pBlendAPI->SV_StudioSetupBones( mod, e->v.frame, e->v.sequence, e->v.angles, e->v.origin, e->v.controller, e->v.blending, iBone, e ); + + if( origin ) Matrix3x4_OriginFromMatrix( studio_bones[iBone], origin ); + if( angles ) VectorAngles( studio_bones[iBone][0], angles ); // bone forward to angles +} + +/* +==================== +HitgroupForStudioHull +==================== +*/ +int Mod_HitgroupForStudioHull( int index ) +{ + return studio_hull_hitgroup[index]; +} + +/* +==================== +StudioBoundVertex +==================== +*/ +void Mod_StudioBoundVertex( vec3_t mins, vec3_t maxs, int *numverts, const vec3_t vertex ) +{ + if((*numverts) == 0 ) + ClearBounds( mins, maxs ); + + AddPointToBounds( vertex, mins, maxs ); + (*numverts)++; +} + +/* +==================== +StudioAccumulateBoneVerts +==================== +*/ +void Mod_StudioAccumulateBoneVerts( vec3_t mins, vec3_t maxs, int *numverts, vec3_t bone_mins, vec3_t bone_maxs, int *numbones ) +{ + vec3_t delta; + vec3_t point; + + if( *numbones <= 0 ) + return; + + // calculate the midpoint of the second vertex, + VectorSubtract( bone_maxs, bone_mins, delta ); + + VectorScale( delta, 0.5f, point ); + Mod_StudioBoundVertex( mins, maxs, numverts, point ); + + VectorClear( bone_mins ); + VectorClear( bone_maxs ); + *numbones = 0; +} + +/* +==================== +StudioComputeBounds +==================== +*/ +void Mod_StudioComputeBounds( void *buffer, vec3_t mins, vec3_t maxs, qboolean ignore_sequences ) +{ + int i, j, k, numseq; + studiohdr_t *pstudiohdr; + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *m_pSubModel; + mstudioseqgroup_t *pseqgroup; + mstudioseqdesc_t *pseqdesc; + mstudiobone_t *pbones; + mstudioanim_t *panim; + vec3_t bone_mins, bone_maxs; + vec3_t vert_mins, vert_maxs; + int vert_count, bone_count; + int bodyCount = 0; + vec3_t pos, *pverts; + + vert_count = bone_count = 0; + VectorClear( bone_mins ); + VectorClear( bone_maxs ); + VectorClear( vert_mins ); + VectorClear( vert_maxs ); + + // Get the body part portion of the model + pstudiohdr = (studiohdr_t *)buffer; + pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex); + + // each body part has nummodels variations so there are as many total variations as there + // are in a matrix of each part by each other part + for( i = 0; i < pstudiohdr->numbodyparts; i++ ) + bodyCount += pbodypart[i].nummodels; + + // The studio models we want are vec3_t mins, vec3_t maxsight after the bodyparts (still need to + // find a detailed breakdown of the mdl format). Move pointer there. + m_pSubModel = (mstudiomodel_t *)(&pbodypart[pstudiohdr->numbodyparts]); + + for( i = 0; i < bodyCount; i++ ) + { + pverts = (vec3_t *)((byte *)pstudiohdr + m_pSubModel[i].vertindex); + + for( j = 0; j < m_pSubModel[i].numverts; j++ ) + Mod_StudioBoundVertex( bone_mins, bone_maxs, &vert_count, pverts[j] ); + } + + pbones = (mstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex); + numseq = (ignore_sequences) ? 1 : pstudiohdr->numseq; + + for( i = 0; i < numseq; i++ ) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + i; + pseqgroup = (mstudioseqgroup_t *)((byte *)pstudiohdr + pstudiohdr->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + panim = (mstudioanim_t *)((byte *)pstudiohdr + pseqgroup->data + pseqdesc->animindex); + else continue; + + for( j = 0; j < pstudiohdr->numbones; j++ ) + { + for( k = 0; k < pseqdesc->numframes; k++ ) + { + R_StudioCalcBonePosition( k, 0, &pbones[j], panim, NULL, pos ); + Mod_StudioBoundVertex( vert_mins, vert_maxs, &bone_count, pos ); + } + } + + Mod_StudioAccumulateBoneVerts( bone_mins, bone_maxs, &vert_count, vert_mins, vert_maxs, &bone_count ); + } + + VectorCopy( bone_mins, mins ); + VectorCopy( bone_maxs, maxs ); +} + +/* +==================== +Mod_GetStudioBounds +==================== +*/ +qboolean Mod_GetStudioBounds( const char *name, vec3_t mins, vec3_t maxs ) +{ + int result = false; + byte *f; + + if( !Q_strstr( name, "models" ) || !Q_strstr( name, ".mdl" )) + return false; + + f = FS_LoadFile( name, NULL, false ); + if( !f ) return false; + + if( *(uint *)f == IDSTUDIOHEADER ) + { + VectorClear( mins ); + VectorClear( maxs ); + Mod_StudioComputeBounds( f, mins, maxs, false ); + result = true; + } + Mem_Free( f ); + + return result; +} + +/* +=============== +Mod_StudioTexName + +extract texture filename from modelname +=============== +*/ +const char *Mod_StudioTexName( const char *modname ) +{ + static char texname[MAX_QPATH]; + + Q_strncpy( texname, modname, sizeof( texname )); + COM_StripExtension( texname ); + Q_strncat( texname, "T.mdl", sizeof( texname )); + + return texname; +} + +/* +================ +Mod_StudioBodyVariations + +calc studio body variations +================ +*/ +static int Mod_StudioBodyVariations( model_t *mod ) +{ + studiohdr_t *pstudiohdr; + mstudiobodyparts_t *pbodypart; + int i, count = 1; + + pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod ); + if( !pstudiohdr ) return 0; + + pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex); + + // each body part has nummodels variations so there are as many total variations as there + // are in a matrix of each part by each other part + for( i = 0; i < pstudiohdr->numbodyparts; i++ ) + count = count * pbodypart[i].nummodels; + + return count; +} + +/* +================= +R_StudioLoadHeader +================= +*/ +studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer ) +{ + byte *pin; + studiohdr_t *phdr; + int i; + + if( !buffer ) return NULL; + + pin = (byte *)buffer; + phdr = (studiohdr_t *)pin; + i = phdr->version; + + if( i != STUDIO_VERSION ) + { + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", mod->name, i, STUDIO_VERSION ); + return NULL; + } + + return (studiohdr_t *)buffer; +} + +/* +================= +Mod_LoadStudioModel +================= +*/ +void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) +{ + studiohdr_t *phdr; + + if( loaded ) *loaded = false; + loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); + loadmodel->type = mod_studio; + + phdr = R_StudioLoadHeader( mod, buffer ); + if( !phdr ) return; // bad model + + if( phdr->numtextures == 0 ) + { + studiohdr_t *thdr; + byte *in, *out; + void *buffer2 = NULL; + size_t size1, size2; + + buffer2 = FS_LoadFile( Mod_StudioTexName( mod->name ), NULL, false ); + thdr = R_StudioLoadHeader( mod, buffer2 ); + + if( !thdr ) + { + MsgDev( D_WARN, "Mod_LoadStudioModel: %s missing textures file\n", mod->name ); + if( buffer2 ) Mem_Free( buffer2 ); + } + else + { + Mod_StudioLoadTextures( mod, thdr ); + + // give space for textures and skinrefs + size1 = thdr->numtextures * sizeof( mstudiotexture_t ); + size2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short ); + mod->cache.data = Mem_Alloc( loadmodel->mempool, phdr->length + size1 + size2 ); + memcpy( loadmodel->cache.data, buffer, phdr->length ); // copy main mdl buffer + phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + phdr->numskinfamilies = thdr->numskinfamilies; + phdr->numtextures = thdr->numtextures; + phdr->numskinref = thdr->numskinref; + phdr->textureindex = phdr->length; + phdr->skinindex = phdr->textureindex + size1; + + in = (byte *)thdr + thdr->textureindex; + out = (byte *)phdr + phdr->textureindex; + memcpy( out, in, size1 + size2 ); // copy textures + skinrefs + phdr->length += size1 + size2; + Mem_Free( buffer2 ); // release T.mdl + } + } + else + { + // NOTE: don't modify source buffer because it's used for CRC computing + loadmodel->cache.data = Mem_Alloc( loadmodel->mempool, phdr->length ); + memcpy( loadmodel->cache.data, buffer, phdr->length ); + phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + Mod_StudioLoadTextures( mod, phdr ); + + // NOTE: we wan't keep raw textures in memory. just cutoff model pointer above texture base + loadmodel->cache.data = Mem_Realloc( loadmodel->mempool, loadmodel->cache.data, phdr->texturedataindex ); + phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + phdr->length = phdr->texturedataindex; // update model size + } + + // setup bounding box + if( !VectorCompare( vec3_origin, phdr->bbmin )) + { + // clipping bounding box + VectorCopy( phdr->bbmin, loadmodel->mins ); + VectorCopy( phdr->bbmax, loadmodel->maxs ); + } + else if( !VectorCompare( vec3_origin, phdr->min )) + { + // movement bounding box + VectorCopy( phdr->min, loadmodel->mins ); + VectorCopy( phdr->max, loadmodel->maxs ); + } + else + { + // well compute bounds from vertices and round to nearest even values + Mod_StudioComputeBounds( phdr, loadmodel->mins, loadmodel->maxs, true ); + RoundUpHullSize( loadmodel->mins ); + RoundUpHullSize( loadmodel->maxs ); + } + + loadmodel->numframes = Mod_StudioBodyVariations( loadmodel ); + loadmodel->radius = RadiusFromBounds( loadmodel->mins, loadmodel->maxs ); + loadmodel->flags = phdr->flags; // copy header flags + + if( loaded ) *loaded = true; +} + +/* +================= +Mod_UnloadStudioModel +================= +*/ +void Mod_UnloadStudioModel( model_t *mod ) +{ + Assert( mod != NULL ); + + if( mod->type != mod_studio ) + return; // not a studio + + Mod_StudioUnloadTextures( mod->cache.data ); + Mem_FreePool( &mod->mempool ); + memset( mod, 0, sizeof( *mod )); +} + +static sv_blending_interface_t gBlendAPI = +{ + SV_BLENDING_INTERFACE_VERSION, + SV_StudioSetupBones, +}; + +static server_studio_api_t gStudioAPI = +{ + Mod_Calloc, + Mod_CacheCheck, + Mod_LoadCacheFile, + Mod_StudioExtradata, +}; + +/* +=============== +Mod_InitStudioAPI + +Initialize server studio (blending interface) +=============== +*/ +void Mod_InitStudioAPI( void ) +{ + static STUDIOAPI pBlendIface; + + pBlendAPI = &gBlendAPI; + + pBlendIface = (STUDIOAPI)COM_GetProcAddress( svgame.hInstance, "Server_GetBlendingInterface" ); + if( pBlendIface && pBlendIface( SV_BLENDING_INTERFACE_VERSION, &pBlendAPI, &gStudioAPI, &studio_transform, &studio_bones )) + { + MsgDev( D_REPORT, "SV_LoadProgs: ^2initailized Server Blending interface ^7ver. %i\n", SV_BLENDING_INTERFACE_VERSION ); + return; + } + + // just restore pointer to builtin function + pBlendAPI = &gBlendAPI; +} + +/* +=============== +Mod_ResetStudioAPI + +Returns to default callbacks +=============== +*/ +void Mod_ResetStudioAPI( void ) +{ + pBlendAPI = &gBlendAPI; +} \ No newline at end of file diff --git a/engine/common/model.c b/engine/common/model.c new file mode 100644 index 00000000..4a57483c --- /dev/null +++ b/engine/common/model.c @@ -0,0 +1,596 @@ +/* +model.c - modelloader +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "mod_local.h" +#include "sprite.h" +#include "mathlib.h" +#include "alias.h" +#include "studio.h" +#include "wadfile.h" +#include "world.h" +#include "gl_local.h" +#include "features.h" +#include "client.h" +#include "server.h" // LUMP_ error codes + +static model_info_t mod_crcinfo[MAX_MODELS]; +static model_t mod_known[MAX_MODELS]; +static int mod_numknown = 0; +byte *com_studiocache; // cache for submodels +convar_t *mod_studiocache; +convar_t *r_wadtextures; +model_t *loadmodel; + +/* +=============================================================================== + + MOD COMMON UTILS + +=============================================================================== +*/ +/* +================ +Mod_Modellist_f +================ +*/ +static void Mod_Modellist_f( void ) +{ + int i, nummodels; + model_t *mod; + + Con_Printf( "\n" ); + Con_Printf( "-----------------------------------\n" ); + + for( i = nummodels = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) + { + if( !mod->name[0] ) + continue; // free slot + Con_Printf( "%s\n", mod->name ); + nummodels++; + } + + Con_Printf( "-----------------------------------\n" ); + Con_Printf( "%i total models\n", nummodels ); + Con_Printf( "\n" ); +} + +/* +================ +Mod_FreeUserData +================ +*/ +static void Mod_FreeUserData( model_t *mod ) +{ + // ignore submodels and freed models + if( !mod->name[0] || mod->name[0] == '*' ) + return; + + if( host.type == HOST_DEDICATED ) + { + if( svgame.physFuncs.Mod_ProcessUserData != NULL ) + { + // let the server.dll free custom data + svgame.physFuncs.Mod_ProcessUserData( mod, false, NULL ); + } + } + else + { + if( clgame.drawFuncs.Mod_ProcessUserData != NULL ) + { + // let the client.dll free custom data + clgame.drawFuncs.Mod_ProcessUserData( mod, false, NULL ); + } + } +} + +/* +================ +Mod_FreeModel +================ +*/ +static void Mod_FreeModel( model_t *mod ) +{ + // already freed? + if( !mod || !mod->name[0] ) + return; + + if( mod->name[0] != '*' ) + Mod_FreeUserData( mod ); + + // select the properly unloader + switch( mod->type ) + { + case mod_sprite: + Mod_UnloadSpriteModel( mod ); + break; + case mod_studio: + Mod_UnloadStudioModel( mod ); + break; + case mod_brush: + Mod_UnloadBrushModel( mod ); + break; + case mod_alias: + Mod_UnloadAliasModel( mod ); + break; + } + + memset( mod, 0, sizeof( *mod )); +} + +/* +=============================================================================== + + MODEL INITALIZE\SHUTDOWN + +=============================================================================== +*/ +void Mod_Init( void ) +{ + com_studiocache = Mem_AllocPool( "Studio Cache" ); + mod_studiocache = Cvar_Get( "r_studiocache", "1", FCVAR_ARCHIVE, "enables studio cache for speedup tracing hitboxes" ); + r_wadtextures = Cvar_Get( "r_wadtextures", "0", 0, "completely ignore textures in the bsp-file if enabled" ); + + Cmd_AddCommand( "mapstats", Mod_PrintWorldStats_f, "show stats for currently loaded map" ); + Cmd_AddCommand( "modellist", Mod_Modellist_f, "display loaded models list" ); + + Mod_ResetStudioAPI (); + Mod_InitStudioHull (); +} + +/* +================ +Mod_FreeAll +================ +*/ +void Mod_FreeAll( void ) +{ + model_t *mod; + int i; + + for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) + Mod_FreeModel( mod ); + mod_numknown = 0; +} + +/* +================ +Mod_ClearUserData +================ +*/ +void Mod_ClearUserData( void ) +{ + int i; + + for( i = 0; i < mod_numknown; i++ ) + Mod_FreeUserData( &mod_known[i] ); +} + +/* +================ +Mod_Shutdown +================ +*/ +void Mod_Shutdown( void ) +{ + Mod_FreeAll(); + Mem_FreePool( &com_studiocache ); +} + +/* +=============================================================================== + + MODELS MANAGEMENT + +=============================================================================== +*/ +/* +================== +Mod_FindName + +================== +*/ +model_t *Mod_FindName( const char *filename, qboolean trackCRC ) +{ + char modname[MAX_QPATH]; + model_t *mod; + int i; + + if( !COM_CheckString( filename )) + return NULL; + + Q_strncpy( modname, filename, sizeof( modname )); + + // search the currently loaded models + for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) + { + if( !Q_stricmp( mod->name, modname )) + { + if( mod->mempool || mod->name[0] == '*' ) + mod->needload = NL_PRESENT; + else mod->needload = NL_NEEDS_LOADED; + + return mod; + } + } + + // find a free model slot spot + for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) + if( !mod->name[0] ) break; // this is a valid spot + + if( i == mod_numknown ) + { + if( mod_numknown == MAX_MODELS ) + Host_Error( "Mod_ForName: MAX_MODELS limit exceeded\n" ); + mod_numknown++; + } + + // copy name, so model loader can find model file + Q_strncpy( mod->name, modname, sizeof( mod->name )); + if( trackCRC ) mod_crcinfo[i].flags = FCRC_SHOULD_CHECKSUM; + else mod_crcinfo[i].flags = 0; + mod->needload = NL_NEEDS_LOADED; + mod_crcinfo[i].initialCRC = 0; + + return mod; +} + +/* +================== +Mod_LoadModel + +Loads a model into the cache +================== +*/ +model_t *Mod_LoadModel( model_t *mod, qboolean crash ) +{ + char tempname[64]; + long length = 0; + qboolean loaded; + byte *buf; + model_info_t *p; + + ASSERT( mod != NULL ); + + // check if already loaded (or inline bmodel) + if( mod->mempool || mod->name[0] == '*' ) + { + mod->needload = NL_PRESENT; + return mod; + } + + ASSERT( mod->needload == NL_NEEDS_LOADED ); + + // store modelname to show error + Q_strncpy( tempname, mod->name, sizeof( tempname )); + COM_FixSlashes( tempname ); + + buf = FS_LoadFile( tempname, &length, false ); + + if( !buf ) + { + memset( mod, 0, sizeof( model_t )); + + if( crash ) Host_Error( "%s couldn't load\n", tempname ); + else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); + + return NULL; + } + + Con_Reportf( "loading %s\n", mod->name ); + mod->needload = NL_PRESENT; + mod->type = mod_bad; + loadmodel = mod; + + // call the apropriate loader + switch( *(uint *)buf ) + { + case IDSTUDIOHEADER: + Mod_LoadStudioModel( mod, buf, &loaded ); + break; + case IDSPRITEHEADER: + Mod_LoadSpriteModel( mod, buf, &loaded, 0 ); + break; + case IDALIASHEADER: + Mod_LoadAliasModel( mod, buf, &loaded ); + break; + case Q1BSP_VERSION: + case HLBSP_VERSION: + case QBSP2_VERSION: + Mod_LoadBrushModel( mod, buf, &loaded ); + break; + default: + Mem_Free( buf ); + if( crash ) Host_Error( "%s has unknown format\n", tempname ); + else Con_Printf( S_ERROR "%s has unknown format\n", tempname ); + return NULL; + } + + if( !loaded ) + { + Mod_FreeModel( mod ); + Mem_Free( buf ); + + if( crash ) Host_Error( "%s couldn't load\n", tempname ); + else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); + + return NULL; + } + else + { + if( world.loading ) + SetBits( mod->flags, MODEL_WORLD ); // mark worldmodel + + if( host.type == HOST_DEDICATED ) + { + if( svgame.physFuncs.Mod_ProcessUserData != NULL ) + { + // let the server.dll load custom data + svgame.physFuncs.Mod_ProcessUserData( mod, true, buf ); + } + } + else + { + if( clgame.drawFuncs.Mod_ProcessUserData != NULL ) + { + // let the client.dll load custom data + clgame.drawFuncs.Mod_ProcessUserData( mod, true, buf ); + } + } + } + + p = &mod_crcinfo[mod - mod_known]; + mod->needload = NL_PRESENT; + + if( FBitSet( p->flags, FCRC_SHOULD_CHECKSUM )) + { + CRC32_t currentCRC; + + CRC32_Init( ¤tCRC ); + CRC32_ProcessBuffer( ¤tCRC, buf, length ); + currentCRC = CRC32_Final( currentCRC ); + + if( FBitSet( p->flags, FCRC_CHECKSUM_DONE )) + { + if( currentCRC != p->initialCRC ) + Host_Error( "Mod_ForName: %s has a bad checksum\n", tempname ); + } + else + { + SetBits( p->flags, FCRC_CHECKSUM_DONE ); + p->initialCRC = currentCRC; + } + } + Mem_Free( buf ); + + return mod; +} + +/* +================== +Mod_ForName + +Loads in a model for the given name +================== +*/ +model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ) +{ + model_t *mod = Mod_FindName( name, trackCRC ); + return Mod_LoadModel( mod, crash ); +} + +/* +================== +Mod_PurgeStudioCache + +free studio cache on change level +================== +*/ +void Mod_PurgeStudioCache( void ) +{ + int i; + + // release previois map + Mod_FreeModel( mod_known ); // world is stuck on slot #0 always + + // we should release all the world submodels + // and clear studio sequences + for( i = 1; i < mod_numknown; i++ ) + { + if( mod_known[i].type == mod_studio ) + mod_known[i].submodels = NULL; + if( mod_known[i].name[0] == '*' ) + Mod_FreeModel( &mod_known[i] ); + mod_known[i].needload = NL_UNREFERENCED; + } + + Mem_EmptyPool( com_studiocache ); + Mod_ClearStudioCache(); +} + +/* +================== +Mod_LoadWorld + +Loads in the map and all submodels +================== +*/ +model_t *Mod_LoadWorld( const char *name, qboolean preload ) +{ + model_t *pworld; + + // already loaded? + if( !Q_stricmp( mod_known->name, name )) + return mod_known; + + // free sequence files on studiomodels + Mod_PurgeStudioCache(); + + // load the newmap + world.loading = true; + pworld = Mod_FindName( name, false ); + if( preload ) Mod_LoadModel( pworld, true ); + world.loading = false; + + ASSERT( pworld == mod_known ); + + return pworld; +} + +/* +================== +Mod_FreeUnused + +Purge all unused models +================== +*/ +void Mod_FreeUnused( void ) +{ + model_t *mod; + int i; + + // never tries to release worldmodel + for( i = 1, mod = &mod_known[1]; i < mod_numknown; i++, mod++ ) + { + if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) + Mod_FreeModel( mod ); + } +} + +/* +=============================================================================== + + MODEL ROUTINES + +=============================================================================== +*/ +/* +=============== +Mod_Calloc + +=============== +*/ +void *Mod_Calloc( int number, size_t size ) +{ + cache_user_t *cu; + + if( number <= 0 || size <= 0 ) return NULL; + cu = (cache_user_t *)Mem_Alloc( com_studiocache, sizeof( cache_user_t ) + number * size ); + cu->data = (void *)cu; // make sure what cu->data is not NULL + + return cu; +} + +/* +=============== +Mod_CacheCheck + +=============== +*/ +void *Mod_CacheCheck( cache_user_t *c ) +{ + return Cache_Check( com_studiocache, c ); +} + +/* +=============== +Mod_LoadCacheFile + +=============== +*/ +void Mod_LoadCacheFile( const char *filename, cache_user_t *cu ) +{ + char modname[MAX_QPATH]; + size_t size; + byte *buf; + + Assert( cu != NULL ); + + if( !COM_CheckString( filename )) + return; + + Q_strncpy( modname, filename, sizeof( modname )); + COM_FixSlashes( modname ); + + buf = FS_LoadFile( modname, &size, false ); + if( !buf || !size ) Host_Error( "LoadCacheFile: ^1can't load %s^7\n", filename ); + cu->data = Mem_Alloc( com_studiocache, size ); + memcpy( cu->data, buf, size ); + Mem_Free( buf ); +} + +/* +=============== +Mod_AliasExtradata + +=============== +*/ +void *Mod_AliasExtradata( model_t *mod ) +{ + if( mod && mod->type == mod_alias ) + return mod->cache.data; + return NULL; +} + +/* +=============== +Mod_StudioExtradata + +=============== +*/ +void *Mod_StudioExtradata( model_t *mod ) +{ + if( mod && mod->type == mod_studio ) + return mod->cache.data; + return NULL; +} + +/* +================== +Mod_ValidateCRC + +================== +*/ +qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ) +{ + model_info_t *p; + model_t *mod; + + mod = Mod_FindName( name, true ); + p = &mod_crcinfo[mod - mod_known]; + + if( !FBitSet( p->flags, FCRC_CHECKSUM_DONE )) + return true; + if( p->initialCRC == crc ) + return true; + return false; +} + +/* +================== +Mod_NeedCRC + +================== +*/ +void Mod_NeedCRC( const char *name, qboolean needCRC ) +{ + model_t *mod; + model_info_t *p; + + mod = Mod_FindName( name, true ); + p = &mod_crcinfo[mod - mod_known]; + + if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); + else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); +} \ No newline at end of file diff --git a/engine/common/net_buffer.c b/engine/common/net_buffer.c new file mode 100644 index 00000000..e8cfbc48 --- /dev/null +++ b/engine/common/net_buffer.c @@ -0,0 +1,688 @@ +/* +net_buffer.c - network bitbuffer io functions +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "protocol.h" +#include "net_buffer.h" +#include "mathlib.h" + +//#define DEBUG_NET_MESSAGES_SEND +//#define DEBUG_NET_MESSAGES_READ + +// precalculated bit masks for WriteUBitLong. +// Using these tables instead of doing the calculations +// gives a 33% speedup in WriteUBitLong. +static dword BitWriteMasks[32][33]; +static dword ExtraMasks[32]; + +short MSG_BigShort( short swap ) +{ + return (swap >> 8)|(swap << 8); +} + +void MSG_InitMasks( void ) +{ + uint startbit, endbit; + uint maskBit, nBitsLeft; + + for( startbit = 0; startbit < 32; startbit++ ) + { + for( nBitsLeft = 0; nBitsLeft < 33; nBitsLeft++ ) + { + endbit = startbit + nBitsLeft; + + BitWriteMasks[startbit][nBitsLeft] = (uint)BIT( startbit ) - 1; + if( endbit < 32 ) BitWriteMasks[startbit][nBitsLeft] |= ~((uint)BIT( endbit ) - 1 ); + } + } + + for( maskBit = 0; maskBit < 32; maskBit++ ) + ExtraMasks[maskBit] = (uint)BIT( maskBit ) - 1; +} + +void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nMaxBits ) +{ + MSG_StartWriting( sb, pData, nBytes, 0, nMaxBits ); + + sb->pDebugName = pDebugName; +} + +void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits ) +{ + // make sure it's dword aligned and padded. + Assert(((dword)pData & 3 ) == 0 ); + + sb->pDebugName = "Unnamed"; + sb->pData = (byte *)pData; + + if( nBits == -1 ) + { + sb->nDataBits = nBytes << 3; + } + else + { + Assert( nBits <= nBytes * 8 ); + sb->nDataBits = nBits; + } + + sb->iCurBit = iStartBit; + sb->bOverflow = false; +} + +/* +======================= +MSG_Clear + +for clearing overflowed buffer +======================= +*/ +void MSG_Clear( sizebuf_t *sb ) +{ + sb->iCurBit = 0; + sb->bOverflow = false; +} + +static qboolean MSG_Overflow( sizebuf_t *sb, int nBits ) +{ + if( sb->iCurBit + nBits > sb->nDataBits ) + sb->bOverflow = true; + return sb->bOverflow; +} + +qboolean MSG_CheckOverflow( sizebuf_t *sb ) +{ + return MSG_Overflow( sb, 0 ); +} + +int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ) +{ + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + bitPos += sb->iCurBit; + break; + case SEEK_SET: + break; + case SEEK_END: + bitPos += sb->nDataBits; + break; + default: + return -1; + } + + if( bitPos < 0 || bitPos > sb->nDataBits ) + return -1; + + sb->iCurBit = bitPos; + + return 0; +} + +void MSG_SeekToByte( sizebuf_t *sb, int bytePos ) +{ + sb->iCurBit = bytePos << 3; +} + +void MSG_WriteOneBit( sizebuf_t *sb, int nValue ) +{ + if( !MSG_Overflow( sb, 1 )) + { + if( nValue ) sb->pData[sb->iCurBit>>3] |= BIT( sb->iCurBit & 7 ); + else sb->pData[sb->iCurBit>>3] &= ~BIT( sb->iCurBit & 7 ); + + sb->iCurBit++; + } +} + +void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ) +{ + Assert( numbits >= 0 && numbits <= 32 ); + + // bounds checking.. + if(( sb->iCurBit + numbits ) > sb->nDataBits ) + { + sb->bOverflow = true; + sb->iCurBit = sb->nDataBits; + } + else + { + int nBitsLeft = numbits; + int iCurBit = sb->iCurBit; + uint iDWord = iCurBit >> 5; // Mask in a dword. + dword iCurBitMasked; + int nBitsWritten; + + Assert(( iDWord * 4 + sizeof( long )) <= (uint)MSG_GetMaxBytes( sb )); + + iCurBitMasked = iCurBit & 31; + ((dword *)sb->pData)[iDWord] &= BitWriteMasks[iCurBitMasked][nBitsLeft]; + ((dword *)sb->pData)[iDWord] |= curData << iCurBitMasked; + + // did it span a dword? + nBitsWritten = 32 - iCurBitMasked; + + if( nBitsWritten < nBitsLeft ) + { + nBitsLeft -= nBitsWritten; + iCurBit += nBitsWritten; + curData >>= nBitsWritten; + + iCurBitMasked = iCurBit & 31; + ((dword *)sb->pData)[iDWord+1] &= BitWriteMasks[iCurBitMasked][nBitsLeft]; + ((dword *)sb->pData)[iDWord+1] |= curData << iCurBitMasked; + } + sb->iCurBit += numbits; + } +} + +/* +======================= +MSG_WriteSBitLong + +sign bit comes first +======================= +*/ +void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ) +{ + // do we have a valid # of bits to encode with? + Assert( numbits >= 1 && numbits <= 32 ); + + // NOTE: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. + // (Some old code writes direct integers right into the buffer). + if( data < 0 ) + { + MSG_WriteUBitLong( sb, (uint)( 0x80000000 + data ), numbits - 1 ); + MSG_WriteOneBit( sb, 1 ); + } + else + { + MSG_WriteUBitLong( sb, (uint)data, numbits - 1 ); + MSG_WriteOneBit( sb, 0 ); + } +} + +void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned ) +{ + if( bSigned ) + MSG_WriteSBitLong( sb, (int)data, numbits ); + else MSG_WriteUBitLong( sb, data, numbits ); +} + +qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ) +{ + byte *pOut = (byte *)pData; + int nBitsLeft = nBits; + + // get output dword-aligned. + while((( dword )pOut & 3 ) != 0 && nBitsLeft >= 8 ) + { + MSG_WriteUBitLong( sb, *pOut, 8 ); + + nBitsLeft -= 8; + ++pOut; + } + + // read dwords. + while( nBitsLeft >= 32 ) + { + MSG_WriteUBitLong( sb, *(( dword *)pOut ), 32 ); + + pOut += sizeof( dword ); + nBitsLeft -= 32; + } + + // read the remaining bytes. + while( nBitsLeft >= 8 ) + { + MSG_WriteUBitLong( sb, *pOut, 8 ); + + nBitsLeft -= 8; + ++pOut; + } + + // Read the remaining bits. + if( nBitsLeft ) + { + MSG_WriteUBitLong( sb, *pOut, nBitsLeft ); + } + + return !sb->bOverflow; +} + +void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits ) +{ + uint mask, shift; + int d; + + // clamp the angle before receiving + fAngle = fmod( fAngle, 360.0f ); + if( fAngle < 0 ) fAngle += 360.0f; + + shift = ( 1 << numbits ); + mask = shift - 1; + + d = (int)(( fAngle * shift ) / 360.0f ); + d &= mask; + + MSG_WriteUBitLong( sb, (uint)d, numbits ); +} + +void MSG_WriteCoord( sizebuf_t *sb, float val ) +{ + // g-cont. we loose precision here but keep old size of coord variable! + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) + MSG_WriteShort( sb, (int)( val * 2.0f )); + else MSG_WriteShort( sb, (int)( val * 8.0f )); +} + +void MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa ) +{ + MSG_WriteCoord( sb, fa[0] ); + MSG_WriteCoord( sb, fa[1] ); + MSG_WriteCoord( sb, fa[2] ); +} + +void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ) +{ + MSG_WriteBitAngle( sb, fa[0], 16 ); + MSG_WriteBitAngle( sb, fa[1], 16 ); + MSG_WriteBitAngle( sb, fa[2], 16 ); +} + +void MSG_WriteBitFloat( sizebuf_t *sb, float val ) +{ + long intVal; + + Assert( sizeof( long ) == sizeof( float )); + Assert( sizeof( float ) == 4 ); + + intVal = *((long *)&val ); + MSG_WriteUBitLong( sb, intVal, 32 ); +} + +void MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name ) +{ +#ifdef DEBUG_NET_MESSAGES_SEND + if( name != NULL ) + { + // get custom name + Con_Printf( "^1sv^7 write: %s\n", name ); + } + else if( type == NS_SERVER ) + { + if( cmd >= 0 && cmd <= svc_lastmsg ) + { + // get engine message name + Con_Printf( "^1sv^7 write: %s\n", svc_strings[cmd] ); + } + } + else if( type == NS_CLIENT ) + { + if( cmd >= 0 && cmd <= clc_lastmsg ) + { + Con_Printf( "^1cl^7 write: %s\n", clc_strings[cmd] ); + } + } +#endif + MSG_WriteUBitLong( sb, cmd, sizeof( byte ) << 3 ); +} + +void MSG_WriteChar( sizebuf_t *sb, int val ) +{ + MSG_WriteSBitLong( sb, val, sizeof( char ) << 3 ); +} + +void MSG_WriteByte( sizebuf_t *sb, int val ) +{ + MSG_WriteUBitLong( sb, val, sizeof( byte ) << 3 ); +} + +void MSG_WriteShort( sizebuf_t *sb, int val ) +{ + MSG_WriteSBitLong( sb, val, sizeof(short ) << 3 ); +} + +void MSG_WriteWord( sizebuf_t *sb, int val ) +{ + MSG_WriteUBitLong( sb, val, sizeof( word ) << 3 ); +} + +void MSG_WriteLong( sizebuf_t *sb, long val ) +{ + MSG_WriteSBitLong( sb, val, sizeof( long ) << 3 ); +} + +void MSG_WriteDword( sizebuf_t *sb, dword val ) +{ + MSG_WriteUBitLong( sb, val, sizeof( dword ) << 3 ); +} + +void MSG_WriteFloat( sizebuf_t *sb, float val ) +{ + MSG_WriteBits( sb, &val, sizeof( val ) << 3 ); +} + +qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ) +{ + return MSG_WriteBits( sb, pBuf, nBytes << 3 ); +} + +qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ) +{ + if( pStr ) + { + do + { + MSG_WriteChar( sb, *pStr ); + pStr++; + } while( *( pStr - 1 )); + } + else MSG_WriteChar( sb, 0 ); + + return !sb->bOverflow; +} + +int MSG_ReadOneBit( sizebuf_t *sb ) +{ + if( !MSG_Overflow( sb, 1 )) + { + int value = sb->pData[sb->iCurBit >> 3] & (1 << ( sb->iCurBit & 7 )); + sb->iCurBit++; + return !!value; + } + return 0; +} + +uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits ) +{ + int idword1; + uint dword1, ret; + + if( numbits == 8 ) + { + int leftBits = MSG_GetNumBitsLeft( sb ); + + if( leftBits >= 0 && leftBits < 8 ) + return 0; // end of message + } + + if(( sb->iCurBit + numbits ) > sb->nDataBits ) + { + sb->bOverflow = true; + sb->iCurBit = sb->nDataBits; + return 0; + } + + Assert( numbits > 0 && numbits <= 32 ); + + // Read the current dword. + idword1 = sb->iCurBit >> 5; + dword1 = ((uint *)sb->pData)[idword1]; + dword1 >>= ( sb->iCurBit & 31 ); // get the bits we're interested in. + + sb->iCurBit += numbits; + ret = dword1; + + // Does it span this dword? + if(( sb->iCurBit - 1 ) >> 5 == idword1 ) + { + if( numbits != 32 ) + ret &= ExtraMasks[numbits]; + } + else + { + int nExtraBits = sb->iCurBit & 31; + uint dword2 = ((uint *)sb->pData)[idword1+1] & ExtraMasks[nExtraBits]; + + // no need to mask since we hit the end of the dword. + // shift the second dword's part into the high bits. + ret |= (dword2 << ( numbits - nExtraBits )); + } + return ret; +} + +float MSG_ReadBitFloat( sizebuf_t *sb ) +{ + long val; + int bit, byte; + + Assert( sizeof( float ) == sizeof( long )); + Assert( sizeof( float ) == 4 ); + + if( MSG_Overflow( sb, 32 )) + return 0.0f; + + bit = sb->iCurBit & 0x7; + byte = sb->iCurBit >> 3; + + val = sb->pData[byte] >> bit; + val |= ((int)sb->pData[byte + 1]) << ( 8 - bit ); + val |= ((int)sb->pData[byte + 2]) << ( 16 - bit ); + val |= ((int)sb->pData[byte + 3]) << ( 24 - bit ); + + if( bit != 0 ) + val |= ((int)sb->pData[byte + 4]) << ( 32 - bit ); + sb->iCurBit += 32; + + return *((float *)&val); +} + +qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ) +{ + byte *pOut = (byte *)pOutData; + int nBitsLeft = nBits; + + // get output dword-aligned. + while((( dword )pOut & 3) != 0 && nBitsLeft >= 8 ) + { + *pOut = (byte)MSG_ReadUBitLong( sb, 8 ); + ++pOut; + nBitsLeft -= 8; + } + + // read dwords. + while( nBitsLeft >= 32 ) + { + *((dword *)pOut) = MSG_ReadUBitLong( sb, 32 ); + pOut += sizeof( dword ); + nBitsLeft -= 32; + } + + // read the remaining bytes. + while( nBitsLeft >= 8 ) + { + *pOut = MSG_ReadUBitLong( sb, 8 ); + ++pOut; + nBitsLeft -= 8; + } + + // read the remaining bits. + if( nBitsLeft ) + { + *pOut = MSG_ReadUBitLong( sb, nBitsLeft ); + } + + return !sb->bOverflow; +} + +float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ) +{ + float fReturn, shift; + int i; + + shift = (float)( 1 << numbits ); + + i = MSG_ReadUBitLong( sb, numbits ); + fReturn = (float)i * ( 360.0f / shift ); + + // clamp the finale angle + if( fReturn < -180.0f ) fReturn += 360.0f; + else if( fReturn > 180.0f ) fReturn -= 360.0f; + + return fReturn; +} + +// Append numbits least significant bits from data to the current bit stream +int MSG_ReadSBitLong( sizebuf_t *sb, int numbits ) +{ + int r, sign; + + r = MSG_ReadUBitLong( sb, numbits - 1 ); + + // NOTE: it does this wierdness here so it's bit-compatible with regular integer data in the buffer. + // (Some old code writes direct integers right into the buffer). + sign = MSG_ReadOneBit( sb ); + if( sign ) r = -( BIT( numbits - 1 ) - r ); + + return r; +} + +uint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned ) +{ + if( bSigned ) + return (uint)MSG_ReadSBitLong( sb, numbits ); + return MSG_ReadUBitLong( sb, numbits ); +} + +int MSG_ReadCmd( sizebuf_t *sb, netsrc_t type ) +{ + int cmd = MSG_ReadUBitLong( sb, sizeof( byte ) << 3 ); + +#ifdef DEBUG_NET_MESSAGES_READ + if( type == NS_SERVER ) + { + Con_Printf( "^1cl^7 read: %s\n", CL_MsgInfo( cmd )); + } + else if( cmd >= 0 && cmd <= clc_lastmsg ) + { + Con_Printf( "^1sv^7 read: %s\n", clc_strings[cmd] ); + } +#endif + return cmd; +} + +int MSG_ReadChar( sizebuf_t *sb ) +{ + return MSG_ReadSBitLong( sb, sizeof( char ) << 3 ); +} + +int MSG_ReadByte( sizebuf_t *sb ) +{ + return MSG_ReadUBitLong( sb, sizeof( byte ) << 3 ); +} + +int MSG_ReadShort( sizebuf_t *sb ) +{ + return MSG_ReadSBitLong( sb, sizeof( short ) << 3 ); +} + +int MSG_ReadWord( sizebuf_t *sb ) +{ + return MSG_ReadUBitLong( sb, sizeof( word ) << 3 ); +} + +float MSG_ReadCoord( sizebuf_t *sb ) +{ + // g-cont. we loose precision here but keep old size of coord variable! + if( FBitSet( host.features, ENGINE_WRITE_LARGE_COORD )) + return (float)(MSG_ReadShort( sb ) * ( 1.0f / 2.0f )); + return (float)(MSG_ReadShort( sb ) * ( 1.0f / 8.0f )); +} + +void MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa ) +{ + fa[0] = MSG_ReadCoord( sb ); + fa[1] = MSG_ReadCoord( sb ); + fa[2] = MSG_ReadCoord( sb ); +} + +void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa ) +{ + fa[0] = MSG_ReadBitAngle( sb, 16 ); + fa[1] = MSG_ReadBitAngle( sb, 16 ); + fa[2] = MSG_ReadBitAngle( sb, 16 ); +} + + +long MSG_ReadLong( sizebuf_t *sb ) +{ + return MSG_ReadSBitLong( sb, sizeof( long ) << 3 ); +} + +dword MSG_ReadDword( sizebuf_t *sb ) +{ + return MSG_ReadUBitLong( sb, sizeof( dword ) << 3 ); +} + +float MSG_ReadFloat( sizebuf_t *sb ) +{ + float ret; + + Assert( sizeof( ret ) == 4 ); + + MSG_ReadBits( sb, &ret, 32 ); + + return ret; +} + +qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes ) +{ + return MSG_ReadBits( sb, pOut, nBytes << 3 ); +} + +char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ) +{ + static char string[2048]; + int l = 0, c; + + do + { + // use MSG_ReadByte so -1 is out of bounds + c = MSG_ReadByte( sb ); + + if( c == 0 ) break; + else if( bLine && c == '\n' ) + break; + + // translate all fmt spec to avoid crash bugs + // NOTE: but game strings leave unchanged. see pfnWriteString for details + if( c == '%' ) c = '.'; + + string[l] = c; + l++; + } while( l < sizeof( string ) - 1 ); + string[l] = 0; // terminator + + return string; +} + +void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ) +{ + int i, endbit = startbit + bitstoremove; + int remaining_to_end = sb->nDataBits - endbit; + sizebuf_t temp; + + MSG_StartWriting( &temp, sb->pData, MSG_GetMaxBytes( sb ), startbit, -1 ); + MSG_SeekToBit( sb, endbit, SEEK_SET ); + + for( i = 0; i < remaining_to_end; i++ ) + { + MSG_WriteOneBit( &temp, MSG_ReadOneBit( sb )); + } + + MSG_SeekToBit( sb, startbit, SEEK_SET ); + sb->nDataBits -= bitstoremove; +} \ No newline at end of file diff --git a/engine/common/net_buffer.h b/engine/common/net_buffer.h new file mode 100644 index 00000000..67e36762 --- /dev/null +++ b/engine/common/net_buffer.h @@ -0,0 +1,135 @@ +/* +net_buffer.h - network message io functions +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef NET_BUFFER_H +#define NET_BUFFER_H + +#include "features.h" + +/* +============================================================================== + + MESSAGE IO FUNCTIONS + Handles byte ordering and avoids alignment errors +============================================================================== +*/ + +// Pad a number so it lies on an N byte boundary. +// So PAD_NUMBER(0,4) is 0 and PAD_NUMBER(1,4) is 4 +#define PAD_NUMBER( num, boundary ) ((( num ) + (( boundary ) - 1 )) / ( boundary )) * ( boundary ) + +_inline int BitByte( int bits ) +{ + return PAD_NUMBER( bits, 8 ) >> 3; +} + +typedef struct sizebuf_s +{ + qboolean bOverflow; // overflow reading or writing + const char *pDebugName; // buffer name (pointer to const name) + + byte *pData; + int iCurBit; + int nDataBits; +} sizebuf_t; + +#define MSG_StartReading MSG_StartWriting +#define MSG_GetNumBytesRead MSG_GetNumBytesWritten +#define MSG_GetRealBytesRead MSG_GetRealBytesWritten +#define MSG_GetNumBitsRead MSG_GetNumBitsWritten +#define MSG_ReadBitAngles MSG_ReadBitVec3Coord +#define MSG_ReadString( sb ) MSG_ReadStringExt( sb, false ) +#define MSG_ReadStringLine( sb ) MSG_ReadStringExt( sb, true ) +#define MSG_ReadAngle( sb ) (float)(MSG_ReadChar( sb ) * ( 360.0f / 256.0f )) +#define MSG_Init( sb, name, data, bytes ) MSG_InitExt( sb, name, data, bytes, -1 ) + +// common functions +void MSG_InitExt( sizebuf_t *sb, const char *pDebugName, void *pData, int nBytes, int nMaxBits ); +void MSG_InitMasks( void ); // called once at startup engine +int MSG_SeekToBit( sizebuf_t *sb, int bitPos, int whence ); +void MSG_ExciseBits( sizebuf_t *sb, int startbit, int bitstoremove ); +_inline int MSG_TellBit( sizebuf_t *sb ) { return sb->iCurBit; } +_inline const char *MSG_GetName( sizebuf_t *sb ) { return sb->pDebugName; } +qboolean MSG_CheckOverflow( sizebuf_t *sb ); +short MSG_BigShort( short swap ); + +// init writing +void MSG_StartWriting( sizebuf_t *sb, void *pData, int nBytes, int iStartBit, int nBits ); +void MSG_Clear( sizebuf_t *sb ); + +// Bit-write functions +void MSG_WriteOneBit( sizebuf_t *sb, int nValue ); +void MSG_WriteUBitLong( sizebuf_t *sb, uint curData, int numbits ); +void MSG_WriteSBitLong( sizebuf_t *sb, int data, int numbits ); +void MSG_WriteBitLong( sizebuf_t *sb, uint data, int numbits, qboolean bSigned ); +qboolean MSG_WriteBits( sizebuf_t *sb, const void *pData, int nBits ); +void MSG_WriteBitAngle( sizebuf_t *sb, float fAngle, int numbits ); +void MSG_WriteBitFloat( sizebuf_t *sb, float val ); + +// Byte-write functions +#define MSG_BeginServerCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_SERVER, NULL ) +#define MSG_BeginClientCmd( sb, cmd ) MSG_WriteCmdExt( sb, cmd, NS_CLIENT, NULL ) +void MSG_WriteCmdExt( sizebuf_t *sb, int cmd, netsrc_t type, const char *name ); // message marker +void MSG_WriteChar( sizebuf_t *sb, int val ); +void MSG_WriteByte( sizebuf_t *sb, int val ); +void MSG_WriteShort( sizebuf_t *sb, int val ); +void MSG_WriteWord( sizebuf_t *sb, int val ); +void MSG_WriteLong( sizebuf_t *sb, long val ); +void MSG_WriteDword( sizebuf_t *sb, dword val ); +void MSG_WriteCoord( sizebuf_t *sb, float val ); +void MSG_WriteFloat( sizebuf_t *sb, float val ); +void MSG_WriteVec3Coord( sizebuf_t *sb, const float *fa ); +void MSG_WriteVec3Angles( sizebuf_t *sb, const float *fa ); +qboolean MSG_WriteBytes( sizebuf_t *sb, const void *pBuf, int nBytes ); // same as MSG_WriteData +qboolean MSG_WriteString( sizebuf_t *sb, const char *pStr ); // returns false if it overflows the buffer. + +// helper functions +_inline int MSG_GetNumBytesWritten( sizebuf_t *sb ) { return BitByte( sb->iCurBit ); } +_inline int MSG_GetRealBytesWritten( sizebuf_t *sb ) { return sb->iCurBit >> 3; } // unpadded +_inline int MSG_GetNumBitsWritten( sizebuf_t *sb ) { return sb->iCurBit; } +_inline int MSG_GetMaxBits( sizebuf_t *sb ) { return sb->nDataBits; } +_inline int MSG_GetMaxBytes( sizebuf_t *sb ) { return sb->nDataBits >> 3; } +_inline int MSG_GetNumBitsLeft( sizebuf_t *sb ) { return sb->nDataBits - sb->iCurBit; } +_inline int MSG_GetNumBytesLeft( sizebuf_t *sb ) { return MSG_GetNumBitsLeft( sb ) >> 3; } +_inline byte *MSG_GetData( sizebuf_t *sb ) { return sb->pData; } +_inline byte *MSG_GetBuf( sizebuf_t *sb ) { return sb->pData; } // just an alias + +// Bit-read functions +int MSG_ReadOneBit( sizebuf_t *sb ); +float MSG_ReadBitFloat( sizebuf_t *sb ); +qboolean MSG_ReadBits( sizebuf_t *sb, void *pOutData, int nBits ); +float MSG_ReadBitAngle( sizebuf_t *sb, int numbits ); +int MSG_ReadSBitLong( sizebuf_t *sb, int numbits ); +uint MSG_ReadUBitLong( sizebuf_t *sb, int numbits ); +uint MSG_ReadBitLong( sizebuf_t *sb, int numbits, qboolean bSigned ); + +// Byte-read functions +#define MSG_ReadServerCmd( sb ) MSG_ReadCmd( sb, NS_SERVER ) +#define MSG_ReadClientCmd( sb ) MSG_ReadCmd( sb, NS_CLIENT ) +int MSG_ReadCmd( sizebuf_t *sb, netsrc_t type ); // message marker +int MSG_ReadChar( sizebuf_t *sb ); +int MSG_ReadByte( sizebuf_t *sb ); +int MSG_ReadShort( sizebuf_t *sb ); +int MSG_ReadWord( sizebuf_t *sb ); +long MSG_ReadLong( sizebuf_t *sb ); +dword MSG_ReadDword( sizebuf_t *sb ); +float MSG_ReadCoord( sizebuf_t *sb ); +float MSG_ReadFloat( sizebuf_t *sb ); +void MSG_ReadVec3Coord( sizebuf_t *sb, vec3_t fa ); +void MSG_ReadVec3Angles( sizebuf_t *sb, vec3_t fa ); +qboolean MSG_ReadBytes( sizebuf_t *sb, void *pOut, int nBytes ); +char *MSG_ReadStringExt( sizebuf_t *sb, qboolean bLine ); + +#endif//NET_BUFFER_H \ No newline at end of file diff --git a/engine/common/net_chan.c b/engine/common/net_chan.c new file mode 100644 index 00000000..b471eb11 --- /dev/null +++ b/engine/common/net_chan.c @@ -0,0 +1,1748 @@ +/* +net_chan.c - network channel +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "netchan.h" +#include "mathlib.h" +#include "net_encode.h" +#include "protocol.h" + +#define MAKE_FRAGID( id, count ) ((( id & 0xffff ) << 16 ) | ( count & 0xffff )) +#define FRAG_GETID( fragid ) (( fragid >> 16 ) & 0xffff ) +#define FRAG_GETCOUNT( fragid ) ( fragid & 0xffff ) + +#define UDP_HEADER_SIZE 28 + +#define FLOW_AVG ( 2.0 / 3.0 ) // how fast to converge flow estimates +#define FLOW_INTERVAL 0.1 // don't compute more often than this +#define MAX_RELIABLE_PAYLOAD 1400 // biggest packet that has frag and or reliable data + +// forward declarations +void Netchan_FlushIncoming( netchan_t *chan, int stream ); +void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf ); + +/* +packet header ( size in bits ) +------------- +31 sequence +1 does this message contain a reliable payload +31 acknowledge sequence +1 acknowledge receipt of even/odd message +16 qport + +The remote connection never knows if it missed a reliable message, the +local side detects that it has been dropped by seeing a sequence acknowledge +higher thatn the last reliable sequence, but without the correct evon/odd +bit for the reliable set. + +If the sender notices that a reliable message has been dropped, it will be +retransmitted. It will not be retransmitted again until a message after +the retransmit has been acknowledged and the reliable still failed to get there. + +if the sequence number is -1, the packet should be handled without a netcon + +The reliable message can be added to at any time by doing +MSG_Write* (&netchan->message, ). + +If the message buffer is overflowed, either by a single message, or by +multiple frames worth piling up while the last reliable transmit goes +unacknowledged, the netchan signals a fatal error. + +Reliable messages are allways placed first in a packet, then the unreliable +message is included if there is sufficient room. + +To the receiver, there is no distinction between the reliable and unreliable +parts of the message, they are just processed out as a single larger message. + +Illogical packet sequence numbers cause the packet to be dropped, but do +not kill the connection. This, combined with the tight window of valid +reliable acknowledgement numbers provides protection against malicious +address spoofing. + +The qport field is a workaround for bad address translating routers that +sometimes remap the client's source port on a packet during gameplay. + +If the base part of the net address matches and the qport matches, then the +channel matches even if the IP port differs. The IP port should be updated +to the new value before sending out any replies. + + +If there is no information that needs to be transfered on a given frame, +such as during the connection stage while waiting for the client to load, +then a packet only needs to be delivered if there is something in the +unacknowledged reliable +*/ +convar_t *net_showpackets; +convar_t *net_chokeloopback; +convar_t *net_showdrop; +convar_t *net_speeds; +convar_t *net_qport; + +int net_drop; +netadr_t net_from; +sizebuf_t net_message; +byte *net_mempool; +byte net_message_buffer[NET_MAX_MESSAGE]; + +const char *ns_strings[NS_COUNT] = +{ + "Client", + "Server", +}; + +/* +=============== +Netchan_Init +=============== +*/ +void Netchan_Init( void ) +{ + int port; + + // pick a port value that should be nice and random + port = COM_RandomLong( 1, 65535 ); + + net_showpackets = Cvar_Get ("net_showpackets", "0", 0, "show network packets" ); + net_chokeloopback = Cvar_Get( "net_chokeloop", "0", 0, "apply bandwidth choke to loopback packets" ); + net_showdrop = Cvar_Get( "net_showdrop", "0", 0, "show packets that are dropped" ); + net_speeds = Cvar_Get( "net_speeds", "0", FCVAR_ARCHIVE, "show network packets" ); + net_qport = Cvar_Get( "net_qport", va( "%i", port ), FCVAR_READ_ONLY, "current quake netport" ); + + net_mempool = Mem_AllocPool( "Network Pool" ); + + MSG_InitMasks(); // initialize bit-masks +} + +void Netchan_Shutdown( void ) +{ + Mem_FreePool( &net_mempool ); +} + +void Netchan_ReportFlow( netchan_t *chan ) +{ + char incoming[64]; + char outgoing[64]; + + if( CL_IsPlaybackDemo( )) + return; + + Assert( chan != NULL ); + + Q_strcpy( incoming, Q_pretifymem((float)chan->flow[FLOW_INCOMING].totalbytes, 3 )); + Q_strcpy( outgoing, Q_pretifymem((float)chan->flow[FLOW_OUTGOING].totalbytes, 3 )); + + Con_DPrintf( "Signon network traffic: %s from server, %s to server\n", incoming, outgoing ); +} + +/* +============== +Netchan_IsLocal + +detect a loopback message +============== +*/ +qboolean Netchan_IsLocal( netchan_t *chan ) +{ + if( !NET_IsActive() || NET_IsLocalAddress( chan->remote_address )) + return true; + return false; +} + +/* +============== +Netchan_Setup + +called to open a channel to a remote system +============== +*/ +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void * )) +{ + Netchan_Clear( chan ); + + memset( chan, 0, sizeof( *chan )); + + chan->sock = sock; + chan->remote_address = adr; + chan->last_received = host.realtime; + chan->connect_time = host.realtime; + chan->incoming_sequence = 0; + chan->outgoing_sequence = 1; + chan->rate = DEFAULT_RATE; + chan->qport = qport; + chan->client = client; + chan->pfnBlockSize = pfnBlockSize; + + MSG_Init( &chan->message, "NetData", chan->message_buf, sizeof( chan->message_buf )); +} + +/* +============================== +Netchan_IncomingReady + +============================== +*/ +qboolean Netchan_IncomingReady( netchan_t *chan ) +{ + int i; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( chan->incomingready[i] ) + return true; + } + + return false; +} + +/* +=============== +Netchan_CanPacket + +Returns true if the bandwidth choke isn't active +================ +*/ +qboolean Netchan_CanPacket( netchan_t *chan, qboolean choke ) +{ + // never choke loopback packets. + if( !choke || !net_chokeloopback->value && NET_IsLocalAddress( chan->remote_address )) + { + chan->cleartime = host.realtime; + return true; + } + + return chan->cleartime < host.realtime ? true : false; +} + +/* +============================== +Netchan_UnlinkFragment + +============================== +*/ +void Netchan_UnlinkFragment( fragbuf_t *buf, fragbuf_t **list ) +{ + fragbuf_t *search; + + if( !list ) return; + + // at head of list + if( buf == *list ) + { + // remove first element + *list = buf->next; + + // destroy remnant + Mem_Free( buf ); + return; + } + + search = *list; + while( search->next ) + { + if( search->next == buf ) + { + search->next = buf->next; + + // destroy remnant + Mem_Free( buf ); + return; + } + search = search->next; + } +} + +/* +============================== +Netchan_ClearFragbufs + +============================== +*/ +void Netchan_ClearFragbufs( fragbuf_t **ppbuf ) +{ + fragbuf_t *buf, *n; + + if( !ppbuf ) return; + + // Throw away any that are sitting around + buf = *ppbuf; + + while( buf ) + { + n = buf->next; + Mem_Free( buf ); + buf = n; + } + + *ppbuf = NULL; +} + +/* +============================== +Netchan_ClearFragments + +============================== +*/ +void Netchan_ClearFragments( netchan_t *chan ) +{ + fragbufwaiting_t *wait, *next; + int i; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + wait = chan->waitlist[i]; + + while( wait ) + { + next = wait->next; + Netchan_ClearFragbufs( &wait->fragbufs ); + Mem_Free( wait ); + wait = next; + } + chan->waitlist[i] = NULL; + + Netchan_ClearFragbufs( &chan->fragbufs[i] ); + Netchan_FlushIncoming( chan, i ); + } +} + +/* +============================== +Netchan_Clear + +============================== +*/ +void Netchan_Clear( netchan_t *chan ) +{ + int i; + + Netchan_ClearFragments( chan ); + + chan->cleartime = 0.0; + chan->reliable_length = 0; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + chan->reliable_fragid[i] = 0; + chan->reliable_fragment[i] = 0; + chan->fragbufcount[i] = 0; + chan->frag_startpos[i] = 0; + chan->frag_length[i] = 0; + chan->incomingready[i] = false; + } + + if( chan->tempbuffer ) + { + Mem_Free( chan->tempbuffer ); + chan->tempbuffer = NULL; + } + chan->tempbuffersize = 0; + + memset( chan->flow, 0, sizeof( chan->flow )); +} + +/* +=============== +Netchan_OutOfBand + +Sends an out-of-band datagram +================ +*/ +void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ) +{ + byte send_buf[MAX_PRINT_MSG]; + sizebuf_t send; + + // write the packet header + MSG_Init( &send, "SequencePacket", send_buf, sizeof( send_buf )); + + MSG_WriteLong( &send, NET_HEADER_OUTOFBANDPACKET ); // -1 sequence means out of band + MSG_WriteBytes( &send, data, length ); + + if( !CL_IsPlaybackDemo( )) + { + // send the datagram + NET_SendPacket( net_socket, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), adr ); + } +} + +/* +=============== +Netchan_OutOfBandPrint + +Sends a text message in an out-of-band datagram +================ +*/ +void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, char *format, ... ) +{ + char string[MAX_PRINT_MSG]; + va_list argptr; + + va_start( argptr, format ); + Q_vsnprintf( string, sizeof( string ) - 1, format, argptr ); + va_end( argptr ); + + Netchan_OutOfBand( net_socket, adr, Q_strlen( string ), string ); +} + +/* +============================== +Netchan_AllocFragbuf + +============================== +*/ +fragbuf_t *Netchan_AllocFragbuf( void ) +{ + fragbuf_t *buf; + + buf = (fragbuf_t *)Mem_Alloc( net_mempool, sizeof( fragbuf_t )); + MSG_Init( &buf->frag_message, "Frag Message", buf->frag_message_buf, sizeof( buf->frag_message_buf )); + + return buf; +} + +/* +============================== +Netchan_AddFragbufToTail + +============================== +*/ +void Netchan_AddFragbufToTail( fragbufwaiting_t *wait, fragbuf_t *buf ) +{ + fragbuf_t *p; + + buf->next = NULL; + wait->fragbufcount++; + p = wait->fragbufs; + + if( p ) + { + while( p->next ) + p = p->next; + p->next = buf; + } + else wait->fragbufs = buf; +} + +/* +============================== +Netchan_UpdateFlow + +============================== +*/ +void Netchan_UpdateFlow( netchan_t *chan ) +{ + float faccumulatedtime = 0.0; + int i, bytes = 0; + int flow, start; + + if( !chan ) return; + + for( flow = 0; flow < 2; flow++ ) + { + flow_t *pflow = &chan->flow[flow]; + + if(( host.realtime - pflow->nextcompute ) < FLOW_INTERVAL ) + continue; + + pflow->nextcompute = host.realtime + FLOW_INTERVAL; + start = pflow->current - 1; + + // compute data flow rate + for( i = 0; i < MASK_LATENT; i++ ) + { + flowstats_t *pprev = &pflow->stats[(start - i) & MASK_LATENT]; + flowstats_t *pstat = &pflow->stats[(start - i - 1) & MASK_LATENT]; + + faccumulatedtime += ( pprev->time - pstat->time ); + bytes += pstat->size; + } + + pflow->kbytespersec = (faccumulatedtime == 0.0f) ? 0.0f : bytes / faccumulatedtime / 1024.0f; + pflow->avgkbytespersec = pflow->avgkbytespersec * FLOW_AVG + pflow->kbytespersec * (1.0 - FLOW_AVG); + } +} + +/* +============================== +Netchan_FragSend + +Fragmentation buffer is full and user is prepared to send +============================== +*/ +void Netchan_FragSend( netchan_t *chan ) +{ + fragbufwaiting_t *wait; + int i; + + if( !chan ) return; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + // already something queued up, just leave in waitlist + if( chan->fragbufs[i] ) continue; + + wait = chan->waitlist[i]; + + // nothing to queue? + if( !wait ) continue; + + chan->waitlist[i] = wait->next; + + wait->next = NULL; + + // copy in to fragbuf + chan->fragbufs[i] = wait->fragbufs; + chan->fragbufcount[i] = wait->fragbufcount; + + // throw away wait list + Mem_Free( wait ); + } +} + +/* +============================== +Netchan_AddBufferToList + +============================== +*/ +void Netchan_AddBufferToList( fragbuf_t **pplist, fragbuf_t *pbuf ) +{ + // Find best slot + fragbuf_t *pprev, *n; + int id1, id2; + + pbuf->next = NULL; + + if( !pplist ) + return; + + if( !*pplist ) + { + pbuf->next = *pplist; + *pplist = pbuf; + return; + } + + pprev = *pplist; + while( pprev->next ) + { + n = pprev->next; // next item in list + id1 = FRAG_GETID( n->bufferid ); + id2 = FRAG_GETID( pbuf->bufferid ); + + if( id1 > id2 ) + { + // insert here + pbuf->next = n->next; + pprev->next = pbuf; + return; + } + pprev = pprev->next; + } + + // insert at end + pprev->next = pbuf; +} + +/* +============================== +Netchan_CreateFragments_ + +============================== +*/ +static void Netchan_CreateFragments_( netchan_t *chan, sizebuf_t *msg ) +{ + fragbuf_t *buf; + int chunksize; + int remaining; + int bytes, pos; + int bufferid = 1; + fragbufwaiting_t *wait, *p; + + if( MSG_GetNumBytesWritten( msg ) == 0 ) + return; + + if( chan->pfnBlockSize != NULL ) + chunksize = chan->pfnBlockSize( chan->client ); + else chunksize = FRAGMENT_MAX_SIZE; // fallback + + wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); + + if( !LZSS_IsCompressed( MSG_GetData( msg ))) + { + uint uCompressedSize = 0; + uint uSourceSize = MSG_GetNumBytesWritten( msg ); + byte *pbOut = LZSS_Compress( msg->pData, uSourceSize, &uCompressedSize ); + + if( pbOut && uCompressedSize > 0 && uCompressedSize < uSourceSize ) + { + Con_DPrintf( "Compressing split packet (%d -> %d bytes)\n", uSourceSize, uCompressedSize ); + memcpy( msg->pData, pbOut, uCompressedSize ); + MSG_SeekToBit( msg, uCompressedSize << 3, SEEK_SET ); + } + if( pbOut ) free( pbOut ); + } + + remaining = MSG_GetNumBytesWritten( msg ); + pos = 0; // current position in bytes + + while( remaining > 0 ) + { + bytes = Q_min( remaining, chunksize ); + remaining -= bytes; + + buf = Netchan_AllocFragbuf(); + buf->bufferid = bufferid++; + + // Copy in data + MSG_Clear( &buf->frag_message ); + MSG_WriteBits( &buf->frag_message, &msg->pData[pos], bytes << 3 ); + + Netchan_AddFragbufToTail( wait, buf ); + pos += bytes; + } + + // now add waiting list item to end of buffer queue + if( !chan->waitlist[FRAG_NORMAL_STREAM] ) + { + chan->waitlist[FRAG_NORMAL_STREAM] = wait; + } + else + { + p = chan->waitlist[FRAG_NORMAL_STREAM]; + + while( p->next ) + p = p->next; + p->next = wait; + } +} + +/* +============================== +Netchan_CreateFragments + +============================== +*/ +void Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg ) +{ + // always queue any pending reliable data ahead of the fragmentation buffer + if( MSG_GetNumBytesWritten( &chan->message ) > 0 ) + { + Netchan_CreateFragments_( chan, &chan->message ); + MSG_Clear( &chan->message ); + } + + Netchan_CreateFragments_( chan, msg ); +} + +/* +============================== +Netchan_FindBufferById + +============================== +*/ +fragbuf_t *Netchan_FindBufferById( fragbuf_t **pplist, int id, qboolean allocate ) +{ + fragbuf_t *list = *pplist; + fragbuf_t *pnewbuf; + + while( list ) + { + if( list->bufferid == id ) + return list; + + list = list->next; + } + + if( !allocate ) + return NULL; + + // create new entry + pnewbuf = Netchan_AllocFragbuf(); + pnewbuf->bufferid = id; + Netchan_AddBufferToList( pplist, pnewbuf ); + + return pnewbuf; +} + +/* +============================== +Netchan_CheckForCompletion + +============================== +*/ +void Netchan_CheckForCompletion( netchan_t *chan, int stream, int intotalbuffers ) +{ + int c, id; + int size; + fragbuf_t *p; + + size = 0; + c = 0; + + p = chan->incomingbufs[stream]; + if( !p ) return; + + while( p ) + { + size += MSG_GetNumBytesWritten( &p->frag_message ); + c++; + + id = FRAG_GETID( p->bufferid ); + if( id != c ) + { + if( chan->sock == NS_CLIENT ) + { + MsgDev( D_ERROR, "Lost/dropped fragment would cause stall, retrying connection\n" ); + Cbuf_AddText( "reconnect\n" ); + } + } + p = p->next; + } + + // received final message + if( c == intotalbuffers ) + { +// MsgDev( D_NOTE, "\n%s: incoming is complete %i bytes waiting\n", ns_strings[chan->sock], size ); + chan->incomingready[stream] = true; + } +} + +/* +============================== +Netchan_CreateFileFragmentsFromBuffer + +============================== +*/ +void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, char *filename, byte *pbuf, int size ) +{ + int chunksize; + int send, pos; + int remaining; + int bufferid = 1; + qboolean firstfragment = true; + fragbufwaiting_t *wait, *p; + fragbuf_t *buf; + + if( !size ) return; + + if( chan->pfnBlockSize != NULL ) + chunksize = chan->pfnBlockSize( chan->client ); + else chunksize = FRAGMENT_MAX_SIZE; // fallback + + if( !LZSS_IsCompressed( pbuf )) + { + uint uCompressedSize = 0; + byte *pbOut = LZSS_Compress( pbuf, size, &uCompressedSize ); + + if( pbOut && uCompressedSize > 0 && uCompressedSize < size ) + { + Con_DPrintf( "Compressing filebuffer (%s -> %s)\n", Q_memprint( size ), Q_memprint( uCompressedSize )); + memcpy( pbuf, pbOut, uCompressedSize ); + size = uCompressedSize; + } + if( pbOut ) free( pbOut ); + } + + wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); + remaining = size; + pos = 0; + + while( remaining > 0 ) + { + send = Q_min( remaining, chunksize ); + + buf = Netchan_AllocFragbuf(); + buf->bufferid = bufferid++; + + // copy in data + MSG_Clear( &buf->frag_message ); + + if( firstfragment ) + { + // write filename + MSG_WriteString( &buf->frag_message, filename ); + + // send a bit less on first package + send -= MSG_GetNumBytesWritten( &buf->frag_message ); + + firstfragment = false; + } + + buf->isbuffer = true; + buf->isfile = true; + buf->size = send; + buf->foffset = pos; + + MSG_WriteBits( &buf->frag_message, pbuf + pos, send << 3 ); + + remaining -= send; + pos += send; + + Netchan_AddFragbufToTail( wait, buf ); + } + + // now add waiting list item to end of buffer queue + if( !chan->waitlist[FRAG_FILE_STREAM] ) + { + chan->waitlist[FRAG_FILE_STREAM] = wait; + } + else + { + p = chan->waitlist[FRAG_FILE_STREAM]; + + while( p->next ) + p = p->next; + p->next = wait; + } +} + +/* +============================== +Netchan_CreateFileFragments + +============================== +*/ +int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ) +{ + int chunksize; + int send, pos; + int remaining; + int bufferid = 1; + int filesize = 0; + char compressedfilename[MAX_OSPATH]; + int compressedFileTime; + int fileTime; + qboolean firstfragment = true; + qboolean bCompressed = false; + fragbufwaiting_t *wait, *p; + fragbuf_t *buf; + + if(( filesize = FS_FileSize( filename, false )) <= 0 ) + { + Con_Printf( S_WARN "Unable to open %s for transfer\n", filename ); + return 0; + } + + if( chan->pfnBlockSize != NULL ) + chunksize = chan->pfnBlockSize( chan->client ); + else chunksize = FRAGMENT_MAX_SIZE; // fallback + + Q_strncpy( compressedfilename, filename, sizeof( compressedfilename )); + COM_ReplaceExtension( compressedfilename, ".ztmp" ); + compressedFileTime = FS_FileTime( compressedfilename, false ); + fileTime = FS_FileTime( filename, false ); + + if( compressedFileTime >= fileTime ) + { + // if compressed file already created and newer than source + if( FS_FileSize( compressedfilename, false ) != -1 ) + bCompressed = true; + } + else + { + uint uCompressedSize; + byte *uncompressed; + byte *compressed; + + uncompressed = FS_LoadFile( filename, &filesize, false ); + compressed = LZSS_Compress( uncompressed, filesize, &uCompressedSize ); + + if( compressed ) + { + Con_DPrintf( "compressed file %s (%s -> %s)\n", filename, Q_memprint( filesize ), Q_memprint( uCompressedSize )); + FS_WriteFile( compressedfilename, compressed, uCompressedSize ); + filesize = uCompressedSize; + bCompressed = true; + free( compressed ); + } + Mem_Free( uncompressed ); + } + + wait = (fragbufwaiting_t *)Mem_Alloc( net_mempool, sizeof( fragbufwaiting_t )); + remaining = filesize; + pos = 0; + + while( remaining > 0 ) + { + send = Q_min( remaining, chunksize ); + + buf = Netchan_AllocFragbuf(); + buf->bufferid = bufferid++; + + // copy in data + MSG_Clear( &buf->frag_message ); + + if( firstfragment ) + { + // Write filename + MSG_WriteString( &buf->frag_message, filename ); + + // Send a bit less on first package + send -= MSG_GetNumBytesWritten( &buf->frag_message ); + + firstfragment = false; + } + + buf->isfile = true; + buf->size = send; + buf->foffset = pos; + buf->iscompressed = bCompressed; + Q_strncpy( buf->filename, filename, sizeof( buf->filename )); + + pos += send; + remaining -= send; + + Netchan_AddFragbufToTail( wait, buf ); + } + + // now add waiting list item to end of buffer queue + if( !chan->waitlist[FRAG_FILE_STREAM] ) + { + chan->waitlist[FRAG_FILE_STREAM] = wait; + } + else + { + p = chan->waitlist[FRAG_FILE_STREAM]; + while( p->next ) + p = p->next; + p->next = wait; + } + + return 1; +} + +/* +============================== +Netchan_FlushIncoming + +============================== +*/ +void Netchan_FlushIncoming( netchan_t *chan, int stream ) +{ + fragbuf_t *p, *n; + + MSG_Clear( &net_message ); + + p = chan->incomingbufs[ stream ]; + + while( p ) + { + n = p->next; + Mem_Free( p ); + p = n; + } + chan->incomingbufs[stream] = NULL; + chan->incomingready[stream] = false; +} + +/* +============================== +Netchan_CopyNormalFragments + +============================== +*/ +qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length ) +{ + size_t size = 0; + fragbuf_t *p, *n; + + if( !chan->incomingready[FRAG_NORMAL_STREAM] ) + return false; + + if( !chan->incomingbufs[FRAG_NORMAL_STREAM] ) + { + chan->incomingready[FRAG_NORMAL_STREAM] = false; + return false; + } + + p = chan->incomingbufs[FRAG_NORMAL_STREAM]; + + MSG_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); + + while( p ) + { + n = p->next; + + // copy it in + MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message )); + size += MSG_GetNumBytesWritten( &p->frag_message ); + + Mem_Free( p ); + p = n; + } + + if( LZSS_IsCompressed( MSG_GetData( msg ))) + { + uint uDecompressedLen = LZSS_GetActualSize( MSG_GetData( msg )); + byte buf[NET_MAX_MESSAGE]; + + if( uDecompressedLen <= sizeof( buf )) + { + size = LZSS_Decompress( MSG_GetData( msg ), buf ); + memcpy( msg->pData, buf, size ); + } + else + { + // g-cont. this should not happens + Con_Printf( S_ERROR "buffer to small to decompress message\n" ); + return false; + } + } + + chan->incomingbufs[FRAG_NORMAL_STREAM] = NULL; + + // reset flag + chan->incomingready[FRAG_NORMAL_STREAM] = false; + + // tell about message size + if( length ) *length = size; + + return true; +} + +/* +============================== +Netchan_CopyFileFragments + +============================== +*/ +qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ) +{ + char filename[MAX_OSPATH]; + int nsize, pos; + byte *buffer; + fragbuf_t *p, *n; + + if( !chan->incomingready[FRAG_FILE_STREAM] ) + return false; + + if( !chan->incomingbufs[FRAG_FILE_STREAM] ) + { + chan->incomingready[FRAG_FILE_STREAM] = false; + return false; + } + + p = chan->incomingbufs[FRAG_FILE_STREAM]; + + MSG_Init( msg, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); + + // copy in first chunk so we can get filename out + MSG_WriteBytes( msg, MSG_GetData( &p->frag_message ), MSG_GetNumBytesWritten( &p->frag_message )); + MSG_Clear( msg ); + + Q_strncpy( filename, MSG_ReadString( msg ), sizeof( filename )); + + if( !COM_CheckString( filename )) + { + Con_Printf( S_ERROR "file fragment received with no filename\nFlushing input queue\n" ); + Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); + return false; + } + else if( filename[0] != '!' && !COM_IsSafeFileToDownload( filename )) + { + Con_Printf( S_ERROR "file fragment received with bad path, ignoring\n" ); + Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); + return false; + } + + Q_strncpy( chan->incomingfilename, filename, sizeof( chan->incomingfilename )); + + if( filename[0] != '!' && FS_FileExists( filename, false )) + { + Con_Printf( S_ERROR "can't download %s, already exists\n", filename ); + Netchan_FlushIncoming( chan, FRAG_FILE_STREAM ); + return true; + } + + // create file from buffers + nsize = 0; + while ( p ) + { + nsize += MSG_GetNumBytesWritten( &p->frag_message ); // Size will include a bit of slop, oh well + if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) + nsize -= MSG_GetNumBytesRead( msg ); + p = p->next; + } + + buffer = Mem_Alloc( net_mempool, nsize + 1 ); + p = chan->incomingbufs[FRAG_FILE_STREAM]; + pos = 0; + + while( p ) + { + int cursize; + + n = p->next; + + cursize = MSG_GetNumBytesWritten( &p->frag_message ); + + // first message has the file name, don't write that into the data stream, + // just write the rest of the actual data + if( p == chan->incomingbufs[FRAG_FILE_STREAM] ) + { + // copy it in + cursize -= MSG_GetNumBytesRead( msg ); + memcpy( &buffer[pos], &p->frag_message.pData[MSG_GetNumBytesRead( msg )], cursize ); + } + else + { + memcpy( &buffer[pos], p->frag_message.pData, cursize ); + } + + pos += cursize; + Mem_Free( p ); + p = n; + } + + if( LZSS_IsCompressed( buffer )) + { + uint uncompressedSize = LZSS_GetActualSize( buffer ) + 1; + byte *uncompressedBuffer = Mem_Alloc( net_mempool, uncompressedSize ); + + nsize = LZSS_Decompress( buffer, uncompressedBuffer ); + Mem_Free( buffer ); + buffer = uncompressedBuffer; + } + + // customization files goes int tempbuffer + if( filename[0] == '!' ) + { + if( chan->tempbuffer ) + Mem_Free( chan->tempbuffer ); + chan->tempbuffer = buffer; + chan->tempbuffersize = nsize; + } + else + { + // g-cont. it's will be stored downloaded files directly into game folder + FS_WriteFile( filename, buffer, nsize ); + Mem_Free( buffer ); + } + + // clear remnants + MSG_Clear( msg ); + + chan->incomingbufs[FRAG_FILE_STREAM] = NULL; + chan->incomingready[FRAG_FILE_STREAM] = false; + + return true; +} + +qboolean Netchan_Validate( netchan_t *chan, sizebuf_t *sb, qboolean *frag_message, uint *fragid, int *frag_offset, int *frag_length ) +{ + int i, buffer, offset; + int count, length; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( !frag_message[i] ) + continue; + + buffer = FRAG_GETID( fragid[i] ); + count = FRAG_GETCOUNT( fragid[i] ); + offset = BitByte( frag_offset[i] ); + length = BitByte( frag_length[i] ); + + if( buffer < 0 || buffer > NET_MAX_BUFFER_ID ) + return false; + + if( count < 0 || count > NET_MAX_BUFFERS_COUNT ) + return false; + + if( length < 0 || length > ( FRAGMENT_MAX_SIZE << 3 )) + return false; + + if( offset < 0 || offset > ( FRAGMENT_MAX_SIZE << 3 )) + return false; + } + + return true; +} + +/* +============================== +Netchan_UpdateProgress + +============================== +*/ +void Netchan_UpdateProgress( netchan_t *chan ) +{ + fragbuf_t *p; + int i, c = 0; + int total = 0; + float bestpercent = 0.0; + + if( host.downloadcount == 0 ) + { + scr_download->value = -1.0f; + host.downloadfile[0] = '\0'; + } + + // do show slider for file downloads. + if( !chan->incomingbufs[FRAG_FILE_STREAM] ) + return; + + for( i = MAX_STREAMS - 1; i >= 0; i-- ) + { + // receiving data + if( chan->incomingbufs[i] ) + { + p = chan->incomingbufs[i]; + + total = FRAG_GETCOUNT( p->bufferid ); + + while( p ) + { + c++; + p = p->next; + } + + if( total ) + { + float percent = 100.0f * (float)c / (float)total; + + if( percent > bestpercent ) + bestpercent = percent; + } + + p = chan->incomingbufs[i]; + + if( i == FRAG_FILE_STREAM ) + { + char sz[MAX_SYSPATH]; + char *in, *out; + int len = 0; + + in = (char *)MSG_GetData( &p->frag_message ); + out = sz; + + while( *in ) + { + *out++ = *in++; + len++; + if( len > 128 ) + break; + } + *out = '\0'; + + if( Q_strlen( sz ) > 0 && sz[0] != '!' ) + Q_strncpy( host.downloadfile, sz, sizeof( host.downloadfile )); + } + } + else if( chan->fragbufs[i] ) // Sending data + { + if( chan->fragbufcount[i] ) + { + float percent = 100.0f * (float)chan->fragbufs[i]->bufferid / (float)chan->fragbufcount[i]; + + if( percent > bestpercent ) + bestpercent = percent; + } + } + + } + + scr_download->value = bestpercent; +} + +/* +=============== +Netchan_TransmitBits + +tries to send an unreliable message to a connection, and handles the +transmition / retransmition of the reliable messages. + +A 0 length will still generate a packet and deal with the reliable messages. +================ +*/ +void Netchan_TransmitBits( netchan_t *chan, int length, byte *data ) +{ + byte send_buf[NET_MAX_MESSAGE]; + qboolean send_reliable_fragment; + uint w1, w2, statId; + qboolean send_reliable; + sizebuf_t send; + int i, j; + float fRate; + + // check for message overflow + if( MSG_CheckOverflow( &chan->message )) + { + Con_Printf( S_ERROR "%s:outgoing message overflow\n", NET_AdrToString( chan->remote_address )); + return; + } + + // if the remote side dropped the last reliable message, resend it + send_reliable = false; + + if( chan->incoming_acknowledged > chan->last_reliable_sequence && chan->incoming_reliable_acknowledged != chan->reliable_sequence ) + send_reliable = true; + + // A packet can have "reliable payload + frag payload + unreliable payload + // frag payload can be a file chunk, if so, it needs to be parsed on the receiving end and reliable payload + unreliable payload need + // to be passed on to the message queue. The processing routine needs to be able to handle the case where a message comes in and a file + // transfer completes + + // if the reliable transmit buffer is empty, copy the current message out + if( !chan->reliable_length ) + { + qboolean send_frag = false; + fragbuf_t *pbuf; + + // will be true if we are active and should let chan->message get some bandwidth + int send_from_frag[MAX_STREAMS] = { 0, 0 }; + int send_from_regular = 0; + + // if we have data in the waiting list(s) and we have cleared the current queue(s), then + // push the waitlist(s) into the current queue(s) + Netchan_FragSend( chan ); + + // sending regular payload + send_from_regular = MSG_GetNumBytesWritten( &chan->message ) ? 1 : 0; + + // check to see if we are sending a frag payload + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( chan->fragbufs[i] ) + send_from_frag[i] = 1; + } + + // stall reliable payloads if sending from frag buffer + if( send_from_regular && ( send_from_frag[FRAG_NORMAL_STREAM] )) + { + send_from_regular = false; + + // if the reliable buffer has gotten too big, queue it at the end of everything and clear out buffer + if( MSG_GetNumBytesWritten( &chan->message ) > MAX_RELIABLE_PAYLOAD ) + { + Netchan_CreateFragments_( chan, &chan->message ); + MSG_Clear( &chan->message ); + } + } + + // startpos will be zero if there is no regular payload + for( i = 0; i < MAX_STREAMS; i++ ) + { + chan->frag_startpos[i] = 0; + + // assume no fragment is being sent + chan->reliable_fragment[i] = 0; + chan->reliable_fragid[i] = 0; + chan->frag_length[i] = 0; + + if( send_from_frag[i] ) + { + send_frag = true; + } + } + + if( send_from_regular || send_frag ) + { + chan->reliable_sequence ^= 1; + send_reliable = true; + } + + if( send_from_regular ) + { + memcpy( chan->reliable_buf, chan->message_buf, MSG_GetNumBytesWritten( &chan->message )); + chan->reliable_length = MSG_GetNumBitsWritten( &chan->message ); + MSG_Clear( &chan->message ); + + // if we send fragments, this is where they'll start + for( i = 0; i < MAX_STREAMS; i++ ) + chan->frag_startpos[i] = chan->reliable_length; + } + + for( i = 0; i < MAX_STREAMS; i++ ) + { + int newpayloadsize; + int fragment_size; + + // is there someting in the fragbuf? + pbuf = chan->fragbufs[i]; + fragment_size = 0; + + if( pbuf ) + { + fragment_size = MSG_GetNumBytesWritten( &pbuf->frag_message ); + + // files set size a bit differently. + if( pbuf->isfile && !pbuf->isbuffer ) + { + fragment_size = pbuf->size; + } + } + + newpayloadsize = (( chan->reliable_length + ( fragment_size << 3 )) + 7 ) >> 3; + + // make sure we have enought space left + if( send_from_frag[i] && pbuf && newpayloadsize < NET_MAX_FRAGMENT ) + { + sizebuf_t temp; + + // which buffer are we sending ? + chan->reliable_fragid[i] = MAKE_FRAGID( pbuf->bufferid, chan->fragbufcount[i] ); + + // if it's not in-memory, then we'll need to copy it in frame the file handle. + if( pbuf->isfile && !pbuf->isbuffer ) + { + byte filebuffer[NET_MAX_FRAGMENT]; + file_t *file; + + if( pbuf->iscompressed ) + { + char compressedfilename[MAX_OSPATH]; + + Q_strncpy( compressedfilename, pbuf->filename, sizeof( compressedfilename )); + COM_ReplaceExtension( compressedfilename, ".ztmp" ); + file = FS_Open( compressedfilename, "rb", false ); + } + else file = FS_Open( pbuf->filename, "rb", false ); + + FS_Seek( file, pbuf->foffset, SEEK_SET ); + FS_Read( file, filebuffer, pbuf->size ); + + MSG_WriteBits( &pbuf->frag_message, filebuffer, pbuf->size << 3 ); + FS_Close( file ); + } + + // copy frag stuff on top of current buffer + MSG_StartWriting( &temp, chan->reliable_buf, sizeof( chan->reliable_buf ), chan->reliable_length, -1 ); + MSG_WriteBits( &temp, MSG_GetData( &pbuf->frag_message ), MSG_GetNumBitsWritten( &pbuf->frag_message )); + chan->reliable_length += MSG_GetNumBitsWritten( &pbuf->frag_message ); + chan->frag_length[i] = MSG_GetNumBitsWritten( &pbuf->frag_message ); + + // unlink pbuf + Netchan_UnlinkFragment( pbuf, &chan->fragbufs[i] ); + + chan->reliable_fragment[i] = 1; + + // offset the rest of the starting positions + for( j = i + 1; j < MAX_STREAMS; j++ ) + chan->frag_startpos[j] += chan->frag_length[i]; + } + } + } + + MSG_Init( &send, "NetSend", send_buf, sizeof( send_buf )); + + // prepare the packet header + w1 = chan->outgoing_sequence | (send_reliable << 31); + w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence << 31); + + send_reliable_fragment = false; + + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( chan->reliable_fragment[i] ) + { + send_reliable_fragment = true; + break; + } + } + + if( send_reliable && send_reliable_fragment ) + SetBits( w1, BIT( 30 )); + + chan->outgoing_sequence++; + + MSG_WriteLong( &send, w1 ); + MSG_WriteLong( &send, w2 ); + + // send the qport if we are a client + if( chan->sock == NS_CLIENT ) + { + MSG_WriteWord( &send, Cvar_VariableInteger( "net_qport" )); + } + + if( send_reliable && send_reliable_fragment ) + { + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( chan->reliable_fragment[i] ) + { + MSG_WriteByte( &send, 1 ); + MSG_WriteLong( &send, chan->reliable_fragid[i] ); + MSG_WriteLong( &send, chan->frag_startpos[i] ); + MSG_WriteLong( &send, chan->frag_length[i] ); + } + else + { + MSG_WriteByte( &send, 0 ); + } + } + } + + // copy the reliable message to the packet first + if( send_reliable ) + { + MSG_WriteBits( &send, chan->reliable_buf, chan->reliable_length ); + chan->last_reliable_sequence = chan->outgoing_sequence - 1; + } + + if( MSG_GetNumBitsLeft( &send ) >= length ) + MSG_WriteBits( &send, data, length ); + else Con_Printf( S_WARN "Netchan_Transmit: unreliable message overflow\n" ); + + // deal with packets that are too small for some networks + if( MSG_GetNumBytesWritten( &send ) < 16 && !NET_IsLocalAddress( chan->remote_address )) // packet too small for some networks + { + // go ahead and pad a full 16 extra bytes -- this only happens during authentication / signon + for( i = MSG_GetNumBytesWritten( &send ); i < 16; i++ ) + { + if( chan->sock == NS_CLIENT ) + MSG_BeginClientCmd( &send, clc_nop ); + else if( chan->sock == NS_SERVER ) + MSG_BeginServerCmd( &send, svc_nop ); + else break; + } + } + + statId = chan->flow[FLOW_OUTGOING].current & MASK_LATENT; + chan->flow[FLOW_OUTGOING].stats[statId].size = MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE; + chan->flow[FLOW_OUTGOING].stats[statId].time = host.realtime; + chan->flow[FLOW_OUTGOING].totalbytes += chan->flow[FLOW_OUTGOING].stats[statId].size; + chan->flow[FLOW_OUTGOING].current++; + + Netchan_UpdateFlow( chan ); + + chan->total_sended += MSG_GetNumBytesWritten( &send ); + + // send the datagram + if( !CL_IsPlaybackDemo( )) + { + NET_SendPacket( chan->sock, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), chan->remote_address ); + } + + if( SV_Active() && sv_lan.value && sv_lan_rate.value > 1000.0 ) + fRate = 1.0f / sv_lan_rate.value; + else fRate = 1.0f / chan->rate; + + if( chan->cleartime < host.realtime ) + chan->cleartime = host.realtime; + + chan->cleartime += ( MSG_GetNumBytesWritten( &send ) + UDP_HEADER_SIZE ) * fRate; + + if( net_showpackets->value && net_showpackets->value != 2.0f ) + { + Con_Printf( " %s --> sz=%i seq=%i ack=%i rel=%i tm=%f\n" + , ns_strings[chan->sock] + , MSG_GetNumBytesWritten( &send ) + , ( chan->outgoing_sequence - 1 ) & 63 + , chan->incoming_sequence & 63 + , send_reliable ? 1 : 0 + , (float)host.realtime ); + } +} + +/* +=============== +Netchan_Transmit + +tries to send an unreliable message to a connection, and handles the +transmition / retransmition of the reliable messages. + +A 0 length will still generate a packet and deal with the reliable messages. +================ +*/ +void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ) +{ + Netchan_TransmitBits( chan, lengthInBytes << 3, data ); +} + +/* +================= +Netchan_Process + +called when the current net_message is from remote_address +modifies net_message so that it points to the packet payload +================= +*/ +qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ) +{ + uint sequence, sequence_ack; + uint reliable_ack, reliable_message; + uint fragid[MAX_STREAMS] = { 0, 0 }; + qboolean frag_message[MAX_STREAMS] = { false, false }; + int frag_offset[MAX_STREAMS] = { 0, 0 }; + int frag_length[MAX_STREAMS] = { 0, 0 }; + qboolean message_contains_fragments; + int i, qport, statId; + + if( !CL_IsPlaybackDemo() && !NET_CompareAdr( net_from, chan->remote_address )) + return false; + + // get sequence numbers + MSG_Clear( msg ); + sequence = MSG_ReadLong( msg ); + sequence_ack = MSG_ReadLong( msg ); + + // read the qport if we are a server + if( chan->sock == NS_SERVER ) + qport = MSG_ReadShort( msg ); + + reliable_message = sequence >> 31; + reliable_ack = sequence_ack >> 31; + + message_contains_fragments = FBitSet( sequence, BIT( 30 )) ? true : false; + + if( message_contains_fragments ) + { + for( i = 0; i < MAX_STREAMS; i++ ) + { + if( MSG_ReadByte( msg )) + { + frag_message[i] = true; + fragid[i] = MSG_ReadLong( msg ); + frag_offset[i] = MSG_ReadLong( msg ); + frag_length[i] = MSG_ReadLong( msg ); + } + } + + if( !Netchan_Validate( chan, msg, frag_message, fragid, frag_offset, frag_length )) + return false; + } + + sequence &= ~BIT( 31 ); + sequence &= ~BIT( 30 ); + sequence_ack &= ~BIT( 30 ); + sequence_ack &= ~BIT( 31 ); + + if( net_showpackets->value && net_showpackets->value != 3.0f ) + { + Con_Printf( " %s <-- sz=%i seq=%i ack=%i rel=%i tm=%f\n" + , ns_strings[chan->sock] + , MSG_GetMaxBytes( msg ) + , sequence & 63 + , sequence_ack & 63 + , reliable_message + , host.realtime ); + } + + // discard stale or duplicated packets + if( sequence <= (uint)chan->incoming_sequence ) + { + if( net_showdrop->value ) + { + const char *adr = NET_AdrToString( chan->remote_address ); + + if( sequence == (uint)chan->incoming_sequence ) + Con_Printf( "%s:duplicate packet %i at %i\n", adr, sequence, chan->incoming_sequence ); + else Con_Printf( "%s:out of order packet %i at %i\n", adr, sequence, chan->incoming_sequence ); + } + return false; + } + + // dropped packets don't keep the message from being used + net_drop = sequence - ( chan->incoming_sequence + 1 ); + if( net_drop > 0 && net_showdrop->value ) + Con_Printf( "%s:dropped %i packets at %i\n", NET_AdrToString( chan->remote_address ), net_drop, sequence ); + + // if the current outgoing reliable message has been acknowledged + // clear the buffer to make way for the next + if( reliable_ack == (uint)chan->reliable_sequence ) + { + // make sure we actually could have ack'd this message + if( sequence_ack >= (uint)chan->last_reliable_sequence ) + { + chan->reliable_length = 0; // it has been received + } + } + + // if this message contains a reliable message, bump incoming_reliable_sequence + chan->incoming_sequence = sequence; + chan->incoming_acknowledged = sequence_ack; + chan->incoming_reliable_acknowledged = reliable_ack; + if( reliable_message ) + { + chan->incoming_reliable_sequence ^= 1; + } + + chan->last_received = host.realtime; + + // Update data flow stats + statId = chan->flow[FLOW_INCOMING].current & MASK_LATENT; + chan->flow[FLOW_INCOMING].stats[statId].size = MSG_GetMaxBytes( msg ) + UDP_HEADER_SIZE; + chan->flow[FLOW_INCOMING].stats[statId].time = host.realtime; + chan->flow[FLOW_INCOMING].totalbytes += chan->flow[FLOW_INCOMING].stats[statId].size; + chan->flow[FLOW_INCOMING].current++; + + Netchan_UpdateFlow( chan ); + + chan->total_received += MSG_GetMaxBytes( msg ); + + if( message_contains_fragments ) + { + for( i = 0; i < MAX_STREAMS; i++ ) + { + int j, inbufferid; + int intotalbuffers; + int oldpos, curbit; + int numbitstoremove; + fragbuf_t *pbuf; + + if( !frag_message[i] ) + continue; + + inbufferid = FRAG_GETID( fragid[i] ); + intotalbuffers = FRAG_GETCOUNT( fragid[i] ); + + if( fragid[i] != 0 ) + { + pbuf = Netchan_FindBufferById( &chan->incomingbufs[i], fragid[i], true ); + + if( pbuf ) + { + byte buffer[NET_MAX_FRAGMENT]; + int bits, size; + sizebuf_t temp; + + size = MSG_GetNumBitsRead( msg ) + frag_offset[i]; + bits = frag_length[i]; + + // copy in data + MSG_Clear( &pbuf->frag_message ); + + MSG_StartReading( &temp, msg->pData, MSG_GetMaxBytes( msg ), size, -1 ); + MSG_ReadBits( &temp, buffer, bits ); + MSG_WriteBits( &pbuf->frag_message, buffer, bits ); + } + + // count # of incoming bufs we've queued? are we done? + Netchan_CheckForCompletion( chan, i, intotalbuffers ); + } + + // rearrange incoming data to not have the frag stuff in the middle of it + oldpos = MSG_GetNumBitsRead( msg ); + curbit = MSG_GetNumBitsRead( msg ) + frag_offset[i]; + numbitstoremove = frag_length[i]; + + MSG_ExciseBits( msg, curbit, numbitstoremove ); + MSG_SeekToBit( msg, oldpos, SEEK_SET ); + + for( j = i + 1; j < MAX_STREAMS; j++ ) + frag_offset[j] -= frag_length[i]; + } + + // is there anything left to process? + if( MSG_GetNumBitsLeft( msg ) <= 0 ) + { + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/engine/common/net_encode.c b/engine/common/net_encode.c new file mode 100644 index 00000000..abfd9100 --- /dev/null +++ b/engine/common/net_encode.c @@ -0,0 +1,1992 @@ +/* +net_encode.c - encode network messages +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "netchan.h" +#include "mathlib.h" +#include "net_encode.h" +#include "event_api.h" +#include "usercmd.h" +#include "pm_movevars.h" +#include "entity_state.h" +#include "weaponinfo.h" +#include "event_args.h" +#include "protocol.h" +#include "client.h" + +#define DELTA_PATH "delta.lst" + +static qboolean delta_init = false; + +// list of all the struct names +static const delta_field_t cmd_fields[] = +{ +{ UCMD_DEF( lerp_msec ) }, +{ UCMD_DEF( msec ) }, +{ UCMD_DEF( viewangles[0] ) }, +{ UCMD_DEF( viewangles[1] ) }, +{ UCMD_DEF( viewangles[2] ) }, +{ UCMD_DEF( forwardmove ) }, +{ UCMD_DEF( sidemove ) }, +{ UCMD_DEF( upmove ) }, +{ UCMD_DEF( lightlevel ) }, +{ UCMD_DEF( buttons ) }, +{ UCMD_DEF( impulse ) }, +{ UCMD_DEF( weaponselect ) }, +{ UCMD_DEF( impact_index ) }, +{ UCMD_DEF( impact_position[0] ) }, +{ UCMD_DEF( impact_position[1] ) }, +{ UCMD_DEF( impact_position[2] ) }, +{ NULL }, +}; + +static const delta_field_t pm_fields[] = +{ +{ PHYS_DEF( gravity ) }, +{ PHYS_DEF( stopspeed ) }, +{ PHYS_DEF( maxspeed ) }, +{ PHYS_DEF( spectatormaxspeed ) }, +{ PHYS_DEF( accelerate ) }, +{ PHYS_DEF( airaccelerate ) }, +{ PHYS_DEF( wateraccelerate ) }, +{ PHYS_DEF( friction ) }, +{ PHYS_DEF( edgefriction ) }, +{ PHYS_DEF( waterfriction ) }, +{ PHYS_DEF( bounce ) }, +{ PHYS_DEF( stepsize ) }, +{ PHYS_DEF( maxvelocity ) }, +{ PHYS_DEF( zmax ) }, +{ PHYS_DEF( waveHeight ) }, +{ PHYS_DEF( footsteps ) }, +{ PHYS_DEF( skyName ) }, +{ PHYS_DEF( rollangle ) }, +{ PHYS_DEF( rollspeed ) }, +{ PHYS_DEF( skycolor_r ) }, +{ PHYS_DEF( skycolor_g ) }, +{ PHYS_DEF( skycolor_b ) }, +{ PHYS_DEF( skyvec_x ) }, +{ PHYS_DEF( skyvec_y ) }, +{ PHYS_DEF( skyvec_z ) }, +{ PHYS_DEF( fog_settings ) }, +{ PHYS_DEF( wateralpha ) }, +{ NULL }, +}; + +static const delta_field_t ev_fields[] = +{ +{ EVNT_DEF( flags ) }, +{ EVNT_DEF( entindex ) }, +{ EVNT_DEF( origin[0] ) }, +{ EVNT_DEF( origin[1] ) }, +{ EVNT_DEF( origin[2] ) }, +{ EVNT_DEF( angles[0] ) }, +{ EVNT_DEF( angles[1] ) }, +{ EVNT_DEF( angles[2] ) }, +{ EVNT_DEF( velocity[0] ) }, +{ EVNT_DEF( velocity[1] ) }, +{ EVNT_DEF( velocity[2] ) }, +{ EVNT_DEF( ducking ) }, +{ EVNT_DEF( fparam1 ) }, +{ EVNT_DEF( fparam2 ) }, +{ EVNT_DEF( iparam1 ) }, +{ EVNT_DEF( iparam2 ) }, +{ EVNT_DEF( bparam1 ) }, +{ EVNT_DEF( bparam2 ) }, +{ NULL }, +}; + +static const delta_field_t wd_fields[] = +{ +{ WPDT_DEF( m_iId ) }, +{ WPDT_DEF( m_iClip ) }, +{ WPDT_DEF( m_flNextPrimaryAttack ) }, +{ WPDT_DEF( m_flNextSecondaryAttack ) }, +{ WPDT_DEF( m_flTimeWeaponIdle ) }, +{ WPDT_DEF( m_fInReload ) }, +{ WPDT_DEF( m_fInSpecialReload ) }, +{ WPDT_DEF( m_flNextReload ) }, +{ WPDT_DEF( m_flPumpTime ) }, +{ WPDT_DEF( m_fReloadTime ) }, +{ WPDT_DEF( m_fAimedDamage ) }, +{ WPDT_DEF( m_fNextAimBonus ) }, +{ WPDT_DEF( m_fInZoom ) }, +{ WPDT_DEF( m_iWeaponState ) }, +{ WPDT_DEF( iuser1 ) }, +{ WPDT_DEF( iuser2 ) }, +{ WPDT_DEF( iuser3 ) }, +{ WPDT_DEF( iuser4 ) }, +{ WPDT_DEF( fuser1 ) }, +{ WPDT_DEF( fuser2 ) }, +{ WPDT_DEF( fuser3 ) }, +{ WPDT_DEF( fuser4 ) }, +{ NULL }, +}; + +static const delta_field_t cd_fields[] = +{ +{ CLDT_DEF( origin[0] ) }, +{ CLDT_DEF( origin[1] ) }, +{ CLDT_DEF( origin[2] ) }, +{ CLDT_DEF( velocity[0] ) }, +{ CLDT_DEF( velocity[1] ) }, +{ CLDT_DEF( velocity[2] ) }, +{ CLDT_DEF( viewmodel ) }, +{ CLDT_DEF( punchangle[0] ) }, +{ CLDT_DEF( punchangle[1] ) }, +{ CLDT_DEF( punchangle[2] ) }, +{ CLDT_DEF( flags ) }, +{ CLDT_DEF( waterlevel ) }, +{ CLDT_DEF( watertype ) }, +{ CLDT_DEF( view_ofs[0] ) }, +{ CLDT_DEF( view_ofs[1] ) }, +{ CLDT_DEF( view_ofs[2] ) }, +{ CLDT_DEF( health ) }, +{ CLDT_DEF( bInDuck ) }, +{ CLDT_DEF( weapons ) }, +{ CLDT_DEF( flTimeStepSound ) }, +{ CLDT_DEF( flDuckTime ) }, +{ CLDT_DEF( flSwimTime ) }, +{ CLDT_DEF( waterjumptime ) }, +{ CLDT_DEF( maxspeed ) }, +{ CLDT_DEF( fov ) }, +{ CLDT_DEF( weaponanim ) }, +{ CLDT_DEF( m_iId ) }, +{ CLDT_DEF( ammo_shells ) }, +{ CLDT_DEF( ammo_nails ) }, +{ CLDT_DEF( ammo_cells ) }, +{ CLDT_DEF( ammo_rockets ) }, +{ CLDT_DEF( m_flNextAttack ) }, +{ CLDT_DEF( tfstate ) }, +{ CLDT_DEF( pushmsec ) }, +{ CLDT_DEF( deadflag ) }, +{ CLDT_DEF( physinfo ) }, +{ CLDT_DEF( iuser1 ) }, +{ CLDT_DEF( iuser2 ) }, +{ CLDT_DEF( iuser3 ) }, +{ CLDT_DEF( iuser4 ) }, +{ CLDT_DEF( fuser1 ) }, +{ CLDT_DEF( fuser2 ) }, +{ CLDT_DEF( fuser3 ) }, +{ CLDT_DEF( fuser4 ) }, +{ CLDT_DEF( vuser1[0] ) }, +{ CLDT_DEF( vuser1[1] ) }, +{ CLDT_DEF( vuser1[2] ) }, +{ CLDT_DEF( vuser2[0] ) }, +{ CLDT_DEF( vuser2[1] ) }, +{ CLDT_DEF( vuser2[2] ) }, +{ CLDT_DEF( vuser3[0] ) }, +{ CLDT_DEF( vuser3[1] ) }, +{ CLDT_DEF( vuser3[2] ) }, +{ CLDT_DEF( vuser4[0] ) }, +{ CLDT_DEF( vuser4[1] ) }, +{ CLDT_DEF( vuser4[2] ) }, +{ NULL }, +}; + +static const delta_field_t ent_fields[] = +{ +{ ENTS_DEF( entityType ) }, +{ ENTS_DEF( origin[0] ) }, +{ ENTS_DEF( origin[1] ) }, +{ ENTS_DEF( origin[2] ) }, +{ ENTS_DEF( angles[0] ) }, +{ ENTS_DEF( angles[1] ) }, +{ ENTS_DEF( angles[2] ) }, +{ ENTS_DEF( modelindex ) }, +{ ENTS_DEF( sequence ) }, +{ ENTS_DEF( frame ) }, +{ ENTS_DEF( colormap ) }, +{ ENTS_DEF( skin ) }, +{ ENTS_DEF( solid ) }, +{ ENTS_DEF( effects ) }, +{ ENTS_DEF( scale ) }, +{ ENTS_DEF( eflags ) }, +{ ENTS_DEF( rendermode ) }, +{ ENTS_DEF( renderamt ) }, +{ ENTS_DEF( rendercolor.r ) }, +{ ENTS_DEF( rendercolor.g ) }, +{ ENTS_DEF( rendercolor.b ) }, +{ ENTS_DEF( renderfx ) }, +{ ENTS_DEF( movetype ) }, +{ ENTS_DEF( animtime ) }, +{ ENTS_DEF( framerate ) }, +{ ENTS_DEF( body ) }, +{ ENTS_DEF( controller[0] ) }, +{ ENTS_DEF( controller[1] ) }, +{ ENTS_DEF( controller[2] ) }, +{ ENTS_DEF( controller[3] ) }, +{ ENTS_DEF( blending[0] ) }, +{ ENTS_DEF( blending[1] ) }, +{ ENTS_DEF( blending[2] ) }, +{ ENTS_DEF( blending[3] ) }, +{ ENTS_DEF( velocity[0] ) }, +{ ENTS_DEF( velocity[1] ) }, +{ ENTS_DEF( velocity[2] ) }, +{ ENTS_DEF( mins[0] ) }, +{ ENTS_DEF( mins[1] ) }, +{ ENTS_DEF( mins[2] ) }, +{ ENTS_DEF( maxs[0] ) }, +{ ENTS_DEF( maxs[1] ) }, +{ ENTS_DEF( maxs[2] ) }, +{ ENTS_DEF( aiment ) }, +{ ENTS_DEF( owner ) }, +{ ENTS_DEF( friction ) }, +{ ENTS_DEF( gravity ) }, +{ ENTS_DEF( team ) }, +{ ENTS_DEF( playerclass ) }, +{ ENTS_DEF( health ) }, +{ ENTS_DEF( spectator ) }, +{ ENTS_DEF( weaponmodel ) }, +{ ENTS_DEF( gaitsequence ) }, +{ ENTS_DEF( basevelocity[0] ) }, +{ ENTS_DEF( basevelocity[1] ) }, +{ ENTS_DEF( basevelocity[2] ) }, +{ ENTS_DEF( usehull ) }, +{ ENTS_DEF( oldbuttons ) }, // probably never transmitted +{ ENTS_DEF( onground ) }, +{ ENTS_DEF( iStepLeft ) }, +{ ENTS_DEF( flFallVelocity ) }, +{ ENTS_DEF( fov ) }, +{ ENTS_DEF( weaponanim ) }, +{ ENTS_DEF( startpos[0] ) }, +{ ENTS_DEF( startpos[1] ) }, +{ ENTS_DEF( startpos[2] ) }, +{ ENTS_DEF( endpos[0] ) }, +{ ENTS_DEF( endpos[1] ) }, +{ ENTS_DEF( endpos[2] ) }, +{ ENTS_DEF( impacttime ) }, +{ ENTS_DEF( starttime ) }, +{ ENTS_DEF( iuser1 ) }, +{ ENTS_DEF( iuser2 ) }, +{ ENTS_DEF( iuser3 ) }, +{ ENTS_DEF( iuser4 ) }, +{ ENTS_DEF( fuser1 ) }, +{ ENTS_DEF( fuser2 ) }, +{ ENTS_DEF( fuser3 ) }, +{ ENTS_DEF( fuser4 ) }, +{ ENTS_DEF( vuser1[0] ) }, +{ ENTS_DEF( vuser1[1] ) }, +{ ENTS_DEF( vuser1[2] ) }, +{ ENTS_DEF( vuser2[0] ) }, +{ ENTS_DEF( vuser2[1] ) }, +{ ENTS_DEF( vuser2[2] ) }, +{ ENTS_DEF( vuser3[0] ) }, +{ ENTS_DEF( vuser3[1] ) }, +{ ENTS_DEF( vuser3[2] ) }, +{ ENTS_DEF( vuser4[0] ) }, +{ ENTS_DEF( vuser4[1] ) }, +{ ENTS_DEF( vuser4[2] ) }, +{ NULL }, +}; + +static delta_info_t dt_info[] = +{ +{ "event_t", ev_fields, NUM_FIELDS( ev_fields ) }, +{ "movevars_t", pm_fields, NUM_FIELDS( pm_fields ) }, +{ "usercmd_t", cmd_fields, NUM_FIELDS( cmd_fields ) }, +{ "clientdata_t", cd_fields, NUM_FIELDS( cd_fields ) }, +{ "weapon_data_t", wd_fields, NUM_FIELDS( wd_fields ) }, +{ "entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, +{ "entity_state_player_t", ent_fields, NUM_FIELDS( ent_fields ) }, +{ "custom_entity_state_t", ent_fields, NUM_FIELDS( ent_fields ) }, +{ NULL }, +}; + +delta_info_t *Delta_FindStruct( const char *name ) +{ + int i; + + if( !name || !name[0] ) + return NULL; + + for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + { + if( !Q_stricmp( dt_info[i].pName, name )) + return &dt_info[i]; + } + + MsgDev( D_WARN, "Struct %s not found in delta_info\n", name ); + + // found nothing + return NULL; +} + +int Delta_NumTables( void ) +{ + return NUM_FIELDS( dt_info ); +} + +delta_info_t *Delta_FindStructByIndex( int index ) +{ + if( index < 0 || index >= NUM_FIELDS( dt_info )) + return NULL; + + return &dt_info[index]; +} + +delta_info_t *Delta_FindStructByEncoder( const char *encoderName ) +{ + int i; + + if( !encoderName || !encoderName[0] ) + return NULL; + + for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + { + if( !Q_stricmp( dt_info[i].funcName, encoderName )) + return &dt_info[i]; + } + // found nothing + return NULL; +} + +delta_info_t *Delta_FindStructByDelta( const delta_t *pFields ) +{ + int i; + + if( !pFields ) return NULL; + + for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + { + if( dt_info[i].pFields == pFields ) + return &dt_info[i]; + } + // found nothing + return NULL; +} + +void Delta_CustomEncode( delta_info_t *dt, const void *from, const void *to ) +{ + int i; + + Assert( dt != NULL ); + + // set all fields is active by default + for( i = 0; i < dt->numFields; i++ ) + dt->pFields[i].bInactive = false; + + if( dt->userCallback ) + { + dt->userCallback( dt->pFields, from, to ); + } +} + +delta_field_t *Delta_FindFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +{ + if( !fieldName || !*fieldName ) + return NULL; + + for( ; pInfo->name; pInfo++ ) + { + if( !Q_strcmp( pInfo->name, fieldName )) + return (delta_field_t *)pInfo; + } + return NULL; +} + +int Delta_IndexForFieldInfo( const delta_field_t *pInfo, const char *fieldName ) +{ + int i; + + if( !fieldName || !*fieldName ) + return -1; + + for( i = 0; pInfo->name; i++, pInfo++ ) + { + if( !Q_strcmp( pInfo->name, fieldName )) + return i; + } + return -1; +} + +qboolean Delta_AddField( const char *pStructName, const char *pName, int flags, int bits, float mul, float post_mul ) +{ + delta_info_t *dt; + delta_field_t *pFieldInfo; + delta_t *pField; + int i; + + // get the delta struct + dt = Delta_FindStruct( pStructName ); + Assert( dt != NULL ); + + // check for coexisting field + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + if( !Q_strcmp( pField->name, pName )) + { + MsgDev( D_NOTE, "Delta_Add: %s->%s already existing\n", pStructName, pName ); + return false; // field already exist + } + } + + // find field description + pFieldInfo = Delta_FindFieldInfo( dt->pInfo, pName ); + if( !pFieldInfo ) + { + MsgDev( D_ERROR, "Delta_Add: couldn't find description for %s->%s\n", pStructName, pName ); + return false; + } + + if( dt->numFields + 1 > dt->maxFields ) + { + MsgDev( D_WARN, "Delta_Add: can't add %s->%s encoder list is full\n", pStructName, pName ); + return false; // too many fields specified (duplicated ?) + } + + // allocate a new one + dt->pFields = Z_Realloc( dt->pFields, (dt->numFields + 1) * sizeof( delta_t )); + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ); + + // copy info to new field + pField->name = pFieldInfo->name; + pField->offset = pFieldInfo->offset; + pField->size = pFieldInfo->size; + pField->flags = flags; + pField->bits = bits; + pField->multiplier = mul; + pField->post_multiplier = post_mul; + dt->numFields++; + + return true; +} + +void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ) +{ + int nameIndex; + delta_info_t *dt; + + Assert( pField != NULL ); + + if( !pField->name || !*pField->name ) + return; // not initialized ? + + dt = Delta_FindStructByIndex( tableIndex ); + Assert( dt && dt->bInitialized ); + + nameIndex = Delta_IndexForFieldInfo( dt->pInfo, pField->name ); + Assert( nameIndex >= 0 && nameIndex < dt->maxFields ); + + MSG_BeginServerCmd( msg, svc_deltatable ); + MSG_WriteUBitLong( msg, tableIndex, 4 ); // assume we support 16 network tables + MSG_WriteUBitLong( msg, nameIndex, 8 ); // 255 fields by struct should be enough + MSG_WriteUBitLong( msg, pField->flags, 10 ); // flags are indicated various input types + MSG_WriteUBitLong( msg, pField->bits - 1, 5 ); // max received value is 32 (32 bit) + + // multipliers is null-compressed + if( pField->multiplier != 1.0f ) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteFloat( msg, pField->multiplier ); + } + else MSG_WriteOneBit( msg, 0 ); + + if( pField->post_multiplier != 1.0f ) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteFloat( msg, pField->post_multiplier ); + } + else MSG_WriteOneBit( msg, 0 ); +} + +void Delta_ParseTableField( sizebuf_t *msg ) +{ + int tableIndex, nameIndex; + float mul = 1.0f, post_mul = 1.0f; + int flags, bits; + const char *pName; + delta_info_t *dt; + + tableIndex = MSG_ReadUBitLong( msg, 4 ); + dt = Delta_FindStructByIndex( tableIndex ); + + Assert( dt != NULL ); + + nameIndex = MSG_ReadUBitLong( msg, 8 ); // read field name index + Assert( nameIndex >= 0 && nameIndex < dt->maxFields ); + pName = dt->pInfo[nameIndex].name; + flags = MSG_ReadUBitLong( msg, 10 ); + bits = MSG_ReadUBitLong( msg, 5 ) + 1; + + // read the multipliers + if( MSG_ReadOneBit( msg )) + mul = MSG_ReadFloat( msg ); + + if( MSG_ReadOneBit( msg )) + post_mul = MSG_ReadFloat( msg ); + + // delta encoders it's already initialized on this machine (local game) + if( delta_init ) return; + + // add field to table + Delta_AddField( dt->pName, pName, flags, bits, mul, post_mul ); +} + +qboolean Delta_ParseField( char **delta_script, const delta_field_t *pInfo, delta_t *pField, qboolean bPost ) +{ + string token; + delta_field_t *pFieldInfo; + char *oldpos; + + *delta_script = COM_ParseFile( *delta_script, token ); + if( Q_strcmp( token, "(" )) + { + MsgDev( D_ERROR, "Delta_ParseField: expected '(', found '%s' instead\n", token ); + return false; + } + + // read the variable name + if(( *delta_script = COM_ParseFile( *delta_script, token )) == NULL ) + { + MsgDev( D_ERROR, "Delta_ParseField: missing field name\n" ); + return false; + } + + pFieldInfo = Delta_FindFieldInfo( pInfo, token ); + if( !pFieldInfo ) + { + MsgDev( D_ERROR, "Delta_ParseField: unable to find field %s\n", token ); + return false; + } + + *delta_script = COM_ParseFile( *delta_script, token ); + if( Q_strcmp( token, "," )) + { + MsgDev( D_ERROR, "Delta_ParseField: expected ',', found '%s' instead\n", token ); + return false; + } + + // copy base info to new field + pField->name = pFieldInfo->name; + pField->offset = pFieldInfo->offset; + pField->size = pFieldInfo->size; + pField->flags = 0; + + // read delta-flags + while(( *delta_script = COM_ParseFile( *delta_script, token )) != NULL ) + { + if( !Q_strcmp( token, "," )) + break; // end of flags argument + + if( !Q_strcmp( token, "|" )) + continue; + + if( !Q_strcmp( token, "DT_BYTE" )) + pField->flags |= DT_BYTE; + else if( !Q_strcmp( token, "DT_SHORT" )) + pField->flags |= DT_SHORT; + else if( !Q_strcmp( token, "DT_FLOAT" )) + pField->flags |= DT_FLOAT; + else if( !Q_strcmp( token, "DT_INTEGER" )) + pField->flags |= DT_INTEGER; + else if( !Q_strcmp( token, "DT_ANGLE" )) + pField->flags |= DT_ANGLE; + else if( !Q_strcmp( token, "DT_TIMEWINDOW_8" )) + pField->flags |= DT_TIMEWINDOW_8; + else if( !Q_strcmp( token, "DT_TIMEWINDOW_BIG" )) + pField->flags |= DT_TIMEWINDOW_BIG; + else if( !Q_strcmp( token, "DT_STRING" )) + pField->flags |= DT_STRING; + else if( !Q_strcmp( token, "DT_SIGNED" )) + pField->flags |= DT_SIGNED; + } + + if( Q_strcmp( token, "," )) + { + MsgDev( D_ERROR, "Delta_ParseField: expected ',', found '%s' instead\n", token ); + return false; + } + + // read delta-bits + + if(( *delta_script = COM_ParseFile( *delta_script, token )) == NULL ) + { + MsgDev( D_ERROR, "Delta_ReadField: %s field bits argument is missing\n", pField->name ); + return false; + } + + pField->bits = Q_atoi( token ); + + *delta_script = COM_ParseFile( *delta_script, token ); + if( Q_strcmp( token, "," )) + { + MsgDev( D_ERROR, "Delta_ReadField: expected ',', found '%s' instead\n", token ); + return false; + } + + // read delta-multiplier + if(( *delta_script = COM_ParseFile( *delta_script, token )) == NULL ) + { + MsgDev( D_ERROR, "Delta_ReadField: %s missing 'multiplier' argument\n", pField->name ); + return false; + } + + pField->multiplier = Q_atof( token ); + + if( bPost ) + { + *delta_script = COM_ParseFile( *delta_script, token ); + if( Q_strcmp( token, "," )) + { + MsgDev( D_ERROR, "Delta_ReadField: expected ',', found '%s' instead\n", token ); + return false; + } + + // read delta-postmultiplier + if(( *delta_script = COM_ParseFile( *delta_script, token )) == NULL ) + { + MsgDev( D_ERROR, "Delta_ReadField: %s missing 'post_multiply' argument\n", pField->name ); + return false; + } + + pField->post_multiplier = Q_atof( token ); + } + else + { + // to avoid division by zero + pField->post_multiplier = 1.0f; + } + + // closing brace... + *delta_script = COM_ParseFile( *delta_script, token ); + if( Q_strcmp( token, ")" )) + { + MsgDev( D_ERROR, "Delta_ParseField: expected ')', found '%s' instead\n", token ); + return false; + } + + // ... and trying to parse optional ',' post-symbol + oldpos = *delta_script; + *delta_script = COM_ParseFile( *delta_script, token ); + if( token[0] != ',' ) *delta_script = oldpos; // not a ',' + + return true; +} + +void Delta_ParseTable( char **delta_script, delta_info_t *dt, const char *encodeDll, const char *encodeFunc ) +{ + string token; + delta_t *pField; + const delta_field_t *pInfo; + + // allocate the delta-structures + if( !dt->pFields ) dt->pFields = (delta_t *)Z_Malloc( dt->maxFields * sizeof( delta_t )); + + pField = dt->pFields; + pInfo = dt->pInfo; + dt->numFields = 0; + + // assume we have handled '{' + while(( *delta_script = COM_ParseFile( *delta_script, token )) != NULL ) + { + Assert( dt->numFields <= dt->maxFields ); + + if( !Q_strcmp( token, "DEFINE_DELTA" )) + { + if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], false )) + dt->numFields++; + } + else if( !Q_strcmp( token, "DEFINE_DELTA_POST" )) + { + if( Delta_ParseField( delta_script, pInfo, &pField[dt->numFields], true )) + dt->numFields++; + } + else if( token[0] == '}' ) + { + // end of the section + break; + } + } + + // copy function name + Q_strncpy( dt->funcName, encodeFunc, sizeof( dt->funcName )); + + if( !Q_stricmp( encodeDll, "none" )) + dt->customEncode = CUSTOM_NONE; + else if( !Q_stricmp( encodeDll, "gamedll" )) + dt->customEncode = CUSTOM_SERVER_ENCODE; + else if( !Q_stricmp( encodeDll, "clientdll" )) + dt->customEncode = CUSTOM_CLIENT_ENCODE; + + // adjust to fit memory size + if( dt->numFields < dt->maxFields ) + { + dt->pFields = Z_Realloc( dt->pFields, dt->numFields * sizeof( delta_t )); + } + + dt->bInitialized = true; // table is ok +} + +void Delta_InitFields( void ) +{ + char *afile, *pfile; + string encodeDll, encodeFunc, token; + delta_info_t *dt; + + afile = FS_LoadFile( DELTA_PATH, NULL, false ); + if( !afile ) Sys_Error( "DELTA_Load: couldn't load file %s\n", DELTA_PATH ); + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + dt = Delta_FindStruct( token ); + + if( dt == NULL ) + { + Sys_Error( "%s: unknown struct %s\n", DELTA_PATH, token ); + } + + pfile = COM_ParseFile( pfile, encodeDll ); + + if( !Q_stricmp( encodeDll, "none" )) + Q_strcpy( encodeFunc, "null" ); + else pfile = COM_ParseFile( pfile, encodeFunc ); + + // jump to '{' + pfile = COM_ParseFile( pfile, token ); + + if( token[0] != '{' ) + { + Sys_Error( "%s: missing '{' in section %s\n", DELTA_PATH, dt->pName ); + } + + Delta_ParseTable( &pfile, dt, encodeDll, encodeFunc ); + } + Mem_Free( afile ); +#if 0 + // adding some required fields that user may forget or don't know how to specified + Delta_AddField( "event_t", "velocity[0]", DT_SIGNED | DT_FLOAT, 16, 8.0f, 1.0f ); + Delta_AddField( "event_t", "velocity[1]", DT_SIGNED | DT_FLOAT, 16, 8.0f, 1.0f ); + Delta_AddField( "event_t", "velocity[2]", DT_SIGNED | DT_FLOAT, 16, 8.0f, 1.0f ); +#endif +} + +void Delta_Init( void ) +{ + delta_info_t *dt; + + // shutdown it first + if( delta_init ) Delta_Shutdown (); + + Delta_InitFields (); // initialize fields + delta_init = true; + + dt = Delta_FindStruct( "movevars_t" ); + + Assert( dt != NULL ); + if( dt->bInitialized ) return; // "movevars_t" already specified by user + + // create movevars_t delta internal + Delta_AddField( "movevars_t", "gravity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "stopspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "maxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "spectatormaxspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "accelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "airaccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "wateraccelerate", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "friction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "edgefriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "waterfriction", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "bounce", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "stepsize", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); + Delta_AddField( "movevars_t", "maxvelocity", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + + if( host.features & ENGINE_WRITE_LARGE_COORD ) + Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 18, 1.0f, 1.0f ); + else Delta_AddField( "movevars_t", "zmax", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + + Delta_AddField( "movevars_t", "waveHeight", DT_FLOAT|DT_SIGNED, 16, 16.0f, 1.0f ); + Delta_AddField( "movevars_t", "skyName", DT_STRING, 1, 1.0f, 1.0f ); + Delta_AddField( "movevars_t", "footsteps", DT_INTEGER, 1, 1.0f, 1.0f ); + Delta_AddField( "movevars_t", "rollangle", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( "movevars_t", "rollspeed", DT_FLOAT|DT_SIGNED, 16, 8.0f, 1.0f ); + Delta_AddField( "movevars_t", "skycolor_r", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); // 0 - 264 + Delta_AddField( "movevars_t", "skycolor_g", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( "movevars_t", "skycolor_b", DT_FLOAT|DT_SIGNED, 16, 1.0f, 1.0f ); + Delta_AddField( "movevars_t", "skyvec_x", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); // 0 - 1 + Delta_AddField( "movevars_t", "skyvec_y", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( "movevars_t", "skyvec_z", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( "movevars_t", "wateralpha", DT_FLOAT|DT_SIGNED, 16, 32.0f, 1.0f ); + Delta_AddField( "movevars_t", "fog_settings", DT_INTEGER, 32, 1.0f, 1.0f ); + + // now done + dt->bInitialized = true; +} + +void Delta_InitClient( void ) +{ + int i, numActive = 0; + + // already initalized + if( delta_init ) return; + + for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + { + if( dt_info[i].numFields > 0 ) + { + dt_info[i].bInitialized = true; + numActive++; + } + } + + if( numActive ) delta_init = true; +} + +void Delta_Shutdown( void ) +{ + int i; + + if( !delta_init ) return; + + for( i = 0; i < NUM_FIELDS( dt_info ); i++ ) + { + dt_info[i].numFields = 0; + dt_info[i].customEncode = CUSTOM_NONE; + dt_info[i].userCallback = NULL; + dt_info[i].funcName[0] = '\0'; + + if( dt_info[i].pFields ) + { + Z_Free( dt_info[i].pFields ); + dt_info[i].pFields = NULL; + } + + dt_info[i].bInitialized = false; + } + + delta_init = false; +} + +/* +===================== +Delta_ClampIntegerField + +prevent data to out of range +===================== +*/ +int Delta_ClampIntegerField( int iValue, qboolean bSigned, int bits ) +{ + switch( bits ) + { + case 1: + iValue = bound( 0, (byte)iValue, 1 ); + break; + case 2: + if( bSigned ) iValue = bound( -2, (short)iValue, 1 ); + else iValue = bound( 0, (word)iValue, 3 ); + break; + case 3: + if( bSigned ) iValue = bound( -4, (short)iValue, 3 ); + else iValue = bound( 0, (word)iValue, 7 ); + break; + case 4: + if( bSigned ) iValue = bound( -8, (short)iValue, 7 ); + else iValue = bound( 0, (word)iValue, 15 ); + break; + case 5: + if( bSigned ) iValue = bound( -16, (short)iValue, 15 ); + else iValue = bound( 0, (word)iValue, 31 ); + break; + case 6: + if( bSigned ) iValue = bound( -32, (short)iValue, 31 ); + else iValue = bound( 0, (word)iValue, 63 ); + break; + case 7: + if( bSigned ) iValue = bound( -64, (short)iValue, 63 ); + else iValue = bound( 0, (word)iValue, 127 ); + break; + case 8: + if( bSigned ) iValue = bound( -128, (short)iValue, 127 ); + else iValue = bound( 0, (word)iValue, 255 ); + break; + case 9: + if( bSigned ) iValue = bound( -256, (short)iValue, 255 ); + else iValue = bound( 0, (word)iValue, 511 ); + break; + case 10: + if( bSigned ) iValue = bound( -512, (short)iValue, 511 ); + else iValue = bound( 0, (word)iValue, 1023 ); + break; + case 11: + if( bSigned ) iValue = bound( -1024, (short)iValue, 1023 ); + else iValue = bound( 0, (word)iValue, 2047 ); + break; + case 12: + if( bSigned ) iValue = bound( -2048, (short)iValue, 2047 ); + else iValue = bound( 0, (word)iValue, 4095 ); + break; + case 13: + if( bSigned ) iValue = bound( -4096, (short)iValue, 4095 ); + else iValue = bound( 0, (word)iValue, 8191 ); + break; + case 14: + if( bSigned ) iValue = bound( -8192, (short)iValue, 8191 ); + else iValue = bound( 0, (word)iValue, 16383 ); + break; + case 15: + if( bSigned ) iValue = bound( -16384, (short)iValue, 16383 ); + else iValue = bound( 0, (word)iValue, 32767 ); + break; + case 16: + if( bSigned ) iValue = bound( -32768, (short)iValue, 32767 ); + else iValue = bound( 0, (word)iValue, 65535 ); + break; + } + + return iValue; // clamped; +} + +/* +===================== +Delta_CompareField + +compare fields by offsets +assume from and to is valid +===================== +*/ +qboolean Delta_CompareField( delta_t *pField, void *from, void *to, float timebase ) +{ + qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; + float val_a, val_b; + int fromF, toF; + + Assert( pField != NULL ); + Assert( from != NULL ); + Assert( to != NULL ); + + if( pField->bInactive ) + return true; + + fromF = toF = 0; + + if( pField->flags & DT_BYTE ) + { + if( pField->flags & DT_SIGNED ) + { + fromF = *(signed char *)((byte *)from + pField->offset ); + toF = *(signed char *)((byte *)to + pField->offset ); + } + else + { + fromF = *(byte *)((byte *)from + pField->offset ); + toF = *(byte *)((byte *)to + pField->offset ); + } + + fromF = Delta_ClampIntegerField( fromF, bSigned, pField->bits ); + toF = Delta_ClampIntegerField( toF, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) fromF *= pField->multiplier; + if( pField->multiplier != 1.0f ) toF *= pField->multiplier; + } + else if( pField->flags & DT_SHORT ) + { + if( pField->flags & DT_SIGNED ) + { + fromF = *(short *)((byte *)from + pField->offset ); + toF = *(short *)((byte *)to + pField->offset ); + } + else + { + fromF = *(word *)((byte *)from + pField->offset ); + toF = *(word *)((byte *)to + pField->offset ); + } + + fromF = Delta_ClampIntegerField( fromF, bSigned, pField->bits ); + toF = Delta_ClampIntegerField( toF, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) fromF *= pField->multiplier; + if( pField->multiplier != 1.0f ) toF *= pField->multiplier; + } + else if( pField->flags & DT_INTEGER ) + { + if( pField->flags & DT_SIGNED ) + { + fromF = *(int *)((byte *)from + pField->offset ); + toF = *(int *)((byte *)to + pField->offset ); + } + else + { + fromF = *(uint *)((byte *)from + pField->offset ); + toF = *(uint *)((byte *)to + pField->offset ); + } + + fromF = Delta_ClampIntegerField( fromF, bSigned, pField->bits ); + toF = Delta_ClampIntegerField( toF, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) fromF *= pField->multiplier; + if( pField->multiplier != 1.0f ) toF *= pField->multiplier; + } + else if( pField->flags & ( DT_ANGLE|DT_FLOAT )) + { + // don't convert floats to integers + fromF = *((int *)((byte *)from + pField->offset )); + toF = *((int *)((byte *)to + pField->offset )); + } + else if( pField->flags & DT_TIMEWINDOW_8 ) + { + val_a = Q_rint((*(float *)((byte *)from + pField->offset )) * 100.0f ); + val_b = Q_rint((*(float *)((byte *)to + pField->offset )) * 100.0f ); + val_a -= Q_rint(timebase * 100.0f); + val_b -= Q_rint(timebase * 100.0f); + fromF = *((int *)&val_a); + toF = *((int *)&val_b); + } + else if( pField->flags & DT_TIMEWINDOW_BIG ) + { + val_a = (*(float *)((byte *)from + pField->offset )); + val_b = (*(float *)((byte *)to + pField->offset )); + + if( pField->multiplier != 1.0f ) + { + val_a *= pField->multiplier; + val_b *= pField->multiplier; + val_a = (timebase * pField->multiplier) - val_a; + val_b = (timebase * pField->multiplier) - val_b; + } + else + { + val_a = timebase - val_a; + val_b = timebase - val_b; + } + + fromF = *((int *)&val_a); + toF = *((int *)&val_b); + } + else if( pField->flags & DT_STRING ) + { + // compare strings + char *s1 = (char *)((byte *)from + pField->offset ); + char *s2 = (char *)((byte *)to + pField->offset ); + + // 0 is equal, otherwise not equal + toF = Q_strcmp( s1, s2 ); + } + + return ( fromF == toF ) ? true : false; +} + +/* +===================== +Delta_TestBaseline + +compare baselines to find optimal +===================== +*/ +int Delta_TestBaseline( entity_state_t *from, entity_state_t *to, qboolean player, float timebase ) +{ + delta_info_t *dt = NULL; + delta_t *pField; + int i, countBits; + int numChanges = 0; + + countBits = MAX_ENTITY_BITS + 2; + + if( to == NULL ) + { + if( from == NULL ) return 0; + return countBits; + } + + if( FBitSet( to->entityType, ENTITY_BEAM )) + dt = Delta_FindStruct( "custom_entity_state_t" ); + else if( player ) + dt = Delta_FindStruct( "entity_state_player_t" ); + else dt = Delta_FindStruct( "entity_state_t" ); + + Assert( dt && dt->bInitialized ); + + countBits++; // entityType flag + + pField = dt->pFields; + Assert( pField != NULL ); + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + // flag about field change (sets always) + countBits++; + + if( !Delta_CompareField( pField, from, to, timebase )) + { + // strings are handled difference + if( FBitSet( pField->flags, DT_STRING )) + countBits += Q_strlen(((byte *)to + pField->offset )) * 8; + else countBits += pField->bits; + } + } + + // g-cont. compare bitcount directly no reason to call BitByte here + return countBits; +} + +/* +===================== +Delta_WriteField + +write fields by offsets +assume from and to is valid +===================== +*/ +qboolean Delta_WriteField( sizebuf_t *msg, delta_t *pField, void *from, void *to, float timebase ) +{ + qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; + float flValue, flAngle, flTime; + uint iValue; + const char *pStr; + + if( Delta_CompareField( pField, from, to, timebase )) + { + MSG_WriteOneBit( msg, 0 ); // unchanged + return false; + } + + MSG_WriteOneBit( msg, 1 ); // changed + + if( pField->flags & DT_BYTE ) + { + iValue = *(byte *)((byte *)to + pField->offset ); + iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) iValue *= pField->multiplier; + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_SHORT ) + { + iValue = *(word *)((byte *)to + pField->offset ); + iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) iValue *= pField->multiplier; + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_INTEGER ) + { + iValue = *(uint *)((byte *)to + pField->offset ); + iValue = Delta_ClampIntegerField( iValue, bSigned, pField->bits ); + if( pField->multiplier != 1.0f ) iValue *= pField->multiplier; + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_FLOAT ) + { + flValue = *(float *)((byte *)to + pField->offset ); + iValue = (int)(flValue * pField->multiplier); + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_ANGLE ) + { + flAngle = *(float *)((byte *)to + pField->offset ); + + // NOTE: never applies multipliers to angle because + // result may be wrong on client-side + MSG_WriteBitAngle( msg, flAngle, pField->bits ); + } + else if( pField->flags & DT_TIMEWINDOW_8 ) + { + flValue = *(float *)((byte *)to + pField->offset ); + flTime = Q_rint( timebase * 100.0f ) - Q_rint(flValue * 100.0f); + iValue = (uint)abs( flTime ); + + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_TIMEWINDOW_BIG ) + { + flValue = *(float *)((byte *)to + pField->offset ); + flTime = Q_rint( timebase * pField->multiplier ) - Q_rint( flValue * pField->multiplier ); + iValue = (uint)abs( flTime ); + + MSG_WriteBitLong( msg, iValue, pField->bits, bSigned ); + } + else if( pField->flags & DT_STRING ) + { + pStr = (char *)((byte *)to + pField->offset ); + MSG_WriteString( msg, pStr ); + } + return true; +} + +/* +===================== +Delta_ReadField + +read fields by offsets +assume 'from' and 'to' is valid +===================== +*/ +qboolean Delta_ReadField( sizebuf_t *msg, delta_t *pField, void *from, void *to, float timebase ) +{ + qboolean bSigned = ( pField->flags & DT_SIGNED ) ? true : false; + float flValue, flAngle, flTime; + qboolean bChanged; + uint iValue; + const char *pStr; + char *pOut; + + bChanged = MSG_ReadOneBit( msg ); + + Assert( pField->multiplier != 0.0f ); + + if( pField->flags & DT_BYTE ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( pField->multiplier != 1.0f ) iValue /= pField->multiplier; + } + else + { + iValue = *(byte *)((byte *)from + pField->offset ); + } + *(byte *)((byte *)to + pField->offset ) = iValue; + } + else if( pField->flags & DT_SHORT ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( pField->multiplier != 1.0f ) iValue /= pField->multiplier; + } + else + { + iValue = *(word *)((byte *)from + pField->offset ); + } + *(word *)((byte *)to + pField->offset ) = iValue; + } + else if( pField->flags & DT_INTEGER ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + if( pField->multiplier != 1.0f ) iValue /= pField->multiplier; + } + else + { + iValue = *(uint *)((byte *)from + pField->offset ); + } + *(uint *)((byte *)to + pField->offset ) = iValue; + } + else if( pField->flags & DT_FLOAT ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + flValue = (int)iValue * ( 1.0f / pField->multiplier ); + flValue = flValue * pField->post_multiplier; + } + else + { + flValue = *(float *)((byte *)from + pField->offset ); + } + *(float *)((byte *)to + pField->offset ) = flValue; + } + else if( pField->flags & DT_ANGLE ) + { + if( bChanged ) + { + flAngle = MSG_ReadBitAngle( msg, pField->bits ); + } + else + { + flAngle = *(float *)((byte *)from + pField->offset ); + } + *(float *)((byte *)to + pField->offset ) = flAngle; + } + else if( pField->flags & DT_TIMEWINDOW_8 ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + flValue = (float)((int)(iValue * 0.01f )); + flTime = timebase + flValue; + } + else + { + flTime = *(float *)((byte *)from + pField->offset ); + } + *(float *)((byte *)to + pField->offset ) = flTime; + } + else if( pField->flags & DT_TIMEWINDOW_BIG ) + { + if( bChanged ) + { + iValue = MSG_ReadBitLong( msg, pField->bits, bSigned ); + flValue = (float)((int)iValue) * ( 1.0f / pField->multiplier ); + flTime = timebase + flValue; + } + else + { + flTime = *(float *)((byte *)from + pField->offset ); + } + *(float *)((byte *)to + pField->offset ) = flTime; + } + else if( pField->flags & DT_STRING ) + { + if( bChanged ) + { + pStr = MSG_ReadString( msg ); + } + else + { + pStr = (char *)((byte *)from + pField->offset ); + } + + pOut = (char *)((byte *)to + pField->offset ); + Q_strncpy( pOut, pStr, pField->size ); + } + return bChanged; +} + +/* +============================================================================= + +usercmd_t communication + +============================================================================= +*/ +/* +===================== +MSG_WriteDeltaUsercmd +===================== +*/ +void MSG_WriteDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "usercmd_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_WriteField( msg, pField, from, to, 0.0f ); + } +} + +/* +===================== +MSG_ReadDeltaUsercmd +===================== +*/ +void MSG_ReadDeltaUsercmd( sizebuf_t *msg, usercmd_t *from, usercmd_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "usercmd_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + *to = *from; + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, 0.0f ); + } + + COM_NormalizeAngles( to->viewangles ); +} + +/* +============================================================================ + +event_args_t communication + +============================================================================ +*/ +/* +===================== +MSG_WriteDeltaEvent +===================== +*/ +void MSG_WriteDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "event_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_WriteField( msg, pField, from, to, 0.0f ); + } +} + +/* +===================== +MSG_ReadDeltaEvent +===================== +*/ +void MSG_ReadDeltaEvent( sizebuf_t *msg, event_args_t *from, event_args_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "event_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + *to = *from; + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, 0.0f ); + } +} + +/* +============================================================================= + +movevars_t communication + +============================================================================= +*/ +qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i, startBit; + int numChanges = 0; + + dt = Delta_FindStruct( "movevars_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + startBit = msg->iCurBit; + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + MSG_BeginServerCmd( msg, svc_deltamovevars ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + if( Delta_WriteField( msg, pField, from, to, 0.0f )) + numChanges++; + } + + // if we have no changes - kill the message + if( !numChanges ) + { + MSG_SeekToBit( msg, startBit, SEEK_SET ); + return false; + } + return true; +} + +void MSG_ReadDeltaMovevars( sizebuf_t *msg, movevars_t *from, movevars_t *to ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "movevars_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + *to = *from; + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, 0.0f ); + } +} + +/* +============================================================================= + +clientdata_t communication + +============================================================================= +*/ +/* +================== +MSG_WriteClientData + +Writes current client data only for local client +Other clients can grab the client state from entity_state_t +================== +*/ +void MSG_WriteClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, float timebase ) +{ + delta_t *pField; + delta_info_t *dt; + int i, startBit; + int numChanges = 0; + + dt = Delta_FindStruct( "clientdata_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + startBit = msg->iCurBit; + + MSG_WriteOneBit( msg, 1 ); // have clientdata + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + if( Delta_WriteField( msg, pField, from, to, timebase )) + numChanges++; + } + + if( numChanges ) return; // we have updates + + MSG_SeekToBit( msg, startBit, SEEK_SET ); + MSG_WriteOneBit( msg, 0 ); // no changes +} + +/* +================== +MSG_ReadClientData + +Read the clientdata +================== +*/ +void MSG_ReadClientData( sizebuf_t *msg, clientdata_t *from, clientdata_t *to, float timebase ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "clientdata_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + *to = *from; + + if( !MSG_ReadOneBit( msg )) + return; // we have no changes + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, timebase ); + } +} + +/* +============================================================================= + +weapon_data_t communication + +============================================================================= +*/ +/* +================== +MSG_WriteWeaponData + +Writes current client data only for local client +Other clients can grab the client state from entity_state_t +================== +*/ +void MSG_WriteWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase, int index ) +{ + delta_t *pField; + delta_info_t *dt; + int i, startBit; + int numChanges = 0; + + dt = Delta_FindStruct( "weapon_data_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + startBit = msg->iCurBit; + + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, index, MAX_WEAPON_BITS ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + if( Delta_WriteField( msg, pField, from, to, timebase )) + numChanges++; + } + + // if we have no changes - kill the message + if( !numChanges ) MSG_SeekToBit( msg, startBit, SEEK_SET ); +} + +/* +================== +MSG_ReadWeaponData + +Read the clientdata +================== +*/ +void MSG_ReadWeaponData( sizebuf_t *msg, weapon_data_t *from, weapon_data_t *to, float timebase ) +{ + delta_t *pField; + delta_info_t *dt; + int i; + + dt = Delta_FindStruct( "weapon_data_t" ); + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + *to = *from; + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, timebase ); + } +} + +/* +============================================================================= + +entity_state_t communication + +============================================================================= +*/ +/* +================== +MSG_WriteDeltaEntity + +Writes part of a packetentities message, including the entity number. +Can delta from either a baseline or a previous packet_entity +If to is NULL, a remove entity update will be sent +If force is not set, then nothing at all will be generated if the entity is +identical, under the assumption that the in-order delta code will catch it. +================== +*/ +void MSG_WriteDeltaEntity( entity_state_t *from, entity_state_t *to, sizebuf_t *msg, qboolean force, qboolean player, float timebase, int baseline ) +{ + delta_info_t *dt = NULL; + delta_t *pField; + int i, startBit; + int numChanges = 0; + + if( to == NULL ) + { + int fRemoveType; + + if( from == NULL ) return; + + // a NULL to is a delta remove message + MSG_WriteUBitLong( msg, from->number, MAX_ENTITY_BITS ); + + // fRemoveType: + // 0 - keep alive, has delta-update + // 1 - remove from delta message (but keep states) + // 2 - completely remove from server + if( force ) fRemoveType = 2; + else fRemoveType = 1; + + MSG_WriteUBitLong( msg, fRemoveType, 2 ); + return; + } + + startBit = msg->iCurBit; + + if( to->number < 0 || to->number >= GI->max_edicts ) + Host_Error( "MSG_WriteDeltaEntity: Bad entity number: %i\n", to->number ); + + MSG_WriteUBitLong( msg, to->number, MAX_ENTITY_BITS ); + MSG_WriteUBitLong( msg, 0, 2 ); // alive + + if( baseline != 0 ) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteSBitLong( msg, baseline, 7 ); + } + else MSG_WriteOneBit( msg, 0 ); + + if( force || ( to->entityType != from->entityType )) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, to->entityType, 2 ); + numChanges++; + } + else MSG_WriteOneBit( msg, 0 ); + + if( FBitSet( to->entityType, ENTITY_BEAM )) + { + dt = Delta_FindStruct( "custom_entity_state_t" ); + } + else if( player ) + { + dt = Delta_FindStruct( "entity_state_player_t" ); + } + else + { + dt = Delta_FindStruct( "entity_state_t" ); + } + + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + // activate fields and call custom encode func + Delta_CustomEncode( dt, from, to ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + if( Delta_WriteField( msg, pField, from, to, timebase )) + numChanges++; + } + + // if we have no changes - kill the message + if( !numChanges && !force ) MSG_SeekToBit( msg, startBit, SEEK_SET ); +} + +/* +================== +MSG_ReadDeltaEntity + +The entity number has already been read from the message, which +is how the from state is identified. + +If the delta removes the entity, entity_state_t->number will be set to MAX_EDICTS +Can go from either a baseline or a previous packet_entity +================== +*/ +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, entity_state_t *from, entity_state_t *to, int number, qboolean player, float timebase ) +{ + delta_info_t *dt = NULL; + delta_t *pField; + int i, fRemoveType; + int baseline_offset = 0; + + if( number < 0 || number >= clgame.maxEntities ) + Host_Error( "MSG_ReadDeltaEntity: bad delta entity number: %i\n", number ); + + fRemoveType = MSG_ReadUBitLong( msg, 2 ); + + if( fRemoveType ) + { + // check for a remove + memset( to, 0, sizeof( *to )); + + if( fRemoveType & 1 ) + { + // removed from delta-message + return false; + } + + if( fRemoveType & 2 ) + { + // entity was removed from server + to->number = -1; + return false; + } + + Host_Error( "MSG_ReadDeltaEntity: unknown update type %i\n", fRemoveType ); + } + + if( MSG_ReadOneBit( msg )) + baseline_offset = MSG_ReadSBitLong( msg, 7 ); + + if( baseline_offset != 0 ) + { + if( baseline_offset > 0 ) + { + int backup = cls.next_client_entities - baseline_offset; + from = &cls.packet_entities[backup % cls.num_client_entities]; + } + else + { + baseline_offset = abs( baseline_offset ); + if( baseline_offset < cl.instanced_baseline_count ) + from = &cl.instanced_baseline[baseline_offset]; + } + } + + // g-cont. probably is redundant + *to = *from; + + if( MSG_ReadOneBit( msg )) + to->entityType = MSG_ReadUBitLong( msg, 2 ); + to->number = number; + + if( FBitSet( to->entityType, ENTITY_BEAM )) + { + dt = Delta_FindStruct( "custom_entity_state_t" ); + } + else if( player ) + { + dt = Delta_FindStruct( "entity_state_player_t" ); + } + else + { + dt = Delta_FindStruct( "entity_state_t" ); + } + + Assert( dt && dt->bInitialized ); + + pField = dt->pFields; + Assert( pField != NULL ); + + // process fields + for( i = 0; i < dt->numFields; i++, pField++ ) + { + Delta_ReadField( msg, pField, from, to, timebase ); + } + + // message parsed + return true; +} + +/* +============================================================================= + + game.dll interface + +============================================================================= +*/ +void Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ) +{ + delta_info_t *dt; + + dt = Delta_FindStructByEncoder( name ); + + if( !dt || !dt->bInitialized ) + { + MsgDev( D_ERROR, "Delta_AddEncoder: couldn't find delta with specified custom encode %s\n", name ); + return; + } + + if( dt->customEncode == CUSTOM_NONE ) + { + MsgDev( D_ERROR, "Delta_AddEncoder: %s not supposed for custom encoding\n", dt->pName ); + return; + } + + // register new encode func + dt->userCallback = encodeFunc; +} + +int Delta_FindField( delta_t *pFields, const char *fieldname ) +{ + delta_info_t *dt; + delta_t *pField; + int i; + + dt = Delta_FindStructByDelta( pFields ); + if( dt == NULL || !fieldname || !fieldname[0] ) + return -1; + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + if( !Q_strcmp( pField->name, fieldname )) + return i; + } + return -1; +} + +void Delta_SetField( delta_t *pFields, const char *fieldname ) +{ + delta_info_t *dt; + delta_t *pField; + int i; + + dt = Delta_FindStructByDelta( pFields ); + if( dt == NULL || !fieldname || !fieldname[0] ) + return; + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + if( !Q_strcmp( pField->name, fieldname )) + { + pField->bInactive = false; + return; + } + } +} + +void Delta_UnsetField( delta_t *pFields, const char *fieldname ) +{ + delta_info_t *dt; + delta_t *pField; + int i; + + dt = Delta_FindStructByDelta( pFields ); + if( dt == NULL || !fieldname || !fieldname[0] ) + return; + + for( i = 0, pField = dt->pFields; i < dt->numFields; i++, pField++ ) + { + if( !Q_strcmp( pField->name, fieldname )) + { + pField->bInactive = true; + return; + } + } +} + +void Delta_SetFieldByIndex( delta_t *pFields, int fieldNumber ) +{ + delta_info_t *dt; + + dt = Delta_FindStructByDelta( pFields ); + if( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields ) + return; + + dt->pFields[fieldNumber].bInactive = false; +} + +void Delta_UnsetFieldByIndex( delta_t *pFields, int fieldNumber ) +{ + delta_info_t *dt; + + dt = Delta_FindStructByDelta( pFields ); + if( dt == NULL || fieldNumber < 0 || fieldNumber >= dt->numFields ) + return; + + dt->pFields[fieldNumber].bInactive = true; +} \ No newline at end of file diff --git a/engine/common/net_encode.h b/engine/common/net_encode.h new file mode 100644 index 00000000..aa7bf02f --- /dev/null +++ b/engine/common/net_encode.h @@ -0,0 +1,121 @@ +/* +net_encode.h - delta encode routines +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef NET_ENCODE_H +#define NET_ENCODE_H + +#define DT_BYTE BIT( 0 ) // A byte +#define DT_SHORT BIT( 1 ) // 2 byte field +#define DT_FLOAT BIT( 2 ) // A floating point field +#define DT_INTEGER BIT( 3 ) // 4 byte integer +#define DT_ANGLE BIT( 4 ) // A floating point angle ( will get masked correctly ) +#define DT_TIMEWINDOW_8 BIT( 5 ) // A floating point timestamp, relative to sv.time +#define DT_TIMEWINDOW_BIG BIT( 6 ) // and re-encoded on the client relative to the client's clock +#define DT_STRING BIT( 7 ) // A null terminated string, sent as 8 byte chars +#define DT_SIGNED BIT( 8 ) // sign modificator + +#define offsetof( s, m ) (size_t)&(((s *)0)->m) +#define NUM_FIELDS( x ) ((sizeof( x ) / sizeof( x[0] )) - 1) + +// helper macroses +#define ENTS_DEF( x ) #x, offsetof( entity_state_t, x ), sizeof( ((entity_state_t *)0)->x ) +#define UCMD_DEF( x ) #x, offsetof( usercmd_t, x ), sizeof( ((usercmd_t *)0)->x ) +#define EVNT_DEF( x ) #x, offsetof( event_args_t, x ), sizeof( ((event_args_t *)0)->x ) +#define PHYS_DEF( x ) #x, offsetof( movevars_t, x ), sizeof( ((movevars_t *)0)->x ) +#define CLDT_DEF( x ) #x, offsetof( clientdata_t, x ), sizeof( ((clientdata_t *)0)->x ) +#define WPDT_DEF( x ) #x, offsetof( weapon_data_t, x ), sizeof( ((weapon_data_t *)0)->x ) + +enum +{ + CUSTOM_NONE = 0, + CUSTOM_SERVER_ENCODE, // keyword "gamedll" + CUSTOM_CLIENT_ENCODE, // keyword "client" +}; + +// struct info (filled by engine) +typedef struct +{ + const char *name; + const int offset; + const int size; +} delta_field_t; + +// one field +typedef struct delta_s +{ + const char *name; + int offset; // in bytes + int size; // used for bounds checking in DT_STRING + int flags; // DT_INTEGER, DT_FLOAT etc + float multiplier; + float post_multiplier; // for DEFINE_DELTA_POST + int bits; // how many bits we send\receive + qboolean bInactive; // unsetted by user request +} delta_t; + +typedef void (*pfnDeltaEncode)( delta_t *pFields, const byte *from, const byte *to ); + +typedef struct +{ + const char *pName; + const delta_field_t *pInfo; + const int maxFields; // maximum number of fields in struct + int numFields; // may be merged during initialization + delta_t *pFields; + + // added these for custom entity encode + int customEncode; + char funcName[32]; + pfnDeltaEncode userCallback; + qboolean bInitialized; +} delta_info_t; + +// +// net_encode.c +// +void Delta_Init( void ); +void Delta_InitClient( void ); +void Delta_Shutdown( void ); +void Delta_InitFields( void ); +int Delta_NumTables( void ); +delta_info_t *Delta_FindStructByIndex( int index ); +void Delta_AddEncoder( char *name, pfnDeltaEncode encodeFunc ); +int Delta_FindField( delta_t *pFields, const char *fieldname ); +void Delta_SetField( delta_t *pFields, const char *fieldname ); +void Delta_UnsetField( delta_t *pFields, const char *fieldname ); +void Delta_SetFieldByIndex( struct delta_s *pFields, int fieldNumber ); +void Delta_UnsetFieldByIndex( struct delta_s *pFields, int fieldNumber ); + +// send table over network +void Delta_WriteTableField( sizebuf_t *msg, int tableIndex, const delta_t *pField ); +void Delta_ParseTableField( sizebuf_t *msg ); + + +// encode routines +void MSG_WriteDeltaUsercmd( sizebuf_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_ReadDeltaUsercmd( sizebuf_t *msg, struct usercmd_s *from, struct usercmd_s *to ); +void MSG_WriteDeltaEvent( sizebuf_t *msg, struct event_args_s *from, struct event_args_s *to ); +void MSG_ReadDeltaEvent( sizebuf_t *msg, struct event_args_s *from, struct event_args_s *to ); +qboolean MSG_WriteDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct movevars_s *to ); +void MSG_ReadDeltaMovevars( sizebuf_t *msg, struct movevars_s *from, struct movevars_s *to ); +void MSG_WriteClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, float timebase ); +void MSG_ReadClientData( sizebuf_t *msg, struct clientdata_s *from, struct clientdata_s *to, float timebase ); +void MSG_WriteWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase, int index ); +void MSG_ReadWeaponData( sizebuf_t *msg, struct weapon_data_s *from, struct weapon_data_s *to, float timebase ); +void MSG_WriteDeltaEntity( struct entity_state_s *from, struct entity_state_s *to, sizebuf_t *msg, qboolean force, qboolean pl, float tbase, int bl ); +qboolean MSG_ReadDeltaEntity( sizebuf_t *msg, struct entity_state_s *from, struct entity_state_s *to, int num, qboolean player, float timebase ); +int Delta_TestBaseline( struct entity_state_s *from, struct entity_state_s *to, qboolean player, float timebase ); + +#endif//NET_ENCODE_H \ No newline at end of file diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c new file mode 100644 index 00000000..6a05da3f --- /dev/null +++ b/engine/common/net_ws.c @@ -0,0 +1,1511 @@ +/* +net_ws.c - win network interface +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include +#include "common.h" +#include "netchan.h" +#include "mathlib.h" + +//#define NET_USE_FRAGMENTS + +#define PORT_ANY -1 +#define MAX_LOOPBACK 4 +#define MASK_LOOPBACK (MAX_LOOPBACK - 1) + +#define MAX_ROUTEABLE_PACKET 1400 +#define SPLIT_SIZE ( MAX_ROUTEABLE_PACKET - sizeof( SPLITPACKET )) +#define NET_MAX_FRAGMENTS ( NET_MAX_FRAGMENT / SPLIT_SIZE ) + +// wsock32.dll exports +static int (_stdcall *pWSACleanup)( void ); +static word (_stdcall *pNtohs)( word netshort ); +static int (_stdcall *pWSAGetLastError)( void ); +static int (_stdcall *pCloseSocket)( SOCKET s ); +static word (_stdcall *pHtons)( word hostshort ); +static dword (_stdcall *pInet_Addr)( const char* cp ); +static char* (_stdcall *pInet_Ntoa)( struct in_addr in ); +static SOCKET (_stdcall *pSocket)( int af, int type, int protocol ); +static struct hostent *(_stdcall *pGetHostByName)( const char* name ); +static int (_stdcall *pIoctlSocket)( SOCKET s, long cmd, dword* argp ); +static int (_stdcall *pWSAStartup)( word wVersionRequired, LPWSADATA lpWSAData ); +static int (_stdcall *pBind)( SOCKET s, const struct sockaddr* addr, int namelen ); +static int (_stdcall *pSetSockopt)( SOCKET s, int level, int optname, const char* optval, int optlen ); +static int (_stdcall *pRecvFrom)( SOCKET s, char* buf, int len, int flags, struct sockaddr* from, int* fromlen ); +static int (_stdcall *pSendTo)( SOCKET s, const char* buf, int len, int flags, const struct sockaddr* to, int tolen ); +static int (_stdcall *pSelect)( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout ); +static int (_stdcall *pConnect)( SOCKET s, const struct sockaddr *name, int namelen ); +static int (_stdcall *pGetSockName)( SOCKET s, struct sockaddr *name, int *namelen ); +static int (_stdcall *pSend)( SOCKET s, const char *buf, int len, int flags ); +static int (_stdcall *pRecv)( SOCKET s, char *buf, int len, int flags ); +static int (_stdcall *pGetHostName)( char *name, int namelen ); +static dword (_stdcall *pNtohl)( dword netlong ); + +static dllfunc_t winsock_funcs[] = +{ +{ "bind", (void **) &pBind }, +{ "send", (void **) &pSend }, +{ "recv", (void **) &pRecv }, +{ "ntohs", (void **) &pNtohs }, +{ "htons", (void **) &pHtons }, +{ "ntohl", (void **) &pNtohl }, +{ "socket", (void **) &pSocket }, +{ "select", (void **) &pSelect }, +{ "sendto", (void **) &pSendTo }, +{ "connect", (void **) &pConnect }, +{ "recvfrom", (void **) &pRecvFrom }, +{ "inet_addr", (void **) &pInet_Addr }, +{ "inet_ntoa", (void **) &pInet_Ntoa }, +{ "WSAStartup", (void **) &pWSAStartup }, +{ "WSACleanup", (void **) &pWSACleanup }, +{ "setsockopt", (void **) &pSetSockopt }, +{ "ioctlsocket", (void **) &pIoctlSocket }, +{ "closesocket", (void **) &pCloseSocket }, +{ "gethostname", (void **) &pGetHostName }, +{ "getsockname", (void **) &pGetSockName }, +{ "gethostbyname", (void **) &pGetHostByName }, +{ "WSAGetLastError", (void **) &pWSAGetLastError }, +{ NULL, NULL } +}; + +dll_info_t winsock_dll = { "wsock32.dll", winsock_funcs, false }; + +typedef struct +{ + byte data[NET_MAX_MESSAGE]; + int datalen; +} net_loopmsg_t; + +typedef struct +{ + net_loopmsg_t msgs[MAX_LOOPBACK]; + int get, send; +} net_loopback_t; + +typedef struct packetlag_s +{ + byte *data; // Raw stream data is stored. + int size; + netadr_t from; + float receivedtime; + struct packetlag_s *next; + struct packetlag_s *prev; +} packetlag_t; + +// split long packets. Anything over 1460 is failing on some routers. +typedef struct +{ + int current_sequence; + int split_count; + int total_size; + char buffer[NET_MAX_FRAGMENT]; +} LONGPACKET; + +// use this to pick apart the network stream, must be packed +#pragma pack(push, 1) +typedef struct +{ + int net_id; + int sequence_number; + short packet_id; +} SPLITPACKET; +#pragma pack(pop) + +typedef struct +{ + net_loopback_t loopbacks[NS_COUNT]; + packetlag_t lagdata[NS_COUNT]; + int losscount[NS_COUNT]; + float fakelag; // cached fakelag value + LONGPACKET split; + int split_flags[NET_MAX_FRAGMENTS]; + long sequence_number; + int ip_sockets[NS_COUNT]; + WSADATA winsockdata; + qboolean initialized; + qboolean configured; + qboolean allow_ip; +} net_state_t; + +static net_state_t net; +static convar_t *net_ipname; +static convar_t *net_hostport; +static convar_t *net_iphostport; +static convar_t *net_clientport; +static convar_t *net_ipclientport; +static convar_t *net_fakelag; +static convar_t *net_fakeloss; +static convar_t *net_address; +convar_t *net_clockwindow; +netadr_t net_local; + +/* +==================== +NET_OpenWinSock + +load wsock32.dll +==================== +*/ +qboolean NET_OpenWinSock( void ) +{ + // initialize the Winsock function vectors (we do this instead of statically linking + // so we can run on Win 3.1, where there isn't necessarily Winsock) + if( Sys_LoadLibrary( &winsock_dll )) + return true; + return false; +} + +/* +==================== +NET_FreeWinSock + +unload wsock32.dll +==================== +*/ +void NET_FreeWinSock( void ) +{ + Sys_FreeLibrary( &winsock_dll ); +} + +/* +==================== +NET_ErrorString +==================== +*/ +char *NET_ErrorString( void ) +{ + int err = WSANOTINITIALISED; + + if( net.initialized ) + err = pWSAGetLastError(); + + switch( err ) + { + case WSAEINTR: return "WSAEINTR"; + case WSAEBADF: return "WSAEBADF"; + case WSAEACCES: return "WSAEACCES"; + case WSAEDISCON: return "WSAEDISCON"; + case WSAEFAULT: return "WSAEFAULT"; + case WSAEINVAL: return "WSAEINVAL"; + case WSAEMFILE: return "WSAEMFILE"; + case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK"; + case WSAEINPROGRESS: return "WSAEINPROGRESS"; + case WSAEALREADY: return "WSAEALREADY"; + case WSAENOTSOCK: return "WSAENOTSOCK"; + case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ"; + case WSAEMSGSIZE: return "WSAEMSGSIZE"; + case WSAEPROTOTYPE: return "WSAEPROTOTYPE"; + case WSAENOPROTOOPT: return "WSAENOPROTOOPT"; + case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT"; + case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT"; + case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP"; + case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT"; + case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT"; + case WSAEADDRINUSE: return "WSAEADDRINUSE"; + case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL"; + case WSAENETDOWN: return "WSAENETDOWN"; + case WSAENETUNREACH: return "WSAENETUNREACH"; + case WSAENETRESET: return "WSAENETRESET"; + case WSAECONNABORTED: return "WSWSAECONNABORTEDAEINTR"; + case WSAECONNRESET: return "WSAECONNRESET"; + case WSAENOBUFS: return "WSAENOBUFS"; + case WSAEISCONN: return "WSAEISCONN"; + case WSAENOTCONN: return "WSAENOTCONN"; + case WSAESHUTDOWN: return "WSAESHUTDOWN"; + case WSAETOOMANYREFS: return "WSAETOOMANYREFS"; + case WSAETIMEDOUT: return "WSAETIMEDOUT"; + case WSAECONNREFUSED: return "WSAECONNREFUSED"; + case WSAELOOP: return "WSAELOOP"; + case WSAENAMETOOLONG: return "WSAENAMETOOLONG"; + case WSAEHOSTDOWN: return "WSAEHOSTDOWN"; + case WSASYSNOTREADY: return "WSASYSNOTREADY"; + case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED"; + case WSANOTINITIALISED: return "WSANOTINITIALISED"; + case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND"; + case WSATRY_AGAIN: return "WSATRY_AGAIN"; + case WSANO_RECOVERY: return "WSANO_RECOVERY"; + case WSANO_DATA: return "WSANO_DATA"; + default: return "NO ERROR"; + } +} + +/* +==================== +NET_NetadrToSockadr +==================== +*/ +static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr *s ) +{ + memset( s, 0, sizeof( *s )); + + if( a->type == NA_BROADCAST ) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = INADDR_BROADCAST; + } + else if( a->type == NA_IP ) + { + ((struct sockaddr_in *)s)->sin_family = AF_INET; + ((struct sockaddr_in *)s)->sin_addr.s_addr = *(int *)&a->ip; + ((struct sockaddr_in *)s)->sin_port = a->port; + } +} + +/* +==================== +NET_SockadrToNetAdr +==================== +*/ +static void NET_SockadrToNetadr( struct sockaddr *s, netadr_t *a ) +{ + if( s->sa_family == AF_INET ) + { + a->type = NA_IP; + *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->port = ((struct sockaddr_in *)s)->sin_port; + } +} + +/* +============= +NET_StringToAdr + +localhost +idnewt +idnewt:28000 +192.246.40.70 +192.246.40.70:28000 +============= +*/ +static qboolean NET_StringToSockaddr( const char *s, struct sockaddr *sadr ) +{ + char *colon; + char copy[128]; + + if( !net.initialized ) return false; + + memset( sadr, 0, sizeof( *sadr )); + + ((struct sockaddr_in *)sadr)->sin_family = AF_INET; + ((struct sockaddr_in *)sadr)->sin_port = 0; + + Q_strncpy( copy, s, sizeof( copy )); + + // strip off a trailing :port if present + for( colon = copy; *colon; colon++ ) + { + if( *colon == ':' ) + { + *colon = 0; + ((struct sockaddr_in *)sadr)->sin_port = pHtons((short)Q_atoi( colon + 1 )); + } + } + + ((struct sockaddr_in *)sadr)->sin_addr.s_addr = pInet_Addr( copy ); + + if(((struct sockaddr_in *)sadr)->sin_addr.s_addr == INADDR_NONE ) + { + struct hostent *h = pGetHostByName( copy ); + + if( h == NULL || h->h_addr == NULL ) + return false; + + ((struct sockaddr_in *)sadr)->sin_addr.s_addr = *(uint *)h->h_addr; + } + return true; +} + +/* +==================== +NET_AdrToString +==================== +*/ +char *NET_AdrToString( const netadr_t a ) +{ + if( a.type == NA_LOOPBACK ) + return "loopback"; + return va( "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], pNtohs( a.port )); +} + +/* +==================== +NET_BaseAdrToString +==================== +*/ +char *NET_BaseAdrToString( const netadr_t a ) +{ + if( a.type == NA_LOOPBACK ) + return "loopback"; + return va( "%i.%i.%i.%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3] ); +} + +/* +=================== +NET_CompareBaseAdr + +Compares without the port +=================== +*/ +qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ) +{ + if( a.type != b.type ) + return false; + + if( a.type == NA_LOOPBACK ) + return true; + + if( a.type == NA_IP ) + { + if( !memcmp( a.ip, b.ip, 4 )) + return true; + } + + return false; +} + +/* +==================== +NET_CompareClassBAdr + +Compare local masks +==================== +*/ +qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b ) +{ + if( a.type != b.type ) + return false; + + if( a.type == NA_LOOPBACK ) + return true; + + if( a.type == NA_IP ) + { + if( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] ) + return true; + } + return false; +} + +/* +==================== +NET_IsReservedAdr + +Check for reserved ip's +==================== +*/ +qboolean NET_IsReservedAdr( netadr_t a ) +{ + if( a.type == NA_LOOPBACK ) + return true; + + if( a.type == NA_IP ) + { + if( a.ip[0] == 10 || a.ip[0] == 127 ) + return true; + + if( a.ip[0] == 172 && a.ip[1] >= 16 ) + { + if( a.ip[1] >= 32 ) + return false; + return true; + } + + if( a.ip[0] == 192 && a.ip[1] >= 168 ) + return true; + } + + return false; +} + +/* +==================== +NET_CompareAdr + +Compare full address +==================== +*/ +qboolean NET_CompareAdr( const netadr_t a, const netadr_t b ) +{ + if( a.type != b.type ) + return false; + + if( a.type == NA_LOOPBACK ) + return true; + + if( a.type == NA_IP ) + { + if(!memcmp( a.ip, b.ip, 4 ) && a.port == b.port ) + return true; + return false; + } + + MsgDev( D_ERROR, "NET_CompareAdr: bad address type\n" ); + return false; +} + +/* +==================== +NET_IsLocalAddress +==================== +*/ +qboolean NET_IsLocalAddress( netadr_t adr ) +{ + return (adr.type == NA_LOOPBACK) ? true : false; +} + +/* +============= +NET_StringToAdr + +idnewt +192.246.40.70 +============= +*/ +qboolean NET_StringToAdr( const char *string, netadr_t *adr ) +{ + struct sockaddr s; + + if( !Q_stricmp( string, "localhost" )) + { + memset( adr, 0, sizeof( netadr_t )); + adr->type = NA_LOOPBACK; + return true; + } + + if( !NET_StringToSockaddr( string, &s )) + return false; + NET_SockadrToNetadr( &s, adr ); + + return true; +} + +/* +============================================================================= + +LOOPBACK BUFFERS FOR LOCAL PLAYER + +============================================================================= +*/ +/* +==================== +NET_GetLoopPacket +==================== +*/ +static qboolean NET_GetLoopPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ) +{ + net_loopback_t *loop; + int i; + + if( !data || !length ) + return false; + + loop = &net.loopbacks[sock]; + + if( loop->send - loop->get > MAX_LOOPBACK ) + loop->get = loop->send - MAX_LOOPBACK; + + if( loop->get >= loop->send ) + return false; + i = loop->get & MASK_LOOPBACK; + loop->get++; + + memcpy( data, loop->msgs[i].data, loop->msgs[i].datalen ); + *length = loop->msgs[i].datalen; + + memset( from, 0, sizeof( *from )); + from->type = NA_LOOPBACK; + + return true; +} + +/* +==================== +NET_SendLoopPacket +==================== +*/ +static void NET_SendLoopPacket( netsrc_t sock, size_t length, const void *data, netadr_t to ) +{ + net_loopback_t *loop; + int i; + + loop = &net.loopbacks[sock^1]; + + i = loop->send & MASK_LOOPBACK; + loop->send++; + + memcpy( loop->msgs[i].data, data, length ); + loop->msgs[i].datalen = length; +} + +/* +==================== +NET_ClearLoopback +==================== +*/ +static void NET_ClearLoopback( void ) +{ + net.loopbacks[0].send = net.loopbacks[0].get = 0; + net.loopbacks[1].send = net.loopbacks[1].get = 0; +} + +/* +============================================================================= + +LAG & LOSS SIMULATION SYSTEM (network debugging) + +============================================================================= +*/ +/* +================== +NET_RemoveFromPacketList + +double linked list remove entry +================== +*/ +static void NET_RemoveFromPacketList( packetlag_t *p ) +{ + p->prev->next = p->next; + p->next->prev = p->prev; + p->prev = NULL; + p->next = NULL; +} + +/* +================== +NET_ClearLaggedList + +double linked list remove queue +================== +*/ +static void NET_ClearLaggedList( packetlag_t *list ) +{ + packetlag_t *p, *n; + + p = list->next; + while( p && p != list ) + { + n = p->next; + + NET_RemoveFromPacketList( p ); + + if( p->data ) + { + Mem_Free( p->data ); + p->data = NULL; + } + + Mem_Free( p ); + p = n; + } + + list->prev = list; + list->next = list; +} + +/* +================== +NET_AddToLagged + +add lagged packet to stream +================== +*/ +static void NET_AddToLagged( netsrc_t sock, packetlag_t *list, packetlag_t *packet, netadr_t *from, size_t length, const void *data, float timestamp ) +{ + byte *pStart; + + if( packet->prev || packet->next ) + return; + + packet->prev = list->prev; + list->prev->next = packet; + list->prev = packet; + packet->next = list; + + pStart = (byte *)Z_Malloc( length ); + memcpy( pStart, data, length ); + packet->data = pStart; + packet->size = length; + packet->receivedtime = timestamp; + memcpy( &packet->from, from, sizeof( netadr_t )); +} + +/* +================== +NET_AdjustLag + +adjust time to next fake lag +================== +*/ +static void NET_AdjustLag( void ) +{ + static double lasttime = 0.0; + float diff, converge; + double dt; + + dt = host.realtime - lasttime; + dt = bound( 0.0, dt, 0.1 ); + lasttime = host.realtime; + + if( host_developer.value || !net_fakelag->value ) + { + if( net_fakelag->value != net.fakelag ) + { + diff = net_fakelag->value - net.fakelag; + converge = dt * 200.0f; + if( fabs( diff ) < converge ) + converge = fabs( diff ); + if( diff < 0.0 ) + converge = -converge; + net.fakelag += converge; + } + } + else + { + Con_Printf( "Server must enable dev-mode to activate fakelag\n" ); + Cvar_SetValue( "fakelag", 0.0 ); + net.fakelag = 0.0f; + } +} + +/* +================== +NET_LagPacket + +add fake lagged packet into rececived message +================== +*/ +static qboolean NET_LagPacket( qboolean newdata, netsrc_t sock, netadr_t *from, size_t *length, void *data ) +{ + packetlag_t *pNewPacketLag; + packetlag_t *pPacket; + int ninterval; + float curtime; + + if( net.fakelag <= 0.0f ) + { + NET_ClearLagData( true, true ); + return newdata; + } + + curtime = host.realtime; + + if( newdata ) + { + if( net_fakeloss->value != 0.0f ) + { + if( host_developer.value ) + { + net.losscount[sock]++; + if( net_fakeloss->value <= 0.0f ) + { + ninterval = fabs( net_fakeloss->value ); + if( ninterval < 2 ) ninterval = 2; + + if(( net.losscount[sock] % ninterval ) == 0 ) + return false; + } + else + { + if( COM_RandomLong( 0, 100 ) <= net_fakeloss->value ) + return false; + } + } + else + { + Cvar_SetValue( "fakeloss", 0.0 ); + } + } + + pNewPacketLag = (packetlag_t *)Z_Malloc( sizeof( packetlag_t )); + // queue packet to simulate fake lag + NET_AddToLagged( sock, &net.lagdata[sock], pNewPacketLag, from, *length, data, curtime ); + } + + pPacket = net.lagdata[sock].next; + + while( pPacket != &net.lagdata[sock] ) + { + if( pPacket->receivedtime <= curtime - ( net.fakelag / 1000.0 )) + break; + + pPacket = pPacket->next; + } + + if( pPacket == &net.lagdata[sock] ) + return false; + + NET_RemoveFromPacketList( pPacket ); + + // delivery packet from fake lag queue + memcpy( data, pPacket->data, pPacket->size ); + memcpy( &net_from, &pPacket->from, sizeof( netadr_t )); + *length = pPacket->size; + + if( pPacket->data ) + Mem_Free( pPacket->data ); + + Mem_Free( pPacket ); + + return TRUE; +} + +/* +================== +NET_GetLong + +receive long packet from network +================== +*/ +qboolean NET_GetLong( byte *pData, int size, int *outSize ) +{ + int i, sequence_number, offset; + SPLITPACKET *pHeader = (SPLITPACKET *)pData; + int packet_number; + int packet_count; + short packet_id; + + if( size < sizeof( SPLITPACKET )) + { + Con_Printf( S_ERROR "invalid split packet length %i\n", size ); + return false; + } + + sequence_number = pHeader->sequence_number; + packet_id = pHeader->packet_id; + packet_count = ( packet_id & 0xFF ); + packet_number = ( packet_id >> 8 ); + + if( packet_number >= NET_MAX_FRAGMENTS || packet_count > NET_MAX_FRAGMENTS ) + { + Con_Printf( S_ERROR "malformed packet number (%i/%i)\n", packet_number + 1, packet_count ); + return false; + } + + if( net.split.current_sequence == -1 || sequence_number != net.split.current_sequence ) + { + net.split.current_sequence = sequence_number; + net.split.split_count = packet_count; + net.split.total_size = 0; + + // clear part's sequence + for( i = 0; i < NET_MAX_FRAGMENTS; i++ ) + net.split_flags[i] = -1; + + if( net_showpackets && net_showpackets->value == 4.0f ) + Con_Printf( "<-- Split packet restart %i count %i seq\n", net.split.split_count, sequence_number ); + } + + size -= sizeof( SPLITPACKET ); + + if( net.split_flags[packet_number] != sequence_number ) + { + if( packet_number == ( packet_count - 1 )) + net.split.total_size = size + SPLIT_SIZE * ( packet_count - 1 ); + + net.split.split_count--; + net.split_flags[packet_number] = sequence_number; + + if( net_showpackets && net_showpackets->value == 4.0f ) + Con_Printf( "<-- Split packet %i of %i, %i bytes %i seq\n", packet_number + 1, packet_count, size, sequence_number ); + } + else + { + Con_DPrintf( "NET_GetLong: Ignoring duplicated split packet %i of %i ( %i bytes )\n", packet_number + 1, packet_count, size ); + } + + offset = (packet_number * SPLIT_SIZE); + memcpy( net.split.buffer + offset, pData + sizeof( SPLITPACKET ), size ); + + // have we received all of the pieces to the packet? + if( net.split.split_count <= 0 ) + { + net.split.current_sequence = -1; // Clear packet + + if( net.split.total_size > sizeof( net.split.buffer )) + { + Con_Printf( "Split packet too large! %d bytes\n", net.split.total_size ); + return false; + } + + memcpy( pData, net.split.buffer, net.split.total_size ); + *outSize = net.split.total_size; + + return true; + } + + return false; +} + +/* +================== +NET_QueuePacket + +queue normal and lagged packets +================== +*/ +qboolean NET_QueuePacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ) +{ + byte buf[NET_MAX_FRAGMENT]; + int ret = SOCKET_ERROR; + int net_socket; + int addr_len; + struct sockaddr addr; + + *length = 0; + + net_socket = net.ip_sockets[sock]; + + if( net_socket != INVALID_SOCKET ) + { + addr_len = sizeof( addr ); + ret = pRecvFrom( net_socket, buf, sizeof( buf ), 0, (struct sockaddr *)&addr, &addr_len ); + + if( ret != SOCKET_ERROR ) + { + NET_SockadrToNetadr( &addr, from ); + + if( ret < NET_MAX_FRAGMENT ) + { + // Transfer data + memcpy( data, buf, ret ); + *length = ret; + + // check for split message + if( *(int *)data == NET_HEADER_SPLITPACKET ) + { + return NET_GetLong( data, ret, length ); + } + + // lag the packet, if needed + return NET_LagPacket( true, sock, from, length, data ); + } + else + { + MsgDev( D_REPORT, "NET_QueuePacket: oversize packet from %s\n", NET_AdrToString( *from )); + } + } + else + { + int err = pWSAGetLastError(); + + switch( err ) + { + case WSAEWOULDBLOCK: + case WSAECONNRESET: + case WSAECONNREFUSED: + case WSAEMSGSIZE: + break; + default: // let's continue even after errors + MsgDev( D_ERROR, "NET_QueuePacket: %s from %s\n", NET_ErrorString(), NET_AdrToString( *from )); + break; + } + } + } + + return NET_LagPacket( false, sock, from, length, data ); +} + +/* +================== +NET_GetPacket + +Never called by the game logic, just the system event queing +================== +*/ +qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ) +{ + if( !data || !length ) + return false; + + NET_AdjustLag(); + + if( NET_GetLoopPacket( sock, from, data, length )) + { + return NET_LagPacket( true, sock, from, length, data ); + } + else + { + return NET_QueuePacket( sock, from, data, length ); + } +} + +/* +================== +NET_SendLong + +Fragment long packets, send short directly +================== +*/ +int NET_SendLong( netsrc_t sock, int net_socket, const char *buf, int len, int flags, const struct sockaddr *to, int tolen ) +{ +#ifdef NET_USE_FRAGMENTS + // do we need to break this packet up? + if( sock == NS_SERVER && len > MAX_ROUTEABLE_PACKET ) + { + char packet[MAX_ROUTEABLE_PACKET]; + int total_sent, size, packet_count; + int ret, packet_number; + SPLITPACKET *pPacket; + + net.sequence_number++; + if( net.sequence_number <= 0 ) + net.sequence_number = 1; + + pPacket = (SPLITPACKET *)packet; + pPacket->sequence_number = net.sequence_number; + pPacket->net_id = NET_HEADER_SPLITPACKET; + packet_number = 0; + total_sent = 0; + packet_count = (len + SPLIT_SIZE - 1) / SPLIT_SIZE; + + while( len > 0 ) + { + size = Q_min( SPLIT_SIZE, len ); + pPacket->packet_id = (packet_number << 8) + packet_count; + memcpy( packet + sizeof( SPLITPACKET ), buf + ( packet_number * SPLIT_SIZE ), size ); + + if( net_showpackets && net_showpackets->value == 3.0f ) + { + netadr_t adr; + + memset( &adr, 0, sizeof( adr )); + NET_SockadrToNetadr((struct sockaddr *)to, &adr ); + + Con_Printf( "Sending split %i of %i with %i bytes and seq %i to %s\n", + packet_number + 1, packet_count, size, net.sequence_number, NET_AdrToString( adr )); + } + + ret = pSendTo( net_socket, packet, size + sizeof( SPLITPACKET ), flags, to, tolen ); + if( ret < 0 ) return ret; // error + + if( ret >= size ) + total_sent += size; + len -= size; + packet_number++; + Sleep( 1 ); + } + + return total_sent; + } + else +#endif + { + // no fragmenantion for client connection + return pSendTo( net_socket, buf, len, flags, to, tolen ); + } +} + +/* +================== +NET_SendPacket +================== +*/ +void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to ) +{ + int ret, err; + struct sockaddr addr; + SOCKET net_socket; + + if( !net.initialized || to.type == NA_LOOPBACK ) + { + NET_SendLoopPacket( sock, length, data, to ); + return; + } + else if( to.type == NA_BROADCAST ) + { + net_socket = net.ip_sockets[sock]; + if( net_socket == INVALID_SOCKET ) + return; + } + else if( to.type == NA_IP ) + { + net_socket = net.ip_sockets[sock]; + if( net_socket == INVALID_SOCKET ) + return; + } + else + { + Host_Error( "NET_SendPacket: bad address type %i\n", to.type ); + } + + NET_NetadrToSockadr( &to, &addr ); + + ret = NET_SendLong( sock, net_socket, data, length, 0, &addr, sizeof( addr )); + + if( ret == SOCKET_ERROR ) + { + err = pWSAGetLastError(); + + // WSAEWOULDBLOCK is silent + if( err == WSAEWOULDBLOCK ) + return; + + // some PPP links don't allow broadcasts + if( err == WSAEADDRNOTAVAIL && to.type == NA_BROADCAST ) + return; + + // let dedicated servers continue after errors + if( host.type == HOST_DEDICATED ) + { + MsgDev( D_ERROR, "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + } + else if( err == WSAEADDRNOTAVAIL || err == WSAENOBUFS ) + { + MsgDev( D_ERROR, "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + } + else + { + Host_Error( "NET_SendPacket: %s to %s\n", NET_ErrorString(), NET_AdrToString( to )); + } + } +} + +/* +==================== +NET_BufferToBufferCompress + +generic fast compression +==================== +*/ +qboolean NET_BufferToBufferCompress( char *dest, uint *destLen, char *source, uint sourceLen ) +{ + uint uCompressedLen = 0; + byte *pbOut = NULL; + + memcpy( dest, source, sourceLen ); + pbOut = LZSS_Compress( source, sourceLen, &uCompressedLen ); + + if( pbOut && uCompressedLen > 0 && uCompressedLen <= *destLen ) + { + memcpy( dest, pbOut, uCompressedLen ); + *destLen = uCompressedLen; + free( pbOut ); + return true; + } + else + { + if( pbOut ) free( pbOut ); + memcpy( dest, source, sourceLen ); + *destLen = sourceLen; + return false; + } +} + +/* +==================== +NET_BufferToBufferDecompress + +generic fast decompression +==================== +*/ +qboolean NET_BufferToBufferDecompress( char *dest, uint *destLen, char *source, uint sourceLen ) +{ + if( LZSS_IsCompressed( source )) + { + uint uDecompressedLen = LZSS_GetActualSize( source ); + + if( uDecompressedLen <= *destLen ) + { + *destLen = LZSS_Decompress( source, dest ); + } + else + { + return false; + } + } + else + { + memcpy( dest, source, sourceLen ); + *destLen = sourceLen; + } + + return true; +} + +/* +==================== +NET_IPSocket +==================== +*/ +static int NET_IPSocket( const char *net_interface, int port, qboolean multicast ) +{ + int err, net_socket; + uint optval = 1; + struct sockaddr_in addr; + + if(( net_socket = pSocket( PF_INET, SOCK_DGRAM, IPPROTO_UDP )) == SOCKET_ERROR ) + { + err = pWSAGetLastError(); + if( err != WSAEAFNOSUPPORT ) + MsgDev( D_WARN, "NET_UDPSocket: port: %d socket: %s\n", port, NET_ErrorString( )); + return INVALID_SOCKET; + } + + if( pIoctlSocket( net_socket, FIONBIO, &optval ) == SOCKET_ERROR ) + { + MsgDev( D_WARN, "NET_UDPSocket: port: %d ioctl FIONBIO: %s\n", port, NET_ErrorString( )); + pCloseSocket( net_socket ); + return INVALID_SOCKET; + } + + // make it broadcast capable + if( pSetSockopt( net_socket, SOL_SOCKET, SO_BROADCAST, (const char *)&optval, sizeof( optval )) == SOCKET_ERROR ) + { + MsgDev( D_WARN, "NET_UDPSocket: port: %d setsockopt SO_BROADCAST: %s\n", port, NET_ErrorString( )); + pCloseSocket( net_socket ); + return INVALID_SOCKET; + } + + if( Sys_CheckParm( "-reuse" ) || multicast ) + { + if( pSetSockopt( net_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof( optval )) == SOCKET_ERROR ) + { + MsgDev( D_WARN, "NET_UDPSocket: port: %d setsockopt SO_REUSEADDR: %s\n", port, NET_ErrorString( )); + pCloseSocket( net_socket ); + return INVALID_SOCKET; + } + } + + if( Sys_CheckParm( "-tos" )) + { + optval = 16; + Con_Printf( "Enabling LOWDELAY TOS option\n" ); + + if( pSetSockopt( net_socket, IPPROTO_IP, IP_TOS, (const char *)&optval, sizeof( optval )) == SOCKET_ERROR ) + { + err = pWSAGetLastError(); + if( err != WSAENOPROTOOPT ) + Con_Printf( S_WARN "NET_UDPSocket: port: %d setsockopt IP_TOS: %s\n", port, NET_ErrorString( )); + pCloseSocket( net_socket ); + return INVALID_SOCKET; + } + } + + if( !net_interface[0] || !Q_stricmp( net_interface, "localhost" )) + addr.sin_addr.s_addr = INADDR_ANY; + else NET_StringToSockaddr( net_interface, (struct sockaddr *)&addr ); + + if( port == PORT_ANY ) addr.sin_port = 0; + else addr.sin_port = pHtons((short)port); + + addr.sin_family = AF_INET; + + if( pBind( net_socket, (void *)&addr, sizeof( addr )) == SOCKET_ERROR ) + { + MsgDev( D_WARN, "NET_UDPSocket: port: %d bind: %s\n", port, NET_ErrorString( )); + pCloseSocket( net_socket ); + return INVALID_SOCKET; + } + + if( Sys_CheckParm( "-loopback" )) + { + optval = 1; + if( pSetSockopt( net_socket, IPPROTO_IP, IP_MULTICAST_LOOP, (const char *)&optval, sizeof( optval )) == SOCKET_ERROR ) + MsgDev( D_WARN, "NET_UDPSocket: port %d setsockopt IP_MULTICAST_LOOP: %s\n", port, NET_ErrorString( )); + } + + return net_socket; +} + +/* +==================== +NET_OpenIP +==================== +*/ +static void NET_OpenIP( void ) +{ + int port, sv_port = 0, cl_port = 0; + + if( net.ip_sockets[NS_SERVER] == INVALID_SOCKET ) + { + port = net_iphostport->value; + if( !port ) port = net_hostport->value; + if( !port ) port = PORT_SERVER; // forcing to default + net.ip_sockets[NS_SERVER] = NET_IPSocket( net_ipname->string, port, false ); + + if( net.ip_sockets[NS_SERVER] == INVALID_SOCKET && host.type == HOST_DEDICATED ) + Host_Error( "Couldn't allocate dedicated server IP port %d.\n", port ); + sv_port = port; + } + + // dedicated servers don't need client ports + if( host.type == HOST_DEDICATED ) return; + + if( net.ip_sockets[NS_CLIENT] == INVALID_SOCKET ) + { + port = net_ipclientport->value; + if( !port ) port = net_clientport->value; + if( !port ) port = PORT_ANY; // forcing to default + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, port, false ); + + if( net.ip_sockets[NS_CLIENT] == INVALID_SOCKET ) + net.ip_sockets[NS_CLIENT] = NET_IPSocket( net_ipname->string, PORT_ANY, false ); + cl_port = port; + } +} + +/* +================ +NET_GetLocalAddress + +Returns the servers' ip address as a string. +================ +*/ +void NET_GetLocalAddress( void ) +{ + char buff[512]; + struct sockaddr_in address; + int namelen; + + memset( &net_local, 0, sizeof( netadr_t )); + buff[0] = '\0'; + + if( net.allow_ip ) + { + // If we have changed the ip var from the command line, use that instead. + if( Q_strcmp( net_ipname->string, "localhost" )) + { + Q_strcpy( buff, net_ipname->string ); + } + else + { + pGetHostName( buff, 512 ); + } + + // ensure that it doesn't overrun the buffer + buff[511] = 0; + + if( NET_StringToAdr( buff, &net_local )) + { + namelen = sizeof( address ); + + if( pGetSockName( net.ip_sockets[NS_SERVER], (struct sockaddr *)&address, &namelen ) == SOCKET_ERROR ) + { + // this may happens if multiple clients running on single machine + MsgDev( D_ERROR, "Could not get TCP/IP address. Reason: %s\n", NET_ErrorString( )); +// net.allow_ip = false; + } + else + { + net_local.port = address.sin_port; + Con_Printf( "Server IP address %s\n", NET_AdrToString( net_local )); + Cvar_FullSet( "net_address", va( NET_AdrToString( net_local )), FCVAR_READ_ONLY ); + } + } + else + { + MsgDev( D_ERROR, "Could not get TCP/IP address, Invalid hostname: '%s'\n", buff ); + } + } + else + { + Con_Printf( "TCP/IP Disabled.\n" ); + } +} + +/* +==================== +NET_Config + +A single player game will only use the loopback code +==================== +*/ +void NET_Config( qboolean multiplayer ) +{ + static qboolean bFirst = true; + static qboolean old_config; + + if( !net.initialized ) + return; + + if( old_config == multiplayer ) + return; + + old_config = multiplayer; + + if( multiplayer ) + { + // open sockets + if( net.allow_ip ) NET_OpenIP(); + + // get our local address, if possible + if( bFirst ) + { + NET_GetLocalAddress(); + bFirst = false; + } + } + else + { + int i; + + // shut down any existing sockets + for( i = 0; i < NS_COUNT; i++ ) + { + if( net.ip_sockets[i] != INVALID_SOCKET ) + { + pCloseSocket( net.ip_sockets[i] ); + net.ip_sockets[i] = INVALID_SOCKET; + } + } + } + + NET_ClearLoopback (); + + net.configured = multiplayer ? true : false; +} + +/* +==================== +NET_IsConfigured + +Is winsock ip initialized? +==================== +*/ +qboolean NET_IsConfigured( void ) +{ + return net.configured; +} + +/* +==================== +NET_IsActive +==================== +*/ +qboolean NET_IsActive( void ) +{ + return net.initialized; +} + +/* +==================== +NET_Sleep + +sleeps msec or until net socket is ready +==================== +*/ +void NET_Sleep( int msec ) +{ + struct timeval timeout; + fd_set fdset; + int i = 0; + + if( !net.initialized || host.type == HOST_NORMAL ) + return; // we're not a dedicated server, just run full speed + + FD_ZERO( &fdset ); + + if( net.ip_sockets[NS_SERVER] != INVALID_SOCKET ) + { + FD_SET( net.ip_sockets[NS_SERVER], &fdset ); // network socket + i = net.ip_sockets[NS_SERVER]; + } + + timeout.tv_sec = msec / 1000; + timeout.tv_usec = (msec % 1000) * 1000; + pSelect( i+1, &fdset, NULL, NULL, &timeout ); +} + +/* +==================== +NET_ClearLagData + +clear fakelag list +==================== +*/ +void NET_ClearLagData( qboolean bClient, qboolean bServer ) +{ + if( bClient ) NET_ClearLaggedList( &net.lagdata[NS_CLIENT] ); + if( bServer ) NET_ClearLaggedList( &net.lagdata[NS_SERVER] ); +} + +/* +==================== +NET_Init +==================== +*/ +void NET_Init( void ) +{ + char cmd[64]; + int i = 1; + + if( net.initialized ) return; + + net_clockwindow = Cvar_Get( "clockwindow", "0.5", 0, "timewindow to execute client moves" ); + net_address = Cvar_Get( "net_address", "0", FCVAR_READ_ONLY, "contain local address of current client" ); + net_ipname = Cvar_Get( "ip", "localhost", FCVAR_READ_ONLY, "network ip address" ); + net_iphostport = Cvar_Get( "ip_hostport", "0", FCVAR_READ_ONLY, "network ip host port" ); + net_hostport = Cvar_Get( "hostport", va( "%i", PORT_SERVER ), FCVAR_READ_ONLY, "network default host port" ); + net_ipclientport = Cvar_Get( "ip_clientport", "0", FCVAR_READ_ONLY, "network ip client port" ); + net_clientport = Cvar_Get( "clientport", va( "%i", PORT_CLIENT ), FCVAR_READ_ONLY, "network default client port" ); + net_fakelag = Cvar_Get( "fakelag", "0", 0, "lag all incoming network data (including loopback) by xxx ms." ); + net_fakeloss = Cvar_Get( "fakeloss", "0", 0, "act like we dropped the packet this % of the time." ); + + // prepare some network data + for( i = 0; i < NS_COUNT; i++ ) + { + net.lagdata[i].prev = &net.lagdata[i]; + net.lagdata[i].next = &net.lagdata[i]; + net.ip_sockets[i] = INVALID_SOCKET; + } + + if( !NET_OpenWinSock( )) // loading wsock32.dll + { + MsgDev( D_ERROR, "network failed to load wsock32.dll.\n" ); + return; + } + + if( pWSAStartup( MAKEWORD( 1, 1 ), &net.winsockdata )) + { + MsgDev( D_ERROR, "network initialization failed.\n" ); + NET_FreeWinSock(); + return; + } + + if( Sys_CheckParm( "-noip" )) + net.allow_ip = false; + else net.allow_ip = true; + + // specify custom host port + if( Sys_GetParmFromCmdLine( "-port", cmd ) && Q_isdigit( cmd )) + Cvar_FullSet( "hostport", cmd, FCVAR_READ_ONLY ); + + // adjust clockwindow + if( Sys_GetParmFromCmdLine( "-clockwindow", cmd )) + Cvar_SetValue( "clockwindow", Q_atof( cmd )); + + net.sequence_number = 1; + net.initialized = true; + MsgDev( D_REPORT, "Base networking initialized.\n" ); +} + + +/* +==================== +NET_Shutdown +==================== +*/ +void NET_Shutdown( void ) +{ + if( !net.initialized ) + return; + + NET_ClearLagData( true, true ); + + NET_Config( false ); + pWSACleanup(); + NET_FreeWinSock(); + net.initialized = false; +} \ No newline at end of file diff --git a/engine/common/net_ws.h b/engine/common/net_ws.h new file mode 100644 index 00000000..067090b0 --- /dev/null +++ b/engine/common/net_ws.h @@ -0,0 +1,65 @@ +/* +net_ws.h - network shared functions +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef NET_WS_H +#define NET_WS_H + +typedef enum +{ + NS_CLIENT, + NS_SERVER, + NS_COUNT +} netsrc_t; + +// Max length of unreliable message +#define MAX_DATAGRAM 16384 + +// Max length of a multicast message +#define MAX_MULTICAST 8192 // some mods spamming for rain effect + +#define MAX_INIT_MSG 0x20000 // max length of possible message + +// net packets type +#define NET_HEADER_OUTOFBANDPACKET -1 +#define NET_HEADER_SPLITPACKET -2 +#define NET_HEADER_COMPRESSEDPACKET -3 + + +#include "netadr.h" + +extern convar_t *net_showpackets; +extern convar_t *net_clockwindow; + +void NET_Init( void ); +void NET_Shutdown( void ); +void NET_Sleep( int msec ); +qboolean NET_IsActive( void ); +qboolean NET_IsConfigured( void ); +void NET_Config( qboolean net_enable ); +qboolean NET_IsLocalAddress( netadr_t adr ); +char *NET_AdrToString( const netadr_t a ); +char *NET_BaseAdrToString( const netadr_t a ); +qboolean NET_IsReservedAdr( netadr_t a ); +qboolean NET_CompareClassBAdr( netadr_t a, netadr_t b ); +qboolean NET_StringToAdr( const char *string, netadr_t *adr ); +qboolean NET_CompareAdr( const netadr_t a, const netadr_t b ); +qboolean NET_CompareBaseAdr( const netadr_t a, const netadr_t b ); +qboolean NET_GetPacket( netsrc_t sock, netadr_t *from, byte *data, size_t *length ); +qboolean NET_BufferToBufferCompress( char *dest, uint *destLen, char *source, uint sourceLen ); +qboolean NET_BufferToBufferDecompress( char *dest, uint *destLen, char *source, uint sourceLen ); +void NET_SendPacket( netsrc_t sock, size_t length, const void *data, netadr_t to ); +void NET_ClearLagData( qboolean bClient, qboolean bServer ); + +#endif//NET_WS_H \ No newline at end of file diff --git a/engine/common/netchan.h b/engine/common/netchan.h new file mode 100644 index 00000000..6805ea45 --- /dev/null +++ b/engine/common/netchan.h @@ -0,0 +1,234 @@ +/* +netchan.h - net channel abstraction layer +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef NET_MSG_H +#define NET_MSG_H + +/* +========================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +========================================================== +*/ +#include "net_buffer.h" + +// 0 == regular, 1 == file stream +#define MAX_STREAMS 2 + +// flow control bytes per second limits +#define MAX_RATE 100000.0f +#define MIN_RATE 1000.0f + +// default data rate +#define DEFAULT_RATE (9999.0f) + +// NETWORKING INFO + +// This is the packet payload without any header bytes (which are attached for actual sending) +#define NET_MAX_PAYLOAD MAX_INIT_MSG + +// Theoretically maximum size of UDP-packet without header and hardware-specific data +#define NET_MAX_FRAGMENT 65536 + +// because encoded as highpart of uint32 +#define NET_MAX_BUFFER_ID 32767 + +// because encoded as lowpart of uint32 +#define NET_MAX_BUFFERS_COUNT 32767 + +// This is the payload plus any header info (excluding UDP header) + +// Packet header is: +// 4 bytes of outgoing seq +// 4 bytes of incoming seq +// and for each stream +// { +// byte (on/off) +// int (fragment id) +// short (startpos) +// short (length) +// } +#define HEADER_BYTES ( 8 + MAX_STREAMS * 9 ) + +// Pad this to next higher 16 byte boundary +// This is the largest packet that can come in/out over the wire, before processing the header +// bytes will be stripped by the networking channel layer +#define NET_MAX_MESSAGE PAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 ) + +#define MASTERSERVER_ADR "ms.xash.su:27010" +#define PORT_MASTER 27010 +#define PORT_CLIENT 27005 +#define PORT_SERVER 27015 + +#define MULTIPLAYER_BACKUP 64 // how many data slots to use when in multiplayer (must be power of 2) +#define SINGLEPLAYER_BACKUP 16 // same for single player +#define CMD_BACKUP 64 // allow a lot of command backups for very fast systems +#define CMD_MASK (CMD_BACKUP - 1) +#define NUM_PACKET_ENTITIES 256 // 170 Mb for multiplayer with 32 players +#define MAX_CUSTOM_BASELINES 64 + +/* +============================================================== + +NET + +============================================================== +*/ +#define MAX_FLOWS 2 + +#define FLOW_OUTGOING 0 +#define FLOW_INCOMING 1 +#define MAX_LATENT 32 +#define MASK_LATENT ( MAX_LATENT - 1 ) + +#define FRAG_NORMAL_STREAM 0 +#define FRAG_FILE_STREAM 1 + +// message data +typedef struct +{ + int size; // size of message sent/received + double time; // time that message was sent/received +} flowstats_t; + +typedef struct +{ + flowstats_t stats[MAX_LATENT]; // data for last MAX_LATENT messages + int current; // current message position + double nextcompute; // time when we should recompute k/sec data + float kbytespersec; // average data + float avgkbytespersec; + int totalbytes; +} flow_t; + +// generic fragment structure +typedef struct fragbuf_s +{ + struct fragbuf_s *next; // next buffer in chain + int bufferid; // id of this buffer + sizebuf_t frag_message; // message buffer where raw data is stored + byte frag_message_buf[NET_MAX_FRAGMENT]; // the actual data sits here + qboolean isfile; // is this a file buffer? + qboolean isbuffer; // is this file buffer from memory ( custom decal, etc. ). + qboolean iscompressed; // is compressed file, we should using filename.ztmp + char filename[MAX_OSPATH]; // name of the file to save out on remote host + int foffset; // offset in file from which to read data + int size; // size of data to read at that offset +} fragbuf_t; + +// Waiting list of fragbuf chains +typedef struct fbufqueue_s +{ + struct fbufqueue_s *next; // next chain in waiting list + int fragbufcount; // number of buffers in this chain + fragbuf_t *fragbufs; // the actual buffers +} fragbufwaiting_t; + +// Network Connection Channel +typedef struct netchan_s +{ + netsrc_t sock; // NS_SERVER or NS_CLIENT, depending on channel. + netadr_t remote_address; // address this channel is talking to. + int qport; // qport value to write when transmitting + + double last_received; // for timeouts + double connect_time; // Usage: host.realtime - netchan.connect_time + double rate; // bandwidth choke. bytes per second + double cleartime; // if realtime > cleartime, free to send next packet + + // Sequencing variables + int incoming_sequence; // increasing count of sequence numbers + int incoming_acknowledged; // # of last outgoing message that has been ack'd. + int incoming_reliable_acknowledged; // toggles T/F as reliable messages are received. + int incoming_reliable_sequence; // single bit, maintained local + int outgoing_sequence; // message we are sending to remote + int reliable_sequence; // whether the message contains reliable payload, single bit + int last_reliable_sequence; // outgoing sequence number of last send that had reliable data + + // callback to get actual framgment size + void *client; + int (*pfnBlockSize)( void *cl ); + + // staging and holding areas + sizebuf_t message; + byte message_buf[NET_MAX_MESSAGE]; + + // reliable message buffer. + // we keep adding to it until reliable is acknowledged. Then we clear it. + int reliable_length; + byte reliable_buf[NET_MAX_MESSAGE]; // unacked reliable message (max size for loopback connection) + + // Waiting list of buffered fragments to go onto queue. + // Multiple outgoing buffers can be queued in succession + fragbufwaiting_t *waitlist[MAX_STREAMS]; + + int reliable_fragment[MAX_STREAMS]; // is reliable waiting buf a fragment? + uint reliable_fragid[MAX_STREAMS]; // buffer id for each waiting fragment + + fragbuf_t *fragbufs[MAX_STREAMS]; // the current fragment being set + int fragbufcount[MAX_STREAMS]; // the total number of fragments in this stream + + int frag_startpos[MAX_STREAMS]; // position in outgoing buffer where frag data starts + int frag_length[MAX_STREAMS]; // length of frag data in the buffer + + fragbuf_t *incomingbufs[MAX_STREAMS]; // incoming fragments are stored here + qboolean incomingready[MAX_STREAMS]; // set to true when incoming data is ready + + // Only referenced by the FRAG_FILE_STREAM component + char incomingfilename[MAX_OSPATH]; // Name of file being downloaded + + void *tempbuffer; // download file buffer + int tempbuffersize; // current size + + // incoming and outgoing flow metrics + flow_t flow[MAX_FLOWS]; + + // added for net_speeds + size_t total_sended; + size_t total_received; +} netchan_t; + +extern netadr_t net_from; +extern netadr_t net_local; +extern sizebuf_t net_message; +extern byte net_message_buffer[NET_MAX_MESSAGE]; +extern convar_t *net_speeds; +extern convar_t sv_lan; +extern convar_t sv_lan_rate; +extern int net_drop; + +void Netchan_Init( void ); +void Netchan_Shutdown( void ); +void Netchan_Setup( netsrc_t sock, netchan_t *chan, netadr_t adr, int qport, void *client, int (*pfnBlockSize)(void * ) ); +void Netchan_CreateFileFragmentsFromBuffer( netchan_t *chan, char *filename, byte *pbuf, int size ); +qboolean Netchan_CopyNormalFragments( netchan_t *chan, sizebuf_t *msg, size_t *length ); +qboolean Netchan_CopyFileFragments( netchan_t *chan, sizebuf_t *msg ); +void Netchan_CreateFragments( netchan_t *chan, sizebuf_t *msg ); +int Netchan_CreateFileFragments( netchan_t *chan, const char *filename ); +void Netchan_Transmit( netchan_t *chan, int lengthInBytes, byte *data ); +void Netchan_TransmitBits( netchan_t *chan, int lengthInBits, byte *data ); +void Netchan_OutOfBand( int net_socket, netadr_t adr, int length, byte *data ); +void Netchan_OutOfBandPrint( int net_socket, netadr_t adr, char *format, ... ); +qboolean Netchan_Process( netchan_t *chan, sizebuf_t *msg ); +void Netchan_UpdateProgress( netchan_t *chan ); +qboolean Netchan_IncomingReady( netchan_t *chan ); +qboolean Netchan_CanPacket( netchan_t *chan, qboolean choke ); +qboolean Netchan_IsLocal( netchan_t *chan ); +void Netchan_ReportFlow( netchan_t *chan ); +void Netchan_FragSend( netchan_t *chan ); +void Netchan_Clear( netchan_t *chan ); + +#endif//NET_MSG_H \ No newline at end of file diff --git a/engine/common/pm_debug.c b/engine/common/pm_debug.c new file mode 100644 index 00000000..87f36c7f --- /dev/null +++ b/engine/common/pm_debug.c @@ -0,0 +1,88 @@ +/* +pm_debug.c - player move debugging code +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" +#include "pm_local.h" + +// expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +/* +=============== +PM_ParticleLine + +draw line from particles +================ +*/ +void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ) +{ + float len, curdist; + vec3_t diff, pos; + + // determine distance + VectorSubtract( end, start, diff ); + len = VectorNormalizeLength( diff ); + curdist = 0; + + while( curdist <= len ) + { + VectorMA( start, curdist, diff, pos ); + CL_Particle( pos, pcolor, life, 0, zvel ); + curdist += 2.0f; + } +} + +/* +================ +PM_DrawRectangle + +================ +*/ +static void PM_DrawRectangle( const vec3_t tl, const vec3_t bl, const vec3_t tr, const vec3_t br, int pcolor, float life ) +{ + PM_ParticleLine( tl, bl, pcolor, life, 0 ); + PM_ParticleLine( bl, br, pcolor, life, 0 ); + PM_ParticleLine( br, tr, pcolor, life, 0 ); + PM_ParticleLine( tr, tl, pcolor, life, 0 ); +} + +/* +================ +PM_DrawBBox + +================ +*/ +void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ) +{ + vec3_t p[8], tmp; + float gap = BOX_GAP; + int i; + + for( i = 0; i < 8; i++ ) + { + tmp[0] = (i & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (i & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (i & 4) ? mins[2] - gap : maxs[2] + gap ; + + VectorAdd( tmp, origin, tmp ); + VectorCopy( tmp, p[i] ); + } + + for( i = 0; i < 6; i++ ) + { + PM_DrawRectangle( p[boxpnt[i][1]], p[boxpnt[i][0]], p[boxpnt[i][2]], p[boxpnt[i][3]], pcolor, life ); + } +} \ No newline at end of file diff --git a/engine/common/pm_local.h b/engine/common/pm_local.h new file mode 100644 index 00000000..a61ad1cd --- /dev/null +++ b/engine/common/pm_local.h @@ -0,0 +1,49 @@ +/* +pm_local.h - player move interface +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef PM_LOCAL_H +#define PM_LOCAL_H + +#include "pm_defs.h" + +typedef int (*pfnIgnore)( physent_t *pe ); // custom trace filter + +// +// pm_debug.c +// +void PM_ParticleLine( const vec3_t start, const vec3_t end, int pcolor, float life, float zvel ); +void PM_DrawBBox( const vec3_t mins, const vec3_t maxs, const vec3_t origin, int pcolor, float life ); + +// +// pm_trace.c +// +void Pmove_Init( void ); +void PM_InitBoxHull( void ); +hull_t *PM_HullForBsp( physent_t *pe, playermove_t *pmove, float *offset ); +qboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace ); +pmtrace_t PM_PlayerTraceExt( playermove_t *pm, vec3_t p1, vec3_t p2, int flags, int numents, physent_t *ents, int ignore_pe, pfnIgnore pmFilter ); +int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, pfnIgnore pmFilter ); +int PM_HullPointContents( hull_t *hull, int num, const vec3_t p ); +void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ); + +// +// pm_surface.c +// +const char *PM_TraceTexture( physent_t *pe, vec3_t vstart, vec3_t vend ); +msurface_t *PM_RecursiveSurfCheck( model_t *model, mnode_t *node, vec3_t p1, vec3_t p2 ); +msurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end ); +int PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags ); + +#endif//PM_LOCAL_H \ No newline at end of file diff --git a/engine/common/pm_surface.c b/engine/common/pm_surface.c new file mode 100644 index 00000000..59a9ab1b --- /dev/null +++ b/engine/common/pm_surface.c @@ -0,0 +1,270 @@ +/* +pm_surface.c - surface tracing +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" +#include "pm_local.h" + +typedef struct +{ + float fraction; + int contents; +} linetrace_t; + +/* +================== +PM_RecursiveSurfCheck + +================== +*/ +msurface_t *PM_RecursiveSurfCheck( model_t *model, mnode_t *node, vec3_t p1, vec3_t p2 ) +{ + float t1, t2, frac; + int side, ds, dt; + mplane_t *plane; + msurface_t *surf; + vec3_t mid; + int i; + + if( node->contents < 0 ) + return NULL; + + plane = node->plane; + + if( plane->type < 3 ) + { + t1 = p1[plane->type] - plane->dist; + t2 = p2[plane->type] - plane->dist; + } + else + { + t1 = DotProduct( plane->normal, p1 ) - plane->dist; + t2 = DotProduct( plane->normal, p2 ) - plane->dist; + } + + if( t1 >= 0.0f && t2 >= 0.0f ) + return PM_RecursiveSurfCheck( model, node->children[0], p1, p2 ); + if( t1 < 0.0f && t2 < 0.0f ) + return PM_RecursiveSurfCheck( model, node->children[1], p1, p2 ); + + frac = t1 / ( t1 - t2 ); + + if( frac < 0.0f ) frac = 0.0f; + if( frac > 1.0f ) frac = 1.0f; + + VectorLerp( p1, frac, p2, mid ); + + side = (t1 < 0.0f); + + // now this is weird. + surf = PM_RecursiveSurfCheck( model, node->children[side], p1, mid ); + + if( surf != NULL || ( t1 >= 0.0f && t2 >= 0.0f ) || ( t1 < 0.0f && t2 < 0.0f )) + { + return surf; + } + + surf = model->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + ds = (int)((float)DotProduct( mid, surf->texinfo->vecs[0] ) + surf->texinfo->vecs[0][3] ); + dt = (int)((float)DotProduct( mid, surf->texinfo->vecs[1] ) + surf->texinfo->vecs[1][3] ); + + if( ds >= surf->texturemins[0] && dt >= surf->texturemins[1] ) + { + int s = ds - surf->texturemins[0]; + int t = dt - surf->texturemins[1]; + + if( s <= surf->extents[0] && t <= surf->extents[1] ) + return surf; + } + } + + return PM_RecursiveSurfCheck( model, node->children[side^1], mid, p2 ); +} + +/* +================== +PM_TraceTexture + +find the face where the traceline hit +assume physentity is valid +================== +*/ +msurface_t *PM_TraceSurface( physent_t *pe, vec3_t start, vec3_t end ) +{ + matrix4x4 matrix; + model_t *bmodel; + hull_t *hull; + vec3_t start_l, end_l; + vec3_t offset; + + bmodel = pe->model; + + if( !bmodel || bmodel->type != mod_brush ) + return NULL; + + hull = &pe->model->hulls[0]; + VectorSubtract( hull->clip_mins, vec3_origin, offset ); + VectorAdd( offset, pe->origin, offset ); + + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + + // rotate start and end into the models frame of reference + if( !VectorIsNull( pe->angles )) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + + return PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l ); +} + +/* +================== +PM_TraceTexture + +find the face where the traceline hit +assume physentity is valid +================== +*/ +const char *PM_TraceTexture( physent_t *pe, vec3_t start, vec3_t end ) +{ + msurface_t *surf = PM_TraceSurface( pe, start, end ); + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return NULL; + + return surf->texinfo->texture->name; +} + +/* +================== +PM_TestLine_r + +optimized trace for light gathering +================== +*/ +int PM_TestLine_r( mnode_t *node, vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t stop, linetrace_t *trace ) +{ + float front, back; + float frac, midf; + int r, side; + vec3_t mid; +loc0: + if( node->contents < 0 ) + trace->contents = node->contents; + if( node->contents == CONTENTS_SOLID ) + return CONTENTS_SOLID; + if( node->contents == CONTENTS_SKY ) + return CONTENTS_SKY; + if( node->contents < 0 ) + return CONTENTS_EMPTY; + + front = PlaneDiff( start, node->plane ); + back = PlaneDiff( stop, node->plane ); + + if( front >= -ON_EPSILON && back >= -ON_EPSILON ) + { + node = node->children[0]; + goto loc0; + } + + if( front < ON_EPSILON && back < ON_EPSILON ) + { + node = node->children[1]; + goto loc0; + } + + side = (front < 0); + frac = front / (front - back); + frac = bound( 0.0, frac, 1.0 ); + + VectorLerp( start, frac, stop, mid ); + midf = p1f + ( p2f - p1f ) * frac; + + r = PM_TestLine_r( node->children[side], p1f, midf, start, mid, trace ); + + if( r != CONTENTS_EMPTY ) + { + trace->fraction = midf; + return r; + } + + return PM_TestLine_r( node->children[!side], midf, p2f, mid, stop, trace ); +} + +int PM_TestLineExt( playermove_t *pmove, physent_t *ents, int numents, const vec3_t start, const vec3_t end, int flags ) +{ + linetrace_t trace, trace_bbox; + matrix4x4 matrix; + hull_t *hull = NULL; + vec3_t offset, start_l, end_l; + qboolean rotated; + physent_t *pe; + int i; + + trace.contents = CONTENTS_EMPTY; + trace.fraction = 1.0f; + + for( i = 0; i < numents; i++ ) + { + pe = &ents[i]; + + if( i != 0 && ( flags & PM_WORLD_ONLY )) + break; + + if( !pe->model || pe->model->type != mod_brush || pe->solid != SOLID_BSP ) + continue; + + if( pe->rendermode != kRenderNormal ) + continue; + + hull = &pe->model->hulls[0]; + + hull = PM_HullForBsp( pe, pmove, offset ); + + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + rotated = true; + else rotated = false; + + if( rotated ) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + else + { + VectorSubtract( start, pe->origin, start_l ); + VectorSubtract( end, pe->origin, end_l ); + } + + trace_bbox.contents = CONTENTS_EMPTY; + trace_bbox.fraction = 1.0f; + + PM_TestLine_r( &pe->model->nodes[hull->firstclipnode], 0.0f, 1.0f, start_l, end_l, &trace_bbox ); + + if( trace_bbox.contents != CONTENTS_EMPTY || trace_bbox.fraction < trace.fraction ) + { + trace = trace_bbox; + } + } + + return trace.contents; +} \ No newline at end of file diff --git a/engine/common/pm_trace.c b/engine/common/pm_trace.c new file mode 100644 index 00000000..6820b2b8 --- /dev/null +++ b/engine/common/pm_trace.c @@ -0,0 +1,653 @@ +/* +pm_trace.c - pmove player trace code +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" +#include "mod_local.h" +#include "pm_local.h" +#include "pm_movevars.h" +#include "features.h" +#include "studio.h" +#include "world.h" + +#define PM_AllowHitBoxTrace( model, hull ) ( model && model->type == mod_studio && ( FBitSet( model->flags, STUDIO_TRACE_HITBOX ) || hull == 2 )) + +static mplane_t pm_boxplanes[6]; +static mclipnode_t pm_boxclipnodes[6]; +static hull_t pm_boxhull; + +// default hullmins +static const vec3_t pm_hullmins[MAX_MAP_HULLS] = +{ +{ -16, -16, -36 }, +{ -16, -16, -18 }, +{ 0, 0, 0 }, +{ -32, -32, -32 }, +}; + +// defualt hullmaxs +static const vec3_t pm_hullmaxs[MAX_MAP_HULLS] = +{ +{ 16, 16, 36 }, +{ 16, 16, 18 }, +{ 0, 0, 0 }, +{ 32, 32, 32 }, +}; + +void Pmove_Init( void ) +{ + PM_InitBoxHull (); + + // init default hull sizes + memcpy( host.player_mins, pm_hullmins, sizeof( pm_hullmins )); + memcpy( host.player_maxs, pm_hullmaxs, sizeof( pm_hullmaxs )); +} + +/* +=================== +PM_InitBoxHull + +Set up the planes and clipnodes so that the six floats of a bounding box +can just be stored out and get a proper hull_t structure. +=================== +*/ +void PM_InitBoxHull( void ) +{ + int i, side; + + pm_boxhull.clipnodes = pm_boxclipnodes; + pm_boxhull.planes = pm_boxplanes; + pm_boxhull.firstclipnode = 0; + pm_boxhull.lastclipnode = 5; + + for( i = 0; i < 6; i++ ) + { + pm_boxclipnodes[i].planenum = i; + + side = i & 1; + + pm_boxclipnodes[i].children[side] = CONTENTS_EMPTY; + if( i != 5 ) pm_boxclipnodes[i].children[side^1] = i + 1; + else pm_boxclipnodes[i].children[side^1] = CONTENTS_SOLID; + + pm_boxplanes[i].type = i>>1; + pm_boxplanes[i].normal[i>>1] = 1.0f; + pm_boxplanes[i].signbits = 0; + } + +} + +/* +=================== +PM_HullForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +hull_t *PM_HullForBox( const vec3_t mins, const vec3_t maxs ) +{ + pm_boxplanes[0].dist = maxs[0]; + pm_boxplanes[1].dist = mins[0]; + pm_boxplanes[2].dist = maxs[1]; + pm_boxplanes[3].dist = mins[1]; + pm_boxplanes[4].dist = maxs[2]; + pm_boxplanes[5].dist = mins[2]; + + return &pm_boxhull; +} + +void PM_ConvertTrace( trace_t *out, pmtrace_t *in, edict_t *ent ) +{ + memcpy( out, in, 48 ); // matched + out->hitgroup = in->hitgroup; + out->ent = ent; +} + +/* +================== +PM_HullPointContents + +================== +*/ +int PM_HullPointContents( hull_t *hull, int num, const vec3_t p ) +{ + mplane_t *plane; + + if( !hull || !hull->planes ) // fantom bmodels? + return CONTENTS_NONE; + + while( num >= 0 ) + { + plane = &hull->planes[hull->clipnodes[num].planenum]; + num = hull->clipnodes[num].children[PlaneDiff( p, plane ) < 0]; + } + return num; +} + +/* +================== +PM_HullForBsp + +assume physent is valid +================== +*/ +hull_t *PM_HullForBsp( physent_t *pe, playermove_t *pmove, float *offset ) +{ + hull_t *hull; + + Assert( pe != NULL ); + Assert( pe->model != NULL ); + + switch( pmove->usehull ) + { + case 1: + hull = &pe->model->hulls[3]; + break; + case 2: + hull = &pe->model->hulls[0]; + break; + case 3: + hull = &pe->model->hulls[2]; + break; + default: + hull = &pe->model->hulls[1]; + break; + } + + Assert( hull != NULL ); + + // calculate an offset value to center the origin + VectorSubtract( hull->clip_mins, pmove->player_mins[pmove->usehull], offset ); + VectorAdd( offset, pe->origin, offset ); + + return hull; +} + +/* +================== +PM_HullForStudio + +generate multiple hulls as hitboxes +================== +*/ +hull_t *PM_HullForStudio( physent_t *pe, playermove_t *pmove, int *numhitboxes ) +{ + vec3_t size; + + VectorSubtract( pmove->player_maxs[pmove->usehull], pmove->player_mins[pmove->usehull], size ); + VectorScale( size, 0.5f, size ); + + return Mod_HullForStudio( pe->studiomodel, pe->frame, pe->sequence, pe->angles, pe->origin, size, pe->controller, pe->blending, numhitboxes, NULL ); +} + +/* +================== +PM_RecursiveHullCheck +================== +*/ +qboolean PM_RecursiveHullCheck( hull_t *hull, int num, float p1f, float p2f, vec3_t p1, vec3_t p2, pmtrace_t *trace ) +{ + mclipnode_t *node; + mplane_t *plane; + float t1, t2; + float frac, midf; + int side; + vec3_t mid; +loc0: + // check for empty + if( num < 0 ) + { + if( num != CONTENTS_SOLID ) + { + trace->allsolid = false; + if( num == CONTENTS_EMPTY ) + trace->inopen = true; + else trace->inwater = true; + } + else trace->startsolid = true; + return true; // empty + } + + if( hull->firstclipnode >= hull->lastclipnode ) + { + // empty hull? + trace->allsolid = false; + trace->inopen = true; + return true; + } + + if( num < hull->firstclipnode || num > hull->lastclipnode ) + Host_Error( "PM_RecursiveHullCheck: bad node number %i\n", num ); + + // find the point distances + node = hull->clipnodes + num; + plane = hull->planes + node->planenum; + + t1 = PlaneDiff( p1, plane ); + t2 = PlaneDiff( p2, plane ); + + if( t1 >= 0.0f && t2 >= 0.0f ) + { + num = node->children[0]; + goto loc0; + } + + if( t1 < 0.0f && t2 < 0.0f ) + { + num = node->children[1]; + goto loc0; + } + + // put the crosspoint DIST_EPSILON pixels on the near side + side = (t1 < 0.0f); + + if( side ) frac = ( t1 + DIST_EPSILON ) / ( t1 - t2 ); + else frac = ( t1 - DIST_EPSILON ) / ( t1 - t2 ); + + if( frac < 0.0f ) frac = 0.0f; + if( frac > 1.0f ) frac = 1.0f; + + midf = p1f + ( p2f - p1f ) * frac; + VectorLerp( p1, frac, p2, mid ); + + // move up to the node + if( !PM_RecursiveHullCheck( hull, node->children[side], p1f, midf, p1, mid, trace )) + return false; + + // this recursion can not be optimized because mid would need to be duplicated on a stack + if( PM_HullPointContents( hull, node->children[side^1], mid ) != CONTENTS_SOLID ) + { + // go past the node + return PM_RecursiveHullCheck( hull, node->children[side^1], midf, p2f, mid, p2, trace ); + } + + // never got out of the solid area + if( trace->allsolid ) + return false; + + // the other side of the node is solid, this is the impact point + if( !side ) + { + VectorCopy( plane->normal, trace->plane.normal ); + trace->plane.dist = plane->dist; + } + else + { + VectorNegate( plane->normal, trace->plane.normal ); + trace->plane.dist = -plane->dist; + } + + while( PM_HullPointContents( hull, hull->firstclipnode, mid ) == CONTENTS_SOLID ) + { + // shouldn't really happen, but does occasionally + frac -= 0.1f; + + if( frac < 0.0f ) + { + trace->fraction = midf; + VectorCopy( mid, trace->endpos ); + MsgDev( D_WARN, "trace backed up past 0.0\n" ); + return false; + } + + midf = p1f + ( p2f - p1f ) * frac; + VectorLerp( p1, frac, p2, mid ); + } + + trace->fraction = midf; + VectorCopy( mid, trace->endpos ); + + return false; +} + +pmtrace_t PM_PlayerTraceExt( playermove_t *pmove, vec3_t start, vec3_t end, int flags, int numents, physent_t *ents, int ignore_pe, pfnIgnore pmFilter ) +{ + physent_t *pe; + matrix4x4 matrix; + pmtrace_t trace_bbox; + pmtrace_t trace_hitbox; + pmtrace_t trace_total; + vec3_t offset, start_l, end_l; + vec3_t temp, mins, maxs; + int i, j, hullcount; + qboolean rotated, transform_bbox; + hull_t *hull = NULL; + + memset( &trace_total, 0, sizeof( trace_total )); + VectorCopy( end, trace_total.endpos ); + trace_total.fraction = 1.0f; + trace_total.ent = -1; + + for( i = 0; i < numents; i++ ) + { + pe = &ents[i]; + + if( i != 0 && ( flags & PM_WORLD_ONLY )) + break; + + // run custom user filter + if( pmFilter != NULL ) + { + if( pmFilter( pe )) + continue; + } + else if( ignore_pe != -1 ) + { + if( i == ignore_pe ) + continue; + } + + if( pe->model != NULL && pe->solid == SOLID_NOT && pe->skin != CONTENTS_NONE ) + continue; + + if(( flags & PM_GLASS_IGNORE ) && pe->rendermode != kRenderNormal ) + continue; + + if(( flags & PM_CUSTOM_IGNORE ) && pe->solid == SOLID_CUSTOM ) + continue; + + hullcount = 1; + + if( pe->solid == SOLID_CUSTOM ) + { + VectorCopy( pmove->player_mins[pmove->usehull], mins ); + VectorCopy( pmove->player_maxs[pmove->usehull], maxs ); + VectorClear( offset ); + } + else if( pe->model ) + { + hull = PM_HullForBsp( pe, pmove, offset ); + } + else + { + if( pe->studiomodel ) + { + if( FBitSet( flags, PM_STUDIO_IGNORE )) + continue; + + if( PM_AllowHitBoxTrace( pe->studiomodel, pmove->usehull ) && !FBitSet( flags, PM_STUDIO_BOX )) + { + hull = PM_HullForStudio( pe, pmove, &hullcount ); + VectorClear( offset ); + } + else + { + VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + + hull = PM_HullForBox( mins, maxs ); + VectorCopy( pe->origin, offset ); + } + } + else + { + VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + + hull = PM_HullForBox( mins, maxs ); + VectorCopy( pe->origin, offset ); + } + + } + + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + rotated = true; + else rotated = false; + + if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) + { + if(( check_angles( pe->angles[0] ) || check_angles( pe->angles[2] )) && pmove->usehull != 2 ) + transform_bbox = true; + else transform_bbox = false; + } + else transform_bbox = false; + + if( rotated ) + { + if( transform_bbox ) + Matrix4x4_CreateFromEntity( matrix, pe->angles, pe->origin, 1.0f ); + else Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + + if( transform_bbox ) + { + World_TransformAABB( matrix, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], mins, maxs ); + VectorSubtract( hull->clip_mins, mins, offset ); // calc new local offset + + for( j = 0; j < 3; j++ ) + { + if( start_l[j] >= 0.0f ) + start_l[j] -= offset[j]; + else start_l[j] += offset[j]; + if( end_l[j] >= 0.0f ) + end_l[j] -= offset[j]; + else end_l[j] += offset[j]; + } + } + } + else + { + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + } + + memset( &trace_bbox, 0, sizeof( trace_bbox )); + VectorCopy( end, trace_bbox.endpos ); + trace_bbox.allsolid = true; + trace_bbox.fraction = 1.0f; + + if( hullcount < 1 ) + { + // g-cont. probably this never happens + trace_bbox.allsolid = false; + } + else if( pe->solid == SOLID_CUSTOM ) + { + // run custom sweep callback + if( pmove->server || Host_IsLocalClient( )) + SV_ClipPMoveToEntity( pe, start, mins, maxs, end, &trace_bbox ); + else CL_ClipPMoveToEntity( pe, start, mins, maxs, end, &trace_bbox ); + } + else if( hullcount == 1 ) + { + PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, &trace_bbox ); + } + else + { + int last_hitgroup; + + for( last_hitgroup = 0, j = 0; j < hullcount; j++ ) + { + memset( &trace_hitbox, 0, sizeof( trace_hitbox )); + VectorCopy( end, trace_hitbox.endpos ); + trace_hitbox.allsolid = true; + trace_hitbox.fraction = 1.0f; + + PM_RecursiveHullCheck( &hull[j], hull[j].firstclipnode, 0, 1, start_l, end_l, &trace_hitbox ); + + if( j == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace_bbox.fraction ) + { + if( trace_bbox.startsolid ) + { + trace_bbox = trace_hitbox; + trace_bbox.startsolid = true; + } + else trace_bbox = trace_hitbox; + + last_hitgroup = j; + } + } + + trace_bbox.hitgroup = Mod_HitgroupForStudioHull( last_hitgroup ); + } + + if( trace_bbox.allsolid ) + trace_bbox.startsolid = true; + + if( trace_bbox.startsolid ) + trace_bbox.fraction = 0.0f; + + if( !trace_bbox.startsolid ) + { + VectorLerp( start, trace_bbox.fraction, end, trace_bbox.endpos ); + + if( rotated ) + { + VectorCopy( trace_bbox.plane.normal, temp ); + Matrix4x4_TransformPositivePlane( matrix, temp, trace_bbox.plane.dist, trace_bbox.plane.normal, &trace_bbox.plane.dist ); + } + else + { + trace_bbox.plane.dist = DotProduct( trace_bbox.endpos, trace_bbox.plane.normal ); + } + } + + if( trace_bbox.fraction < trace_total.fraction ) + { + trace_total = trace_bbox; + trace_total.ent = i; + } + } + + return trace_total; +} + +int PM_TestPlayerPosition( playermove_t *pmove, vec3_t pos, pmtrace_t *ptrace, pfnIgnore pmFilter ) +{ + int i, j, hullcount; + vec3_t pos_l, offset; + hull_t *hull = NULL; + vec3_t mins, maxs; + pmtrace_t trace; + physent_t *pe; + + trace = PM_PlayerTraceExt( pmove, pmove->origin, pmove->origin, 0, pmove->numphysent, pmove->physents, -1, pmFilter ); + if( ptrace ) *ptrace = trace; + + for( i = 0; i < pmove->numphysent; i++ ) + { + pe = &pmove->physents[i]; + + // run custom user filter + if( pmFilter != NULL ) + { + if( pmFilter( pe )) + continue; + } + + if( pe->model != NULL && pe->solid == SOLID_NOT && pe->skin != CONTENTS_NONE ) + continue; + + hullcount = 1; + + if( pe->solid == SOLID_CUSTOM ) + { + VectorCopy( pmove->player_mins[pmove->usehull], mins ); + VectorCopy( pmove->player_maxs[pmove->usehull], maxs ); + VectorClear( offset ); + } + else if( pe->model ) + { + hull = PM_HullForBsp( pe, pmove, offset ); + } + else if( PM_AllowHitBoxTrace( pe->studiomodel, pmove->usehull )) + { + hull = PM_HullForStudio( pe, pmove, &hullcount ); + VectorClear( offset ); + } + else + { + VectorSubtract( pe->mins, pmove->player_maxs[pmove->usehull], mins ); + VectorSubtract( pe->maxs, pmove->player_mins[pmove->usehull], maxs ); + + hull = PM_HullForBox( mins, maxs ); + VectorCopy( pe->origin, offset ); + } + + // CM_TransformedPointContents :-) + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + { + qboolean transform_bbox = false; + matrix4x4 matrix; + + if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) + { + if(( check_angles( pe->angles[0] ) || check_angles( pe->angles[2] )) && pmove->usehull != 2 ) + transform_bbox = true; + } + + if( transform_bbox ) + Matrix4x4_CreateFromEntity( matrix, pe->angles, pe->origin, 1.0f ); + else Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + + Matrix4x4_VectorITransform( matrix, pos, pos_l ); + + if( transform_bbox ) + { + World_TransformAABB( matrix, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], mins, maxs ); + VectorSubtract( hull->clip_mins, mins, offset ); // calc new local offset + + for( j = 0; j < 3; j++ ) + { + if( pos_l[j] >= 0.0f ) + pos_l[j] -= offset[j]; + else pos_l[j] += offset[j]; + } + } + } + else + { + // offset the test point appropriately for this hull. + VectorSubtract( pos, offset, pos_l ); + } + + if( pe->solid == SOLID_CUSTOM ) + { + pmtrace_t trace; + + memset( &trace, 0, sizeof( trace )); + VectorCopy( pos, trace.endpos ); + trace.allsolid = true; + trace.fraction = 1.0f; + + // run custom sweep callback + if( pmove->server || Host_IsLocalClient( )) + SV_ClipPMoveToEntity( pe, pos, mins, maxs, pos, &trace ); + else CL_ClipPMoveToEntity( pe, pos, mins, maxs, pos, &trace ); + + // if we inside the custom hull + if( trace.allsolid ) + return i; + } + else if( hullcount == 1 ) + { + if( PM_HullPointContents( hull, hull->firstclipnode, pos_l ) == CONTENTS_SOLID ) + return i; + } + else + { + for( j = 0; j < hullcount; j++ ) + { + if( PM_HullPointContents( &hull[j], hull[j].firstclipnode, pos_l ) == CONTENTS_SOLID ) + return i; + } + } + } + + return -1; // didn't hit anything +} \ No newline at end of file diff --git a/engine/common/protocol.h b/engine/common/protocol.h new file mode 100644 index 00000000..7c01e303 --- /dev/null +++ b/engine/common/protocol.h @@ -0,0 +1,185 @@ +/* +protocol.h - communications protocols +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef NET_PROTOCOL_H +#define NET_PROTOCOL_H + +#define PROTOCOL_VERSION 49 + +// server to client +#define svc_bad 0 // immediately crash client when received +#define svc_nop 1 // does nothing +#define svc_disconnect 2 // kick client from server +#define svc_event 3 // playback event queue +#define svc_changing 4 // changelevel by server request +#define svc_setview 5 // [short] entity number +#define svc_sound 6 // +#define svc_time 7 // [float] server time +#define svc_print 8 // [byte] id [string] null terminated string +#define svc_stufftext 9 // [string] stuffed into client's console buffer +#define svc_setangle 10 // [angle angle angle] set the view angle to this absolute value +#define svc_serverdata 11 // [long] protocol ... +#define svc_lightstyle 12 // [index][pattern][float] +#define svc_updateuserinfo 13 // [byte] playernum, [string] userinfo +#define svc_deltatable 14 // [table header][...] +#define svc_clientdata 15 // [...] +#define svc_resource 16 // [...] late-precached resource will be download in-game +#define svc_pings 17 // [bit][idx][ping][packet_loss] +#define svc_particle 18 // [float*3][char*3][byte][byte] +#define svc_restoresound 19 // +#define svc_spawnstatic 20 // creates a static client entity +#define svc_event_reliable 21 // playback event directly from message, not queue +#define svc_spawnbaseline 22 // +#define svc_temp_entity 23 // +#define svc_setpause 24 // [byte] 0 = unpaused, 1 = paused +#define svc_signonnum 25 // [byte] used for the signon sequence +#define svc_centerprint 26 // [string] to put in center of the screen +// reserved +// reserved +// reserved +#define svc_intermission 30 // empty message (event) +#define svc_finale 31 // empty message (event) +#define svc_cdtrack 32 // [string] trackname +#define svc_restore 33 // [string] savename +#define svc_cutscene 34 // empty message (event) +#define svc_weaponanim 35 // [byte]iAnim [byte]body +#define svc_bspdecal 36 // [float*3][short][short][short] +#define svc_roomtype 37 // [short] room type +#define svc_addangle 38 // [angle] add angles when client turn on mover +#define svc_usermessage 39 // [byte][byte][string] REG_USER_MSG stuff +#define svc_packetentities 40 // [short][...] +#define svc_deltapacketentities 41 // [short][byte][...] +#define svc_choke 42 // just event +#define svc_resourcelist 43 // [short][...] +#define svc_deltamovevars 44 // [movevars_t] +#define svc_resourcerequest 45 // +#define svc_customization 46 +#define svc_crosshairangle 47 // [byte][byte] +#define svc_soundfade 48 // [float*4] sound fade parms +#define svc_filetxferfailed 49 // [string] +#define svc_hltv 50 // sending from the game.dll +#define svc_director 51 // +#define svc_voiceinit 52 // +#define svc_voicedata 53 // [byte][short][...] +// reserved +// reserved +#define svc_resourcelocation 56 // [string] +#define svc_querycvarvalue 57 // [string] +#define svc_querycvarvalue2 58 // [string][long] (context) +#define svc_lastmsg 58 // start user messages at this point + +// client to server +#define clc_bad 0 // immediately drop client when received +#define clc_nop 1 +#define clc_move 2 // [[usercmd_t] +#define clc_stringcmd 3 // [string] message +#define clc_delta 4 // [byte] sequence number, requests delta compression of message +#define clc_resourcelist 5 +// reserved +#define clc_fileconsistency 7 +#define clc_voicedata 8 +#define clc_requestcvarvalue 9 +#define clc_requestcvarvalue2 10 +#define clc_lastmsg 10 // end client messages + +#define MAX_VISIBLE_PACKET_BITS 11 // 2048 visible entities per frame (hl1 has 256) +#define MAX_VISIBLE_PACKET (1<buffer = sound.wav; + pack->width = sound.width; + pack->rate = sound.rate; + pack->type = sound.type; + pack->size = sound.size; + pack->loopStart = sound.loopstart; + pack->samples = sound.samples; + pack->channels = sound.channels; + pack->flags = sound.flags; + + return pack; +} + +/* +================ +FS_LoadSound + +loading and unpack to wav any known sound +================ +*/ +wavdata_t *FS_LoadSound( const char *filename, const byte *buffer, size_t size ) +{ + const char *ext = COM_FileExtension( filename ); + string path, loadname; + qboolean anyformat = true; + int filesize = 0; + const loadwavfmt_t *format; + byte *f; + + Sound_Reset(); // clear old sounddata + Q_strncpy( loadname, filename, sizeof( loadname )); + + if( Q_stricmp( ext, "" )) + { + // we needs to compare file extension with list of supported formats + // and be sure what is real extension, not a filename with dot + for( format = sound.loadformats; format && format->formatstring; format++ ) + { + if( !Q_stricmp( format->ext, ext )) + { + COM_StripExtension( loadname ); + anyformat = false; + break; + } + } + } + + // special mode: skip any checks, load file from buffer + if( filename[0] == '#' && buffer && size ) + goto load_internal; + + // now try all the formats in the selected list + for( format = sound.loadformats; format && format->formatstring; format++) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + f = FS_LoadFile( path, &filesize, false ); + if( f && filesize > 0 ) + { + if( format->loadfunc( path, f, filesize )) + { + Mem_Free(f); // release buffer + return SoundPack(); // loaded + } + else Mem_Free(f); // release buffer + } + } + } + +load_internal: + for( format = sound.loadformats; format && format->formatstring; format++ ) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + if( buffer && size > 0 ) + { + if( format->loadfunc( loadname, buffer, size )) + return SoundPack(); // loaded + } + } + } + + if( !sound.loadformats || sound.loadformats->ext == NULL ) + MsgDev( D_NOTE, "FS_LoadSound: soundlib offline\n" ); + else if( filename[0] != '#' ) + MsgDev( D_WARN, "FS_LoadSound: couldn't load \"%s\"\n", loadname ); + + return NULL; +} + +/* +================ +Sound_FreeSound + +free WAV buffer +================ +*/ +void FS_FreeSound( wavdata_t *pack ) +{ + if( pack ) + { + if( pack->buffer ) Mem_Free( pack->buffer ); + Mem_Free( pack ); + } + else MsgDev( D_WARN, "FS_FreeSound: trying to free NULL sound\n" ); +} + +/* +================ +FS_OpenStream + +open and reading basic info from sound stream +================ +*/ +stream_t *FS_OpenStream( const char *filename ) +{ + const char *ext = COM_FileExtension( filename ); + string path, loadname; + qboolean anyformat = true; + const streamfmt_t *format; + stream_t *stream; + + Sound_Reset(); // clear old streaminfo + Q_strncpy( loadname, filename, sizeof( loadname )); + + if( Q_stricmp( ext, "" )) + { + // we needs to compare file extension with list of supported formats + // and be sure what is real extension, not a filename with dot + for( format = sound.streamformat; format && format->formatstring; format++ ) + { + if( !Q_stricmp( format->ext, ext )) + { + COM_StripExtension( loadname ); + anyformat = false; + break; + } + } + } + + // now try all the formats in the selected list + for( format = sound.streamformat; format && format->formatstring; format++) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, loadname, "", format->ext ); + if(( stream = format->openfunc( path )) != NULL ) + { + stream->format = format; + return stream; // done + } + } + } + + if( !sound.streamformat || sound.streamformat->ext == NULL ) + MsgDev( D_NOTE, "FS_OpenStream: soundlib offline\n" ); + else MsgDev( D_NOTE, "FS_OpenStream: couldn't open \"%s\"\n", loadname ); + + return NULL; +} + +/* +================ +FS_StreamInfo + +get basic stream info +================ +*/ +wavdata_t *FS_StreamInfo( stream_t *stream ) +{ + static wavdata_t info; + + if( !stream ) return NULL; + + // fill structure + info.loopStart = -1; + info.rate = stream->rate; + info.width = stream->width; + info.channels = stream->channels; + info.flags = SOUND_STREAM; + info.size = stream->size; + info.buffer = NULL; + info.samples = 0; // not actual for streams + info.type = stream->type; + + return &info; +} + +/* +================ +FS_ReadStream + +extract stream as wav-data and put into buffer, move file pointer +================ +*/ +long FS_ReadStream( stream_t *stream, int bytes, void *buffer ) +{ + if( !stream || !stream->format || !stream->format->readfunc ) + return 0; + + if( bytes <= 0 || buffer == NULL ) + return 0; + + return stream->format->readfunc( stream, bytes, buffer ); +} + +/* +================ +FS_GetStreamPos + +get stream position (in bytes) +================ +*/ +long FS_GetStreamPos( stream_t *stream ) +{ + if( !stream || !stream->format || !stream->format->getposfunc ) + return -1; + + return stream->format->getposfunc( stream ); +} + +/* +================ +FS_SetStreamPos + +set stream position (in bytes) +================ +*/ +long FS_SetStreamPos( stream_t *stream, long newpos ) +{ + if( !stream || !stream->format || !stream->format->setposfunc ) + return -1; + + return stream->format->setposfunc( stream, newpos ); +} + +/* +================ +FS_FreeStream + +close sound stream +================ +*/ +void FS_FreeStream( stream_t *stream ) +{ + if( !stream || !stream->format || !stream->format->freefunc ) + return; + + stream->format->freefunc( stream ); +} \ No newline at end of file diff --git a/engine/common/soundlib/snd_mp3.c b/engine/common/soundlib/snd_mp3.c new file mode 100644 index 00000000..8b72b17b --- /dev/null +++ b/engine/common/soundlib/snd_mp3.c @@ -0,0 +1,301 @@ +/* +snd_mp3.c - mp3 format loading and streaming +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "soundlib.h" + +/* +======================================================================= + MPG123 DEFINITION +======================================================================= +*/ +#define MP3_ERR -1 +#define MP3_OK 0 +#define MP3_NEED_MORE 1 + +typedef struct +{ + int rate; // num samples per second (e.g. 11025 - 11 khz) + int channels; // num channels (1 - mono, 2 - stereo) + int playtime; // stream size in milliseconds +} wavinfo_t; + +// custom stdio +typedef long (*pfread)( void *handle, void *buf, size_t count ); +typedef long (*pfseek)( void *handle, long offset, int whence ); + +extern void *create_decoder( int *error ); +extern int feed_mpeg_header( void *mpg, const char *data, long bufsize, long streamsize, wavinfo_t *sc ); +extern int feed_mpeg_stream( void *mpg, const char *data, long bufsize, char *outbuf, size_t *outsize ); +extern int open_mpeg_stream( void *mpg, void *file, pfread f_read, pfseek f_seek, wavinfo_t *sc ); +extern int read_mpeg_stream( void *mpg, char *outbuf, size_t *outsize ); +extern int get_stream_pos( void *mpg ); +extern int set_stream_pos( void *mpg, int curpos ); +extern void close_decoder( void *mpg ); +const char *get_error( void *mpeg ); + +/* +================================================================= + + MPEG decompression + +================================================================= +*/ +qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize ) +{ + void *mpeg; + size_t pos = 0; + size_t bytesWrite = 0; + char out[OUTBUF_SIZE]; + size_t outsize; + int ret; + wavinfo_t sc; + + // load the file + if( !buffer || filesize < FRAME_SIZE ) + return false; + + // couldn't create decoder + if(( mpeg = create_decoder( &ret )) == NULL ) + return false; + +#ifdef _DEBUG + if( ret ) MsgDev( D_ERROR, "%s\n", get_error( mpeg )); +#endif + + // trying to read header + if( !feed_mpeg_header( mpeg, buffer, FRAME_SIZE, filesize, &sc )) + { +#ifdef _DEBUG + MsgDev( D_ERROR, "Sound_LoadMPG: failed to load (%s): %s\n", name, get_error( mpeg )); +#else + MsgDev( D_ERROR, "Sound_LoadMPG: (%s) is probably corrupted\n", name ); +#endif + close_decoder( mpeg ); + return false; + } + + sound.channels = sc.channels; + sound.rate = sc.rate; + sound.width = 2; // always 16-bit PCM + sound.loopstart = -1; + sound.size = ( sound.channels * sound.rate * sound.width ) * ( sc.playtime / 1000 ); // in bytes + pos += FRAME_SIZE; // evaluate pos + + if( !sound.size ) + { + // bad mpeg file ? + MsgDev( D_ERROR, "Sound_LoadMPG: (%s) is probably corrupted\n", name ); + close_decoder( mpeg ); + return false; + } + + sound.type = WF_PCMDATA; + sound.wav = (byte *)Mem_Alloc( host.soundpool, sound.size ); + + // decompress mpg into pcm wav format + while( bytesWrite < sound.size ) + { + int size; + + if( feed_mpeg_stream( mpeg, NULL, 0, out, &outsize ) != MP3_OK && outsize <= 0 ) + { + char *data = (char *)buffer + pos; + int bufsize; + + // if there are no bytes remainig so we can decompress the new frame + if( pos + FRAME_SIZE > filesize ) + bufsize = ( filesize - pos ); + else bufsize = FRAME_SIZE; + pos += bufsize; + + if( feed_mpeg_stream( mpeg, data, bufsize, out, &outsize ) != MP3_OK ) + break; // there was end of the stream + } + + if( bytesWrite + outsize > sound.size ) + size = ( sound.size - bytesWrite ); + else size = outsize; + + memcpy( &sound.wav[bytesWrite], out, size ); + bytesWrite += outsize; + } + + sound.samples = bytesWrite / ( sound.width * sound.channels ); + close_decoder( mpeg ); + + return true; +} + +/* +================= +Stream_OpenMPG +================= +*/ +stream_t *Stream_OpenMPG( const char *filename ) +{ + stream_t *stream; + void *mpeg; + file_t *file; + int ret; + wavinfo_t sc; + + file = FS_Open( filename, "rb", false ); + if( !file ) return NULL; + + // at this point we have valid stream + stream = Mem_Alloc( host.soundpool, sizeof( stream_t )); + stream->file = file; + stream->pos = 0; + + // couldn't create decoder + if(( mpeg = create_decoder( &ret )) == NULL ) + { + MsgDev( D_ERROR, "Stream_OpenMPG: couldn't create decoder\n" ); + Mem_Free( stream ); + FS_Close( file ); + return NULL; + } + +#ifdef _DEBUG + if( ret ) MsgDev( D_ERROR, "%s\n", get_error( mpeg )); +#endif + // trying to open stream and read header + if( !open_mpeg_stream( mpeg, file, FS_Read, FS_Seek, &sc )) + { +#ifdef _DEBUG + MsgDev( D_ERROR, "Stream_OpenMPG: failed to load (%s): %s\n", filename, get_error( mpeg )); +#else + MsgDev( D_ERROR, "Stream_OpenMPG: (%s) is probably corrupted\n", filename ); +#endif + close_decoder( mpeg ); + Mem_Free( stream ); + FS_Close( file ); + + return NULL; + } + + stream->buffsize = 0; // how many samples left from previous frame + stream->channels = sc.channels; + stream->rate = sc.rate; + stream->width = 2; // always 16 bit + stream->ptr = mpeg; + stream->type = WF_MPGDATA; + + return stream; +} + +/* +================= +Stream_ReadMPG + +assume stream is valid +================= +*/ +long Stream_ReadMPG( stream_t *stream, long needBytes, void *buffer ) +{ + // buffer handling + int bytesWritten = 0; + void *mpg; + + mpg = stream->ptr; + + while( 1 ) + { + byte *data; + long outsize; + + if( !stream->buffsize ) + { + if( read_mpeg_stream( mpg, stream->temp, &stream->pos ) != MP3_OK ) + break; // there was end of the stream + } + + // check remaining size + if( bytesWritten + stream->pos > needBytes ) + outsize = ( needBytes - bytesWritten ); + else outsize = stream->pos; + + // copy raw sample to output buffer + data = (byte *)buffer + bytesWritten; + memcpy( data, &stream->temp[stream->buffsize], outsize ); + bytesWritten += outsize; + stream->pos -= outsize; + stream->buffsize += outsize; + + // continue from this sample on a next call + if( bytesWritten >= needBytes ) + return bytesWritten; + + stream->buffsize = 0; // no bytes remaining + } + + return 0; +} + +/* +================= +Stream_SetPosMPG + +assume stream is valid +================= +*/ +long Stream_SetPosMPG( stream_t *stream, long newpos ) +{ + if( set_stream_pos( stream->ptr, newpos ) != -1 ) + { + // flush any previous data + stream->buffsize = 0; + return true; + } + + // failed to seek for some reasons + return false; +} + +/* +================= +Stream_GetPosMPG + +assume stream is valid +================= +*/ +long Stream_GetPosMPG( stream_t *stream ) +{ + return get_stream_pos( stream->ptr ); +} + +/* +================= +Stream_FreeMPG + +assume stream is valid +================= +*/ +void Stream_FreeMPG( stream_t *stream ) +{ + if( stream->ptr ) + { + close_decoder( stream->ptr ); + stream->ptr = NULL; + } + + if( stream->file ) + { + FS_Close( stream->file ); + stream->file = NULL; + } + + Mem_Free( stream ); +} \ No newline at end of file diff --git a/engine/common/soundlib/snd_utils.c b/engine/common/soundlib/snd_utils.c new file mode 100644 index 00000000..382752e7 --- /dev/null +++ b/engine/common/soundlib/snd_utils.c @@ -0,0 +1,241 @@ +/* +snd_utils.c - sound common tools +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "soundlib.h" + +/* +============================================================================= + + XASH3D LOAD SOUND FORMATS + +============================================================================= +*/ +// stub +static const loadwavfmt_t load_null[] = +{ +{ NULL, NULL, NULL } +}; + +static const loadwavfmt_t load_game[] = +{ +{ DEFAULT_SOUNDPATH "%s%s.%s", "wav", Sound_LoadWAV }, +{ "%s%s.%s", "wav", Sound_LoadWAV }, +{ DEFAULT_SOUNDPATH "%s%s.%s", "mp3", Sound_LoadMPG }, +{ "%s%s.%s", "mp3", Sound_LoadMPG }, +{ NULL, NULL, NULL } +}; + +/* +============================================================================= + + XASH3D PROCESS STREAM FORMATS + +============================================================================= +*/ +// stub +static const streamfmt_t stream_null[] = +{ +{ NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + +static const streamfmt_t stream_game[] = +{ +{ "%s%s.%s", "mp3", Stream_OpenMPG, Stream_ReadMPG, Stream_SetPosMPG, Stream_GetPosMPG, Stream_FreeMPG }, +{ "%s%s.%s", "wav", Stream_OpenWAV, Stream_ReadWAV, Stream_SetPosWAV, Stream_GetPosWAV, Stream_FreeWAV }, +{ NULL, NULL, NULL, NULL, NULL, NULL, NULL } +}; + +void Sound_Init( void ) +{ + // init pools + host.soundpool = Mem_AllocPool( "SoundLib Pool" ); + + // install image formats (can be re-install later by Sound_Setup) + switch( host.type ) + { + case HOST_NORMAL: + sound.loadformats = load_game; + sound.streamformat = stream_game; + break; + default: // all other instances not using soundlib or will be reinstalling later + sound.loadformats = load_null; + sound.streamformat = stream_null; + break; + } + sound.tempbuffer = NULL; +} + +void Sound_Shutdown( void ) +{ + Mem_Check(); // check for leaks + Mem_FreePool( &host.soundpool ); +} + +byte *Sound_Copy( size_t size ) +{ + byte *out; + + out = Mem_Alloc( host.soundpool, size ); + memcpy( out, sound.tempbuffer, size ); + + return out; +} + +/* +================ +Sound_ConvertToSigned + +Convert unsigned data to signed +================ +*/ +void Sound_ConvertToSigned( const byte *data, int channels, int samples ) +{ + int i; + + if( channels == 2 ) + { + for( i = 0; i < samples; i++ ) + { + ((signed char *)sound.tempbuffer)[i*2+0] = (int)((byte)(data[i*2+0]) - 128); + ((signed char *)sound.tempbuffer)[i*2+1] = (int)((byte)(data[i*2+1]) - 128); + } + } + else + { + for( i = 0; i < samples; i++ ) + ((signed char *)sound.tempbuffer)[i] = (int)((unsigned char)(data[i]) - 128); + } +} + +/* +================ +Sound_ResampleInternal + +We need convert sound to signed even if nothing to resample +================ +*/ +qboolean Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate, int outwidth ) +{ + float stepscale; + int outcount, srcsample; + int i, sample, sample2, samplefrac, fracstep; + byte *data; + + data = sc->buffer; + stepscale = (float)inrate / outrate; // this is usually 0.5, 1, or 2 + outcount = sc->samples / stepscale; + sc->size = outcount * outwidth * sc->channels; + + sound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, sc->size ); + + sc->samples = outcount; + if( sc->loopStart != -1 ) + sc->loopStart = sc->loopStart / stepscale; + + // resample / decimate to the current source rate + if( stepscale == 1.0f && inwidth == 1 && outwidth == 1 ) + { + Sound_ConvertToSigned( data, sc->channels, outcount ); + } + else + { + // general case + samplefrac = 0; + fracstep = stepscale * 256; + + if( sc->channels == 2 ) + { + for( i = 0; i < outcount; i++ ) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + + if( inwidth == 2 ) + { + sample = ((short *)data)[srcsample*2+0]; + sample2 = ((short *)data)[srcsample*2+1]; + } + else + { + sample = (int)((char)(data[srcsample*2+0])) << 8; + sample2 = (int)((char)(data[srcsample*2+1])) << 8; + } + + if( outwidth == 2 ) + { + ((short *)sound.tempbuffer)[i*2+0] = sample; + ((short *)sound.tempbuffer)[i*2+1] = sample2; + } + else + { + ((signed char *)sound.tempbuffer)[i*2+0] = sample >> 8; + ((signed char *)sound.tempbuffer)[i*2+1] = sample2 >> 8; + } + } + } + else + { + for( i = 0; i < outcount; i++ ) + { + srcsample = samplefrac >> 8; + samplefrac += fracstep; + + if( inwidth == 2 ) sample = ((short *)data)[srcsample]; + else sample = (int)( (char)(data[srcsample])) << 8; + + if( outwidth == 2 ) ((short *)sound.tempbuffer)[i] = sample; + else ((signed char *)sound.tempbuffer)[i] = sample >> 8; + } + } + + MsgDev( D_NOTE, "Sound_Resample: from[%d bit %d kHz] to [%d bit %d kHz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); + } + + sc->rate = outrate; + sc->width = outwidth; + + return true; +} + +qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ) +{ + wavdata_t *snd = *wav; + qboolean result = true; + + // check for buffers + if( !snd || !snd->buffer ) + { + MsgDev( D_WARN, "Sound_Process: NULL sound\n" ); + return false; + } + + if(( flags & SOUND_RESAMPLE ) && ( width > 0 || rate > 0 )) + { + if( Sound_ResampleInternal( snd, snd->rate, snd->width, rate, width )) + { + Mem_Free( snd->buffer ); // free original image buffer + snd->buffer = Sound_Copy( snd->size ); // unzone buffer (don't touch image.tempbuffer) + } + else + { + // not resampled + result = false; + } + } + + *wav = snd; + + return false; +} \ No newline at end of file diff --git a/engine/common/soundlib/snd_wav.c b/engine/common/soundlib/snd_wav.c new file mode 100644 index 00000000..e99b2919 --- /dev/null +++ b/engine/common/soundlib/snd_wav.c @@ -0,0 +1,465 @@ +/* +snd_wav.c - wav format load & save +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "soundlib.h" + +static const byte *iff_data; +static const byte *iff_dataPtr; +static const byte *iff_end; +static const byte *iff_lastChunk; +static int iff_chunkLen; + +/* +================= +GetLittleShort +================= +*/ +static short GetLittleShort( void ) +{ + short val = 0; + + val += (*(iff_dataPtr+0) << 0); + val += (*(iff_dataPtr+1) << 8); + iff_dataPtr += 2; + + return val; +} + +/* +================= +GetLittleLong +================= +*/ +static int GetLittleLong( void ) +{ + int val = 0; + + val += (*(iff_dataPtr+0) << 0); + val += (*(iff_dataPtr+1) << 8); + val += (*(iff_dataPtr+2) <<16); + val += (*(iff_dataPtr+3) <<24); + iff_dataPtr += 4; + + return val; +} + +/* +================= +FindNextChunk +================= +*/ +static void FindNextChunk( const char *name ) +{ + while( 1 ) + { + iff_dataPtr = iff_lastChunk; + + if( iff_dataPtr >= iff_end ) + { + // didn't find the chunk + iff_dataPtr = NULL; + return; + } + + iff_dataPtr += 4; + iff_chunkLen = GetLittleLong(); + + if( iff_chunkLen < 0 ) + { + iff_dataPtr = NULL; + return; + } + + iff_dataPtr -= 8; + iff_lastChunk = iff_dataPtr + 8 + ((iff_chunkLen + 1) & ~1); + + if( !Q_strncmp( iff_dataPtr, name, 4 )) + return; + } +} + +/* +================= +FindChunk +================= +*/ +static void FindChunk( const char *name ) +{ + iff_lastChunk = iff_data; + FindNextChunk( name ); +} + +/* +============ +StreamFindNextChunk +============ +*/ +qboolean StreamFindNextChunk( file_t *file, const char *name, int *last_chunk ) +{ + char chunkName[4]; + int iff_chunk_len; + + while( 1 ) + { + FS_Seek( file, *last_chunk, SEEK_SET ); + + if( FS_Eof( file )) + return false; // didn't find the chunk + + FS_Seek( file, 4, SEEK_CUR ); + FS_Read( file, &iff_chunk_len, sizeof( iff_chunk_len )); + if( iff_chunk_len < 0 ) + return false; // didn't find the chunk + + FS_Seek( file, -8, SEEK_CUR ); + *last_chunk = FS_Tell( file ) + 8 + (( iff_chunk_len + 1 ) & ~1 ); + FS_Read( file, chunkName, 4 ); + + if( !Q_strncmp( chunkName, name, 4 )) + return true; + } + + return false; +} + +/* +============= +Sound_LoadWAV +============= +*/ +qboolean Sound_LoadWAV( const char *name, const byte *buffer, size_t filesize ) +{ + int samples, fmt; + qboolean mpeg_stream = false; + + if( !buffer || filesize <= 0 ) + return false; + + iff_data = buffer; + iff_end = buffer + filesize; + + // find "RIFF" chunk + FindChunk( "RIFF" ); + + if( !( iff_dataPtr && !Q_strncmp( iff_dataPtr + 8, "WAVE", 4 ))) + { + MsgDev( D_ERROR, "Sound_LoadWAV: %s missing 'RIFF/WAVE' chunks\n", name ); + return false; + } + + // get "fmt " chunk + iff_data = iff_dataPtr + 12; + FindChunk( "fmt " ); + + if( !iff_dataPtr ) + { + MsgDev( D_ERROR, "Sound_LoadWAV: %s missing 'fmt ' chunk\n", name ); + return false; + } + + iff_dataPtr += 8; + fmt = GetLittleShort(); + + if( fmt != 1 ) + { + if( fmt != 85 ) + { + MsgDev( D_ERROR, "Sound_LoadWAV: %s not a microsoft PCM format\n", name ); + return false; + } + else + { + // mpeg stream in wav container + mpeg_stream = true; + } + } + + sound.channels = GetLittleShort(); + if( sound.channels != 1 && sound.channels != 2 ) + { + MsgDev( D_ERROR, "Sound_LoadWAV: only mono and stereo WAV files supported (%s)\n", name ); + return false; + } + + sound.rate = GetLittleLong(); + iff_dataPtr += 6; + + sound.width = GetLittleShort() / 8; + if( mpeg_stream ) sound.width = 2; // mp3 always 16bit + + if( sound.width != 1 && sound.width != 2 ) + { + MsgDev( D_WARN, "Sound_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name ); + return false; + } + + // get cue chunk + FindChunk( "cue " ); + + if( iff_dataPtr ) + { + iff_dataPtr += 32; + sound.loopstart = GetLittleLong(); + FindNextChunk( "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker + + if( iff_dataPtr ) + { + if( !Q_strncmp( iff_dataPtr + 28, "mark", 4 )) + { + // this is not a proper parse, but it works with CoolEdit... + iff_dataPtr += 24; + sound.samples = sound.loopstart + GetLittleLong(); // samples in loop + } + } + } + else + { + sound.loopstart = -1; + sound.samples = 0; + } + + // find data chunk + FindChunk( "data" ); + + if( !iff_dataPtr ) + { + MsgDev( D_WARN, "Sound_LoadWAV: %s missing 'data' chunk\n", name ); + return false; + } + + iff_dataPtr += 4; + samples = GetLittleLong() / sound.width; + + if( sound.samples ) + { + if( samples < sound.samples ) + { + MsgDev( D_ERROR, "Sound_LoadWAV: %s has a bad loop length\n", name ); + return false; + } + } + else sound.samples = samples; + + if( sound.samples <= 0 ) + { + MsgDev( D_ERROR, "Sound_LoadWAV: file with %i samples (%s)\n", sound.samples, name ); + return false; + } + + sound.type = WF_PCMDATA; + sound.samples /= sound.channels; + + // g-cont. get support for mp3 streams packed in wav container + // e.g. CAd menu sounds + if( mpeg_stream ) + { + int hdr_size = (iff_dataPtr - buffer); + + if(( filesize - hdr_size ) < FRAME_SIZE ) + { + sound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, FRAME_SIZE ); + memcpy( sound.tempbuffer, buffer + (iff_dataPtr - buffer), filesize - hdr_size ); + return Sound_LoadMPG( name, sound.tempbuffer, FRAME_SIZE ); + } + + return Sound_LoadMPG( name, buffer + hdr_size, filesize - hdr_size ); + } + + // Load the data + sound.size = sound.samples * sound.width * sound.channels; + sound.wav = Mem_Alloc( host.soundpool, sound.size ); + + memcpy( sound.wav, buffer + (iff_dataPtr - buffer), sound.size ); + + // now convert 8-bit sounds to signed + if( sound.width == 1 ) + { + int i, j; + char *pData = sound.wav; + + for( i = 0; i < sound.samples; i++ ) + { + for( j = 0; j < sound.channels; j++ ) + { + *pData = (byte)((int)((byte)*pData) - 128 ); + pData++; + } + } + } + + return true; +} + +/* +================= +Stream_OpenWAV +================= +*/ +stream_t *Stream_OpenWAV( const char *filename ) +{ + stream_t *stream; + int last_chunk = 0; + char chunkName[4]; + int iff_data; + file_t *file; + short t; + + if( !filename || !*filename ) + return NULL; + + // open + file = FS_Open( filename, "rb", false ); + if( !file ) return NULL; + + // find "RIFF" chunk + if( !StreamFindNextChunk( file, "RIFF", &last_chunk )) + { + MsgDev( D_ERROR, "Stream_OpenWAV: %s missing RIFF chunk\n", filename ); + FS_Close( file ); + return NULL; + } + + FS_Read( file, chunkName, 4 ); + if( !Q_strncmp( chunkName, "WAVE", 4 )) + { + MsgDev( D_ERROR, "Stream_OpenWAV: %s missing WAVE chunk\n", filename ); + FS_Close( file ); + return NULL; + } + + // get "fmt " chunk + iff_data = FS_Tell( file ) + 4; + last_chunk = iff_data; + if( !StreamFindNextChunk( file, "fmt ", &last_chunk )) + { + MsgDev( D_ERROR, "Stream_OpenWAV: %s missing 'fmt ' chunk\n", filename ); + FS_Close( file ); + return NULL; + } + + FS_Read( file, chunkName, 4 ); + + FS_Read( file, &t, sizeof( t )); + if( t != 1 ) + { + MsgDev( D_ERROR, "Stream_OpenWAV: %s not a microsoft PCM format\n", filename ); + FS_Close( file ); + return NULL; + } + + FS_Read( file, &t, sizeof( t )); + sound.channels = t; + + FS_Read( file, &sound.rate, sizeof( int )); + + FS_Seek( file, 6, SEEK_CUR ); + + FS_Read( file, &t, sizeof( t )); + sound.width = t / 8; + + sound.loopstart = 0; + + // find data chunk + last_chunk = iff_data; + if( !StreamFindNextChunk( file, "data", &last_chunk )) + { + MsgDev( D_ERROR, "Stream_OpenWAV: %s missing 'data' chunk\n", filename ); + FS_Close( file ); + return NULL; + } + + FS_Read( file, &sound.samples, sizeof( int )); + sound.samples = ( sound.samples / sound.width ) / sound.channels; + + // at this point we have valid stream + stream = Mem_Alloc( host.soundpool, sizeof( stream_t )); + stream->file = file; + stream->size = sound.samples * sound.width * sound.channels; + stream->buffsize = FS_Tell( file ); // header length + stream->channels = sound.channels; + stream->width = sound.width; + stream->rate = sound.rate; + stream->type = WF_PCMDATA; + + return stream; +} + +/* +================= +Stream_ReadWAV + +assume stream is valid +================= +*/ +long Stream_ReadWAV( stream_t *stream, long bytes, void *buffer ) +{ + int remaining; + + if( !stream->file ) return 0; // invalid file + + remaining = stream->size - stream->pos; + if( remaining <= 0 ) return 0; + if( bytes > remaining ) bytes = remaining; + + stream->pos += bytes; + FS_Read( stream->file, buffer, bytes ); + + return bytes; +} + +/* +================= +Stream_SetPosWAV + +assume stream is valid +================= +*/ +long Stream_SetPosWAV( stream_t *stream, long newpos ) +{ + // NOTE: stream->pos it's real file position without header size + if( FS_Seek( stream->file, stream->buffsize + newpos, SEEK_SET ) != -1 ) + { + stream->pos = newpos; + return true; + } + + return false; +} + +/* +================= +Stream_GetPosWAV + +assume stream is valid +================= +*/ +long Stream_GetPosWAV( stream_t *stream ) +{ + return stream->pos; +} + +/* +================= +Stream_FreeWAV + +assume stream is valid +================= +*/ +void Stream_FreeWAV( stream_t *stream ) +{ + if( stream->file ) + FS_Close( stream->file ); + Mem_Free( stream ); +} \ No newline at end of file diff --git a/engine/common/soundlib/soundlib.h b/engine/common/soundlib/soundlib.h new file mode 100644 index 00000000..f345ef57 --- /dev/null +++ b/engine/common/soundlib/soundlib.h @@ -0,0 +1,137 @@ +/* +soundlib.h - engine sound lib +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef SOUNDLIB_H +#define SOUNDLIB_H + +#include "common.h" + +#define FRAME_SIZE 32768 // must match with mp3 frame size +#define OUTBUF_SIZE 8192 // don't change! + +typedef struct loadwavfmt_s +{ + const char *formatstring; + const char *ext; + qboolean (*loadfunc)( const char *name, const byte *buffer, size_t filesize ); +} loadwavfmt_t; + +typedef struct streamfmt_s +{ + const char *formatstring; + const char *ext; + + stream_t *(*openfunc)( const char *filename ); + long (*readfunc)( stream_t *stream, long bytes, void *buffer ); + long (*setposfunc)( stream_t *stream, long newpos ); + long (*getposfunc)( stream_t *stream ); + void (*freefunc)( stream_t *stream ); +} streamfmt_t; + +typedef struct sndlib_s +{ + const loadwavfmt_t *loadformats; + const streamfmt_t *streamformat; // music stream + + // current sound state + int type; // sound type + int rate; // num samples per second (e.g. 11025 - 11 khz) + int width; // resolution - bum bits divided by 8 (8 bit is 1, 16 bit is 2) + int channels; // num channels (1 - mono, 2 - stereo) + int loopstart; // start looping from + uint samples; // total samplecount in sound + uint flags; // additional sound flags + size_t size; // sound unpacked size (for bounds checking) + byte *wav; // sound pointer (see sound_type for details) + + byte *tempbuffer; // for convert operations + int cmd_flags; +} sndlib_t; + +typedef struct stream_s +{ + const streamfmt_t *format; // streamformat to operate + + // stream info + file_t *file; // stream file + int width; // resolution - num bits divided by 8 (8 bit is 1, 16 bit is 2) + int rate; // stream rate + int channels; // stream channels + int type; // wavtype + size_t size; // total stream size + + // current stream state + void *ptr; // internal decoder state + char temp[OUTBUF_SIZE]; // mpeg decoder stuff + size_t pos; // actual track position (or actual buffer remains) + int buffsize; // cached buffer size +}; + +/* +======================================================================== + +.WAV sound format + +======================================================================== +*/ + +#define RIFFHEADER (('F'<<24)+('F'<<16)+('I'<<8)+'R') // little-endian "RIFF" +#define WAVEHEADER (('E'<<24)+('V'<<16)+('A'<<8)+'W') // little-endian "WAVE" +#define FORMHEADER ((' '<<24)+('t'<<16)+('m'<<8)+'f') // little-endian "fmt " +#define DATAHEADER (('a'<<24)+('t'<<16)+('a'<<8)+'d') // little-endian "data" + +typedef struct +{ + int riff_id; // 'RIFF' + long rLen; + int wave_id; // 'WAVE' + int fmt_id; // 'fmt ' + long pcm_header_len; // varies... + short wFormatTag; + short nChannels; // 1,2 for stereo data is (l,r) pairs + long nSamplesPerSec; + long nAvgBytesPerSec; + short nBlockAlign; + short nBitsPerSample; +} wavehdr_t; + +typedef struct +{ + int data_id; // 'data' or 'fact' + long dLen; +} chunkhdr_t; + +extern sndlib_t sound; +// +// formats load +// +qboolean Sound_LoadWAV( const char *name, const byte *buffer, size_t filesize ); +qboolean Sound_LoadMPG( const char *name, const byte *buffer, size_t filesize ); + +// +// stream operate +// +stream_t *Stream_OpenWAV( const char *filename ); +long Stream_ReadWAV( stream_t *stream, long bytes, void *buffer ); +long Stream_SetPosWAV( stream_t *stream, long newpos ); +long Stream_GetPosWAV( stream_t *stream ); +void Stream_FreeWAV( stream_t *stream ); +stream_t *Stream_OpenMPG( const char *filename ); +long Stream_ReadMPG( stream_t *stream, long bytes, void *buffer ); +long Stream_SetPosMPG( stream_t *stream, long newpos ); +long Stream_GetPosMPG( stream_t *stream ); +void Stream_FreeMPG( stream_t *stream ); + +#endif//SOUNDLIB_H \ No newline at end of file diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c new file mode 100644 index 00000000..8488f97a --- /dev/null +++ b/engine/common/sys_con.c @@ -0,0 +1,535 @@ +/* +sys_con.c - win32 dedicated and developer console +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +/* +=============================================================================== + +WIN32 CONSOLE + +=============================================================================== +*/ + +// console defines +#define SUBMIT_ID 1 // "submit" button +#define QUIT_ON_ESCAPE_ID 2 // escape event +#define EDIT_ID 110 +#define INPUT_ID 109 +#define IDI_ICON1 101 + +#define SYSCONSOLE "XashConsole" +#define COMMAND_HISTORY 64 // system console keep more commands than game console + +typedef struct +{ + char title[64]; + HWND hWnd; + HWND hwndBuffer; + HWND hwndButtonSubmit; + HBRUSH hbrEditBackground; + HFONT hfBufferFont; + HWND hwndInputLine; + string consoleText; + string returnedText; + string historyLines[COMMAND_HISTORY]; + int nextHistoryLine; + int historyLine; + int status; + int windowWidth, windowHeight; + WNDPROC SysInputLineWndProc; + size_t outLen; + + // log stuff + qboolean log_active; + char log_path[MAX_SYSPATH]; + FILE *logfile; +} WinConData; + +static WinConData s_wcd; + +void Con_ShowConsole( qboolean show ) +{ + if( !s_wcd.hWnd || show == s_wcd.status ) + return; + + s_wcd.status = show; + if( show ) + { + ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL ); + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + } + else ShowWindow( s_wcd.hWnd, SW_HIDE ); +} + +void Con_DisableInput( void ) +{ + if( host.type != HOST_DEDICATED ) return; + SendMessage( s_wcd.hwndButtonSubmit, WM_ENABLE, 0, 0 ); + SendMessage( s_wcd.hwndInputLine, WM_ENABLE, 0, 0 ); +} + +void Con_SetInputText( const char *inputText ) +{ + if( host.type != HOST_DEDICATED ) return; + SetWindowText( s_wcd.hwndInputLine, inputText ); + SendMessage( s_wcd.hwndInputLine, EM_SETSEL, Q_strlen( inputText ), -1 ); +} + +static void Con_Clear_f( void ) +{ + if( host.type != HOST_DEDICATED ) return; + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, (LPARAM)"" ); + UpdateWindow( s_wcd.hwndBuffer ); +} + +static int Con_KeyEvent( int key, qboolean down ) +{ + char inputBuffer[1024]; + + if( !down ) + return 0; + + switch( key ) + { + case VK_TAB: + GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); + Cmd_AutoComplete( inputBuffer ); + Con_SetInputText( inputBuffer ); + return 1; + case VK_DOWN: + if( s_wcd.historyLine == s_wcd.nextHistoryLine ) + return 0; + s_wcd.historyLine++; + Con_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); + return 1; + case VK_UP: + if( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) + s_wcd.historyLine--; + Con_SetInputText( s_wcd.historyLines[s_wcd.historyLine % COMMAND_HISTORY] ); + return 1; + } + return 0; +} + +static long _stdcall Con_WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch( uMsg ) + { + case WM_ACTIVATE: + if( LOWORD( wParam ) != WA_INACTIVE ) + SetFocus( s_wcd.hwndInputLine ); + break; + case WM_CLOSE: + if( host.status == HOST_ERR_FATAL ) + { + // send windows message + PostQuitMessage( 0 ); + } + else Sys_Quit(); // otherwise + return 0; + case WM_CTLCOLORSTATIC: + if((HWND)lParam == s_wcd.hwndBuffer ) + { + SetBkColor((HDC)wParam, RGB( 0x90, 0x90, 0x90 )); + SetTextColor((HDC)wParam, RGB( 0xff, 0xff, 0xff )); + return (long)s_wcd.hbrEditBackground; + } + break; + case WM_COMMAND: + if( wParam == SUBMIT_ID ) + { + SendMessage( s_wcd.hwndInputLine, WM_CHAR, 13, 0L ); + SetFocus( s_wcd.hwndInputLine ); + } + break; + case WM_HOTKEY: + switch( LOWORD( wParam )) + { + case QUIT_ON_ESCAPE_ID: + PostQuitMessage( 0 ); + break; + } + break; + case WM_CREATE: + s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x90, 0x90, 0x90 )); + break; + } + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +long _stdcall Con_InputLineProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) +{ + char inputBuffer[1024]; + + switch( uMsg ) + { + case WM_KILLFOCUS: + if(( HWND )wParam == s_wcd.hWnd ) + { + SetFocus( hWnd ); + return 0; + } + break; + case WM_SYSKEYDOWN: + case WM_KEYDOWN: + if( Con_KeyEvent( LOWORD( wParam ), true )) + return 0; + break; + case WM_SYSKEYUP: + case WM_KEYUP: + if( Con_KeyEvent( LOWORD( wParam ), false )) + return 0; + break; + case WM_CHAR: + if( Con_KeyEvent( wParam, true )) + return 0; + if( wParam == 13 && host.status != HOST_ERR_FATAL ) + { + GetWindowText( s_wcd.hwndInputLine, inputBuffer, sizeof( inputBuffer )); + Q_strncat( s_wcd.consoleText, inputBuffer, sizeof( s_wcd.consoleText ) - Q_strlen( s_wcd.consoleText ) - 5 ); + Q_strcat( s_wcd.consoleText, "\n" ); + SetWindowText( s_wcd.hwndInputLine, "" ); + Con_Printf( ">%s\n", inputBuffer ); + + // copy line to history buffer + Q_strncpy( s_wcd.historyLines[s_wcd.nextHistoryLine % COMMAND_HISTORY], inputBuffer, MAX_STRING ); + s_wcd.nextHistoryLine++; + s_wcd.historyLine = s_wcd.nextHistoryLine; + return 0; + } + break; + } + return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam ); +} + + +/* +=============================================================================== + +WIN32 IO + +=============================================================================== +*/ +/* +================ +Con_WinPrint + +print into window console +================ +*/ +void Con_WinPrint( const char *pMsg ) +{ + size_t len = Q_strlen( pMsg ); + + // replace selection instead of appending if we're overflowing + s_wcd.outLen += len; + if( s_wcd.outLen >= 0x7fff ) + { + SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 ); + s_wcd.outLen = len; + } + + SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM)pMsg ); + + // put this text into the windows console + SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff ); + SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 ); +} + + +/* +================ +Con_CreateConsole + +create win32 console +================ +*/ +void Con_CreateConsole( void ) +{ + HDC hDC; + WNDCLASS wc; + RECT rect; + int nHeight; + int swidth, sheight, fontsize; + int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION; + int CONSTYLE = WS_CHILD|WS_VISIBLE|WS_VSCROLL|WS_BORDER|WS_EX_CLIENTEDGE|ES_LEFT|ES_MULTILINE|ES_AUTOVSCROLL|ES_READONLY; + string FontName; + + wc.style = 0; + wc.lpfnWndProc = (WNDPROC)Con_WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = host.hInst; + wc.hIcon = LoadIcon( host.hInst, MAKEINTRESOURCE( IDI_ICON1 )); + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = (void *)COLOR_3DSHADOW; + wc.lpszClassName = SYSCONSOLE; + wc.lpszMenuName = 0; + + if( Sys_CheckParm( "-log" )) + s_wcd.log_active = true; + + if( host.type == HOST_NORMAL ) + { + rect.left = 0; + rect.right = 536; + rect.top = 0; + rect.bottom = 364; + Q_strncpy( FontName, "Fixedsys", sizeof( FontName )); + Q_strncpy( s_wcd.title, va( "Xash3D %g", XASH_VERSION ), sizeof( s_wcd.title )); + Q_strncpy( s_wcd.log_path, "engine.log", sizeof( s_wcd.log_path )); + fontsize = 8; + } + else // dedicated console + { + rect.left = 0; + rect.right = 640; + rect.top = 0; + rect.bottom = 392; + Q_strncpy( FontName, "System", sizeof( FontName )); + Q_strncpy( s_wcd.title, "Xash Dedicated Server", sizeof( s_wcd.title )); + Q_strncpy( s_wcd.log_path, "dedicated.log", sizeof( s_wcd.log_path )); + s_wcd.log_active = true; // always make log + fontsize = 14; + } + + Sys_InitLog(); + + if( !RegisterClass( &wc )) + { + // print into log + MsgDev( D_ERROR, "Can't register window class '%s'\n", SYSCONSOLE ); + return; + } + + AdjustWindowRect( &rect, DEDSTYLE, FALSE ); + + hDC = GetDC( GetDesktopWindow() ); + swidth = GetDeviceCaps( hDC, HORZRES ); + sheight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + s_wcd.windowWidth = rect.right - rect.left; + s_wcd.windowHeight = rect.bottom - rect.top; + + s_wcd.hWnd = CreateWindowEx( WS_EX_DLGMODALFRAME, SYSCONSOLE, s_wcd.title, DEDSTYLE, ( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1, NULL, NULL, host.hInst, NULL ); + if( s_wcd.hWnd == NULL ) + { + MsgDev( D_ERROR, "Can't create window '%s'\n", s_wcd.title ); + return; + } + + // create fonts + hDC = GetDC( s_wcd.hWnd ); + nHeight = -MulDiv( fontsize, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ); + s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN|FIXED_PITCH, FontName ); + ReleaseDC( s_wcd.hWnd, hDC ); + + if( host.type == HOST_DEDICATED ) + { + // create the input line + s_wcd.hwndInputLine = CreateWindowEx( WS_EX_CLIENTEDGE, "edit", NULL, WS_CHILD|WS_VISIBLE|WS_BORDER|ES_LEFT|ES_AUTOHSCROLL, 0, 366, 550, 25, s_wcd.hWnd, (HMENU)INPUT_ID, host.hInst, NULL ); + + s_wcd.hwndButtonSubmit = CreateWindow( "button", NULL, BS_PUSHBUTTON|WS_VISIBLE|WS_CHILD|BS_DEFPUSHBUTTON, 552, 367, 87, 25, s_wcd.hWnd, (HMENU)SUBMIT_ID, host.hInst, NULL ); + SendMessage( s_wcd.hwndButtonSubmit, WM_SETTEXT, 0, ( LPARAM ) "submit" ); + } + + // create the scrollbuffer + GetClientRect( s_wcd.hWnd, &rect ); + + s_wcd.hwndBuffer = CreateWindowEx( WS_EX_DLGMODALFRAME|WS_EX_CLIENTEDGE, "edit", NULL, CONSTYLE, 0, 0, rect.right - rect.left, min(365, rect.bottom), s_wcd.hWnd, (HMENU)EDIT_ID, host.hInst, NULL ); + SendMessage( s_wcd.hwndBuffer, WM_SETFONT, (WPARAM)s_wcd.hfBufferFont, 0 ); + + if( host.type == HOST_DEDICATED ) + { + s_wcd.SysInputLineWndProc = (WNDPROC)SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, (long)Con_InputLineProc ); + SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM )s_wcd.hfBufferFont, 0 ); + } + + // show console if needed + if( host.con_showalways ) + { + // make console visible + ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT ); + UpdateWindow( s_wcd.hWnd ); + SetForegroundWindow( s_wcd.hWnd ); + + if( host.type != HOST_DEDICATED ) + SetFocus( s_wcd.hWnd ); + else SetFocus( s_wcd.hwndInputLine ); + s_wcd.status = true; + } + else s_wcd.status = false; +} + +/* +================ +Con_InitConsoleCommands + +register console commands (dedicated only) +================ +*/ +void Con_InitConsoleCommands( void ) +{ + if( host.type != HOST_DEDICATED ) return; + Cmd_AddCommand( "clear", Con_Clear_f, "clear console history" ); +} + +/* +================ +Con_DestroyConsole + +destroy win32 console +================ +*/ +void Con_DestroyConsole( void ) +{ + // last text message into console or log + MsgDev( D_NOTE, "Sys_FreeLibrary: Unloading xash.dll\n" ); + + Sys_CloseLog(); + + if( s_wcd.hWnd ) + { + DeleteObject( s_wcd.hbrEditBackground ); + DeleteObject( s_wcd.hfBufferFont ); + + if( host.type == HOST_DEDICATED ) + { + ShowWindow( s_wcd.hwndButtonSubmit, SW_HIDE ); + DestroyWindow( s_wcd.hwndButtonSubmit ); + s_wcd.hwndButtonSubmit = 0; + + ShowWindow( s_wcd.hwndInputLine, SW_HIDE ); + DestroyWindow( s_wcd.hwndInputLine ); + s_wcd.hwndInputLine = 0; + } + + ShowWindow( s_wcd.hwndBuffer, SW_HIDE ); + DestroyWindow( s_wcd.hwndBuffer ); + s_wcd.hwndBuffer = 0; + + ShowWindow( s_wcd.hWnd, SW_HIDE ); + DestroyWindow( s_wcd.hWnd ); + s_wcd.hWnd = 0; + } + + UnregisterClass( SYSCONSOLE, host.hInst ); + + // place it here in case Sys_Crash working properly + if( host.hMutex ) CloseHandle( host.hMutex ); +} + +/* +================ +Con_Input + +returned input text +================ +*/ +char *Con_Input( void ) +{ + if( s_wcd.consoleText[0] == 0 ) + return NULL; + + Q_strncpy( s_wcd.returnedText, s_wcd.consoleText, sizeof( s_wcd.returnedText )); + s_wcd.consoleText[0] = 0; + + return s_wcd.returnedText; +} + +/* +================ +Con_SetFocus + +change focus to console hwnd +================ +*/ +void Con_RegisterHotkeys( void ) +{ + SetFocus( s_wcd.hWnd ); + + // user can hit escape for quit + RegisterHotKey( s_wcd.hWnd, QUIT_ON_ESCAPE_ID, 0, VK_ESCAPE ); +} + +/* +=============================================================================== + +SYSTEM LOG + +=============================================================================== +*/ +void Sys_InitLog( void ) +{ + const char *mode; + + if( host.change_game && host.type != HOST_DEDICATED ) + mode = "a"; + else mode = "w"; + + // create log if needed + if( s_wcd.log_active ) + { + s_wcd.logfile = fopen( s_wcd.log_path, mode ); + if( !s_wcd.logfile ) MsgDev( D_ERROR, "Sys_InitLog: can't create log file %s\n", s_wcd.log_path ); + + fprintf( s_wcd.logfile, "=================================================================================\n" ); + fprintf( s_wcd.logfile, "\t%s (build %i) started at %s\n", s_wcd.title, Q_buildnum(), Q_timestamp( TIME_FULL )); + fprintf( s_wcd.logfile, "=================================================================================\n" ); + } +} + +void Sys_CloseLog( void ) +{ + char event_name[64]; + + // continue logged + switch( host.status ) + { + case HOST_CRASHED: + Q_strncpy( event_name, "crashed", sizeof( event_name )); + break; + case HOST_ERR_FATAL: + Q_strncpy( event_name, "stopped with error", sizeof( event_name )); + break; + default: + if( !host.change_game ) Q_strncpy( event_name, "stopped", sizeof( event_name )); + else Q_strncpy( event_name, host.finalmsg, sizeof( event_name )); + break; + } + + if( s_wcd.logfile ) + { + fprintf( s_wcd.logfile, "\n"); + fprintf( s_wcd.logfile, "================================================================================="); + if( host.change_game ) fprintf( s_wcd.logfile, "\n\t%s (build %i) %s\n", s_wcd.title, Q_buildnum(), event_name ); + else fprintf( s_wcd.logfile, "\n\t%s (build %i) %s at %s\n", s_wcd.title, Q_buildnum(), event_name, Q_timestamp( TIME_FULL )); + fprintf( s_wcd.logfile, "================================================================================="); + if( host.change_game ) fprintf( s_wcd.logfile, "\n" ); // just for tabulate + + fclose( s_wcd.logfile ); + s_wcd.logfile = NULL; + } +} + +void Sys_PrintLog( const char *pMsg ) +{ + if( !s_wcd.logfile ) return; + fprintf( s_wcd.logfile, pMsg ); + fflush( s_wcd.logfile ); +} \ No newline at end of file diff --git a/engine/common/sys_win.c b/engine/common/sys_win.c new file mode 100644 index 00000000..ecfeecf0 --- /dev/null +++ b/engine/common/sys_win.c @@ -0,0 +1,659 @@ +/* +sys_win.c - platform dependent code +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" + +qboolean error_on_exit = false; // arg for exit(); + +/* +================ +Sys_DoubleTime +================ +*/ +double Sys_DoubleTime( void ) +{ + static LARGE_INTEGER g_PerformanceFrequency; + static LARGE_INTEGER g_ClockStart; + LARGE_INTEGER CurrentTime; + + if( !g_PerformanceFrequency.QuadPart ) + { + QueryPerformanceFrequency( &g_PerformanceFrequency ); + QueryPerformanceCounter( &g_ClockStart ); + } + QueryPerformanceCounter( &CurrentTime ); + + return (double)( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / (double)( g_PerformanceFrequency.QuadPart ); +} + +/* +================ +Sys_GetClipboardData + +create buffer, that contain clipboard +================ +*/ +char *Sys_GetClipboardData( void ) +{ + static char *data = NULL; + char *cliptext; + + if( data ) + { + // release previous cbd + Z_Free( data ); + data = NULL; + } + + if( OpenClipboard( NULL ) != 0 ) + { + HANDLE hClipboardData; + + if(( hClipboardData = GetClipboardData( CF_TEXT )) != 0 ) + { + if(( cliptext = GlobalLock( hClipboardData )) != 0 ) + { + data = Z_Malloc( GlobalSize( hClipboardData ) + 1 ); + Q_strcpy( data, cliptext ); + GlobalUnlock( hClipboardData ); + } + } + CloseClipboard(); + } + + return data; +} + +/* +================ +Sys_SetClipboardData + +write screenshot into clipboard +================ +*/ +void Sys_SetClipboardData( const byte *buffer, size_t size ) +{ + EmptyClipboard(); + + if( OpenClipboard( NULL ) != 0 ) + { + HGLOBAL hResult = GlobalAlloc( GMEM_MOVEABLE, size ); + byte *bufferCopy = (byte *)GlobalLock( hResult ); + + memcpy( bufferCopy, buffer, size ); + GlobalUnlock( hResult ); + + if( SetClipboardData( CF_DIB, hResult ) == NULL ) + { + MsgDev( D_ERROR, "unable to write screenshot\n" ); + GlobalFree( hResult ); + } + CloseClipboard(); + } +} + +/* +================ +Sys_Sleep + +freeze application for some time +================ +*/ +void Sys_Sleep( int msec ) +{ + msec = bound( 1, msec, 1000 ); + Sleep( msec ); +} + +/* +================ +Sys_GetCurrentUser + +returns username for current profile +================ +*/ +char *Sys_GetCurrentUser( void ) +{ + static string sys_user_name; + dword size = sizeof( sys_user_name ); + + if( !sys_user_name[0] ) + { + HINSTANCE advapi32_dll = LoadLibrary( "advapi32.dll" ); + BOOL (_stdcall *pGetUserNameA)( LPSTR lpBuffer, LPDWORD nSize ) = NULL; + if( advapi32_dll ) pGetUserNameA = (void *)GetProcAddress( advapi32_dll, "GetUserNameA" ); + if( pGetUserNameA) pGetUserNameA( sys_user_name, &size ); + if( advapi32_dll ) FreeLibrary( advapi32_dll ); // no need anymore... + if( !sys_user_name[0] ) Q_strcpy( sys_user_name, "player" ); + } + + return sys_user_name; +} + +/* +================= +Sys_GetMachineKey +================= +*/ +const char *Sys_GetMachineKey( int *nLength ) +{ + HINSTANCE rpcrt4_dll = LoadLibrary( "rpcrt4.dll" ); + RPC_STATUS (_stdcall *pUuidCreateSequential)( UUID __RPC_FAR *Uuid ) = NULL; + static byte key[32]; + byte mac[8]; + UUID uuid; + int i; + + if( rpcrt4_dll ) pUuidCreateSequential = (void *)GetProcAddress( rpcrt4_dll, "UuidCreateSequential" ); + if( pUuidCreateSequential ) pUuidCreateSequential( &uuid ); // ask OS to create UUID + if( rpcrt4_dll ) FreeLibrary( rpcrt4_dll ); // no need anymore... + + for( i = 2; i < 8; i++ ) // bytes 2 through 7 inclusive are MAC address + mac[i-2] = uuid.Data4[i]; + + Q_snprintf( key, sizeof( key ), "%02X-%02X-%02X-%02X-%02X-%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] ); + + if( nLength ) *nLength = Q_strlen( key ); + return key; +} + +/* +================= +Sys_ShellExecute +================= +*/ +void Sys_ShellExecute( const char *path, const char *parms, qboolean exit ) +{ + HINSTANCE shell32_dll = LoadLibrary( "shell32.dll" ); + HINSTANCE (_stdcall *pShellExecuteA)( HWND hwnd, LPCSTR lpOp, LPCSTR lpFile, LPCSTR lpParam, LPCSTR lpDir, INT nShowCmd ) = NULL; + if( shell32_dll ) pShellExecuteA = (void *)GetProcAddress( shell32_dll, "ShellExecuteA" ); + if( pShellExecuteA ) pShellExecuteA( NULL, "open", path, parms, NULL, SW_SHOW ); + if( shell32_dll ) FreeLibrary( shell32_dll ); // no need anymore... + + if( exit ) Sys_Quit(); +} + +/* +================== +Sys_ParseCommandLine + +================== +*/ +void Sys_ParseCommandLine( LPSTR lpCmdLine, qboolean uncensored ) +{ + const char *blank = "censored"; + static char commandline[MAX_SYSPATH]; + int i; + + host.argc = 1; + host.argv[0] = "exe"; + + Q_strncpy( commandline, lpCmdLine, Q_strlen( lpCmdLine ) + 1 ); + lpCmdLine = commandline; // to prevent modify original commandline + + while( *lpCmdLine && ( host.argc < MAX_NUM_ARGVS )) + { + while( *lpCmdLine && *lpCmdLine <= ' ' ) + lpCmdLine++; + if( !*lpCmdLine ) break; + + if( *lpCmdLine == '\"' ) + { + // quoted string + lpCmdLine++; + host.argv[host.argc] = lpCmdLine; + host.argc++; + while( *lpCmdLine && ( *lpCmdLine != '\"' )) + lpCmdLine++; + } + else + { + // unquoted word + host.argv[host.argc] = lpCmdLine; + host.argc++; + while( *lpCmdLine && *lpCmdLine > ' ') + lpCmdLine++; + } + + if( *lpCmdLine ) + { + *lpCmdLine = 0; + lpCmdLine++; + } + } + + if( uncensored || !host.change_game ) + return; + + for( i = 0; i < host.argc; i++ ) + { + // we wan't return to first game + if( !Q_stricmp( "-game", host.argv[i] )) host.argv[i] = (char *)blank; + // probably it's timewaster, because engine rejected second change + if( !Q_stricmp( "+game", host.argv[i] )) host.argv[i] = (char *)blank; + // you sure what is map exists in new game? + if( !Q_stricmp( "+map", host.argv[i] )) host.argv[i] = (char *)blank; + // just stupid action + if( !Q_stricmp( "+load", host.argv[i] )) host.argv[i] = (char *)blank; + // changelevel beetwen games? wow it's great idea! + if( !Q_stricmp( "+changelevel", host.argv[i] )) host.argv[i] = (char *)blank; + } +} + +/* +================== +Sys_MergeCommandLine + +================== +*/ +void Sys_MergeCommandLine( LPSTR lpCmdLine ) +{ + static char commandline[MAX_SYSPATH]; + + if( !host.change_game ) return; + + Q_strncpy( commandline, lpCmdLine, Q_strlen( lpCmdLine ) + 1 ); + lpCmdLine = commandline; // to prevent modify original commandline + + while( *lpCmdLine && ( host.argc < MAX_NUM_ARGVS )) + { + while( *lpCmdLine && *lpCmdLine <= ' ' ) + lpCmdLine++; + if( !*lpCmdLine ) break; + + if( *lpCmdLine == '\"' ) + { + // quoted string + lpCmdLine++; + host.argv[host.argc] = lpCmdLine; + host.argc++; + while( *lpCmdLine && ( *lpCmdLine != '\"' )) + lpCmdLine++; + } + else + { + // unquoted word + host.argv[host.argc] = lpCmdLine; + host.argc++; + while( *lpCmdLine && *lpCmdLine > ' ') + lpCmdLine++; + } + + if( *lpCmdLine ) + { + *lpCmdLine = 0; + lpCmdLine++; + } + } +} + +/* +================ +Sys_CheckParm + +Returns the position (1 to argc-1) in the program's argument list +where the given parameter apears, or 0 if not present +================ +*/ +int Sys_CheckParm( const char *parm ) +{ + int i; + + for( i = 1; i < host.argc; i++ ) + { + if( !host.argv[i] ) continue; + if( !Q_stricmp( parm, host.argv[i] )) + return i; + } + return 0; +} + +/* +================ +Sys_GetParmFromCmdLine + +Returns the argument for specified parm +================ +*/ +qboolean _Sys_GetParmFromCmdLine( char *parm, char *out, size_t size ) +{ + int argc = Sys_CheckParm( parm ); + + if( !argc || !out || !host.argv[argc + 1] ) + return false; + + Q_strncpy( out, host.argv[argc+1], size ); + + return true; +} + +void Sys_SendKeyEvents( void ) +{ + MSG msg; + + while( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE )) + { + if( !GetMessage( &msg, NULL, 0, 0 )) + Sys_Quit (); + + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +} + +//======================================================================= +// DLL'S MANAGER SYSTEM +//======================================================================= +qboolean Sys_LoadLibrary( dll_info_t *dll ) +{ + const dllfunc_t *func; + string errorstring; + + // check errors + if( !dll ) return false; // invalid desc + if( dll->link ) return true; // already loaded + + if( !dll->name || !*dll->name ) + return false; // nothing to load + + MsgDev( D_NOTE, "Sys_LoadLibrary: Loading %s", dll->name ); + + if( dll->fcts ) + { + // lookup export table + for( func = dll->fcts; func && func->name != NULL; func++ ) + *func->func = NULL; + } + + if( !dll->link ) dll->link = LoadLibrary ( dll->name ); // environment pathes + + // no DLL found + if( !dll->link ) + { + Q_snprintf( errorstring, sizeof( errorstring ), "Sys_LoadLibrary: couldn't load %s\n", dll->name ); + goto error; + } + + // Get the function adresses + for( func = dll->fcts; func && func->name != NULL; func++ ) + { + if( !( *func->func = Sys_GetProcAddress( dll, func->name ))) + { + Q_snprintf( errorstring, sizeof( errorstring ), "Sys_LoadLibrary: %s missing or invalid function (%s)\n", dll->name, func->name ); + goto error; + } + } + MsgDev( D_NOTE, " - ok\n" ); + + return true; +error: + MsgDev( D_NOTE, " - failed\n" ); + Sys_FreeLibrary( dll ); // trying to free + if( dll->crash ) Sys_Error( errorstring ); + else MsgDev( D_ERROR, errorstring ); + + return false; +} + +void* Sys_GetProcAddress( dll_info_t *dll, const char* name ) +{ + if( !dll || !dll->link ) // invalid desc + return NULL; + + return (void *)GetProcAddress( dll->link, name ); +} + +qboolean Sys_FreeLibrary( dll_info_t *dll ) +{ + // invalid desc or alredy freed + if( !dll || !dll->link ) + return false; + + if( host.status == HOST_CRASHED ) + { + // we need to hold down all modules, while MSVC can find error + MsgDev( D_NOTE, "Sys_FreeLibrary: hold %s for debugging\n", dll->name ); + return false; + } + else MsgDev( D_NOTE, "Sys_FreeLibrary: Unloading %s\n", dll->name ); + + FreeLibrary( dll->link ); + dll->link = NULL; + + return true; +} + +/* +================ +Sys_WaitForQuit + +wait for 'Esc' key will be hit +================ +*/ +void Sys_WaitForQuit( void ) +{ + MSG msg; + + Con_RegisterHotkeys(); + + msg.message = 0; + + // wait for the user to quit + while( msg.message != WM_QUIT ) + { + if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + else Sys_Sleep( 20 ); + } +} + +long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ) +{ + // save config + if( host.status != HOST_CRASHED ) + { + // check to avoid recursive call + error_on_exit = true; + host.crashed = true; + + if( host.type == HOST_NORMAL ) + CL_Crashed(); // tell client about crash + else host.status = HOST_CRASHED; + + Con_Printf( "unhandled exception: %p at address %p\n", pInfo->ExceptionRecord->ExceptionAddress, pInfo->ExceptionRecord->ExceptionCode ); + + if( !host_developer.value ) + { + // for non-development mode + Sys_Quit(); + return EXCEPTION_CONTINUE_EXECUTION; + } + + // all other states keep unchanged to let debugger find bug + Con_DestroyConsole(); + } + + if( host.oldFilter ) + return host.oldFilter( pInfo ); + return EXCEPTION_CONTINUE_EXECUTION; +} + +/* +================ +Sys_Error + +NOTE: we must prepare engine to shutdown +before call this +================ +*/ +void Sys_Error( const char *error, ... ) +{ + va_list argptr; + char text[MAX_SYSPATH]; + + if( host.status == HOST_ERR_FATAL ) + return; // don't multiple executes + + // make sure what console received last message + if( host.change_game ) Sys_Sleep( 200 ); + + error_on_exit = true; + host.status = HOST_ERR_FATAL; + va_start( argptr, error ); + Q_vsprintf( text, error, argptr ); + va_end( argptr ); + + SV_SysError( text ); + + if( host.type == HOST_NORMAL ) + { + if( host.hWnd ) ShowWindow( host.hWnd, SW_HIDE ); + } + + if( host_developer.value ) + { + Con_ShowConsole( true ); + Con_DisableInput(); // disable input line for dedicated server + Sys_Print( text ); // print error message + Sys_WaitForQuit(); + } + else + { + Con_ShowConsole( false ); + MSGBOX( text ); + } + + Sys_Quit(); +} + +/* +================ +Sys_Quit +================ +*/ +void Sys_Quit( void ) +{ + Host_Shutdown(); + exit( error_on_exit ); +} + +/* +================ +Sys_Print + +print into window console +================ +*/ +void Sys_Print( const char *pMsg ) +{ + const char *msg; + static char buffer[MAX_PRINT_MSG]; + static char logbuf[MAX_PRINT_MSG]; + char *b = buffer; + char *c = logbuf; + int i = 0; + + if( host.type == HOST_NORMAL ) + Con_Print( pMsg ); + + // if the message is REALLY long, use just the last portion of it + if( Q_strlen( pMsg ) > sizeof( buffer ) - 1 ) + msg = pMsg + Q_strlen( pMsg ) - sizeof( buffer ) + 1; + else msg = pMsg; + + // copy into an intermediate buffer + while( msg[i] && (( b - buffer ) < sizeof( buffer ) - 1 )) + { + if( msg[i] == '\n' && msg[i+1] == '\r' ) + { + b[0] = '\r'; + b[1] = c[0] = '\n'; + b += 2, c++; + i++; + } + else if( msg[i] == '\r' ) + { + b[0] = c[0] = '\r'; + b[1] = '\n'; + b += 2, c++; + } + else if( msg[i] == '\n' ) + { + b[0] = '\r'; + b[1] = c[0] = '\n'; + b += 2, c++; + } + else if( msg[i] == '\35' || msg[i] == '\36' || msg[i] == '\37' ) + { + i++; // skip console pseudo graph + } + else if( IsColorString( &msg[i] )) + { + i++; // skip color prefix + } + else + { + if( msg[i] == '\1' || msg[i] == '\2' ) + i++; + *b = *c = msg[i]; + b++, c++; + } + i++; + } + + *b = *c = 0; // terminator + + Sys_PrintLog( logbuf ); + Con_WinPrint( buffer ); +} + +/* +================ +MsgDev + +formatted developer message +================ +*/ +void MsgDev( int type, const char *pMsg, ... ) +{ + static char text[MAX_PRINT_MSG]; + va_list argptr; + + if( type >= D_REPORT && host_developer.value < DEV_EXTENDED ) + return; + + va_start( argptr, pMsg ); + Q_vsnprintf( text, sizeof( text ) - 1, pMsg, argptr ); + va_end( argptr ); + + switch( type ) + { + case D_WARN: + Sys_Print( va( "^3Warning:^7 %s", text )); + break; + case D_ERROR: + Sys_Print( va( "^1Error:^7 %s", text )); + break; + case D_INFO: + case D_NOTE: + case D_REPORT: + Sys_Print( text ); + break; + } +} \ No newline at end of file diff --git a/engine/common/system.h b/engine/common/system.h new file mode 100644 index 00000000..88b08446 --- /dev/null +++ b/engine/common/system.h @@ -0,0 +1,112 @@ +/* +system.h - platform dependent code +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef SYSTEM_H +#define SYSTEM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include +#include + +#define MSGBOX( x ) MessageBox( NULL, x, "Xash Error", MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ) +#define MSGBOX2( x ) MessageBox( host.hWnd, x, "Host Error", MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ) +#define MSGBOX3( x ) MessageBox( host.hWnd, x, "Host Recursive Error", MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ) + +// basic typedefs +typedef int sound_t; +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef byte rgba_t[4]; // unsigned byte colorpack +typedef vec_t matrix3x4[3][4]; +typedef vec_t matrix4x4[4][4]; + +#include "const.h" + +#define ASSERT( exp ) if(!( exp )) Sys_Error( "assert failed at %s:%i\n", __FILE__, __LINE__ ) + +/* +======================================================================== +internal dll's loader + +two main types - native dlls and other win32 libraries will be recognized automatically +NOTE: never change this structure because all dll descriptions in xash code +writes into struct by offsets not names +======================================================================== +*/ +typedef struct dllfunc_s +{ + const char *name; + void **func; +} dllfunc_t; + +typedef struct dll_info_s +{ + const char *name; // name of library + const dllfunc_t *fcts; // list of dll exports + qboolean crash; // crash if dll not found + void *link; // hinstance of loading library +} dll_info_t; + +void Sys_Sleep( int msec ); +double Sys_DoubleTime( void ); +char *Sys_GetClipboardData( void ); +char *Sys_GetCurrentUser( void ); +int Sys_CheckParm( const char *parm ); +void Sys_Error( const char *error, ... ); +qboolean Sys_LoadLibrary( dll_info_t *dll ); +void* Sys_GetProcAddress( dll_info_t *dll, const char* name ); +qboolean Sys_FreeLibrary( dll_info_t *dll ); +void Sys_ParseCommandLine( LPSTR lpCmdLine, qboolean uncensored ); +void Sys_MergeCommandLine( LPSTR lpCmdLine ); +long _stdcall Sys_Crash( PEXCEPTION_POINTERS pInfo ); +void Sys_SetClipboardData( const byte *buffer, size_t size ); +#define Sys_GetParmFromCmdLine( parm, out ) _Sys_GetParmFromCmdLine( parm, out, sizeof( out )) +qboolean _Sys_GetParmFromCmdLine( char *parm, char *out, size_t size ); +void Sys_ShellExecute( const char *path, const char *parms, qboolean exit ); +const char *Sys_GetMachineKey( int *nLength ); +void Sys_SendKeyEvents( void ); +void Sys_Print( const char *pMsg ); +void Sys_PrintLog( const char *pMsg ); +void Sys_InitLog( void ); +void Sys_CloseLog( void ); +void Sys_Quit( void ); + +// +// sys_con.c +// +void Con_ShowConsole( qboolean show ); +void Con_WinPrint( const char *pMsg ); +void Con_InitConsoleCommands( void ); +void Con_CreateConsole( void ); +void Con_DestroyConsole( void ); +void Con_RegisterHotkeys( void ); +void Con_DisableInput( void ); +char *Con_Input( void ); + +// text messages +#define Msg Con_Printf +void MsgDev( int level, const char *pMsg, ... ); + +#ifdef __cplusplus +} +#endif +#endif//SYSTEM_H \ No newline at end of file diff --git a/engine/common/titles.c b/engine/common/titles.c new file mode 100644 index 00000000..0a318d6b --- /dev/null +++ b/engine/common/titles.c @@ -0,0 +1,345 @@ +/* +titles.c - implementation of titles.txt parser +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "client.h" + +#define MAX_MESSAGES 2048 + +#define MSGFILE_NAME 0 +#define MSGFILE_TEXT 1 + +client_textmessage_t gMessageParms; + +// the string "pText" is assumed to have all whitespace from both ends cut out +static int IsComment( const char *pText ) +{ + if( pText ) + { + int length = Q_strlen( pText ); + + if( length >= 2 && pText[0] == '/' && pText[1] == '/' ) + return 1; + + // no text? + if( length > 0 ) + return 0; + } + + // no text is a comment too + return 1; +} + +// the string "pText" is assumed to have all whitespace from both ends cut out +static int IsStartOfText( const char *pText ) +{ + if( pText ) + { + if( pText[0] == '{' ) + return 1; + } + return 0; +} + +// the string "pText" is assumed to have all whitespace from both ends cut out +static int IsEndOfText( const char *pText ) +{ + if( pText ) + { + if( pText[0] == '}' ) + return 1; + } + return 0; +} + +static int IsWhiteSpace( char space ) +{ + if( space == ' ' || space == '\t' || space == '\r' || space == '\n' ) + return 1; + return 0; +} + +static const char *SkipSpace( const char *pText ) +{ + if( pText ) + { + int pos = 0; + while( pText[pos] && IsWhiteSpace( pText[pos] )) + pos++; + return pText + pos; + } + return NULL; +} + +static const char *SkipText( const char *pText ) +{ + if( pText ) + { + int pos = 0; + while( pText[pos] && !IsWhiteSpace( pText[pos] )) + pos++; + return pText + pos; + } + return NULL; +} + +static int ParseFloats( const char *pText, float *pFloat, int count ) +{ + const char *pTemp = pText; + int index = 0; + + while( pTemp && count > 0 ) + { + // skip current token / float + pTemp = SkipText( pTemp ); + // skip any whitespace in between + pTemp = SkipSpace( pTemp ); + + if( pTemp ) + { + // parse a float + pFloat[index] = Q_atof( pTemp ); + count--; + index++; + } + } + + if( count == 0 ) + return 1; + return 0; +} + +static int IsToken( const char *pText, const char *pTokenName ) +{ + if( !pText || !pTokenName ) + return 0; + + if( !Q_strnicmp( pText+1, pTokenName, Q_strlen( pTokenName ))) + return 1; + + return 0; +} + +static int ParseDirective( const char *pText ) +{ + if( pText && pText[0] == '$' ) + { + float tempFloat[8]; + + if( IsToken( pText, "position" )) + { + if( ParseFloats( pText, tempFloat, 2 )) + { + gMessageParms.x = tempFloat[0]; + gMessageParms.y = tempFloat[1]; + } + } + else if( IsToken( pText, "effect" )) + { + if( ParseFloats( pText, tempFloat, 1 )) + { + gMessageParms.effect = (int)tempFloat[0]; + } + } + else if( IsToken( pText, "fxtime" )) + { + if( ParseFloats( pText, tempFloat, 1 )) + { + gMessageParms.fxtime = tempFloat[0]; + } + } + else if( IsToken( pText, "color2" )) + { + if( ParseFloats( pText, tempFloat, 3 )) + { + gMessageParms.r2 = (int)tempFloat[0]; + gMessageParms.g2 = (int)tempFloat[1]; + gMessageParms.b2 = (int)tempFloat[2]; + } + } + else if( IsToken( pText, "color" )) + { + if( ParseFloats( pText, tempFloat, 3 )) + { + gMessageParms.r1 = (int)tempFloat[0]; + gMessageParms.g1 = (int)tempFloat[1]; + gMessageParms.b1 = (int)tempFloat[2]; + } + } + else if( IsToken( pText, "fadein" )) + { + if( ParseFloats( pText, tempFloat, 1 )) + { + gMessageParms.fadein = tempFloat[0]; + } + } + else if( IsToken( pText, "fadeout" )) + { + if( ParseFloats( pText, tempFloat, 3 )) + { + gMessageParms.fadeout = tempFloat[0]; + } + } + else if( IsToken( pText, "holdtime" )) + { + if( ParseFloats( pText, tempFloat, 3 )) + { + gMessageParms.holdtime = tempFloat[0]; + } + } + else + { + MsgDev( D_ERROR, "unknown token: %s\n", pText ); + } + return 1; + } + return 0; +} + +void CL_TextMessageParse( byte *pMemFile, int fileSize ) +{ + char buf[512], trim[512], currentName[512]; + char *pCurrentText = NULL, *pNameHeap; + char nameHeap[32768]; // g-cont. i will scale up heap to handle all TFC messages + int mode = MSGFILE_NAME; // searching for a message name + int lineNumber, filePos, lastLinePos; + client_textmessage_t textMessages[MAX_MESSAGES]; + int i, nameHeapSize, textHeapSize, messageSize, nameOffset; + int messageCount, lastNamePos; + + lastNamePos = 0; + lineNumber = 0; + filePos = 0; + lastLinePos = 0; + messageCount = 0; + + while( COM_MemFgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL ) + { + COM_TrimSpace( buf, trim ); + + switch( mode ) + { + case MSGFILE_NAME: + // skip comment lines + if( IsComment( trim )) + break; + + // Is this a directive "$command"?, if so parse it and break + if( ParseDirective( trim )) + break; + + if( IsStartOfText( trim )) + { + mode = MSGFILE_TEXT; + pCurrentText = (char*)(pMemFile + filePos); + break; + } + + if( IsEndOfText( trim )) + { + MsgDev( D_ERROR, "TextMessage: unexpected '}' found, line %d\n", lineNumber ); + return; + } + Q_strcpy( currentName, trim ); + break; + case MSGFILE_TEXT: + if( IsEndOfText( trim )) + { + int length = Q_strlen( currentName ); + + // save name on name heap + if( lastNamePos + length > 16384 ) + { + MsgDev( D_ERROR, "TextMessage: error while parsing!\n" ); + return; + } + + Q_strcpy( nameHeap + lastNamePos, currentName ); + + // terminate text in-place in the memory file + // (it's temporary memory that will be deleted) + pMemFile[lastLinePos-1] = 0; + + // Save name/text on heap + textMessages[messageCount] = gMessageParms; + textMessages[messageCount].pName = nameHeap + lastNamePos; + lastNamePos += Q_strlen( currentName ) + 1; + textMessages[messageCount].pMessage = pCurrentText; + messageCount++; + + // reset parser to search for names + mode = MSGFILE_NAME; + break; + } + if( IsStartOfText( trim )) + { + MsgDev( D_ERROR, "TextMessage: unexpected '{' found, line %d\n", lineNumber ); + return; + } + break; + } + + lineNumber++; + lastLinePos = filePos; + + if( messageCount >= MAX_MESSAGES ) + { + MsgDev( D_WARN, "Too many messages in titles.txt, max is %d\n", MAX_MESSAGES ); + break; + } + } + + MsgDev( D_NOTE, "TextMessage: parsed %d text messages\n", messageCount ); + nameHeapSize = lastNamePos; + textHeapSize = 0; + + for( i = 0; i < messageCount; i++ ) + textHeapSize += Q_strlen( textMessages[i].pMessage ) + 1; + messageSize = ( messageCount * sizeof( client_textmessage_t )); + + if(( textHeapSize + nameHeapSize + messageSize ) <= 0 ) + { + clgame.titles = NULL; + clgame.numTitles = 0; + return; + } + + // must malloc because we need to be able to clear it after initialization + clgame.titles = (client_textmessage_t *)Mem_Alloc( cls.mempool, textHeapSize + nameHeapSize + messageSize ); + + // copy table over + memcpy( clgame.titles, textMessages, messageSize ); + + // copy Name heap + pNameHeap = ((char *)clgame.titles) + messageSize; + memcpy( pNameHeap, nameHeap, nameHeapSize ); + nameOffset = pNameHeap - clgame.titles[0].pName; + + // copy text & fixup pointers + pCurrentText = pNameHeap + nameHeapSize; + + for( i = 0; i < messageCount; i++ ) + { + clgame.titles[i].pName += nameOffset; // adjust name pointer (parallel buffer) + Q_strcpy( pCurrentText, clgame.titles[i].pMessage ); // copy text over + clgame.titles[i].pMessage = pCurrentText; + pCurrentText += Q_strlen( pCurrentText ) + 1; + } + + if(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize )) + MsgDev( D_ERROR, "TextMessage: overflow text message buffer!\n" ); + + clgame.numTitles = messageCount; +} \ No newline at end of file diff --git a/engine/common/world.c b/engine/common/world.c new file mode 100644 index 00000000..2178b957 --- /dev/null +++ b/engine/common/world.c @@ -0,0 +1,256 @@ +/* +world.c - common worldtrace routines +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "world.h" +#include "pm_defs.h" +#include "mod_local.h" +#include "mathlib.h" +#include "studio.h" + +// just for debug +const char *et_name[] = +{ + "normal", + "player", + "tempentity", + "beam", + "fragmented", +}; + +/* +=============================================================================== + + ENTITY LINKING + +=============================================================================== +*/ +/* +=============== +ClearLink + +ClearLink is used for new headnodes +=============== +*/ +void ClearLink( link_t *l ) +{ + l->prev = l->next = l; +} + +/* +=============== +RemoveLink + +remove link from chain +=============== +*/ +void RemoveLink( link_t *l ) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +/* +=============== +InsertLinkBefore + +kept trigger and solid entities seperate +=============== +*/ +void InsertLinkBefore( link_t *l, link_t *before ) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +/* +================== +World_MoveBounds +================== +*/ +void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( end[i] > start[i] ) + { + boxmins[i] = start[i] + mins[i] - 1.0f; + boxmaxs[i] = end[i] + maxs[i] + 1.0f; + } + else + { + boxmins[i] = end[i] + mins[i] - 1.0f; + boxmaxs[i] = start[i] + maxs[i] + 1.0f; + } + } +} + +trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch ) +{ + if( trace->allsolid || trace->startsolid || trace->fraction < cliptrace->fraction ) + { + trace->ent = touch; + + if( cliptrace->startsolid ) + { + *cliptrace = *trace; + cliptrace->startsolid = true; + } + else *cliptrace = *trace; + } + + return *cliptrace; +} + +/* +================== +World_TransformAABB +================== +*/ +void World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t maxs, vec3_t outmins, vec3_t outmaxs ) +{ + vec3_t p1, p2; + matrix4x4 itransform; + int i; + + if( !outmins || !outmaxs ) return; + + Matrix4x4_Invert_Simple( itransform, transform ); + ClearBounds( outmins, outmaxs ); + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p1[0] = ( i & 1 ) ? mins[0] : maxs[0]; + p1[1] = ( i & 2 ) ? mins[1] : maxs[1]; + p1[2] = ( i & 4 ) ? mins[2] : maxs[2]; + + p2[0] = DotProduct( p1, itransform[0] ); + p2[1] = DotProduct( p1, itransform[1] ); + p2[2] = DotProduct( p1, itransform[2] ); + + if( p2[0] < outmins[0] ) outmins[0] = p2[0]; + if( p2[0] > outmaxs[0] ) outmaxs[0] = p2[0]; + if( p2[1] < outmins[1] ) outmins[1] = p2[1]; + if( p2[1] > outmaxs[1] ) outmaxs[1] = p2[1]; + if( p2[2] < outmins[2] ) outmins[2] = p2[2]; + if( p2[2] > outmaxs[2] ) outmaxs[2] = p2[2]; + } + + // sanity check + for( i = 0; i < 3; i++ ) + { + if( outmins[i] > outmaxs[i] ) + { + MsgDev( D_ERROR, "World_TransformAABB: backwards mins/maxs\n" ); + VectorClear( outmins ); + VectorClear( outmaxs ); + return; + } + } +} + +/* +================== +RankForContents + +Used for determine contents priority +================== +*/ +int RankForContents( int contents ) +{ + switch( contents ) + { + case CONTENTS_EMPTY: return 0; + case CONTENTS_WATER: return 1; + case CONTENTS_TRANSLUCENT: return 2; + case CONTENTS_CURRENT_0: return 3; + case CONTENTS_CURRENT_90: return 4; + case CONTENTS_CURRENT_180: return 5; + case CONTENTS_CURRENT_270: return 6; + case CONTENTS_CURRENT_UP: return 7; + case CONTENTS_CURRENT_DOWN: return 8; + case CONTENTS_SLIME: return 9; + case CONTENTS_LAVA: return 10; + case CONTENTS_SKY: return 11; + case CONTENTS_SOLID: return 12; + default: return 13; // any user contents has more priority than default + } +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ) +{ + float dist1, dist2; + int sides = 0; + + // general case + switch( p->signbits ) + { + case 0: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + // shut up compiler + dist1 = dist2 = 0; + break; + } + + if( dist1 >= p->dist ) + sides = 1; + if( dist2 < p->dist ) + sides |= 2; + + return sides; +} \ No newline at end of file diff --git a/engine/common/world.h b/engine/common/world.h new file mode 100644 index 00000000..b9e2819e --- /dev/null +++ b/engine/common/world.h @@ -0,0 +1,107 @@ +/* +world.h - shared world routines +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef WORLD_H +#define WORLD_H + +#define MOVE_NORMAL 0 // normal trace +#define MOVE_NOMONSTERS 1 // ignore monsters (edicts with flags (FL_MONSTER|FL_FAKECLIENT|FL_CLIENT) set) +#define MOVE_MISSILE 2 // extra size for monsters + +#define FMOVE_IGNORE_GLASS 0x100 +#define FMOVE_SIMPLEBOX 0x200 +#define FMOVE_MONSTERCLIP 0x400 + +#define CONTENTS_NONE 0 // no custom contents specified + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ +#define MAX_TOTAL_ENT_LEAFS 128 +#define AREA_NODES 32 +#define AREA_DEPTH 4 + +#include "lightstyle.h" + +extern const char *et_name[]; + +// linked list +void InsertLinkBefore( link_t *l, link_t *before ); +void RemoveLink( link_t *l ); +void ClearLink( link_t *l ); + +// trace common +void World_MoveBounds( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ); +void World_TransformAABB( matrix4x4 transform, const vec3_t mins, const vec3_t maxs, vec3_t outmins, vec3_t outmaxs ); +trace_t World_CombineTraces( trace_t *cliptrace, trace_t *trace, edict_t *touch ); +int BoxOnPlaneSide( const vec3_t emins, const vec3_t emaxs, const mplane_t *p ); +int RankForContents( int contents ); + +#define BOX_ON_PLANE_SIDE( emins, emaxs, p ) \ + ((( p )->type < 3 ) ? \ + ( \ + ((p)->dist <= (emins)[(p)->type]) ? \ + 1 \ + : \ + ( \ + ((p)->dist >= (emaxs)[(p)->type]) ? \ + 2 \ + : \ + 3 \ + ) \ + ) \ + : \ + BoxOnPlaneSide(( emins ), ( emaxs ), ( p ))) + + +#define check_angles( x ) ( (int)x == 90 || (int)x == 180 || (int)x == 270 || (int)x == -90 || (int)x == -180 || (int)x == -270 ) + +#include "pm_shared.h" + +/* +=============================================================================== + + EVENTS QUEUE (hl1 events code) + +=============================================================================== +*/ +#include "event_api.h" +#include "event_args.h" + +#define MAX_EVENT_QUEUE 64 // 16 simultaneous events, max + +typedef struct event_info_s +{ + word index; // 0 implies not in use + short packet_index; // Use data from state info for entity in delta_packet . + // -1 implies separate info based on event + // parameter signature + short entity_index; // The edict this event is associated with + float fire_time; // if non-zero, the time when the event should be fired + // ( fixed up on the client ) + event_args_t args; + int flags; // reliable or not, etc. ( CLIENT ONLY ) +} event_info_t; + +typedef struct event_state_s +{ + event_info_t ei[MAX_EVENT_QUEUE]; +} event_state_t; + +#endif//WORLD_H \ No newline at end of file diff --git a/engine/common/zone.c b/engine/common/zone.c new file mode 100644 index 00000000..f1292d01 --- /dev/null +++ b/engine/common/zone.c @@ -0,0 +1,486 @@ +/* +zone.c - zone memory allocation from DarkPlaces +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" + +#define MEMUNIT 8 // smallest unit we care about is this many bytes +#define MEMCLUMPSIZE (65536 - 1536) // give malloc padding so we can't waste most of a page at the end +#define MEMBITS (MEMCLUMPSIZE / MEMUNIT) +#define MEMBITINTS (MEMBITS / 32) + +#define MEMCLUMP_SENTINEL 0xABADCAFE +#define MEMHEADER_SENTINEL1 0xDEADF00D +#define MEMHEADER_SENTINEL2 0xDF + +typedef struct memheader_s +{ + struct memheader_s *next; // next and previous memheaders in chain belonging to pool + struct memheader_s *prev; + struct mempool_s *pool; // pool this memheader belongs to + struct memclump_s *clump; // clump this memheader lives in, NULL if not in a clump + size_t size; // size of the memory after the header (excluding header and sentinel2) + const char *filename; // file name and line where Mem_Alloc was called + uint fileline; + uint sentinel1; // should always be MEMHEADER_SENTINEL1 + + // immediately followed by data, which is followed by a MEMHEADER_SENTINEL2 byte +} memheader_t; + +typedef struct memclump_s +{ + byte block[MEMCLUMPSIZE];// contents of the clump + uint sentinel1; // should always be MEMCLUMP_SENTINEL + int bits[MEMBITINTS]; // if a bit is on, it means that the MEMUNIT bytes it represents are allocated, otherwise free + uint sentinel2; // should always be MEMCLUMP_SENTINEL + size_t blocksinuse; // if this drops to 0, the clump is freed + size_t largestavailable; // largest block of memory available + struct memclump_s *chain; // next clump in the chain +} memclump_t; + +typedef struct mempool_s +{ + uint sentinel1; // should always be MEMHEADER_SENTINEL1 + struct memheader_s *chain; // chain of individual memory allocations + struct memclump_s *clumpchain; // chain of clumps (if any) + size_t totalsize; // total memory allocated in this pool (inside memheaders) + size_t realsize; // total memory allocated in this pool (actual malloc total) + size_t lastchecksize; // updated each time the pool is displayed by memlist + struct mempool_s *next; // linked into global mempool list + const char *filename; // file name and line where Mem_AllocPool was called + int fileline; + char name[64]; // name of the pool + uint sentinel2; // should always be MEMHEADER_SENTINEL1 +} mempool_t; + +mempool_t *poolchain = NULL; // critical stuff + +void *_Mem_Alloc( byte *poolptr, size_t size, const char *filename, int fileline ) +{ + int i, j, k, needed, endbit, largest; + memclump_t *clump, **clumpchainpointer; + memheader_t *mem; + mempool_t *pool = (mempool_t *)poolptr; + + if( size <= 0 ) return NULL; + if( poolptr == NULL ) Sys_Error( "Mem_Alloc: pool == NULL (alloc at %s:%i)\n", filename, fileline ); + pool->totalsize += size; + + if( size < 4096 ) + { + // clumping + needed = ( sizeof( memheader_t ) + size + sizeof( int ) + (MEMUNIT - 1)) / MEMUNIT; + endbit = MEMBITS - needed; + for( clumpchainpointer = &pool->clumpchain; *clumpchainpointer; clumpchainpointer = &(*clumpchainpointer)->chain ) + { + clump = *clumpchainpointer; + if( clump->sentinel1 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_Alloc: trashed clump sentinel 1 (alloc at %s:%d)\n", filename, fileline ); + if( clump->sentinel2 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_Alloc: trashed clump sentinel 2 (alloc at %s:%d)\n", filename, fileline ); + if( clump->largestavailable >= needed ) + { + largest = 0; + for( i = 0; i < endbit; i++ ) + { + if( clump->bits[i>>5] & (1 << (i & 31))) + continue; + k = i + needed; + for( j = i; i < k; i++ ) + if( clump->bits[i>>5] & (1 << (i & 31))) + goto loopcontinue; + goto choseclump; +loopcontinue:; + if( largest < j - i ) + largest = j - i; + } + // since clump falsely advertised enough space (nothing wrong + // with that), update largest count to avoid wasting time in + // later allocations + clump->largestavailable = largest; + } + } + + pool->realsize += sizeof( memclump_t ); + clump = malloc( sizeof( memclump_t )); + if( clump == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); + memset( clump, 0, sizeof( memclump_t )); + *clumpchainpointer = clump; + clump->sentinel1 = MEMCLUMP_SENTINEL; + clump->sentinel2 = MEMCLUMP_SENTINEL; + clump->chain = NULL; + clump->blocksinuse = 0; + clump->largestavailable = MEMBITS - needed; + j = 0; +choseclump: + mem = (memheader_t *)((byte *)clump->block + j * MEMUNIT ); + mem->clump = clump; + clump->blocksinuse += needed; + + for( i = j + needed; j < i; j++ ) + clump->bits[j >> 5] |= (1 << (j & 31)); + } + else + { + // big allocations are not clumped + pool->realsize += sizeof( memheader_t ) + size + sizeof( int ); + mem = (memheader_t *)malloc( sizeof( memheader_t ) + size + sizeof( int )); + if( mem == NULL ) Sys_Error( "Mem_Alloc: out of memory (alloc at %s:%i)\n", filename, fileline ); + mem->clump = NULL; + } + + mem->filename = filename; + mem->fileline = fileline; + mem->size = size; + mem->pool = pool; + mem->sentinel1 = MEMHEADER_SENTINEL1; + // we have to use only a single byte for this sentinel, because it may not be aligned + // and some platforms can't use unaligned accesses + *((byte *)mem + sizeof( memheader_t ) + mem->size ) = MEMHEADER_SENTINEL2; + // append to head of list + mem->next = pool->chain; + mem->prev = NULL; + pool->chain = mem; + if( mem->next ) mem->next->prev = mem; + memset((void *)((byte *)mem + sizeof( memheader_t )), 0, mem->size ); + + return (void *)((byte *)mem + sizeof( memheader_t )); +} + +static const char *Mem_CheckFilename( const char *filename ) +{ + static const char *dummy = "\0"; + const char *out = filename; + int i; + + if( !out ) return dummy; + for( i = 0; i < 128; i++, out++ ) + if( out == '\0' ) break; // valid name + if( i == 128 ) return dummy; + return filename; +} + +static void Mem_FreeBlock( memheader_t *mem, const char *filename, int fileline ) +{ + int i, firstblock, endblock; + memclump_t *clump, **clumpchainpointer; + mempool_t *pool; + + if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) + { + mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args + Sys_Error( "Mem_Free: trashed header sentinel 1 (alloc at %s:%i, free at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); + } + + if( *((byte *)mem + sizeof( memheader_t ) + mem->size ) != MEMHEADER_SENTINEL2 ) + { + mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args + Sys_Error( "Mem_Free: trashed header sentinel 2 (alloc at %s:%i, free at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); + } + + pool = mem->pool; + // unlink memheader from doubly linked list + if(( mem->prev ? mem->prev->next != mem : pool->chain != mem ) || ( mem->next && mem->next->prev != mem )) + Sys_Error( "Mem_Free: not allocated or double freed (free at %s:%i)\n", filename, fileline ); + + if( mem->prev ) mem->prev->next = mem->next; + else pool->chain = mem->next; + + if( mem->next ) + mem->next->prev = mem->prev; + + // memheader has been unlinked, do the actual free now + pool->totalsize -= mem->size; + + if(( clump = mem->clump ) != NULL ) + { + if( clump->sentinel1 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_Free: trashed clump sentinel 1 (free at %s:%i)\n", filename, fileline ); + if( clump->sentinel2 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_Free: trashed clump sentinel 2 (free at %s:%i)\n", filename, fileline ); + firstblock = ((byte *)mem - (byte *)clump->block ); + if( firstblock & ( MEMUNIT - 1 )) + Sys_Error( "Mem_Free: address not valid in clump (free at %s:%i)\n", filename, fileline ); + firstblock /= MEMUNIT; + endblock = firstblock + ((sizeof( memheader_t ) + mem->size + sizeof( int ) + (MEMUNIT - 1)) / MEMUNIT ); + clump->blocksinuse -= endblock - firstblock; + + // could use &, but we know the bit is set + for( i = firstblock; i < endblock; i++ ) + clump->bits[i >> 5] -= (1 << (i & 31)); + if( clump->blocksinuse <= 0 ) + { + // unlink from chain + for( clumpchainpointer = &pool->clumpchain; *clumpchainpointer; clumpchainpointer = &(*clumpchainpointer)->chain ) + { + if (*clumpchainpointer == clump) + { + *clumpchainpointer = clump->chain; + break; + } + } + + pool->realsize -= sizeof( memclump_t ); + memset( clump, 0xBF, sizeof( memclump_t )); + free( clump ); + } + else + { + // clump still has some allocations + // force re-check of largest available space on next alloc + clump->largestavailable = MEMBITS - clump->blocksinuse; + } + } + else + { + pool->realsize -= sizeof( memheader_t ) + mem->size + sizeof( int ); + free( mem ); + } +} + +void _Mem_Free( void *data, const char *filename, int fileline ) +{ + if( data == NULL ) Sys_Error( "Mem_Free: data == NULL (called at %s:%i)\n", filename, fileline ); + Mem_FreeBlock((memheader_t *)((byte *)data - sizeof( memheader_t )), filename, fileline ); +} + +void *_Mem_Realloc( byte *poolptr, void *memptr, size_t size, const char *filename, int fileline ) +{ + memheader_t *memhdr = NULL; + char *nb; + + if( size <= 0 ) return memptr; // no need to reallocate + + if( memptr ) + { + memhdr = (memheader_t *)((byte *)memptr - sizeof( memheader_t )); + if( size == memhdr->size ) return memptr; + } + + nb = _Mem_Alloc( poolptr, size, filename, fileline ); + + if( memptr ) // first allocate? + { + size_t newsize = memhdr->size < size ? memhdr->size : size; // upper data can be trucnated! + memcpy( nb, memptr, newsize ); + _Mem_Free( memptr, filename, fileline ); // free unused old block + } + + return (void *)nb; +} + +byte *_Mem_AllocPool( const char *name, const char *filename, int fileline ) +{ + mempool_t *pool; + + pool = (mempool_t *)malloc( sizeof( mempool_t )); + if( pool == NULL ) Sys_Error( "Mem_AllocPool: out of memory (allocpool at %s:%i)\n", filename, fileline ); + memset( pool, 0, sizeof( mempool_t )); + + // fill header + pool->sentinel1 = MEMHEADER_SENTINEL1; + pool->sentinel2 = MEMHEADER_SENTINEL1; + pool->filename = filename; + pool->fileline = fileline; + pool->chain = NULL; + pool->totalsize = 0; + pool->realsize = sizeof( mempool_t ); + Q_strncpy( pool->name, name, sizeof( pool->name )); + pool->next = poolchain; + poolchain = pool; + + return (byte *)pool; +} + +void _Mem_FreePool( byte **poolptr, const char *filename, int fileline ) +{ + mempool_t *pool = (mempool_t *)*poolptr; + mempool_t **chainaddress; + + if( pool ) + { + // unlink pool from chain + for( chainaddress = &poolchain; *chainaddress && *chainaddress != pool; chainaddress = &((*chainaddress)->next)); + if( *chainaddress != pool ) Sys_Error( "Mem_FreePool: pool already free (freepool at %s:%i)\n", filename, fileline ); + if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_FreePool: trashed pool sentinel 1 (allocpool at %s:%i, freepool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_FreePool: trashed pool sentinel 2 (allocpool at %s:%i, freepool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + *chainaddress = pool->next; + + // free memory owned by the pool + while( pool->chain ) Mem_FreeBlock( pool->chain, filename, fileline ); + // free the pool itself + memset( pool, 0xBF, sizeof( mempool_t )); + free( pool ); + *poolptr = NULL; + } +} + +void _Mem_EmptyPool( byte *poolptr, const char *filename, int fileline ) +{ + mempool_t *pool = (mempool_t *)poolptr; + if( poolptr == NULL ) Sys_Error( "Mem_EmptyPool: pool == NULL (emptypool at %s:%i)\n", filename, fileline ); + + if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_EmptyPool: trashed pool sentinel 1 (allocpool at %s:%i, emptypool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) Sys_Error( "Mem_EmptyPool: trashed pool sentinel 2 (allocpool at %s:%i, emptypool at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + + // free memory owned by the pool + while( pool->chain ) Mem_FreeBlock( pool->chain, filename, fileline ); +} + +qboolean Mem_CheckAlloc( mempool_t *pool, void *data ) +{ + memheader_t *header, *target; + + if( pool ) + { + // search only one pool + target = (memheader_t *)((byte *)data - sizeof( memheader_t )); + for( header = pool->chain; header; header = header->next ) + if( header == target ) return true; + } + else + { + // search all pools + for( pool = poolchain; pool; pool = pool->next ) + if( Mem_CheckAlloc( pool, data )) + return true; + } + return false; +} + +/* +======================== +Check pointer for memory +======================== +*/ +qboolean Mem_IsAllocatedExt( byte *poolptr, void *data ) +{ + mempool_t *pool = NULL; + if( poolptr ) pool = (mempool_t *)poolptr; + + return Mem_CheckAlloc( pool, data ); +} + +void Mem_CheckHeaderSentinels( void *data, const char *filename, int fileline ) +{ + memheader_t *mem; + + if( data == NULL ) + Sys_Error( "Mem_CheckSentinels: data == NULL (sentinel check at %s:%i)\n", filename, fileline ); + + mem = (memheader_t *)((byte *) data - sizeof(memheader_t)); + + if( mem->sentinel1 != MEMHEADER_SENTINEL1 ) + { + mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args + Sys_Error( "Mem_CheckSentinels: trashed header sentinel 1 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); + } + + if( *((byte *) mem + sizeof(memheader_t) + mem->size) != MEMHEADER_SENTINEL2 ) + { + mem->filename = Mem_CheckFilename( mem->filename ); // make sure what we don't crash var_args + Sys_Error( "Mem_CheckSentinels: trashed header sentinel 2 (block allocated at %s:%i, sentinel check at %s:%i)\n", mem->filename, mem->fileline, filename, fileline ); + } +} + +static void Mem_CheckClumpSentinels( memclump_t *clump, const char *filename, int fileline ) +{ + // this isn't really very useful + if( clump->sentinel1 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_CheckClumpSentinels: trashed sentinel 1 (sentinel check at %s:%i)\n", filename, fileline ); + if( clump->sentinel2 != MEMCLUMP_SENTINEL ) + Sys_Error( "Mem_CheckClumpSentinels: trashed sentinel 2 (sentinel check at %s:%i)\n", filename, fileline ); +} + +void _Mem_Check( const char *filename, int fileline ) +{ + memheader_t *mem; + mempool_t *pool; + memclump_t *clump; + + for( pool = poolchain; pool; pool = pool->next ) + { + if( pool->sentinel1 != MEMHEADER_SENTINEL1 ) + Sys_Error( "Mem_CheckSentinelsGlobal: trashed pool sentinel 1 (allocpool at %s:%i, sentinel check at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + if( pool->sentinel2 != MEMHEADER_SENTINEL1 ) + Sys_Error( "Mem_CheckSentinelsGlobal: trashed pool sentinel 2 (allocpool at %s:%i, sentinel check at %s:%i)\n", pool->filename, pool->fileline, filename, fileline ); + } + + for( pool = poolchain; pool; pool = pool->next ) + for( mem = pool->chain; mem; mem = mem->next ) + Mem_CheckHeaderSentinels((void *)((byte *) mem + sizeof(memheader_t)), filename, fileline ); + + for( pool = poolchain; pool; pool = pool->next ) + for( clump = pool->clumpchain; clump; clump = clump->chain ) + Mem_CheckClumpSentinels( clump, filename, fileline ); +} + +void Mem_PrintStats( void ) +{ + size_t count = 0, size = 0, realsize = 0; + mempool_t *pool; + + Mem_Check(); + for( pool = poolchain; pool; pool = pool->next ) + { + count++; + size += pool->totalsize; + realsize += pool->realsize; + } + + Con_Printf( "^3%lu^7 memory pools, totalling: ^1%s\n", (dword)count, Q_memprint( size )); + Con_Printf( "total allocated size: ^1%s\n", Q_memprint( realsize )); +} + +void Mem_PrintList( size_t minallocationsize ) +{ + mempool_t *pool; + memheader_t *mem; + + Mem_Check(); + + Con_Printf( "memory pool list:\n"" ^3size name\n"); + for( pool = poolchain; pool; pool = pool->next ) + { + long changed_size = (long)pool->totalsize - (long)pool->lastchecksize; + + // poolnames can contain color symbols, make sure what color is reset + if( changed_size != 0 ) + { + char sign = (changed_size < 0) ? '-' : '+'; + + Con_Printf( "%10s (%10s actual) %s (^7%c%s change)\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), + pool->name, sign, Q_memprint( abs( changed_size ))); + } + else + { + Con_Printf( "%5s (%5s actual) %s\n", Q_memprint( pool->totalsize ), Q_memprint( pool->realsize ), pool->name ); + } + + pool->lastchecksize = pool->totalsize; + for( mem = pool->chain; mem; mem = mem->next ) + if( mem->size >= minallocationsize ) + Con_Printf( "%10s allocated at %s:%i\n", Q_memprint( mem->size ), mem->filename, mem->fileline ); + } +} + +/* +======================== +Memory_Init +======================== +*/ +void Memory_Init( void ) +{ + poolchain = NULL; // init mem chain +} \ No newline at end of file diff --git a/engine/custom.h b/engine/custom.h new file mode 100644 index 00000000..aed6d4a1 --- /dev/null +++ b/engine/custom.h @@ -0,0 +1,93 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef CUSTOM_H +#define CUSTOM_H + +#include "const.h" + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. + +typedef enum +{ + t_sound = 0, + t_skin, + t_model, + t_decal, + t_generic, + t_eventscript, + t_world, // Fake type for world, is really t_model +} resourcetype_t; + +typedef struct +{ + int size; +} _resourceinfo_t; + +typedef struct resourceinfo_s +{ + _resourceinfo_t info[8]; +} resourceinfo_t; + +#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. +#define RES_WASMISSING (1<<1) // Do we have the file locally, did we get it ok? +#define RES_CUSTOM (1<<2) // Is this resource one that corresponds to another player's customization + // or is it a server startup resource. +#define RES_REQUESTED (1<<3) // Already requested a download of this one +#define RES_PRECACHED (1<<4) // Already precached +#define RES_ALWAYS (1<<5) // Download always even if available on client +#define RES_CHECKFILE (1<<7) // Check file on client + +typedef struct resource_s +{ + char szFileName[64]; // File name to download/precache. + resourcetype_t type; // t_sound, t_skin, t_model, t_decal. + int nIndex; // For t_decals + int nDownloadSize; // Size in Bytes if this must be downloaded. + unsigned char ucFlags; + + // for handling client to client resource propagation + unsigned char rgucMD5_hash[16]; // To determine if we already have it. + unsigned char playernum; // Which player index this resource is associated with, + // if it's a custom resource. + + unsigned char rguc_reserved[32]; // For future expansion + struct resource_s *pNext; // Next in chain. + struct resource_s *pPrev; +} resource_t; + +typedef struct customization_s +{ + qboolean bInUse; // Is this customization in use; + resource_t resource; // The resource_t for this customization + qboolean bTranslated; // Has the raw data been translated into a useable format? + // (e.g., raw decal .wad make into texture_t *) + int nUserData1; // Customization specific data + int nUserData2; // Customization specific data + void *pInfo; // Buffer that holds the data structure that references + // the data (e.g., the cachewad_t) + void *pBuffer; // Buffer that holds the data for the customization + // (the raw .wad data) + struct customization_s *pNext; // Next in chain +} customization_t; + +#define FCUST_FROMHPAK ( 1<<0 ) +#define FCUST_WIPEDATA ( 1<<1 ) +#define FCUST_IGNOREINIT ( 1<<2 ) + +#endif // CUSTOM_H \ No newline at end of file diff --git a/engine/customentity.h b/engine/customentity.h new file mode 100644 index 00000000..dc867e5b --- /dev/null +++ b/engine/customentity.h @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef CUSTOMENTITY_H +#define CUSTOMENTITY_H + +// Custom Entities + +// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12) +#define BEAMENT_ENTITY( x ) ((x) & 0xFFF) +#define BEAMENT_ATTACHMENT( x ) (((x)>>12) & 0xF) + +// Beam types, encoded as a byte +enum +{ + BEAM_POINTS = 0, + BEAM_ENTPOINT, + BEAM_ENTS, + BEAM_HOSE, +}; + +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#endif//CUSTOMENTITY_H \ No newline at end of file diff --git a/engine/edict.h b/engine/edict.h new file mode 100644 index 00000000..30ba0810 --- /dev/null +++ b/engine/edict.h @@ -0,0 +1,49 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EDICT_H +#define EDICT_H + +#ifdef SUPPORT_BSP2_FORMAT +#define MAX_ENT_LEAFS 24 // Orignally was 16 +#else +#define MAX_ENT_LEAFS 48 +#endif + +#include "progdefs.h" + +struct edict_s +{ + qboolean free; + int serialnumber; + + link_t area; // linked to a division node or leaf + int headnode; // -1 to use normal leaf check + + int num_leafs; +#ifdef SUPPORT_BSP2_FORMAT + int leafnums[MAX_ENT_LEAFS]; +#else + short leafnums[MAX_ENT_LEAFS]; +#endif + float freetime; // sv.time when the object was freed + + void* pvPrivateData; // Alloced and freed by engine, used by DLLs + entvars_t v; // C exported fields from progs + + // other fields from progs come immediately after +}; + +#endif//EDICT_H \ No newline at end of file diff --git a/engine/eiface.h b/engine/eiface.h new file mode 100644 index 00000000..746e9937 --- /dev/null +++ b/engine/eiface.h @@ -0,0 +1,499 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EIFACE_H +#define EIFACE_H + +#ifdef HLDEMO_BUILD +#define INTERFACE_VERSION 001 +#else // !HLDEMO_BUILD, i.e., regular version of HL +#define INTERFACE_VERSION 140 +#endif // !HLDEMO_BUILD + +#include +#include "custom.h" +#include "cvardef.h" +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT /* */ +#endif + +typedef enum +{ + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). +} ALERT_TYPE; + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum +{ + print_console, + print_center, + print_chat, +} PRINT_TYPE; + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct +{ + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + int iHitgroup; // 0 == generic, non zero is specific body part +} TraceResult; + +// CD audio status +typedef struct +{ + int fPlaying;// is sound playing right now? + int fWasPlaying;// if not, CD is paused if WasPlaying is true. + int fInitialized; + int fEnabled; + int fPlayLooping; + float cdvolume; + int fCDRom; + int fPlayTrack; +} CDStatus; + +typedef unsigned long CRC32_t; + +// Engine hands this to DLLs for functionality callbacks +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel)( char* s ); + int (*pfnPrecacheSound)( char* s ); + void (*pfnSetModel)( edict_t *e, const char *m ); + int (*pfnModelIndex)( const char *m ); + int (*pfnModelFrames)( int modelIndex ); + void (*pfnSetSize)( edict_t *e, const float *rgflMin, const float *rgflMax ); + void (*pfnChangeLevel)( char* s1, char* s2 ); + void (*pfnGetSpawnParms)( edict_t *ent ); + void (*pfnSaveSpawnParms)( edict_t *ent ); + float (*pfnVecToYaw)( const float *rgflVector ); + void (*pfnVecToAngles)( const float *rgflVectorIn, float *rgflVectorOut ); + void (*pfnMoveToOrigin)( edict_t *ent, const float *pflGoal, float dist, int iMoveType ); + void (*pfnChangeYaw)( edict_t* ent ); + void (*pfnChangePitch)( edict_t* ent ); + edict_t* (*pfnFindEntityByString)( edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue ); + int (*pfnGetEntityIllum)( edict_t* pEnt ); + edict_t* (*pfnFindEntityInSphere)( edict_t *pEdictStartSearchAfter, const float *org, float rad ); + edict_t* (*pfnFindClientInPVS)( edict_t *pEdict ); + edict_t* (*pfnEntitiesInPVS)( edict_t *pplayer ); + void (*pfnMakeVectors)( const float *rgflVector ); + void (*pfnAngleVectors)( const float *rgflVector, float *forward, float *right, float *up ); + edict_t* (*pfnCreateEntity)( void ); + void (*pfnRemoveEntity)( edict_t* e ); + edict_t* (*pfnCreateNamedEntity)( int className ); + void (*pfnMakeStatic)( edict_t *ent ); + int (*pfnEntIsOnFloor)( edict_t *e ); + int (*pfnDropToFloor)( edict_t* e ); + int (*pfnWalkMove)( edict_t *ent, float yaw, float dist, int iMode ); + void (*pfnSetOrigin)( edict_t *e, const float *rgflOrigin ); + void (*pfnEmitSound)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch ); + void (*pfnEmitAmbientSound)( edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch ); + void (*pfnTraceLine)( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceToss)( edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr ); + int (*pfnTraceMonsterHull)( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceHull)( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceModel)( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr ); + const char *(*pfnTraceTexture)( edict_t *pTextureEntity, const float *v1, const float *v2 ); + void (*pfnTraceSphere)( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnGetAimVector)( edict_t* ent, float speed, float *rgflReturn ); + void (*pfnServerCommand)( char* str ); + void (*pfnServerExecute)( void ); + void (*pfnClientCommand)( edict_t* pEdict, char* szFmt, ... ); + void (*pfnParticleEffect)( const float *org, const float *dir, float color, float count ); + void (*pfnLightStyle)( int style, char* val ); + int (*pfnDecalIndex)( const char *name ); + int (*pfnPointContents)( const float *rgflVector ); + void (*pfnMessageBegin)( int msg_dest, int msg_type, const float *pOrigin, edict_t *ed ); + void (*pfnMessageEnd)( void ); + void (*pfnWriteByte)( int iValue ); + void (*pfnWriteChar)( int iValue ); + void (*pfnWriteShort)( int iValue ); + void (*pfnWriteLong)( int iValue ); + void (*pfnWriteAngle)( float flValue ); + void (*pfnWriteCoord)( float flValue ); + void (*pfnWriteString)( const char *sz ); + void (*pfnWriteEntity)( int iValue ); + void (*pfnCVarRegister)( cvar_t *pCvar ); + float (*pfnCVarGetFloat)( const char *szVarName ); + const char* (*pfnCVarGetString)( const char *szVarName ); + void (*pfnCVarSetFloat)( const char *szVarName, float flValue ); + void (*pfnCVarSetString)( const char *szVarName, const char *szValue ); + void (*pfnAlertMessage)( ALERT_TYPE atype, char *szFmt, ... ); + void (*pfnEngineFprintf)( FILE *pfile, char *szFmt, ... ); + void* (*pfnPvAllocEntPrivateData)( edict_t *pEdict, long cb ); + void* (*pfnPvEntPrivateData)( edict_t *pEdict ); + void (*pfnFreeEntPrivateData)( edict_t *pEdict ); + const char *(*pfnSzFromIndex)( int iString ); + int (*pfnAllocString)( const char *szValue ); + struct entvars_s *(*pfnGetVarsOfEnt)( edict_t *pEdict ); + edict_t* (*pfnPEntityOfEntOffset)( int iEntOffset ); + int (*pfnEntOffsetOfPEntity)( const edict_t *pEdict ); + int (*pfnIndexOfEdict)( const edict_t *pEdict ); + edict_t* (*pfnPEntityOfEntIndex)( int iEntIndex ); + edict_t* (*pfnFindEntityByVars)( struct entvars_s* pvars ); + void* (*pfnGetModelPtr)( edict_t* pEdict ); + int (*pfnRegUserMsg)( const char *pszName, int iSize ); + void (*pfnAnimationAutomove)( const edict_t* pEdict, float flTime ); + void (*pfnGetBonePosition)( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); + unsigned long (*pfnFunctionFromName)( const char *pName ); + const char *(*pfnNameForFunction)( unsigned long function ); + void (*pfnClientPrintf)( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint)( const char *szMsg ); + const char *(*pfnCmd_Args)( void ); // these 3 added + const char *(*pfnCmd_Argv)( int argc ); // so game DLL can easily + int (*pfnCmd_Argc)( void ); // access client 'cmd' strings + void (*pfnGetAttachment)( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); + void (*pfnCRC32_Init)( CRC32_t *pulCRC ); + void (*pfnCRC32_ProcessBuffer)( CRC32_t *pulCRC, void *p, int len ); + void (*pfnCRC32_ProcessByte)( CRC32_t *pulCRC, unsigned char ch ); + CRC32_t (*pfnCRC32_Final)( CRC32_t pulCRC ); + long (*pfnRandomLong)( long lLow, long lHigh ); + float (*pfnRandomFloat)( float flLow, float flHigh ); + void (*pfnSetView)( const edict_t *pClient, const edict_t *pViewent ); + float (*pfnTime)( void ); + void (*pfnCrosshairAngle)( const edict_t *pClient, float pitch, float yaw ); + byte* (*pfnLoadFileForMe)( char *filename, int *pLength ); + void (*pfnFreeFile)( void *buffer ); + void (*pfnEndSection)( const char *pszSectionName ); // trigger_endsection + int (*pfnCompareFileTime)( char *filename1, char *filename2, int *iCompare ); + void (*pfnGetGameDir)( char *szGetGameDir ); + void (*pfnCvar_RegisterVariable)( cvar_t *variable ); + void (*pfnFadeClientVolume)( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ); + void (*pfnSetClientMaxspeed)( const edict_t *pEdict, float fNewMaxspeed ); + edict_t *(*pfnCreateFakeClient)( const char *netname ); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove)( edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); + int (*pfnNumberOfEntities)( void ); + char* (*pfnGetInfoKeyBuffer)( edict_t *e ); // passing in NULL gets the serverinfo + char* (*pfnInfoKeyValue)( char *infobuffer, char *key ); + void (*pfnSetKeyValue)( char *infobuffer, char *key, char *value ); + void (*pfnSetClientKeyValue)( int clientIndex, char *infobuffer, char *key, char *value ); + int (*pfnIsMapValid)( char *filename ); + void (*pfnStaticDecal)( const float *origin, int decalIndex, int entityIndex, int modelIndex ); + int (*pfnPrecacheGeneric)( char *s ); + int (*pfnGetPlayerUserId)( edict_t *e ); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed ); + int (*pfnIsDedicatedServer)( void ); // is this a dedicated server? + cvar_t *(*pfnCVarGetPointer)( const char *szVarName ); + unsigned int (*pfnGetPlayerWONId)( edict_t *e ); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + // YWB 8/1/99 TFF Physics additions + void (*pfnInfo_RemoveKey)( char *s, const char *key ); + const char *(*pfnGetPhysicsKeyValue)( const edict_t *pClient, const char *key ); + void (*pfnSetPhysicsKeyValue)( const edict_t *pClient, const char *key, const char *value ); + const char *(*pfnGetPhysicsInfoString)( const edict_t *pClient ); + unsigned short (*pfnPrecacheEvent)( int type, const char*psz ); + void (*pfnPlaybackEvent)( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + unsigned char *(*pfnSetFatPVS)( float *org ); + unsigned char *(*pfnSetFatPAS)( float *org ); + + int (*pfnCheckVisibility )( const edict_t *entity, unsigned char *pset ); + + void (*pfnDeltaSetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaUnsetField)( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaAddEncoder)( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); + int (*pfnGetCurrentPlayer)( void ); + int (*pfnCanSkipPlayer)( const edict_t *player ); + int (*pfnDeltaFindField)( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaSetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + void (*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + void (*pfnSetGroupMask)( int mask, int op ); + int (*pfnCreateInstancedBaseline)( int classname, struct entity_state_s *baseline ); + void (*pfnCvar_DirectSet)( struct cvar_s *var, char *value ); + + // Forces the client and server to be running with the same version of the specified file + // ( e.g., a player model ). + // Calling this has no effect in single player + void (*pfnForceUnmodified)( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); + + void (*pfnGetPlayerStats)( const edict_t *pClient, int *ping, int *packet_loss ); + + void (*pfnAddServerCommand)( char *cmd_name, void (*function) (void) ); + + // For voice communications, set which clients hear eachother. + // NOTE: these functions take player entity indices (starting at 1). + qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); + qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); + + void (*pfnUnused1)( void ); + void (*pfnUnused2)( void ); + void (*pfnUnused3)( void ); + void (*pfnUnused4)( void ); + void (*pfnUnused5)( void ); + void (*pfnUnused6)( void ); + void (*pfnUnused7)( void ); + void (*pfnUnused8)( void ); + void (*pfnUnused9)( void ); + void (*pfnUnused10)( void ); + void (*pfnUnused11)( void ); + + // three useable funcs + void (*pfnQueryClientCvarValue)( const edict_t *player, const char *cvarName ); + void (*pfnQueryClientCvarValue2)( const edict_t *player, const char *cvarName, int requestID ); + int (*pfnCheckParm)( char *parm, char **ppnext ); +} enginefuncs_t; +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + long fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +typedef struct +{ + char mapName[32]; + char landmarkName[32]; + edict_t *pentLandmark; + vec3_t vecLandmarkOrigin; +} LEVELLIST; + +typedef struct +{ + int id; // Ordinal ID of this entity (used for entity <--> pointer conversions) + edict_t *pent; // Pointer to the in-game entity + + int location; // Offset from the base data of this entity + int size; // Byte size of this entity's data + int flags; // This could be a short -- bit mask of transitions that this entity is in the PVS of + string_t classname; // entity class name + +} ENTITYTABLE; + +#define MAX_LEVEL_CONNECTIONS 16 // These are encoded in the lower 16bits of ENTITYTABLE->flags + +#define FENTTABLE_PLAYER 0x80000000 +#define FENTTABLE_REMOVED 0x40000000 +#define FENTTABLE_MOVEABLE 0x20000000 +#define FENTTABLE_GLOBAL 0x10000000 + +typedef struct saverestore_s +{ + char *pBaseData; // Start of all entity save data + char *pCurrentData; // Current buffer pointer for sequential access + int size; // Current data size + int bufferSize; // Total space for data + int tokenSize; // Size of the linear list of tokens + int tokenCount; // Number of elements in the pTokens table + char **pTokens; // Hash table of entity strings (sparse) + int currentIndex; // Holds a global entity table ID + int tableCount; // Number of elements in the entity table + int connectionCount; // Number of elements in the levelList[] + ENTITYTABLE *pTable; // Array of ENTITYTABLE elements (1 for each entity) + LEVELLIST levelList[MAX_LEVEL_CONNECTIONS]; // List of connections from this level + + // smooth transition + int fUseLandmark; + char szLandmarkName[20]; // landmark we'll spawn near in next level + vec3_t vecLandmarkOffset; // for landmark transitions + float time; + char szCurrentMapName[32]; // To check global entities +} SAVERESTOREDATA; + +typedef enum _fieldtypes +{ + FIELD_FLOAT = 0, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_ENTITY, // An entity offset (EOFFSET) + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EVARS, // EVARS * + FIELD_EDICT, // edict_t *, or edict_t * (same thing) + FIELD_VECTOR, // Any vector + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level transitions automagically) + FIELD_POINTER, // Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER + FIELD_INTEGER, // Any integer or enum + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + + FIELD_TYPECOUNT, // MUST BE LAST +} FIELDTYPE; + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +#define _FIELD(type,name,fieldtype,count,flags) { fieldtype, #name, offsetof(type, name), count, flags } +#define DEFINE_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, 0) +#define DEFINE_ARRAY(type,name,fieldtype,count) _FIELD(type, name, fieldtype, count, 0) +#define DEFINE_ENTITY_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, 0 ) +#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL ) +#define DEFINE_GLOBAL_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL ) + +#define FTYPEDESC_GLOBAL 0x0001 // This field is masked for global entity save/restore +#define FTYPEDESC_SAVE 0x0002 // This field is saved to disk +#define FTYPEDESC_KEY 0x0004 // This field can be requested and written to by string name at load time +#define FTYPEDESC_FUNCTIONTABLE 0x0008 // This is a table entry for a member function pointer + +typedef struct +{ + FIELDTYPE fieldType; + char *fieldName; + int fieldOffset; + short fieldSize; + short flags; +} TYPEDESCRIPTION; + +#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit)( void ); + int (*pfnSpawn)( edict_t *pent ); + void (*pfnThink)( edict_t *pent ); + void (*pfnUse)( edict_t *pentUsed, edict_t *pentOther ); + void (*pfnTouch)( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnBlocked)( edict_t *pentBlocked, edict_t *pentOther ); + void (*pfnKeyValue)( edict_t *pentKeyvalue, KeyValueData *pkvd ); + void (*pfnSave)( edict_t *pent, SAVERESTOREDATA *pSaveData ); + int (*pfnRestore)( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); + void (*pfnSetAbsBox)( edict_t *pent ); + + void (*pfnSaveWriteFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int ); + void (*pfnSaveReadFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int ); + void (*pfnSaveGlobalState)( SAVERESTOREDATA * ); + void (*pfnRestoreGlobalState)( SAVERESTOREDATA * ); + void (*pfnResetGlobalState)( void ); + + qboolean (*pfnClientConnect)( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128] ); + + void (*pfnClientDisconnect)( edict_t *pEntity ); + void (*pfnClientKill)( edict_t *pEntity ); + void (*pfnClientPutInServer)( edict_t *pEntity ); + void (*pfnClientCommand)( edict_t *pEntity ); + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + void (*pfnServerActivate)( edict_t *pEdictList, int edictCount, int clientMax ); + void (*pfnServerDeactivate)( void ); + void (*pfnPlayerPreThink)( edict_t *pEntity ); + void (*pfnPlayerPostThink)( edict_t *pEntity ); + + void (*pfnStartFrame)( void ); + void (*pfnParmsNewLevel)( void ); + void (*pfnParmsChangeLevel)( void ); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription)( void ); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization)( edict_t *pEntity, customization_t *pCustom ); + + // Spectator funcs + void (*pfnSpectatorConnect)( edict_t *pEntity ); + void (*pfnSpectatorDisconnect)( edict_t *pEntity ); + void (*pfnSpectatorThink)( edict_t *pEntity ); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error)( const char *error_string ); + + void (*pfnPM_Move)( struct playermove_s *ppmove, qboolean server ); + void (*pfnPM_Init)( struct playermove_s *ppmove ); + char (*pfnPM_FindTextureType)( char *name ); + void (*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas ); + void (*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); + int (*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); + void (*pfnCreateBaseline)( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); + void (*pfnRegisterEncoders)( void ); + int (*pfnGetWeaponData)( struct edict_s *player, struct weapon_data_s *info ); + + void (*pfnCmdStart)( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); + void (*pfnCmdEnd)( const edict_t *player ); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket )( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) ( int hullnumber, float *mins, float *maxs ); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) ( void ); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message ); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation)( void ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +// Current version. +#define NEW_DLL_FUNCTIONS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData)( edict_t *pEnt ); + void (*pfnGameShutdown)(void); + int (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnCvarValue)( const edict_t *pEnt, const char *value ); + void (*pfnCvarValue2)( const edict_t *pEnt, int requestID, const char *cvarName, const char *value ); +} NEW_DLL_FUNCTIONS; +typedef int (*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// Pointers will be null if the game DLL doesn't support this API. +extern NEW_DLL_FUNCTIONS gNewDLLFunctions; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +typedef int (*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +#endif//EIFACE_H \ No newline at end of file diff --git a/engine/keydefs.h b/engine/keydefs.h new file mode 100644 index 00000000..ea22139f --- /dev/null +++ b/engine/keydefs.h @@ -0,0 +1,133 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef KEYDEFS_H +#define KEYDEFS_H + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 +#define K_KP_RIGHTARROW 165 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 +#define K_CAPSLOCK 175 +#define K_KP_NUMLOCK 176 + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 241 +#define K_MOUSE2 242 +#define K_MOUSE3 243 +#define K_MOUSE4 244 +#define K_MOUSE5 245 + +#endif//KEYDEFS_H \ No newline at end of file diff --git a/engine/menu_int.h b/engine/menu_int.h new file mode 100644 index 00000000..bbc93ab0 --- /dev/null +++ b/engine/menu_int.h @@ -0,0 +1,189 @@ +/* +menu_int.h - interface between engine and menu +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef MENU_INT_H +#define MENU_INT_H + +#include "cvardef.h" +#include "gameinfo.h" +#include "wrect.h" + +typedef int HIMAGE; // handle to a graphic + +// flags for PIC_Load +#define PIC_NEAREST (1<<0) // disable texfilter +#define PIC_KEEP_SOURCE (1<<1) // some images keep source +#define PIC_NOFLIP_TGA (1<<2) // Steam background completely ignore tga attribute 0x20 + +typedef struct ui_globalvars_s +{ + float time; // unclamped host.realtime + float frametime; + + int scrWidth; // actual values + int scrHeight; + + int maxClients; + int allow_console; + int demoplayback; + int demorecording; + char demoname[64]; // name of currently playing demo + char maptitle[64]; // title of active map +} ui_globalvars_t; + +typedef struct ui_enginefuncs_s +{ + // image handlers + HIMAGE (*pfnPIC_Load)( const char *szPicName, const byte *ucRawImage, long ulRawImageSize, long flags ); + void (*pfnPIC_Free)( const char *szPicName ); + int (*pfnPIC_Width)( HIMAGE hPic ); + int (*pfnPIC_Height)( HIMAGE hPic ); + void (*pfnPIC_Set)( HIMAGE hPic, int r, int g, int b, int a ); + void (*pfnPIC_Draw)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawHoles)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawTrans)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawAdditive)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_EnableScissor)( int x, int y, int width, int height ); + void (*pfnPIC_DisableScissor)( void ); + + // screen handlers + void (*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a ); + + // cvar handlers + cvar_t* (*pfnRegisterVariable)( const char *szName, const char *szValue, int flags ); + float (*pfnGetCvarFloat)( const char *szName ); + char* (*pfnGetCvarString)( const char *szName ); + void (*pfnCvarSetString)( const char *szName, const char *szValue ); + void (*pfnCvarSetValue)( const char *szName, float flValue ); + + // command handlers + int (*pfnAddCommand)( const char *cmd_name, void (*function)(void) ); + void (*pfnClientCmd)( int execute_now, const char *szCmdString ); + void (*pfnDelCommand)( const char *cmd_name ); + int (*pfnCmdArgc)( void ); + char* (*pfnCmdArgv)( int argc ); + char* (*pfnCmd_Args)( void ); + + // debug messages (in-menu shows only notify) + void (*Con_Printf)( char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_NPrintf)( int pos, char *fmt, ... ); + void (*Con_NXPrintf)( struct con_nprint_s *info, char *fmt, ... ); + + // sound handlers + void (*pfnPlayLocalSound)( const char *szSound ); + + // cinematic handlers + void (*pfnDrawLogo)( const char *filename, float x, float y, float width, float height ); + int (*pfnGetLogoWidth)( void ); + int (*pfnGetLogoHeight)( void ); + float (*pfnGetLogoLength)( void ); // cinematic duration in seconds + + // text message system + void (*pfnDrawCharacter)( int x, int y, int width, int height, int ch, int ulRGBA, HIMAGE hFont ); + int (*pfnDrawConsoleString)( int x, int y, const char *string ); + void (*pfnDrawSetTextColor)( int r, int g, int b, int alpha ); + void (*pfnDrawConsoleStringLen)( const char *string, int *length, int *height ); + void (*pfnSetConsoleDefaultColor)( int r, int g, int b ); // color must came from colors.lst + + // custom rendering (for playermodel preview) + struct cl_entity_s* (*pfnGetPlayerModel)( void ); // for drawing playermodel previews + void (*pfnSetModel)( struct cl_entity_s *ed, const char *path ); + void (*pfnClearScene)( void ); + void (*pfnRenderScene)( const struct ref_viewpass_s *rvp ); + int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); + + // misc handlers + void (*pfnHostError)( const char *szFmt, ... ); + int (*pfnFileExists)( const char *filename, int gamedironly ); + void (*pfnGetGameDir)( char *szGetGameDir ); + + // gameinfo handlers + int (*pfnCreateMapsList)( int fRefresh ); + int (*pfnClientInGame)( void ); + void (*pfnClientJoin)( const struct netadr_s adr ); + + // parse txt files + byte* (*COM_LoadFile)( const char *filename, int *pLength ); + char* (*COM_ParseFile)( char *data, char *token ); + void (*COM_FreeFile)( void *buffer ); + + // keyfuncs + void (*pfnKeyClearStates)( void ); // call when menu open or close + void (*pfnSetKeyDest)( int dest ); + const char *(*pfnKeynumToString)( int keynum ); + const char *(*pfnKeyGetBinding)( int keynum ); + void (*pfnKeySetBinding)( int keynum, const char *binding ); + int (*pfnKeyIsDown)( int keynum ); + int (*pfnKeyGetOverstrikeMode)( void ); + void (*pfnKeySetOverstrikeMode)( int fActive ); + void *(*pfnKeyGetState)( const char *name ); // for mlook, klook etc + + // engine memory manager + void* (*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // collect info from engine + int (*pfnGetGameInfo)( GAMEINFO *pgameinfo ); + GAMEINFO **(*pfnGetGamesList)( int *numGames ); // collect info about all mods + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); // find in files + int (*pfnGetSaveComment)( const char *savename, char *comment ); + int (*pfnGetDemoComment)( const char *demoname, char *comment ); + int (*pfnCheckGameDll)( void ); // returns false if hl.dll is missed or invalid + char *(*pfnGetClipboardData)( void ); + + // engine launcher + void (*pfnShellExecute)( const char *name, const char *args, int closeEngine ); + void (*pfnWriteServerConfig)( const char *name ); + void (*pfnChangeInstance)( const char *newInstance, const char *szFinalMessage ); + void (*pfnPlayBackgroundTrack)( const char *introName, const char *loopName ); + void (*pfnHostEndGame)( const char *szFinalMessage ); + + // menu interface is freezed at version 0.75 + // new functions starts here + float (*pfnRandomFloat)( float flLow, float flHigh ); + long (*pfnRandomLong)( long lLow, long lHigh ); + + void (*pfnSetCursor)( void *hCursor ); // change cursor + int (*pfnIsMapValid)( char *filename ); + void (*pfnProcessImage)( int texnum, float gamma, int topColor, int bottomColor ); + int (*pfnCompareFileTime)( char *filename1, char *filename2, int *iCompare ); + + const char *(*pfnGetModeString)( int vid_mode ); +} ui_enginefuncs_t; + +typedef struct +{ + int (*pfnVidInit)( void ); + void (*pfnInit)( void ); + void (*pfnShutdown)( void ); + void (*pfnRedraw)( float flTime ); + void (*pfnKeyEvent)( int key, int down ); + void (*pfnMouseMove)( int x, int y ); + void (*pfnSetActiveMenu)( int active ); + void (*pfnAddServerToList)( struct netadr_s adr, const char *info ); + void (*pfnGetCursorPos)( int *pos_x, int *pos_y ); + void (*pfnSetCursorPos)( int pos_x, int pos_y ); + void (*pfnShowCursor)( int show ); + void (*pfnCharEvent)( int key ); + int (*pfnMouseInRect)( void ); // mouse entering\leave game window + int (*pfnIsVisible)( void ); + int (*pfnCreditsActive)( void ); // unused + void (*pfnFinalCredits)( void ); // show credits + game end +} UI_FUNCTIONS; + +typedef int (*MENUAPI)( UI_FUNCTIONS *pFunctionTable, ui_enginefuncs_t* engfuncs, ui_globalvars_t *pGlobals ); + +#endif//MENU_INT_H \ No newline at end of file diff --git a/engine/physint.h b/engine/physint.h new file mode 100644 index 00000000..654c09fe --- /dev/null +++ b/engine/physint.h @@ -0,0 +1,164 @@ +/* +physint.h - Server Physics Interface +Copyright (C) 2011 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef PHYSINT_H +#define PHYSINT_H + +#define SV_PHYSICS_INTERFACE_VERSION 6 + +#define STRUCT_FROM_LINK( l, t, m ) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define EDICT_FROM_AREA( l ) STRUCT_FROM_LINK( l, edict_t, area ) + +// values that can be returned with pfnServerState +#define SERVER_DEAD 0 +#define SERVER_LOADING 1 +#define SERVER_ACTIVE 2 + +// LUMP reading errors +#define LUMP_LOAD_OK 0 +#define LUMP_LOAD_COULDNT_OPEN 1 +#define LUMP_LOAD_BAD_HEADER 2 +#define LUMP_LOAD_BAD_VERSION 3 +#define LUMP_LOAD_NO_EXTRADATA 4 +#define LUMP_LOAD_INVALID_NUM 5 +#define LUMP_LOAD_NOT_EXIST 6 +#define LUMP_LOAD_MEM_FAILED 7 +#define LUMP_LOAD_CORRUPTED 8 + +// LUMP saving errors +#define LUMP_SAVE_OK 0 +#define LUMP_SAVE_COULDNT_OPEN 1 +#define LUMP_SAVE_BAD_HEADER 2 +#define LUMP_SAVE_BAD_VERSION 3 +#define LUMP_SAVE_NO_EXTRADATA 4 +#define LUMP_SAVE_INVALID_NUM 5 +#define LUMP_SAVE_ALREADY_EXIST 6 +#define LUMP_SAVE_NO_DATA 7 +#define LUMP_SAVE_CORRUPTED 8 + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t trigger_edicts; + link_t solid_edicts; + link_t portal_edicts; +} areanode_t; + +typedef struct server_physics_api_s +{ + // unlink edict from old position and link onto new + void ( *pfnLinkEdict) ( edict_t *ent, qboolean touch_triggers ); + double ( *pfnGetServerTime )( void ); // unclamped + double ( *pfnGetFrameTime )( void ); // unclamped + void* ( *pfnGetModel )( int modelindex ); + areanode_t* ( *pfnGetHeadnode )( void ); // AABB tree for all physic entities + int ( *pfnServerState )( void ); + void ( *pfnHost_Error )( const char *error, ... ); // cause Host Error +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6 + struct triangleapi_s *pTriAPI; // draw coliisions etc. Only for local system + + // draw debug messages (must be called from DrawOrthoTriangles). Only for local system + int ( *pfnDrawConsoleString )( int x, int y, char *string ); + void ( *pfnDrawSetTextColor )( float r, float g, float b ); + void ( *pfnDrawConsoleStringLen )( const char *string, int *length, int *height ); + void ( *Con_NPrintf )( int pos, char *fmt, ... ); + void ( *Con_NXPrintf )( struct con_nprint_s *info, char *fmt, ... ); + const char *( *pfnGetLightStyle )( int style ); // read custom appreance for selected lightstyle + void ( *pfnUpdateFogSettings )( unsigned int packed_fog ); + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); + struct msurface_s *(*pfnTraceSurface)( edict_t *pTextureEntity, const float *v1, const float *v2 ); + const byte *(*pfnGetTextureData)( unsigned int texnum ); + + // static allocations + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // trace & contents + int (*pfnMaskPointContents)( const float *pos, int groupmask ); + trace_t (*pfnTrace)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e ); + trace_t (*pfnTraceNoEnts)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e ); + int (*pfnBoxInPVS)( const float *org, const float *boxmins, const float *boxmaxs ); + + // message handler (missed function to write raw bytes) + void (*pfnWriteBytes)( byte *bytes, int count ); + + // BSP lump management + int (*pfnCheckLump)( const char *filename, const int lump, int *lumpsize ); + int (*pfnReadLump)( const char *filename, const int lump, void **lumpdata, int *lumpsize ); + int (*pfnSaveLump)( const char *filename, const int lump, void *lumpdata, int lumpsize ); + + // FS tools + int (*pfnSaveFile)( const char *filename, const void *data, long len ); + const byte *(*pfnLoadImagePixels)( const char *filename, int *width, int *height ); + + const char* (*pfnGetModelName)( int modelindex ); +} server_physics_api_t; + +// physic callbacks +typedef struct physics_interface_s +{ + int version; + // passed through pfnCreate (0 is attempt to create, -1 is reject) + int ( *SV_CreateEntity )( edict_t *pent, const char *szName ); + // run custom physics for each entity (return 0 to use built-in engine physic) + int ( *SV_PhysicsEntity )( edict_t *pEntity ); + // spawn entities with internal mod function e.g. for re-arrange spawn order (0 - use engine parser, 1 - use mod parser) + int ( *SV_LoadEntities )( const char *mapname, char *entities ); + // update conveyor belt for clients + void ( *SV_UpdatePlayerBaseVelocity )( edict_t *ent ); + // The game .dll should return 1 if save game should be allowed + int ( *SV_AllowSaveGame )( void ); +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6 + // override trigger area checking and touching + int ( *SV_TriggerTouch )( edict_t *pent, edict_t *trigger ); + // some engine features can be enabled only through this function + unsigned int ( *SV_CheckFeatures )( void ); + // used for draw debug collisions for custom physic engine etc + void ( *DrawDebugTriangles )( void ); + // used for draw debug overlay (textured) + void ( *DrawNormalTriangles )( void ); + // used for draw debug messages (2d mode) + void ( *DrawOrthoTriangles )( void ); + // tracing entities with SOLID_CUSTOM mode on a server (not used by pmove code) + void ( *ClipMoveToEntity)( edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace ); + // tracing entities with SOLID_CUSTOM mode on a server (only used by pmove code) + void ( *ClipPMoveToEntity)( struct physent_s *pe, const float *start, float *mins, float *maxs, const float *end, struct pmtrace_s *tr ); + // called at end the frame of SV_Physics call + void ( *SV_EndFrame )( void ); + // obsolete + void (*pfnReserved)( void ); + // called through save\restore process + void (*pfnCreateEntitiesInRestoreList)( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world ); + // allocate custom string (e.g. using user implementation of stringtable, not engine strings) + string_t (*pfnAllocString)( const char *szValue ); + // make custom string (e.g. using user implementation of stringtable, not engine strings) + string_t (*pfnMakeString)( const char *szValue ); + // read custom string (e.g. using user implementation of stringtable, not engine strings) + const char* (*pfnGetString)( string_t iString ); + // helper for restore custom decals that have custom message (e.g. Paranoia) + int (*pfnRestoreDecal)( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent ); + // handle custom trigger touching for player + void (*PM_PlayerTouch)( struct playermove_s *ppmove, edict_t *client ); + // alloc or destroy model custom data (called only for dedicated servers, otherwise using an client version) + void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer ); + // select BSP-hull for trace with specified mins\maxs + void *(*SV_HullForBsp)( edict_t *ent, const float *mins, const float *maxs, float *offset ); + // handle player custom think function + int (*SV_PlayerThink)( edict_t *ent, float frametime, double time ); +} physics_interface_t; + +#endif//PHYSINT_H \ No newline at end of file diff --git a/engine/progdefs.h b/engine/progdefs.h new file mode 100644 index 00000000..74004a27 --- /dev/null +++ b/engine/progdefs.h @@ -0,0 +1,218 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PROGDEFS_H +#define PROGDEFS_H + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int changelevel; // transition in progress when true (was msg_entity) + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + + void *pSaveData; // (SAVERESTOREDATA *) pointer + vec3_t vecLandmarkOffset; +} globalvars_t; + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + + string_t model; + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprites and models rendering scale (0..255) + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + +#endif//PROGDEFS_H \ No newline at end of file diff --git a/engine/server/server.h b/engine/server/server.h new file mode 100644 index 00000000..b4966c5a --- /dev/null +++ b/engine/server/server.h @@ -0,0 +1,684 @@ +/* +server.h - primary header for server +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#ifndef SERVER_H +#define SERVER_H + +#include "mathlib.h" +#include "edict.h" +#include "eiface.h" +#include "physint.h" // physics interface +#include "mod_local.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "entity_state.h" +#include "protocol.h" +#include "netchan.h" +#include "custom.h" +#include "world.h" + +//============================================================================= + +#define SV_UPDATE_MASK (SV_UPDATE_BACKUP - 1) +extern int SV_UPDATE_BACKUP; + +// hostflags +#define SVF_SKIPLOCALHOST BIT( 0 ) +#define SVF_MERGE_VISIBILITY BIT( 1 ) // we are do portal pass + +// mapvalid flags +#define MAP_IS_EXIST BIT( 0 ) +#define MAP_HAS_SPAWNPOINT BIT( 1 ) +#define MAP_HAS_LANDMARK BIT( 2 ) +#define MAP_INVALID_VERSION BIT( 3 ) + +#define SV_SPAWN_TIME 0.1 + +// group flags +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +#ifdef NDEBUG +#define SV_IsValidEdict( e ) ( e && !e->free ) +#else +#define SV_IsValidEdict( e ) SV_CheckEdict( e, __FILE__, __LINE__ ) +#endif +#define NUM_FOR_EDICT(e) ((int)((edict_t *)(e) - svgame.edicts)) +#define EDICT_NUM( num ) SV_EdictNum( num ) +#define STRING( offset ) SV_GetString( offset ) +#define ALLOC_STRING(str) SV_AllocString( str ) +#define MAKE_STRING(str) SV_MakeString( str ) + +#define MAX_PUSHED_ENTS 256 +#define MAX_VIEWENTS 128 + +#define FCL_RESEND_USERINFO BIT( 0 ) +#define FCL_RESEND_MOVEVARS BIT( 1 ) +#define FCL_SKIP_NET_MESSAGE BIT( 2 ) +#define FCL_SEND_NET_MESSAGE BIT( 3 ) +#define FCL_PREDICT_MOVEMENT BIT( 4 ) // movement prediction is enabled +#define FCL_LOCAL_WEAPONS BIT( 5 ) // weapon prediction is enabled +#define FCL_LAG_COMPENSATION BIT( 6 ) // lag compensation is enabled +#define FCL_FAKECLIENT BIT( 7 ) // this client is a fake player controlled by the game DLL +#define FCL_HLTV_PROXY BIT( 8 ) // this is a proxy for a HLTV client (spectator) +#define FCL_SEND_RESOURCES BIT( 9 ) +#define FCL_FORCE_UNMODIFIED BIT( 10 ) + +typedef enum +{ + ss_dead, // no map loaded + ss_loading, // spawning level edicts + ss_active // actively running +} sv_state_t; + +typedef enum +{ + cs_free = 0, // can be reused for a new connection + cs_zombie, // client has been disconnected, but don't reuse connection for a couple seconds + cs_connected, // has been assigned to a sv_client_t, but not in game yet + cs_spawned // client is fully in game +} cl_state_t; + +typedef enum +{ + us_inactive = 0, + us_processing, + us_complete, +} cl_upload_t; + +// instanced baselines container +typedef struct +{ + const char *classname; + entity_state_t baseline; +} sv_baseline_t; + +typedef struct +{ + qboolean active; + qboolean net_log; + netadr_t net_address; + file_t *file; +} server_log_t; + +// like as entity_state_t in Quake +typedef struct +{ + char model[MAX_QPATH]; // name of static-entity model for right precache + vec3_t origin; + vec3_t angles; + short sequence; + short frame; + short colormap; + byte skin; // can't set contents! only real skin! + byte body; + float scale; + byte rendermode; + byte renderamt; + color24 rendercolor; + byte renderfx; +} sv_static_entity_t; + +typedef struct server_s +{ + sv_state_t state; // precache commands are only valid during load + + qboolean background; // this is background map + qboolean loadgame; // client begins should reuse existing entity + double time; // sv.time += sv.frametime + double time_residual; // unclamped + float frametime; // 1.0 / sv_fps->value + int framecount; // count physic frames + struct sv_client_s *current_client; // current client who network message sending on + + int hostflags; // misc server flags: predicting etc + CRC32_t worldmapCRC; // check crc for catch cheater maps + int progsCRC; // this is used with feature ENGINE_QUAKE_COMPATIBLE + + char name[MAX_QPATH]; // map name + char startspot[MAX_QPATH]; + + double lastchecktime; + int lastcheck; // number of last checked client + + char model_precache[MAX_MODELS][MAX_QPATH]; + char sound_precache[MAX_SOUNDS][MAX_QPATH]; + char files_precache[MAX_CUSTOM][MAX_QPATH]; + char event_precache[MAX_EVENTS][MAX_QPATH]; + byte model_precache_flags[MAX_MODELS]; + model_t *models[MAX_MODELS]; + + sv_static_entity_t static_entities[MAX_STATIC_ENTITIES]; + int num_static_entities; + + // run local lightstyles to let SV_LightPoint grab the actual information + lightstyle_t lightstyles[MAX_LIGHTSTYLES]; + + consistency_t consistency_list[MAX_MODELS]; + resource_t resources[MAX_RESOURCES]; + int num_consistency; // typically check model bounds on this + int num_resources; + + sv_baseline_t instanced[MAX_CUSTOM_BASELINES]; // instanced baselines + int last_valid_baseline;// all the entities with number more than that was created in-game and doesn't have the baseline + int num_instanced; + + // unreliable data to send to clients. + sizebuf_t datagram; + byte datagram_buf[MAX_DATAGRAM]; + + // reliable data to send to clients. + sizebuf_t reliable_datagram; // copied to all clients at end of frame + byte reliable_datagram_buf[MAX_DATAGRAM]; + + // the multicast buffer is used to send a message to a set of clients + sizebuf_t multicast; + byte multicast_buf[MAX_MULTICAST]; + + sizebuf_t signon; + byte signon_buf[MAX_INIT_MSG]; // need a get to maximum size + + sizebuf_t spec_datagram; + byte spectator_buf[MAX_MULTICAST]; + + model_t *worldmodel; // pointer to world + + qboolean simulating; + qboolean playersonly; + qboolean paused; + + // statistics + int ignored_static_ents; + int ignored_world_decals; + int static_ents_overflow; +} server_t; + +typedef struct +{ + double senttime; + float ping_time; + + clientdata_t clientdata; + weapon_data_t weapondata[MAX_LOCAL_WEAPONS]; + + int num_entities; + int first_entity; // into the circular sv_packet_entities[] +} client_frame_t; + +typedef struct sv_client_s +{ + cl_state_t state; + cl_upload_t upstate; // uploading state + char name[32]; // extracted from userinfo, color string allowed + int flags; // client flags, some info + CRC32_t crcValue; + + char userinfo[MAX_INFO_STRING]; // name, etc (received from client) + char physinfo[MAX_INFO_STRING]; // set on server (transmit to client) + + netchan_t netchan; + int chokecount; // number of messages rate supressed + int delta_sequence; // -1 = no compression. + + double next_messagetime; // time when we should send next world state update + double next_checkpingtime; // time to send all players pings to client + double next_sendinfotime; // time to send info about all players + double cl_updaterate; // client requested updaterate + double timebase; // client timebase + double lastservertime; // check if server time was not changed so no resaon to send update + double connection_started; + + char hashedcdkey[34]; // MD5 hash is 32 hex #'s, plus trailing 0 + + customization_t customdata; // player customization linked list + resource_t resourcesonhand; + resource_t resourcesneeded; // from client (server downloading) + usercmd_t lastcmd; // for filling in big drops + + double connecttime; + double cmdtime; + double ignorecmdtime; + + int packet_loss; + float latency; + + int ignored_ents; // if visibility list is full we should know how many entities will be ignored + edict_t *edict; // EDICT_NUM(clientnum+1) + edict_t *pViewEntity; // svc_setview member + edict_t *viewentity[MAX_VIEWENTS]; // list of portal cameras in player PVS + int num_viewents; // num of portal cameras that can merge PVS + + qboolean m_bLoopback; // Does this client want to hear his own voice? + uint listeners; // which other clients does this guy's voice stream go to? + + // the datagram is written to by sound calls, prints, temp ents, etc. + // it can be harmlessly overflowed. + sizebuf_t datagram; + byte datagram_buf[MAX_DATAGRAM]; + + client_frame_t *frames; // updates can be delta'd from here + event_state_t events; // delta-updated events cycle + + int challenge; // challenge of this user, randomly generated + int userid; // identifying number on server +} sv_client_t; + +/* +============================================================================= + a client can leave the server in one of four ways: + dropping properly by quiting or disconnecting + timing out if no valid messages are received for timeout.value seconds + getting kicked off by the server operator + a program error, like an overflowed reliable buffer +============================================================================= +*/ + +// MAX_CHALLENGES is made large to prevent a denial +// of service attack that could cycle all of them +// out before legitimate users connected +#define MAX_CHALLENGES 1024 + +typedef struct +{ + netadr_t adr; + double time; + int challenge; + qboolean connected; +} challenge_t; + +typedef struct +{ + char name[32]; // in GoldSrc max name length is 12 + int number; // svc_ number + int size; // if size == -1, size come from first byte after svcnum +} sv_user_message_t; + +typedef struct +{ + edict_t *ent; + vec3_t origin; + vec3_t angles; + int fixangle; +} sv_pushed_t; + +typedef struct +{ + qboolean active; + qboolean moving; + qboolean firstframe; + qboolean nointerp; + + vec3_t mins; + vec3_t maxs; + + vec3_t curpos; + vec3_t oldpos; + vec3_t newpos; + vec3_t finalpos; +} sv_interp_t; + +typedef struct +{ + // user messages stuff + const char *msg_name; // just for debug + sv_user_message_t msg[MAX_USER_MESSAGES]; // user messages array + int msg_size_index; // write message size at this pos in bitbuf + int msg_realsize; // left in bytes + int msg_index; // for debug messages + int msg_dest; // msg destination ( MSG_ONE, MSG_ALL etc ) + qboolean msg_started; // to avoid recursive included messages + edict_t *msg_ent; // user message member entity + vec3_t msg_org; // user message member origin + + void *hInstance; // pointer to game.dll + qboolean config_executed; // should to execute config.cfg once time to restore FCVAR_ARCHIVE that specified in hl.dll + + edict_t *edicts; // solid array of server entities + int numEntities; // actual entities count + + movevars_t movevars; // movement variables curstate + movevars_t oldmovevars; // movement variables oldstate + playermove_t *pmove; // pmove state + sv_interp_t interp[MAX_CLIENTS]; // interpolate clients + sv_pushed_t pushed[MAX_PUSHED_ENTS]; // no reason to keep array for all edicts + // 256 it should be enough for any game situation + + globalvars_t *globals; // server globals + + DLL_FUNCTIONS dllFuncs; // dll exported funcs + NEW_DLL_FUNCTIONS dllFuncs2; // new dll exported funcs (may be NULL) + physics_interface_t physFuncs; // physics interface functions (Xash3D extension) + + byte *mempool; // server premamnent pool: edicts etc + byte *stringspool; // for engine strings +} svgame_static_t; + +typedef struct +{ + qboolean initialized; // sv_init has completed + double timestart; // just for profiling + + int maxclients; // server max clients + + int groupmask; + int groupop; + + server_log_t log; + + char serverinfo[MAX_SERVERINFO_STRING]; + char localinfo[MAX_LOCALINFO_STRING]; + + int spawncount; // incremented each server start + // used to check late spawns + sv_client_t *clients; // [svs.maxclients] + int num_client_entities; // svs.maxclients*UPDATE_BACKUP*MAX_PACKET_ENTITIES + int next_client_entities; // next client_entity to use + entity_state_t *packet_entities; // [num_client_entities] + entity_state_t *baselines; // [GI->max_edicts] + + double last_heartbeat; + challenge_t challenges[MAX_CHALLENGES]; // to prevent invalid IPs from connecting +} server_static_t; + +//============================================================================= + +extern server_static_t svs; // persistant server info +extern server_t sv; // local server +extern svgame_static_t svgame; // persistant game info +extern areanode_t sv_areanodes[]; // AABB dynamic tree + +extern convar_t mp_logecho; +extern convar_t mp_logfile; +extern convar_t sv_unlag; +extern convar_t sv_maxunlag; +extern convar_t sv_unlagpush; +extern convar_t sv_unlagsamples; +extern convar_t rcon_password; +extern convar_t sv_instancedbaseline; +extern convar_t sv_minupdaterate; +extern convar_t sv_maxupdaterate; +extern convar_t sv_downloadurl; +extern convar_t sv_newunit; +extern convar_t sv_clienttrace; +extern convar_t sv_failuretime; +extern convar_t sv_send_resources; +extern convar_t sv_send_logos; +extern convar_t sv_allow_upload; +extern convar_t sv_allow_download; +extern convar_t sv_airaccelerate; +extern convar_t sv_accelerate; +extern convar_t sv_friction; +extern convar_t sv_edgefriction; +extern convar_t sv_gravity; +extern convar_t sv_stopspeed; +extern convar_t sv_maxspeed; +extern convar_t sv_wateralpha; +extern convar_t sv_wateramp; +extern convar_t sv_stepsize; +extern convar_t sv_maxvelocity; +extern convar_t sv_rollangle; +extern convar_t sv_rollspeed; +extern convar_t sv_skyname; +extern convar_t sv_skycolor_r; +extern convar_t sv_skycolor_g; +extern convar_t sv_skycolor_b; +extern convar_t sv_skyvec_x; +extern convar_t sv_skyvec_y; +extern convar_t sv_skyvec_z; +extern convar_t sv_consistency; +extern convar_t sv_password; +extern convar_t sv_uploadmax; +extern convar_t deathmatch; +extern convar_t hostname; +extern convar_t skill; +extern convar_t coop; + +extern convar_t *sv_pausable; // allows pause in multiplayer +extern convar_t *sv_check_errors; +extern convar_t *sv_reconnect_limit; +extern convar_t *sv_lighting_modulate; +extern convar_t *sv_novis; +extern convar_t *sv_hostmap; +extern convar_t *sv_validate_changelevel; +extern convar_t *public_server; + +//=========================================================== +// +// sv_main.c +// +void SV_FinalMessage( const char *message, qboolean reconnect ); +void SV_DropClient( sv_client_t *cl, qboolean crash ); +void SV_UpdateMovevars( qboolean initialize ); +int SV_ModelIndex( const char *name ); +int SV_SoundIndex( const char *name ); +int SV_EventIndex( const char *name ); +int SV_GenericIndex( const char *name ); +int SV_CalcPacketLoss( sv_client_t *cl ); +void SV_ExecuteUserCommand (char *s); +void SV_InitOperatorCommands( void ); +void SV_KillOperatorCommands( void ); +void SV_UserinfoChanged( sv_client_t *cl ); +void SV_RemoteCommand( netadr_t from, sizebuf_t *msg ); +void SV_PrepWorldFrame( void ); +void SV_ProcessFile( sv_client_t *cl, const char *filename ); +void SV_SendResource( resource_t *pResource, sizebuf_t *msg ); +void SV_SendResourceList( sv_client_t *cl ); +void SV_AddToMaster( netadr_t from, sizebuf_t *msg ); +qboolean SV_IsSimulating( void ); +qboolean SV_InitGame( void ); +void SV_FreeClients( void ); +void Master_Add( void ); +void Master_Heartbeat( void ); +void Master_Packet( void ); + +// +// sv_init.c +// +void SV_ActivateServer( int runPhysics ); +qboolean SV_SpawnServer( const char *server, const char *startspot, qboolean background ); +model_t *SV_ModelHandle( int modelindex ); +void SV_DeactivateServer( void ); + +// +// sv_phys.c +// +void SV_Physics( void ); +qboolean SV_InitPhysicsAPI( void ); +void SV_CheckVelocity( edict_t *ent ); +qboolean SV_CheckWater( edict_t *ent ); +qboolean SV_RunThink( edict_t *ent ); +qboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time ); +qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ); +void SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace ); +qboolean SV_CanPushed( edict_t *ent ); +void SV_FreeOldEntities( void ); +void SV_CheckAllEnts( void ); + +// +// sv_move.c +// +qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ); +qboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink ); +void SV_MoveToOrigin( edict_t *ed, const vec3_t goal, float dist, int iMode ); +qboolean SV_CheckBottom( edict_t *ent, int iMode ); +float SV_VecToYaw( const vec3_t src ); +void SV_WaterMove( edict_t *ent ); + +// +// sv_send.c +// +void SV_SendClientMessages( void ); +void SV_ClientPrintf( sv_client_t *cl, char *fmt, ... ); +void SV_BroadcastPrintf( sv_client_t *ignore, char *fmt, ... ); +void SV_BroadcastCommand( const char *fmt, ... ); + +// +// sv_client.c +// +char *SV_StatusString( void ); +void SV_RefreshUserinfo( void ); +void SV_GetChallenge( netadr_t from ); +void SV_DirectConnect( netadr_t from ); +void SV_TogglePause( const char *msg ); +qboolean SV_ShouldUpdatePing( sv_client_t *cl ); +const char *SV_GetClientIDString( sv_client_t *cl ); +void SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg ); +void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg ); +void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss ); +void SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl ); +void SV_ClientThink( sv_client_t *cl, usercmd_t *cmd ); +void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg ); +void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ); +edict_t *SV_FakeConnect( const char *netname ); +void SV_ExecuteClientCommand( sv_client_t *cl, char *s ); +void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ); +void SV_BuildReconnect( sizebuf_t *msg ); +qboolean SV_IsPlayerIndex( int idx ); +int SV_CalcPing( sv_client_t *cl ); +void SV_InitClientMove( void ); +void SV_UpdateServerInfo( void ); +void SV_EndRedirect( void ); + +// +// sv_cmds.c +// +void SV_Status_f( void ); +void SV_Newgame_f( void ); +void SV_InitHostCommands( void ); + +// +// sv_custom.c +// +void SV_AddToResourceList( resource_t *pResource, resource_t *pList ); +void SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource ); +void SV_RemoveFromResourceList( resource_t *pResource ); +void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg ); +int SV_EstimateNeededResources( sv_client_t *cl ); +void SV_ClearResourceList( resource_t *pList ); +void SV_BatchUploadRequest( sv_client_t *cl ); +void SV_SendResources( sv_client_t *cl, sizebuf_t *msg ); +void SV_ClearResourceLists( sv_client_t *cl ); +void SV_TransferConsistencyInfo( void ); +void SV_RequestMissingResources( void ); + +// +// sv_frame.c +// +void SV_InactivateClients( void ); +void SV_WriteFrameToClient( sv_client_t *client, sizebuf_t *msg ); +void SV_BuildClientFrame( sv_client_t *client ); +void SV_SendMessagesToAll( void ); +void SV_SkipUpdates( void ); + +// +// sv_game.c +// +qboolean SV_LoadProgs( const char *name ); +void SV_UnloadProgs( void ); +void SV_FreeEdicts( void ); +edict_t *SV_AllocEdict( void ); +void SV_FreeEdict( edict_t *pEdict ); +void SV_InitEdict( edict_t *pEdict ); +const char *SV_ClassName( const edict_t *e ); +void SV_FreePrivateData( edict_t *pEdict ); +void SV_CopyTraceToGlobal( trace_t *trace ); +qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ); +void SV_SetMinMaxSize( edict_t *e, const float *min, const float *max, qboolean relink ); +edict_t* SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ); +void SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); +void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ); +int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos ); +qboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ); +void SV_WriteEntityPatch( const char *filename ); +float SV_AngleMod( float ideal, float current, float speed ); +void SV_SpawnEntities( const char *mapname ); +edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ); +edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ); +string_t SV_AllocString( const char *szValue ); +string_t SV_MakeString( const char *szValue ); +const char *SV_GetString( string_t iString ); +sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ); +int SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ); +void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ); +void SV_CreateStaticEntity( struct sizebuf_s *msg, sv_static_entity_t *ent ); +edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ); +void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ); +edict_t* pfnPEntityOfEntIndex( int iEntIndex ); +int pfnIndexOfEdict( const edict_t *pEdict ); +void pfnWriteBytes( const byte *bytes, int count ); +void SV_UpdateBaseVelocity( edict_t *ent ); +byte *pfnSetFatPVS( const float *org ); +byte *pfnSetFatPAS( const float *org ); +int pfnPrecacheModel( const char *s ); +void pfnRemoveEntity( edict_t* e ); +void SV_RestartAmbientSounds( void ); +void SV_RestartDecals( void ); +void SV_RestartStaticEnts( void ); +int pfnGetCurrentPlayer( void ); +edict_t *SV_EdictNum( int n ); +char *SV_Localinfo( void ); + +// +// sv_log.c +// +void Log_Close( void ); +void Log_Open( void ); +void Log_PrintServerVars( void ); +qboolean SV_ServerLog_f( sv_client_t *cl ); + +// +// sv_save.c +// +void SV_SaveGame( const char *pName ); +qboolean SV_LoadGame( const char *pName ); +int SV_LoadGameState( char const *level ); +void SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char *start, qboolean background ); +const char *SV_GetLatestSave( void ); +void SV_InitSaveRestore( void ); +void SV_ClearGameState( void ); + +// +// sv_pmove.c +// +void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ); +void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs ); +qboolean SV_PlayerIsFrozen( edict_t *pClient ); + +// +// sv_world.c +// +void SV_ClearWorld( void ); +void SV_UnlinkEdict( edict_t *ent ); +void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ); +void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ); +trace_t SV_TraceHull( edict_t *ent, int hullNum, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end ); +trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip ); +trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ); +trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ); +const char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end ); +msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ); +trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ); +void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ); +void SV_TouchLinks( edict_t *ent, areanode_t *node ); +int SV_TruePointContents( const vec3_t p ); +int SV_PointContents( const vec3_t p ); +void SV_RunLightStyles( void ); +void SV_SetLightStyle( int style, const char* s, float f ); +const char *SV_GetLightStyle( int style ); +int SV_LightForEntity( edict_t *pEdict ); +void SV_ClearPhysEnts( void ); + +#endif//SERVER_H \ No newline at end of file diff --git a/engine/server/sv_client.c b/engine/server/sv_client.c new file mode 100644 index 00000000..75034e20 --- /dev/null +++ b/engine/server/sv_client.c @@ -0,0 +1,2414 @@ +/* +sv_client.c - client interactions +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "const.h" +#include "server.h" +#include "net_encode.h" +#include "net_api.h" + +const char *clc_strings[clc_lastmsg+1] = +{ + "clc_bad", + "clc_nop", + "clc_move", + "clc_stringcmd", + "clc_delta", + "clc_resourcelist", + "clc_unused6", + "clc_fileconsistency", + "clc_voicedata", + "clc_cvarvalue", + "clc_cvarvalue2", +}; + +typedef struct ucmd_s +{ + const char *name; + qboolean (*func)( sv_client_t *cl ); +} ucmd_t; + +static int g_userid = 1; + +/* +================= +SV_GetChallenge + +Returns a challenge number that can be used +in a subsequent client_connect command. +We do this to prevent denial of service attacks that +flood the server with invalid connection IPs. With a +challenge, they must give a valid IP address. +================= +*/ +void SV_GetChallenge( netadr_t from ) +{ + int i, oldest = 0; + double oldestTime; + + oldestTime = 0x7fffffff; + + // see if we already have a challenge for this ip + for( i = 0; i < MAX_CHALLENGES; i++ ) + { + if( !svs.challenges[i].connected && NET_CompareAdr( from, svs.challenges[i].adr )) + break; + + if( svs.challenges[i].time < oldestTime ) + { + oldestTime = svs.challenges[i].time; + oldest = i; + } + } + + if( i == MAX_CHALLENGES ) + { + // this is the first time this client has asked for a challenge + svs.challenges[oldest].challenge = (COM_RandomLong( 0, 0xFFFF ) << 16) | COM_RandomLong( 0, 0xFFFF ); + svs.challenges[oldest].adr = from; + svs.challenges[oldest].time = host.realtime; + svs.challenges[oldest].connected = false; + i = oldest; + } + + // send it back + Netchan_OutOfBandPrint( NS_SERVER, svs.challenges[i].adr, "challenge %i", svs.challenges[i].challenge ); +} + +int SV_GetFragmentSize( sv_client_t *cl ) +{ + int cl_frag_size; + + if( Netchan_IsLocal( &cl->netchan )) + return FRAGMENT_LOCAL_SIZE; + + cl_frag_size = Q_atoi( Info_ValueForKey( cl->userinfo, "cl_dlmax" )); + cl_frag_size = bound( FRAGMENT_MIN_SIZE, cl_frag_size, FRAGMENT_MAX_SIZE ); + + return cl_frag_size; +} + +/* +================ +SV_RejectConnection + +Rejects connection request and sends back a message +================ +*/ +void SV_RejectConnection( netadr_t from, char *fmt, ... ) +{ + char text[1024]; + va_list argptr; + + va_start( argptr, fmt ); + Q_vsnprintf( text, sizeof( text ), fmt, argptr ); + va_end( argptr ); + + MsgDev( D_REPORT, "%s connection refused. Reason: %s\n", NET_AdrToString( from ), text ); + Netchan_OutOfBandPrint( NS_SERVER, from, "print\n^1Server was reject the connection:^7 %s", text ); + Netchan_OutOfBandPrint( NS_SERVER, from, "disconnect\n" ); +} + +/* +================ +SV_FailDownload + +for some reasons file can't be downloaded +tell the client about this problem +================ +*/ +void SV_FailDownload( sv_client_t *cl, const char *filename ) +{ + if( !COM_CheckString( filename )) + return; + + MSG_BeginServerCmd( &cl->netchan.message, svc_filetxferfailed ); + MSG_WriteString( &cl->netchan.message, filename ); +} + +/* +================ +SV_CheckChallenge + +Make sure connecting client is not spoofing +================ +*/ +int SV_CheckChallenge( netadr_t from, int challenge ) +{ + int i; + + // see if the challenge is valid + // don't care if it is a local address. + if( NET_IsLocalAddress( from )) + return 1; + + for( i = 0; i < MAX_CHALLENGES; i++ ) + { + if( NET_CompareAdr( from, svs.challenges[i].adr )) + { + if( challenge == svs.challenges[i].challenge ) + break; // valid challenge +#if 0 + // g-cont. this breaks multiple connections from single machine + SV_RejectConnection( from, "bad challenge %i\n", challenge ); + return 0; +#endif + } + } + + if( i == MAX_CHALLENGES ) + { + SV_RejectConnection( from, "no challenge for your address\n" ); + return 0; + } + svs.challenges[i].connected = true; + + return 1; +} + +/* +================ +SV_CheckIPRestrictions + +Determine if client is outside appropriate address range +================ +*/ +int SV_CheckIPRestrictions( netadr_t from ) +{ + if( !sv_lan.value ) + { + if( !NET_CompareClassBAdr( from, net_local ) && !NET_IsReservedAdr( from )) + return 0; + } + return 1; +} + +/* +================ +SV_FindEmptySlot + +Get slot # and set client_t pointer for player, if possible +We don't do this search on a "reconnect, we just reuse the slot +================ +*/ +int SV_FindEmptySlot( netadr_t from, int *pslot, sv_client_t **ppClient ) +{ + sv_client_t *cl; + int i; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state == cs_free ) + { + *ppClient = cl; + *pslot = i; + return 1; + } + } + + SV_RejectConnection( from, "server is full\n" ); + return 0; +} + +/* +================== +SV_ConnectClient + +A connection request that did not come from the master +================== +*/ +void SV_ConnectClient( netadr_t from ) +{ + char userinfo[MAX_INFO_STRING]; + char protinfo[MAX_INFO_STRING]; + sv_client_t *cl, *newcl = NULL; + qboolean reconnect = false; + int nClientSlot = 0; + int qport, version; + int i, count = 0; + int challenge; + char *s; + + if( Cmd_Argc() < 5 ) + { + SV_RejectConnection( from, "insufficient connection info\n" ); + return; + } + + version = Q_atoi( Cmd_Argv( 1 )); + + if( version != PROTOCOL_VERSION ) + { + SV_RejectConnection( from, "unsupported protocol (%i should be %i)\n", version, PROTOCOL_VERSION ); + return; + } + + challenge = Q_atoi( Cmd_Argv( 2 )); // get challenge + + // see if the challenge is valid (local clients don't need to challenge) + if( !SV_CheckChallenge( from, challenge )) + return; + + s = Cmd_Argv( 3 ); // protocol info + + if( !Info_IsValid( s )) + { + SV_RejectConnection( from, "invalid protinfo in connect command\n" ); + return; + } + + Q_strncpy( protinfo, s, sizeof( protinfo )); + + // extract qport from protocol info + qport = Q_atoi( Info_ValueForKey( protinfo, "qport" )); + + s = Info_ValueForKey( protinfo, "uuid" ); + if( Q_strlen( s ) != 32 ) + { + SV_RejectConnection( from, "invalid authentication certificate length\n" ); + return; + } + + // LAN servers restrict to class b IP addresses + if( !SV_CheckIPRestrictions( from )) + { + SV_RejectConnection( from, "LAN servers are restricted to local clients (class C)\n" ); + return; + } + + s = Cmd_Argv( 4 ); // user info + + if( Q_strlen( s ) > MAX_INFO_STRING || !Info_IsValid( s )) + { + SV_RejectConnection( from, "invalid userinfo in connect command\n" ); + return; + } + + Q_strncpy( userinfo, s, sizeof( userinfo )); + + // check connection password (don't verify local client) + if( !NET_IsLocalAddress( from ) && sv_password.string[0] && Q_stricmp( sv_password.string, Info_ValueForKey( userinfo, "password" ))) + { + SV_RejectConnection( from, "invalid password\n" ); + return; + } + + // if there is already a slot for this ip, reuse it + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state == cs_free || cl->state == cs_zombie ) + continue; + + if( NET_CompareBaseAdr( from, cl->netchan.remote_address ) && ( cl->netchan.qport == qport || from.port == cl->netchan.remote_address.port )) + { + reconnect = true; + newcl = cl; + break; + } + } + + // A reconnecting client will re-use the slot found above when checking for reconnection. + // the slot will be wiped clean. + if( !reconnect ) + { + // connect the client if there are empty slots. + if( !SV_FindEmptySlot( from, &nClientSlot, &newcl )) + return; + } + else + { + MsgDev( D_INFO, "%s:reconnect\n", NET_AdrToString( from )); + } + + // find a client slot + ASSERT( newcl != NULL ); + + // build a new connection + // accept the new client + sv.current_client = newcl; + newcl->edict = EDICT_NUM( (newcl - svs.clients) + 1 ); + newcl->challenge = challenge; // save challenge for checksumming + newcl->frames = (client_frame_t *)Z_Malloc( sizeof( client_frame_t ) * SV_UPDATE_BACKUP ); + newcl->userid = g_userid++; // create unique userid + newcl->state = cs_connected; + + // reset viewentities (from previous level) + memset( newcl->viewentity, 0, sizeof( newcl->viewentity )); + newcl->num_viewents = 0; + newcl->listeners = 0; + + // initailize netchan + Netchan_Setup( NS_SERVER, &newcl->netchan, from, qport, newcl, SV_GetFragmentSize ); + MSG_Init( &newcl->datagram, "Datagram", newcl->datagram_buf, sizeof( newcl->datagram_buf )); // datagram buf + + // send the connect packet to the client + Netchan_OutOfBandPrint( NS_SERVER, from, "client_connect" ); + + newcl->upstate = us_inactive; + newcl->connection_started = host.realtime; + newcl->cl_updaterate = 0.05; // 20 fps as default + newcl->delta_sequence = -1; + newcl->flags = 0; + + Q_strncpy( newcl->hashedcdkey, Info_ValueForKey( protinfo, "uuid" ), 32 ); + newcl->hashedcdkey[32] = '\0'; + + // reset any remaining events + memset( &newcl->events, 0, sizeof( newcl->events )); + + // parse some info from the info strings (this can override cl_updaterate) + Q_strncpy( newcl->userinfo, userinfo, sizeof( newcl->userinfo )); + SV_UserinfoChanged( newcl ); + SV_ClearResourceLists( newcl ); +#if 0 + memset( &newcl->resourcesneeded, 0, sizeof( resource_t )); + memset( &newcl->resourcesonhand, 0, sizeof( resource_t )); + newcl->resourcesneeded.pNext = newcl->resourcesneeded.pPrev = &newcl->resourcesneeded; + newcl->resourcesonhand.pNext = newcl->resourcesonhand.pPrev = &newcl->resourcesonhand; +#endif + newcl->next_messagetime = host.realtime + newcl->cl_updaterate; + newcl->next_sendinfotime = 0.0; + newcl->lastservertime = -1.0; + newcl->ignored_ents = 0; + newcl->chokecount = 0; + + // reset stats + newcl->next_checkpingtime = -1.0; + newcl->packet_loss = 0.0f; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + if( cl->state >= cs_connected ) count++; + + Log_Printf( "\"%s<%i><%i><>\" connected, address \"%s\"\n", newcl->name, newcl->userid, i, NET_AdrToString( newcl->netchan.remote_address )); + + if( count == 1 || count == svs.maxclients ) + svs.last_heartbeat = MAX_HEARTBEAT; +} + +/* +================== +SV_FakeConnect + +A connection request that came from the game module +================== +*/ +edict_t *SV_FakeConnect( const char *netname ) +{ + char userinfo[MAX_INFO_STRING]; + int i, count = 0; + sv_client_t *cl; + + if( !COM_CheckString( netname )) + netname = "Bot"; + + // find a client slot + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state == cs_free ) + break; + } + + if( i == svs.maxclients ) + return NULL; // server is full + + userinfo[0] = '\0'; + + // setup fake client params + Info_SetValueForKey( userinfo, "name", netname, MAX_INFO_STRING ); + Info_SetValueForKey( userinfo, "model", "gordon", MAX_INFO_STRING ); + Info_SetValueForKey( userinfo, "topcolor", "1", MAX_INFO_STRING ); + Info_SetValueForKey( userinfo, "bottomcolor", "1", MAX_INFO_STRING ); + + // build a new connection + // accept the new client + sv.current_client = cl; + + if( cl->frames ) Mem_Free( cl->frames ); // fakeclients doesn't have frames + memset( cl, 0, sizeof( sv_client_t )); + + cl->edict = EDICT_NUM( (cl - svs.clients) + 1 ); + cl->userid = g_userid++; // create unique userid + SetBits( cl->flags, FCL_FAKECLIENT ); + + // parse some info from the info strings + Q_strncpy( cl->userinfo, userinfo, sizeof( cl->userinfo )); + SV_UserinfoChanged( cl ); + SetBits( cl->flags, FCL_RESEND_USERINFO ); + cl->next_sendinfotime = 0.0; + + // if this was the first client on the server, or the last client + // the server can hold, send a heartbeat to the master. + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + if( cl->state >= cs_connected ) count++; + cl = sv.current_client; + + Log_Printf( "\"%s<%i><%i><>\" connected, address \"local\"\n", cl->name, cl->userid, i ); + + SetBits( cl->edict->v.flags, FL_CLIENT|FL_FAKECLIENT ); // mark it as fakeclient + cl->connection_started = host.realtime; + cl->state = cs_spawned; + + if( count == 1 || count == svs.maxclients ) + svs.last_heartbeat = MAX_HEARTBEAT; + + return cl->edict; +} + +/* +===================== +SV_DropClient + +Called when the player is totally leaving the server, either willingly +or unwillingly. This is NOT called if the entire server is quiting +or crashing. +===================== +*/ +void SV_DropClient( sv_client_t *cl, qboolean crash ) +{ + int i; + + if( cl->state == cs_zombie ) + return; // already dropped + + if( !crash ) + { + // add the disconnect + if( !FBitSet( cl->flags, FCL_FAKECLIENT )) + MSG_BeginServerCmd( &cl->netchan.message, svc_disconnect ); + + if( cl->edict && cl->state == cs_spawned ) + svgame.dllFuncs.pfnClientDisconnect( cl->edict ); + Netchan_TransmitBits( &cl->netchan, 0, NULL ); + } + + ClearBits( cl->flags, FCL_FAKECLIENT ); + ClearBits( cl->flags, FCL_HLTV_PROXY ); + cl->state = cs_zombie; // become free in a few seconds + cl->name[0] = 0; + + if( cl->frames ) + Mem_Free( cl->frames ); // release delta + cl->frames = NULL; + + if( NET_CompareBaseAdr( cl->netchan.remote_address, host.rd.address )) + SV_EndRedirect(); + + // throw away any residual garbage in the channel. + Netchan_Clear( &cl->netchan ); + + // clean client data on disconnect + memset( cl->userinfo, 0, MAX_INFO_STRING ); + memset( cl->physinfo, 0, MAX_INFO_STRING ); + COM_ClearCustomizationList( &cl->customdata, false ); + + // don't send to other clients + cl->edict = NULL; + + // send notification to all other clients + SV_FullClientUpdate( cl, &sv.reliable_datagram ); + + // if this was the last client on the server, send a heartbeat + // to the master so it is known the server is empty + // send a heartbeat now so the master will get up to date info + // if there is already a slot for this ip, reuse it + for( i = 0; i < svs.maxclients; i++ ) + { + if( svs.clients[i].state >= cs_connected ) + break; + } + + if( i == svs.maxclients ) + svs.last_heartbeat = MAX_HEARTBEAT; +} + +/* +============================================================================== + +SVC COMMAND REDIRECT + +============================================================================== +*/ +void SV_BeginRedirect( netadr_t adr, int target, char *buffer, int buffersize, void (*flush)) +{ + if( !target || !buffer || !buffersize || !flush ) + return; + + host.rd.target = target; + host.rd.buffer = buffer; + host.rd.buffersize = buffersize; + host.rd.flush = flush; + host.rd.address = adr; + host.rd.buffer[0] = 0; +} + +void SV_FlushRedirect( netadr_t adr, int dest, char *buf ) +{ + if( sv.current_client && FBitSet( sv.current_client->flags, FCL_FAKECLIENT )) + return; + + switch( dest ) + { + case RD_PACKET: + Netchan_OutOfBandPrint( NS_SERVER, adr, "print\n%s", buf ); + break; + case RD_CLIENT: + if( !sv.current_client ) return; // client not set + MSG_BeginServerCmd( &sv.current_client->netchan.message, svc_print ); + MSG_WriteString( &sv.current_client->netchan.message, buf ); + break; + case RD_NONE: + Con_Printf( S_ERROR "SV_FlushRedirect: %s: invalid destination\n", NET_AdrToString( adr )); + break; + } +} + +void SV_EndRedirect( void ) +{ + if( host.rd.flush ) + host.rd.flush( host.rd.address, host.rd.target, host.rd.buffer ); + + host.rd.target = 0; + host.rd.buffer = NULL; + host.rd.buffersize = 0; + host.rd.flush = NULL; +} + +/* +=============== +SV_GetClientIDString + +Returns a pointer to a static char for most likely only printing. +=============== +*/ +const char *SV_GetClientIDString( sv_client_t *cl ) +{ + static char result[MAX_QPATH]; + + if( !cl ) return ""; + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + { + Q_strncpy( result, "ID_BOT", sizeof( result )); + } + else if( NET_IsLocalAddress( cl->netchan.remote_address )) + { + Q_strncpy( result, "ID_LOOPBACK", sizeof( result )); + } + else if( sv_lan.value ) + { + Q_strncpy( result, "ID_LAN", sizeof( result )); + } + else + { + Q_snprintf( result, sizeof( result ), "ID_%s", MD5_Print( cl->hashedcdkey )); + } + + return result; +} + +/* +================ +SV_TestBandWidth + +================ +*/ +void SV_TestBandWidth( netadr_t from ) +{ + int version = Q_atoi( Cmd_Argv( 1 )); + int packetsize = Q_atoi( Cmd_Argv( 2 )); + byte send_buf[FRAGMENT_MAX_SIZE]; + dword crcValue = 0; + byte *filepos; + int crcpos; + file_t *test; + sizebuf_t send; + + // don't waste time of protocol mismatched + if( version != PROTOCOL_VERSION ) + { + SV_RejectConnection( from, "unsupported protocol (%i should be %i)\n", version, PROTOCOL_VERSION ); + return; + } + + test = FS_Open( "gfx.wad", "rb", false ); + + if( FS_FileLength( test ) < sizeof( send_buf )) + { + // skip the test and just get challenge + SV_GetChallenge( from ); + return; + } + + // write the packet header + MSG_Init( &send, "BandWidthPacket", send_buf, sizeof( send_buf )); + MSG_WriteLong( &send, -1 ); // -1 sequence means out of band + MSG_WriteString( &send, "testpacket" ); + crcpos = MSG_GetNumBytesWritten( &send ); + MSG_WriteLong( &send, 0 ); // reserve space for crc + filepos = send.pData + MSG_GetNumBytesWritten( &send ); + packetsize = packetsize - MSG_GetNumBytesWritten( &send ); // adjust the packet size + FS_Read( test, filepos, packetsize ); + FS_Close( test ); + + CRC32_ProcessBuffer( &crcValue, filepos, packetsize ); // calc CRC + MSG_SeekToBit( &send, packetsize << 3, SEEK_CUR ); + *(uint *)&send.pData[crcpos] = crcValue; + + // send the datagram + NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &send ), MSG_GetData( &send ), from ); +} + +/* +================ +SV_Ack + +================ +*/ +void SV_Ack( netadr_t from ) +{ + Con_Printf( "ping %s\n", NET_AdrToString( from )); +} + +/* +================ +SV_Info + +Responds with short info for broadcast scans +The second parameter should be the current protocol version number. +================ +*/ +void SV_Info( netadr_t from ) +{ + char string[MAX_INFO_STRING]; + int i, count = 0; + int version; + + // ignore in single player + if( svs.maxclients == 1 || !svs.initialized ) + return; + + version = Q_atoi( Cmd_Argv( 1 )); + string[0] = '\0'; + + if( version != PROTOCOL_VERSION ) + { + Q_snprintf( string, sizeof( string ), "%s: wrong version\n", hostname.string ); + } + else + { + for( i = 0; i < svs.maxclients; i++ ) + if( svs.clients[i].state >= cs_connected ) + count++; + + Info_SetValueForKey( string, "host", hostname.string, MAX_INFO_STRING ); + Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); + Info_SetValueForKey( string, "dm", va( "%i", (int)svgame.globals->deathmatch ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "team", va( "%i", (int)svgame.globals->teamplay ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "coop", va( "%i", (int)svgame.globals->coop ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "numcl", va( "%i", count ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "maxcl", va( "%i", svs.maxclients ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING ); + } + + Netchan_OutOfBandPrint( NS_SERVER, from, "info\n%s", string ); +} + +/* +================ +SV_BuildNetAnswer + +Responds with long info for local and broadcast requests +================ +*/ +void SV_BuildNetAnswer( netadr_t from ) +{ + char string[MAX_INFO_STRING], answer[512]; + int version, context, type; + int i, count = 0; + + // ignore in single player + if( svs.maxclients == 1 || !svs.initialized ) + return; + + version = Q_atoi( Cmd_Argv( 1 )); + context = Q_atoi( Cmd_Argv( 2 )); + type = Q_atoi( Cmd_Argv( 3 )); + + if( version != PROTOCOL_VERSION ) + { + // handle the unsupported protocol + string[0] = '\0'; + Info_SetValueForKey( string, "neterror", "protocol", MAX_INFO_STRING ); + + // send error unsupported protocol + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, string ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + return; + } + + if( type == NETAPI_REQUEST_PING ) + { + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, "" ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + } + else if( type == NETAPI_REQUEST_RULES ) + { + // send serverinfo + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, svs.serverinfo ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + } + else if( type == NETAPI_REQUEST_PLAYERS ) + { + string[0] = '\0'; + + for( i = 0; i < svs.maxclients; i++ ) + { + if( svs.clients[i].state >= cs_connected ) + { + edict_t *ed = svs.clients[i].edict; + float time = host.realtime - svs.clients[i].connection_started; + Q_strncat( string, va( "%c\\%s\\%i\\%f\\", count, svs.clients[i].name, (int)ed->v.frags, time ), sizeof( string )); + count++; + } + } + + // send playernames + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, string ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + } + else if( type == NETAPI_REQUEST_DETAILS ) + { + for( i = 0; i < svs.maxclients; i++ ) + if( svs.clients[i].state >= cs_connected ) + count++; + + string[0] = '\0'; + Info_SetValueForKey( string, "hostname", hostname.string, MAX_INFO_STRING ); + Info_SetValueForKey( string, "gamedir", GI->gamefolder, MAX_INFO_STRING ); + Info_SetValueForKey( string, "current", va( "%i", count ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "max", va( "%i", svs.maxclients ), MAX_INFO_STRING ); + Info_SetValueForKey( string, "map", sv.name, MAX_INFO_STRING ); + + // send serverinfo + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, string ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + } + else + { + string[0] = '\0'; + Info_SetValueForKey( string, "neterror", "undefined", MAX_INFO_STRING ); + + // send error undefined request type + Q_snprintf( answer, sizeof( answer ), "netinfo %i %i %s\n", context, type, string ); + Netchan_OutOfBandPrint( NS_SERVER, from, answer ); + } +} + +/* +================ +SV_Ping + +Just responds with an acknowledgement +================ +*/ +void SV_Ping( netadr_t from ) +{ + Netchan_OutOfBandPrint( NS_SERVER, from, "ack" ); +} + +/* +================ +Rcon_Validate +================ +*/ +qboolean Rcon_Validate( void ) +{ + if( !Q_strlen( rcon_password.string )) + return false; + if( Q_strcmp( Cmd_Argv( 1 ), rcon_password.string )) + return false; + return true; +} + +/* +=============== +SV_RemoteCommand + +A client issued an rcon command. +Shift down the remaining args +Redirect all printfs +=============== +*/ +void SV_RemoteCommand( netadr_t from, sizebuf_t *msg ) +{ + static char outputbuf[2048]; + char remaining[1024]; + int i; + + MsgDev( D_INFO, "Rcon from %s:\n%s\n", NET_AdrToString( from ), MSG_GetData( msg ) + 4 ); + Log_Printf( "Rcon: \"%s\" from \"%s\"\n", MSG_GetData( msg ) + 4, NET_AdrToString( from )); + SV_BeginRedirect( from, RD_PACKET, outputbuf, sizeof( outputbuf ) - 16, SV_FlushRedirect ); + + if( Rcon_Validate( )) + { + remaining[0] = 0; + for( i = 2; i < Cmd_Argc(); i++ ) + { + Q_strcat( remaining, Cmd_Argv( i )); + Q_strcat( remaining, " " ); + } + Cmd_ExecuteString( remaining ); + } + else MsgDev( D_ERROR, "Bad rcon_password.\n" ); + + SV_EndRedirect(); +} + +/* +=================== +SV_CalcPing + +recalc ping on current client +=================== +*/ +int SV_CalcPing( sv_client_t *cl ) +{ + float ping = 0; + int i, count; + int idx, back; + client_frame_t *frame; + + // bots don't have a real ping + if( FBitSet( cl->flags, FCL_FAKECLIENT ) || !cl->frames ) + return 5; + + if( SV_UPDATE_BACKUP <= 31 ) + { + back = SV_UPDATE_BACKUP / 2; + if( back <= 0 ) return 0; + } + else back = 16; + + count = 0; + + for( i = 0; i < back; i++ ) + { + idx = cl->netchan.incoming_acknowledged + ~i; + frame = &cl->frames[idx & SV_UPDATE_MASK]; + + if( frame->ping_time > 0.0f ) + { + ping += frame->ping_time; + count++; + } + } + + if( count > 0 ) + return (( ping / count ) * 1000.0f ); + return 0; +} + +/* +=================== +SV_EstablishTimeBase + +Finangles latency and the like. +=================== +*/ +void SV_EstablishTimeBase( sv_client_t *cl, usercmd_t *cmds, int dropped, int numbackup, int numcmds ) +{ + double runcmd_time = 0.0; + int i, cmdnum = dropped; + + if( dropped < 24 ) + { + while( dropped > numbackup ) + { + runcmd_time = (double)cl->lastcmd.msec / 1000.0; + dropped--; + } + + while( dropped > 0 ) + { + cmdnum = dropped + numcmds - 1; + runcmd_time += (double)cmds[cmdnum].msec / 1000.0; + dropped--; + } + } + + for( i = numcmds - 1; i >= 0; i-- ) + runcmd_time += cmds[i].msec / 1000.0; + + cl->timebase = sv.time + sv.frametime - runcmd_time; +} + +/* +=================== +SV_CalcClientTime + +compute latency for client +=================== +*/ +float SV_CalcClientTime( sv_client_t *cl ) +{ + float minping, maxping; + float ping = 0.0f; + int i, count = 0; + int backtrack; + + backtrack = (int)sv_unlagsamples.value; + if( backtrack < 1 ) backtrack = 1; + + if( backtrack >= (SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 )) + backtrack = ( SV_UPDATE_BACKUP <= 16 ? SV_UPDATE_BACKUP : 16 ); + + if( backtrack <= 0 ) + return 0.0f; + + for( i = 0; i < backtrack; i++ ) + { + client_frame_t *frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)]; + if( frame->ping_time <= 0.0f ) + continue; + + ping += frame->ping_time; + count++; + } + + if( !count ) return 0.0f; + + minping = 9999.0f; + maxping = -9999.0f; + ping /= count; + + for( i = 0; i < ( SV_UPDATE_BACKUP <= 4 ? SV_UPDATE_BACKUP : 4 ); i++ ) + { + client_frame_t *frame = &cl->frames[SV_UPDATE_MASK & (cl->netchan.incoming_acknowledged - i)]; + if( frame->ping_time <= 0.0f ) + continue; + + if( frame->ping_time < minping ) + minping = frame->ping_time; + + if( frame->ping_time > maxping ) + maxping = frame->ping_time; + } + + if( maxping < minping || fabs( maxping - minping ) <= 0.2f ) + return ping; + + return 0.0f; +} + +/* +=================== +SV_FullClientUpdate + +Writes all update values to a bitbuf +=================== +*/ +void SV_FullClientUpdate( sv_client_t *cl, sizebuf_t *msg ) +{ + char info[MAX_INFO_STRING]; + char digest[16]; + MD5Context_t ctx; + int i; + + // process userinfo before updating + SV_UserinfoChanged( cl ); + + i = cl - svs.clients; + + MSG_BeginServerCmd( msg, svc_updateuserinfo ); + MSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS ); + MSG_WriteLong( msg, cl->userid ); + + if( cl->name[0] ) + { + MSG_WriteOneBit( msg, 1 ); + + Q_strncpy( info, cl->userinfo, sizeof( info )); + + // remove server passwords, etc. + Info_RemovePrefixedKeys( info, '_' ); + MSG_WriteString( msg, info ); + + MD5Init( &ctx ); + MD5Update( &ctx, cl->hashedcdkey, sizeof( cl->hashedcdkey )); + MD5Final( digest, &ctx ); + + MSG_WriteBytes( msg, digest, sizeof( digest )); + } + else MSG_WriteOneBit( msg, 0 ); +} + +/* +=================== +SV_RefreshUserinfo + +=================== +*/ +void SV_RefreshUserinfo( void ) +{ + sv_client_t *cl; + int i; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state >= cs_connected ) + SetBits( cl->flags, FCL_RESEND_USERINFO ); + } +} + +/* +=================== +SV_FullUpdateMovevars + +this is send all movevars values when client connected +otherwise see code SV_UpdateMovevars() +=================== +*/ +void SV_FullUpdateMovevars( sv_client_t *cl, sizebuf_t *msg ) +{ + movevars_t nullmovevars; + + memset( &nullmovevars, 0, sizeof( nullmovevars )); + MSG_WriteDeltaMovevars( msg, &nullmovevars, &svgame.movevars ); +} + +/* +=================== +SV_ShouldUpdatePing + +determine should we recalculate +ping times now +=================== +*/ +qboolean SV_ShouldUpdatePing( sv_client_t *cl ) +{ + if( FBitSet( cl->flags, FCL_HLTV_PROXY )) + { + if( host.realtime < cl->next_checkpingtime ) + return false; + + cl->next_checkpingtime = host.realtime + 2.0; + return true; + } + + // they are viewing the scoreboard. Send them pings. + return FBitSet( cl->lastcmd.buttons, IN_SCORE ) ? true : false; +} + +/* +=================== +SV_IsPlayerIndex + +=================== +*/ +qboolean SV_IsPlayerIndex( int idx ) +{ + if( idx > 0 && idx <= svs.maxclients ) + return true; + return false; +} + +/* +=================== +SV_GetPlayerStats + +This function and its static vars track some of the networking +conditions. I haven't bothered to trace it beyond that, because +this fucntion sucks pretty badly. +=================== +*/ +void SV_GetPlayerStats( sv_client_t *cl, int *ping, int *packet_loss ) +{ + static int last_ping[MAX_CLIENTS]; + static int last_loss[MAX_CLIENTS]; + int i; + + i = cl - svs.clients; + + if( host.realtime >= cl->next_checkpingtime ) + { + cl->next_checkpingtime = host.realtime + 2.0; + last_ping[i] = SV_CalcPing( cl ); + last_loss[i] = cl->packet_loss; + } + + if( ping ) *ping = last_ping[i]; + if( packet_loss ) *packet_loss = last_loss[i]; +} + +/* +=========== +PutClientInServer + +Called when a player connects to a server or respawns in +a deathmatch. +============ +*/ +void SV_PutClientInServer( sv_client_t *cl ) +{ + static byte msg_buf[MAX_INIT_MSG]; + edict_t *ent = cl->edict; + sizebuf_t msg; + + MSG_Init( &msg, "Spawn", msg_buf, sizeof( msg_buf )); + + if( sv.loadgame ) + { + // NOTE: we needs to setup angles on restore here + if( ent->v.fixangle == 1 ) + { + MSG_BeginServerCmd( &msg, svc_setangle ); + MSG_WriteVec3Angles( &msg, ent->v.angles ); + ent->v.fixangle = 0; + } + + if( svgame.dllFuncs.pfnParmsChangeLevel ) + { + SAVERESTOREDATA levelData; + string name; + int i; + + memset( &levelData, 0, sizeof( levelData )); + svgame.globals->pSaveData = &levelData; + svgame.dllFuncs.pfnParmsChangeLevel(); + + MSG_BeginServerCmd( &msg, svc_restore ); + Q_snprintf( name, sizeof( name ), "%s%s.HL2", DEFAULT_SAVE_DIRECTORY, sv.name ); + COM_FixSlashes( name ); + MSG_WriteString( &msg, name ); + MSG_WriteByte( &msg, levelData.connectionCount ); + + for( i = 0; i < levelData.connectionCount; i++ ) + MSG_WriteString( &msg, levelData.levelList[i].mapName ); + + svgame.globals->pSaveData = NULL; + } + + // reset weaponanim + MSG_BeginServerCmd( &msg, svc_weaponanim ); + MSG_WriteByte( &msg, 0 ); + MSG_WriteByte( &msg, 0 ); + + sv.loadgame = false; + sv.paused = false; + } + else + { + if( Q_atoi( Info_ValueForKey( cl->userinfo, "hltv" ))) + SetBits( cl->flags, FCL_HLTV_PROXY ); + + if( FBitSet( cl->flags, FCL_HLTV_PROXY )) + SetBits( ent->v.flags, FL_PROXY ); + else ent->v.flags = 0; + + ent->v.netname = MAKE_STRING( cl->name ); + ent->v.colormap = NUM_FOR_EDICT( ent ); // ??? + + // fisrt entering + svgame.globals->time = sv.time; + svgame.dllFuncs.pfnClientPutInServer( ent ); + + if( sv.background ) // don't attack player in background mode + SetBits( ent->v.flags, FL_GODMODE|FL_NOTARGET ); + + cl->pViewEntity = NULL; // reset pViewEntity + + if( svgame.globals->cdAudioTrack ) + { + MSG_BeginServerCmd( &msg, svc_stufftext ); + MSG_WriteString( &msg, va( "cd loop %3d\n", svgame.globals->cdAudioTrack )); + svgame.globals->cdAudioTrack = 0; + } + } + + // enable dev-mode to prevent crash cheat-protecting from Invasion mod + if( FBitSet( ent->v.flags, FL_GODMODE|FL_NOTARGET ) && !Q_stricmp( GI->gamefolder, "invasion" )) + SV_ExecuteClientCommand( cl, "test\n" ); + + // refresh the userinfo and movevars + // NOTE: because movevars can be changed during the connection process + SetBits( cl->flags, FCL_RESEND_USERINFO|FCL_RESEND_MOVEVARS ); + + // reset client times + cl->connecttime = 0.0; + cl->ignorecmdtime = 0.0; + cl->cmdtime = 0.0; + + if( !FBitSet( cl->flags, FCL_FAKECLIENT )) + { + int viewEnt; + + // NOTE: it's will be fragmented automatically in right ordering + MSG_WriteBits( &msg, MSG_GetData( &sv.signon ), MSG_GetNumBitsWritten( &sv.signon )); + + if( cl->pViewEntity ) + viewEnt = NUM_FOR_EDICT( cl->pViewEntity ); + else viewEnt = NUM_FOR_EDICT( cl->edict ); + + MSG_BeginServerCmd( &msg, svc_setview ); + MSG_WriteWord( &msg, viewEnt ); + + MSG_BeginServerCmd( &msg, svc_signonnum ); + MSG_WriteByte( &msg, 1 ); + + if( MSG_CheckOverflow( &msg )) + Host_Error( "overflow\n" ); + + // send initialization data + Netchan_CreateFragments( &cl->netchan, &msg ); + Netchan_FragSend( &cl->netchan ); + } +} + +/* +=========== +SV_UpdateClientView + +Resend the client viewentity (used for demos) +============ +*/ +void SV_UpdateClientView( sv_client_t *cl ) +{ + int viewEnt; + + if( cl->pViewEntity ) + viewEnt = NUM_FOR_EDICT( cl->pViewEntity ); + else viewEnt = NUM_FOR_EDICT( cl->edict ); + + MSG_BeginServerCmd( &cl->netchan.message, svc_setview ); + MSG_WriteWord( &cl->netchan.message, viewEnt ); +} + +/* +================== +SV_TogglePause +================== +*/ +void SV_TogglePause( const char *msg ) +{ + if( sv.background ) return; + + sv.paused ^= 1; + + if( COM_CheckString( msg )) + SV_BroadcastPrintf( NULL, "%s", msg ); + + // send notification to all clients + MSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause ); + MSG_WriteOneBit( &sv.reliable_datagram, sv.paused ); +} + +/* +================ +SV_SendReconnect + +Tell all the clients that the server is changing levels +================ +*/ +void SV_BuildReconnect( sizebuf_t *msg ) +{ + MSG_BeginServerCmd( msg, svc_stufftext ); + MSG_WriteString( msg, "reconnect\n" ); +} + +/* +================== +SV_WriteDeltaDescriptionToClient + +send delta communication encoding +================== +*/ +void SV_WriteDeltaDescriptionToClient( sizebuf_t *msg ) +{ + int tableIndex; + int fieldIndex; + + for( tableIndex = 0; tableIndex < Delta_NumTables(); tableIndex++ ) + { + delta_info_t *dt = Delta_FindStructByIndex( tableIndex ); + + for( fieldIndex = 0; fieldIndex < dt->numFields; fieldIndex++ ) + Delta_WriteTableField( msg, tableIndex, &dt->pFields[fieldIndex] ); + } +} + +/* +================ +SV_SendServerdata + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +void SV_SendServerdata( sizebuf_t *msg, sv_client_t *cl ) +{ + string message; + int i; + + // Only send this message to developer console, or multiplayer clients. + if(( host_developer.value ) || ( svs.maxclients > 1 )) + { + MSG_BeginServerCmd( msg, svc_print ); + Q_snprintf( message, sizeof( message ), "\n^3BUILD %d SERVER (%i CRC)\nServer #%i\n", Q_buildnum(), sv.progsCRC, svs.spawncount ); + MSG_WriteString( msg, message ); + } + + // send the serverdata + MSG_BeginServerCmd( msg, svc_serverdata ); + MSG_WriteLong( msg, PROTOCOL_VERSION ); + MSG_WriteLong( msg, svs.spawncount ); + MSG_WriteLong( msg, sv.worldmapCRC ); + MSG_WriteByte( msg, cl - svs.clients ); + MSG_WriteByte( msg, svs.maxclients ); + MSG_WriteWord( msg, GI->max_edicts ); + MSG_WriteWord( msg, MAX_MODELS ); + MSG_WriteString( msg, sv.name ); + MSG_WriteString( msg, STRING( svgame.edicts->v.message )); // Map Message + MSG_WriteOneBit( msg, sv.background ); // tell client about background map + MSG_WriteString( msg, GI->gamefolder ); + MSG_WriteLong( msg, host.features ); + + // send the player hulls + for( i = 0; i < MAX_MAP_HULLS * 3; i++ ) + { + MSG_WriteChar( msg, host.player_mins[i/3][i%3] ); + MSG_WriteChar( msg, host.player_maxs[i/3][i%3] ); + } + + // send delta-encoding + SV_WriteDeltaDescriptionToClient( msg ); + + // now client know delta and can reading encoded messages + SV_FullUpdateMovevars( cl, msg ); + + // send the user messages registration + for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) + SV_SendUserReg( msg, &svgame.msg[i] ); + + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + if( !sv.lightstyles[i].pattern[0] ) + continue; // unused style + + MSG_BeginServerCmd( msg, svc_lightstyle ); + MSG_WriteByte( msg, i ); // stylenum + MSG_WriteString( msg, sv.lightstyles[i].pattern ); + MSG_WriteFloat( msg, sv.lightstyles[i].time ); + } +} + +/* +============================================================ + +CLIENT COMMAND EXECUTION + +============================================================ +*/ +/* +================ +SV_New_f + +Sends the first message from the server to a connected client. +This will be sent on the initial connection and upon each server load. +================ +*/ +static qboolean SV_New_f( sv_client_t *cl ) +{ + byte msg_buf[MAX_INIT_MSG]; + char szRejectReason[128]; + char szAddress[128]; + char szName[32]; + sv_client_t *cur; + sizebuf_t msg; + int i; + + MSG_Init( &msg, "New", msg_buf, sizeof( msg_buf )); + + if( cl->state != cs_connected ) + return false; + + // send the serverdata + SV_SendServerdata( &msg, cl ); + + // if the client was connected, tell the game .dll to disconnect him/her. + if(( cl->state == cs_spawned ) && cl->edict ) + svgame.dllFuncs.pfnClientDisconnect( cl->edict ); + + Q_snprintf( szName, sizeof( szName ), "%s", cl->name ); + Q_snprintf( szAddress, sizeof( szAddress ), "%s", NET_AdrToString( cl->netchan.remote_address )); + Q_snprintf( szRejectReason, sizeof( szRejectReason ), "Connection rejected by game\n"); + + // Allow the game dll to reject this client. + if( !svgame.dllFuncs.pfnClientConnect( cl->edict, szName, szAddress, szRejectReason )) + { + // reject the connection and drop the client. + SV_RejectConnection( cl->netchan.remote_address, "%s\n", szRejectReason ); + SV_DropClient( cl, false ); + return true; + } + + // server info string + MSG_BeginServerCmd( &msg, svc_stufftext ); + MSG_WriteString( &msg, va( "fullserverinfo \"%s\"\n", SV_Serverinfo( ))); + + // collect the info about all the players and send to me + for( i = 0, cur = svs.clients; i < svs.maxclients; i++, cur++ ) + { + if( !cur->edict || cur->state != cs_spawned ) + continue; // not in game yet + SV_FullClientUpdate( cur, &msg ); + } + + // g-cont. why this is there? + memset( &cl->lastcmd, 0, sizeof( cl->lastcmd )); + + Netchan_CreateFragments( &cl->netchan, &msg ); + Netchan_FragSend( &cl->netchan ); + + return true; +} + +/* +================= +SV_Disconnect_f + +The client is going to disconnect, so remove the connection immediately +================= +*/ +static qboolean SV_Disconnect_f( sv_client_t *cl ) +{ + SV_DropClient( cl, false ); + return true; +} + +/* +================== +SV_ShowServerinfo_f + +Dumps the serverinfo info string +================== +*/ +static qboolean SV_ShowServerinfo_f( sv_client_t *cl ) +{ + Info_Print( svs.serverinfo ); + return true; +} + +/* +================== +SV_Pause_f +================== +*/ +static qboolean SV_Pause_f( sv_client_t *cl ) +{ + string message; + + if( UI_CreditsActive( )) + return true; + + if( !sv_pausable->value ) + { + SV_ClientPrintf( cl, "Pause not allowed.\n" ); + return true; + } + + if( FBitSet( cl->flags, FCL_HLTV_PROXY )) + { + SV_ClientPrintf( cl, "Spectators can not pause.\n" ); + return true; + } + + if( !sv.paused ) Q_snprintf( message, MAX_STRING, "^2%s^7 paused the game\n", cl->name ); + else Q_snprintf( message, MAX_STRING, "^2%s^7 unpaused the game\n", cl->name ); + + SV_TogglePause( message ); + + return true; +} + +/* +================= +SV_UserinfoChanged + +Pull specific info from a newly changed userinfo string +into a more C freindly form. +================= +*/ +void SV_UserinfoChanged( sv_client_t *cl ) +{ + int i, dupc = 1; + edict_t *ent = cl->edict; + string name1, name2; + sv_client_t *current; + char *val; + + if( !COM_CheckString( cl->userinfo )) + return; + + val = Info_ValueForKey( cl->userinfo, "name" ); + Q_strncpy( name2, val, sizeof( name2 )); + COM_TrimSpace( name2, name1 ); + + if( !Q_stricmp( name1, "console" )) + { + Info_SetValueForKey( cl->userinfo, "name", "unnamed", MAX_INFO_STRING ); + val = Info_ValueForKey( cl->userinfo, "name" ); + } + else if( Q_strcmp( name1, val )) + { + Info_SetValueForKey( cl->userinfo, "name", name1, MAX_INFO_STRING ); + val = Info_ValueForKey( cl->userinfo, "name" ); + } + + if( !Q_strlen( name1 )) + { + Info_SetValueForKey( cl->userinfo, "name", "unnamed", MAX_INFO_STRING ); + val = Info_ValueForKey( cl->userinfo, "name" ); + Q_strncpy( name2, "unnamed", sizeof( name2 )); + Q_strncpy( name1, "unnamed", sizeof( name1 )); + } + + // check to see if another user by the same name exists + while( 1 ) + { + for( i = 0, current = svs.clients; i < svs.maxclients; i++, current++ ) + { + if( current == cl || current->state != cs_spawned ) + continue; + + if( !Q_stricmp( current->name, val )) + break; + } + + if( i != svs.maxclients ) + { + // dup name + Q_snprintf( name2, sizeof( name2 ), "%s (%u)", name1, dupc++ ); + Info_SetValueForKey( cl->userinfo, "name", name2, MAX_INFO_STRING ); + val = Info_ValueForKey( cl->userinfo, "name" ); + Q_strncpy( cl->name, name2, sizeof( cl->name )); + } + else + { + if( dupc == 1 ) // unchanged + Q_strncpy( cl->name, name1, sizeof( cl->name )); + break; + } + } + + // rate command + val = Info_ValueForKey( cl->userinfo, "rate" ); + if( Q_strlen( val )) + cl->netchan.rate = bound( MIN_RATE, Q_atoi( val ), MAX_RATE ); + else cl->netchan.rate = DEFAULT_RATE; + + // movement prediction + if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_nopred" ))) + ClearBits( cl->flags, FCL_PREDICT_MOVEMENT ); + else SetBits( cl->flags, FCL_PREDICT_MOVEMENT ); + + // lag compensation + if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_lc" ))) + SetBits( cl->flags, FCL_LAG_COMPENSATION ); + else ClearBits( cl->flags, FCL_LAG_COMPENSATION ); + + // weapon perdiction + if( Q_atoi( Info_ValueForKey( cl->userinfo, "cl_lw" ))) + SetBits( cl->flags, FCL_LOCAL_WEAPONS ); + else ClearBits( cl->flags, FCL_LOCAL_WEAPONS ); + + val = Info_ValueForKey( cl->userinfo, "cl_updaterate" ); + + if( Q_strlen( val )) + { + if( Q_atoi( val ) != 0 ) + { + int i = bound( 10, Q_atoi( val ), 300 ); + cl->cl_updaterate = 1.0 / i; + } + else cl->cl_updaterate = 0.0; + } + + // call prog code to allow overrides + svgame.dllFuncs.pfnClientUserInfoChanged( cl->edict, cl->userinfo ); + + val = Info_ValueForKey( cl->userinfo, "name" ); + Q_strncpy( cl->name, val, sizeof( cl->name )); + ent->v.netname = MAKE_STRING( cl->name ); +} + +/* +================== +SV_UpdateUserinfo_f +================== +*/ +static qboolean SV_UpdateUserinfo_f( sv_client_t *cl ) +{ + Q_strncpy( cl->userinfo, Cmd_Argv( 1 ), sizeof( cl->userinfo )); + + if( cl->state >= cs_connected ) + SetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info + return true; +} + +/* +================== +SV_SetInfo_f +================== +*/ +static qboolean SV_SetInfo_f( sv_client_t *cl ) +{ + Info_SetValueForKey( cl->userinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_INFO_STRING ); + + if( cl->state >= cs_connected ) + SetBits( cl->flags, FCL_RESEND_USERINFO ); // needs for update client info + return true; +} + +/* +================== +SV_Noclip_f +================== +*/ +static qboolean SV_Noclip_f( sv_client_t *cl ) +{ + edict_t *pEntity = cl->edict; + + if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + return true; + + if( pEntity->v.movetype != MOVETYPE_NOCLIP ) + { + SV_ClientPrintf( cl, "noclip ON\n" ); + pEntity->v.movetype = MOVETYPE_NOCLIP; + } + else + { + SV_ClientPrintf( cl, "noclip OFF\n" ); + pEntity->v.movetype = MOVETYPE_WALK; + } + + return true; +} + +/* +================== +SV_Godmode_f +================== +*/ +static qboolean SV_Godmode_f( sv_client_t *cl ) +{ + edict_t *pEntity = cl->edict; + + if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + return true; + + pEntity->v.flags = pEntity->v.flags ^ FL_GODMODE; + + if( !FBitSet( pEntity->v.flags, FL_GODMODE )) + SV_ClientPrintf( cl, "godmode OFF\n" ); + else SV_ClientPrintf( cl, "godmode ON\n" ); + + return true; +} + +/* +================== +SV_Notarget_f +================== +*/ +static qboolean SV_Notarget_f( sv_client_t *cl ) +{ + edict_t *pEntity = cl->edict; + + if( !Cvar_VariableInteger( "sv_cheats" ) || sv.background ) + return true; + + pEntity->v.flags = pEntity->v.flags ^ FL_NOTARGET; + + if( !FBitSet( pEntity->v.flags, FL_NOTARGET )) + SV_ClientPrintf( cl, "notarget OFF\n" ); + else SV_ClientPrintf( cl, "notarget ON\n" ); + + return true; +} + +/* +================== +SV_SendRes_f +================== +*/ +static qboolean SV_SendRes_f( sv_client_t *cl ) +{ + byte buffer[MAX_INIT_MSG]; + sizebuf_t msg; + + if( cl->state != cs_connected ) + return false; + + MSG_Init( &msg, "SendResources", buffer, sizeof( buffer )); + + if( svs.maxclients > 1 && FBitSet( cl->flags, FCL_SEND_RESOURCES )) + return true; + + SetBits( cl->flags, FCL_SEND_RESOURCES ); + SV_SendResources( cl, &msg ); + + Netchan_CreateFragments( &cl->netchan, &msg ); + Netchan_FragSend( &cl->netchan ); + + return true; +} + +/* +================== +SV_DownloadFile_f +================== +*/ +static qboolean SV_DownloadFile_f( sv_client_t *cl ) +{ + char *name; + + if( Cmd_Argc() < 2 ) + return true; + + name = Cmd_Argv( 1 ); + + if( !COM_CheckString( name )) + return true; + + if( !COM_IsSafeFileToDownload( name ) || !sv_allow_download.value ) + { + SV_FailDownload( cl, name ); + return true; + } + + // g-cont. now we supports hot precache + if( name[0] != '!' ) + { + if( sv_send_resources.value ) + { + // also check the model textures + if( !Q_stricmp( COM_FileExtension( name ), "mdl" )) + { + if( FS_FileExists( Mod_StudioTexName( name ), false ) > 0 ) + Netchan_CreateFileFragments( &cl->netchan, Mod_StudioTexName( name )); + } + + if( Netchan_CreateFileFragments( &cl->netchan, name )) + { + Netchan_FragSend( &cl->netchan ); + return true; + } + } + + SV_FailDownload( cl, name ); + return true; + } + + if( Q_strlen( name ) == 36 && !Q_strnicmp( name, "!MD5", 4 ) && sv_send_logos.value ) + { + resource_t custResource; + byte md5[32]; + byte *pbuf; + int size; + + memset( &custResource, 0, sizeof( custResource ) ); + COM_HexConvert( name + 4, 32, md5 ); + + if( HPAK_ResourceForHash( CUSTOM_RES_PATH, md5, &custResource )) + { + if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &custResource, &pbuf, &size )) + { + if( size ) + { + Netchan_CreateFileFragmentsFromBuffer( &cl->netchan, name, pbuf, size ); + Netchan_FragSend( &cl->netchan ); + Mem_Free( pbuf ); + } + } + } + } + else + { + SV_FailDownload( cl, name ); + } + + return true; +} + +/* +================== +SV_Spawn_f +================== +*/ +static qboolean SV_Spawn_f( sv_client_t *cl ) +{ + if( cl->state != cs_connected ) + return false; + + // handle the case of a level changing while a client was connecting + if( Q_atoi( Cmd_Argv( 1 )) != svs.spawncount ) + { + SV_New_f( cl ); + return true; + } + + SV_PutClientInServer( cl ); + + // if we are paused, tell the clients + if( sv.paused ) + { + MSG_BeginServerCmd( &sv.reliable_datagram, svc_setpause ); + MSG_WriteByte( &sv.reliable_datagram, sv.paused ); + SV_ClientPrintf( cl, "Server is paused.\n" ); + } + return true; +} + +/* +================== +SV_Begin_f +================== +*/ +static qboolean SV_Begin_f( sv_client_t *cl ) +{ + if( cl->state != cs_connected ) + return false; + + // now client is spawned + cl->state = cs_spawned; + return true; +} + +ucmd_t ucmds[] = +{ +{ "new", SV_New_f }, +{ "god", SV_Godmode_f }, +{ "begin", SV_Begin_f }, +{ "spawn", SV_Spawn_f }, +{ "pause", SV_Pause_f }, +{ "noclip", SV_Noclip_f }, +{ "log", SV_ServerLog_f }, +{ "setinfo", SV_SetInfo_f }, +{ "sendres", SV_SendRes_f }, +{ "notarget", SV_Notarget_f }, +{ "info", SV_ShowServerinfo_f }, +{ "dlfile", SV_DownloadFile_f }, +{ "disconnect", SV_Disconnect_f }, +{ "userinfo", SV_UpdateUserinfo_f }, +{ NULL, NULL } +}; + +/* +================== +SV_ExecuteUserCommand +================== +*/ +void SV_ExecuteClientCommand( sv_client_t *cl, char *s ) +{ + ucmd_t *u; + + Cmd_TokenizeString( s ); + + for( u = ucmds; u->name; u++ ) + { + if( !Q_strcmp( Cmd_Argv( 0 ), u->name )) + { + if( !u->func( cl )) + Con_Printf( "'%s' is not valid from the console\n", u->name ); + else MsgDev( D_NOTE, "ucmd->%s()\n", u->name ); + break; + } + } + + if( !u->name && sv.state == ss_active ) + { + // custom client commands + svgame.dllFuncs.pfnClientCommand( cl->edict ); + + if( !Q_strcmp( Cmd_Argv( 0 ), "fullupdate" )) + { + // resend the ambient sounds for demo recording + SV_RestartAmbientSounds(); + // resend all the decals for demo recording + SV_RestartDecals(); + // resend all the static ents for demo recording + SV_RestartStaticEnts(); + // resend the viewentity + SV_UpdateClientView( cl ); + } + } +} + +/* +================== +SV_TSourceEngineQuery +================== +*/ +void SV_TSourceEngineQuery( netadr_t from ) +{ + // A2S_INFO + char answer[1024] = ""; + int count = 0, bots = 0; + int index; + sizebuf_t buf; + + if( svs.clients ) + { + for( index = 0; index < svs.maxclients; index++ ) + { + if( svs.clients[index].state >= cs_connected ) + { + if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) + bots++; + else count++; + } + } + } + + MSG_Init( &buf, "TSourceEngineQuery", answer, sizeof( answer )); + + MSG_WriteByte( &buf, 'm' ); + MSG_WriteString( &buf, NET_AdrToString( net_local )); + MSG_WriteString( &buf, hostname.string ); + MSG_WriteString( &buf, sv.name ); + MSG_WriteString( &buf, GI->gamefolder ); + MSG_WriteString( &buf, GI->title ); + MSG_WriteByte( &buf, count ); + MSG_WriteByte( &buf, svs.maxclients ); + MSG_WriteByte( &buf, PROTOCOL_VERSION ); + MSG_WriteByte( &buf, host.type == HOST_DEDICATED ? 'D' : 'L' ); + MSG_WriteByte( &buf, 'W' ); + + if( Q_stricmp( GI->gamedir, "valve" )) + { + MSG_WriteByte( &buf, 1 ); // mod + MSG_WriteString( &buf, GI->game_url ); + MSG_WriteString( &buf, GI->update_url ); + MSG_WriteByte( &buf, 0 ); + MSG_WriteLong( &buf, (long)GI->version ); + MSG_WriteLong( &buf, GI->size ); + + if( GI->gamemode == 2 ) + MSG_WriteByte( &buf, 1 ); // multiplayer_only + else MSG_WriteByte( &buf, 0 ); + + if( Q_strstr( GI->game_dll, "hl." )) + MSG_WriteByte( &buf, 0 ); // Half-Life DLL + else MSG_WriteByte( &buf, 1 ); // Own DLL + } + else MSG_WriteByte( &buf, 0 ); // Half-Life + + MSG_WriteByte( &buf, GI->secure ); // unsecure + MSG_WriteByte( &buf, bots ); + + NET_SendPacket( NS_SERVER, MSG_GetNumBytesWritten( &buf ), MSG_GetData( &buf ), from ); +} + +/* +================= +SV_ConnectionlessPacket + +A connectionless packet has four leading 0xff +characters to distinguish it from a game channel. +Clients that are in the game can still send +connectionless packets. +================= +*/ +void SV_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) +{ + char *args; + char *pcmd, buf[MAX_SYSPATH]; + int len = sizeof( buf ); + + MSG_Clear( msg ); + MSG_ReadLong( msg );// skip the -1 marker + + args = MSG_ReadStringLine( msg ); + Cmd_TokenizeString( args ); + + pcmd = Cmd_Argv( 0 ); + Con_DPrintf( "SV_ConnectionlessPacket: %s : %s\n", NET_AdrToString( from ), pcmd ); + + if( !Q_strcmp( pcmd, "ping" )) SV_Ping( from ); + else if( !Q_strcmp( pcmd, "ack" )) SV_Ack( from ); + else if( !Q_strcmp( pcmd, "info" )) SV_Info( from ); + else if( !Q_strcmp( pcmd, "bandwidth" )) SV_TestBandWidth( from ); + else if( !Q_strcmp( pcmd, "getchallenge" )) SV_GetChallenge( from ); + else if( !Q_strcmp( pcmd, "connect" )) SV_ConnectClient( from ); + else if( !Q_strcmp( pcmd, "rcon" )) SV_RemoteCommand( from, msg ); + else if( !Q_strcmp( pcmd, "netinfo" )) SV_BuildNetAnswer( from ); + else if( !Q_strcmp( pcmd, "s" )) SV_AddToMaster( from, msg ); + else if( !Q_strcmp( pcmd, "T" "Source" )) SV_TSourceEngineQuery( from ); + else if( !Q_strcmp( pcmd, "i" )) NET_SendPacket( NS_SERVER, 5, "\xFF\xFF\xFF\xFFj", from ); // A2A_PING + else if( svgame.dllFuncs.pfnConnectionlessPacket( &from, args, buf, &len )) + { + // user out of band message (must be handled in CL_ConnectionlessPacket) + if( len > 0 ) Netchan_OutOfBand( NS_SERVER, from, len, buf ); + } + else MsgDev( D_ERROR, "bad connectionless packet from %s:\n%s\n", NET_AdrToString( from ), args ); +} + +/* +================== +SV_ParseClientMove + +The message usually contains all the movement commands +that were in the last three packets, so that the information +in dropped packets can be recovered. + +On very fast clients, there may be multiple usercmd packed into +each of the backup packets. +================== +*/ +static void SV_ParseClientMove( sv_client_t *cl, sizebuf_t *msg ) +{ + client_frame_t *frame; + int key, size, checksum1, checksum2; + int i, numbackup, totalcmds, numcmds; + usercmd_t nullcmd, *to, *from; + usercmd_t cmds[CMD_BACKUP]; + float packet_loss; + edict_t *player; + model_t *model; + + player = cl->edict; + + frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK]; + memset( &nullcmd, 0, sizeof( usercmd_t )); + memset( cmds, 0, sizeof( cmds )); + + key = MSG_GetRealBytesRead( msg ); + checksum1 = MSG_ReadByte( msg ); + packet_loss = MSG_ReadByte( msg ); + + numbackup = MSG_ReadByte( msg ); + numcmds = MSG_ReadByte( msg ); + + totalcmds = numcmds + numbackup; + net_drop -= (numcmds - 1); + + if( totalcmds < 0 || totalcmds >= CMD_MASK ) + { + MsgDev( D_ERROR, "SV_ParseClientMove: %s sending too many commands %i\n", cl->name, totalcmds ); + SV_DropClient( cl, false ); + return; + } + + from = &nullcmd; // first cmd are starting from null-compressed usercmd_t + + for( i = totalcmds - 1; i >= 0; i-- ) + { + to = &cmds[i]; + MSG_ReadDeltaUsercmd( msg, from, to ); + from = to; // get new baseline + } + + if( cl->state != cs_spawned ) + return; + + // if the checksum fails, ignore the rest of the packet + size = MSG_GetRealBytesRead( msg ) - key - 1; + checksum2 = CRC32_BlockSequence( msg->pData + key + 1, size, cl->netchan.incoming_sequence ); + + if( checksum2 != checksum1 ) + { + MsgDev( D_ERROR, "SV_UserMove: failed command checksum for %s (%d != %d)\n", cl->name, checksum2, checksum1 ); + return; + } + + cl->packet_loss = packet_loss; + + // check for pause or frozen + if( sv.paused || !CL_IsInGame() || SV_PlayerIsFrozen( player )) + { + for( i = 0; i < numcmds; i++ ) + { + cmds[i].msec = 0; + cmds[i].forwardmove = 0; + cmds[i].sidemove = 0; + cmds[i].upmove = 0; + cmds[i].buttons = 0; + + if( SV_PlayerIsFrozen( player )) + cmds[i].impulse = 0; + + VectorCopy( cmds[i].viewangles, player->v.v_angle ); + } + net_drop = 0; + } + else + { + if( !player->v.fixangle ) + VectorCopy( cmds[0].viewangles, player->v.v_angle ); + } + + SV_EstablishTimeBase( cl, cmds, net_drop, numbackup, numcmds ); + + if( net_drop < 24 ) + { + while( net_drop > numbackup ) + { + SV_RunCmd( cl, &cl->lastcmd, 0 ); + net_drop--; + } + + while( net_drop > 0 ) + { + i = numcmds + net_drop - 1; + SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i ); + net_drop--; + } + } + + for( i = numcmds - 1; i >= 0; i-- ) + { + SV_RunCmd( cl, &cmds[i], cl->netchan.incoming_sequence - i ); + } + + cl->lastcmd = cmds[0]; + + // adjust latency time by 1/2 last client frame since + // the message probably arrived 1/2 through client's frame loop + frame->ping_time -= ( cl->lastcmd.msec * 0.5f ) / 1000.0f; + frame->ping_time = Q_max( 0.0f, frame->ping_time ); + model = SV_ModelHandle( player->v.modelindex ); + + if( model && model->type == mod_studio ) + { + // g-cont. yes we using svgame.globals->time instead of sv.time + if( player->v.animtime > svgame.globals->time + sv.frametime ) + player->v.animtime = svgame.globals->time + sv.frametime; + } +} + +/* +=================== +SV_ParseResourceList + +Parse resource list +=================== +*/ +void SV_ParseResourceList( sv_client_t *cl, sizebuf_t *msg ) +{ + int totalsize; + resource_t *resource; + int i, total; + resourceinfo_t ri; + + total = MSG_ReadShort( msg ); + + SV_ClearResourceList( &cl->resourcesneeded ); + SV_ClearResourceList( &cl->resourcesonhand ); + + for( i = 0; i < total; i++ ) + { + resource = Z_Malloc( sizeof( resource_t ) ); + Q_strncpy( resource->szFileName, MSG_ReadString( msg ), sizeof( resource->szFileName )); + resource->type = MSG_ReadByte( msg ); + resource->nIndex = MSG_ReadShort( msg ); + resource->nDownloadSize = MSG_ReadLong( msg ); + resource->ucFlags = MSG_ReadByte( msg ); + resource->pNext = NULL; + resource->pPrev = NULL; + ClearBits( resource->ucFlags, RES_WASMISSING ); + + if( FBitSet( resource->ucFlags, RES_CUSTOM )) + MSG_ReadBytes( msg, resource->rgucMD5_hash, 16 ); + + if( resource->type > t_world || resource->nDownloadSize > 1024 * 1024 * 1024 ) + { + SV_ClearResourceList( &cl->resourcesneeded ); + SV_ClearResourceList( &cl->resourcesonhand ); + return; + } + SV_AddToResourceList( resource, &cl->resourcesneeded ); + } + + if( sv_allow_upload.value ) + MsgDev( D_REPORT, "Verifying and uploading resources...\n" ); + + totalsize = COM_SizeofResourceList( &cl->resourcesneeded, &ri ); + + if( totalsize != 0 && sv_allow_upload.value ) + { + if( totalsize != 0 ) + { + Con_DPrintf( "Custom resources total %.2fK\n", totalsize / 1024.0 ); + + if ( ri.info[t_model].size != 0 ) + Con_DPrintf( " Models: %.2fK\n", ri.info[t_model].size / 1024.0 ); + + if ( ri.info[t_sound].size != 0 ) + Con_DPrintf( " Sounds: %.2fK\n", ri.info[t_sound].size / 1024.0 ); + + if ( ri.info[t_decal].size != 0 ) + Con_DPrintf( " Decals: %.2fK\n", ri.info[t_decal].size / 1024.0 ); + + if ( ri.info[t_skin].size != 0 ) + Con_DPrintf( " Skins : %.2fK\n", ri.info[t_skin].size / 1024.0 ); + + if ( ri.info[t_generic].size != 0 ) + Con_DPrintf( " Generic : %.2fK\n", ri.info[t_generic].size / 1024.0 ); + + if ( ri.info[t_eventscript].size != 0 ) + Con_DPrintf( " Events : %.2fK\n", ri.info[t_eventscript].size / 1024.0 ); + + Con_DPrintf( "----------------------\n" ); + } + + totalsize = SV_EstimateNeededResources( cl ); + + if( totalsize > sv_uploadmax.value * 1024 * 1024 ) + { + SV_ClearResourceList( &cl->resourcesneeded ); + SV_ClearResourceList( &cl->resourcesonhand ); + return; + } + MsgDev( D_REPORT, "resources to request: %s\n", Q_memprint( totalsize )); + } + + cl->upstate = us_processing; + SV_BatchUploadRequest( cl ); +} + +/* +=================== +SV_ParseCvarValue + +Parse a requested value from client cvar +=================== +*/ +void SV_ParseCvarValue( sv_client_t *cl, sizebuf_t *msg ) +{ + const char *value = MSG_ReadString( msg ); + + if( svgame.dllFuncs2.pfnCvarValue != NULL ) + svgame.dllFuncs2.pfnCvarValue( cl->edict, value ); + MsgDev( D_REPORT, "Cvar query response: name:%s, value:%s\n", cl->name, value ); +} + +/* +=================== +SV_ParseCvarValue2 + +Parse a requested value from client cvar +=================== +*/ +void SV_ParseCvarValue2( sv_client_t *cl, sizebuf_t *msg ) +{ + string name, value; + int requestID = MSG_ReadLong( msg ); + + Q_strcpy( name, MSG_ReadString( msg )); + Q_strcpy( value, MSG_ReadString( msg )); + + if( svgame.dllFuncs2.pfnCvarValue2 != NULL ) + svgame.dllFuncs2.pfnCvarValue2( cl->edict, requestID, name, value ); + MsgDev( D_REPORT, "Cvar query response: name:%s, request ID %d, cvar:%s, value:%s\n", cl->name, requestID, name, value ); +} + +/* +=================== +SV_ExecuteClientMessage + +Parse a client packet +=================== +*/ +void SV_ExecuteClientMessage( sv_client_t *cl, sizebuf_t *msg ) +{ + qboolean move_issued = false; + client_frame_t *frame; + char *s; + int c; + + ASSERT( cl->frames != NULL ); + + // calc ping time + frame = &cl->frames[cl->netchan.incoming_acknowledged & SV_UPDATE_MASK]; + + // ping time doesn't factor in message interval, either + frame->ping_time = host.realtime - frame->senttime - cl->cl_updaterate; + + // on first frame ( no senttime ) don't skew ping + if( frame->senttime == 0.0f ) frame->ping_time = 0.0f; + + // don't skew ping based on signon stuff either + if(( host.realtime - cl->connection_started ) < 2.0f && ( frame->ping_time > 0.0 )) + frame->ping_time = 0.0f; + + cl->latency = SV_CalcClientTime( cl ); + cl->delta_sequence = -1; // no delta unless requested + + // read optional clientCommand strings + while( cl->state != cs_zombie ) + { + if( MSG_CheckOverflow( msg )) + { + MsgDev( D_ERROR, "SV_ReadClientMessage: clc_bad\n" ); + SV_DropClient( cl, false ); + return; + } + + // end of message + if( MSG_GetNumBitsLeft( msg ) < 8 ) + break; + + c = MSG_ReadClientCmd( msg ); + + switch( c ) + { + case clc_nop: + break; + case clc_delta: + cl->delta_sequence = MSG_ReadByte( msg ); + break; + case clc_move: + if( move_issued ) return; // someone is trying to cheat... + move_issued = true; + SV_ParseClientMove( cl, msg ); + break; + case clc_stringcmd: + s = MSG_ReadString( msg ); + // malicious users may try using too many string commands + SV_ExecuteClientCommand( cl, s ); + if( cl->state == cs_zombie ) return; // disconnect command + break; + case clc_resourcelist: + SV_ParseResourceList( cl, msg ); + break; + case clc_fileconsistency: + SV_ParseConsistencyResponse( cl, msg ); + break; + case clc_requestcvarvalue: + SV_ParseCvarValue( cl, msg ); + break; + case clc_requestcvarvalue2: + SV_ParseCvarValue2( cl, msg ); + break; + default: + MsgDev( D_ERROR, "clc_bad\n" ); + SV_DropClient( cl, false ); + return; + } + } + } \ No newline at end of file diff --git a/engine/server/sv_cmds.c b/engine/server/sv_cmds.c new file mode 100644 index 00000000..134c0be6 --- /dev/null +++ b/engine/server/sv_cmds.c @@ -0,0 +1,898 @@ +/* +sv_cmds.c - server console commands +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" + +/* +================= +SV_ClientPrintf + +Sends text across to be displayed if the level passes +================= +*/ +void SV_ClientPrintf( sv_client_t *cl, char *fmt, ... ) +{ + char string[MAX_SYSPATH]; + va_list argptr; + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + return; + + va_start( argptr, fmt ); + Q_vsprintf( string, fmt, argptr ); + va_end( argptr ); + + MSG_BeginServerCmd( &cl->netchan.message, svc_print ); + MSG_WriteString( &cl->netchan.message, string ); +} + +/* +================= +SV_BroadcastPrintf + +Sends text to all active clients +================= +*/ +void SV_BroadcastPrintf( sv_client_t *ignore, char *fmt, ... ) +{ + char string[MAX_SYSPATH]; + va_list argptr; + sv_client_t *cl; + int i; + + va_start( argptr, fmt ); + Q_vsprintf( string, fmt, argptr ); + va_end( argptr ); + + if( sv.state == ss_active ) + { + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + if( cl == ignore || cl->state != cs_spawned ) + continue; + + MSG_BeginServerCmd( &cl->netchan.message, svc_print ); + MSG_WriteString( &cl->netchan.message, string ); + } + } + + // echo to console + Con_DPrintf( string ); +} + +/* +================= +SV_BroadcastCommand + +Sends text to all active clients +================= +*/ +void SV_BroadcastCommand( const char *fmt, ... ) +{ + char string[MAX_SYSPATH]; + va_list argptr; + + if( sv.state == ss_dead ) + return; + + va_start( argptr, fmt ); + Q_vsprintf( string, fmt, argptr ); + va_end( argptr ); + + MSG_BeginServerCmd( &sv.reliable_datagram, svc_stufftext ); + MSG_WriteString( &sv.reliable_datagram, string ); +} + +/* +================== +SV_SetPlayer + +Sets sv_client and sv_player to the player with idnum Cmd_Argv(1) +================== +*/ +static sv_client_t *SV_SetPlayer( void ) +{ + char *s; + sv_client_t *cl; + int i, idnum; + + if( !svs.clients || sv.background ) + return NULL; + + if( svs.maxclients == 1 || Cmd_Argc() < 2 ) + { + // special case for local client + return svs.clients; + } + + s = Cmd_Argv( 1 ); + + // numeric values are just slot numbers + if( Q_isdigit( s ) || (s[0] == '-' && Q_isdigit( s + 1 ))) + { + idnum = Q_atoi( s ); + + if( idnum < 0 || idnum >= svs.maxclients ) + { + Con_Printf( "Bad client slot: %i\n", idnum ); + return NULL; + } + + cl = &svs.clients[idnum]; + + if( !cl->state ) + { + Con_Printf( "Client %i is not active\n", idnum ); + return NULL; + } + return cl; + } + + // check for a name match + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( !cl->state ) continue; + + if( !Q_strcmp( cl->name, s )) + return cl; + } + + Con_Printf( "Userid %s is not on the server\n", s ); + return NULL; +} + +/* +================== +SV_ValidateMap + +check map for typically errors +================== +*/ +qboolean SV_ValidateMap( const char *pMapName, qboolean check_spawn ) +{ + char *spawn_entity; + int flags; + + // determine spawn entity classname + if( !check_spawn || (int)sv_maxclients->value <= 1 ) + spawn_entity = GI->sp_entity; + else spawn_entity = GI->mp_entity; + + flags = SV_MapIsValid( pMapName, spawn_entity, NULL ); + + if( FBitSet( flags, MAP_INVALID_VERSION )) + { + Con_Printf( S_ERROR "map %s is invalid or not supported\n", pMapName ); + return false; + } + + if( !FBitSet( flags, MAP_IS_EXIST )) + { + Con_Printf( S_ERROR "map %s doesn't exist\n", pMapName ); + return false; + } + + if( check_spawn && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) + { + Con_Printf( S_ERROR "map %s doesn't have a valid spawnpoint\n", pMapName ); + return false; + } + + return true; +} + +/* +================== +SV_Map_f + +Goes directly to a given map without any savegame archiving. +For development work +================== +*/ +void SV_Map_f( void ) +{ + char mapname[MAX_QPATH]; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "map \n" ); + return; + } + + // hold mapname to other place + Q_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname )); + COM_StripExtension( mapname ); + + if( !SV_ValidateMap( mapname, true )) + return; + + Cvar_DirectSet( sv_hostmap, mapname ); + COM_LoadLevel( mapname, false ); +} + +/* +================== +SV_MapBackground_f + +Set background map (enable physics in menu) +================== +*/ +void SV_MapBackground_f( void ) +{ + char mapname[MAX_QPATH]; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "map_background \n" ); + return; + } + + if( SV_Active() && !sv.background ) + { + MsgDev( D_ERROR, "can't set background map while game is active\n" ); + return; + } + + // hold mapname to other place + Q_strncpy( mapname, Cmd_Argv( 1 ), sizeof( mapname )); + COM_StripExtension( mapname ); + + if( !SV_ValidateMap( mapname, false )) + return; + + // background map is always run as singleplayer + Cvar_FullSet( "maxplayers", "1", FCVAR_LATCH ); + Cvar_FullSet( "deathmatch", "0", FCVAR_LATCH ); + Cvar_FullSet( "coop", "0", FCVAR_LATCH ); + + COM_LoadLevel( mapname, true ); +} + +/* +============== +SV_NewGame_f + +============== +*/ +void SV_NewGame_f( void ) +{ + if( Cmd_Argc() != 1 ) + { + Con_Printf( S_USAGE "newgame\n" ); + return; + } + + COM_NewGame( GI->startmap ); +} + +/* +============== +SV_HazardCourse_f + +============== +*/ +void SV_HazardCourse_f( void ) +{ + if( Cmd_Argc() != 1 ) + { + Con_Printf( S_USAGE "hazardcourse\n" ); + return; + } + + // special case for Gunman Chronicles: playing avi-file + if( FS_FileExists( va( "media/%s.avi", GI->trainmap ), false )) + { + Cbuf_AddText( va( "wait; movie %s\n", GI->trainmap )); + Host_EndGame( true, DEFAULT_ENDGAME_MESSAGE ); + } + else COM_NewGame( GI->trainmap ); +} + +/* +============== +SV_Load_f + +============== +*/ +void SV_Load_f( void ) +{ + string path; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "load \n" ); + return; + } + + Q_snprintf( path, sizeof( path ), "%s%s.sav", DEFAULT_SAVE_DIRECTORY, Cmd_Argv( 1 )); + SV_LoadGame( path ); +} + +/* +============== +SV_QuickLoad_f + +============== +*/ +void SV_QuickLoad_f( void ) +{ + Cbuf_AddText( "echo Quick Loading...; wait; load quick" ); +} + +/* +============== +SV_Save_f + +============== +*/ +void SV_Save_f( void ) +{ + switch( Cmd_Argc( )) + { + case 1: + SV_SaveGame( "new" ); + break; + case 2: + SV_SaveGame( Cmd_Argv( 1 )); + break; + default: + Con_Printf( S_USAGE "save \n" ); + break; + } +} + +/* +============== +SV_QuickSave_f + +============== +*/ +void SV_QuickSave_f( void ) +{ + Cbuf_AddText( "echo Quick Saving...; wait; save quick" ); +} + +/* +============== +SV_DeleteSave_f + +============== +*/ +void SV_DeleteSave_f( void ) +{ + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "killsave \n" ); + return; + } + + // delete save and saveshot + FS_Delete( va( "%s%s.sav", DEFAULT_SAVE_DIRECTORY, Cmd_Argv( 1 ))); + FS_Delete( va( "%s%s.bmp", DEFAULT_SAVE_DIRECTORY, Cmd_Argv( 1 ))); +} + +/* +============== +SV_AutoSave_f + +============== +*/ +void SV_AutoSave_f( void ) +{ + if( Cmd_Argc() != 1 ) + { + Con_Printf( S_USAGE "autosave\n" ); + return; + } + + SV_SaveGame( "autosave" ); +} + +/* +================== +SV_Restart_f + +restarts current level +================== +*/ +void SV_Restart_f( void ) +{ + // because restart can be multiple issued + if( sv.state != ss_active ) + return; + COM_LoadLevel( sv.name, sv.background ); +} + +/* +================== +SV_Reload_f + +continue from latest savedgame +================== +*/ +void SV_Reload_f( void ) +{ + // because reload can be multiple issued + if( GameState->nextstate != STATE_RUNFRAME ) + return; + + if( !SV_LoadGame( SV_GetLatestSave( ))) + COM_LoadLevel( sv_hostmap->string, false ); +} + +/* +================== +SV_Kick_f + +Kick a user off of the server +================== +*/ +void SV_Kick_f( void ) +{ + sv_client_t *cl; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "kick | \n" ); + return; + } + + if(( cl = SV_SetPlayer( )) == NULL ) + return; + + if( NET_IsLocalAddress( cl->netchan.remote_address )) + { + Con_Printf( "The local player cannot be kicked!\n" ); + return; + } + + Log_Printf( "Kick: \"%s<%i>\" was kicked\n", cl->name, cl->userid ); + SV_BroadcastPrintf( cl, "%s was kicked\n", cl->name ); + SV_ClientPrintf( cl, "You were kicked from the game\n" ); + SV_DropClient( cl, false ); +} + +/* +================== +SV_Kill_f +================== +*/ +void SV_Kill_f( void ) +{ + sv_client_t *cl; + + if(( cl = SV_SetPlayer( )) == NULL ) + return; + + if( !SV_IsValidEdict( cl->edict )) + return; + + if( cl->edict->v.health <= 0.0f ) + { + SV_ClientPrintf( cl, "Can't suicide - already dead!\n"); + return; + } + + svgame.dllFuncs.pfnClientKill( cl->edict ); +} + +/* +================== +SV_EntPatch_f +================== +*/ +void SV_EntPatch_f( void ) +{ + const char *mapname; + + if( Cmd_Argc() < 2 ) + { + if( sv.state != ss_dead ) + { + mapname = sv.name; + } + else + { + Con_Printf( S_USAGE "entpatch \n" ); + return; + } + } + else mapname = Cmd_Argv( 1 ); + + SV_WriteEntityPatch( mapname ); +} + +/* +================ +SV_Status_f +================ +*/ +void SV_Status_f( void ) +{ + sv_client_t *cl; + int i; + + if( !svs.clients || sv.background ) + { + Con_Printf( "^3no server running.\n" ); + return; + } + + Con_Printf( "map: %s\n", sv.name ); + Con_Printf( "num score ping name lastmsg address port \n" ); + Con_Printf( "--- ----- ------- --------------- ------- --------------------- ------\n" ); + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + int j, l; + char *s; + + if( !cl->state ) continue; + + Con_Printf( "%3i ", i ); + Con_Printf( "%5i ", (int)cl->edict->v.frags ); + + if( cl->state == cs_connected ) Con_Printf( "Connect" ); + else if( cl->state == cs_zombie ) Con_Printf( "Zombie " ); + else if( FBitSet( cl->flags, FCL_FAKECLIENT )) Con_Printf( "Bot " ); + else Con_Printf( "%7i ", SV_CalcPing( cl )); + + Con_Printf( "%s", cl->name ); + l = 24 - Q_strlen( cl->name ); + for( j = 0; j < l; j++ ) Con_Printf( " " ); + Con_Printf( "%g ", ( host.realtime - cl->netchan.last_received )); + s = NET_BaseAdrToString( cl->netchan.remote_address ); + Con_Printf( "%s", s ); + l = 22 - Q_strlen( s ); + for( j = 0; j < l; j++ ) Con_Printf( " " ); + Con_Printf( "%5i", cl->netchan.qport ); + Con_Printf( "\n" ); + } + Con_Printf( "\n" ); +} + +/* +================== +SV_ConSay_f +================== +*/ +void SV_ConSay_f( void ) +{ + char *p, text[MAX_SYSPATH]; + + if( Cmd_Argc() < 2 ) return; + + if( !svs.clients || sv.background ) + { + Con_Printf( "^3no server running.\n" ); + return; + } + + Q_snprintf( text, sizeof( text ), "%s: ", Cvar_VariableString( "hostname" )); + p = Cmd_Args(); + + if( *p == '"' ) + { + p++; + p[Q_strlen(p) - 1] = 0; + } + + Q_strncat( text, p, MAX_SYSPATH ); + SV_BroadcastPrintf( NULL, "%s\n", text ); + Log_Printf( "Server say: \"%s\"\n", p ); +} + +/* +================== +SV_Heartbeat_f +================== +*/ +void SV_Heartbeat_f( void ) +{ + svs.last_heartbeat = MAX_HEARTBEAT; +} + +/* +=========== +SV_ServerInfo_f + +Examine or change the serverinfo string +=========== +*/ +void SV_ServerInfo_f( void ) +{ + convar_t *var; + + if( Cmd_Argc() == 1 ) + { + Con_Printf( "Server info settings:\n" ); + Info_Print( svs.serverinfo ); + Con_Printf( "Total %i symbols\n", Q_strlen( svs.serverinfo )); + return; + } + + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "serverinfo [ ]\n"); + return; + } + + if( Cmd_Argv(1)[0] == '*' ) + { + Con_Printf( "Star variables cannot be changed.\n" ); + return; + } + + // if this is a cvar, change it too + var = Cvar_FindVar( Cmd_Argv( 1 )); + if( var ) + { + freestring( var->string ); // free the old value string + var->string = copystring( Cmd_Argv( 2 )); + var->value = Q_atof( var->string ); + } + + Info_SetValueForStarKey( svs.serverinfo, Cmd_Argv( 1 ), Cmd_Argv( 2 ), MAX_SERVERINFO_STRING ); + SV_BroadcastCommand( "fullserverinfo \"%s\"\n", SV_Serverinfo( )); +} + +/* +=========== +SV_LocalInfo_f + +Examine or change the localinfo string +=========== +*/ +void SV_LocalInfo_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Con_Printf( "Local info settings:\n" ); + Info_Print( svs.localinfo ); + Con_Printf( "Total %i symbols\n", Q_strlen( svs.localinfo )); + return; + } + + if( Cmd_Argc() != 3 ) + { + Con_Printf( S_USAGE "localinfo [ ]\n"); + return; + } + + if( Cmd_Argv(1)[0] == '*' ) + { + Con_Printf( "Star variables cannot be changed.\n" ); + return; + } + + Info_SetValueForStarKey( svs.localinfo, Cmd_Argv(1), Cmd_Argv(2), MAX_LOCALINFO_STRING ); +} + +/* +=========== +SV_ClientInfo_f + +Examine all a users info strings +=========== +*/ +void SV_ClientInfo_f( void ) +{ + sv_client_t *cl; + + if( Cmd_Argc() != 2 ) + { + Con_Printf( S_USAGE "clientinfo \n" ); + return; + } + + if(( cl = SV_SetPlayer( )) == NULL ) + return; + + Con_Printf( "userinfo\n" ); + Con_Printf( "--------\n" ); + Info_Print( cl->userinfo ); + +} + +/* +=============== +SV_KillServer_f + +Kick everyone off, possibly in preparation for a new game +=============== +*/ +void SV_KillServer_f( void ) +{ + Host_ShutdownServer(); +} + +/* +=============== +SV_PlayersOnly_f + +disable plhysics but players +=============== +*/ +void SV_PlayersOnly_f( void ) +{ + if( !Cvar_VariableInteger( "sv_cheats" )) return; + + sv.playersonly ^= 1; + + SV_BroadcastPrintf( NULL, "%s game physic\n", sv.playersonly ? "Freeze" : "Resume" ); +} + +/* +=============== +SV_EdictUsage_f + +=============== +*/ +void SV_EdictUsage_f( void ) +{ + int active; + + if( sv.state != ss_active ) + { + Con_Printf( "^3no server running.\n" ); + return; + } + + active = pfnNumberOfEntities(); + Con_Printf( "%5i edicts is used\n", active ); + Con_Printf( "%5i edicts is free\n", GI->max_edicts - active ); + Con_Printf( "%5i total\n", GI->max_edicts ); +} + +/* +=============== +SV_EntityInfo_f + +=============== +*/ +void SV_EntityInfo_f( void ) +{ + edict_t *ent; + int i; + + if( sv.state != ss_active ) + { + Con_Printf( "^3no server running.\n" ); + return; + } + + for( i = 0; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + if( !SV_IsValidEdict( ent )) continue; + + Con_Printf( "%5i origin: %.f %.f %.f", i, ent->v.origin[0], ent->v.origin[1], ent->v.origin[2] ); + + if( ent->v.classname ) + Con_Printf( ", class: %s", STRING( ent->v.classname )); + + if( ent->v.globalname ) + Con_Printf( ", global: %s", STRING( ent->v.globalname )); + + if( ent->v.targetname ) + Con_Printf( ", name: %s", STRING( ent->v.targetname )); + + if( ent->v.target ) + Con_Printf( ", target: %s", STRING( ent->v.target )); + + if( ent->v.model ) + Con_Printf( ", model: %s", STRING( ent->v.model )); + + Con_Printf( "\n" ); + } +} +/* +================== +SV_InitHostCommands + +commands that create server +is available always +================== +*/ +void SV_InitHostCommands( void ) +{ + Cmd_AddCommand( "map", SV_Map_f, "start new level" ); + + if( host.type == HOST_NORMAL ) + { + Cmd_AddCommand( "newgame", SV_NewGame_f, "begin new game" ); + Cmd_AddCommand( "hazardcourse", SV_HazardCourse_f, "starting a Hazard Course" ); + Cmd_AddCommand( "map_background", SV_MapBackground_f, "set background map" ); + Cmd_AddCommand( "load", SV_Load_f, "load a saved game file" ); + Cmd_AddCommand( "loadquick", SV_QuickLoad_f, "load a quick-saved game file" ); + } +} + +/* +================== +SV_InitOperatorCommands +================== +*/ +void SV_InitOperatorCommands( void ) +{ + Cmd_AddCommand( "heartbeat", SV_Heartbeat_f, "send a heartbeat to the master server" ); + Cmd_AddCommand( "kick", SV_Kick_f, "kick a player off the server by number or name" ); + Cmd_AddCommand( "kill", SV_Kill_f, "die instantly" ); + Cmd_AddCommand( "status", SV_Status_f, "print server status information" ); + Cmd_AddCommand( "localinfo", SV_LocalInfo_f, "examine or change the localinfo string" ); + Cmd_AddCommand( "serverinfo", SV_ServerInfo_f, "examine or change the serverinfo string" ); + Cmd_AddCommand( "clientinfo", SV_ClientInfo_f, "print user infostring (player num required)" ); + Cmd_AddCommand( "playersonly", SV_PlayersOnly_f, "freezes time, except for players" ); + Cmd_AddCommand( "restart", SV_Restart_f, "restarting current level" ); + Cmd_AddCommand( "reload", SV_Reload_f, "continue from latest save or restart level" ); + Cmd_AddCommand( "entpatch", SV_EntPatch_f, "write entity patch to allow external editing" ); + Cmd_AddCommand( "edict_usage", SV_EdictUsage_f, "show info about edicts usage" ); + Cmd_AddCommand( "entity_info", SV_EntityInfo_f, "show more info about edicts" ); + Cmd_AddCommand( "shutdownserver", SV_KillServer_f, "shutdown current server" ); + + if( host.type == HOST_NORMAL ) + { + Cmd_AddCommand( "save", SV_Save_f, "save the game to a file" ); + Cmd_AddCommand( "savequick", SV_QuickSave_f, "save the game to the quicksave" ); + Cmd_AddCommand( "autosave", SV_AutoSave_f, "save the game to 'autosave' file" ); + Cmd_AddCommand( "killsave", SV_DeleteSave_f, "delete a saved game file and saveshot" ); + } + else if( host.type == HOST_DEDICATED ) + { + Cmd_AddCommand( "say", SV_ConSay_f, "send a chat message to everyone on the server" ); + } +} + +/* +================== +SV_KillOperatorCommands +================== +*/ +void SV_KillOperatorCommands( void ) +{ + Cvar_Reset( "public" ); + Cvar_Reset( "sv_lan" ); + + Cmd_RemoveCommand( "heartbeat" ); + Cmd_RemoveCommand( "kick" ); + Cmd_RemoveCommand( "kill" ); + Cmd_RemoveCommand( "status" ); + Cmd_RemoveCommand( "localinfo" ); + Cmd_RemoveCommand( "serverinfo" ); + Cmd_RemoveCommand( "clientinfo" ); + Cmd_RemoveCommand( "playersonly" ); + Cmd_RemoveCommand( "restart" ); + Cmd_RemoveCommand( "reload" ); + Cmd_RemoveCommand( "entpatch" ); + Cmd_RemoveCommand( "edict_usage" ); + Cmd_RemoveCommand( "entity_info" ); + Cmd_RemoveCommand( "shutdownserver" ); + + if( host.type == HOST_NORMAL ) + { + Cmd_RemoveCommand( "save" ); + Cmd_RemoveCommand( "savequick" ); + Cmd_RemoveCommand( "killsave" ); + Cmd_RemoveCommand( "autosave" ); + } + else if( host.type == HOST_DEDICATED ) + { + Cmd_RemoveCommand( "say" ); + } +} \ No newline at end of file diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c new file mode 100644 index 00000000..e0fb73c3 --- /dev/null +++ b/engine/server/sv_custom.c @@ -0,0 +1,578 @@ +/* +sv_custom.c - downloading custom resources +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" + +void SV_CreateCustomizationList( sv_client_t *cl ) +{ + resource_t *pResource; + customization_t *pList, *pCust; + qboolean bFound; + int nLumps; + + cl->customdata.pNext = NULL; + + for( pResource = cl->resourcesonhand.pNext; pResource != &cl->resourcesonhand; pResource = pResource->pNext ) + { + bFound = false; + + for( pList = cl->customdata.pNext; pList != NULL; pList = pList->pNext ) + { + if( !memcmp( pList->resource.rgucMD5_hash, pResource->rgucMD5_hash, 16 )) + { + bFound = true; + break; + } + } + + if( !bFound ) + { + nLumps = 0; + + if( COM_CreateCustomization( &cl->customdata, pResource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA, &pCust, &nLumps )) + { + pCust->nUserData2 = nLumps; + svgame.dllFuncs.pfnPlayerCustomization( cl->edict, pCust ); + } + else + { + if( sv_allow_upload.value ) + Con_Printf( "Ignoring invalid custom decal from %s\n", cl->name ); + else Con_Printf( "Ignoring custom decal from %s\n", cl->name ); + } + } + else + { + Con_Printf( S_WARN "SV_CreateCustomization list, ignoring dup. resource for player %s\n", cl->name ); + } + } +} + +qboolean SV_FileInConsistencyList( const char *filename, consistency_t **ppout ) +{ + int i; + + if( ppout != NULL ) + *ppout = NULL; + + for( i = 0; i < MAX_MODELS; i++ ) + { + consistency_t *pc = &sv.consistency_list[i]; + + if( !pc->filename ) + break; + + if( !Q_stricmp( pc->filename, filename )) + { + if( ppout != NULL ) + *ppout = pc; + return true; + } + } + + return false; +} + +void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg ) +{ + int i, c, idx, value; + byte readbuffer[32]; + byte nullbuffer[32]; + byte resbuffer[32]; + qboolean invalid_type; + vec3_t cmins, cmaxs; + int badresindex; + vec3_t mins, maxs; + FORCE_TYPE ft; + resource_t *r; + + memset( nullbuffer, 0, sizeof( nullbuffer )); + invalid_type = false; + badresindex = 0; + c = 0; + + while( MSG_ReadOneBit( msg )) + { + idx = MSG_ReadUBitLong( msg, MAX_MODEL_BITS ); + if( idx < 0 || idx >= sv.num_resources ) + break; + + r = &sv.resources[idx]; + + if( !FBitSet( r->ucFlags, RES_CHECKFILE )) + break; + + memcpy( readbuffer, r->rguc_reserved, 32 ); + + if( !memcmp( readbuffer, nullbuffer, 32 )) + { + value = MSG_ReadUBitLong( msg, 32 ); + + // will be compare only first 4 bytes + if( value != *(int *)r->rgucMD5_hash ) + badresindex = idx + 1; + } + else + { + MSG_ReadBytes( msg, cmins, sizeof( cmins )); + MSG_ReadBytes( msg, cmaxs, sizeof( cmaxs )); + + memcpy( resbuffer, r->rguc_reserved, 32 ); + ft = resbuffer[0]; + + switch( ft ) + { + case force_model_samebounds: + memcpy( mins, &resbuffer[0x01], sizeof( mins )); + memcpy( maxs, &resbuffer[0x0D], sizeof( maxs )); + + if( !VectorCompare( cmins, mins ) || !VectorCompare( cmaxs, maxs )) + badresindex = idx + 1; + break; + case force_model_specifybounds: + memcpy( mins, &resbuffer[0x01], sizeof( mins )); + memcpy( maxs, &resbuffer[0x0D], sizeof( maxs )); + + for( i = 0; i < 3; i++ ) + { + if( cmins[i] < mins[i] || cmaxs[i] > maxs[i] ) + { + badresindex = idx + 1; + break; + } + } + break; + default: + invalid_type = true; + break; + } + } + + if( invalid_type ) + break; + c++; + } + + if( sv.num_consistency != c ) + { + Con_Printf( S_WARN "%s:%s sent bad file data\n", cl->name, NET_AdrToString( cl->netchan.remote_address )); + SV_DropClient( cl, false ); + return; + } + + if( badresindex != 0 ) + { + char dropmessage[256]; + + dropmessage[0] = 0; + if( svgame.dllFuncs.pfnInconsistentFile( cl->edict, sv.resources[badresindex - 1].szFileName, dropmessage )) + { + if( COM_CheckString( dropmessage )) + SV_ClientPrintf( cl, dropmessage ); + SV_DropClient( cl, false ); + } + } + else + { + ClearBits( cl->flags, FCL_FORCE_UNMODIFIED ); + } +} + +void SV_TransferConsistencyInfo( void ) +{ + vec3_t mins, maxs; + int i, total = 0; + resource_t *pResource; + string filepath; + consistency_t *pc; + + for( i = 0; i < sv.num_resources; i++ ) + { + pResource = &sv.resources[i]; + + if( FBitSet( pResource->ucFlags, RES_CHECKFILE )) + continue; // already checked? + + if( !SV_FileInConsistencyList( pResource->szFileName, &pc )) + continue; + + SetBits( pResource->ucFlags, RES_CHECKFILE ); + + if( pResource->type == t_sound ) + Q_snprintf( filepath, sizeof( filepath ), "%s%s", DEFAULT_SOUNDPATH, pResource->szFileName ); + else Q_strncpy( filepath, pResource->szFileName, sizeof( filepath )); + + MD5_HashFile( pResource->rgucMD5_hash, filepath, NULL ); + + if( pResource->type == t_model ) + { + switch( pc->check_type ) + { + case force_exactfile: + // only MD5 hash compare + break; + case force_model_samebounds: + if( !Mod_GetStudioBounds( filepath, mins, maxs )) + Host_Error( "Mod_GetStudioBounds: couldn't get bounds for %s\n", filepath ); + memcpy( &pResource->rguc_reserved[0x01], mins, sizeof( mins )); + memcpy( &pResource->rguc_reserved[0x0D], maxs, sizeof( maxs )); + pResource->rguc_reserved[0] = pc->check_type; + break; + case force_model_specifybounds: + memcpy( &pResource->rguc_reserved[0x01], pc->mins, sizeof( pc->mins )); + memcpy( &pResource->rguc_reserved[0x0D], pc->maxs, sizeof( pc->maxs )); + pResource->rguc_reserved[0] = pc->check_type; + break; + } + } + total++; + } + + sv.num_consistency = total; +} + +void SV_SendConsistencyList( sv_client_t *cl, sizebuf_t *msg ) +{ + int i, lastcheck; + int delta; + + if( svs.maxclients == 1 || !sv_consistency.value || !sv.num_consistency || FBitSet( cl->flags, FCL_HLTV_PROXY )) + { + ClearBits( cl->flags, FCL_FORCE_UNMODIFIED ); + MSG_WriteOneBit( msg, 0 ); + return; + } + + SetBits( cl->flags, FCL_FORCE_UNMODIFIED ); + MSG_WriteOneBit( msg, 1 ); + lastcheck = 0; + + for( i = 0; i < sv.num_resources; i++ ) + { + if( !FBitSet( sv.resources[i].ucFlags, RES_CHECKFILE )) + continue; + + delta = i - lastcheck; + MSG_WriteOneBit( msg, 1 ); + + if( delta > 31 ) + { + MSG_WriteOneBit( msg, 0 ); + MSG_WriteUBitLong( msg, i, MAX_MODEL_BITS ); + } + else + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, delta, 5 ); + } + + lastcheck = i; + } + + // write end of the list + MSG_WriteOneBit( msg, 0 ); +} + +qboolean SV_CheckFile( sizebuf_t *msg, const char *filename ) +{ + resource_t p; + + memset( &p, 0, sizeof( resource_t )); + + if( Q_strlen( filename ) == 36 && !Q_strnicmp( filename, "!MD5", 4 )) + { + COM_HexConvert( filename + 4, 32, p.rgucMD5_hash ); + + if( HPAK_GetDataPointer( CUSTOM_RES_PATH, &p, NULL, NULL )) + return true; + } + + if( !sv_allow_upload.value ) + return true; + + MSG_BeginServerCmd( msg, svc_stufftext ); + MSG_WriteString( msg, va( "upload \"!MD5%s\"\n", MD5_Print( p.rgucMD5_hash ))); + + return false; +} + +void SV_MoveToOnHandList( sv_client_t *cl, resource_t *pResource ) +{ + if( !pResource ) + { + MsgDev( D_REPORT, "Null resource passed to SV_MoveToOnHandList\n" ); + return; + } + + SV_RemoveFromResourceList( pResource ); + SV_AddToResourceList( pResource, &cl->resourcesonhand ); +} + +void SV_AddToResourceList( resource_t *pResource, resource_t *pList ) +{ + if( pResource->pPrev != NULL || pResource->pNext != NULL ) + { + MsgDev( D_ERROR, "Resource already linked\n" ); + return; + } + + pResource->pPrev = pList->pPrev; + pResource->pNext = pList; + pList->pPrev->pNext = pResource; + pList->pPrev = pResource; +} + +void SV_SendCustomization( sv_client_t *cl, int playernum, resource_t *pResource ) +{ + MSG_BeginServerCmd( &cl->netchan.message, svc_customization ); + MSG_WriteByte( &cl->netchan.message, playernum ); // playernum + MSG_WriteByte( &cl->netchan.message, pResource->type ); + MSG_WriteString( &cl->netchan.message, pResource->szFileName ); + MSG_WriteShort( &cl->netchan.message, pResource->nIndex ); + MSG_WriteLong( &cl->netchan.message, pResource->nDownloadSize ); + MSG_WriteByte( &cl->netchan.message, pResource->ucFlags ); + + if( FBitSet( pResource->ucFlags, RES_CUSTOM )) + MSG_WriteBytes( &cl->netchan.message, pResource->rgucMD5_hash, 16 ); +} + +void SV_RemoveFromResourceList( resource_t *pResource ) +{ + pResource->pPrev->pNext = pResource->pNext; + pResource->pNext->pPrev = pResource->pPrev; + pResource->pPrev = NULL; + pResource->pNext = NULL; +} + +void SV_ClearResourceList( resource_t *pList ) +{ + resource_t *p; + resource_t *n; + + for( p = pList->pNext; pList != p && p; p = n ) + { + n = p->pNext; + + SV_RemoveFromResourceList( p ); + Mem_Free( p ); + } + + pList->pPrev = pList; + pList->pNext = pList; +} + +void SV_ClearResourceLists( sv_client_t *cl ) +{ + SV_ClearResourceList( &cl->resourcesneeded ); + SV_ClearResourceList( &cl->resourcesonhand ); +} + +int SV_EstimateNeededResources( sv_client_t *cl ) +{ + int missing = 0; + int size = 0; + resource_t *p; + + for( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = p->pNext ) + { + if( p->type != t_decal ) + continue; + + if( !HPAK_ResourceForHash( CUSTOM_RES_PATH, p->rgucMD5_hash, NULL )) + { + if( p->nDownloadSize != 0 ) + { + SetBits( p->ucFlags, RES_WASMISSING ); + size += p->nDownloadSize; + } + else + { + missing++; + } + } + } + + return size; +} + +void SV_Customization( sv_client_t *pClient, resource_t *pResource, qboolean bSkipPlayer ) +{ + int i, nPlayerNumber = -1; + sv_client_t *cl; + + i = pClient - svs.clients; + if( i >= 0 && i < svs.maxclients ) + nPlayerNumber = i; + else Host_Error( "Couldn't find player index for customization.\n" ); + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned ) + continue; + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + if( cl == pClient && bSkipPlayer ) + continue; + + SV_SendCustomization( cl, nPlayerNumber, pResource ); + } +} + +void SV_PropagateCustomizations( sv_client_t *pHost ) +{ + customization_t *pCust; + resource_t *pResource; + sv_client_t *cl; + int i; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned ) + continue; + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + for( pCust = cl->customdata.pNext; pCust != NULL; pCust = pCust->pNext ) + { + if( !pCust->bInUse ) continue; + pResource = &pCust->resource; + SV_SendCustomization( pHost, i, pResource ); + } + } +} + +void SV_RegisterResources( sv_client_t *pHost ) +{ + resource_t *pResource; + + for( pResource = pHost->resourcesonhand.pNext; pResource != &pHost->resourcesonhand; pResource = pResource->pNext ) + { + SV_CreateCustomizationList( pHost ); + SV_Customization( pHost, pResource, true ); + } +} + +qboolean SV_UploadComplete( sv_client_t *cl ) +{ + if( &cl->resourcesneeded != cl->resourcesneeded.pNext ) + return false; + + SV_RegisterResources( cl ); + SV_PropagateCustomizations( cl ); + + if( sv_allow_upload.value ) + Con_Printf( "Custom resource propagation complete.\n" ); + cl->upstate = us_complete; + + return true; +} + +void SV_RequestMissingResources( void ) +{ + sv_client_t *cl; + int i; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned ) + continue; + + if( cl->upstate == us_processing ) + SV_UploadComplete( cl ); + } +} + +void SV_BatchUploadRequest( sv_client_t *cl ) +{ + string filename; + resource_t *p, *n; + + for( p = cl->resourcesneeded.pNext; p != &cl->resourcesneeded; p = n ) + { + n = p->pNext; + + if( !FBitSet( p->ucFlags, RES_WASMISSING )) + { + SV_MoveToOnHandList( cl, p ); + continue; + } + + if( p->type == t_decal ) + { + if( FBitSet( p->ucFlags, RES_CUSTOM )) + { + Q_snprintf( filename, sizeof( filename ), "!MD5%s", MD5_Print( p->rgucMD5_hash )); + + if( SV_CheckFile( &cl->netchan.message, filename )) + SV_MoveToOnHandList( cl, p ); + } + else + { + MsgDev( D_ERROR, "Non customization in upload queue!\n" ); + SV_MoveToOnHandList( cl, p ); + } + } + } +} + +void SV_SendResource( resource_t *pResource, sizebuf_t *msg ) +{ + static byte nullrguc[36]; + + MSG_WriteUBitLong( msg, pResource->type, 4 ); + MSG_WriteString( msg, pResource->szFileName ); + MSG_WriteUBitLong( msg, pResource->nIndex, MAX_MODEL_BITS ); + MSG_WriteSBitLong( msg, pResource->nDownloadSize, 24 ); // prevent to download a very big files? + MSG_WriteUBitLong( msg, pResource->ucFlags & ( RES_FATALIFMISSING|RES_WASMISSING ), 3 ); + + if( FBitSet( pResource->ucFlags, RES_CUSTOM )) + MSG_WriteBytes( msg, pResource->rgucMD5_hash, sizeof( pResource->rgucMD5_hash )); + + if( memcmp( nullrguc, pResource->rguc_reserved, sizeof( nullrguc ))) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteBytes( msg, pResource->rguc_reserved, sizeof( pResource->rguc_reserved )); + } + else MSG_WriteOneBit( msg, 0 ); +} + +void SV_SendResources( sv_client_t *cl, sizebuf_t *msg ) +{ + int i; + + MSG_BeginServerCmd( msg, svc_resourcerequest ); + MSG_WriteLong( msg, svs.spawncount ); + MSG_WriteLong( msg, 0 ); + + if( COM_CheckString( sv_downloadurl.string ) && Q_strlen( sv_downloadurl.string ) < 256 ) + { + MSG_BeginServerCmd( msg, svc_resourcelocation ); + MSG_WriteString( msg, sv_downloadurl.string ); + } + + MSG_BeginServerCmd( msg, svc_resourcelist ); + MSG_WriteUBitLong( msg, sv.num_resources, MAX_RESOURCE_BITS ); + + for( i = 0; i < sv.num_resources; i++ ) + { + SV_SendResource( &sv.resources[i], msg ); + } + + SV_SendConsistencyList( cl, msg ); +} \ No newline at end of file diff --git a/engine/server/sv_frame.c b/engine/server/sv_frame.c new file mode 100644 index 00000000..d663d478 --- /dev/null +++ b/engine/server/sv_frame.c @@ -0,0 +1,966 @@ +/* +sv_frame.c - server world snapshot +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "const.h" +#include "net_encode.h" + +typedef struct +{ + int num_entities; + entity_state_t entities[MAX_VISIBLE_PACKET]; + byte sended[MAX_EDICTS_BYTES]; +} sv_ents_t; + +int c_fullsend; // just a debug counter +int c_notsend; + +/* +======================= +SV_EntityNumbers +======================= +*/ +static int SV_EntityNumbers( const void *a, const void *b ) +{ + int ent1, ent2; + + ent1 = ((entity_state_t *)a)->number; + ent2 = ((entity_state_t *)b)->number; + + if( ent1 == ent2 ) + Host_Error( "SV_SortEntities: duplicated entity\n" ); + + if( ent1 < ent2 ) + return -1; + return 1; +} + +/* +============= +SV_AddEntitiesToPacket + +============= +*/ +static void SV_AddEntitiesToPacket( edict_t *pViewEnt, edict_t *pClient, client_frame_t *frame, sv_ents_t *ents, qboolean from_client ) +{ + edict_t *ent; + byte *clientpvs; + byte *clientphs; + qboolean fullvis = false; + sv_client_t *netclient; + sv_client_t *cl = NULL; + entity_state_t *state; + int e; + + // during an error shutdown message we may need to transmit + // the shutdown message after the server has shutdown, so + // specifically check for it + if( sv.state == ss_dead ) + return; + + cl = SV_ClientFromEdict( pClient, true ); + + ASSERT( cl != NULL ); + + // portals can't change hostflags + if( from_client ) + { + // setup hostflags + if( FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) + SetBits( sv.hostflags, SVF_SKIPLOCALHOST ); + else ClearBits( sv.hostflags, SVF_SKIPLOCALHOST ); + + // reset viewents each frame + cl->num_viewents = 0; + } + + svgame.dllFuncs.pfnSetupVisibility( pViewEnt, pClient, &clientpvs, &clientphs ); + if( !clientpvs ) fullvis = true; + + // g-cont: of course we can send world but not want to do it :-) + for( e = 1; e < svgame.numEntities; e++ ) + { + byte *pset; + + ent = EDICT_NUM( e ); + + // don't double add an entity through portals (in case this already added) + if( CHECKVISBIT( ents->sended, e )) + continue; + + if( FBitSet( ent->v.effects, EF_REQUEST_PHS )) + pset = clientphs; + else pset = clientpvs; + + state = &ents->entities[ents->num_entities]; + netclient = SV_ClientFromEdict( ent, true ); + + // add entity to the net packet + if( svgame.dllFuncs.pfnAddToFullPack( state, e, ent, pClient, sv.hostflags, ( netclient != NULL ), pset )) + { + // to prevent adds it twice through portals + SETVISBIT( ents->sended, e ); + + if( SV_IsValidEdict( ent->v.aiment ) && FBitSet( ent->v.aiment->v.effects, EF_MERGE_VISIBILITY )) + { + if( cl->num_viewents < MAX_VIEWENTS ) + { + cl->viewentity[cl->num_viewents] = ent->v.aiment; + cl->num_viewents++; + } + } + + // if we are full, silently discard entities + if( ents->num_entities < MAX_VISIBLE_PACKET ) + { + ents->num_entities++; // entity accepted + c_fullsend++; // debug counter + + } + else + { + // visibility list is full + // continue counting entities, + // so we know how many it's ovreflowed + c_notsend++; + } + } + + if( fullvis ) continue; // portal ents will be added anyway, ignore recursion + + // if its a portal entity, add everything visible from its camera position + if( from_client && FBitSet( ent->v.effects, EF_MERGE_VISIBILITY )) + { + SetBits( sv.hostflags, SVF_MERGE_VISIBILITY ); + SV_AddEntitiesToPacket( ent, pClient, frame, ents, false ); + ClearBits( sv.hostflags, SVF_MERGE_VISIBILITY ); + } + } +} + +/* +============================================================================= + +Encode a client frame onto the network channel + +============================================================================= +*/ +int SV_FindBestBaseline( sv_client_t *cl, int index, entity_state_t **baseline, entity_state_t *to, client_frame_t *frame, qboolean player ) +{ + int bestBitCount; + int i, bitCount; + int bestfound, j; + + bestBitCount = j = Delta_TestBaseline( *baseline, to, player, sv.time ); + bestfound = index; + + // lookup backward for previous 64 states and try to interpret current delta as baseline + for( i = index - 1; bestBitCount > 0 && i >= 0 && ( index - i ) < ( MAX_CUSTOM_BASELINES - 1 ); i-- ) + { + // don't worry about underflow in circular buffer + entity_state_t *test = &svs.packet_entities[(frame->first_entity+i) % svs.num_client_entities]; + + if( to->entityType == test->entityType ) + { + bitCount = Delta_TestBaseline( test, to, player, sv.time ); + + if( bitCount < bestBitCount ) + { + bestBitCount = bitCount; + bestfound = i; + } + } + } + + // using delta from previous entity as baseline for current + if( index != bestfound ) + *baseline = &svs.packet_entities[(frame->first_entity+bestfound) % svs.num_client_entities]; + return index - bestfound; +} + +/* +============= +SV_EmitPacketEntities + +Writes a delta update of an entity_state_t list to the message-> +============= +*/ +static void SV_EmitPacketEntities( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg ) +{ + entity_state_t *oldent, *newent; + int oldindex, newindex; + int i, oldnum, newnum; + qboolean player; + int oldmax; + client_frame_t *from; + + // this is the frame that we are going to delta update from + if( cl->delta_sequence != -1 ) + { + from = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK]; + oldmax = from->num_entities; + + // the snapshot's entities may still have rolled off the buffer, though + if( from->first_entity <= ( svs.next_client_entities - svs.num_client_entities )) + { + Con_DPrintf( S_WARN "%s: delta request from out of date entities.\n", cl->name ); + MSG_BeginServerCmd( msg, svc_packetentities ); + MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS ); + + from = NULL; + oldmax = 0; + } + else + { + MSG_BeginServerCmd( msg, svc_deltapacketentities ); + MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS ); + MSG_WriteByte( msg, cl->delta_sequence ); + } + } + else + { + from = NULL; + oldmax = 0; + + MSG_BeginServerCmd( msg, svc_packetentities ); + MSG_WriteUBitLong( msg, to->num_entities - 1, MAX_VISIBLE_PACKET_BITS ); + } + + newent = NULL; + oldent = NULL; + newindex = 0; + oldindex = 0; + + while( newindex < to->num_entities || oldindex < oldmax ) + { + if( newindex >= to->num_entities ) + { + newnum = MAX_ENTNUMBER; + player = false; + } + else + { + newent = &svs.packet_entities[(to->first_entity+newindex) % svs.num_client_entities]; + player = SV_IsPlayerIndex( newent->number ); + newnum = newent->number; + } + + if( oldindex >= oldmax ) + { + oldnum = MAX_ENTNUMBER; + } + else + { + oldent = &svs.packet_entities[(from->first_entity+oldindex) % svs.num_client_entities]; + oldnum = oldent->number; + } + + if( newnum == oldnum ) + { + // delta update from old position + // because the force parm is false, this will not result + // in any bytes being emited if the entity has not changed at all + MSG_WriteDeltaEntity( oldent, newent, msg, false, player, sv.time, 0 ); + oldindex++; + newindex++; + continue; + } + + if( newnum < oldnum ) + { + entity_state_t *baseline = &svs.baselines[newnum]; + const char *classname = SV_ClassName( EDICT_NUM( newnum )); + int offset = 0; + + // trying to reduce message by select optimal baseline + if( !sv_instancedbaseline.value || !sv.num_instanced || sv.last_valid_baseline > newnum ) + { + offset = SV_FindBestBaseline( cl, newindex, &baseline, newent, to, player ); + } + else + { + for( i = 0; i < sv.num_instanced; i++ ) + { + if( !Q_strcmp( classname, sv.instanced[i].classname )) + { + baseline = &sv.instanced[i].baseline; + offset = -i; + break; + } + } + } + + // this is a new entity, send it from the baseline + MSG_WriteDeltaEntity( baseline, newent, msg, true, player, sv.time, offset ); + newindex++; + continue; + } + + if( newnum > oldnum ) + { + edict_t *ed = EDICT_NUM( oldent->number ); + qboolean force = false; + + // check if entity completely removed from server + if( ed->free || FBitSet( ed->v.flags, FL_KILLME )) + force = true; + + // remove from message + MSG_WriteDeltaEntity( oldent, NULL, msg, force, false, sv.time, 0 ); + oldindex++; + continue; + } + } + + MSG_WriteUBitLong( msg, LAST_EDICT, MAX_ENTITY_BITS ); // end of packetentities +} + +/* +============= +SV_EmitEvents + +============= +*/ +static void SV_EmitEvents( sv_client_t *cl, client_frame_t *to, sizebuf_t *msg ) +{ + event_state_t *es; + event_info_t *info; + entity_state_t *state; + event_args_t nullargs; + int ev_count = 0; + int count, ent_index; + int i, j, ev; + + memset( &nullargs, 0, sizeof( nullargs )); + es = &cl->events; + + // count events + for( ev = 0; ev < MAX_EVENT_QUEUE; ev++ ) + { + if( es->ei[ev].index ) + ev_count++; + } + + // nothing to send + if( !ev_count ) return; // nothing to send + + if ( ev_count >= MAX_EVENT_QUEUE / 2 ) + ev_count = ( MAX_EVENT_QUEUE / 2 ) - 1; + + for( i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + info = &es->ei[i]; + if( info->index == 0 ) + continue; + + ent_index = info->entity_index; + + for( j = 0; j < to->num_entities; j++ ) + { + state = &svs.packet_entities[(to->first_entity+j) % svs.num_client_entities]; + if( state->number == ent_index ) + break; + } + + if( j < to->num_entities ) + { + info->packet_index = j; + info->args.ducking = 0; + + if( !FBitSet( info->args.flags, FEVENT_ORIGIN )) + VectorClear( info->args.origin ); + + if( !FBitSet( info->args.flags, FEVENT_ANGLES )) + VectorClear( info->args.angles ); + + VectorClear( info->args.velocity ); + } + else + { + // couldn't find + info->packet_index = to->num_entities; + info->args.entindex = ent_index; + } + } + + MSG_BeginServerCmd( msg, svc_event ); // create message + MSG_WriteUBitLong( msg, ev_count, 5 ); // up to MAX_EVENT_QUEUE events + + for( count = i = 0; i < MAX_EVENT_QUEUE; i++ ) + { + info = &es->ei[i]; + + if( info->index == 0 ) + { + info->packet_index = -1; + info->entity_index = -1; + continue; + } + + // only send if there's room + if( count < ev_count ) + { + MSG_WriteUBitLong( msg, info->index, MAX_EVENT_BITS ); // 1024 events + + if( info->packet_index == -1 ) + { + MSG_WriteOneBit( msg, 0 ); + } + else + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, info->packet_index, MAX_ENTITY_BITS ); + + if( !memcmp( &nullargs, &info->args, sizeof( event_args_t ))) + { + MSG_WriteOneBit( msg, 0 ); + } + else + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteDeltaEvent( msg, &nullargs, &info->args ); + } + } + + if( info->fire_time ) + { + MSG_WriteOneBit( msg, 1 ); + MSG_WriteWord( msg, ( info->fire_time * 100.0f )); + } + else MSG_WriteOneBit( msg, 0 ); + } + + info->index = 0; + info->packet_index = -1; + info->entity_index = -1; + count++; + } +} + +/* +============= +SV_EmitPings + +============= +*/ +void SV_EmitPings( sizebuf_t *msg ) +{ + sv_client_t *cl; + int packet_loss; + int i, ping; + + MSG_BeginServerCmd( msg, svc_pings ); + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned ) + continue; + + SV_GetPlayerStats( cl, &ping, &packet_loss ); + + // there are 25 bits for each client + MSG_WriteOneBit( msg, 1 ); + MSG_WriteUBitLong( msg, i, MAX_CLIENT_BITS ); + MSG_WriteUBitLong( msg, ping, 12 ); + MSG_WriteUBitLong( msg, packet_loss, 7 ); + } + + // end marker + MSG_WriteOneBit( msg, 0 ); +} + +/* +================== +SV_WriteClientdataToMessage + +================== +*/ +void SV_WriteClientdataToMessage( sv_client_t *cl, sizebuf_t *msg ) +{ + clientdata_t nullcd; + clientdata_t *from_cd, *to_cd; + weapon_data_t nullwd; + weapon_data_t *from_wd, *to_wd; + client_frame_t *frame; + edict_t *clent; + int i; + + memset( &nullcd, 0, sizeof( nullcd )); + frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK]; + frame->senttime = host.realtime; + frame->ping_time = -1.0f; + clent = cl->edict; + + if( cl->chokecount != 0 ) + { + MSG_BeginServerCmd( msg, svc_choke ); + cl->chokecount = 0; + } + + // update client fixangle + switch( clent->v.fixangle ) + { + case 1: + MSG_BeginServerCmd( msg, svc_setangle ); + MSG_WriteVec3Angles( msg, clent->v.angles ); + break; + case 2: + MSG_BeginServerCmd( msg, svc_addangle ); + MSG_WriteBitAngle( msg, clent->v.avelocity[YAW], 16 ); + clent->v.avelocity[YAW] = 0.0f; + break; + } + + clent->v.fixangle = 0; // reset fixangle + + memset( &frame->clientdata, 0, sizeof( frame->clientdata )); + + // update clientdata_t + svgame.dllFuncs.pfnUpdateClientData( clent, FBitSet( cl->flags, FCL_LOCAL_WEAPONS ), &frame->clientdata ); + + MSG_BeginServerCmd( msg, svc_clientdata ); + if( FBitSet( cl->flags, FCL_HLTV_PROXY )) return; // don't send more nothing + + if( cl->delta_sequence == -1 ) from_cd = &nullcd; + else from_cd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].clientdata; + to_cd = &frame->clientdata; + + if( cl->delta_sequence == -1 ) + { + MSG_WriteOneBit( msg, 0 ); // no delta-compression + } + else + { + MSG_WriteOneBit( msg, 1 ); // we are delta-ing from + MSG_WriteByte( msg, cl->delta_sequence ); + } + + // write clientdata_t + MSG_WriteClientData( msg, from_cd, to_cd, sv.time ); + + if( FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) && svgame.dllFuncs.pfnGetWeaponData( clent, frame->weapondata )) + { + memset( &nullwd, 0, sizeof( nullwd )); + + for( i = 0; i < MAX_LOCAL_WEAPONS; i++ ) + { + if( cl->delta_sequence == -1 ) from_wd = &nullwd; + else from_wd = &cl->frames[cl->delta_sequence & SV_UPDATE_MASK].weapondata[i]; + to_wd = &frame->weapondata[i]; + + MSG_WriteWeaponData( msg, from_wd, to_wd, sv.time, i ); + } + } + + // end marker + MSG_WriteOneBit( msg, 0 ); +} + +/* +================== +SV_WriteEntitiesToClient + +================== +*/ +void SV_WriteEntitiesToClient( sv_client_t *cl, sizebuf_t *msg ) +{ + client_frame_t *frame; + entity_state_t *state; + static sv_ents_t frame_ents; + int i, send_pings; + + frame = &cl->frames[cl->netchan.outgoing_sequence & SV_UPDATE_MASK]; + send_pings = SV_ShouldUpdatePing( cl ); + + memset( frame_ents.sended, 0, sizeof( frame_ents.sended )); + ClearBits( sv.hostflags, SVF_MERGE_VISIBILITY ); + + // clear everything in this snapshot + frame_ents.num_entities = c_fullsend = c_notsend = 0; + + // add all the entities directly visible to the eye, which + // may include portal entities that merge other viewpoints + SV_AddEntitiesToPacket( cl->pViewEntity, cl->edict, frame, &frame_ents, true ); + + if( c_notsend != cl->ignored_ents ) + { + if( c_notsend > 0 ) + Con_Printf( S_ERROR "Too many entities in visible packet list. Ignored %d entities\n", c_notsend ); + cl->ignored_ents = c_notsend; + } + + // if there were portals visible, there may be out of order entities + // in the list which will need to be resorted for the delta compression + // to work correctly. This also catches the error condition + // of an entity being included twice. + qsort( frame_ents.entities, frame_ents.num_entities, sizeof( frame_ents.entities[0] ), SV_EntityNumbers ); + + // it will break all connected clients, but it takes more than one week to overflow it + if(( (uint)svs.next_client_entities ) + frame_ents.num_entities >= 0x7FFFFFFE ) + { + svs.next_client_entities = 0; + + // delta is broken for now, cannot keep connected clients + SV_FinalMessage( "Server will restart due delta is outdated\n", true ); + } + + // copy the entity states out + frame->first_entity = svs.next_client_entities; + frame->num_entities = 0; + + for( i = 0; i < frame_ents.num_entities; i++ ) + { + // add it to the circular packet_entities array + state = &svs.packet_entities[svs.next_client_entities % svs.num_client_entities]; + *state = frame_ents.entities[i]; + svs.next_client_entities++; + frame->num_entities++; + } + + SV_EmitPacketEntities( cl, frame, msg ); + SV_EmitEvents( cl, frame, msg ); + if( send_pings ) SV_EmitPings( msg ); +} + +/* +=============================================================================== + +FRAME UPDATES + +=============================================================================== +*/ +/* +======================= +SV_SendClientDatagram +======================= +*/ +void SV_SendClientDatagram( sv_client_t *cl ) +{ + byte msg_buf[MAX_DATAGRAM]; + sizebuf_t msg; + + // if we running server with fixed fps so no reason + // to send updates too fast: time just not changed + if( FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + { + if( sv.simulating && cl->lastservertime == sv.time ) + return; + } + + MSG_Init( &msg, "Datagram", msg_buf, sizeof( msg_buf )); + + // always send servertime at new frame + MSG_BeginServerCmd( &msg, svc_time ); + MSG_WriteFloat( &msg, sv.time ); + cl->lastservertime = sv.time; + + SV_WriteClientdataToMessage( cl, &msg ); + SV_WriteEntitiesToClient( cl, &msg ); + + // copy the accumulated multicast datagram + // for this client out to the message + if( MSG_CheckOverflow( &cl->datagram )) + { + Con_Printf( S_WARN "%s overflowed for %s\n", MSG_GetName( &cl->datagram ), cl->name ); + } + else + { + if( MSG_GetNumBytesWritten( &cl->datagram ) < MSG_GetNumBytesLeft( &msg )) + MSG_WriteBits( &msg, MSG_GetData( &cl->datagram ), MSG_GetNumBitsWritten( &cl->datagram )); + else MsgDev( D_WARN, "Ignoring unreliable datagram for %s, would overflow on msg\n", cl->name ); + } + + MSG_Clear( &cl->datagram ); + + if( MSG_CheckOverflow( &msg )) + { + // must have room left for the packet header + Con_Printf( S_ERROR "%s overflowed for %s\n", MSG_GetName( &msg ), cl->name ); + MSG_Clear( &msg ); + } + + // send the datagram + Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg )); +} + +/* +======================= +SV_UpdateUserInfo +======================= +*/ +void SV_UpdateUserInfo( sv_client_t *cl ) +{ + SV_FullClientUpdate( cl, &sv.reliable_datagram ); + ClearBits( cl->flags, FCL_RESEND_USERINFO ); + cl->next_sendinfotime = host.realtime + 1.0; +} + +/* +======================= +SV_UpdateToReliableMessages +======================= +*/ +void SV_UpdateToReliableMessages( void ) +{ + sv_client_t *cl; + int i; + + // check for changes to be sent over the reliable streams to all clients + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( !cl->edict ) continue; // not in game yet + + if( cl->state != cs_spawned ) + continue; + + if( FBitSet( cl->flags, FCL_RESEND_USERINFO ) && cl->next_sendinfotime <= host.realtime ) + { + if( MSG_GetNumBytesLeft( &sv.reliable_datagram ) >= ( Q_strlen( cl->userinfo ) + 6 )) + SV_UpdateUserInfo( cl ); + } + + if( FBitSet( cl->flags, FCL_RESEND_MOVEVARS )) + { + SV_FullUpdateMovevars( cl, &cl->netchan.message ); + ClearBits( cl->flags, FCL_RESEND_MOVEVARS ); + } + } + + // clear the server datagram if it overflowed. + if( MSG_CheckOverflow( &sv.datagram )) + { + MsgDev( D_ERROR, "sv.datagram overflowed!\n" ); + MSG_Clear( &sv.datagram ); + } + + // clear the server datagram if it overflowed. + if( MSG_CheckOverflow( &sv.spec_datagram )) + { + MsgDev( D_ERROR, "sv.spec_datagram overflowed!\n" ); + MSG_Clear( &sv.spec_datagram ); + } + + // now send the reliable and server datagrams to all clients. + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state < cs_connected || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; // reliables go to all connected or spawned + + if( MSG_GetNumBytesWritten( &sv.reliable_datagram ) < MSG_GetNumBytesLeft( &cl->netchan.message )) + { + MSG_WriteBits( &cl->netchan.message, MSG_GetBuf( &sv.reliable_datagram ), MSG_GetNumBitsWritten( &sv.reliable_datagram )); + } + else + { + Netchan_CreateFragments( &cl->netchan, &sv.reliable_datagram ); + } + + if( MSG_GetNumBytesWritten( &sv.datagram ) < MSG_GetNumBytesLeft( &cl->datagram )) + { + MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.datagram ), MSG_GetNumBitsWritten( &sv.datagram )); + } + else + { + MsgDev( D_WARN, "Ignoring unreliable datagram for %s, would overflow\n", cl->name ); + } + + if( FBitSet( cl->flags, FCL_HLTV_PROXY )) + { + if( MSG_GetNumBytesWritten( &sv.spec_datagram ) < MSG_GetNumBytesLeft( &cl->datagram )) + { + MSG_WriteBits( &cl->datagram, MSG_GetBuf( &sv.spec_datagram ), MSG_GetNumBitsWritten( &sv.spec_datagram )); + } + else + { + MsgDev( D_WARN, "Ignoring spectator datagram for %s, would overflow\n", cl->name ); + } + } + } + + // now clear the reliable and datagram buffers. + MSG_Clear( &sv.reliable_datagram ); + MSG_Clear( &sv.spec_datagram ); + MSG_Clear( &sv.datagram ); +} + +/* +======================= +SV_SendClientMessages +======================= +*/ +void SV_SendClientMessages( void ) +{ + sv_client_t *cl; + int i; + + if( sv.state == ss_dead ) + return; + + SV_UpdateToReliableMessages (); + + // send a message to each connected client + for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ ) + { + cl = sv.current_client; + + if( !cl->state || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + if( FBitSet( cl->flags, FCL_SKIP_NET_MESSAGE )) + { + ClearBits( cl->flags, FCL_SKIP_NET_MESSAGE ); + continue; + } + + if( !FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + { + if( !host_limitlocal->value && NET_IsLocalAddress( cl->netchan.remote_address )) + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); + } + + if( cl->state == cs_spawned ) + { + if(( host.realtime + sv.frametime ) >= cl->next_messagetime ) + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); + } + + // if the reliable message overflowed, drop the client + if( MSG_CheckOverflow( &cl->netchan.message )) + { + MSG_Clear( &cl->netchan.message ); + MSG_Clear( &cl->datagram ); + SV_BroadcastPrintf( NULL, "%s overflowed\n", cl->name ); + MsgDev( D_WARN, "reliable overflow for %s\n", cl->name ); + SV_DropClient( cl, false ); + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); + cl->netchan.cleartime = 0.0; // don't choke this message + } + else if( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE )) + { + // If we haven't gotten a message in sv_failuretime seconds, then stop sending messages to this client + // until we get another packet in from the client. This prevents crash/drop and reconnect where they are + // being hosed with "sequenced packet without connection" packets. + if( sv_failuretime.value < ( host.realtime - cl->netchan.last_received )) + ClearBits( cl->flags, FCL_SEND_NET_MESSAGE ); + } + + // only send messages if the client has sent one + // and the bandwidth is not choked + if( FBitSet( cl->flags, FCL_SEND_NET_MESSAGE )) + { + // bandwidth choke active? + if( !Netchan_CanPacket( &cl->netchan, cl->state == cs_spawned )) + { + cl->chokecount++; + continue; + } + + // now that we were able to send, reset timer to point to next possible send time. + cl->next_messagetime = host.realtime + sv.frametime + cl->cl_updaterate; + ClearBits( cl->flags, FCL_SEND_NET_MESSAGE ); + + // NOTE: we should send frame even if server is not simulated to prevent overflow + if( cl->state == cs_spawned ) + SV_SendClientDatagram( cl ); + else Netchan_TransmitBits( &cl->netchan, 0, NULL ); // just update reliable + } + } + + // reset current client + sv.current_client = NULL; +} + +/* +======================= +SV_SendMessagesToAll + +e.g. before changing level +======================= +*/ +void SV_SendMessagesToAll( void ) +{ + sv_client_t *cl; + int i; + + if( sv.state == ss_dead ) + return; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state >= cs_connected ) + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); + } + + SV_SendClientMessages(); +} + +/* +======================= +SV_SkipUpdates + +used before changing level +======================= +*/ +void SV_SkipUpdates( void ) +{ + sv_client_t *cl; + int i; + + if( sv.state == ss_dead ) + return; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + SetBits( cl->flags, FCL_SKIP_NET_MESSAGE ); + } +} + +/* +======================= +SV_InactivateClients + +Purpose: Prepare for level transition, etc. +======================= +*/ +void SV_InactivateClients( void ) +{ + int i; + sv_client_t *cl; + + if( sv.state == ss_dead ) + return; + + // send a message to each connected client + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( !cl->state || !cl->edict ) + continue; + + if( !cl->edict || FBitSet( cl->edict->v.flags, FL_FAKECLIENT )) + continue; + + if( cl->state > cs_connected ) + cl->state = cs_connected; + + COM_ClearCustomizationList( &cl->customdata, false ); + memset( cl->physinfo, 0, MAX_PHYSINFO_STRING ); + MSG_Clear( &cl->netchan.message ); + MSG_Clear( &cl->datagram ); + } +} \ No newline at end of file diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c new file mode 100644 index 00000000..3e98688f --- /dev/null +++ b/engine/server/sv_game.c @@ -0,0 +1,4883 @@ +/* +sv_game.c - gamedll interaction +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "net_encode.h" +#include "event_flags.h" +#include "library.h" +#include "pm_defs.h" +#include "studio.h" +#include "const.h" +#include "render_api.h" // modelstate_t + +#define ENTVARS_COUNT ARRAYSIZE( gEntvarsDescription ) + +// fatpvs stuff +static byte fatpvs[MAX_MAP_LEAFS/8]; +static byte fatphs[MAX_MAP_LEAFS/8]; +static byte clientpvs[MAX_MAP_LEAFS/8]; // for find client in PVS +static vec3_t viewPoint[MAX_CLIENTS]; + +// exports +typedef void (__cdecl *LINK_ENTITY_FUNC)( entvars_t *pev ); +typedef void (__stdcall *GIVEFNPTRSTODLL)( enginefuncs_t* engfuncs, globalvars_t *pGlobals ); + +edict_t *SV_EdictNum( int n ) +{ + if(( n >= 0 ) && ( n < GI->max_edicts )) + return svgame.edicts + n; + return NULL; +} + +#ifdef _DEBUG +qboolean SV_CheckEdict( const edict_t *e, const char *file, const int line ) +{ + int n; + + if( !e ) return false; // may be NULL + + n = ((int)((edict_t *)(e) - svgame.edicts)); + + if(( n >= 0 ) && ( n < GI->max_edicts )) + return !e->free; + Con_Printf( "bad entity %i (called at %s:%i)\n", n, file, line ); + + return false; +} +#endif + +/* +============= +EntvarsDescription + +entavrs table for FindEntityByString +============= +*/ +static TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( globalname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( model, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), +}; + +/* +============= +SV_GetEntvarsDescription + +entavrs table for FindEntityByString +============= +*/ +TYPEDESCRIPTION *SV_GetEntvarsDescirption( int number ) +{ + if( number < 0 && number >= ENTVARS_COUNT ) + return NULL; + return &gEntvarsDescription[number]; +} + +/* +============= +SV_SysError + +tell the game.dll about system error +============= +*/ +void SV_SysError( const char *error_string ) +{ + Log_Printf( "FATAL ERROR (shutting down): %s\n", error_string ); + + if( svgame.hInstance != NULL ) + svgame.dllFuncs.pfnSys_Error( error_string ); +} + +/* +============= +SV_Serverinfo + +get server infostring +============= +*/ +char *SV_Serverinfo( void ) +{ + return svs.serverinfo; +} + +/* +============= +SV_LocalInfo + +get local infostring +============= +*/ +char *SV_Localinfo( void ) +{ + return svs.localinfo; +} + +/* +============= +SV_AngleMod + +do modulo on entity angles +============= +*/ +float SV_AngleMod( float ideal, float current, float speed ) +{ + float move; + + current = anglemod( current ); + + if( current == ideal ) // already there? + return current; + + move = ideal - current; + + if( ideal > current ) + { + if( move >= 180 ) + move = move - 360; + } + else + { + if( move <= -180 ) + move = move + 360; + } + + if( move > 0 ) + { + if( move > speed ) + move = speed; + } + else + { + if( move < -speed ) + move = -speed; + } + + return anglemod( current + move ); +} + +/* +============= +SV_SetMinMaxSize + +update entity bounds, relink into world +============= +*/ +void SV_SetMinMaxSize( edict_t *e, const float *mins, const float *maxs, qboolean relink ) +{ + int i; + + if( !SV_IsValidEdict( e )) + return; + + for( i = 0; i < 3; i++ ) + { + if( mins[i] > maxs[i] ) + { + Con_Printf( S_ERROR "%s[%i] has backwards mins/maxs\n", SV_ClassName( e ), NUM_FOR_EDICT( e )); + if( relink ) SV_LinkEdict( e, false ); // just relink edict and exit + return; + } + } + + VectorCopy( mins, e->v.mins ); + VectorCopy( maxs, e->v.maxs ); + VectorSubtract( maxs, mins, e->v.size ); + if( relink ) SV_LinkEdict( e, false ); +} + +/* +============= +SV_CopyTraceToGlobal + +each trace will share their result into global state +============= +*/ +void SV_CopyTraceToGlobal( trace_t *trace ) +{ + svgame.globals->trace_allsolid = trace->allsolid; + svgame.globals->trace_startsolid = trace->startsolid; + svgame.globals->trace_fraction = trace->fraction; + svgame.globals->trace_plane_dist = trace->plane.dist; + svgame.globals->trace_inopen = trace->inopen; + svgame.globals->trace_inwater = trace->inwater; + VectorCopy( trace->endpos, svgame.globals->trace_endpos ); + VectorCopy( trace->plane.normal, svgame.globals->trace_plane_normal ); + svgame.globals->trace_hitgroup = trace->hitgroup; + svgame.globals->trace_flags = 0; // g-cont: always reset config flags when trace is finished + + if( SV_IsValidEdict( trace->ent )) + svgame.globals->trace_ent = trace->ent; + else svgame.globals->trace_ent = svgame.edicts; +} + +/* +============= +SV_ConvertTrace + +convert trace_t to TraceResult +============= +*/ +void SV_ConvertTrace( TraceResult *dst, trace_t *src ) +{ + if( !src || !dst ) return; + + dst->fAllSolid = src->allsolid; + dst->fStartSolid = src->startsolid; + dst->fInOpen = src->inopen; + dst->fInWater = src->inwater; + dst->flFraction = src->fraction; + VectorCopy( src->endpos, dst->vecEndPos ); + dst->flPlaneDist = src->plane.dist; + VectorCopy( src->plane.normal, dst->vecPlaneNormal ); + dst->pHit = src->ent; + dst->iHitgroup = src->hitgroup; + + // g-cont: always reset config flags when trace is finished + svgame.globals->trace_flags = 0; +} + +/* +============= +SV_CheckClientVisiblity + +Check visibility through client camera, portal camera, etc +============= +*/ +qboolean SV_CheckClientVisiblity( sv_client_t *cl, const byte *mask ) +{ + int i, clientnum; + vec3_t vieworg; + mleaf_t *leaf; + + if( !mask ) return true; // GoldSrc rules + + clientnum = cl - svs.clients; + VectorCopy( viewPoint[clientnum], vieworg ); + + // Invasion issues: wrong camera position received in ENGINE_SET_PVS + if( cl->pViewEntity && !VectorCompare( vieworg, cl->pViewEntity->v.origin )) + VectorCopy( cl->pViewEntity->v.origin, vieworg ); + + leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); + + if( CHECKVISBIT( mask, leaf->cluster )) + return true; // visible from player view or camera view + + // now check all the portal cameras + for( i = 0; i < cl->num_viewents; i++ ) + { + edict_t *view = cl->viewentity[i]; + + if( !SV_IsValidEdict( view )) + continue; + + VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); + leaf = Mod_PointInLeaf( vieworg, sv.worldmodel->nodes ); + + if( CHECKVISBIT( mask, leaf->cluster )) + return true; // visible from portal camera view + } + + // not visible from any viewpoint + return false; +} + +/* +================= +SV_Multicast + +Sends the contents of sv.multicast to a subset of the clients, +then clears sv.multicast. + +MSG_INIT write message into signon buffer +MSG_ONE send to one client (ent can't be NULL) +MSG_ALL same as broadcast (origin can be NULL) +MSG_PVS send to clients potentially visible from org +MSG_PHS send to clients potentially audible from org +================= +*/ +static int SV_Multicast( int dest, const vec3_t origin, const edict_t *ent, qboolean usermessage, qboolean filter ) +{ + byte *mask = NULL; + int j, numclients = svs.maxclients; + sv_client_t *cl, *current = svs.clients; + qboolean reliable = false; + qboolean specproxy = false; + int numsends = 0; + + // some mods trying to send messages after SV_FinalMessage + if( !svs.initialized || sv.state == ss_dead ) + { + MSG_Clear( &sv.multicast ); + return 0; + } + + switch( dest ) + { + case MSG_INIT: + if( sv.state == ss_loading ) + { + // copy to signon buffer + MSG_WriteBits( &sv.signon, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); + MSG_Clear( &sv.multicast ); + return 1; + } + // intentional fallthrough (in-game MSG_INIT it's a MSG_ALL reliable) + case MSG_ALL: + reliable = true; + // intentional fallthrough + case MSG_BROADCAST: + // nothing to sort + break; + case MSG_PAS_R: + reliable = true; + // intentional fallthrough + case MSG_PAS: + if( origin == NULL ) return false; + // NOTE: GoldSource not using PHS for singleplayer + Mod_FatPVS( origin, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); + mask = fatphs; // using the FatPVS like a PHS + break; + case MSG_PVS_R: + reliable = true; + // intentional fallthrough + case MSG_PVS: + if( origin == NULL ) return 0; + mask = Mod_GetPVSForPoint( origin ); + break; + case MSG_ONE: + reliable = true; + // intentional fallthrough + case MSG_ONE_UNRELIABLE: + if( !SV_IsValidEdict( ent )) return 0; + j = NUM_FOR_EDICT( ent ); + if( j < 1 || j > numclients ) return 0; + current = svs.clients + (j - 1); + numclients = 1; // send to one + break; + case MSG_SPEC: + specproxy = reliable = true; + break; + default: + Host_Error( "SV_Multicast: bad dest: %i\n", dest ); + return 0; + } + + // send the data to all relevent clients (or once only) + for( j = 0, cl = current; j < numclients; j++, cl++ ) + { + if( cl->state == cs_free || cl->state == cs_zombie ) + continue; + + if( cl->state != cs_spawned && ( !reliable || usermessage )) + continue; + + if( specproxy && !FBitSet( cl->flags, FCL_HLTV_PROXY )) + continue; + + if( !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + // reject step sounds while predicting is enabled + // FIXME: make sure what this code doesn't cutoff something important!!! + if( filter && cl == sv.current_client && FBitSet( sv.current_client->flags, FCL_PREDICT_MOVEMENT )) + continue; + + if( SV_IsValidEdict( ent ) && ent->v.groupinfo && cl->edict->v.groupinfo ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) + continue; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, ent->v.groupinfo )) + continue; + } + + if( !SV_CheckClientVisiblity( cl, mask )) + continue; + + if( specproxy ) MSG_WriteBits( &sv.spec_datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); + else if( reliable ) MSG_WriteBits( &cl->netchan.message, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); + else MSG_WriteBits( &cl->datagram, MSG_GetData( &sv.multicast ), MSG_GetNumBitsWritten( &sv.multicast )); + numsends++; + } + + MSG_Clear( &sv.multicast ); + + return numsends; // just for debug +} + +/* +======================= +SV_GetReliableDatagram + +Get shared reliable buffer +======================= +*/ +sizebuf_t *SV_GetReliableDatagram( void ) +{ + return &sv.reliable_datagram; +} + +/* +======================= +SV_RestoreCustomDecal + +Let the user spawn decal in game code +======================= +*/ +qboolean SV_RestoreCustomDecal( decallist_t *entry, edict_t *pEdict, qboolean adjacent ) +{ + if( svgame.physFuncs.pfnRestoreDecal != NULL ) + { + if( !pEdict ) pEdict = EDICT_NUM( entry->entityIndex ); + // true if decal was sucessfully restored at the game-side + return svgame.physFuncs.pfnRestoreDecal( entry, pEdict, adjacent ); + } + return false; +} + +/* +======================= +SV_CreateDecal + +NOTE: static decals only accepted when game is loading +======================= +*/ +void SV_CreateDecal( sizebuf_t *msg, const float *origin, int decalIndex, int entityIndex, int modelIndex, int flags, float scale ) +{ + if( msg == &sv.signon && sv.state != ss_loading ) + return; + + // this can happens if serialized map contain 4096 static decals... + if( MSG_GetNumBytesLeft( msg ) < 20 ) + { + sv.ignored_world_decals++; + return; + } + + // static decals are posters, it's always reliable + MSG_BeginServerCmd( msg, svc_bspdecal ); + MSG_WriteVec3Coord( msg, origin ); + MSG_WriteWord( msg, decalIndex ); + MSG_WriteShort( msg, entityIndex ); + if( entityIndex > 0 ) + MSG_WriteWord( msg, modelIndex ); + MSG_WriteByte( msg, flags ); + MSG_WriteWord( msg, scale * 4096 ); +} + +/* +======================= +SV_CreateStaticEntity + +NOTE: static entities only accepted when game is loading +======================= +*/ +void SV_CreateStaticEntity( sizebuf_t *msg, sv_static_entity_t *ent ) +{ + int index; + + // this can happens if serialized map contain too many static entities... + if( MSG_GetNumBytesLeft( msg ) < 35 ) + { + sv.ignored_static_ents++; + return; + } + + index = SV_ModelIndex( ent->model ); + + MSG_BeginServerCmd( msg, svc_spawnstatic ); + MSG_WriteShort( msg, index ); + MSG_WriteWord( msg, ent->sequence ); + MSG_WriteWord( msg, ent->frame ); + MSG_WriteWord( msg, ent->colormap ); + MSG_WriteByte( msg, ent->skin ); + MSG_WriteByte( msg, ent->body ); + MSG_WriteCoord( msg, ent->scale ); + MSG_WriteVec3Coord( msg, ent->origin ); + MSG_WriteVec3Angles( msg, ent->angles ); + MSG_WriteByte( msg, ent->rendermode ); + + if( ent->rendermode != kRenderNormal ) + { + MSG_WriteByte( msg, ent->renderamt ); + MSG_WriteByte( msg, ent->rendercolor.r ); + MSG_WriteByte( msg, ent->rendercolor.g ); + MSG_WriteByte( msg, ent->rendercolor.b ); + MSG_WriteByte( msg, ent->renderfx ); + } +} + +/* +================= +SV_RestartStaticEnts + +Write all the static ents into demo +================= +*/ +void SV_RestartStaticEnts( void ) +{ + sv_static_entity_t *clent; + int i; + + // remove all the static entities on the client + R_ClearStaticEntities(); + + // resend them again + for( i = 0; i < sv.num_static_entities; i++ ) + { + clent = &sv.static_entities[i]; + SV_CreateStaticEntity( &sv.reliable_datagram, clent ); + } +} + +/* +================= +SV_RestartAmbientSounds + +Write ambient sounds into demo +================= +*/ +void SV_RestartAmbientSounds( void ) +{ + soundlist_t soundInfo[256]; + string curtrack, looptrack; + int i, nSounds; + long position; + + if( !SV_Active( )) return; + + nSounds = S_GetCurrentStaticSounds( soundInfo, 256 ); + + for( i = 0; i < nSounds; i++ ) + { + soundlist_t *si = &soundInfo[i]; + + if( !si->looping || si->entnum == -1 ) + continue; + + S_StopSound( si->entnum, si->channel, si->name ); + SV_StartSound( pfnPEntityOfEntIndex( si->entnum ), CHAN_STATIC, si->name, si->volume, si->attenuation, 0, si->pitch ); + } + + // restart soundtrack + if( S_StreamGetCurrentState( curtrack, looptrack, &position )) + { + SV_StartMusic( curtrack, looptrack, position ); + } +} + +/* +================= +SV_RestartDecals + +Write all the decals into demo +================= +*/ +void SV_RestartDecals( void ) +{ + decallist_t *entry; + int decalIndex; + int modelIndex; + sizebuf_t *msg; + int i; + + if( !SV_Active( )) return; + + // g-cont. add space for studiodecals if present + host.decalList = (decallist_t *)Z_Malloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); + host.numdecals = R_CreateDecalList( host.decalList ); + + // remove decals from map + R_ClearAllDecals(); + + // write decals into reliable datagram + msg = SV_GetReliableDatagram(); + + // restore decals and write them into network message + for( i = 0; i < host.numdecals; i++ ) + { + entry = &host.decalList[i]; + modelIndex = pfnPEntityOfEntIndex( entry->entityIndex )->v.modelindex; + + // game override + if( SV_RestoreCustomDecal( entry, pfnPEntityOfEntIndex( entry->entityIndex ), false )) + continue; + + decalIndex = pfnDecalIndex( entry->name ); + + // studiodecals will be restored at game-side + if( !FBitSet( entry->flags, FDECAL_STUDIO )) + SV_CreateDecal( msg, entry->position, decalIndex, entry->entityIndex, modelIndex, entry->flags, entry->scale ); + } + + Z_Free( host.decalList ); + host.decalList = NULL; + host.numdecals = 0; +} + +/* +============== +SV_BoxInPVS + +check brush boxes in fat pvs +============== +*/ +qboolean SV_BoxInPVS( const vec3_t org, const vec3_t absmin, const vec3_t absmax ) +{ + if( !Mod_BoxVisible( absmin, absmax, Mod_GetPVSForPoint( org ))) + return false; + return true; +} + +/* +============== +SV_WriteEntityPatch + +Create entity patch for selected map +============== +*/ +void SV_WriteEntityPatch( const char *filename ) +{ + int ver = -1, lumpofs = 0, lumplen = 0; + byte buf[MAX_SYSPATH]; // 1 kb + string bspfilename; + dheader_t *header; + file_t *f; + + Q_strncpy( bspfilename, va( "maps/%s.bsp", filename ), sizeof( bspfilename )); + f = FS_Open( bspfilename, "rb", false ); + if( !f ) return; + + memset( buf, 0, MAX_SYSPATH ); + FS_Read( f, buf, MAX_SYSPATH ); + header = (dheader_t *)buf; + + // check all the lumps and some other errors + if( !Mod_TestBmodelLumps( bspfilename, buf, true )) + { + FS_Close( f ); + return; + } + + lumpofs = header->lumps[LUMP_ENTITIES].fileofs; + lumplen = header->lumps[LUMP_ENTITIES].filelen; + + if( lumplen >= 10 ) + { + char *entities = NULL; + + FS_Seek( f, lumpofs, SEEK_SET ); + entities = (char *)Z_Malloc( lumplen + 1 ); + FS_Read( f, entities, lumplen ); + FS_WriteFile( va( "maps/%s.ent", filename ), entities, lumplen ); + Con_Printf( "Write 'maps/%s.ent'\n", filename ); + Mem_Free( entities ); + } + + FS_Close( f ); +} + +/* +============== +SV_ReadEntityScript + +pfnMapIsValid use this +============== +*/ +static char *SV_ReadEntityScript( const char *filename, int *flags ) +{ + string bspfilename, entfilename; + int ver = -1, lumpofs = 0, lumplen = 0; + char *ents = NULL; + byte buf[1024]; + dheader_t *header; + size_t ft1, ft2; + file_t *f; + + *flags = 0; + + Q_strncpy( bspfilename, va( "maps/%s.bsp", filename ), sizeof( bspfilename )); + f = FS_Open( bspfilename, "rb", false ); + if( !f ) return NULL; + + SetBits( *flags, MAP_IS_EXIST ); + memset( buf, 0, MAX_SYSPATH ); + FS_Read( f, buf, MAX_SYSPATH ); + header = (dheader_t *)buf; + + // check all the lumps and some other errors + if( !Mod_TestBmodelLumps( bspfilename, buf, (host_developer.value) ? false : true )) + { + SetBits( *flags, MAP_INVALID_VERSION ); + FS_Close( f ); + return NULL; + } + + // after call Mod_TestBmodelLumps we gurantee what map is valid + lumpofs = header->lumps[LUMP_ENTITIES].fileofs; + lumplen = header->lumps[LUMP_ENTITIES].filelen; + + // check for entfile too + Q_strncpy( entfilename, va( "maps/%s.ent", filename ), sizeof( entfilename )); + + // make sure what entity patch is never than bsp + ft1 = FS_FileTime( bspfilename, false ); + ft2 = FS_FileTime( entfilename, true ); + + if( ft2 != -1 && ft1 < ft2 ) + { + // grab .ent files only from gamedir + ents = FS_LoadFile( entfilename, NULL, true ); + } + + if( !ents && lumplen >= 10 ) + { + FS_Seek( f, lumpofs, SEEK_SET ); + ents = Z_Malloc( lumplen + 1 ); + FS_Read( f, ents, lumplen ); + } + FS_Close( f ); // all done + + return ents; +} + +/* +============== +SV_MapIsValid + +Validate map +============== +*/ +int SV_MapIsValid( const char *filename, const char *spawn_entity, const char *landmark_name ) +{ + int flags = 0; + char *pfile; + char *ents; + + ents = SV_ReadEntityScript( filename, &flags ); + + if( ents ) + { + qboolean need_landmark = Q_strlen( landmark_name ) > 0 ? true : false; + char token[2048]; + string check_name; + + // g-cont. in-dev mode we can entering on map even without "info_player_start" + if( !need_landmark && host_developer.value ) + { + // not transition + Mem_Free( ents ); + + // skip spawnpoint checks in devmode + return (flags|MAP_HAS_SPAWNPOINT); + } + + pfile = ents; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_strcmp( token, "classname" )) + { + // check classname for spawn entity + pfile = COM_ParseFile( pfile, check_name ); + if( !Q_strcmp( spawn_entity, check_name )) + { + SetBits( flags, MAP_HAS_SPAWNPOINT ); + + // we already find landmark, stop the parsing + if( need_landmark && FBitSet( flags, MAP_HAS_LANDMARK )) + break; + } + } + else if( need_landmark && !Q_strcmp( token, "targetname" )) + { + // check targetname for landmark entity + pfile = COM_ParseFile( pfile, check_name ); + + if( !Q_strcmp( landmark_name, check_name )) + { + SetBits( flags, MAP_HAS_LANDMARK ); + + // we already find spawnpoint, stop the parsing + if( FBitSet( flags, MAP_HAS_SPAWNPOINT )) + break; + } + } + } + + Mem_Free( ents ); + } + + return flags; +} + +/* +============== +SV_FreePrivateData + +release private edict memory +============== +*/ +void SV_FreePrivateData( edict_t *pEdict ) +{ + if( !pEdict || !pEdict->pvPrivateData ) + return; + + // NOTE: new interface can be missing + if( svgame.dllFuncs2.pfnOnFreeEntPrivateData != NULL ) + svgame.dllFuncs2.pfnOnFreeEntPrivateData( pEdict ); + + if( Mem_IsAllocatedExt( svgame.mempool, pEdict->pvPrivateData )) + Mem_Free( pEdict->pvPrivateData ); + + pEdict->pvPrivateData = NULL; +} + +/* +============== +SV_InitEdict + +clear edict for reuse +============== +*/ +void SV_InitEdict( edict_t *pEdict ) +{ + Assert( pEdict != NULL ); + + SV_FreePrivateData( pEdict ); + memset( &pEdict->v, 0, sizeof( entvars_t )); + pEdict->v.pContainingEntity = pEdict; + pEdict->free = false; +} + +/* +============== +SV_FreeEdict + +unlink edict from world and free it +============== +*/ +void SV_FreeEdict( edict_t *pEdict ) +{ + Assert( pEdict != NULL ); + if( pEdict->free ) return; + + // unlink from world + SV_UnlinkEdict( pEdict ); + + SV_FreePrivateData( pEdict ); + + // mark edict as freed + pEdict->freetime = sv.time; + pEdict->serialnumber++; // invalidate EHANDLE's + pEdict->v.solid = SOLID_NOT; + pEdict->v.flags = 0; + pEdict->v.model = 0; + pEdict->v.takedamage = 0; + pEdict->v.modelindex = 0; + pEdict->v.nextthink = -1; + pEdict->v.colormap = 0; + pEdict->v.frame = 0; + pEdict->v.scale = 0; + pEdict->v.gravity = 0; + pEdict->v.skin = 0; + + VectorClear( pEdict->v.angles ); + VectorClear( pEdict->v.origin ); + pEdict->free = true; +} + +/* +============== +SV_AllocEdict + +allocate new or reuse existing +============== +*/ +edict_t *SV_AllocEdict( void ) +{ + edict_t *e; + int i; + + for( i = svs.maxclients + 1; i < svgame.numEntities; i++ ) + { + e = EDICT_NUM( i ); + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if( e->free && ( e->freetime < 2.0f || ( sv.time - e->freetime ) > 0.5f )) + { + SV_InitEdict( e ); + return e; + } + } + + if( i >= GI->max_edicts ) + Host_Error( "ED_AllocEdict: no free edicts (max is %d)\n", GI->max_edicts ); + + svgame.numEntities++; + e = EDICT_NUM( i ); + SV_InitEdict( e ); + + return e; +} + +/* +============== +SV_GetEntityClass + +get pointer for entity class +============== +*/ +LINK_ENTITY_FUNC SV_GetEntityClass( const char *pszClassName ) +{ + // allocate edict private memory (passed by dlls) + return (LINK_ENTITY_FUNC)COM_GetProcAddress( svgame.hInstance, pszClassName ); +} + +/* +============== +SV_AllocPrivateData + +allocate private data for a given edict +============== +*/ +edict_t* SV_AllocPrivateData( edict_t *ent, string_t className ) +{ + const char *pszClassName; + LINK_ENTITY_FUNC SpawnEdict; + + pszClassName = STRING( className ); + + if( !ent ) + { + // allocate a new one + ent = SV_AllocEdict(); + } + else if( ent->free ) + { + SV_InitEdict( ent ); // re-init edict + } + + ent->v.classname = className; + ent->v.pContainingEntity = ent; // re-link + + // allocate edict private memory (passed by dlls) + SpawnEdict = SV_GetEntityClass( pszClassName ); + + if( !SpawnEdict ) + { + // attempt to create custom entity (Xash3D extension) + if( svgame.physFuncs.SV_CreateEntity && svgame.physFuncs.SV_CreateEntity( ent, pszClassName ) != -1 ) + return ent; + + SpawnEdict = SV_GetEntityClass( "custom" ); + + if( !SpawnEdict ) + { + Con_Printf( S_ERROR "No spawn function for %s\n", STRING( className )); + + // free entity immediately + SV_FreeEdict( ent ); + + return NULL; + } + + SetBits( ent->v.flags, FL_CUSTOMENTITY ); // it's a custom entity but not a beam! + } + + SpawnEdict( &ent->v ); + + return ent; +} + +/* +============== +SV_CreateNamedEntity + +create specified entity, alloc private data +============== +*/ +edict_t* SV_CreateNamedEntity( edict_t *ent, string_t className ) +{ + edict_t *ed = SV_AllocPrivateData( ent, className ); + + // for some reasons this flag should be immediately cleared + if( ed ) ClearBits( ed->v.flags, FL_CUSTOMENTITY ); + + return ed; +} + +/* +============== +SV_FreeEdicts + +release all the edicts from server +============== +*/ +void SV_FreeEdicts( void ) +{ + int i = 0; + edict_t *ent; + + for( i = 0; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + if( ent->free ) continue; + SV_FreeEdict( ent ); + } +} + +/* +============== +SV_PlaybackReliableEvent + +reliable event is must be delivered always +============== +*/ +void SV_PlaybackReliableEvent( sizebuf_t *msg, word eventindex, float delay, event_args_t *args ) +{ + event_args_t nullargs; + + memset( &nullargs, 0, sizeof( nullargs )); + + MSG_BeginServerCmd( msg, svc_event_reliable ); + + // send event index + MSG_WriteUBitLong( msg, eventindex, MAX_EVENT_BITS ); + + if( delay ) + { + // send event delay + MSG_WriteOneBit( msg, 1 ); + MSG_WriteWord( msg, ( delay * 100.0f )); + } + else MSG_WriteOneBit( msg, 0 ); + + // reliable events not use delta-compression just null-compression + MSG_WriteDeltaEvent( msg, &nullargs, args ); +} + +/* +============== +SV_ClassName + +template to get edict classname +============== +*/ +const char *SV_ClassName( const edict_t *e ) +{ + if( !e ) return "(null)"; + if( e->free ) return "freed"; + return STRING( e->v.classname ); +} + +/* +============== +SV_IsValidCmd + +command validation +============== +*/ +static qboolean SV_IsValidCmd( const char *pCmd ) +{ + size_t len = Q_strlen( pCmd ); + + // valid commands all have a ';' or newline '\n' as their last character + if( len && ( pCmd[len-1] == '\n' || pCmd[len-1] == ';' )) + return true; + return false; +} + +/* +============== +SV_AllocPrivateData + +get edict that attached to the client structure +============== +*/ +sv_client_t *SV_ClientFromEdict( const edict_t *pEdict, qboolean spawned_only ) +{ + int i; + + if( !SV_IsValidEdict( pEdict )) + return NULL; + + i = NUM_FOR_EDICT( pEdict ) - 1; + + if( i < 0 || i >= svs.maxclients ) + return NULL; + + if( spawned_only ) + { + if( svs.clients[i].state != cs_spawned ) + return NULL; + } + + return (svs.clients + i); +} + +/* +=============================================================================== + + Game Builtin Functions + +=============================================================================== +*/ +/* +========= +pfnPrecacheModel + +========= +*/ +int pfnPrecacheModel( const char *s ) +{ + qboolean optional = false; + int i; + + if( *s == '!' ) + { + optional = true; + *s++; + } + + if(( i = SV_ModelIndex( s )) == 0 ) + return 0; + + sv.models[i] = Mod_ForName( sv.model_precache[i], false, true ); + + if( !optional ) + SetBits( sv.model_precache_flags[i], RES_FATALIFMISSING ); + + return i; +} + +/* +================= +pfnSetModel + +================= +*/ +void pfnSetModel( edict_t *e, const char *m ) +{ + model_t *mod; + int i; + + if( !SV_IsValidEdict( e )) + return; + + if( COM_CheckString( m )) + { + // check to see if model was properly precached + for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.model_precache[i], m )) + break; + } + + if( i == MAX_MODELS ) + { + Con_Printf( S_ERROR "no precache: %s\n", m ); + return; + } + } + + if( e == svgame.edicts ) + { + if( sv.state == ss_active ) + Con_Printf( S_ERROR "world model can't be changed\n" ); + return; + } + + if( COM_CheckString( m )) + { + e->v.model = MAKE_STRING( sv.model_precache[i] ); + e->v.modelindex = i; + mod = sv.models[i]; + } + else + { + // model will be cleared + e->v.model = e->v.modelindex = 0; + mod = NULL; + } + + // set the model size + if( mod && mod->type != mod_studio ) + SV_SetMinMaxSize( e, mod->mins, mod->maxs, true ); + else SV_SetMinMaxSize( e, vec3_origin, vec3_origin, true ); +} + +/* +================= +pfnModelIndex + +================= +*/ +int pfnModelIndex( const char *m ) +{ + int i; + + if( !COM_CheckString( m )) + return 0; + + for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.model_precache[i], m )) + return i; + } + + Con_Printf( S_ERROR "no precache: %s\n", m ); + return 0; +} + +/* +================= +pfnModelFrames + +================= +*/ +int pfnModelFrames( int modelIndex ) +{ + model_t *pmodel = SV_ModelHandle( modelIndex ); + + if( pmodel != NULL ) + return pmodel->numframes; + return 1; +} + +/* +================= +pfnSetSize + +================= +*/ +void pfnSetSize( edict_t *e, const float *rgflMin, const float *rgflMax ) +{ + if( !SV_IsValidEdict( e )) + return; + + SV_SetMinMaxSize( e, rgflMin, rgflMax, true ); +} + +/* +================= +pfnChangeLevel + +================= +*/ +void pfnChangeLevel( const char *level, const char *landmark ) +{ + int flags, smooth = false; + static uint last_spawncount = 0; + char mapname[MAX_QPATH]; + char landname[MAX_QPATH]; + char *spawn_entity; + char *text; + + if( !COM_CheckString( level ) || sv.state != ss_active ) + return; // ??? + + // make sure we don't issue two changelevels + if( svs.spawncount == last_spawncount ) + return; + last_spawncount = svs.spawncount; + + // hold mapname to other place + Q_strncpy( mapname, level, sizeof( mapname )); + COM_StripExtension( mapname ); + landname[0] ='\0'; + + // g-cont. some level-designers wrote landmark name with space + // and Cmd_TokenizeString separating all the after space as next argument + // emulate this bug for compatibility + if( COM_CheckString( landmark )) + { + text = (char *)landname; + while( *landmark && ((byte)*landmark) != ' ' ) + *text++ = *landmark++; + smooth = true; + *text = '\0'; + } + + // determine spawn entity classname + if( svs.maxclients == 1 ) + spawn_entity = GI->sp_entity; + else spawn_entity = GI->mp_entity; + + flags = SV_MapIsValid( mapname, spawn_entity, landname ); + + if( FBitSet( flags, MAP_INVALID_VERSION )) + { + Con_Printf( S_ERROR "changelevel: %s is invalid or not supported\n", mapname ); + return; + } + + if( !FBitSet( flags, MAP_IS_EXIST )) + { + Con_Printf( S_ERROR "changelevel: map %s doesn't exist\n", mapname ); + return; + } + + if( smooth && !FBitSet( flags, MAP_HAS_LANDMARK )) + { + if( sv_validate_changelevel->value ) + { + // NOTE: we find valid map but specified landmark it's doesn't exist + // run simple changelevel like in q1, throw warning + Con_Printf( S_WARN "changelevel: %s doesn't contain landmark [%s]. smooth transition was disabled\n", mapname, landname ); + smooth = false; + } + } + + if( svs.maxclients > 1 ) + smooth = false; // multiplayer doesn't support smooth transition + + if( smooth && !Q_stricmp( sv.name, level )) + { + Con_Printf( S_ERROR "can't changelevel with same map. Ignored.\n" ); + return; + } + + if( !smooth && !FBitSet( flags, MAP_HAS_SPAWNPOINT )) + { + if( sv_validate_changelevel->value ) + { + Con_Printf( S_ERROR "changelevel: %s doesn't have a valid spawnpoint. Ignored.\n", mapname ); + return; + } + } + + // bad changelevel position invoke enables in one-way transition + if( sv.framecount < 15 ) + { + if( sv_validate_changelevel->value ) + { + Con_Printf( S_WARN "an infinite changelevel was detected and will be disabled until a next save\\restore\n" ); + return; // lock with svs.spawncount here + } + } + + SV_SkipUpdates (); + + // changelevel will be executed on a next frame + if( smooth ) COM_ChangeLevel( mapname, landname, sv.background ); // Smoothed Half-Life changelevel + else COM_ChangeLevel( mapname, NULL, sv.background ); // Classic Quake changlevel +} + +/* +================= +pfnGetSpawnParms + +OBSOLETE, UNUSED +================= +*/ +void pfnGetSpawnParms( edict_t *ent ) +{ +} + +/* +================= +pfnSaveSpawnParms + +OBSOLETE, UNUSED +================= +*/ +void pfnSaveSpawnParms( edict_t *ent ) +{ +} + +/* +================= +pfnVecToYaw + +================= +*/ +float pfnVecToYaw( const float *rgflVector ) +{ + return SV_VecToYaw( rgflVector ); +} + +/* +================= +pfnMoveToOrigin + +================= +*/ +void pfnMoveToOrigin( edict_t *ent, const float *pflGoal, float dist, int iMoveType ) +{ + if( !pflGoal || !SV_IsValidEdict( ent )) + return; + + SV_MoveToOrigin( ent, pflGoal, dist, iMoveType ); +} + +/* +============== +pfnChangeYaw + +============== +*/ +void pfnChangeYaw( edict_t* ent ) +{ + if( !SV_IsValidEdict( ent )) + return; + + ent->v.angles[YAW] = SV_AngleMod( ent->v.ideal_yaw, ent->v.angles[YAW], ent->v.yaw_speed ); +} + +/* +============== +pfnChangePitch + +============== +*/ +void pfnChangePitch( edict_t* ent ) +{ + if( !SV_IsValidEdict( ent )) + return; + + ent->v.angles[PITCH] = SV_AngleMod( ent->v.idealpitch, ent->v.angles[PITCH], ent->v.pitch_speed ); +} + +/* +========= +SV_FindEntityByString + +========= +*/ +edict_t *SV_FindEntityByString( edict_t *pStartEdict, const char *pszField, const char *pszValue ) +{ + int index = 0, e = 0; + TYPEDESCRIPTION *desc = NULL; + edict_t *ed; + const char *t; + + if( !COM_CheckString( pszValue )) + return svgame.edicts; + + if( pStartEdict ) e = NUM_FOR_EDICT( pStartEdict ); + + while(( desc = SV_GetEntvarsDescirption( index++ )) != NULL ) + { + if( !Q_strcmp( pszField, desc->fieldName )) + break; + } + + if( desc == NULL ) + { + Con_Printf( S_ERROR "FindEntityByString: field %s not a string\n", pszField ); + return svgame.edicts; + } + + for( e++; e < svgame.numEntities; e++ ) + { + ed = EDICT_NUM( e ); + if( !SV_IsValidEdict( ed )) continue; + + if( e <= svs.maxclients && !SV_ClientFromEdict( ed, ( svs.maxclients != 1 ))) + continue; + + switch( desc->fieldType ) + { + case FIELD_STRING: + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + t = STRING( *(string_t *)&((byte *)&ed->v)[desc->fieldOffset] ); + if( t != NULL && t != svgame.globals->pStringBase ) + { + if( !Q_strcmp( t, pszValue )) + return ed; + } + break; + } + } + + return svgame.edicts; +} + +/* +========= +SV_FindGlobalEntity + +ripped out from the hl.dll +========= +*/ +edict_t *SV_FindGlobalEntity( string_t classname, string_t globalname ) +{ + edict_t *pent = SV_FindEntityByString( NULL, "globalname", STRING( globalname )); + + if( SV_IsValidEdict( pent )) + { + // don't spam about error - game code already tell us + if( Q_strcmp( SV_ClassName( pent ), STRING( classname ))) + pent = NULL; + } + + return pent; +} + +/* +============== +pfnGetEntityIllum + +returns averaged lightvalue for entity +============== +*/ +int pfnGetEntityIllum( edict_t* pEnt ) +{ + if( !SV_IsValidEdict( pEnt )) + return -1; + + return SV_LightForEntity( pEnt ); +} + +/* +================= +pfnFindEntityInSphere + +find the entity in sphere +================= +*/ +edict_t *pfnFindEntityInSphere( edict_t *pStartEdict, const float *org, float flRadius ) +{ + float distSquared; + int j, e = 0; + float eorg; + edict_t *ent; + + flRadius *= flRadius; + + if( SV_IsValidEdict( pStartEdict )) + e = NUM_FOR_EDICT( pStartEdict ); + + for( e++; e < svgame.numEntities; e++ ) + { + ent = EDICT_NUM( e ); + + if( !SV_IsValidEdict( ent )) + continue; + + // ignore clients that not in a game + if( e <= svs.maxclients && !SV_ClientFromEdict( ent, true )) + continue; + + distSquared = 0.0f; + + for( j = 0; j < 3 && distSquared <= flRadius; j++ ) + { + if( org[j] < ent->v.absmin[j] ) + eorg = org[j] - ent->v.absmin[j]; + else if( org[j] > ent->v.absmax[j] ) + eorg = org[j] - ent->v.absmax[j]; + else eorg = 0.0f; + + distSquared += eorg * eorg; + } + + if( distSquared < flRadius ) + return ent; + } + + return svgame.edicts; +} + +/* +================= +SV_CheckClientPVS + +build the new client PVS +================= +*/ +int SV_CheckClientPVS( int check, qboolean bMergePVS ) +{ + byte *pvs; + vec3_t vieworg; + sv_client_t *cl; + int i, j, k; + edict_t *ent = NULL; + + // cycle to the next one + check = bound( 1, check, svs.maxclients ); + + if( check == svs.maxclients ) + i = 1; // reset cycle + else i = check + 1; + + for( ;; i++ ) + { + if( i == ( svs.maxclients + 1 )) + i = 1; + + ent = EDICT_NUM( i ); + if( i == check ) break; // didn't find anything else + + if( ent->free || !ent->pvPrivateData || FBitSet( ent->v.flags, FL_NOTARGET )) + continue; + + // anything that is a client, or has a client as an enemy + break; + } + + cl = SV_ClientFromEdict( ent, true ); + memset( clientpvs, 0xFF, world.visbytes ); + + // get the PVS for the entity + VectorAdd( ent->v.origin, ent->v.view_ofs, vieworg ); + pvs = Mod_GetPVSForPoint( vieworg ); + if( pvs ) memcpy( clientpvs, pvs, world.visbytes ); + + // transition in progress + if( !cl ) return i; + + // now merge PVS with all the portal cameras + for( k = 0; k < cl->num_viewents && bMergePVS; k++ ) + { + edict_t *view = cl->viewentity[k]; + + if( !SV_IsValidEdict( view )) + continue; + + VectorAdd( view->v.origin, view->v.view_ofs, vieworg ); + pvs = Mod_GetPVSForPoint( vieworg ); + + for( j = 0; j < world.visbytes && pvs; j++ ) + SetBits( clientpvs[j], pvs[j] ); + } + + return i; +} + +/* +================= +pfnFindClientInPVS + +================= +*/ +edict_t* pfnFindClientInPVS( edict_t *pEdict ) +{ + edict_t *pClient; + vec3_t view; + float delta; + model_t *mod; + qboolean bMergePVS; + mleaf_t *leaf; + + if( !SV_IsValidEdict( pEdict )) + return svgame.edicts; + + delta = ( sv.time - sv.lastchecktime ); + + // don't merge visibility for portal entity, only for monsters + bMergePVS = FBitSet( pEdict->v.flags, FL_MONSTER ) ? true : false; + + // find a new check if on a new frame + if( delta < 0.0f || delta >= 0.1f ) + { + sv.lastcheck = SV_CheckClientPVS( sv.lastcheck, bMergePVS ); + sv.lastchecktime = sv.time; + } + + // return check if it might be visible + pClient = EDICT_NUM( sv.lastcheck ); + + if( !SV_ClientFromEdict( pClient, true )) + return svgame.edicts; + + mod = SV_ModelHandle( pEdict->v.modelindex ); + + // portals & monitors + // NOTE: this specific break "radiaton tick" in normal half-life. use only as feature + if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT ) && mod && mod->type == mod_brush && !FBitSet( mod->flags, MODEL_HAS_ORIGIN )) + { + // handle PVS origin for bmodels + VectorAverage( pEdict->v.mins, pEdict->v.maxs, view ); + VectorAdd( view, pEdict->v.origin, view ); + } + else + { + VectorAdd( pEdict->v.origin, pEdict->v.view_ofs, view ); + } + + leaf = Mod_PointInLeaf( view, sv.worldmodel->nodes ); + + if( CHECKVISBIT( clientpvs, leaf->cluster )) + return pClient; // client which currently in PVS + + return svgame.edicts; +} + +/* +================= +pfnEntitiesInPVS + +================= +*/ +edict_t *pfnEntitiesInPVS( edict_t *pview ) +{ + edict_t *pchain, *ptest; + vec3_t viewpoint; + edict_t *pent; + int i; + + if( !SV_IsValidEdict( pview )) + return NULL; + + VectorAdd( pview->v.origin, pview->v.view_ofs, viewpoint ); + pchain = EDICT_NUM( 0 ); + + for( i = 1; i < svgame.numEntities; i++ ) + { + pent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( pent )) + continue; + + if( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( pent->v.aiment )) + ptest = pent->v.aiment; + else ptest = pent; + + if( SV_BoxInPVS( viewpoint, ptest->v.absmin, ptest->v.absmax )) + { + pent->v.chain = pchain; + pchain = pent; + } + } + + return pchain; +} + +/* +============== +pfnMakeVectors + +============== +*/ +void pfnMakeVectors( const float *rgflVector ) +{ + AngleVectors( rgflVector, svgame.globals->v_forward, svgame.globals->v_right, svgame.globals->v_up ); +} + +/* +============== +pfnRemoveEntity + +free edict private mem, unlink physics etc +============== +*/ +void pfnRemoveEntity( edict_t *e ) +{ + if( !SV_IsValidEdict( e )) + return; + + // never free client or world entity + if( NUM_FOR_EDICT( e ) < ( svs.maxclients + 1 )) + { + Con_Printf( S_ERROR "can't delete %s\n", ( e == EDICT_NUM( 0 )) ? "world" : "client" ); + return; + } + + SV_FreeEdict( e ); +} + +/* +============== +pfnCreateNamedEntity + +============== +*/ +edict_t* pfnCreateNamedEntity( string_t className ) +{ + return SV_CreateNamedEntity( NULL, className ); +} + +/* +============= +pfnMakeStatic + +move entity to client +============= +*/ +static void pfnMakeStatic( edict_t *ent ) +{ + sv_static_entity_t *clent; + + if( !SV_IsValidEdict( ent )) + return; + + if( sv.num_static_entities >= MAX_STATIC_ENTITIES ) + { + if( !sv.static_ents_overflow ) + { + Con_Printf( S_WARN "MAX_STATIC_ENTITIES limit exceeded (%d)\n", MAX_STATIC_ENTITIES ); + sv.static_ents_overflow = true; + } + sv.ignored_static_ents++; // continue overflowed entities + return; + } + + clent = &sv.static_entities[sv.num_static_entities++]; + + Q_strncpy( clent->model, STRING( ent->v.model ), sizeof( clent->model )); + VectorCopy( ent->v.origin, clent->origin ); + VectorCopy( ent->v.angles, clent->angles ); + + clent->sequence = ent->v.sequence; + clent->frame = ent->v.frame * 128; + clent->colormap = ent->v.colormap; + clent->skin = ent->v.skin; + clent->body = ent->v.body; + clent->scale = ent->v.scale; + clent->rendermode = ent->v.rendermode; + clent->renderamt = ent->v.renderamt; + clent->rendercolor.r = ent->v.rendercolor[0]; + clent->rendercolor.g = ent->v.rendercolor[1]; + clent->rendercolor.b = ent->v.rendercolor[2]; + clent->renderfx = ent->v.renderfx; + + SV_CreateStaticEntity( &sv.signon, clent ); + + // remove at end of the frame + SetBits( ent->v.flags, FL_KILLME ); +} + +/* +============= +pfnEntIsOnFloor + +legacy builtin +============= +*/ +static int pfnEntIsOnFloor( edict_t *e ) +{ + if( !SV_IsValidEdict( e )) + return 0; + + return SV_CheckBottom( e, MOVE_NORMAL ); +} + +/* +=============== +pfnDropToFloor + +=============== +*/ +int pfnDropToFloor( edict_t* e ) +{ + qboolean monsterClip; + trace_t trace; + vec3_t end; + + if( !SV_IsValidEdict( e )) + return 0; + + monsterClip = FBitSet( e->v.flags, FL_MONSTERCLIP ) ? true : false; + VectorCopy( e->v.origin, end ); + end[2] -= 256.0f; + + trace = SV_Move( e->v.origin, e->v.mins, e->v.maxs, end, MOVE_NORMAL, e, monsterClip ); + + if( trace.allsolid ) + return -1; + + if( trace.fraction == 1.0f ) + return 0; + + VectorCopy( trace.endpos, e->v.origin ); + SV_LinkEdict( e, false ); + SetBits( e->v.flags, FL_ONGROUND ); + e->v.groundentity = trace.ent; + + return 1; +} + +/* +=============== +pfnWalkMove + +=============== +*/ +int pfnWalkMove( edict_t *ent, float yaw, float dist, int iMode ) +{ + vec3_t move; + + if( !SV_IsValidEdict( ent )) + return 0; + + if( !FBitSet( ent->v.flags, FL_FLY|FL_SWIM|FL_ONGROUND )) + return 0; + + yaw = DEG2RAD( yaw ); + VectorSet( move, cos( yaw ) * dist, sin( yaw ) * dist, 0.0f ); + + switch( iMode ) + { + case WALKMOVE_NORMAL: + return SV_MoveStep( ent, move, true ); + case WALKMOVE_WORLDONLY: + return SV_MoveTest( ent, move, true ); + case WALKMOVE_CHECKONLY: + return SV_MoveStep( ent, move, false); + } + return 0; +} + +/* +================= +pfnSetOrigin + +================= +*/ +void pfnSetOrigin( edict_t *e, const float *rgflOrigin ) +{ + if( !SV_IsValidEdict( e )) + return; + + VectorCopy( rgflOrigin, e->v.origin ); + SV_LinkEdict( e, false ); +} + +/* +================= +SV_BuildSoundMsg + +================= +*/ +int SV_BuildSoundMsg( sizebuf_t *msg, edict_t *ent, int chan, const char *sample, int vol, float attn, int flags, int pitch, const vec3_t pos ) +{ + int entityIndex; + int sound_idx; + qboolean spawn; + + if( vol < 0 || vol > 255 ) + { + Con_Printf( S_ERROR "SV_StartSound: volume = %i\n", vol ); + vol = bound( 0, vol, 255 ); + } + + if( attn < 0.0f || attn > 4.0f ) + { + Con_Printf( S_ERROR "SV_StartSound: attenuation %g must be in range 0-4\n", attn ); + attn = bound( 0.0f, attn, 4.0f ); + } + + if( chan < 0 || chan > 7 ) + { + Con_Printf( S_ERROR "SV_StartSound: channel must be in range 0-7\n" ); + chan = bound( 0, chan, 7 ); + } + + if( pitch < 0 || pitch > 255 ) + { + Con_Printf( S_ERROR "SV_StartSound: pitch = %i\n", pitch ); + pitch = bound( 0, pitch, 255 ); + } + + if( !COM_CheckString( sample )) + { + Con_Printf( S_ERROR "SV_StartSound: passed NULL sample\n" ); + return 0; + } + + if( sample[0] == '!' && Q_isdigit( sample + 1 )) + { + SetBits( flags, SND_SENTENCE ); + sound_idx = Q_atoi( sample + 1 ); + } + else if( sample[0] == '#' && Q_isdigit( sample + 1 )) + { + SetBits( flags, SND_SENTENCE|SND_SEQUENCE ); + sound_idx = Q_atoi( sample + 1 ); + } + else + { + // precache_sound can be used twice: cache sounds when loading + // and return sound index when server is active + sound_idx = SV_SoundIndex( sample ); + } + + if( !sound_idx ) + { + Con_Printf( S_ERROR "SV_StartSound: %s not precached (%d)\n", sample, sound_idx ); + return 0; + } + + spawn = FBitSet( flags, SND_RESTORE_POSITION ) ? false : true; + + if( SV_IsValidEdict( ent ) && SV_IsValidEdict( ent->v.aiment )) + entityIndex = NUM_FOR_EDICT( ent->v.aiment ); + else if( SV_IsValidEdict( ent )) + entityIndex = NUM_FOR_EDICT( ent ); + else entityIndex = 0; // assume world + + if( vol != 255 ) SetBits( flags, SND_VOLUME ); + if( attn != ATTN_NONE ) SetBits( flags, SND_ATTENUATION ); + if( pitch != PITCH_NORM ) SetBits( flags, SND_PITCH ); + + // not sending (because this is out of range) + ClearBits( flags, SND_RESTORE_POSITION ); + ClearBits( flags, SND_FILTER_CLIENT ); + ClearBits( flags, SND_SPAWNING ); + + if( spawn ) MSG_BeginServerCmd( msg, svc_sound ); + else MSG_BeginServerCmd( msg, svc_restoresound ); + MSG_WriteUBitLong( msg, flags, MAX_SND_FLAGS_BITS ); + MSG_WriteUBitLong( msg, sound_idx, MAX_SOUND_BITS ); + MSG_WriteUBitLong( msg, chan, MAX_SND_CHAN_BITS ); + + if( FBitSet( flags, SND_VOLUME )) MSG_WriteByte( msg, vol ); + if( FBitSet( flags, SND_ATTENUATION )) MSG_WriteByte( msg, attn * 64 ); + if( FBitSet( flags, SND_PITCH )) MSG_WriteByte( msg, pitch ); + + MSG_WriteUBitLong( msg, entityIndex, MAX_ENTITY_BITS ); + MSG_WriteVec3Coord( msg, pos ); + + return 1; +} + +/* +================= +SV_StartSound + +================= +*/ +void SV_StartSound( edict_t *ent, int chan, const char *sample, float vol, float attn, int flags, int pitch ) +{ + qboolean filter = false; + int msg_dest; + vec3_t origin; + + if( !SV_IsValidEdict( ent )) + return; + + VectorAverage( ent->v.mins, ent->v.maxs, origin ); + VectorAdd( origin, ent->v.origin, origin ); + + if( FBitSet( flags, SND_SPAWNING )) + msg_dest = MSG_INIT; + else if( chan == CHAN_STATIC ) + msg_dest = MSG_ALL; + else if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + msg_dest = MSG_ALL; + else msg_dest = MSG_PAS_R; + + // always sending stop sound command + if( FBitSet( flags, SND_STOP )) + msg_dest = MSG_ALL; + + if( FBitSet( flags, SND_FILTER_CLIENT )) + filter = true; + + if( SV_BuildSoundMsg( &sv.multicast, ent, chan, sample, vol * 255, attn, flags, pitch, origin )) + SV_Multicast( msg_dest, origin, NULL, false, filter ); +} + +/* +================= +pfnEmitAmbientSound + +================= +*/ +void pfnEmitAmbientSound( edict_t *ent, float *pos, const char *sample, float vol, float attn, int flags, int pitch ) +{ + int msg_dest = MSG_PAS_R; + + if( sv.state == ss_loading ) + SetBits( flags, SND_SPAWNING ); + + if( FBitSet( flags, SND_SPAWNING )) + msg_dest = MSG_INIT; + else msg_dest = MSG_ALL; + + // always sending stop sound command + if( FBitSet( flags, SND_STOP )) + msg_dest = MSG_ALL; + + if( SV_BuildSoundMsg( &sv.multicast, ent, CHAN_STATIC, sample, vol * 255, attn, flags, pitch, pos )) + SV_Multicast( msg_dest, pos, NULL, false, false ); +} + +/* +================= +SV_StartMusic + +================= +*/ +void SV_StartMusic( const char *curtrack, const char *looptrack, long position ) +{ + MSG_BeginServerCmd( &sv.multicast, svc_stufftext ); + MSG_WriteString( &sv.multicast, va( "music \"%s\" \"%s\" %i\n", curtrack, looptrack, position )); + SV_Multicast( MSG_ALL, NULL, NULL, false, false ); +} + +/* +================= +pfnTraceLine + +================= +*/ +static void pfnTraceLine( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) +{ + trace_t trace; + + trace = SV_Move( v1, vec3_origin, vec3_origin, v2, fNoMonsters, pentToSkip, false ); + if( !SV_IsValidEdict( trace.ent )) + trace.ent = svgame.edicts; + SV_ConvertTrace( ptr, &trace ); +} + +/* +================= +pfnTraceToss + +================= +*/ +static void pfnTraceToss( edict_t *pent, edict_t *pentToIgnore, TraceResult *ptr ) +{ + trace_t trace; + + if( !SV_IsValidEdict( pent )) + return; + + trace = SV_MoveToss( pent, pentToIgnore ); + SV_ConvertTrace( ptr, &trace ); +} + +/* +================= +pfnTraceHull + +================= +*/ +static void pfnTraceHull( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr ) +{ + trace_t trace; + + if( hullNumber < 0 || hullNumber > 3 ) + hullNumber = 0; + + trace = SV_Move( v1, sv.worldmodel->hulls[hullNumber].clip_mins, sv.worldmodel->hulls[hullNumber].clip_maxs, v2, fNoMonsters, pentToSkip, false ); + SV_ConvertTrace( ptr, &trace ); +} + +/* +============= +pfnTraceMonsterHull + +============= +*/ +static int pfnTraceMonsterHull( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ) +{ + qboolean monsterClip; + trace_t trace; + + if( !SV_IsValidEdict( pEdict )) + return 0; + + monsterClip = FBitSet( pEdict->v.flags, FL_MONSTERCLIP ) ? true : false; + trace = SV_Move( v1, pEdict->v.mins, pEdict->v.maxs, v2, fNoMonsters, pentToSkip, monsterClip ); + SV_ConvertTrace( ptr, &trace ); + + if( trace.allsolid || trace.fraction != 1.0f ) + return true; + return false; +} + +/* +============= +pfnTraceModel + +============= +*/ +static void pfnTraceModel( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr ) +{ + float *mins, *maxs; + model_t *model; + trace_t trace; + + if( !SV_IsValidEdict( pent )) + return; + + if( hullNumber < 0 || hullNumber > 3 ) + hullNumber = 0; + + mins = sv.worldmodel->hulls[hullNumber].clip_mins; + maxs = sv.worldmodel->hulls[hullNumber].clip_maxs; + model = SV_ModelHandle( pent->v.modelindex ); + + if( pent->v.solid == SOLID_CUSTOM ) + { + // NOTE: always goes through custom clipping move + // even if our callbacks is not initialized + SV_CustomClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); + } + else if( model && model->type == mod_brush ) + { + int oldmovetype = pent->v.movetype; + int oldsolid = pent->v.solid; + pent->v.movetype = MOVETYPE_PUSH; + pent->v.solid = SOLID_BSP; + + SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); + + pent->v.movetype = oldmovetype; + pent->v.solid = oldsolid; + } + else + { + SV_ClipMoveToEntity( pent, v1, mins, maxs, v2, &trace ); + } + + SV_ConvertTrace( ptr, &trace ); +} + +/* +============= +pfnTraceTexture + +returns texture basename +============= +*/ +static const char *pfnTraceTexture( edict_t *pTextureEntity, const float *v1, const float *v2 ) +{ + if( !SV_IsValidEdict( pTextureEntity )) + return NULL; + + return SV_TraceTexture( pTextureEntity, v1, v2 ); +} + +/* +============= +pfnTraceSphere + +OBSOLETE, UNUSED +============= +*/ +void pfnTraceSphere( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ) +{ +} + +/* +============= +pfnGetAimVector + +NOTE: speed is unused +============= +*/ +void pfnGetAimVector( edict_t* ent, float speed, float *rgflReturn ) +{ + edict_t *check; + vec3_t start, dir, end, bestdir; + float dist, bestdist; + int i, j; + trace_t tr; + + VectorCopy( svgame.globals->v_forward, rgflReturn ); // assume failure if it returns early + + if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_FAKECLIENT )) + return; + + VectorCopy( ent->v.origin, start ); + VectorAdd( start, ent->v.view_ofs, start ); + + // try sending a trace straight + VectorCopy( svgame.globals->v_forward, dir ); + VectorMA( start, 2048, dir, end ); + tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); + + // don't aim at teammate + if( tr.ent && ( tr.ent->v.takedamage == DAMAGE_AIM || ent->v.team <= 0 || ent->v.team != tr.ent->v.team )) + return; + + // try all possible entities + VectorCopy( svgame.globals->v_forward, bestdir ); + bestdist = Cvar_VariableValue( "sv_aim" ); + + check = EDICT_NUM( 1 ); // start at first client + for( i = 1; i < svgame.numEntities; i++, check++ ) + { + if( check->v.takedamage != DAMAGE_AIM ) + continue; + + if( FBitSet( check->v.flags, FL_FAKECLIENT )) + continue; + + if( ent->v.team > 0 && ent->v.team == check->v.team ) + continue; + + if( check == ent ) + continue; + + for( j = 0; j < 3; j++ ) + end[j] = check->v.origin[j] + 0.5f * (check->v.mins[j] + check->v.maxs[j]); + + VectorSubtract( end, start, dir ); + VectorNormalize( dir ); + dist = DotProduct( dir, svgame.globals->v_forward ); + + if( dist < bestdist ) + continue; // to far to turn + + tr = SV_Move( start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, false ); + + if( tr.ent == check ) + { + // can shoot at this one + VectorCopy( dir, bestdir ); + bestdist = dist; + } + } + + VectorCopy( bestdir, rgflReturn ); +} + +/* +========= +pfnServerCommand + +========= +*/ +void pfnServerCommand( const char* str ) +{ + if( !SV_IsValidCmd( str )) + Con_Printf( S_ERROR "bad server command %s\n", str ); + else Cbuf_AddText( str ); +} + +/* +========= +pfnServerExecute + +========= +*/ +void pfnServerExecute( void ) +{ + Cbuf_Execute(); + + if( svgame.config_executed ) + return; + + // here we restore arhcived cvars only from game.dll + host.apply_game_config = true; + Cbuf_AddText( "exec config.cfg\n" ); + Cbuf_Execute(); + + if( host.sv_cvars_restored > 0 ) + Con_DPrintf( "server executing ^2config.cfg^7 (%i cvars)\n", host.sv_cvars_restored ); + + host.apply_game_config = false; + svgame.config_executed = true; + host.sv_cvars_restored = 0; +} + +/* +========= +pfnClientCommand + +========= +*/ +void pfnClientCommand( edict_t* pEdict, char* szFmt, ... ) +{ + sv_client_t *cl; + string buffer; + va_list args; + + if( sv.state != ss_active ) + return; // early out + + if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) + { + Con_Printf( S_ERROR "stuffcmd: client is not spawned!\n" ); + return; + } + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, MAX_STRING, szFmt, args ); + va_end( args ); + + if( SV_IsValidCmd( buffer )) + { + MSG_BeginServerCmd( &cl->netchan.message, svc_stufftext ); + MSG_WriteString( &cl->netchan.message, buffer ); + } + else Con_Printf( S_ERROR "Tried to stuff bad command %s\n", buffer ); +} + +/* +================= +pfnParticleEffect + +Make sure the event gets sent to all clients +================= +*/ +void pfnParticleEffect( const float *org, const float *dir, float color, float count ) +{ + int v; + + if( MSG_GetNumBytesLeft( &sv.datagram ) < 16 ) + return; + + MSG_BeginServerCmd( &sv.datagram, svc_particle ); + MSG_WriteVec3Coord( &sv.datagram, org ); + v = bound( -128, dir[0] * 16.0f, 127 ); + MSG_WriteChar( &sv.datagram, v ); + v = bound( -128, dir[1] * 16.0f, 127 ); + MSG_WriteChar( &sv.datagram, v ); + v = bound( -128, dir[2] * 16.0f, 127 ); + MSG_WriteChar( &sv.datagram, v ); + MSG_WriteByte( &sv.datagram, count ); + MSG_WriteByte( &sv.datagram, color ); + MSG_WriteByte( &sv.datagram, 0 ); // z-vel +} + +/* +=============== +pfnLightStyle + +=============== +*/ +void pfnLightStyle( int style, const char* val ) +{ + if( style < 0 ) style = 0; + if( style >= MAX_LIGHTSTYLES ) + Host_Error( "SV_LightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); + if( sv.loadgame ) return; // don't let the world overwrite our restored styles + + SV_SetLightStyle( style, val, 0.0f ); // set correct style +} + +/* +================= +pfnDecalIndex + +register decal name on client +================= +*/ +int pfnDecalIndex( const char *m ) +{ + int i; + + if( !COM_CheckString( m )) + return -1; + + for( i = 1; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) + { + if( !Q_stricmp( host.draw_decals[i], m )) + return i; + } + + return -1; +} + +/* +============= +pfnMessageBegin + +============= +*/ +void pfnMessageBegin( int msg_dest, int msg_num, const float *pOrigin, edict_t *ed ) +{ + int i, iSize; + + if( svgame.msg_started ) + Host_Error( "MessageBegin: New message started when msg '%s' has not been sent yet\n", svgame.msg_name ); + svgame.msg_started = true; + + // check range + msg_num = bound( svc_bad, msg_num, 255 ); + + if( msg_num <= svc_lastmsg ) + { + svgame.msg_index = -msg_num; // this is a system message + svgame.msg_name = svc_strings[msg_num]; + + if( msg_num == svc_temp_entity ) + iSize = -1; // temp entity have variable size + else iSize = 0; + } + else + { + // check for existing + for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) + { + if( svgame.msg[i].number == msg_num ) + break; // found + } + + if( i == MAX_USER_MESSAGES ) + { + Host_Error( "MessageBegin: tried to send unregistered message %i\n", msg_num ); + return; + } + + svgame.msg_name = svgame.msg[i].name; + iSize = svgame.msg[i].size; + svgame.msg_index = i; + } + + MSG_WriteCmdExt( &sv.multicast, msg_num, NS_SERVER, svgame.msg_name ); + + // save message destination + if( pOrigin ) VectorCopy( pOrigin, svgame.msg_org ); + else VectorClear( svgame.msg_org ); + + if( iSize == -1 ) + { + // variable sized messages sent size as first short + svgame.msg_size_index = MSG_GetNumBytesWritten( &sv.multicast ); + MSG_WriteWord( &sv.multicast, 0 ); // reserve space for now + } + else svgame.msg_size_index = -1; // message has constant size + + svgame.msg_realsize = 0; + svgame.msg_dest = msg_dest; + svgame.msg_ent = ed; +} + +/* +============= +pfnMessageEnd + +============= +*/ +void pfnMessageEnd( void ) +{ + const char *name = "Unknown"; + float *org = NULL; + + if( svgame.msg_name ) name = svgame.msg_name; + if( !svgame.msg_started ) Host_Error( "MessageEnd: called with no active message\n" ); + svgame.msg_started = false; + + if( MSG_CheckOverflow( &sv.multicast )) + { + Con_Printf( S_ERROR "MessageEnd: %s has overflow multicast buffer\n", name ); + MSG_Clear( &sv.multicast ); + return; + } + + // check for system message + if( svgame.msg_index < 0 ) + { + if( svgame.msg_size_index != -1 ) + { + // variable sized message + if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) + { + Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); + MSG_Clear( &sv.multicast ); + return; + } + else if( svgame.msg_realsize < 0 ) + { + Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); + MSG_Clear( &sv.multicast ); + return; + } + + sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + } + } + else if( svgame.msg[svgame.msg_index].size != -1 ) + { + int expsize = svgame.msg[svgame.msg_index].size; + int realsize = svgame.msg_realsize; + + // compare sizes + if( expsize != realsize ) + { + Con_Printf( S_ERROR "SV_Multicast: %s expected %i bytes, it written %i. Ignored.\n", name, expsize, realsize ); + MSG_Clear( &sv.multicast ); + return; + } + } + else if( svgame.msg_size_index != -1 ) + { + // variable sized message + if( svgame.msg_realsize > MAX_USERMSG_LENGTH ) + { + Con_Printf( S_ERROR "SV_Multicast: %s too long (more than %d bytes)\n", name, MAX_USERMSG_LENGTH ); + MSG_Clear( &sv.multicast ); + return; + } + else if( svgame.msg_realsize < 0 ) + { + Con_Printf( S_ERROR "SV_Multicast: %s writes NULL message\n", name ); + MSG_Clear( &sv.multicast ); + return; + } + + *(word *)&sv.multicast.pData[svgame.msg_size_index] = svgame.msg_realsize; + } + else + { + // this should never happen + Con_Printf( S_ERROR "SV_Multicast: %s have encountered error\n", name ); + MSG_Clear( &sv.multicast ); + return; + } + + // update some messages in case their was format was changed and we want to keep backward compatibility + if( svgame.msg_index < 0 ) + { + int svc_msg = abs( svgame.msg_index ); + + if(( svc_msg == svc_finale || svc_msg == svc_cutscene ) && svgame.msg_realsize == 0 ) + MSG_WriteChar( &sv.multicast, 0 ); // write null string + } + + if( !VectorIsNull( svgame.msg_org )) org = svgame.msg_org; + svgame.msg_dest = bound( MSG_BROADCAST, svgame.msg_dest, MSG_SPEC ); + + SV_Multicast( svgame.msg_dest, org, svgame.msg_ent, true, false ); +} + +/* +============= +pfnWriteByte + +============= +*/ +void pfnWriteByte( int iValue ) +{ + if( iValue == -1 ) iValue = 0xFF; // convert char to byte + MSG_WriteByte( &sv.multicast, (byte)iValue ); + svgame.msg_realsize++; +} + +/* +============= +pfnWriteChar + +============= +*/ +void pfnWriteChar( int iValue ) +{ + MSG_WriteChar( &sv.multicast, (char)iValue ); + svgame.msg_realsize++; +} + +/* +============= +pfnWriteShort + +============= +*/ +void pfnWriteShort( int iValue ) +{ + MSG_WriteShort( &sv.multicast, (short)iValue ); + svgame.msg_realsize += 2; +} + +/* +============= +pfnWriteLong + +============= +*/ +void pfnWriteLong( int iValue ) +{ + MSG_WriteLong( &sv.multicast, iValue ); + svgame.msg_realsize += 4; +} + +/* +============= +pfnWriteAngle + +this is low-res angle +============= +*/ +void pfnWriteAngle( float flValue ) +{ + int iAngle = ((int)(( flValue ) * 256 / 360) & 255); + + MSG_WriteChar( &sv.multicast, iAngle ); + svgame.msg_realsize += 1; +} + +/* +============= +pfnWriteCoord + +============= +*/ +void pfnWriteCoord( float flValue ) +{ + MSG_WriteCoord( &sv.multicast, flValue ); + svgame.msg_realsize += 2; +} + +/* +============= +pfnWriteBytes + +============= +*/ +void pfnWriteBytes( const byte *bytes, int count ) +{ + MSG_WriteBytes( &sv.multicast, bytes, count ); + svgame.msg_realsize += count; +} + +/* +============= +pfnWriteString + +============= +*/ +void pfnWriteString( const char *src ) +{ + static char string[MAX_USERMSG_LENGTH]; + int len = Q_strlen( src ) + 1; + int rem = rem = sizeof( string ) - 1; + char *dst; + + if( len == 1 ) + { + MSG_WriteChar( &sv.multicast, 0 ); + svgame.msg_realsize += 1; + return; // fast exit + } + + // prepare string to sending + dst = string; + + while( 1 ) + { + // some escaped chars parsed as two symbols - merge it here + if( src[0] == '\\' && src[1] == 'n' ) + { + *dst++ = '\n'; + src += 2; + len -= 1; + } + else if( src[0] == '\\' && src[1] == 'r' ) + { + *dst++ = '\r'; + src += 2; + len -= 1; + } + else if( src[0] == '\\' && src[1] == 't' ) + { + *dst++ = '\t'; + src += 2; + len -= 1; + } + else if(( *dst++ = *src++ ) == 0 ) + break; + + if( --rem <= 0 ) + { + Con_Printf( S_ERROR "pfnWriteString: exceeds %i symbols\n", len ); + *dst = '\0'; // string end (not included in count) + len = Q_strlen( string ) + 1; + break; + } + } + + *dst = '\0'; // string end (not included in count) + MSG_WriteString( &sv.multicast, string ); + + // NOTE: some messages with constant string length can be marked as known sized + svgame.msg_realsize += len; +} + +/* +============= +pfnWriteEntity + +============= +*/ +void pfnWriteEntity( int iValue ) +{ + if( iValue < 0 || iValue >= svgame.numEntities ) + Host_Error( "MSG_WriteEntity: invalid entnumber %i\n", iValue ); + MSG_WriteShort( &sv.multicast, (short)iValue ); + svgame.msg_realsize += 2; +} + +/* +============= +pfnAlertMessage + +============= +*/ +static void pfnAlertMessage( ALERT_TYPE type, char *szFmt, ... ) +{ + char buffer[2048]; + va_list args; + + if( type == at_logged && svs.maxclients > 1 ) + { + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + Log_Printf( "%s", buffer ); + return; + } + + if( host_developer.value <= DEV_NONE ) + return; + + // g-cont: some mods have wrong aiconsole messages that crash the engine + if( type == at_aiconsole && host_developer.value < DEV_EXTENDED ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, sizeof( buffer ), szFmt, args ); + va_end( args ); + + // check message for pass + switch( type ) + { + case at_notice: + Con_Printf( S_NOTE "%s", buffer ); + break; + case at_console: + Con_Printf( "%s", buffer ); + break; + case at_aiconsole: + Con_DPrintf( "%s", buffer ); + break; + case at_warning: + Con_Printf( S_WARN "%s", buffer ); + break; + case at_error: + Con_Printf( S_ERROR "%s", buffer ); + break; + } +} + +/* +============= +pfnEngineFprintf + +OBSOLETE, UNUSED +============= +*/ +static void pfnEngineFprintf( FILE *pfile, char *szFmt, ... ) +{ +} + +/* +============= +pfnBuildSoundMsg + +Customizable sound message +============= +*/ +void pfnBuildSoundMsg( edict_t *pSource, int chan, const char *samp, float fvol, float attn, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *pSend ) +{ + pfnMessageBegin( msg_dest, msg_type, pOrigin, pSend ); + SV_BuildSoundMsg( &sv.multicast, pSource, chan, samp, fvol * 255, attn, fFlags, pitch, pOrigin ); + pfnMessageEnd(); +} + +/* +============= +pfnPvAllocEntPrivateData + +============= +*/ +void *pfnPvAllocEntPrivateData( edict_t *pEdict, long cb ) +{ + Assert( pEdict != NULL ); + + SV_FreePrivateData( pEdict ); + + if( cb > 0 ) + { + // a poke646 have memory corrupt in somewhere - this is trashed last sixteen bytes :( + pEdict->pvPrivateData = Mem_Alloc( svgame.mempool, (cb + 15) & ~15 ); + } + + return pEdict->pvPrivateData; +} + +/* +============= +pfnPvEntPrivateData + +we already have copy of this function in 'enginecallback.h' :-) +============= +*/ +void *pfnPvEntPrivateData( edict_t *pEdict ) +{ + if( pEdict ) + return pEdict->pvPrivateData; + return NULL; +} + +/* +============= +SV_AllocString + +allocate new engine string +============= +*/ +string_t SV_AllocString( const char *szString ) +{ + char *out, *out_p; + int i, l; + + if( svgame.physFuncs.pfnAllocString != NULL ) + return svgame.physFuncs.pfnAllocString( szString ); + + if( !COM_CheckString( szString )) + return 0; + + l = Q_strlen( szString ) + 1; + + out = out_p = Mem_Alloc( svgame.stringspool, l ); + for( i = 0; i < l; i++ ) + { + if( szString[i] == '\\' && i < l - 1 ) + { + i++; + if( szString[i] == 'n') + *out_p++ = '\n'; + else *out_p++ = '\\'; + } + else *out_p++ = szString[i]; + } + + return out - svgame.globals->pStringBase; +} + +/* +============= +SV_MakeString + +make constant string +============= +*/ +string_t SV_MakeString( const char *szValue ) +{ + if( svgame.physFuncs.pfnMakeString != NULL ) + return svgame.physFuncs.pfnMakeString( szValue ); + return szValue - svgame.globals->pStringBase; +} + + +/* +============= +SV_GetString + +============= +*/ +const char *SV_GetString( string_t iString ) +{ + if( svgame.physFuncs.pfnGetString != NULL ) + return svgame.physFuncs.pfnGetString( iString ); + return (svgame.globals->pStringBase + iString); +} + +/* +============= +pfnGetVarsOfEnt + +============= +*/ +entvars_t *pfnGetVarsOfEnt( edict_t *pEdict ) +{ + if( pEdict ) + return &pEdict->v; + return NULL; +} + +/* +============= +pfnPEntityOfEntOffset + +============= +*/ +edict_t* pfnPEntityOfEntOffset( int iEntOffset ) +{ + return (edict_t *)((byte *)svgame.edicts + iEntOffset); +} + +/* +============= +pfnEntOffsetOfPEntity + +============= +*/ +int pfnEntOffsetOfPEntity( const edict_t *pEdict ) +{ + return (byte *)pEdict - (byte *)svgame.edicts; +} + +/* +============= +pfnIndexOfEdict + +============= +*/ +int pfnIndexOfEdict( const edict_t *pEdict ) +{ + int number; + + if( !pEdict ) return 0; // world ? + + number = NUM_FOR_EDICT( pEdict ); + if( number < 0 || number > GI->max_edicts ) + Host_Error( "bad entity number %d\n", number ); + return number; +} + +/* +============= +pfnPEntityOfEntIndex + +============= +*/ +edict_t *pfnPEntityOfEntIndex( int iEntIndex ) +{ + if( iEntIndex >= 0 && iEntIndex < GI->max_edicts ) + { + edict_t *pEdict = EDICT_NUM( iEntIndex ); + + if( !iEntIndex || FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return pEdict; // just get access to array + + if( SV_IsValidEdict( pEdict ) && pEdict->pvPrivateData ) + return pEdict; + + // g-cont: world and clients can be acessed even without private data! + if( SV_IsValidEdict( pEdict ) && SV_IsPlayerIndex( iEntIndex )) + return pEdict; + } + + return NULL; +} + +/* +============= +pfnFindEntityByVars + +debug thing +============= +*/ +edict_t* pfnFindEntityByVars( entvars_t *pvars ) +{ + edict_t *pEdict; + int i; + + // don't pass invalid arguments + if( !pvars ) return NULL; + + for( i = 0; i < GI->max_edicts; i++ ) + { + pEdict = EDICT_NUM( i ); + + // g-cont: we should compare pointers + if( &pEdict->v == pvars ) + return pEdict; // found it + } + + return NULL; +} + +/* +============= +pfnGetModelPtr + +returns pointer to a studiomodel +============= +*/ +static void *pfnGetModelPtr( edict_t *pEdict ) +{ + model_t *mod; + + if( !SV_IsValidEdict( pEdict )) + return NULL; + + mod = SV_ModelHandle( pEdict->v.modelindex ); + return Mod_StudioExtradata( mod ); +} + +/* +============= +SV_SendUserReg + +============= +*/ +void SV_SendUserReg( sizebuf_t *msg, sv_user_message_t *user ) +{ + MSG_BeginServerCmd( msg, svc_usermessage ); + MSG_WriteByte( msg, user->number ); + MSG_WriteWord( msg, (word)user->size ); + MSG_WriteString( msg, user->name ); +} + +/* +============= +pfnRegUserMsg + +============= +*/ +int pfnRegUserMsg( const char *pszName, int iSize ) +{ + int i; + + if( !COM_CheckString( pszName )) + return svc_bad; + + if( Q_strlen( pszName ) >= sizeof( svgame.msg[0].name )) + { + Con_Printf( S_ERROR "REG_USER_MSG: too long name %s\n", pszName ); + return svc_bad; // force error + } + + if( iSize > MAX_USERMSG_LENGTH ) + { + Con_Printf( S_ERROR "REG_USER_MSG: %s has too big size %i\n", pszName, iSize ); + return svc_bad; // force error + } + + // make sure what size inrange + iSize = bound( -1, iSize, MAX_USERMSG_LENGTH ); + + // message 0 is reserved for svc_bad + for( i = 1; i < MAX_USER_MESSAGES && svgame.msg[i].name[0]; i++ ) + { + // see if already registered + if( !Q_strcmp( svgame.msg[i].name, pszName )) + return svc_lastmsg + i; // offset + } + + if( i == MAX_USER_MESSAGES ) + { + Con_Printf( S_ERROR "REG_USER_MSG: user messages limit exceeded\n" ); + return svc_bad; + } + + // register new message + Q_strncpy( svgame.msg[i].name, pszName, sizeof( svgame.msg[i].name )); + svgame.msg[i].number = svc_lastmsg + i; + svgame.msg[i].size = iSize; + + if( sv.state == ss_active ) + { + // tell the client about new user message + SV_SendUserReg( &sv.multicast, &svgame.msg[i] ); + SV_Multicast( MSG_ALL, NULL, NULL, false, false ); + } + + return svgame.msg[i].number; +} + +/* +============= +pfnAnimationAutomove + +OBSOLETE, UNUSED +============= +*/ +void pfnAnimationAutomove( const edict_t* pEdict, float flTime ) +{ +} + +/* +============= +pfnGetBonePosition + +============= +*/ +static void pfnGetBonePosition( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ) +{ + if( !SV_IsValidEdict( pEdict )) + return; + Mod_GetBonePosition( pEdict, iBone, rgflOrigin, rgflAngles ); +} + +/* +============= +pfnFunctionFromName + +============= +*/ +dword pfnFunctionFromName( const char *pName ) +{ + return COM_FunctionFromName( svgame.hInstance, pName ); +} + +/* +============= +pfnNameForFunction + +============= +*/ +const char *pfnNameForFunction( dword function ) +{ + return COM_NameForFunction( svgame.hInstance, function ); +} + +/* +============= +pfnClientPrintf + +============= +*/ +void pfnClientPrintf( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ) +{ + sv_client_t *client; + + if(( client = SV_ClientFromEdict( pEdict, false )) == NULL ) + { + Con_Printf( "tried to sprint to a non-client\n" ); + return; + } + + if( FBitSet( client->flags, FCL_FAKECLIENT )) + return; + + switch( ptype ) + { + case print_console: + case print_chat: + SV_ClientPrintf( client, "%s", szMsg ); + break; + case print_center: + MSG_BeginServerCmd( &client->netchan.message, svc_centerprint ); + MSG_WriteString( &client->netchan.message, szMsg ); + break; + } +} + +/* +============= +pfnServerPrint + +print to the server console +============= +*/ +void pfnServerPrint( const char *szMsg ) +{ + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + SV_BroadcastPrintf( NULL, "%s", szMsg ); + else Con_Printf( "%s", szMsg ); +} + +/* +============= +pfnGetAttachment + +============= +*/ +static void pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ) +{ + if( !SV_IsValidEdict( pEdict )) + return; + Mod_StudioGetAttachment( pEdict, iAttachment, rgflOrigin, rgflAngles ); +} + +/* +============= +pfnCrosshairAngle + +============= +*/ +void pfnCrosshairAngle( const edict_t *pClient, float pitch, float yaw ) +{ + sv_client_t *client; + + if(( client = SV_ClientFromEdict( pClient, true )) == NULL ) + return; + + // fakeclients ignores it silently + if( FBitSet( client->flags, FCL_FAKECLIENT )) + return; + + if( pitch > 180.0f ) pitch -= 360; + if( pitch < -180.0f ) pitch += 360; + if( yaw > 180.0f ) yaw -= 360; + if( yaw < -180.0f ) yaw += 360; + + MSG_BeginServerCmd( &client->netchan.message, svc_crosshairangle ); + MSG_WriteChar( &client->netchan.message, pitch * 5 ); + MSG_WriteChar( &client->netchan.message, yaw * 5 ); +} + +/* +============= +pfnSetView + +============= +*/ +void pfnSetView( const edict_t *pClient, const edict_t *pViewent ) +{ + sv_client_t *client; + int viewEnt; + + if( !SV_IsValidEdict( pClient )) + return; + + if(( client = SV_ClientFromEdict( pClient, false )) == NULL ) + { + Con_Printf( S_ERROR "PF_SetView_I: not a client!\n" ); + return; + } + + if( !SV_IsValidEdict( pViewent ) || pClient == pViewent ) + client->pViewEntity = NULL; // just reset viewentity + else client->pViewEntity = (edict_t *)pViewent; + + // fakeclients ignore to send client message (but can see into the trigger_camera through the PVS) + if( FBitSet( client->flags, FCL_FAKECLIENT )) + return; + + if( client->pViewEntity ) + viewEnt = NUM_FOR_EDICT( client->pViewEntity ); + else viewEnt = NUM_FOR_EDICT( client->edict ); + + MSG_BeginServerCmd( &client->netchan.message, svc_setview ); + MSG_WriteWord( &client->netchan.message, viewEnt ); +} + +/* +============= +pfnStaticDecal + +============= +*/ +void pfnStaticDecal( const float *origin, int decalIndex, int entityIndex, int modelIndex ) +{ + SV_CreateDecal( &sv.signon, origin, decalIndex, entityIndex, modelIndex, FDECAL_PERMANENT, 1.0f ); +} + +/* +============= +pfnIsDedicatedServer + +============= +*/ +int pfnIsDedicatedServer( void ) +{ + return (host.type == HOST_DEDICATED); +} + +/* +============= +pfnGetPlayerWONId + +OBSOLETE, UNUSED +============= +*/ +uint pfnGetPlayerWONId( edict_t *e ) +{ + return -1; +} + +/* +============= +pfnIsMapValid + +vaild map must contain one info_player_deatchmatch +============= +*/ +int pfnIsMapValid( char *filename ) +{ + int flags = SV_MapIsValid( filename, GI->mp_entity, NULL ); + + if( FBitSet( flags, MAP_IS_EXIST ) && FBitSet( flags, MAP_HAS_SPAWNPOINT )) + return true; + return false; +} + +/* +============= +pfnFadeClientVolume + +============= +*/ +void pfnFadeClientVolume( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ) +{ + sv_client_t *cl; + + if(( cl = SV_ClientFromEdict( pEdict, true )) == NULL ) + { + MsgDev( D_ERROR, "SV_FadeClientVolume: client is not spawned!\n" ); + return; + } + + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + return; + + MSG_BeginServerCmd( &cl->netchan.message, svc_soundfade ); + MSG_WriteByte( &cl->netchan.message, fadePercent ); + MSG_WriteByte( &cl->netchan.message, holdTime ); + MSG_WriteByte( &cl->netchan.message, fadeOutSeconds ); + MSG_WriteByte( &cl->netchan.message, fadeInSeconds ); +} + +/* +============= +pfnSetClientMaxspeed + +fakeclients can be changed speed to +============= +*/ +void pfnSetClientMaxspeed( const edict_t *pEdict, float fNewMaxspeed ) +{ + sv_client_t *cl; + + // not spawned clients allowed + if(( cl = SV_ClientFromEdict( pEdict, false )) == NULL ) + return; + + fNewMaxspeed = bound( -svgame.movevars.maxspeed, fNewMaxspeed, svgame.movevars.maxspeed ); + Info_SetValueForKey( cl->physinfo, "maxspd", va( "%.f", fNewMaxspeed ), MAX_INFO_STRING ); + cl->edict->v.maxspeed = fNewMaxspeed; +} + +/* +============= +pfnRunPlayerMove + +============= +*/ +void pfnRunPlayerMove( edict_t *pClient, const float *viewangles, float fmove, float smove, float upmove, word buttons, byte impulse, byte msec ) +{ + sv_client_t *cl, *oldcl; + usercmd_t cmd; + uint seed; + + if(( cl = SV_ClientFromEdict( pClient, true )) == NULL ) + return; + + if( !FBitSet( cl->flags, FCL_FAKECLIENT )) + return; // only fakeclients allows + + oldcl = sv.current_client; + + sv.current_client = SV_ClientFromEdict( pClient, true ); + sv.current_client->timebase = (sv.time + sv.frametime) - ((double)msec / 1000.0); + + memset( &cmd, 0, sizeof( cmd )); + VectorCopy( viewangles, cmd.viewangles ); + cmd.forwardmove = fmove; + cmd.sidemove = smove; + cmd.upmove = upmove; + cmd.buttons = buttons; + cmd.impulse = impulse; + cmd.msec = msec; + + seed = COM_RandomLong( 0, 0x7fffffff ); // full range + + SV_RunCmd( cl, &cmd, seed ); + + cl->lastcmd = cmd; + sv.current_client = oldcl; +} + +/* +============= +pfnNumberOfEntities + +returns actual entity count +============= +*/ +int pfnNumberOfEntities( void ) +{ + int i, total = 0; + + for( i = 0; i < svgame.numEntities; i++ ) + { + if( svgame.edicts[i].free ) + continue; + total++; + } + + return total; +} + +/* +============= +pfnGetInfoKeyBuffer + +============= +*/ +char *pfnGetInfoKeyBuffer( edict_t *e ) +{ + sv_client_t *cl; + + // NULL passes localinfo + if( !SV_IsValidEdict( e )) + return SV_Localinfo(); + + // world passes serverinfo + if( e == svgame.edicts ) + return SV_Serverinfo(); + + // userinfo for specified edict + if(( cl = SV_ClientFromEdict( e, false )) != NULL ) + return cl->userinfo; + + return ""; // assume error +} + +/* +============= +pfnSetValueForKey + +============= +*/ +void pfnSetValueForKey( char *infobuffer, char *key, char *value ) +{ + if( infobuffer == svs.localinfo ) + Info_SetValueForStarKey( infobuffer, key, value, MAX_LOCALINFO_STRING ); + else if( infobuffer == svs.serverinfo ) + Info_SetValueForStarKey( infobuffer, key, value, MAX_SERVERINFO_STRING ); + else Con_Printf( S_ERROR "can't set client keys with SetValueForKey\n" ); +} + +/* +============= +pfnSetClientKeyValue + +============= +*/ +void pfnSetClientKeyValue( int clientIndex, char *infobuffer, char *key, char *value ) +{ + sv_client_t *cl; + + if( infobuffer == svs.localinfo || infobuffer == svs.serverinfo ) + return; + + clientIndex -= 1; + + if( !svs.clients || clientIndex < 0 || clientIndex >= svs.maxclients ) + return; + + // value not changed? + if( !Q_strcmp( Info_ValueForKey( infobuffer, key ), value )) + return; + + cl = &svs.clients[clientIndex]; + + Info_SetValueForStarKey( infobuffer, key, value, MAX_INFO_STRING ); + SetBits( cl->flags, FCL_RESEND_USERINFO ); + cl->next_sendinfotime = 0.0; // send immediately +} + +/* +============= +pfnGetPhysicsKeyValue + +============= +*/ +const char *pfnGetPhysicsKeyValue( const edict_t *pClient, const char *key ) +{ + sv_client_t *cl; + + // pfnUserInfoChanged passed + if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) + { + Con_Printf( S_ERROR "GetPhysicsKeyValue: tried to a non-client!\n" ); + return ""; + } + + return Info_ValueForKey( cl->physinfo, key ); +} + +/* +============= +pfnSetPhysicsKeyValue + +============= +*/ +void pfnSetPhysicsKeyValue( const edict_t *pClient, const char *key, const char *value ) +{ + sv_client_t *cl; + + // pfnUserInfoChanged passed + if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) + { + Con_Printf( S_ERROR "SetPhysicsKeyValue: tried to a non-client!\n" ); + return; + } + + Info_SetValueForKey( cl->physinfo, key, value, MAX_INFO_STRING ); +} + +/* +============= +pfnGetPhysicsInfoString + +============= +*/ +const char *pfnGetPhysicsInfoString( const edict_t *pClient ) +{ + sv_client_t *cl; + + // pfnUserInfoChanged passed + if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) + { + Con_Printf( S_ERROR "GetPhysicsInfoString: tried to a non-client!\n" ); + return ""; + } + + return cl->physinfo; +} + +/* +============= +pfnPrecacheEvent + +register or returns already registered event id +a type of event is ignored at this moment +============= +*/ +word pfnPrecacheEvent( int type, const char *psz ) +{ + return (word)SV_EventIndex( psz ); +} + +/* +============= +pfnPlaybackEvent + +============= +*/ +void SV_PlaybackEventFull( int flags, const edict_t *pInvoker, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + sv_client_t *cl; + event_state_t *es; + event_args_t args; + event_info_t *ei = NULL; + int j, slot, bestslot; + int invokerIndex; + byte *mask = NULL; + vec3_t pvspoint; + + if( FBitSet( flags, FEV_CLIENT )) + return; // someone stupid joke + + // first check event for out of bounds + if( eventindex < 1 || eventindex > MAX_EVENTS ) + { + Con_Printf( S_ERROR "EV_Playback: invalid eventindex %i\n", eventindex ); + return; + } + + // check event for precached + if( !COM_CheckString( sv.event_precache[eventindex] )) + { + Con_Printf( S_ERROR "EV_Playback: event %i was not precached\n", eventindex ); + return; + } + + memset( &args, 0, sizeof( args )); + + if( origin && !VectorIsNull( origin )) + { + VectorCopy( origin, args.origin ); + args.flags |= FEVENT_ORIGIN; + } + + if( angles && !VectorIsNull( angles )) + { + VectorCopy( angles, args.angles ); + args.flags |= FEVENT_ANGLES; + } + + // copy other parms + args.fparam1 = fparam1; + args.fparam2 = fparam2; + args.iparam1 = iparam1; + args.iparam2 = iparam2; + args.bparam1 = bparam1; + args.bparam2 = bparam2; + + VectorClear( pvspoint ); + + if( SV_IsValidEdict( pInvoker )) + { + // add the view_ofs to avoid problems with crossed contents line + VectorAdd( pInvoker->v.origin, pInvoker->v.view_ofs, pvspoint ); + args.entindex = invokerIndex = NUM_FOR_EDICT( pInvoker ); + + // g-cont. allow 'ducking' param for all entities + args.ducking = FBitSet( pInvoker->v.flags, FL_DUCKING ) ? true : false; + + // this will be send only for reliable event + if( !FBitSet( args.flags, FEVENT_ORIGIN )) + VectorCopy( pInvoker->v.origin, args.origin ); + + // this will be send only for reliable event + if( !FBitSet( args.flags, FEVENT_ANGLES )) + VectorCopy( pInvoker->v.angles, args.angles ); + } + else + { + VectorCopy( args.origin, pvspoint ); + args.entindex = 0; + invokerIndex = -1; + } + + if( !FBitSet( flags, FEV_GLOBAL ) && VectorIsNull( pvspoint )) + { + Con_DPrintf( S_ERROR "%s: not a FEV_GLOBAL event missing origin. Ignored.\n", sv.event_precache[eventindex] ); + return; + } + + // check event for some user errors + if( FBitSet( flags, FEV_NOTHOST|FEV_HOSTONLY )) + { + if( !SV_ClientFromEdict( pInvoker, true )) + { + const char *ev_name = sv.event_precache[eventindex]; + + if( FBitSet( flags, FEV_NOTHOST )) + { + Con_DPrintf( S_WARN "%s: specified FEV_NOTHOST when invoker not a client\n", ev_name ); + ClearBits( flags, FEV_NOTHOST ); + } + + if( FBitSet( flags, FEV_HOSTONLY )) + { + Con_DPrintf( S_WARN "%s: specified FEV_HOSTONLY when invoker not a client\n", ev_name ); + ClearBits( flags, FEV_HOSTONLY ); + } + } + } + + SetBits( flags, FEV_SERVER ); // it's a server event! + if( delay < 0.0f ) delay = 0.0f; // fixup negative delays + + // setup pvs cluster for invoker + if( !FBitSet( flags, FEV_GLOBAL )) + { + Mod_FatPVS( pvspoint, FATPHS_RADIUS, fatphs, world.fatbytes, false, ( svs.maxclients == 1 )); + mask = fatphs; // using the FatPVS like a PHS + } + + // process all the clients + for( slot = 0, cl = svs.clients; slot < svs.maxclients; slot++, cl++ ) + { + if( cl->state != cs_spawned || !cl->edict || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + if( SV_IsValidEdict( pInvoker ) && pInvoker->v.groupinfo && cl->edict->v.groupinfo ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) + continue; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( cl->edict->v.groupinfo, pInvoker->v.groupinfo )) + continue; + } + + if( SV_IsValidEdict( pInvoker )) + { + if( !SV_CheckClientVisiblity( cl, mask )) + continue; + } + + if( FBitSet( flags, FEV_NOTHOST ) && cl == sv.current_client && FBitSet( cl->flags, FCL_LOCAL_WEAPONS )) + continue; // will be played on client side + + if( FBitSet( flags, FEV_HOSTONLY ) && cl->edict != pInvoker ) + continue; // sending only to invoker + + // all checks passed, send the event + + // reliable event + if( FBitSet( flags, FEV_RELIABLE )) + { + // skipping queue, write direct into reliable datagram + SV_PlaybackReliableEvent( &cl->netchan.message, eventindex, delay, &args ); + continue; + } + + // unreliable event (stores in queue) + es = &cl->events; + bestslot = -1; + + if( FBitSet( flags, FEV_UPDATE )) + { + for( j = 0; j < MAX_EVENT_QUEUE; j++ ) + { + ei = &es->ei[j]; + + if( ei->index == eventindex && invokerIndex != -1 && invokerIndex == ei->entity_index ) + { + bestslot = j; + break; + } + } + } + + if( bestslot == -1 ) + { + for( j = 0; j < MAX_EVENT_QUEUE; j++ ) + { + ei = &es->ei[j]; + + if( ei->index == 0 ) + { + // found an empty slot + bestslot = j; + break; + } + } + } + + // no slot found for this player, oh well + if( bestslot == -1 ) continue; + + // add event to queue + ei->index = eventindex; + ei->fire_time = delay; + ei->entity_index = invokerIndex; + ei->packet_index = -1; + ei->flags = flags; + ei->args = args; + } +} + +/* +============= +pfnSetFatPVS + +The client will interpolate the view position, +so we can't use a single PVS point +============= +*/ +byte *pfnSetFatPVS( const float *org ) +{ + qboolean fullvis = false; + + if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) + fullvis = true; + + ASSERT( pfnGetCurrentPlayer() != -1 ); + + // portals can't change viewpoint! + if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) + { + vec3_t viewPos, offset; + + // see code from client.cpp for understanding: + // org = pView->v.origin + pView->v.view_ofs; + // if ( pView->v.flags & FL_DUCKING ) + // { + // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + // } + // so we have unneeded duck calculations who have affect when player + // is ducked into water. Remove offset to restore right PVS position + if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) + { + VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); + VectorSubtract( org, offset, viewPos ); + } + else VectorCopy( org, viewPos ); + + // build a new PVS frame + Mod_FatPVS( viewPos, FATPVS_RADIUS, fatpvs, world.fatbytes, false, fullvis ); + VectorCopy( viewPos, viewPoint[pfnGetCurrentPlayer()] ); + } + else + { + // merge PVS + Mod_FatPVS( org, FATPVS_RADIUS, fatpvs, world.fatbytes, true, fullvis ); + } + + return fatpvs; +} + +/* +============= +pfnSetFatPHS + +The client will interpolate the hear position, +so we can't use a single PHS point +============= +*/ +byte *pfnSetFatPAS( const float *org ) +{ + qboolean fullvis = false; + + if( !sv.worldmodel->visdata || sv_novis->value || !org || CL_DisableVisibility( )) + fullvis = true; + + ASSERT( pfnGetCurrentPlayer() != -1 ); + + // portals can't change viewpoint! + if( !FBitSet( sv.hostflags, SVF_MERGE_VISIBILITY )) + { + vec3_t viewPos, offset; + + // see code from client.cpp for understanding: + // org = pView->v.origin + pView->v.view_ofs; + // if ( pView->v.flags & FL_DUCKING ) + // { + // org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + // } + // so we have unneeded duck calculations who have affect when player + // is ducked into water. Remove offset to restore right PVS position + if( FBitSet( sv.current_client->edict->v.flags, FL_DUCKING )) + { + VectorSubtract( svgame.pmove->player_mins[0], svgame.pmove->player_mins[1], offset ); + VectorSubtract( org, offset, viewPos ); + } + else VectorCopy( org, viewPos ); + + // build a new PHS frame + Mod_FatPVS( viewPos, FATPHS_RADIUS, fatphs, world.fatbytes, false, fullvis ); + } + else + { + // merge PHS + Mod_FatPVS( org, FATPHS_RADIUS, fatphs, world.fatbytes, true, fullvis ); + } + + return fatphs; +} + +/* +============= +pfnCheckVisibility + +============= +*/ +int pfnCheckVisibility( const edict_t *ent, byte *pset ) +{ + int i, leafnum; + + if( !SV_IsValidEdict( ent )) + return 0; + + // vis not set - fullvis enabled + if( !pset ) return 1; + + if( FBitSet( ent->v.flags, FL_CUSTOMENTITY ) && ent->v.owner && FBitSet( ent->v.owner->v.flags, FL_CLIENT )) + ent = ent->v.owner; // upcast beams to my owner + + if( ent->headnode < 0 ) + { + // check individual leafs + for( i = 0; i < ent->num_leafs; i++ ) + { + if( CHECKVISBIT( pset, ent->leafnums[i] )) + return 1; // visible passed by leaf + } + + return 0; + } + else + { + for( i = 0; i < MAX_ENT_LEAFS; i++ ) + { + leafnum = ent->leafnums[i]; + if( leafnum == -1 ) break; + + if( CHECKVISBIT( pset, leafnum )) + return 1; // visible passed by leaf + } + + // too many leafs for individual check, go by headnode + if( !Mod_HeadnodeVisible( &sv.worldmodel->nodes[ent->headnode], pset, &leafnum )) + return 0; + + ((edict_t *)ent)->leafnums[ent->num_leafs] = leafnum; + ((edict_t *)ent)->num_leafs = (ent->num_leafs + 1) % MAX_ENT_LEAFS; + + return 2; // visible passed by headnode + } +} + +/* +============= +pfnCanSkipPlayer + +============= +*/ +int pfnCanSkipPlayer( const edict_t *player ) +{ + sv_client_t *cl; + + if(( cl = SV_ClientFromEdict( player, false )) == NULL ) + return false; + + return FBitSet( cl->flags, FCL_LOCAL_WEAPONS ) ? true : false; +} + +/* +============= +pfnGetCurrentPlayer + +============= +*/ +int pfnGetCurrentPlayer( void ) +{ + int idx = sv.current_client - svs.clients; + + if( idx < 0 || idx >= svs.maxclients ) + return -1; + return idx; +} + +/* +============= +pfnSetGroupMask + +============= +*/ +void pfnSetGroupMask( int mask, int op ) +{ + svs.groupmask = mask; + svs.groupop = op; +} + +/* +============= +pfnCreateInstancedBaseline + +============= +*/ +int pfnCreateInstancedBaseline( int classname, struct entity_state_s *baseline ) +{ + if( !baseline || sv.num_instanced >= MAX_CUSTOM_BASELINES ) + return 0; + + // g-cont. must sure that classname is really allocated + sv.instanced[sv.num_instanced].classname = SV_CopyString( STRING( classname )); + sv.instanced[sv.num_instanced].baseline = *baseline; + sv.num_instanced++; + + return sv.num_instanced; +} + +/* +============= +pfnEndSection + +============= +*/ +void pfnEndSection( const char *pszSection ) +{ + if( !Q_stricmp( "oem_end_credits", pszSection )) + Host_Credits (); + else Cbuf_AddText( va( "endgame \"%s\"\n", pszSection )); +} + +/* +============= +pfnGetPlayerUserId + +============= +*/ +int pfnGetPlayerUserId( edict_t *e ) +{ + sv_client_t *cl; + + if(( cl = SV_ClientFromEdict( e, false )) == NULL ) + return -1; + return cl->userid; +} + +/* +============= +pfnGetPlayerStats + +============= +*/ +void pfnGetPlayerStats( const edict_t *pClient, int *ping, int *packet_loss ) +{ + sv_client_t *cl; + + if( packet_loss ) *packet_loss = 0; + if( ping ) *ping = 0; + + if(( cl = SV_ClientFromEdict( pClient, false )) == NULL ) + return; + + if( packet_loss ) *packet_loss = cl->packet_loss; + if( ping ) *ping = cl->latency * 1000; +} + +/* +============= +pfnForceUnmodified + +============= +*/ +void pfnForceUnmodified( FORCE_TYPE type, float *mins, float *maxs, const char *filename ) +{ + consistency_t *pc; + int i; + + if( !COM_CheckString( filename )) + return; + + if( sv.state == ss_loading ) + { + for( i = 0; i < MAX_MODELS; i++ ) + { + pc = &sv.consistency_list[i]; + + if( !pc->filename ) + { + if( mins ) VectorCopy( mins, pc->mins ); + if( maxs ) VectorCopy( maxs, pc->maxs ); + pc->filename = SV_CopyString( filename ); + pc->check_type = type; + return; + } + else if( !Q_strcmp( filename, pc->filename )) + return; + } + Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); + } + else + { + for( i = 0; i < MAX_MODELS; i++ ) + { + pc = &sv.consistency_list[i]; + if( !pc->filename ) continue; + + if( !Q_strcmp( filename, pc->filename )) + return; + } + Con_Printf( S_ERROR "no precache: %s\n", filename ); + } +} + +/* +============= +pfnVoice_GetClientListening + +============= +*/ +qboolean pfnVoice_GetClientListening( int iReceiver, int iSender ) +{ + iReceiver -= 1; + iSender -= 1; + + if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) + return false; + + return (FBitSet( svs.clients[iSender].listeners, BIT( iReceiver )) != 0 ); +} + +/* +============= +pfnVoice_SetClientListening + +============= +*/ +qboolean pfnVoice_SetClientListening( int iReceiver, int iSender, qboolean bListen ) +{ + iReceiver -= 1; + iSender -= 1; + + if( iReceiver < 0 || iReceiver >= svs.maxclients || iSender < 0 || iSender > svs.maxclients ) + return false; + + if( bListen ) SetBits( svs.clients[iSender].listeners, BIT( iReceiver )); + else ClearBits( svs.clients[iSender].listeners, BIT( iReceiver )); + + return true; +} + +/* +============= +pfnGetPlayerAuthId + +These function must returns cd-key hashed value +but Xash3D currently doesn't have any security checks +return nullstring for now +============= +*/ +const char *pfnGetPlayerAuthId( edict_t *e ) +{ + return SV_GetClientIDString( SV_ClientFromEdict( e, false )); +} + +/* +============= +pfnQueryClientCvarValue + +request client cvar value +============= +*/ +void pfnQueryClientCvarValue( const edict_t *player, const char *cvarName ) +{ + sv_client_t *cl; + + if( !COM_CheckString( cvarName )) + return; + + if(( cl = SV_ClientFromEdict( player, true )) != NULL ) + { + MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue ); + MSG_WriteString( &cl->netchan.message, cvarName ); + } + else + { + if( svgame.dllFuncs2.pfnCvarValue ) + svgame.dllFuncs2.pfnCvarValue( player, "Bad Player" ); + Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); + } +} + +/* +============= +pfnQueryClientCvarValue2 + +request client cvar value (bugfixed) +============= +*/ +void pfnQueryClientCvarValue2( const edict_t *player, const char *cvarName, int requestID ) +{ + sv_client_t *cl; + + if( !COM_CheckString( cvarName )) + return; + + if(( cl = SV_ClientFromEdict( player, true )) != NULL ) + { + MSG_BeginServerCmd( &cl->netchan.message, svc_querycvarvalue2 ); + MSG_WriteLong( &cl->netchan.message, requestID ); + MSG_WriteString( &cl->netchan.message, cvarName ); + } + else + { + if( svgame.dllFuncs2.pfnCvarValue2 ) + svgame.dllFuncs2.pfnCvarValue2( player, requestID, cvarName, "Bad Player" ); + Con_Printf( S_ERROR "QueryClientCvarValue: tried to send to a non-client!\n" ); + } +} + +/* +============= +pfnEngineStub + +extended iface stubs +============= +*/ +static void pfnEngineStub( void ) +{ +} + +// engine callbacks +static enginefuncs_t gEngfuncs = +{ + pfnPrecacheModel, + SV_SoundIndex, + pfnSetModel, + pfnModelIndex, + pfnModelFrames, + pfnSetSize, + pfnChangeLevel, + pfnGetSpawnParms, + pfnSaveSpawnParms, + pfnVecToYaw, + VectorAngles, + pfnMoveToOrigin, + pfnChangeYaw, + pfnChangePitch, + SV_FindEntityByString, + pfnGetEntityIllum, + pfnFindEntityInSphere, + pfnFindClientInPVS, + pfnEntitiesInPVS, + pfnMakeVectors, + AngleVectors, + SV_AllocEdict, + pfnRemoveEntity, + pfnCreateNamedEntity, + pfnMakeStatic, + pfnEntIsOnFloor, + pfnDropToFloor, + pfnWalkMove, + pfnSetOrigin, + SV_StartSound, + pfnEmitAmbientSound, + pfnTraceLine, + pfnTraceToss, + pfnTraceMonsterHull, + pfnTraceHull, + pfnTraceModel, + pfnTraceTexture, + pfnTraceSphere, + pfnGetAimVector, + pfnServerCommand, + pfnServerExecute, + pfnClientCommand, + pfnParticleEffect, + pfnLightStyle, + pfnDecalIndex, + SV_PointContents, + pfnMessageBegin, + pfnMessageEnd, + pfnWriteByte, + pfnWriteChar, + pfnWriteShort, + pfnWriteLong, + pfnWriteAngle, + pfnWriteCoord, + pfnWriteString, + pfnWriteEntity, + pfnCvar_RegisterServerVariable, + Cvar_VariableValue, + Cvar_VariableString, + Cvar_SetValue, + Cvar_Set, + pfnAlertMessage, + pfnEngineFprintf, + pfnPvAllocEntPrivateData, + pfnPvEntPrivateData, + SV_FreePrivateData, + SV_GetString, + SV_AllocString, + pfnGetVarsOfEnt, + pfnPEntityOfEntOffset, + pfnEntOffsetOfPEntity, + pfnIndexOfEdict, + pfnPEntityOfEntIndex, + pfnFindEntityByVars, + pfnGetModelPtr, + pfnRegUserMsg, + pfnAnimationAutomove, + pfnGetBonePosition, + pfnFunctionFromName, + pfnNameForFunction, + pfnClientPrintf, + pfnServerPrint, + Cmd_Args, + Cmd_Argv, + Cmd_Argc, + pfnGetAttachment, + CRC32_Init, + CRC32_ProcessBuffer, + CRC32_ProcessByte, + CRC32_Final, + COM_RandomLong, + COM_RandomFloat, + pfnSetView, + pfnTime, + pfnCrosshairAngle, + COM_LoadFileForMe, + COM_FreeFile, + pfnEndSection, + COM_CompareFileTime, + pfnGetGameDir, + pfnCvar_RegisterEngineVariable, + pfnFadeClientVolume, + pfnSetClientMaxspeed, + SV_FakeConnect, + pfnRunPlayerMove, + pfnNumberOfEntities, + pfnGetInfoKeyBuffer, + Info_ValueForKey, + pfnSetValueForKey, + pfnSetClientKeyValue, + pfnIsMapValid, + pfnStaticDecal, + SV_GenericIndex, + pfnGetPlayerUserId, + pfnBuildSoundMsg, + pfnIsDedicatedServer, + pfnCVarGetPointer, + pfnGetPlayerWONId, + Info_RemoveKey, + pfnGetPhysicsKeyValue, + pfnSetPhysicsKeyValue, + pfnGetPhysicsInfoString, + pfnPrecacheEvent, + SV_PlaybackEventFull, + pfnSetFatPVS, + pfnSetFatPAS, + pfnCheckVisibility, + Delta_SetField, + Delta_UnsetField, + Delta_AddEncoder, + pfnGetCurrentPlayer, + pfnCanSkipPlayer, + Delta_FindField, + Delta_SetFieldByIndex, + Delta_UnsetFieldByIndex, + pfnSetGroupMask, + pfnCreateInstancedBaseline, + pfnCVarDirectSet, + pfnForceUnmodified, + pfnGetPlayerStats, + Cmd_AddServerCommand, + pfnVoice_GetClientListening, + pfnVoice_SetClientListening, + pfnGetPlayerAuthId, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnEngineStub, + pfnQueryClientCvarValue, + pfnQueryClientCvarValue2, + COM_CheckParm, +}; + +/* +==================== +SV_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +qboolean SV_ParseEdict( char **pfile, edict_t *ent ) +{ + KeyValueData pkvd[256]; // per one entity + qboolean adjust_origin = false; + int i, numpairs = 0; + char *classname = NULL; + char token[2048]; + vec3_t origin; + + // go through all the dictionary pairs + while( 1 ) + { + string keyname; + + // parse key + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); + if( token[0] == '}' ) break; // end of desc + + Q_strncpy( keyname, token, sizeof( keyname )); + + // parse value + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + Host_Error( "ED_ParseEdict: EOF without closing brace\n" ); + + if( token[0] == '}' ) + Host_Error( "ED_ParseEdict: closing brace without data\n" ); + + // ignore attempts to set key "" + if( !keyname[0] ) continue; + + // "wad" field is already handled + if( !Q_strcmp( keyname, "wad" )) + continue; + + // keynames with a leading underscore are used for + // utility comments and are immediately discarded by engine + if( keyname[0] == '_' && Q_strcmp( keyname, "_light" )) + continue; + + // ignore attempts to set value "" + if( !token[0] ) continue; + + // create keyvalue strings + pkvd[numpairs].szClassName = ""; // unknown at this moment + pkvd[numpairs].szKeyName = copystring( keyname ); + pkvd[numpairs].szValue = copystring( token ); + pkvd[numpairs].fHandled = false; + + if( !Q_strcmp( keyname, "classname" ) && classname == NULL ) + classname = copystring( pkvd[numpairs].szValue ); + if( ++numpairs >= 256 ) break; + } + + ent = SV_AllocPrivateData( ent, ALLOC_STRING( classname )); + + if( !SV_IsValidEdict( ent ) || FBitSet( ent->v.flags, FL_KILLME )) + { + // release allocated strings + for( i = 0; i < numpairs; i++ ) + { + Mem_Free( pkvd[i].szKeyName ); + Mem_Free( pkvd[i].szValue ); + } + return false; + } + + if( FBitSet( ent->v.flags, FL_CUSTOMENTITY )) + { + if( numpairs < 256 ) + { + pkvd[numpairs].szClassName = "custom"; + pkvd[numpairs].szKeyName = "customclass"; + pkvd[numpairs].szValue = classname; + pkvd[numpairs].fHandled = false; + numpairs++; + } + + // clear it now - no longer used + ClearBits( ent->v.flags, FL_CUSTOMENTITY ); + } + +#ifdef HACKS_RELATED_HLMODS + // chemical existence have broked changelevels + if( !Q_stricmp( GI->gamedir, "ce" )) + { + if( !Q_stricmp( sv.name, "ce08_02" ) && !Q_stricmp( classname, "info_player_start_force" )) + adjust_origin = true; + } +#endif + + for( i = 0; i < numpairs; i++ ) + { + if( !Q_strcmp( pkvd[i].szKeyName, "angle" )) + { + float flYawAngle = Q_atof( pkvd[i].szValue ); + + Mem_Free( pkvd[i].szKeyName ); // will be replace with 'angles' + Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these + pkvd[i].szKeyName = copystring( "angles" ); + + if( flYawAngle >= 0.0f ) + pkvd[i].szValue = copystring( va( "%g %g %g", ent->v.angles[0], flYawAngle, ent->v.angles[2] )); + else if( flYawAngle == -1.0f ) + pkvd[i].szValue = copystring( "-90 0 0" ); + else if( flYawAngle == -2.0f ) + pkvd[i].szValue = copystring( "90 0 0" ); + else pkvd[i].szValue = copystring( "0 0 0" ); // technically an error + } + +#ifdef HACKS_RELATED_HLMODS + if( adjust_origin && !Q_strcmp( pkvd[i].szKeyName, "origin" )) + { + char *pstart = pkvd[i].szValue; + + COM_ParseVector( &pstart, origin, 3 ); + Mem_Free( pkvd[i].szValue ); // release old value, so we don't need these + copystring( va( "%g %g %g", origin[0], origin[1], origin[2] - 16.0f )); + } +#endif + if( !Q_strcmp( pkvd[i].szKeyName, "light" )) + { + Mem_Free( pkvd[i].szKeyName ); + pkvd[i].szKeyName = copystring( "light_level" ); + } + + if( !pkvd[i].fHandled ) + { + pkvd[i].szClassName = classname; + svgame.dllFuncs.pfnKeyValue( ent, &pkvd[i] ); + } + + // no reason to keep this data + Mem_Free( pkvd[i].szKeyName ); + Mem_Free( pkvd[i].szValue ); + } + + if( classname ) + Mem_Free( classname ); + + return true; +} + +/* +================ +SV_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +ED_Alloc, because otherwise an error loading the map would have entity +number references out of order. + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +================ +*/ +void SV_LoadFromFile( const char *mapname, char *entities ) +{ + char token[2048]; + qboolean create_world = true; + int inhibited; + edict_t *ent; + + Assert( entities != NULL ); + + // user dll can override spawn entities function (Xash3D extension) + if( !svgame.physFuncs.SV_LoadEntities || !svgame.physFuncs.SV_LoadEntities( mapname, entities )) + { + inhibited = 0; + + // parse ents + while(( entities = COM_ParseFile( entities, token )) != NULL ) + { + if( token[0] != '{' ) + Host_Error( "ED_LoadFromFile: found %s when expecting {\n", token ); + + if( create_world ) + { + create_world = false; + ent = EDICT_NUM( 0 ); // already initialized + } + else ent = SV_AllocEdict(); + + if( !SV_ParseEdict( &entities, ent )) + continue; + + if( svgame.dllFuncs.pfnSpawn( ent ) == -1 ) + { + // game rejected the spawn + if( !FBitSet( ent->v.flags, FL_KILLME )) + { + SV_FreeEdict( ent ); + inhibited++; + } + } + } + + Con_DPrintf( "\n%i entities inhibited\n", inhibited ); + } + + // reset world origin and angles for some reason + VectorClear( svgame.edicts->v.origin ); + VectorClear( svgame.edicts->v.angles ); +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void SV_SpawnEntities( const char *mapname ) +{ + edict_t *ent; + + // reset misc parms + Cvar_Reset( "sv_zmax" ); + Cvar_Reset( "sv_wateramp" ); + Cvar_Reset( "sv_wateralpha" ); + + // reset sky parms + Cvar_Reset( "sv_skycolor_r" ); + Cvar_Reset( "sv_skycolor_g" ); + Cvar_Reset( "sv_skycolor_b" ); + Cvar_Reset( "sv_skyvec_x" ); + Cvar_Reset( "sv_skyvec_y" ); + Cvar_Reset( "sv_skyvec_z" ); + Cvar_Reset( "sv_skyname" ); + + ent = EDICT_NUM( 0 ); + if( ent->free ) SV_InitEdict( ent ); + ent->v.model = MAKE_STRING( sv.model_precache[1] ); + ent->v.modelindex = WORLD_INDEX; // world model + ent->v.solid = SOLID_BSP; + ent->v.movetype = MOVETYPE_PUSH; + svgame.movevars.fog_settings = 0; + + svgame.globals->maxEntities = GI->max_edicts; + svgame.globals->mapname = MAKE_STRING( sv.name ); + svgame.globals->startspot = MAKE_STRING( sv.startspot ); + svgame.globals->time = sv.time; + + // spawn the rest of the entities on the map + SV_LoadFromFile( mapname, sv.worldmodel->entities ); +} + +void SV_UnloadProgs( void ) +{ + if( !svgame.hInstance ) + return; + + SV_DeactivateServer (); + Delta_Shutdown (); + Mod_ClearUserData (); + + Mem_FreePool( &svgame.stringspool ); + + if( svgame.dllFuncs2.pfnGameShutdown != NULL ) + svgame.dllFuncs2.pfnGameShutdown (); + + // now we can unload cvars + Cvar_FullSet( "host_gameloaded", "0", FCVAR_READ_ONLY ); + Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); + + // free entity baselines + Z_Free( svs.baselines ); + svs.baselines = NULL; + + // remove server cmds + SV_KillOperatorCommands(); + + // must unlink all game cvars, + // before pointers on them will be lost... + Cvar_Unlink( FCVAR_EXTDLL ); + Cmd_Unlink( CMD_SERVERDLL ); + + Mod_ResetStudioAPI (); + + COM_FreeLibrary( svgame.hInstance ); + Mem_FreePool( &svgame.mempool ); + memset( &svgame, 0, sizeof( svgame )); +} + +qboolean SV_LoadProgs( const char *name ) +{ + int i, version; + static APIFUNCTION GetEntityAPI; + static APIFUNCTION2 GetEntityAPI2; + static GIVEFNPTRSTODLL GiveFnptrsToDll; + static NEW_DLL_FUNCTIONS_FN GiveNewDllFuncs; + static enginefuncs_t gpEngfuncs; + static globalvars_t gpGlobals; + static playermove_t gpMove; + edict_t *e; + + if( svgame.hInstance ) SV_UnloadProgs(); + + // fill it in + svgame.pmove = &gpMove; + svgame.globals = &gpGlobals; + svgame.mempool = Mem_AllocPool( "Server Edicts Zone" ); + svgame.hInstance = COM_LoadLibrary( name, true, false ); + if( !svgame.hInstance ) return false; + + // make sure what new dll functions is cleared + memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); + + // make sure what physic functions is cleared + memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); + + // make local copy of engfuncs to prevent overwrite it with bots.dll + memcpy( &gpEngfuncs, &gEngfuncs, sizeof( gpEngfuncs )); + + GetEntityAPI = (APIFUNCTION)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI" ); + GetEntityAPI2 = (APIFUNCTION2)COM_GetProcAddress( svgame.hInstance, "GetEntityAPI2" ); + GiveNewDllFuncs = (NEW_DLL_FUNCTIONS_FN)COM_GetProcAddress( svgame.hInstance, "GetNewDLLFunctions" ); + + if( !GetEntityAPI && !GetEntityAPI2 ) + { + COM_FreeLibrary( svgame.hInstance ); + Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GetEntityAPI proc\n" ); + svgame.hInstance = NULL; + return false; + } + + GiveFnptrsToDll = (GIVEFNPTRSTODLL)COM_GetProcAddress( svgame.hInstance, "GiveFnptrsToDll" ); + + if( !GiveFnptrsToDll ) + { + COM_FreeLibrary( svgame.hInstance ); + Con_Printf( S_ERROR "SV_LoadProgs: failed to get address of GiveFnptrsToDll proc\n" ); + svgame.hInstance = NULL; + return false; + } + + GiveFnptrsToDll( &gpEngfuncs, svgame.globals ); + + // get extended callbacks + if( GiveNewDllFuncs ) + { + version = NEW_DLL_FUNCTIONS_VERSION; + + if( !GiveNewDllFuncs( &svgame.dllFuncs2, &version )) + { + if( version != NEW_DLL_FUNCTIONS_VERSION ) + Con_Printf( S_WARN "SV_LoadProgs: new interface version %i should be %i\n", NEW_DLL_FUNCTIONS_VERSION, version ); + memset( &svgame.dllFuncs2, 0, sizeof( svgame.dllFuncs2 )); + } + } + + version = INTERFACE_VERSION; + + if( GetEntityAPI2 ) + { + if( !GetEntityAPI2( &svgame.dllFuncs, &version )) + { + Con_Printf( S_WARN "SV_LoadProgs: interface version %i should be %i\n", INTERFACE_VERSION, version ); + + // fallback to old API + if( !GetEntityAPI( &svgame.dllFuncs, version )) + { + COM_FreeLibrary( svgame.hInstance ); + Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); + svgame.hInstance = NULL; + return false; + } + } + else Con_DPrintf( "SV_LoadProgs: ^2initailized extended EntityAPI ^7ver. %i\n", version ); + } + else if( !GetEntityAPI( &svgame.dllFuncs, version )) + { + COM_FreeLibrary( svgame.hInstance ); + Con_Printf( S_ERROR "SV_LoadProgs: couldn't get entity API\n" ); + svgame.hInstance = NULL; + return false; + } + + SV_InitOperatorCommands(); + Mod_InitStudioAPI(); + + if( !SV_InitPhysicsAPI( )) + { + Con_Printf( S_WARN "SV_LoadProgs: couldn't get physics API\n" ); + } +// TESTTEST +//host.features |= ENGINE_FIXED_FRAMERATE; + // grab function SV_SaveGameComment + SV_InitSaveRestore (); + + svgame.globals->pStringBase = ""; // setup string base + + svgame.globals->maxEntities = GI->max_edicts; + svgame.globals->maxClients = svs.maxclients; + svgame.edicts = Mem_Alloc( svgame.mempool, sizeof( edict_t ) * GI->max_edicts ); + svs.baselines = Z_Malloc( sizeof( entity_state_t ) * GI->max_edicts ); + svgame.numEntities = svs.maxclients + 1; // clients + world + + for( i = 0, e = svgame.edicts; i < GI->max_edicts; i++, e++ ) + e->free = true; // mark all edicts as freed + + Cvar_FullSet( "host_gameloaded", "1", FCVAR_READ_ONLY ); + svgame.stringspool = Mem_AllocPool( "Server Strings" ); + + // fire once + Con_Printf( "Dll loaded for game ^2\"%s\"\n", svgame.dllFuncs.pfnGetGameDescription( )); + + // all done, initialize game + svgame.dllFuncs.pfnGameInit(); + + // initialize pm_shared + SV_InitClientMove(); + + Delta_Init (); + + // register custom encoders + svgame.dllFuncs.pfnRegisterEncoders(); + + return true; +} \ No newline at end of file diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c new file mode 100644 index 00000000..92d8c683 --- /dev/null +++ b/engine/server/sv_init.c @@ -0,0 +1,947 @@ +/* +sv_init.c - server initialize operations +Copyright (C) 2009 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "net_encode.h" + +int SV_UPDATE_BACKUP = SINGLEPLAYER_BACKUP; + +server_t sv; // local server +server_static_t svs; // persistant server info +svgame_static_t svgame; // persistant game info + +/* +================ +SV_AddResource + +generic method to put the resources into array +================ +*/ +static void SV_AddResource( resourcetype_t type, const char *name, int size, byte flags, int index ) +{ + resource_t *pResource = &sv.resources[sv.num_resources]; + + if( sv.num_resources >= MAX_RESOURCES ) + Host_Error( "MAX_RESOURCES limit exceeded (%d)\n", MAX_RESOURCES ); + sv.num_resources++; + + Q_strncpy( pResource->szFileName, name, sizeof( pResource->szFileName )); + pResource->nDownloadSize = size; + pResource->ucFlags = flags; + pResource->nIndex = index; + pResource->type = type; +} + +/* +================ +SV_SendSingleResource + +hot precache on a flying +================ +*/ +void SV_SendSingleResource( const char *name, resourcetype_t type, int index, byte flags ) +{ + resource_t *pResource = &sv.resources[sv.num_resources]; + int nSize = 0; + + if( !COM_CheckString( name )) + return; + + switch( type ) + { + case t_model: + nSize = ( name[0] != '*' ) ? FS_FileSize( name, false ) : 0; + break; + case t_sound: + nSize = FS_FileSize( va( "%s%s", DEFAULT_SOUNDPATH, name ), false ); + break; + default: + nSize = FS_FileSize( name, false ); + break; + } + + SV_AddResource( type, name, nSize, flags, index ); + MSG_BeginServerCmd( &sv.reliable_datagram, svc_resource ); + SV_SendResource( pResource, &sv.reliable_datagram ); +} + +/* +================ +SV_ModelIndex + +register unique model for a server and client +================ +*/ +int SV_ModelIndex( const char *filename ) +{ + char name[MAX_QPATH]; + int i; + + if( !COM_CheckString( filename )) + return 0; + + if( *filename == '\\' || *filename == '/' ) + filename++; + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + for( i = 1; i < MAX_MODELS && sv.model_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.model_precache[i], name )) + return i; + } + + if( i == MAX_MODELS ) + { + Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); + return 0; + } + + // register new model + Q_strncpy( sv.model_precache[i], name, sizeof( sv.model_precache[i] )); + + if( sv.state != ss_loading ) + { + // send the update to everyone + SV_SendSingleResource( name, t_model, i, sv.model_precache_flags[i] ); + Con_Printf( S_WARN "late precache of %s\n", name ); + } + + return i; +} + +/* +================ +SV_SoundIndex + +register unique sound for client +================ +*/ +int SV_SoundIndex( const char *filename ) +{ + char name[MAX_QPATH]; + int i; + + // don't precache sentence names! + if( !COM_CheckString( filename )) + return 0; + + if( filename[0] == '!' ) + { + Con_Printf( S_WARN "'%s' do not precache sentence names!\n", filename ); + return 0; + } + + if( *filename == '\\' || *filename == '/' ) + filename++; + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + for( i = 1; i < MAX_SOUNDS && sv.sound_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.sound_precache[i], name )) + return i; + } + + if( i == MAX_SOUNDS ) + { + Host_Error( "MAX_SOUNDS limit exceeded (%d)\n", MAX_SOUNDS ); + return 0; + } + + // register new sound + Q_strncpy( sv.sound_precache[i], name, sizeof( sv.sound_precache[i] )); + + if( sv.state != ss_loading ) + { + // send the update to everyone + SV_SendSingleResource( name, t_sound, i, 0 ); + Con_Printf( S_WARN "late precache of %s\n", name ); + } + + return i; +} + +/* +================ +SV_EventIndex + +register network event for a server and client +================ +*/ +int SV_EventIndex( const char *filename ) +{ + char name[MAX_QPATH]; + int i; + + if( !COM_CheckString( filename )) + return 0; + + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + for( i = 1; i < MAX_EVENTS && sv.event_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.event_precache[i], name )) + return i; + } + + if( i == MAX_EVENTS ) + { + Host_Error( "MAX_EVENTS limit exceeded (%d)\n", MAX_EVENTS ); + return 0; + } + + // register new event + Q_strncpy( sv.event_precache[i], name, sizeof( sv.event_precache[i] )); + + if( sv.state != ss_loading ) + { + // send the update to everyone + SV_SendSingleResource( name, t_eventscript, i, RES_FATALIFMISSING ); + } + + return i; +} + +/* +================ +SV_GenericIndex + +register generic resourse for a server and client +================ +*/ +int SV_GenericIndex( const char *filename ) +{ + char name[MAX_QPATH]; + int i; + + if( !COM_CheckString( filename )) + return 0; + + Q_strncpy( name, filename, sizeof( name )); + COM_FixSlashes( name ); + + for( i = 1; i < MAX_CUSTOM && sv.files_precache[i][0]; i++ ) + { + if( !Q_stricmp( sv.files_precache[i], name )) + return i; + } + + if( i == MAX_CUSTOM ) + { + Host_Error( "MAX_CUSTOM limit exceeded (%d)\n", MAX_CUSTOM ); + return 0; + } + + // register new generic resource + Q_strncpy( sv.files_precache[i], name, sizeof( sv.files_precache[i] )); + + if( sv.state != ss_loading ) + { + // send the update to everyone + SV_SendSingleResource( name, t_generic, i, RES_FATALIFMISSING ); + } + + return i; +} + +/* +================ +SV_ModelHandle + +register unique model for a server and client +================ +*/ +model_t *SV_ModelHandle( int modelindex ) +{ + if( modelindex < 0 || modelindex >= MAX_MODELS ) + return NULL; + return sv.models[modelindex]; +} + +void SV_CreateGenericResources( void ) +{ + string filename, token; + char *afile, *pfile; + + Q_strncpy( filename, sv.model_precache[1], sizeof( filename )); + COM_ReplaceExtension( filename, ".res" ); + COM_FixSlashes( filename ); + + afile = FS_LoadFile( filename, NULL, false ); + if( !afile ) return; + + pfile = afile; + + Con_DPrintf( "Precaching from %s\n", filename ); + Con_DPrintf( "----------------------------------\n" ); + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !COM_IsSafeFileToDownload( token )) + continue; + + Con_DPrintf( " %s\n", token ); + SV_GenericIndex( token ); + } + + Con_DPrintf( "----------------------------------\n" ); + Mem_Free( afile ); +} + +void SV_CreateResourceList( void ) +{ + qboolean ffirstsent = false; + int i, nSize; + char *s; + + sv.num_resources = 0; + + for( i = 1; i < MAX_CUSTOM; i++ ) + { + s = sv.files_precache[i]; + if( !COM_CheckString( s )) break; // end of list + nSize = FS_FileSize( s, false ); + SV_AddResource( t_generic, s, nSize, RES_FATALIFMISSING, i ); + } + + for( i = 1; i < MAX_SOUNDS; i++ ) + { + s = sv.sound_precache[i]; + if( !COM_CheckString( s )) + break; // end of list + + if( s[0] == '!' ) + { + if( !ffirstsent ) + { + SV_AddResource( t_sound, "!", 0, RES_FATALIFMISSING, i ); + ffirstsent = true; + } + } + else + { + nSize = FS_FileSize( va( "%s%s", DEFAULT_SOUNDPATH, s ), false ); + SV_AddResource( t_sound, s, nSize, 0, i ); + } + } + + for( i = 1; i < MAX_MODELS; i++ ) + { + s = sv.model_precache[i]; + if( !COM_CheckString( s )) break; // end of list + nSize = ( s[0] != '*' ) ? FS_FileSize( s, false ) : 0; + SV_AddResource( t_model, s, nSize, sv.model_precache_flags[i], i ); + } + + // just send names + for( i = 0; i < MAX_DECALS && host.draw_decals[i][0]; i++ ) + { + SV_AddResource( t_decal, host.draw_decals[i], 0, 0, i ); + } + + for( i = 1; i < MAX_EVENTS; i++ ) + { + s = sv.event_precache[i]; + if( !COM_CheckString( s )) break; // end of list + nSize = FS_FileSize( s, false ); + SV_AddResource( t_eventscript, s, nSize, RES_FATALIFMISSING, i ); + } +} + +/* +================ +SV_CreateBaseline + +Entity baselines are used to compress the update messages +to the clients -- only the fields that differ from the +baseline will be transmitted + +INTERNAL RESOURCE +================ +*/ +void SV_CreateBaseline( void ) +{ + entity_state_t nullstate, *base; + int playermodel; + qboolean player; + int entnum; + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_QUAKE ); + else playermodel = SV_ModelIndex( DEFAULT_PLAYER_PATH_HALFLIFE ); + + memset( &nullstate, 0, sizeof( nullstate )); + + for( entnum = 0; entnum < svgame.numEntities; entnum++ ) + { + edict_t *pEdict = EDICT_NUM( entnum ); + + if( !SV_IsValidEdict( pEdict )) + continue; + + if( entnum != 0 && entnum <= svs.maxclients ) + { + player = true; + } + else + { + if( !pEdict->v.modelindex ) + continue; // invisible + player = false; + } + + // take current state as baseline + base = &svs.baselines[entnum]; + + base->number = entnum; + + // set entity type + if( FBitSet( pEdict->v.flags, FL_CUSTOMENTITY )) + base->entityType = ENTITY_BEAM; + else base->entityType = ENTITY_NORMAL; + + svgame.dllFuncs.pfnCreateBaseline( player, entnum, base, pEdict, playermodel, host.player_mins[0], host.player_maxs[0] ); + sv.last_valid_baseline = entnum; + } + + // create the instanced baselines + svgame.dllFuncs.pfnCreateInstancedBaselines(); + + // now put the baseline into the signon message. + MSG_BeginServerCmd( &sv.signon, svc_spawnbaseline ); + + for( entnum = 0; entnum < svgame.numEntities; entnum++ ) + { + edict_t *pEdict = EDICT_NUM( entnum ); + + if( !SV_IsValidEdict( pEdict )) + continue; + + if( entnum != 0 && entnum <= svs.maxclients ) + { + player = true; + } + else + { + if( !pEdict->v.modelindex ) + continue; // invisible + player = false; + } + + // take current state as baseline + base = &svs.baselines[entnum]; + + MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, player, 1.0f, 0 ); + } + + MSG_WriteUBitLong( &sv.signon, LAST_EDICT, MAX_ENTITY_BITS ); // end of baselines + MSG_WriteUBitLong( &sv.signon, sv.num_instanced, 6 ); + + for( entnum = 0; entnum < sv.num_instanced; entnum++ ) + { + base = &sv.instanced[entnum].baseline; + MSG_WriteDeltaEntity( &nullstate, base, &sv.signon, true, false, 1.0f, 0 ); + } +} + +/* +================ +SV_FreeOldEntities + +remove immediate entities +================ +*/ +void SV_FreeOldEntities( void ) +{ + edict_t *ent; + int i; + + // at end of frame kill all entities which supposed to it + for( i = svs.maxclients + 1; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + + if( !ent->free && FBitSet( ent->v.flags, FL_KILLME )) + SV_FreeEdict( ent ); + } + + // decrement svgame.numEntities if the highest number entities died + for( ; EDICT_NUM( svgame.numEntities - 1 )->free; svgame.numEntities-- ); +} + +/* +================ +SV_ActivateServer + +activate server on changed map, run physics +================ +*/ +void SV_ActivateServer( int runPhysics ) +{ + int i, numFrames; + byte msg_buf[MAX_INIT_MSG]; + sizebuf_t msg; + sv_client_t *cl; + + if( !svs.initialized ) + return; + + MSG_Init( &msg, "ActivateServer", msg_buf, sizeof( msg_buf )); + + // always clearing newunit variable + Cvar_SetValue( "sv_newunit", 0 ); + + // relese all intermediate entities + SV_FreeOldEntities (); + + // Activate the DLL server code + svgame.globals->time = sv.time; + svgame.dllFuncs.pfnServerActivate( svgame.edicts, svgame.numEntities, svs.maxclients ); + + // parse user-specified resources + SV_CreateGenericResources(); + + if( runPhysics ) + { + numFrames = (svs.maxclients <= 1) ? 2 : 8; + sv.frametime = SV_SPAWN_TIME; + } + else + { + sv.frametime = 0.001; + numFrames = 1; + } + + // run some frames to allow everything to settle + for( i = 0; i < numFrames; i++ ) + SV_Physics(); + + // create a baseline for more efficient communications + SV_CreateBaseline(); + + // collect all info from precached resources + SV_CreateResourceList(); + + // check and count all files that marked by user as unmodified (typically is a player models etc) + SV_TransferConsistencyInfo(); + + // send serverinfo to all connected clients + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state < cs_connected ) + continue; + + Netchan_Clear( &cl->netchan ); + cl->delta_sequence = -1; + } + + // invoke to refresh all movevars + memset( &svgame.oldmovevars, 0, sizeof( movevars_t )); + svgame.globals->changelevel = false; + + // setup hostflags + sv.hostflags = 0; + + HPAK_FlushHostQueue(); + + // tell what kind of server has been started. + if( svs.maxclients > 1 ) + Con_Printf( "%i player server started\n", svs.maxclients ); + else Con_Printf( "Game started\n" ); + + Log_Printf( "Started map \"%s\" (CRC \"%i\")\n", sv.name, sv.worldmapCRC ); + + // dedicated server purge unused resources here + if( host.type == HOST_DEDICATED ) + Mod_FreeUnused (); + + host.movevars_changed = true; + sv.state = ss_active; + + Con_DPrintf( "level loaded at %.2f sec\n", Sys_DoubleTime() - svs.timestart ); + + if( sv.ignored_static_ents ) + Con_Printf( S_WARN "%i static entities was rejected due buffer overflow\n", sv.ignored_static_ents ); + + if( sv.ignored_world_decals ) + Con_Printf( S_WARN "%i static decals was rejected due buffer overflow\n", sv.ignored_world_decals ); + + if( svs.maxclients > 1 ) + { + const char *cycle = Cvar_VariableString( "mapchangecfgfile" ); + + if( COM_CheckString( cycle )) + Cbuf_AddText( va( "exec %s\n", cycle )); + + if( public_server->value ) + Master_Add( ); + } +} + +/* +================ +SV_DeactivateServer + +deactivate server, free edicts, strings etc +================ +*/ +void SV_DeactivateServer( void ) +{ + int i; + + if( !svs.initialized || sv.state == ss_dead ) + return; + + svgame.globals->time = sv.time; + svgame.dllFuncs.pfnServerDeactivate(); + sv.state = ss_dead; + + SV_FreeEdicts (); + + SV_ClearPhysEnts (); + + Mem_EmptyPool( svgame.stringspool ); + + for( i = 0; i < svs.maxclients; i++ ) + { + // release client frames + if( svs.clients[i].frames ) + Mem_Free( svs.clients[i].frames ); + svs.clients[i].frames = NULL; + } + + svgame.globals->maxEntities = GI->max_edicts; + svgame.globals->maxClients = svs.maxclients; + svgame.numEntities = svs.maxclients + 1; // clients + world + svgame.globals->startspot = 0; + svgame.globals->mapname = 0; +} + +/* +============== +SV_InitGame + +A brand new game has been started +============== +*/ +qboolean SV_InitGame( void ) +{ + if( svs.initialized ) + return true; // already initialized ? + + // first initialize? + if( !SV_LoadProgs( GI->game_dll )) + { + Con_Printf( S_ERROR "can't initialize %s\n", GI->game_dll ); + return false; // failed to loading server.dll + } + + // client frames will be allocated in SV_ClientConnect + svs.initialized = true; + + return true; +} + +/* +============== +SV_ShutdownGame + +prepare to close server +============== +*/ +void SV_ShutdownGame( void ) +{ + if( !GameState->loadGame ) + SV_ClearGameState(); + + SV_FinalMessage( "", true ); + S_StopBackgroundTrack(); + + if( GameState->newGame ) + { + Host_EndGame( false, DEFAULT_ENDGAME_MESSAGE ); + } + else + { + S_StopAllSounds( true ); + SV_DeactivateServer(); + } +} + +/* +================ +SV_SetupClients + +determine the game type and prepare clients +================ +*/ +void SV_SetupClients( void ) +{ + qboolean changed_maxclients = false; + + // check if clients count was really changed + if( svs.maxclients != (int)sv_maxclients->value ) + changed_maxclients = true; + + if( !changed_maxclients ) return; // nothing to change + + // if clients count was changed we need to run full shutdown procedure + if( svs.maxclients ) Host_ShutdownServer(); + + // copy the actual value from cvar + svs.maxclients = (int)sv_maxclients->value; + + // dedicated servers are can't be single player and are usually DM + if( host.type == HOST_DEDICATED ) + svs.maxclients = bound( 4, svs.maxclients, MAX_CLIENTS ); + else svs.maxclients = bound( 1, svs.maxclients, MAX_CLIENTS ); + + if( svs.maxclients == 1 ) + Cvar_SetValue( "deathmatch", 0.0f ); + else Cvar_SetValue( "deathmatch", 1.0f ); + + // make cvars consistant + if( coop.value ) Cvar_SetValue( "deathmatch", 0.0f ); + + // feedback for cvar + Cvar_FullSet( "maxplayers", va( "%d", svs.maxclients ), FCVAR_LATCH ); + SV_UPDATE_BACKUP = ( svs.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; + + svs.clients = Z_Realloc( svs.clients, sizeof( sv_client_t ) * svs.maxclients ); + svs.num_client_entities = svs.maxclients * SV_UPDATE_BACKUP * NUM_PACKET_ENTITIES; + svs.packet_entities = Z_Realloc( svs.packet_entities, sizeof( entity_state_t ) * svs.num_client_entities ); + Con_DPrintf( "%s alloced by server packet entities\n", Q_memprint( sizeof( entity_state_t ) * svs.num_client_entities )); + + // init network stuff + NET_Config(( svs.maxclients > 1 )); + svgame.numEntities = svs.maxclients + 1; // clients + world + ClearBits( sv_maxclients->flags, FCVAR_CHANGED ); +} + +/* +================ +SV_SpawnServer + +Change the server to a new map, taking all connected +clients along with it. +================ +*/ +qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean background ) +{ + int i, current_skill; + edict_t *ent; + + SV_SetupClients(); + + if( !SV_InitGame( )) + return false; + + Log_Open(); + Log_Printf( "Loading map \"%s\"\n", mapname ); + Log_PrintServerVars(); + + svs.timestart = Sys_DoubleTime(); + svs.spawncount++; // any partially connected client will be restarted + + // let's not have any servers with no name + if( !COM_CheckString( hostname.string )) + Cvar_Set( "hostname", svgame.dllFuncs.pfnGetGameDescription ? svgame.dllFuncs.pfnGetGameDescription() : FS_Title( )); + + if( startspot ) + { + Con_Printf( "Spawn Server: %s [%s]\n", mapname, startspot ); + } + else + { + Con_DPrintf( "Spawn Server: %s\n", mapname ); + } + + memset( &sv, 0, sizeof( sv )); // wipe the entire per-level structure + sv.time = svgame.globals->time = 1.0f; // server spawn time it's always 1.0 second + sv.background = background; + + // initialize buffers + MSG_Init( &sv.signon, "Signon", sv.signon_buf, sizeof( sv.signon_buf )); + MSG_Init( &sv.multicast, "Multicast", sv.multicast_buf, sizeof( sv.multicast_buf )); + MSG_Init( &sv.datagram, "Datagram", sv.datagram_buf, sizeof( sv.datagram_buf )); + MSG_Init( &sv.reliable_datagram, "Reliable Datagram", sv.reliable_datagram_buf, sizeof( sv.reliable_datagram_buf )); + MSG_Init( &sv.spec_datagram, "Spectator Datagram", sv.spectator_buf, sizeof( sv.spectator_buf )); + + // clearing all the baselines + memset( svs.baselines, 0, sizeof( entity_state_t ) * GI->max_edicts ); + + // make cvars consistant + if( coop.value ) Cvar_SetValue( "deathmatch", 0 ); + current_skill = Q_rint( skill.value ); + current_skill = bound( 0, current_skill, 3 ); + Cvar_SetValue( "skill", (float)current_skill ); + + // force normal player collisions for single player + if( svs.maxclients == 1 ) + Cvar_SetValue( "sv_clienttrace", 1 ); + + // copy gamemode into svgame.globals + svgame.globals->deathmatch = deathmatch.value; + svgame.globals->coop = coop.value; + svgame.globals->maxClients = svs.maxclients; + + if( sv.background ) + { + // tell the game parts about background state + Cvar_FullSet( "sv_background", "1", FCVAR_READ_ONLY ); + Cvar_FullSet( "cl_background", "1", FCVAR_READ_ONLY ); + } + else + { + Cvar_FullSet( "sv_background", "0", FCVAR_READ_ONLY ); + Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY ); + } + + // force normal player collisions for single player + if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); + + // make sure what server name doesn't contain path and extension + COM_FileBase( mapname, sv.name ); + + // precache and static commands can be issued during map initialization + sv.state = ss_loading; + + if( startspot ) + Q_strncpy( sv.startspot, startspot, sizeof( sv.startspot )); + else sv.startspot[0] = '\0'; + + Q_snprintf( sv.model_precache[WORLD_INDEX], sizeof( sv.model_precache[0] ), "maps/%s.bsp", sv.name ); + SetBits( sv.model_precache_flags[WORLD_INDEX], RES_FATALIFMISSING ); + sv.worldmodel = sv.models[WORLD_INDEX] = Mod_LoadWorld( sv.model_precache[WORLD_INDEX], true ); + CRC32_MapFile( &sv.worldmapCRC, sv.model_precache[WORLD_INDEX], svs.maxclients > 1 ); + + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE ) && FS_FileExists( "progs.dat", false )) + { + file_t *f = FS_Open( "progs.dat", "rb", false ); + FS_Seek( f, sizeof( int ), SEEK_SET ); + FS_Read( f, &sv.progsCRC, sizeof( int )); + FS_Close( f ); + } + + for( i = WORLD_INDEX; i < sv.worldmodel->numsubmodels; i++ ) + { + Q_sprintf( sv.model_precache[i+1], "*%i", i ); + sv.models[i+1] = Mod_ForName( sv.model_precache[i+1], false, false ); + SetBits( sv.model_precache_flags[i+1], RES_FATALIFMISSING ); + } + + // leave slots at start for clients only + for( i = 0; i < svs.maxclients; i++ ) + { + // needs to reconnect + if( svs.clients[i].state > cs_connected ) + svs.clients[i].state = cs_connected; + + ent = EDICT_NUM( i + 1 ); + svs.clients[i].pViewEntity = NULL; + svs.clients[i].edict = ent; + SV_InitEdict( ent ); + } + + // heartbeats will always be sent to the id master + svs.last_heartbeat = MAX_HEARTBEAT; // send immediately + + // get actual movevars + SV_UpdateMovevars( true ); + + // clear physics interaction links + SV_ClearWorld(); + + return true; +} + +qboolean SV_Active( void ) +{ + return (sv.state != ss_dead); +} + +qboolean SV_Initialized( void ) +{ + return svs.initialized; +} + +int SV_GetMaxClients( void ) +{ + return svs.maxclients; +} + +void SV_InitGameProgs( void ) +{ + if( svgame.hInstance ) return; // already loaded + + // just try to initialize + SV_LoadProgs( GI->game_dll ); +} + +void SV_FreeGameProgs( void ) +{ + if( svs.initialized ) return; // server is active + + // unload progs (free cvars and commands) + SV_UnloadProgs(); +} + +/* +================ +SV_ExecLoadLevel + +State machine exec new map +================ +*/ +void SV_ExecLoadLevel( void ) +{ + if( SV_SpawnServer( GameState->levelName, NULL, GameState->backgroundMap )) + { + SV_SpawnEntities( GameState->levelName ); + SV_ActivateServer( true ); + } +} + +/* +================ +SV_ExecLoadGame + +State machine exec load saved game +================ +*/ +void SV_ExecLoadGame( void ) +{ + if( SV_SpawnServer( GameState->levelName, NULL, false )) + { + if( !SV_LoadGameState( GameState->levelName )) + SV_SpawnEntities( GameState->levelName ); + SV_ActivateServer( false ); + } +} + +/* +================ +SV_ExecChangeLevel + +State machine exec changelevel path +================ +*/ +void SV_ExecChangeLevel( void ) +{ + SV_ChangeLevel( GameState->loadGame, GameState->levelName, GameState->landmarkName, GameState->backgroundMap ); +} \ No newline at end of file diff --git a/engine/server/sv_log.c b/engine/server/sv_log.c new file mode 100644 index 00000000..fa02154a --- /dev/null +++ b/engine/server/sv_log.c @@ -0,0 +1,193 @@ +/* +sv_log.c - server logging in multiplayer +Copyright (C) 2017 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" + +void Log_Open( void ) +{ + time_t ltime; + struct tm *today; + char szFileBase[ MAX_OSPATH ]; + char szTestFile[ MAX_OSPATH ]; + file_t *fp = NULL; + char *temp; + int i; + + if( !svs.log.active ) + return; + + if( !mp_logfile.value ) + { + Con_Printf( "Server logging data to console.\n" ); + return; + } + + Log_Close(); + + // Find a new log file slot + time( <ime ); + today = localtime( <ime ); + temp = Cvar_VariableString( "logsdir" ); + + if( temp && Q_strlen( temp ) > 0 && !Q_strstr( temp, ":" ) && !Q_strstr( temp, ".." )) + Q_snprintf( szFileBase, sizeof( szFileBase ), "%s/L%02i%02i", temp, today->tm_mon + 1, today->tm_mday ); + else Q_snprintf( szFileBase, sizeof( szFileBase ), "logs/L%02i%02i", today->tm_mon + 1, today->tm_mday ); + + for ( i = 0; i < 1000; i++ ) + { + Q_snprintf( szTestFile, sizeof( szTestFile ), "%s%03i.log", szFileBase, i ); + + if( FS_FileExists( szTestFile, false )) + continue; + + fp = FS_Open( szTestFile, "w", true ); + if( fp ) + { + Con_Printf( "Server logging data to file %s\n", szTestFile ); + } + else + { + i = 1000; + } + break; + } + + if( i == 1000 ) + { + Con_Printf( "Unable to open logfiles under %s\nLogging disabled\n", szFileBase ); + svs.log.active = false; + return; + } + + if( fp ) svs.log.file = fp; + Log_Printf( "Log file started (file \"%s\") (game \"%s\") (version \"%i/%.2f/%d\")\n", + szTestFile, Info_ValueForKey( SV_Serverinfo(), "*gamedir" ), PROTOCOL_VERSION, XASH_VERSION, Q_buildnum() ); +} + +void Log_Close( void ) +{ + if( svs.log.file ) + { + Log_Printf( "Log file closed\n" ); + FS_Close( svs.log.file ); + } + svs.log.file = NULL; +} + +/* +================== +Log_Printf + +Prints a frag log message to the server's frag log file, console, and possible a UDP port. +================== +*/ +void Log_Printf( const char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + char *p; + time_t ltime; + struct tm *today; + + if( !svs.log.active ) + return; + + time( <ime ); + today = localtime( <ime ); + + Q_snprintf( string, sizeof( string ), "%02i/%02i/%04i - %02i:%02i:%02i: ", + today->tm_mon+1, today->tm_mday, 1900 + today->tm_year, today->tm_hour, today->tm_min, today->tm_sec ); + + p = string + Q_strlen( string ); + + va_start( argptr, fmt ); + Q_vsnprintf( p, sizeof( string ) - Q_strlen( string ), fmt, argptr ); + va_end( argptr ); + + if( svs.log.net_log ) + Netchan_OutOfBandPrint( NS_SERVER, svs.log.net_address, "log %s", string ); + + if( svs.log.active && svs.maxclients > 1 ) + { + // echo to server console + if( mp_logecho.value ) + Con_Printf( "%s", string ); + + // echo to log file + if( svs.log.file && mp_logfile.value ) + FS_Printf( svs.log.file, "%s", string ); + } +} + +static void Log_PrintServerCvar( const char *var_name, const char *var_value, const char *unused2, void *unused3 ) +{ + Log_Printf( "Server cvar \"%s\" = \"%s\"\n", var_name, var_value ); +} + +/* +================== +Log_PrintServerVars + +================== +*/ +void Log_PrintServerVars( void ) +{ + if( !svs.log.active ) + return; + + Log_Printf( "Server cvars start\n" ); + Cvar_LookupVars( FCVAR_SERVER, NULL, NULL, Log_PrintServerCvar ); + Log_Printf( "Server cvars end\n" ); +} + +/* +==================== +SV_ServerLog_f + +==================== +*/ +qboolean SV_ServerLog_f( sv_client_t *cl ) +{ + if( svs.maxclients <= 1 ) + return false; + + if( Cmd_Argc() != 2 ) + { + SV_ClientPrintf( cl, "usage: log < on|off >\n" ); + + if( svs.log.active ) + SV_ClientPrintf( cl, "currently logging\n" ); + else SV_ClientPrintf( cl, "not currently logging\n" ); + return true; + } + + if( !Q_stricmp( Cmd_Argv( 1 ), "off" )) + { + if( svs.log.active ) + Log_Close(); + } + else if( !Q_stricmp( Cmd_Argv( 1 ), "on" )) + { + svs.log.active = true; + Log_Open(); + } + else + { + SV_ClientPrintf( cl, "log: unknown parameter %s\n", Cmd_Argv( 1 )); + } + + return true; +} \ No newline at end of file diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c new file mode 100644 index 00000000..fbdc067d --- /dev/null +++ b/engine/server/sv_main.c @@ -0,0 +1,970 @@ +/* +sv_main.c - server main loop +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "net_encode.h" + +#define HEARTBEAT_SECONDS 300.0f // 300 seconds + +// server cvars +CVAR_DEFINE_AUTO( sv_lan, "0", 0, "server is a lan server ( no heartbeat, no authentication, no non-class C addresses, 9999.0 rate, etc." ); +CVAR_DEFINE_AUTO( sv_lan_rate, "20000.0", 0, "rate for lan server" ); +CVAR_DEFINE_AUTO( sv_aim, "1", FCVAR_ARCHIVE|FCVAR_SERVER, "auto aiming option" ); +CVAR_DEFINE_AUTO( sv_unlag, "1", 0, "allow lag compensation on server-side" ); +CVAR_DEFINE_AUTO( sv_maxunlag, "0.5", 0, "max latency value which can be interpolated (by default ping should not exceed 500 units)" ); +CVAR_DEFINE_AUTO( sv_unlagpush, "0.0", 0, "interpolation bias for unlag time" ); +CVAR_DEFINE_AUTO( sv_unlagsamples, "1", 0, "max samples to interpolate" ); +CVAR_DEFINE_AUTO( rcon_password, "", 0, "remote connect password" ); +CVAR_DEFINE_AUTO( sv_filterban, "1", 0, "filter banned users" ); +CVAR_DEFINE_AUTO( sv_cheats, "0", FCVAR_SERVER, "allow cheats on server" ); +CVAR_DEFINE_AUTO( sv_instancedbaseline, "1", 0, "allow to use instanced baselines to saves network overhead" ); +CVAR_DEFINE_AUTO( sv_contact, "", FCVAR_ARCHIVE|FCVAR_SERVER, "server techincal support contact address or web-page" ); +CVAR_DEFINE_AUTO( sv_minupdaterate, "10.0", FCVAR_ARCHIVE, "minimal value for 'cl_updaterate' window" ); +CVAR_DEFINE_AUTO( sv_maxupdaterate, "30.0", FCVAR_ARCHIVE, "maximal value for 'cl_updaterate' window" ); +CVAR_DEFINE_AUTO( sv_minrate, "0", FCVAR_SERVER, "min bandwidth rate allowed on server, 0 == unlimited" ); +CVAR_DEFINE_AUTO( sv_maxrate, "0", FCVAR_SERVER, "max bandwidth rate allowed on server, 0 == unlimited" ); +CVAR_DEFINE_AUTO( sv_logrelay, "0", FCVAR_ARCHIVE, "allow log messages from remote machines to be logged on this server" ); +CVAR_DEFINE_AUTO( sv_newunit, "0", 0, "clear level-saves from previous SP game chapter to help keep .sav file size as minimum" ); +CVAR_DEFINE_AUTO( sv_clienttrace, "1", FCVAR_SERVER, "0 = big box(Quake), 0.5 = halfsize, 1 = normal (100%), otherwise it's a scaling factor" ); +CVAR_DEFINE_AUTO( sv_timeout, "65", 0, "after this many seconds without a message from a client, the client is dropped" ); +CVAR_DEFINE_AUTO( sv_failuretime, "0.5", 0, "after this long without a packet from client, don't send any more until client starts sending again" ); +CVAR_DEFINE_AUTO( sv_password, "", FCVAR_SERVER|FCVAR_PROTECTED, "server password for entry into multiplayer games" ); +CVAR_DEFINE_AUTO( sv_proxies, "1", FCVAR_SERVER, "maximum count of allowed proxies for HLTV spectating" ); +CVAR_DEFINE_AUTO( sv_send_logos, "1", 0, "send custom decal logo to other players so they can view his too" ); +CVAR_DEFINE_AUTO( sv_send_resources, "1", 0, "allow to download missed resources for players" ); +CVAR_DEFINE_AUTO( sv_logbans, "0", 0, "print into the server log info about player bans" ); +CVAR_DEFINE_AUTO( sv_allow_upload, "1", FCVAR_SERVER, "allow uploading custom resources on a server" ); +CVAR_DEFINE_AUTO( sv_allow_download, "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); +CVAR_DEFINE_AUTO( sv_uploadmax, "0.5", FCVAR_SERVER, "max size to upload custom resources (500 kB as default)" ); +CVAR_DEFINE_AUTO( sv_downloadurl, "", FCVAR_PROTECTED, "location from which clients can download missing files" ); +CVAR_DEFINE( sv_consistency, "mp_consistency", "1", FCVAR_SERVER, "enbale consistency check in multiplayer" ); +CVAR_DEFINE_AUTO( mp_logecho, "1", 0, "log multiplayer frags to server logfile" ); +CVAR_DEFINE_AUTO( mp_logfile, "1", 0, "log multiplayer frags to console" ); + +// game-related cvars +CVAR_DEFINE_AUTO( mapcyclefile, "mapcycle.txt", 0, "name of multiplayer map cycle configuration file" ); +CVAR_DEFINE_AUTO( motdfile, "motd.txt", 0, "name of 'message of the day' file" ); +CVAR_DEFINE_AUTO( logsdir, "logs", 0, "place to store multiplayer logs" ); +CVAR_DEFINE_AUTO( bannedcfgfile, "banned.cfg", 0, "name of list of banned users" ); +CVAR_DEFINE_AUTO( deathmatch, "0", 0, "deathmatch mode in multiplayer game" ); +CVAR_DEFINE_AUTO( coop, "0", 0, "cooperative mode in multiplayer game" ); +CVAR_DEFINE_AUTO( teamplay, "0", 0, "team mode in multiplayer game" ); +CVAR_DEFINE_AUTO( skill, "1", 0, "skill level in singleplayer game" ); +CVAR_DEFINE_AUTO( temp1, "0", 0, "temporary cvar that used by some mods" ); + +// physic-related variables +CVAR_DEFINE_AUTO( sv_gravity, "800", FCVAR_MOVEVARS, "world gravity value" ); +CVAR_DEFINE_AUTO( sv_stopspeed, "100", FCVAR_MOVEVARS, "how fast you come to a complete stop" ); +CVAR_DEFINE_AUTO( sv_maxspeed, "320", FCVAR_MOVEVARS, "maximum speed a player can accelerate to when on ground" ); +CVAR_DEFINE_AUTO( sv_spectatormaxspeed, "500", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "maximum speed a spectator can accelerate in air" ); +CVAR_DEFINE_AUTO( sv_accelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed" ); +CVAR_DEFINE_AUTO( sv_airaccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the air" ); +CVAR_DEFINE_AUTO( sv_wateraccelerate, "10", FCVAR_MOVEVARS, "rate at which a player accelerates to sv_maxspeed while in the water" ); +CVAR_DEFINE_AUTO( sv_friction, "4", FCVAR_MOVEVARS, "how fast you slow down" ); +CVAR_DEFINE( sv_edgefriction, "edgefriction", "2", FCVAR_MOVEVARS, "how much you slow down when nearing a ledge you might fall off" ); +CVAR_DEFINE_AUTO( sv_waterfriction, "1", FCVAR_MOVEVARS, "how fast you slow down in water" ); +CVAR_DEFINE_AUTO( sv_bounce, "1", FCVAR_MOVEVARS, "bounce factor for entities with MOVETYPE_BOUNCE" ); +CVAR_DEFINE_AUTO( sv_stepsize, "18", FCVAR_MOVEVARS, "how high you and NPS's can step up" ); +CVAR_DEFINE_AUTO( sv_maxvelocity, "2000", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "max velocity for all things in the world" ); +CVAR_DEFINE_AUTO( sv_zmax, "4096", FCVAR_MOVEVARS|FCVAR_SPONLY, "maximum viewable distance" ); +CVAR_DEFINE_AUTO( sv_wateramp, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world waveheight factor" ); +CVAR_DEFINE( sv_footsteps, "mp_footsteps", "1", FCVAR_MOVEVARS, "world gravity value" ); +CVAR_DEFINE_AUTO( sv_skyname, "desert", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skybox name (can be dynamically changed in-game)" ); +CVAR_DEFINE_AUTO( sv_rollangle, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED|FCVAR_ARCHIVE, "how much to tilt the view when strafing" ); +CVAR_DEFINE_AUTO( sv_rollspeed, "200", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "how much strafing is necessary to tilt the view" ); +CVAR_DEFINE_AUTO( sv_skycolor_r, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight red component value" ); +CVAR_DEFINE_AUTO( sv_skycolor_g, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight green component value" ); +CVAR_DEFINE_AUTO( sv_skycolor_b, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight blue component value" ); +CVAR_DEFINE_AUTO( sv_skyvec_x, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by x-axis" ); +CVAR_DEFINE_AUTO( sv_skyvec_y, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by y-axis" ); +CVAR_DEFINE_AUTO( sv_skyvec_z, "0", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "skylight direction by z-axis" ); +CVAR_DEFINE_AUTO( sv_wateralpha, "1", FCVAR_MOVEVARS|FCVAR_UNLOGGED, "world surfaces water transparency factor. 1.0 - solid, 0.0 - fully transparent" ); +CVAR_DEFINE_AUTO( showtriggers, "0", FCVAR_LATCH, "debug cvar shows triggers" ); +CVAR_DEFINE_AUTO( sv_airmove, "1", FCVAR_SERVER, "obsolete, compatibility issues" ); +CVAR_DEFINE_AUTO( sv_version, "", FCVAR_READ_ONLY, "engine version string" ); +CVAR_DEFINE_AUTO( hostname, "", FCVAR_SERVER|FCVAR_PRINTABLEONLY, "name of current host" ); + +// gore-related cvars +CVAR_DEFINE_AUTO( violence_hblood, "1", 0, "draw human blood" ); +CVAR_DEFINE_AUTO( violence_ablood, "1", 0, "draw alien blood" ); +CVAR_DEFINE_AUTO( violence_hgibs, "1", 0, "show human gib entities" ); +CVAR_DEFINE_AUTO( violence_agibs, "1", 0, "show alien gib entities" ); + +convar_t *sv_novis; // disable server culling entities by vis +convar_t *sv_pausable; +convar_t *timeout; // seconds without any message +convar_t *sv_lighting_modulate; +convar_t *sv_maxclients; +convar_t *sv_check_errors; +convar_t *public_server; // should heartbeats be sent +convar_t *sv_reconnect_limit; // minimum seconds between connect messages +convar_t *sv_validate_changelevel; +convar_t *sv_sendvelocity; +convar_t *sv_hostmap; + +void Master_Shutdown( void ); + +//============================================================================ +/* +================ +SV_HasActivePlayers + +returns true if server have spawned players +================ +*/ +qboolean SV_HasActivePlayers( void ) +{ + int i; + + // server inactive + if( !svs.clients ) return false; + + for( i = 0; i < svs.maxclients; i++ ) + { + if( svs.clients[i].state == cs_spawned ) + return true; + } + return false; +} + +/* +=================== +SV_UpdateMovevars + +check movevars for changes every frame +send updates to client if changed +=================== +*/ +void SV_UpdateMovevars( qboolean initialize ) +{ + if( !initialize && !host.movevars_changed ) + return; + + // check range + if( sv_zmax.value < 256.0f ) Cvar_SetValue( "sv_zmax", 256.0f ); + + // clamp it right + if( host.features & ENGINE_WRITE_LARGE_COORD ) + { + if( sv_zmax.value > 131070.0f ) + Cvar_SetValue( "sv_zmax", 131070.0f ); + } + else + { + if( sv_zmax.value > 32767.0f ) + Cvar_SetValue( "sv_zmax", 32767.0f ); + } + + svgame.movevars.gravity = sv_gravity.value; + svgame.movevars.stopspeed = sv_stopspeed.value; + svgame.movevars.maxspeed = sv_maxspeed.value; + svgame.movevars.spectatormaxspeed = sv_spectatormaxspeed.value; + svgame.movevars.accelerate = sv_accelerate.value; + svgame.movevars.airaccelerate = sv_airaccelerate.value; + svgame.movevars.wateraccelerate = sv_wateraccelerate.value; + svgame.movevars.friction = sv_friction.value; + svgame.movevars.edgefriction = sv_edgefriction.value; + svgame.movevars.waterfriction = sv_waterfriction.value; + svgame.movevars.bounce = sv_bounce.value; + svgame.movevars.stepsize = sv_stepsize.value; + svgame.movevars.maxvelocity = sv_maxvelocity.value; + svgame.movevars.zmax = sv_zmax.value; + svgame.movevars.waveHeight = sv_wateramp.value; + Q_strncpy( svgame.movevars.skyName, sv_skyname.string, sizeof( svgame.movevars.skyName )); + svgame.movevars.footsteps = sv_footsteps.value; + svgame.movevars.rollangle = sv_rollangle.value; + svgame.movevars.rollspeed = sv_rollspeed.value; + svgame.movevars.skycolor_r = sv_skycolor_r.value; + svgame.movevars.skycolor_g = sv_skycolor_g.value; + svgame.movevars.skycolor_b = sv_skycolor_b.value; + svgame.movevars.skyvec_x = sv_skyvec_x.value; + svgame.movevars.skyvec_y = sv_skyvec_y.value; + svgame.movevars.skyvec_z = sv_skyvec_z.value; + svgame.movevars.wateralpha = sv_wateralpha.value; + svgame.movevars.features = host.features; // just in case. not really need + svgame.movevars.entgravity = 1.0f; + + if( initialize ) return; // too early + + if( MSG_WriteDeltaMovevars( &sv.reliable_datagram, &svgame.oldmovevars, &svgame.movevars )) + memcpy( &svgame.oldmovevars, &svgame.movevars, sizeof( movevars_t )); // oldstate changed + + host.movevars_changed = false; +} + +/* +================= +SV_CheckCmdTimes +================= +*/ +void SV_CheckCmdTimes( void ) +{ + sv_client_t *cl; + static double lastreset = 0; + float diff; + int i; + + if( Host_IsLocalGame( )) + return; + + if(( host.realtime - lastreset ) < 1.0 ) + return; + + lastreset = host.realtime; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state != cs_spawned ) + continue; + + if( cl->connecttime == 0.0 ) + { + cl->connecttime = host.realtime; + } + + diff = cl->connecttime + cl->cmdtime - host.realtime; + + if( diff > net_clockwindow->value ) + { + cl->ignorecmdtime = net_clockwindow->value + host.realtime; + cl->cmdtime = host.realtime - cl->connecttime; + } + else if( diff < -net_clockwindow->value ) + { + cl->cmdtime = host.realtime - cl->connecttime; + } + } +} + +void SV_ProcessFile( sv_client_t *cl, const char *filename ) +{ + customization_t *pList; + resource_t *resource; + resource_t *next; + byte md5[16]; + qboolean bFound; + qboolean bError; + + if( filename[0] != '!' ) + { + Con_Printf( "Ignoring non-customization file upload of %s\n", filename ); + return; + } + + COM_HexConvert( filename + 4, 32, md5 ); + + for( resource = cl->resourcesneeded.pNext; resource != &cl->resourcesneeded; resource = next ) + { + next = resource->pNext; + + if( !memcmp( resource->rgucMD5_hash, md5, 16 )) + break; + } + + if( resource == &cl->resourcesneeded ) + { + Con_Printf( "SV_ProcessFile: Unrequested decal\n" ); + return; + } + + if( resource->nDownloadSize != cl->netchan.tempbuffersize ) + { + Con_Printf( "Downloaded %i bytes for purported %i byte file\n", cl->netchan.tempbuffersize, resource->nDownloadSize ); + return; + } + + HPAK_AddLump( true, CUSTOM_RES_PATH, resource, cl->netchan.tempbuffer, NULL ); + ClearBits( resource->ucFlags, RES_WASMISSING ); + SV_MoveToOnHandList( cl, resource ); + + bError = false; + bFound = false; + + for( pList = cl->customdata.pNext; pList; pList = pList->pNext ) + { + if( !memcmp( pList->resource.rgucMD5_hash, resource->rgucMD5_hash, 16 )) + { + bFound = true; + break; + } + } + + if( !bFound ) + { + if( !COM_CreateCustomization( &cl->customdata, resource, -1, FCUST_FROMHPAK|FCUST_WIPEDATA|FCUST_IGNOREINIT, NULL, NULL )) + bError = true; + } + else + { + Con_DPrintf( "Duplicate resource received and ignored.\n" ); + } + + if( bError ) Con_Printf( S_ERROR "parsing custom decal from %s\n", cl->name ); +} + +/* +================= +SV_ReadPackets +================= +*/ +void SV_ReadPackets( void ) +{ + sv_client_t *cl; + int i, qport; + size_t curSize; + + while( NET_GetPacket( NS_SERVER, &net_from, net_message_buffer, &curSize )) + { + MSG_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); + + // check for connectionless packet (0xffffffff) first + if( MSG_GetMaxBytes( &net_message ) >= 4 && *(int *)net_message.pData == -1 ) + { + if( !svs.initialized ) + { + char *args, *c; + + MSG_Clear( &net_message ); + MSG_ReadLong( &net_message );// skip the -1 marker + + args = MSG_ReadStringLine( &net_message ); + Cmd_TokenizeString( args ); + c = Cmd_Argv( 0 ); + + if( !Q_strcmp( c, "rcon" )) + SV_RemoteCommand( net_from, &net_message ); + } + else SV_ConnectionlessPacket( net_from, &net_message ); + + continue; + } + + // read the qport out of the message so we can fix up + // stupid address translating routers + MSG_Clear( &net_message ); + MSG_ReadLong( &net_message ); // sequence number + MSG_ReadLong( &net_message ); // sequence number + qport = (int)MSG_ReadShort( &net_message ) & 0xffff; + + // check for packets from connected clients + for( i = 0, sv.current_client = svs.clients; i < svs.maxclients; i++, sv.current_client++ ) + { + cl = sv.current_client; + + if( cl->state == cs_free || FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + if( !NET_CompareBaseAdr( net_from, cl->netchan.remote_address )) + continue; + + if( cl->netchan.qport != qport ) + continue; + + if( cl->netchan.remote_address.port != net_from.port ) + cl->netchan.remote_address.port = net_from.port; + + if( Netchan_Process( &cl->netchan, &net_message )) + { + if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame + + // this is a valid, sequenced packet, so process it + if( cl->frames != NULL && cl->state != cs_zombie ) + { + SV_ExecuteClientMessage( cl, &net_message ); + svgame.globals->frametime = sv.frametime; + svgame.globals->time = sv.time; + } + } + + // fragmentation/reassembly sending takes priority over all game messages, want this in the future? + if( Netchan_IncomingReady( &cl->netchan )) + { + if( Netchan_CopyNormalFragments( &cl->netchan, &net_message, &curSize )) + { + MSG_Init( &net_message, "ClientPacket", net_message_buffer, curSize ); + + if(( svs.maxclients == 1 && !host_limitlocal->value ) || ( cl->state != cs_spawned )) + SetBits( cl->flags, FCL_SEND_NET_MESSAGE ); // reply at end of frame + + // this is a valid, sequenced packet, so process it + if( cl->frames != NULL && cl->state != cs_zombie ) + { + SV_ExecuteClientMessage( cl, &net_message ); + svgame.globals->frametime = sv.frametime; + svgame.globals->time = sv.time; + } + } + + if( Netchan_CopyFileFragments( &cl->netchan, &net_message )) + { + SV_ProcessFile( cl, cl->netchan.incomingfilename ); + } + } + break; + } + + if( i != svs.maxclients ) + continue; + } + + sv.current_client = NULL; +} + +/* +================== +SV_CheckTimeouts + +If a packet has not been received from a client for timeout->value +seconds, drop the conneciton. Server frames are used instead of +realtime to avoid dropping the local client while debugging. + +When a client is normally dropped, the sv_client_t goes into a zombie state +for a few seconds to make sure any final reliable message gets resent +if necessary +================== +*/ +void SV_CheckTimeouts( void ) +{ + sv_client_t *cl; + double droppoint; + int i, numclients = 0; + + droppoint = host.realtime - timeout->value; + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + { + if( cl->state >= cs_connected ) + { + if( cl->edict && !FBitSet( cl->edict->v.flags, FL_SPECTATOR|FL_FAKECLIENT )) + numclients++; + } + + // fake clients do not timeout + if( FBitSet( cl->flags, FCL_FAKECLIENT )) + continue; + + // FIXME: get rid of the zombie state + if( cl->state == cs_zombie ) + { + cl->state = cs_free; // can now be reused + continue; + } + + if(( cl->state == cs_connected || cl->state == cs_spawned ) && cl->netchan.last_received < droppoint ) + { + if( !NET_IsLocalAddress( cl->netchan.remote_address )) + { + SV_BroadcastPrintf( NULL, "%s timed out\n", cl->name ); + SV_DropClient( cl, false ); + cl->state = cs_free; // don't bother with zombie state + } + } + } + + if( svs.maxclients > 1 && sv.paused && !numclients ) + { + // nobody left, unpause the server + SV_TogglePause( "Pause released since no players are left." ); + } +} + +/* +================ +SV_PrepWorldFrame + +This has to be done before the world logic, because +player processing happens outside RunWorldFrame +================ +*/ +void SV_PrepWorldFrame( void ) +{ + edict_t *ent; + int i; + + for( i = 1; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + if( ent->free ) continue; + + ClearBits( ent->v.effects, EF_MUZZLEFLASH|EF_NOINTERP ); + } +} + +/* +================= +SV_IsSimulating +================= +*/ +qboolean SV_IsSimulating( void ) +{ + if( sv.background && SV_Active() && CL_Active()) + { + if( CL_IsInConsole( )) + return false; + return true; // force simulating for background map + } + + if( !SV_HasActivePlayers( )) + return false; + + if( host.type == HOST_DEDICATED ) + return true; // always active for dedicated servers + + // allow to freeze everything in singleplayer + if( svs.maxclients <= 1 && sv.playersonly ) + return false; + + if( !sv.paused && CL_IsInGame( )) + return true; + + return false; +} + +/* +================= +SV_RunGameFrame +================= +*/ +/* +================= +SV_RunGameFrame +================= +*/ +void SV_RunGameFrame( void ) +{ + int numFrames = 0; // debug + + if(!( sv.simulating = SV_IsSimulating( ))) + return; + + if( FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + { + sv.time_residual += host.frametime; + + if( sv.time_residual >= sv.frametime ) + { + SV_Physics(); + + sv.time_residual -= sv.frametime; + sv.time += sv.frametime; + numFrames++; + } + } + else + { + SV_Physics(); + + sv.time += sv.frametime; + numFrames++; + } +} + +/* +================== +Host_ServerFrame + +================== +*/ +void Host_ServerFrame( void ) +{ + // if server is not active, do nothing + if( !svs.initialized ) return; + + if( FBitSet( host.features, ENGINE_FIXED_FRAMERATE )) + sv.frametime = ( 1.0 / (double)GAME_FPS ); + else sv.frametime = host.frametime; // GoldSrc style + + svgame.globals->frametime = sv.frametime; + + // check clients timewindow + SV_CheckCmdTimes (); + + // read packets from clients + SV_ReadPackets (); + + // let everything in the world think and move + SV_RunGameFrame (); + + // refresh physic movevars on the client side + SV_UpdateMovevars ( false ); + + // request missing resources for clients + SV_RequestMissingResources(); + + // check timeouts + SV_CheckTimeouts (); + + // send messages back to the clients that had packets read this frame + SV_SendClientMessages (); + + // clear edict flags for next frame + SV_PrepWorldFrame (); + + // send a heartbeat to the master if needed + Master_Heartbeat (); +} + +//============================================================================ + +/* +================= +Master_Add +================= +*/ +void Master_Add( void ) +{ + netadr_t adr; + + NET_Config( true ); // allow remote + + if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) + MsgDev( D_INFO, "Can't resolve adr: %s\n", MASTERSERVER_ADR ); + + NET_SendPacket( NS_SERVER, 2, "q\xFF", adr ); +} + +/* +================ +Master_Heartbeat + +Send a message to the master every few minutes to +let it know we are alive, and log information +================ +*/ +void Master_Heartbeat( void ) +{ + if( !public_server->value || svs.maxclients == 1 ) + return; // only public servers send heartbeats + + // check for time wraparound + if( svs.last_heartbeat > host.realtime ) + svs.last_heartbeat = host.realtime; + + if(( host.realtime - svs.last_heartbeat ) < HEARTBEAT_SECONDS ) + return; // not time to send yet + + svs.last_heartbeat = host.realtime; + + Master_Add(); +} + +/* +================= +Master_Shutdown + +Informs all masters that this server is going down +================= +*/ +void Master_Shutdown( void ) +{ + netadr_t adr; + + NET_Config( true ); // allow remote + + if( !NET_StringToAdr( MASTERSERVER_ADR, &adr )) + MsgDev( D_INFO, "Can't resolve addr: %s\n", MASTERSERVER_ADR ); + + NET_SendPacket( NS_SERVER, 2, "\x62\x0A", adr ); +} + +/* +================= +SV_AddToMaster + +A server info answer to master server. +Master will validate challenge and this server to public list +================= +*/ +void SV_AddToMaster( netadr_t from, sizebuf_t *msg ) +{ + uint challenge; + char s[MAX_INFO_STRING] = "0\n"; // skip 2 bytes of header + int clients = 0, bots = 0, index; + int len = sizeof( s ); + + if( svs.clients ) + { + for( index = 0; index < svs.maxclients; index++ ) + { + if( svs.clients[index].state >= cs_connected ) + { + if( FBitSet( svs.clients[index].flags, FCL_FAKECLIENT )) + bots++; + else clients++; + } + } + } + + challenge = MSG_ReadUBitLong( msg, sizeof( uint ) << 3 ); + + Info_SetValueForKey( s, "protocol", va( "%d", PROTOCOL_VERSION ), len ); // protocol version + Info_SetValueForKey( s, "challenge", va( "%u", challenge ), len ); // challenge number + Info_SetValueForKey( s, "players", va( "%d", clients ), len ); // current player number, without bots + Info_SetValueForKey( s, "max", va( "%d", svs.maxclients ), len ); // max_players + Info_SetValueForKey( s, "bots", va( "%d", bots ), len ); // bot count + Info_SetValueForKey( s, "gamedir", GI->gamedir, len ); // gamedir + Info_SetValueForKey( s, "map", sv.name, len ); // current map + Info_SetValueForKey( s, "type", (host.type == HOST_DEDICATED) ? "d" : "l", len ); // dedicated or local + Info_SetValueForKey( s, "password", "0", len ); // is password set + Info_SetValueForKey( s, "os", "w", len ); // Windows + Info_SetValueForKey( s, "secure", "0", len ); // server anti-cheat + Info_SetValueForKey( s, "lan", "0", len ); // LAN servers doesn't send info to master + Info_SetValueForKey( s, "version", va( "%g", XASH_VERSION ), len ); // server region. 255 -- all regions + Info_SetValueForKey( s, "region", "255", len ); // server region. 255 -- all regions + Info_SetValueForKey( s, "product", GI->gamefolder, len ); // product? Where is the difference with gamedir? + + NET_SendPacket( NS_SERVER, Q_strlen( s ), s, from ); +} + +//============================================================================ + +/* +=============== +SV_Init + +Only called at startup, not for each game +=============== +*/ +void SV_Init( void ) +{ + string versionString; + + SV_InitHostCommands(); + + Cvar_Get ("protocol", va( "%i", PROTOCOL_VERSION ), FCVAR_READ_ONLY, "displays server protocol version" ); + Cvar_Get ("suitvolume", "0.25", FCVAR_ARCHIVE, "HEV suit volume" ); + Cvar_Get ("sv_background", "0", FCVAR_READ_ONLY, "indicate what background map is running" ); + Cvar_Get( "gamedir", GI->gamefolder, FCVAR_SERVER, "game folder" ); + Cvar_Get( "sv_alltalk", "1", 0, "allow to talking for all players (legacy, unused)" ); + Cvar_Get( "sv_allow_PhysX", "1", FCVAR_ARCHIVE, "allow XashXT to usage PhysX engine" ); // XashXT cvar + Cvar_Get( "sv_precache_meshes", "1", FCVAR_ARCHIVE, "cache SOLID_CUSTOM meshes before level loading" ); // Paranoia 2 cvar + Cvar_Get ("mapcyclefile", "mapcycle.txt", 0, "name of config file for map changing rules" ); + Cvar_Get ("servercfgfile","server.cfg", 0, "name of dedicated server configuration file" ); + Cvar_Get ("lservercfgfile","listenserver.cfg", 0, "name of listen server configuration file" ); + Cvar_Get ("logsdir","logs", 0, "default folder to write server logs" ); + + Cvar_RegisterVariable (&sv_zmax); + Cvar_RegisterVariable (&sv_wateramp); + Cvar_RegisterVariable (&sv_skycolor_r); + Cvar_RegisterVariable (&sv_skycolor_g); + Cvar_RegisterVariable (&sv_skycolor_b); + Cvar_RegisterVariable (&sv_skyvec_x); + Cvar_RegisterVariable (&sv_skyvec_y); + Cvar_RegisterVariable (&sv_skyvec_z); + Cvar_RegisterVariable (&sv_skyname); + Cvar_RegisterVariable (&sv_footsteps); + Cvar_RegisterVariable (&sv_wateralpha); + Cvar_RegisterVariable (&sv_cheats); + Cvar_RegisterVariable (&sv_airmove); + + Cvar_RegisterVariable (&showtriggers); + Cvar_RegisterVariable (&sv_aim); + Cvar_RegisterVariable (&motdfile); + Cvar_RegisterVariable (&deathmatch); + Cvar_RegisterVariable (&coop); + Cvar_RegisterVariable (&teamplay); + Cvar_RegisterVariable (&skill); + Cvar_RegisterVariable (&temp1); + + Cvar_RegisterVariable (&rcon_password); + Cvar_RegisterVariable (&sv_stepsize); + Cvar_RegisterVariable (&sv_newunit); + Cvar_RegisterVariable (&hostname); + timeout = Cvar_Get( "timeout", "125", FCVAR_SERVER, "connection timeout" ); + sv_pausable = Cvar_Get( "pausable", "1", FCVAR_SERVER, "allow players to pause or not" ); + sv_validate_changelevel = Cvar_Get( "sv_validate_changelevel", "1", FCVAR_ARCHIVE, "test change level for level-designer errors" ); + Cvar_RegisterVariable (&sv_clienttrace); + Cvar_RegisterVariable (&sv_bounce); + Cvar_RegisterVariable (&sv_spectatormaxspeed); + Cvar_RegisterVariable (&sv_waterfriction); + Cvar_RegisterVariable (&sv_wateraccelerate); + Cvar_RegisterVariable (&sv_rollangle); + Cvar_RegisterVariable (&sv_rollspeed); + Cvar_RegisterVariable (&sv_airaccelerate); + Cvar_RegisterVariable (&sv_maxvelocity); + Cvar_RegisterVariable (&sv_gravity); + Cvar_RegisterVariable (&sv_maxspeed); + Cvar_RegisterVariable (&sv_accelerate); + Cvar_RegisterVariable (&sv_friction); + Cvar_RegisterVariable (&sv_edgefriction); + Cvar_RegisterVariable (&sv_stopspeed); + sv_maxclients = Cvar_Get( "maxplayers", "1", FCVAR_LATCH, "server max capacity" ); + sv_check_errors = Cvar_Get( "sv_check_errors", "0", FCVAR_ARCHIVE, "check edicts for errors" ); + public_server = Cvar_Get ("public", "0", 0, "change server type from private to public" ); + sv_lighting_modulate = Cvar_Get( "r_lighting_modulate", "0.6", FCVAR_ARCHIVE, "lightstyles modulate scale" ); + sv_reconnect_limit = Cvar_Get ("sv_reconnect_limit", "3", FCVAR_ARCHIVE, "max reconnect attempts" ); + Cvar_RegisterVariable (&sv_failuretime ); + Cvar_RegisterVariable (&sv_unlag); + Cvar_RegisterVariable (&sv_maxunlag); + Cvar_RegisterVariable (&sv_unlagpush); + Cvar_RegisterVariable (&sv_unlagsamples); + Cvar_RegisterVariable (&sv_allow_upload); + Cvar_RegisterVariable (&sv_allow_download); + Cvar_RegisterVariable (&sv_send_logos); + Cvar_RegisterVariable (&sv_send_resources); + Cvar_RegisterVariable (&sv_uploadmax); + Cvar_RegisterVariable (&sv_version); + Cvar_RegisterVariable (&sv_instancedbaseline); + Cvar_RegisterVariable (&sv_consistency); + Cvar_RegisterVariable (&sv_downloadurl); + sv_novis = Cvar_Get( "sv_novis", "0", 0, "force to ignore server visibility" ); + sv_hostmap = Cvar_Get( "hostmap", GI->startmap, 0, "keep name of last entered map" ); + Cvar_RegisterVariable (&sv_password); + Cvar_RegisterVariable (&sv_lan); + Cvar_RegisterVariable (&violence_ablood); + Cvar_RegisterVariable (&violence_hblood); + Cvar_RegisterVariable (&violence_agibs); + Cvar_RegisterVariable (&violence_hgibs); + Cvar_RegisterVariable (&mp_logecho); + Cvar_RegisterVariable (&mp_logfile); + + // when we in developer-mode automatically turn cheats on + if( host_developer.value ) Cvar_SetValue( "sv_cheats", 1.0f ); + + MSG_Init( &net_message, "NetMessage", net_message_buffer, sizeof( net_message_buffer )); + + Q_snprintf( versionString, sizeof( versionString ), "%s: %.2f,%i,%i", "Xash3D", XASH_VERSION, PROTOCOL_VERSION, Q_buildnum() ); + Cvar_FullSet( "sv_version", versionString, FCVAR_READ_ONLY ); + + SV_ClearGameState (); // delete all temporary *.hl files +} + +/* +================== +SV_FinalMessage + +Used by SV_Shutdown to send a final message to all +connected clients before the server goes down. The messages are sent immediately, +not just stuck on the outgoing message list, because the server is going +to totally exit after returning from this function. +================== +*/ +void SV_FinalMessage( const char *message, qboolean reconnect ) +{ + byte msg_buf[64]; + sv_client_t *cl; + sizebuf_t msg; + int i; + + MSG_Init( &msg, "FinalMessage", msg_buf, sizeof( msg_buf )); + + if( COM_CheckString( message )) + { + MSG_BeginServerCmd( &msg, svc_print ); + MSG_WriteString( &msg, message ); + } + + if( reconnect ) + { + if( svs.maxclients <= 1 ) + { + MSG_BeginServerCmd( &msg, svc_changing ); + MSG_WriteOneBit( &msg, GameState->loadGame ); + } + else SV_BuildReconnect( &msg ); + } + else + { + MSG_BeginServerCmd( &msg, svc_disconnect ); + } + + // send it twice + // stagger the packets to crutch operating system limited buffers + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + if( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT )) + Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg )); + + for( i = 0, cl = svs.clients; i < svs.maxclients; i++, cl++ ) + if( cl->state >= cs_connected && !FBitSet( cl->flags, FCL_FAKECLIENT )) + Netchan_TransmitBits( &cl->netchan, MSG_GetNumBitsWritten( &msg ), MSG_GetData( &msg )); +} + +/* +================ +SV_FreeClients + +release server clients +================ +*/ +void SV_FreeClients( void ) +{ + if( svs.maxclients != 0 ) + { + // free server static data + if( svs.clients ) + { + Z_Free( svs.clients ); + svs.clients = NULL; + } + + if( svs.packet_entities ) + { + Z_Free( svs.packet_entities ); + svs.packet_entities = NULL; + svs.num_client_entities = 0; + svs.next_client_entities = 0; + } + } +} + +/* +================ +SV_Shutdown + +Called when each game quits, +before Sys_Quit or Sys_Error +================ +*/ +void SV_Shutdown( const char *finalmsg ) +{ + // already freed + if( !SV_Initialized( )) + { + // drop the client if want to load a new map + if( CL_IsPlaybackDemo( )) + CL_Drop(); + return; + } + + if( COM_CheckString( finalmsg )) + Con_Printf( "%s", finalmsg ); + + // rcon will be disconnected + SV_EndRedirect(); + + if( svs.clients ) + SV_FinalMessage( finalmsg, false ); + + if( public_server->value && svs.maxclients != 1 ) + Master_Shutdown(); + + NET_Config( false ); + SV_UnloadProgs (); + CL_Drop(); + + // free current level + memset( &sv, 0, sizeof( sv )); + + SV_FreeClients(); + svs.maxclients = 0; + + HPAK_FlushHostQueue(); + Log_Printf( "Server shutdown\n" ); + Log_Close(); + + svs.initialized = false; +} \ No newline at end of file diff --git a/engine/server/sv_move.c b/engine/server/sv_move.c new file mode 100644 index 00000000..bacdcb01 --- /dev/null +++ b/engine/server/sv_move.c @@ -0,0 +1,573 @@ +/* +sv_move.c - monsters movement +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "mathlib.h" +#include "server.h" +#include "const.h" +#include "pm_defs.h" + +#define MOVE_NORMAL 0 // normal move in the direction monster is facing +#define MOVE_STRAFE 1 // moves in direction specified, no matter which way monster is facing + +/* +============= +SV_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +qboolean SV_CheckBottom( edict_t *ent, int iMode ) +{ + vec3_t mins, maxs, start, stop; + float mid, bottom; + qboolean monsterClip; + trace_t trace; + int x, y; + + monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + VectorAdd( ent->v.origin, ent->v.mins, mins ); + VectorAdd( ent->v.origin, ent->v.maxs, maxs ); + + // if all of the points under the corners are solid world, don't bother + // with the tougher checks + // the corners must be within 16 of the midpoint + start[2] = mins[2] - 1.0f; + + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[0] = x ? maxs[0] : mins[0]; + start[1] = y ? maxs[1] : mins[1]; + svs.groupmask = ent->v.groupinfo; + + if( SV_PointContents( start ) != CONTENTS_SOLID ) + goto realcheck; + } + } + return true; // we got out easy +realcheck: + // check it for real... + start[2] = mins[2]; + + if( !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + start[2] += svgame.movevars.stepsize; + + // the midpoint must be within 16 of the bottom + start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5f; + start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5f; + stop[2] = start[2] - 2.0f * svgame.movevars.stepsize; + + if( iMode == WALKMOVE_WORLDONLY ) + trace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent ); + else trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent, monsterClip ); + + if( trace.fraction == 1.0f ) + return false; + + mid = bottom = trace.endpos[2]; + + // the corners must be within 16 of the midpoint + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[0] = stop[0] = x ? maxs[0] : mins[0]; + start[1] = stop[1] = y ? maxs[1] : mins[1]; + + if( iMode == WALKMOVE_WORLDONLY ) + trace = SV_MoveNoEnts( start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent ); + else trace = SV_Move( start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent, monsterClip ); + + if( trace.fraction != 1.0f && trace.endpos[2] > bottom ) + bottom = trace.endpos[2]; + if( trace.fraction == 1.0f || mid - trace.endpos[2] > svgame.movevars.stepsize ) + return false; + } + } + return true; +} + +void SV_WaterMove( edict_t *ent ) +{ + float drownlevel; + int waterlevel; + int watertype; + int flags; + + if( ent->v.movetype == MOVETYPE_NOCLIP ) + { + ent->v.air_finished = sv.time + 12.0f; + return; + } + + // no watermove for monsters but pushables + if(( ent->v.flags & FL_MONSTER ) && ent->v.health <= 0.0f ) + return; + + drownlevel = (ent->v.deadflag == DEAD_NO) ? 3.0 : 1.0; + waterlevel = ent->v.waterlevel; + watertype = ent->v.watertype; + flags = ent->v.flags; + + if( !( flags & ( FL_IMMUNE_WATER|FL_GODMODE ))) + { + if((( flags & FL_SWIM ) && waterlevel > drownlevel ) || waterlevel <= drownlevel ) + { + if( ent->v.air_finished > sv.time && ent->v.pain_finished > sv.time ) + { + ent->v.dmg += 2; + + if( ent->v.dmg < 15 ) + ent->v.dmg = 10; // quake1 original code + ent->v.pain_finished = sv.time + 1.0f; + } + } + else + { + ent->v.air_finished = sv.time + 12.0f; + ent->v.dmg = 2; + } + } + + if( !waterlevel ) + { + if( flags & FL_INWATER ) + { + // leave the water. + switch( COM_RandomLong( 0, 3 )) + { + case 0: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 1: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 2: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 3: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + } + + ent->v.flags = flags & ~FL_INWATER; + } + + ent->v.air_finished = sv.time + 12.0f; + return; + } + + if( watertype == CONTENTS_LAVA ) + { + if((!( flags & ( FL_IMMUNE_LAVA|FL_GODMODE ))) && ent->v.dmgtime < sv.time ) + { + if( ent->v.radsuit_finished < sv.time ) + ent->v.dmgtime = sv.time + 0.2f; + else ent->v.dmgtime = sv.time + 1.0f; + } + } + else if( watertype == CONTENTS_SLIME ) + { + if((!( flags & ( FL_IMMUNE_SLIME|FL_GODMODE ))) && ent->v.dmgtime < sv.time ) + { + if( ent->v.radsuit_finished < sv.time ) + ent->v.dmgtime = sv.time + 1.0; + // otherwise radsuit is fully protect entity from slime + } + } + + if( !( flags & FL_INWATER )) + { + if( watertype == CONTENTS_WATER ) + { + // entering the water + switch( COM_RandomLong( 0, 3 )) + { + case 0: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 1: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 2: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade3.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + case 3: + SV_StartSound( ent, CHAN_BODY, "player/pl_wade4.wav", 1.0f, ATTN_NORM, 0, 100 ); + break; + } + } + + ent->v.flags = flags | FL_INWATER; + ent->v.dmgtime = 0.0f; + } + + if( !( flags & FL_WATERJUMP )) + { + VectorMA( ent->v.velocity, ( ent->v.waterlevel * -0.8f * sv.frametime ), ent->v.velocity, ent->v.velocity ); + } +} + +/* +============= +SV_VecToYaw + +converts dir to yaw +============= +*/ +float SV_VecToYaw( const vec3_t src ) +{ + float yaw; + + if( !src ) return 0.0f; + + if( src[1] == 0.0f && src[0] == 0.0f ) + { + yaw = 0.0f; + } + else + { + yaw = (int)( atan2( src[1], src[0] ) * 180.0f / M_PI ); + if( yaw < 0 ) yaw += 360.0f; + } + return yaw; +} + +//============================================================================ + +qboolean SV_MoveStep( edict_t *ent, vec3_t move, qboolean relink ) +{ + int i; + trace_t trace; + vec3_t oldorg, neworg, end; + qboolean monsterClip; + edict_t *enemy; + float dz; + + VectorCopy( ent->v.origin, oldorg ); + VectorAdd( ent->v.origin, move, neworg ); + monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + + // well, try it. Flying and swimming monsters are easiest. + if( ent->v.flags & ( FL_SWIM|FL_FLY )) + { + // try one move with vertical motion, then one without + for( i = 0; i < 2; i++ ) + { + VectorAdd( ent->v.origin, move, neworg ); + + enemy = ent->v.enemy; + if( i == 0 && enemy != NULL ) + { + dz = ent->v.origin[2] - enemy->v.origin[2]; + + if( dz > 40.0f ) neworg[2] -= 8.0f; + else if( dz < 30.0f ) neworg[2] += 8.0f; + } + + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, neworg, MOVE_NORMAL, ent, monsterClip ); + + if( trace.fraction == 1.0f ) + { + svs.groupmask = ent->v.groupinfo; + + // that move takes us out of the water. + // apparently though, it's okay to travel into solids, lava, sky, etc :) + if(( ent->v.flags & FL_SWIM ) && SV_PointContents( trace.endpos ) == CONTENTS_EMPTY ) + return 0; + + VectorCopy( trace.endpos, ent->v.origin ); + if( relink ) SV_LinkEdict( ent, true ); + + return 1; + } + else + { + if( !SV_IsValidEdict( enemy )) + break; + } + } + return 0; + } + else + { + dz = svgame.movevars.stepsize; + neworg[2] += dz; + VectorCopy( neworg, end ); + end[2] -= dz * 2.0f; + + trace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip ); + if( trace.allsolid ) + return 0; + + if( trace.startsolid != 0 ) + { + neworg[2] -= dz; + trace = SV_Move( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip ); + + if( trace.allsolid != 0 || trace.startsolid != 0 ) + return 0; + } + + if( trace.fraction == 1.0f ) + { + if( ent->v.flags & FL_PARTIALGROUND ) + { + VectorAdd( ent->v.origin, move, ent->v.origin ); + if( relink ) SV_LinkEdict( ent, true ); + ent->v.flags &= ~FL_ONGROUND; + return 1; + } + return 0; + } + else + { + VectorCopy( trace.endpos, ent->v.origin ); + + if( SV_CheckBottom( ent, WALKMOVE_NORMAL ) == 0 ) + { + if( ent->v.flags & FL_PARTIALGROUND ) + { + if( relink ) SV_LinkEdict( ent, true ); + return 1; + } + + VectorCopy( oldorg, ent->v.origin ); + return 0; + } + else + { + ent->v.flags &= ~FL_PARTIALGROUND; + ent->v.groundentity = trace.ent; + if( relink ) SV_LinkEdict( ent, true ); + + return 1; + } + } + } +} + +qboolean SV_MoveTest( edict_t *ent, vec3_t move, qboolean relink ) +{ + float temp; + vec3_t oldorg, neworg, end; + trace_t trace; + + VectorCopy( ent->v.origin, oldorg ); + VectorAdd( ent->v.origin, move, neworg ); + + temp = svgame.movevars.stepsize; + + neworg[2] += temp; + VectorCopy( neworg, end ); + end[2] -= temp * 2.0f; + + trace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent ); + + if( trace.allsolid != 0 ) + return 0; + + if( trace.startsolid != 0 ) + { + neworg[2] -= temp; + trace = SV_MoveNoEnts( neworg, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent ); + + if( trace.allsolid != 0 || trace.startsolid != 0 ) + return 0; + } + + if( trace.fraction == 1.0f ) + { + if( ent->v.flags & FL_PARTIALGROUND ) + { + VectorAdd( ent->v.origin, move, ent->v.origin ); + if( relink ) SV_LinkEdict( ent, true ); + ent->v.flags &= ~FL_ONGROUND; + return 1; + } + return 0; + } + else + { + VectorCopy( trace.endpos, ent->v.origin ); + + if( SV_CheckBottom( ent, WALKMOVE_WORLDONLY ) == 0 ) + { + if( ent->v.flags & FL_PARTIALGROUND ) + { + if( relink ) SV_LinkEdict( ent, true ); + return 1; + } + + VectorCopy( oldorg, ent->v.origin ); + return 0; + } + else + { + ent->v.flags &= ~FL_PARTIALGROUND; + ent->v.groundentity = trace.ent; + if( relink ) SV_LinkEdict( ent, true ); + + return 1; + } + } +} + +qboolean SV_StepDirection( edict_t *ent, float yaw, float dist ) +{ + int ret; + float cSin, cCos; + vec3_t move; + + yaw = yaw * M_PI2 / 360.0f; + SinCos( yaw, &cSin, &cCos ); + VectorSet( move, cCos * dist, cSin * dist, 0.0f ); + + ret = SV_MoveStep( ent, move, false ); + SV_LinkEdict( ent, true ); + + return ret; +} + +qboolean SV_FlyDirection( edict_t *ent, vec3_t move ) +{ + int ret; + + ret = SV_MoveStep( ent, move, false ); + SV_LinkEdict( ent, true ); + + return ret; +} + +void SV_NewChaseDir( edict_t *actor, vec3_t destination, float dist ) +{ + float deltax, deltay; + float tempdir, olddir, turnaround; + vec3_t d; + + olddir = anglemod(((int)( actor->v.ideal_yaw / 45.0f )) * 45.0f ); + turnaround = anglemod( olddir - 180.0f ); + + deltax = destination[0] - actor->v.origin[0]; + deltay = destination[1] - actor->v.origin[1]; + + if( deltax > 10.0f ) + d[1] = 0.0f; + else if( deltax < -10.0f ) + d[1] = 180.0f; + else d[1] = -1; + + if( deltay < -10.0f ) + d[2] = 270.0f; + else if( deltay > 10.0f ) + d[2] = 90.0f; + else d[2] = -1.0f; + + // try direct route + if( d[1] != -1.0f && d[2] != -1.0f ) + { + if( d[1] == 0.0f ) + tempdir = ( d[2] == 90.0f ) ? 45.0f : 315.0f; + else tempdir = ( d[2] == 90.0f ) ? 135.0f : 215.0f; + + if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) + return; + } + + // try other directions + if( COM_RandomLong( 0, 1 ) != 0 || fabs( deltay ) > fabs( deltax )) + { + tempdir = d[1]; + d[1] = d[2]; + d[2] = tempdir; + } + + if( d[1] != -1.0f && d[1] != turnaround && SV_StepDirection( actor, d[1], dist )) + return; + + if( d[2] != -1.0f && d[2] != turnaround && SV_StepDirection( actor, d[2], dist )) + return; + + // there is no direct path to the player, so pick another direction + if( olddir != -1.0f && SV_StepDirection( actor, olddir, dist )) + return; + + // fine, just run somewhere. + if( COM_RandomLong( 0, 1 ) != 1 ) + { + for( tempdir = 0; tempdir <= 315.0f; tempdir += 45.0f ) + { + if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) + return; + } + } + else + { + for( tempdir = 315.0f; tempdir >= 0.0f; tempdir -= 45.0f ) + { + if( tempdir != turnaround && SV_StepDirection( actor, tempdir, dist )) + return; + } + } + + // we tried. run backwards. that ought to work... + if( turnaround != -1.0f && SV_StepDirection( actor, turnaround, dist )) + return; + + // well, we're stuck somehow. + actor->v.ideal_yaw = olddir; + + // if a bridge was pulled out from underneath a monster, it may not have + // a valid standing position at all. + if( !SV_CheckBottom( actor, WALKMOVE_NORMAL )) + { + actor->v.flags |= FL_PARTIALGROUND; + } +} + +void SV_MoveToOrigin( edict_t *ent, const vec3_t pflGoal, float dist, int iMoveType ) +{ + vec3_t vecDist; + + VectorCopy( pflGoal, vecDist ); + + if( ent->v.flags & ( FL_FLY|FL_SWIM|FL_ONGROUND )) + { + if( iMoveType == MOVE_NORMAL ) + { + if( !SV_StepDirection( ent, ent->v.ideal_yaw, dist )) + { + SV_NewChaseDir( ent, vecDist, dist ); + } + } + else + { + vecDist[0] -= ent->v.origin[0]; + vecDist[1] -= ent->v.origin[1]; + + if( ent->v.flags & ( FL_FLY|FL_SWIM )) + vecDist[2] -= ent->v.origin[2]; + else vecDist[2] = 0.0f; + + VectorNormalize( vecDist ); + VectorScale( vecDist, dist, vecDist ); + SV_FlyDirection( ent, vecDist ); + } + } +} \ No newline at end of file diff --git a/engine/server/sv_phys.c b/engine/server/sv_phys.c new file mode 100644 index 00000000..27bd96d6 --- /dev/null +++ b/engine/server/sv_phys.c @@ -0,0 +1,2068 @@ +/* +sv_phys.c - server physic +Copyright (C) 2007 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "const.h" +#include "library.h" +#include "triangleapi.h" +#include "gl_export.h" + +typedef int (*PHYSICAPI)( int, server_physics_api_t*, physics_interface_t* ); +extern triangleapi_t gTriApi; + +/* +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, +but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_BBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_BBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. +*/ +#define MOVE_EPSILON 0.01f +#define MAX_CLIP_PLANES 5 + +static const vec3_t current_table[] = +{ +{ 1, 0, 0 }, +{ 0, 1, 0 }, +{-1, 0, 0 }, +{ 0, -1, 0 }, +{ 0, 0, 1 }, +{ 0, 0, -1} +}; + +/* +=============================================================================== + +Utility functions + +=============================================================================== +*/ +/* +================ +SV_CheckAllEnts +================ +*/ +void SV_CheckAllEnts( void ) +{ + static double nextcheck; + edict_t *e; + int i; + + if( !sv_check_errors->value || sv.state != ss_active ) + return; + + if(( nextcheck - Sys_DoubleTime()) > 0.0 ) + return; + + // don't check entities every frame (but every 5 secs) + nextcheck = Sys_DoubleTime() + 5.0; + + // check edicts errors + for( i = svs.maxclients + 1; i < svgame.numEntities; i++ ) + { + e = EDICT_NUM( i ); + + if( e->free && e->pvPrivateData != NULL ) + { + Con_Printf( S_ERROR "Freed entity %s (%i) has private data.\n", SV_ClassName( e ), i ); + continue; + } + + if( !SV_IsValidEdict( e )) + continue; + + if( !e->v.pContainingEntity || e->v.pContainingEntity != e ) + { + Con_Printf( S_ERROR "Entity %s (%i) has invalid container, fixed.\n", SV_ClassName( e ), i ); + e->v.pContainingEntity = e; + continue; + } + + if( !e->pvPrivateData || !Mem_IsAllocatedExt( svgame.mempool, e->pvPrivateData )) + { + Con_Printf( S_ERROR "Entity %s (%i) trashed private data.\n", SV_ClassName( e ), i ); + e->pvPrivateData = NULL; + continue; + } + + SV_CheckVelocity( e ); + } +} + +/* +================ +SV_CheckVelocity +================ +*/ +void SV_CheckVelocity( edict_t *ent ) +{ + float wishspd; + float maxspd; + int i; + + // bound velocity + for( i = 0; i < 3; i++ ) + { + if( IS_NAN( ent->v.velocity[i] )) + { + if( sv_check_errors->value ) + Con_Printf( "Got a NaN velocity on %s\n", STRING( ent->v.classname )); + ent->v.velocity[i] = 0.0f; + } + + if( IS_NAN( ent->v.origin[i] )) + { + if( sv_check_errors->value ) + Con_Printf( "Got a NaN origin on %s\n", STRING( ent->v.classname )); + ent->v.origin[i] = 0.0f; + } + } + + wishspd = DotProduct( ent->v.velocity, ent->v.velocity ); + maxspd = sv_maxvelocity.value * sv_maxvelocity.value * 1.73f; // half-diagonal + + if( wishspd > maxspd ) + { + wishspd = sqrt( wishspd ); + if( sv_check_errors->value ) + Con_Printf( "Got a velocity too high on %s ( %.2f > %.2f )\n", STRING( ent->v.classname ), wishspd, sqrt( maxspd )); + wishspd = sv_maxvelocity.value / wishspd; + VectorScale( ent->v.velocity, wishspd, ent->v.velocity ); + } +} + +/* +================ +SV_UpdateBaseVelocity +================ +*/ +void SV_UpdateBaseVelocity( edict_t *ent ) +{ + if( ent->v.flags & FL_ONGROUND ) + { + edict_t *groundentity = ent->v.groundentity; + + if( SV_IsValidEdict( groundentity )) + { + // On conveyor belt that's moving? + if( groundentity->v.flags & FL_CONVEYOR ) + { + vec3_t new_basevel; + + VectorScale( groundentity->v.movedir, groundentity->v.speed, new_basevel ); + if( ent->v.flags & FL_BASEVELOCITY ) + VectorAdd( new_basevel, ent->v.basevelocity, new_basevel ); + + ent->v.flags |= FL_BASEVELOCITY; + VectorCopy( new_basevel, ent->v.basevelocity ); + } + } + } +} + +/* +============= +SV_RunThink + +Runs thinking code if time. There is some play in the exact time the think +function will be called, because it is called before any movement is done +in a frame. Not used for pushmove objects, because they must be exact. +Returns false if the entity removed itself. +============= +*/ +qboolean SV_RunThink( edict_t *ent ) +{ + float thinktime; + + if( !FBitSet( ent->v.flags, FL_KILLME )) + { + thinktime = ent->v.nextthink; + if( thinktime <= 0.0f || thinktime > (sv.time + sv.frametime)) + return true; + + if( thinktime < sv.time ) + thinktime = sv.time; // don't let things stay in the past. + // it is possible to start that way + // by a trigger with a local time. + ent->v.nextthink = 0.0f; + svgame.globals->time = thinktime; + svgame.dllFuncs.pfnThink( ent ); + } + + if( FBitSet( ent->v.flags, FL_KILLME )) + SV_FreeEdict( ent ); + + return !ent->free; +} + +/* +============= +SV_PlayerRunThink + +Runs thinking code if player time. There is some play in the exact time the think +function will be called, because it is called before any movement is done +in a frame. Not used for pushmove objects, because they must be exact. +Returns false if the entity removed itself. +============= +*/ +qboolean SV_PlayerRunThink( edict_t *ent, float frametime, double time ) +{ + float thinktime; + + if( svgame.physFuncs.SV_PlayerThink ) + return svgame.physFuncs.SV_PlayerThink( ent, frametime, time ); + + if( !FBitSet( ent->v.flags, FL_KILLME|FL_DORMANT )) + { + thinktime = ent->v.nextthink; + if( thinktime <= 0.0f || thinktime > (time + frametime)) + return true; + + if( thinktime < time ) + thinktime = time; // don't let things stay in the past. + // it is possible to start that way + // by a trigger with a local time. + + ent->v.nextthink = 0.0f; + svgame.globals->time = thinktime; + svgame.dllFuncs.pfnThink( ent ); + } + + if( FBitSet( ent->v.flags, FL_KILLME )) + ClearBits( ent->v.flags, FL_KILLME ); + + return !ent->free; +} + +/* +================== +SV_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void SV_Impact( edict_t *e1, edict_t *e2, trace_t *trace ) +{ + svgame.globals->time = sv.time; + + if(( e1->v.flags|e2->v.flags ) & FL_KILLME ) + return; + + if( e1->v.groupinfo && e2->v.groupinfo ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( e1->v.groupinfo, e2->v.groupinfo )) + return; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( e1->v.groupinfo, e2->v.groupinfo )) + return; + } + + if( e1->v.solid != SOLID_NOT ) + { + SV_CopyTraceToGlobal( trace ); + svgame.dllFuncs.pfnTouch( e1, e2 ); + } + + if( e2->v.solid != SOLID_NOT ) + { + SV_CopyTraceToGlobal( trace ); + svgame.dllFuncs.pfnTouch( e2, e1 ); + } +} + +/* +============= +SV_AngularMove + +may use friction for smooth stopping +============= +*/ +void SV_AngularMove( edict_t *ent, float frametime, float friction ) +{ + float adjustment; + int i; + + VectorMA( ent->v.angles, frametime, ent->v.avelocity, ent->v.angles ); + if( friction == 0.0f ) return; + + adjustment = frametime * (sv_stopspeed.value / 10.0f) * sv_friction.value * fabs( friction ); + + for( i = 0; i < 3; i++ ) + { + if( ent->v.avelocity[i] > 0.0f ) + { + ent->v.avelocity[i] -= adjustment; + if( ent->v.avelocity[i] < 0.0f ) + ent->v.avelocity[i] = 0.0f; + } + else + { + ent->v.avelocity[i] += adjustment; + if( ent->v.avelocity[i] > 0.0f ) + ent->v.avelocity[i] = 0.0f; + } + } +} + +/* +============= +SV_LinearMove + +use friction for smooth stopping +============= +*/ +void SV_LinearMove( edict_t *ent, float frametime, float friction ) +{ + int i; + float adjustment; + + VectorMA( ent->v.origin, frametime, ent->v.velocity, ent->v.origin ); + if( friction == 0.0f ) return; + + adjustment = frametime * (sv_stopspeed.value / 10.0f) * sv_friction.value * fabs( friction ); + + for( i = 0; i < 3; i++ ) + { + if( ent->v.velocity[i] > 0.0f ) + { + ent->v.velocity[i] -= adjustment; + if( ent->v.velocity[i] < 0.0f ) + ent->v.velocity[i] = 0.0f; + } + else + { + ent->v.velocity[i] += adjustment; + if( ent->v.velocity[i] > 0.0f ) + ent->v.velocity[i] = 0.0f; + } + } +} + +/* +============= +SV_RecursiveWaterLevel + +recursively recalculating the middle +============= +*/ +float SV_RecursiveWaterLevel( vec3_t origin, float out, float in, int count ) +{ + vec3_t point; + float offset; + + offset = ((out - in) * 0.5) + in; + if( ++count > 5 ) return offset; + + VectorSet( point, origin[0], origin[1], origin[2] + offset ); + + if( SV_PointContents( point ) == CONTENTS_WATER ) + return SV_RecursiveWaterLevel( origin, out, offset, count ); + return SV_RecursiveWaterLevel( origin, offset, in, count ); +} + +/* +============= +SV_Submerged + +determine how deep the entity is +============= +*/ +float SV_Submerged( edict_t *ent ) +{ + float start, bottom; + vec3_t point; + vec3_t center; + + VectorAverage( ent->v.absmin, ent->v.absmax, center ); + start = ent->v.absmin[2] - center[2]; + + switch( ent->v.waterlevel ) + { + case 1: + bottom = SV_RecursiveWaterLevel( center, 0.0f, start, 0 ); + return bottom - start; + case 3: + VectorSet( point, center[0], center[1], ent->v.absmax[2] ); + svs.groupmask = ent->v.groupinfo; + if( SV_PointContents( point ) == CONTENTS_WATER ) + return (ent->v.maxs[2] - ent->v.mins[2]); + case 2: // intentionally fallthrough + bottom = SV_RecursiveWaterLevel( center, ent->v.absmax[2] - center[2], 0.0f, 0 ); + return bottom - start; + } + + return 0.0f; +} + +/* +============= +SV_CheckWater +============= +*/ +qboolean SV_CheckWater( edict_t *ent ) +{ + int cont, truecont; + vec3_t point; + + point[0] = (ent->v.absmax[0] + ent->v.absmin[0]) * 0.5f; + point[1] = (ent->v.absmax[1] + ent->v.absmin[1]) * 0.5f; + point[2] = (ent->v.absmin[2] + 1.0f); + + ent->v.watertype = CONTENTS_EMPTY; + svs.groupmask = ent->v.groupinfo; + ent->v.waterlevel = 0; + + cont = SV_PointContents( point ); + + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + svs.groupmask = ent->v.groupinfo; + truecont = SV_TruePointContents( point ); + + ent->v.watertype = cont; + ent->v.waterlevel = 1; + + if( ent->v.absmin[2] != ent->v.absmax[2] ) + { + point[2] = (ent->v.absmin[2] + ent->v.absmax[2]) * 0.5f; + + svs.groupmask = ent->v.groupinfo; + cont = SV_PointContents( point ); + + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + ent->v.waterlevel = 2; + + VectorAdd( point, ent->v.view_ofs, point ); + svs.groupmask = ent->v.groupinfo; + cont = SV_PointContents( point ); + + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + ent->v.waterlevel = 3; + } + } + else + { + // a point entity + ent->v.waterlevel = 3; + } + + // Quake2 feature. Probably never was used in Half-Life... + if( truecont <= CONTENTS_CURRENT_0 && truecont >= CONTENTS_CURRENT_DOWN ) + { + float speed = 150.0f * ent->v.waterlevel / 3.0f; + const float *dir = current_table[CONTENTS_CURRENT_0 - truecont]; + + VectorMA( ent->v.basevelocity, speed, dir, ent->v.basevelocity ); + } + } + + return (ent->v.waterlevel > 1); +} + +/* +============= +SV_CheckMover + +test thing (applies the friction to pushables while standing on moving platform) +============= +*/ +qboolean SV_CheckMover( edict_t *ent ) +{ + edict_t *gnd = ent->v.groundentity; + + if( !SV_IsValidEdict( gnd )) + return false; + + if( gnd->v.movetype != MOVETYPE_PUSH ) + return false; + + if( VectorIsNull( gnd->v.velocity ) && VectorIsNull( gnd->v.avelocity )) + return false; + + return true; +} + +/* +================== +SV_ClipVelocity + +Slide off of the impacting object +================== +*/ +int SV_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) +{ + float backoff; + float change; + int i, blocked; + + blocked = 0; + if( normal[2] > 0.0f ) blocked |= 1; // floor + if( !normal[2] ) blocked |= 2; // step + + backoff = DotProduct( in, normal ) * overbounce; + + for( i = 0; i < 3; i++ ) + { + change = normal[i] * backoff; + out[i] = in[i] - change; + + if( out[i] > -1.0f && out[i] < 1.0f ) + out[i] = 0.0f; + } + + return blocked; +} + +/* +=============================================================================== + + FLYING MOVEMENT CODE + +=============================================================================== +*/ +/* +============ +SV_FlyMove + +The basic solid body movement clip that slides along multiple planes +*steptrace - if not NULL, the trace results of any vertical wall hit will be stored +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +int SV_FlyMove( edict_t *ent, float time, trace_t *steptrace ) +{ + int i, j, numplanes, bumpcount, blocked; + vec3_t dir, end, planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity, original_velocity, new_velocity; + float d, time_left, allFraction; + qboolean monsterClip; + trace_t trace; + + blocked = 0; + monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + VectorCopy( ent->v.velocity, original_velocity ); + VectorCopy( ent->v.velocity, primal_velocity ); + numplanes = 0; + + allFraction = 0.0f; + time_left = time; + + for( bumpcount = 0; bumpcount < MAX_CLIP_PLANES - 1; bumpcount++ ) + { + if( VectorIsNull( ent->v.velocity )) + break; + + VectorMA( ent->v.origin, time_left, ent->v.velocity, end ); + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, end, MOVE_NORMAL, ent, monsterClip ); + + allFraction += trace.fraction; + + if( trace.allsolid ) + { + // entity is trapped in another solid + VectorClear( ent->v.velocity ); + return 4; + } + + if( trace.fraction > 0.0f ) + { + // actually covered some distance + VectorCopy( trace.endpos, ent->v.origin ); + VectorCopy( ent->v.velocity, original_velocity ); + numplanes = 0; + } + + if( trace.fraction == 1.0f ) + break; // moved the entire distance + + if( !SV_IsValidEdict( trace.ent )) + break; // g-cont. this should never happens + + if( trace.plane.normal[2] > 0.7f ) + { + blocked |= 1; // floor + + if( trace.ent->v.solid == SOLID_BSP || trace.ent->v.solid == SOLID_SLIDEBOX || + trace.ent->v.movetype == MOVETYPE_PUSHSTEP || (trace.ent->v.flags & FL_CLIENT)) + { + SetBits( ent->v.flags, FL_ONGROUND ); + ent->v.groundentity = trace.ent; + } + } + + if( trace.plane.normal[2] == 0.0f ) + { + blocked |= 2; // step + if( steptrace ) *steptrace = trace; // save for player extrafriction + } + + // run the impact function + SV_Impact( ent, trace.ent, &trace ); + + // break if removed by the impact function + if( ent->free ) break; + + time_left -= time_left * trace.fraction; + + // clipped to another plane + if( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + VectorClear( ent->v.velocity ); + break; + } + + VectorCopy( trace.plane.normal, planes[numplanes] ); + numplanes++; + + // modify original_velocity so it parallels all of the clip planes + for( i = 0; i < numplanes; i++ ) + { + SV_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0f ); + + for( j = 0; j < numplanes; j++ ) + { + if( j != i ) + { + if( DotProduct( new_velocity, planes[j] ) < 0.0f ) + break; // not ok + } + } + + if( j == numplanes ) + break; + } + + if( i != numplanes ) + { + // go along this plane + VectorCopy( new_velocity, ent->v.velocity ); + } + else + { + // go along the crease + if( numplanes != 2 ) + { + VectorClear( ent->v.velocity ); + break; + } + + CrossProduct( planes[0], planes[1], dir ); + d = DotProduct( dir, ent->v.velocity ); + VectorScale( dir, d, ent->v.velocity ); + } + + // if current velocity is against the original velocity, + // stop dead to avoid tiny occilations in sloping corners + if( DotProduct( ent->v.velocity, primal_velocity ) <= 0.0f ) + { + VectorClear( ent->v.velocity ); + break; + } + } + + if( allFraction == 0.0f ) + VectorClear( ent->v.velocity ); + + return blocked; +} + +/* +============ +SV_AddGravity + +============ +*/ +void SV_AddGravity( edict_t *ent ) +{ + float ent_gravity; + + if( ent->v.gravity ) + ent_gravity = ent->v.gravity; + else ent_gravity = 1.0f; + + // add gravity incorrectly + ent->v.velocity[2] -= ( ent_gravity * sv_gravity.value * sv.frametime ); + ent->v.velocity[2] += ( ent->v.basevelocity[2] * sv.frametime ); + ent->v.basevelocity[2] = 0.0f; + + // bound velocity + SV_CheckVelocity( ent ); +} + +/* +============ +SV_AddHalfGravity + +============ +*/ +void SV_AddHalfGravity( edict_t *ent, float timestep ) +{ + float ent_gravity; + + if( ent->v.gravity ) + ent_gravity = ent->v.gravity; + else ent_gravity = 1.0f; + + // Add 1/2 of the total gravitational effects over this timestep + ent->v.velocity[2] -= ( 0.5f * ent_gravity * sv_gravity.value * timestep ); + ent->v.velocity[2] += ( ent->v.basevelocity[2] * sv.frametime ); + ent->v.basevelocity[2] = 0.0f; + + // bound velocity + SV_CheckVelocity( ent ); +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ +/* +============ +SV_AllowPushRotate + +Allows to change entity yaw? +============ +*/ +qboolean SV_AllowPushRotate( edict_t *ent ) +{ + model_t *mod; + + mod = SV_ModelHandle( ent->v.modelindex ); + + if( !mod || mod->type != mod_brush ) + return true; + + if( !FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) + return false; + + if( FBitSet( mod->flags, MODEL_HAS_ORIGIN )) + return true; + + return false; +} + +/* +============ +SV_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t SV_PushEntity( edict_t *ent, const vec3_t lpush, const vec3_t apush, int *blocked, float flDamage ) +{ + trace_t trace; + qboolean monsterClip; + int type; + vec3_t end; + + monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + VectorAdd( ent->v.origin, lpush, end ); + + if( ent->v.movetype == MOVETYPE_FLYMISSILE ) + type = MOVE_MISSILE; + else if( ent->v.solid == SOLID_TRIGGER || ent->v.solid == SOLID_NOT ) + type = MOVE_NOMONSTERS; // only clip against bmodels + else type = MOVE_NORMAL; + + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, end, type, ent, monsterClip ); + + if( trace.fraction != 0.0f ) + { + VectorCopy( trace.endpos, ent->v.origin ); + + if( sv.state == ss_active && apush[YAW] && ( ent->v.flags & FL_CLIENT )) + { + ent->v.avelocity[1] += apush[1]; + ent->v.fixangle = 2; + } + + // don't rotate pushables! + if( SV_AllowPushRotate( ent )) + ent->v.angles[YAW] += trace.fraction * apush[YAW]; + } + + SV_LinkEdict( ent, true ); + + if( blocked ) + { + // more accuracy blocking code + if( flDamage <= 0.0f ) + *blocked = !VectorCompare( ent->v.origin, end ); // can't move full distance + else *blocked = true; + } + + // so we can run impact function afterwards. + if( SV_IsValidEdict( trace.ent )) + SV_Impact( ent, trace.ent, &trace ); + + return trace; +} + +/* +============ +SV_CanPushed + +filter entities for push +============ +*/ +qboolean SV_CanPushed( edict_t *ent ) +{ + // filter movetypes to collide with + switch( ent->v.movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_FOLLOW: + case MOVETYPE_NOCLIP: + case MOVETYPE_COMPOUND: + return false; + } + return true; +} + +/* +============ +SV_CanBlock + +allow entity to block pusher? +============ +*/ +static qboolean SV_CanBlock( edict_t *ent ) +{ + if( ent->v.mins[0] == ent->v.maxs[0] ) + return false; + + if( ent->v.solid == SOLID_NOT || ent->v.solid == SOLID_TRIGGER ) + { + // clear bounds for deadbody + ent->v.mins[0] = ent->v.mins[1] = 0.0f; + ent->v.maxs[0] = ent->v.maxs[1] = 0.0f; + ent->v.maxs[2] = ent->v.mins[2]; + return false; + } + + return true; +} + +/* +============ +SV_PushMove + +============ +*/ +static edict_t *SV_PushMove( edict_t *pusher, float movetime ) +{ + int i, e, block; + int num_moved, oldsolid; + vec3_t mins, maxs, lmove; + sv_pushed_t *p, *pushed_p; + edict_t *check; + + if( svgame.globals->changelevel || VectorIsNull( pusher->v.velocity )) + { + pusher->v.ltime += movetime; + return NULL; + } + + for( i = 0; i < 3; i++ ) + { + lmove[i] = pusher->v.velocity[i] * movetime; + mins[i] = pusher->v.absmin[i] + lmove[i]; + maxs[i] = pusher->v.absmax[i] + lmove[i]; + } + + pushed_p = svgame.pushed; + + // save the pusher's original position + pushed_p->ent = pusher; + VectorCopy( pusher->v.origin, pushed_p->origin ); + VectorCopy( pusher->v.angles, pushed_p->angles ); + pushed_p++; + + // move the pusher to it's final position + SV_LinearMove( pusher, movetime, 0.0f ); + SV_LinkEdict( pusher, false ); + pusher->v.ltime += movetime; + oldsolid = pusher->v.solid; + + // non-solid pushers can't push anything + if( pusher->v.solid == SOLID_NOT ) + return NULL; + + // see if any solid entities are inside the final position + num_moved = 0; + + for( e = 1; e < svgame.numEntities; e++ ) + { + check = EDICT_NUM( e ); + if( !SV_IsValidEdict( check )) continue; + + // filter movetypes to collide with + if( !SV_CanPushed( check )) + continue; + + pusher->v.solid = SOLID_NOT; + block = SV_TestEntityPosition( check, pusher ); + pusher->v.solid = oldsolid; + if( block ) continue; + + // if the entity is standing on the pusher, it will definately be moved + if( !(( check->v.flags & FL_ONGROUND ) && check->v.groundentity == pusher )) + { + if( check->v.absmin[0] >= maxs[0] + || check->v.absmin[1] >= maxs[1] + || check->v.absmin[2] >= maxs[2] + || check->v.absmax[0] <= mins[0] + || check->v.absmax[1] <= mins[1] + || check->v.absmax[2] <= mins[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if( !SV_TestEntityPosition( check, NULL )) + continue; + } + + // remove the onground flag for non-players + if( check->v.movetype != MOVETYPE_WALK ) + check->v.flags &= ~FL_ONGROUND; + + // save original position of contacted entity + pushed_p->ent = check; + VectorCopy( check->v.origin, pushed_p->origin ); + VectorCopy( check->v.angles, pushed_p->angles ); + pushed_p++; + + // try moving the contacted entity + pusher->v.solid = SOLID_NOT; + SV_PushEntity( check, lmove, vec3_origin, &block, pusher->v.dmg ); + pusher->v.solid = oldsolid; + + // if it is still inside the pusher, block + if( SV_TestEntityPosition( check, NULL ) && block ) + { + if( !SV_CanBlock( check )) + continue; + + pusher->v.ltime -= movetime; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for( p = pushed_p - 1; p >= svgame.pushed; p-- ) + { + VectorCopy( p->origin, p->ent->v.origin ); + VectorCopy( p->angles, p->ent->v.angles ); + SV_LinkEdict( p->ent, (p->ent == check) ? true : false ); + } + return check; + } + } + + return NULL; +} + +/* +============ +SV_PushRotate + +============ +*/ +static edict_t *SV_PushRotate( edict_t *pusher, float movetime ) +{ + int i, e, block, oldsolid; + matrix4x4 start_l, end_l; + vec3_t lmove, amove; + sv_pushed_t *p, *pushed_p; + vec3_t org, org2, temp; + edict_t *check; + + if( svgame.globals->changelevel || VectorIsNull( pusher->v.avelocity )) + { + pusher->v.ltime += movetime; + return NULL; + } + + for( i = 0; i < 3; i++ ) + amove[i] = pusher->v.avelocity[i] * movetime; + + // create pusher initial position + Matrix4x4_CreateFromEntity( start_l, pusher->v.angles, pusher->v.origin, 1.0f ); + + pushed_p = svgame.pushed; + + // save the pusher's original position + pushed_p->ent = pusher; + VectorCopy( pusher->v.origin, pushed_p->origin ); + VectorCopy( pusher->v.angles, pushed_p->angles ); + pushed_p++; + + // move the pusher to it's final position + SV_AngularMove( pusher, movetime, pusher->v.friction ); + SV_LinkEdict( pusher, false ); + pusher->v.ltime += movetime; + oldsolid = pusher->v.solid; + + // non-solid pushers can't push anything + if( pusher->v.solid == SOLID_NOT ) + return NULL; + + // create pusher final position + Matrix4x4_CreateFromEntity( end_l, pusher->v.angles, pusher->v.origin, 1.0f ); + + // see if any solid entities are inside the final position + for( e = 1; e < svgame.numEntities; e++ ) + { + check = EDICT_NUM( e ); + if( !SV_IsValidEdict( check )) + continue; + + // filter movetypes to collide with + if( !SV_CanPushed( check )) + continue; + + pusher->v.solid = SOLID_NOT; + block = SV_TestEntityPosition( check, pusher ); + pusher->v.solid = oldsolid; + if( block ) continue; + + // if the entity is standing on the pusher, it will definately be moved + if( !(( check->v.flags & FL_ONGROUND ) && check->v.groundentity == pusher )) + { + if( check->v.absmin[0] >= pusher->v.absmax[0] + || check->v.absmin[1] >= pusher->v.absmax[1] + || check->v.absmin[2] >= pusher->v.absmax[2] + || check->v.absmax[0] <= pusher->v.absmin[0] + || check->v.absmax[1] <= pusher->v.absmin[1] + || check->v.absmax[2] <= pusher->v.absmin[2] ) + continue; + + // see if the ent's bbox is inside the pusher's final position + if( !SV_TestEntityPosition( check, NULL )) + continue; + } + + // save original position of contacted entity + pushed_p->ent = check; + VectorCopy( check->v.origin, pushed_p->origin ); + VectorCopy( check->v.angles, pushed_p->angles ); + pushed_p->fixangle = check->v.fixangle; + pushed_p++; + + // calculate destination position + if( check->v.movetype == MOVETYPE_PUSHSTEP || check->v.movetype == MOVETYPE_STEP ) + VectorAverage( check->v.absmin, check->v.absmax, org ); + else VectorCopy( check->v.origin, org ); + + Matrix4x4_VectorITransform( start_l, org, temp ); + Matrix4x4_VectorTransform( end_l, temp, org2 ); + VectorSubtract( org2, org, lmove ); + + // i can't clear FL_ONGROUND in all cases because many bad things may be happen + if( check->v.movetype != MOVETYPE_WALK ) + { + if( lmove[2] != 0.0f ) check->v.flags &= ~FL_ONGROUND; + if( lmove[2] < 0.0f && !pusher->v.dmg ) + lmove[2] = 0.0f; // let's the free falling + } + + // try moving the contacted entity + pusher->v.solid = SOLID_NOT; + SV_PushEntity( check, lmove, amove, &block, pusher->v.dmg ); + pusher->v.solid = oldsolid; + + // pushed entity blocked by wall + if( block && check->v.movetype != MOVETYPE_WALK ) + check->v.flags &= ~FL_ONGROUND; + + // if it is still inside the pusher, block + if( SV_TestEntityPosition( check, NULL ) && block ) + { + if( !SV_CanBlock( check )) + continue; + + pusher->v.ltime -= movetime; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for( p = pushed_p - 1; p >= svgame.pushed; p-- ) + { + VectorCopy( p->origin, p->ent->v.origin ); + VectorCopy( p->angles, p->ent->v.angles ); + SV_LinkEdict( p->ent, (p->ent == check) ? true : false ); + p->ent->v.fixangle = p->fixangle; + } + return check; + } + } + + return NULL; +} + +/* +================ +SV_Physics_Pusher + +================ +*/ +void SV_Physics_Pusher( edict_t *ent ) +{ + float oldtime, oldtime2; + float thinktime, movetime; + edict_t *pBlocker; + int i; + + pBlocker = NULL; + oldtime = ent->v.ltime; + thinktime = ent->v.nextthink; + + if( thinktime < oldtime + sv.frametime ) + { + movetime = thinktime - oldtime; + if( movetime < 0.0f ) movetime = 0.0f; + } + else movetime = sv.frametime; + + if( movetime ) + { + if( !VectorIsNull( ent->v.avelocity )) + { + if( !VectorIsNull( ent->v.velocity )) + { + pBlocker = SV_PushRotate( ent, movetime ); + + if( !pBlocker ) + { + oldtime2 = ent->v.ltime; + + // reset the local time to what it was before we rotated + ent->v.ltime = oldtime; + pBlocker = SV_PushMove( ent, movetime ); + if( ent->v.ltime < oldtime2 ) + ent->v.ltime = oldtime2; + } + } + else + { + pBlocker = SV_PushRotate( ent, movetime ); + } + } + else + { + pBlocker = SV_PushMove( ent, movetime ); + } + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + if( pBlocker ) svgame.dllFuncs.pfnBlocked( ent, pBlocker ); + + for( i = 0; i < 3; i++ ) + { + if( ent->v.angles[i] < -3600.0f || ent->v.angles[i] > 3600.0f ) + ent->v.angles[i] = fmod( ent->v.angles[i], 3600.0f ); + } + + if( thinktime > oldtime && (( ent->v.flags & FL_ALWAYSTHINK ) || thinktime <= ent->v.ltime )) + { + ent->v.nextthink = 0.0f; + svgame.globals->time = sv.time; + svgame.dllFuncs.pfnThink( ent ); + } +} + +//============================================================================ +/* +============= +SV_Physics_Follow + +just copy angles and origin of parent +============= +*/ +void SV_Physics_Follow( edict_t *ent ) +{ + edict_t *parent; + + // regular thinking + if( !SV_RunThink( ent )) return; + + parent = ent->v.aiment; + + if( !SV_IsValidEdict( parent )) + { + ent->v.movetype = MOVETYPE_NONE; + return; + } + + VectorAdd( parent->v.origin, ent->v.v_angle, ent->v.origin ); + VectorCopy( parent->v.angles, ent->v.angles ); + + SV_LinkEdict( ent, true ); +} + +/* +============= +SV_Physics_Compound + +a glue two entities together +============= +*/ +void SV_Physics_Compound( edict_t *ent ) +{ + edict_t *parent; + + // regular thinking + if( !SV_RunThink( ent )) return; + + parent = ent->v.aiment; + + if( !SV_IsValidEdict( parent )) + { + ent->v.movetype = MOVETYPE_NONE; + return; + } + + if( ent->v.solid != SOLID_TRIGGER ) + ent->v.solid = SOLID_NOT; + + switch( parent->v.movetype ) + { + case MOVETYPE_PUSH: + case MOVETYPE_PUSHSTEP: + break; + default: return; + } + + // not initialized ? + if( ent->v.ltime == 0.0f ) + { + VectorCopy( parent->v.origin, ent->v.oldorigin ); + VectorCopy( parent->v.angles, ent->v.avelocity ); + ent->v.ltime = sv.frametime; + return; + } + + if( !VectorCompare( parent->v.origin, ent->v.oldorigin ) || !VectorCompare( parent->v.angles, ent->v.avelocity )) + { + matrix4x4 start_l, end_l, temp_l, child; + + // create parent old position + Matrix4x4_CreateFromEntity( temp_l, ent->v.avelocity, ent->v.oldorigin, 1.0f ); + Matrix4x4_Invert_Simple( start_l, temp_l ); + + // create parent actual position + Matrix4x4_CreateFromEntity( end_l, parent->v.angles, parent->v.origin, 1.0f ); + + // stupid quake bug!!! + if( !( host.features & ENGINE_COMPENSATE_QUAKE_BUG )) + ent->v.angles[PITCH] = -ent->v.angles[PITCH]; + + // create child actual position + Matrix4x4_CreateFromEntity( child, ent->v.angles, ent->v.origin, 1.0f ); + + // transform child from start to end + Matrix4x4_ConcatTransforms( temp_l, start_l, child ); + Matrix4x4_ConcatTransforms( child, end_l, temp_l ); + + // create child final position + Matrix4x4_ConvertToEntity( child, ent->v.angles, ent->v.origin ); + + // stupid quake bug!!! + if( !( host.features & ENGINE_COMPENSATE_QUAKE_BUG )) + ent->v.angles[PITCH] = -ent->v.angles[PITCH]; + } + + // notsolid ents never touch triggers + SV_LinkEdict( ent, (ent->v.solid == SOLID_NOT) ? false : true ); + + // shuffle states + VectorCopy( parent->v.origin, ent->v.oldorigin ); + VectorCopy( parent->v.angles, ent->v.avelocity ); +} + +/* +============= +SV_PhysicsNoclip + +A moving object that doesn't obey physics +============= +*/ +void SV_Physics_Noclip( edict_t *ent ) +{ + // regular thinking + if( !SV_RunThink( ent )) return; + + SV_CheckWater( ent ); + + VectorMA( ent->v.origin, sv.frametime, ent->v.velocity, ent->v.origin ); + VectorMA( ent->v.angles, sv.frametime, ent->v.avelocity, ent->v.angles ); + + // noclip ents never touch triggers + SV_LinkEdict( ent, false ); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ +/* +============= +SV_CheckWaterTransition + +============= +*/ +void SV_CheckWaterTransition( edict_t *ent ) +{ + vec3_t point; + int cont; + + point[0] = (ent->v.absmax[0] + ent->v.absmin[0]) * 0.5f; + point[1] = (ent->v.absmax[1] + ent->v.absmin[1]) * 0.5f; + point[2] = (ent->v.absmin[2] + 1.0f); + + svs.groupmask = ent->v.groupinfo; + cont = SV_PointContents( point ); + + if( !ent->v.watertype ) + { + // just spawned here + ent->v.watertype = cont; + ent->v.waterlevel = 1; + return; + } + + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + if( ent->v.watertype == CONTENTS_EMPTY ) + { + // just crossed into water + SV_StartSound( ent, CHAN_AUTO, "player/pl_wade1.wav", 1.0f, ATTN_NORM, 0, 100 ); + ent->v.velocity[2] *= 0.5; + } + + ent->v.watertype = cont; + ent->v.waterlevel = 1; + + if( ent->v.absmin[2] != ent->v.absmax[2] ) + { + point[2] = (ent->v.absmin[2] + ent->v.absmax[2]) * 0.5f; + svs.groupmask = ent->v.groupinfo; + cont = SV_PointContents( point ); + + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + { + ent->v.waterlevel = 2; + VectorAdd( point, ent->v.view_ofs, point ); + svs.groupmask = ent->v.groupinfo; + cont = SV_PointContents( point ); + if( cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) + ent->v.waterlevel = 3; + } + } + else + { + // point entity + ent->v.waterlevel = 3; + } + } + else + { + if( ent->v.watertype != CONTENTS_EMPTY ) + { + // just crossed into water + SV_StartSound( ent, CHAN_AUTO, "player/pl_wade2.wav", 1.0f, ATTN_NORM, 0, 100 ); + } + ent->v.watertype = CONTENTS_EMPTY; + ent->v.waterlevel = 0; + } +} + +/* +============= +SV_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void SV_Physics_Toss( edict_t *ent ) +{ + trace_t trace; + vec3_t move; + float backoff; + edict_t *ground; + + SV_CheckWater( ent ); + + // regular thinking + if( !SV_RunThink( ent )) return; + + ground = ent->v.groundentity; + + if( ent->v.velocity[2] > 0 ) + ClearBits( ent->v.flags, FL_ONGROUND ); + + if( !SV_IsValidEdict( ground ) || FBitSet( ground->v.flags, FL_MONSTER|FL_CLIENT )) + ClearBits( ent->v.flags, FL_ONGROUND ); + + // if on ground and not moving, return. + if( FBitSet( ent->v.flags, FL_ONGROUND ) && VectorIsNull( ent->v.velocity )) + { + VectorClear( ent->v.avelocity ); + + if( VectorIsNull( ent->v.basevelocity )) + return; // at rest + } + + SV_CheckVelocity( ent ); + + // add gravity + switch( ent->v.movetype ) + { + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_BOUNCEMISSILE: + break; + default: + SV_AddGravity( ent ); + break; + } + + // move angles (with friction) + switch( ent->v.movetype ) + { + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + SV_AngularMove( ent, sv.frametime, ent->v.friction ); + break; + default: + SV_AngularMove( ent, sv.frametime, 0.0f ); + break; + } + + // move origin + // Base velocity is not properly accounted for since this entity will move again + // after the bounce without taking it into account + VectorAdd( ent->v.velocity, ent->v.basevelocity, ent->v.velocity ); + + SV_CheckVelocity( ent ); + VectorScale( ent->v.velocity, sv.frametime, move ); + + VectorSubtract( ent->v.velocity, ent->v.basevelocity, ent->v.velocity ); + + trace = SV_PushEntity( ent, move, vec3_origin, NULL, 0.0f ); + if( ent->free ) return; + + SV_CheckVelocity( ent ); + + if( trace.allsolid ) + { + // entity is trapped in another solid + VectorClear( ent->v.avelocity ); + VectorClear( ent->v.velocity ); + return; + } + + if( trace.fraction == 1.0f ) + { + SV_CheckWaterTransition( ent ); + return; + } + + if( ent->v.movetype == MOVETYPE_BOUNCE ) + backoff = 2.0f - ent->v.friction; + else if( ent->v.movetype == MOVETYPE_BOUNCEMISSILE ) + backoff = 2.0f; + else backoff = 1.0f; + + SV_ClipVelocity( ent->v.velocity, trace.plane.normal, ent->v.velocity, backoff ); + + // stop if on ground + if( trace.plane.normal[2] > 0.7f ) + { + float vel; + + VectorAdd( ent->v.velocity, ent->v.basevelocity, move ); + vel = DotProduct( move, move ); + + if( ent->v.velocity[2] < sv_gravity.value * sv.frametime ) + { + // we're rolling on the ground, add static friction. + ent->v.groundentity = trace.ent; + ent->v.flags |= FL_ONGROUND; + ent->v.velocity[2] = 0.0f; + } + + if( vel < 900.0f || ( ent->v.movetype != MOVETYPE_BOUNCE && ent->v.movetype != MOVETYPE_BOUNCEMISSILE )) + { + ent->v.flags |= FL_ONGROUND; + ent->v.groundentity = trace.ent; + VectorClear( ent->v.avelocity ); + VectorClear( ent->v.velocity ); + } + else + { + VectorScale( ent->v.velocity, (1.0f - trace.fraction) * sv.frametime * 0.9f, move ); + VectorMA( move, (1.0f - trace.fraction) * sv.frametime * 0.9f, ent->v.basevelocity, move ); + trace = SV_PushEntity( ent, move, vec3_origin, NULL, 0.0f ); + if( ent->free ) return; + } + } + + // check for in water + SV_CheckWaterTransition( ent ); +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ +/* +============= +SV_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +============= +*/ +void SV_Physics_Step( edict_t *ent ) +{ + qboolean inwater; + qboolean wasonground; + qboolean wasonmover; + vec3_t mins, maxs; + vec3_t point; + trace_t trace; + int x, y; + + SV_WaterMove( ent ); + SV_CheckVelocity( ent ); + + wasonground = (ent->v.flags & FL_ONGROUND); + wasonmover = SV_CheckMover( ent ); + inwater = SV_CheckWater( ent ); + + if( FBitSet( ent->v.flags, FL_FLOAT ) && ent->v.waterlevel > 0 ) + { + float buoyancy = SV_Submerged( ent ) * ent->v.skin * sv.frametime; + + SV_AddGravity( ent ); + ent->v.velocity[2] += buoyancy; + } + + if( !wasonground ) + { + if( !FBitSet( ent->v.flags, FL_FLY )) + { + if( !FBitSet( ent->v.flags, FL_SWIM ) || ( ent->v.waterlevel <= 0 )) + { + if( !inwater ) + SV_AddGravity( ent ); + } + } + } + + if( !VectorIsNull( ent->v.velocity ) || !VectorIsNull( ent->v.basevelocity )) + { + ent->v.flags &= ~FL_ONGROUND; + + if(( wasonground || wasonmover ) && ( ent->v.health > 0 || SV_CheckBottom( ent, MOVE_NORMAL ))) + { + float *vel = ent->v.velocity; + float control, speed, newspeed; + float friction; + + speed = sqrt(( vel[0] * vel[0] ) + ( vel[1] * vel[1] )); // DotProduct2D + + if( speed ) + { + friction = sv_friction.value * ent->v.friction; // factor + ent->v.friction = 1.0f; // g-cont. ??? + if( wasonmover ) friction *= 0.5f; // add a little friction + + control = (speed < sv_stopspeed.value) ? sv_stopspeed.value : speed; + newspeed = speed - (sv.frametime * control * friction); + if( newspeed < 0 ) newspeed = 0; + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + } + } + + VectorAdd( ent->v.velocity, ent->v.basevelocity, ent->v.velocity ); + SV_CheckVelocity( ent ); + + SV_FlyMove( ent, sv.frametime, NULL ); + if( ent->free ) return; + + SV_CheckVelocity( ent ); + VectorSubtract( ent->v.velocity, ent->v.basevelocity, ent->v.velocity ); + SV_CheckVelocity( ent ); + + VectorAdd( ent->v.origin, ent->v.mins, mins ); + VectorAdd( ent->v.origin, ent->v.maxs, maxs ); + + point[2] = mins[2] - 1.0f; + + for( x = 0; x <= 1; x++ ) + { + if( FBitSet( ent->v.flags, FL_ONGROUND )) + break; + + for( y = 0; y <= 1; y++ ) + { + point[0] = x ? maxs[0] : mins[0]; + point[1] = y ? maxs[1] : mins[1]; + + trace = SV_Move( point, vec3_origin, vec3_origin, point, MOVE_NORMAL, ent, false ); + + if( trace.startsolid ) + { + SetBits( ent->v.flags, FL_ONGROUND ); + ent->v.groundentity = trace.ent; + ent->v.friction = 1.0f; + break; + } + } + } + + SV_LinkEdict( ent, true ); + } + else + { + if( svgame.globals->force_retouch != 0 ) + { + qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL, ent, monsterClip ); + + // hentacle impact code + if(( trace.fraction < 1.0f || trace.startsolid ) && SV_IsValidEdict( trace.ent )) + { + SV_Impact( ent, trace.ent, &trace ); + if( ent->free ) return; + } + } + } + + if( !SV_RunThink( ent )) return; + SV_CheckWaterTransition( ent ); + +} + +/* +============= +SV_PhysicsNone + +Non moving objects can only think +============= +*/ +void SV_Physics_None( edict_t *ent ) +{ + SV_RunThink( ent ); +} + +//============================================================================ +static void SV_Physics_Entity( edict_t *ent ) +{ + // user dll can override movement type (Xash3D extension) + if( svgame.physFuncs.SV_PhysicsEntity && svgame.physFuncs.SV_PhysicsEntity( ent )) + return; // overrided + + SV_UpdateBaseVelocity( ent ); + + if( !FBitSet( ent->v.flags, FL_BASEVELOCITY ) && !VectorIsNull( ent->v.basevelocity )) + { + // Apply momentum (add in half of the previous frame of velocity first) + VectorMA( ent->v.velocity, 1.0f + (sv.frametime * 0.5f), ent->v.basevelocity, ent->v.velocity ); + VectorClear( ent->v.basevelocity ); + } + + ent->v.flags &= ~FL_BASEVELOCITY; + + if( svgame.globals->force_retouch != 0.0f ) + { + // force retouch even for stationary + SV_LinkEdict( ent, true ); + } + + switch( ent->v.movetype ) + { + case MOVETYPE_NONE: + SV_Physics_None( ent ); + break; + case MOVETYPE_NOCLIP: + SV_Physics_Noclip( ent ); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow( ent ); + break; + case MOVETYPE_COMPOUND: + SV_Physics_Compound( ent ); + break; + case MOVETYPE_STEP: + case MOVETYPE_PUSHSTEP: + SV_Physics_Step( ent ); + break; + case MOVETYPE_FLY: + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_BOUNCEMISSILE: + SV_Physics_Toss( ent ); + break; + case MOVETYPE_PUSH: + SV_Physics_Pusher( ent ); + break; + case MOVETYPE_WALK: + Host_Error( "SV_Physics: bad movetype %i\n", ent->v.movetype ); + break; + } + + // g-cont. don't alow free entities during loading because + // this produce a corrupted baselines + if( sv.state == ss_active && FBitSet( ent->v.flags, FL_KILLME )) + SV_FreeEdict( ent ); +} + +/* +================ +SV_Physics + +================ +*/ +void SV_Physics( void ) +{ + edict_t *ent; + int i; + + SV_CheckAllEnts (); + + svgame.globals->time = sv.time; + + // let the progs know that a new frame has started + svgame.dllFuncs.pfnStartFrame(); + + // treat each object in turn + for( i = 0; i < svgame.numEntities; i++ ) + { + ent = EDICT_NUM( i ); + + if( !SV_IsValidEdict( ent )) + continue; + + if( i > 0 && i <= svs.maxclients ) + continue; + + SV_Physics_Entity( ent ); + } + + if( svgame.globals->force_retouch != 0.0f ) + svgame.globals->force_retouch--; + + if( svgame.physFuncs.SV_EndFrame != NULL ) + svgame.physFuncs.SV_EndFrame(); + + // animate lightstyles (used for GetEntityIllum) + SV_RunLightStyles (); + + // increase framecount + sv.framecount++; + + // decrement svgame.numEntities if the highest number entities died + for( ; EDICT_NUM( svgame.numEntities - 1 )->free; svgame.numEntities-- ); +} + +/* +================ +SV_GetServerTime + +Inplementation for new physics interface +================ +*/ +double SV_GetServerTime( void ) +{ + return sv.time; +} + +/* +================ +SV_GetFrameTime + +Inplementation for new physics interface +================ +*/ +double SV_GetFrameTime( void ) +{ + return sv.frametime; +} + +/* +================ +SV_GetHeadNode + +Inplementation for new physics interface +================ +*/ +areanode_t *SV_GetHeadNode( void ) +{ + return sv_areanodes; +} + +/* +================ +SV_ServerState + +Inplementation for new physics interface +================ +*/ +int SV_ServerState( void ) +{ + return sv.state; +} + +/* +================ +SV_DrawDebugTriangles + +Called from renderer for debug purposes +================ +*/ +void SV_DrawDebugTriangles( void ) +{ + if( host.type != HOST_NORMAL ) + return; + + if( svgame.physFuncs.DrawNormalTriangles != NULL ) + { + // draw solid overlay + svgame.physFuncs.DrawNormalTriangles (); + } + + if( svgame.physFuncs.DrawDebugTriangles != NULL ) + { + // debug draws only + pglDisable( GL_BLEND ); + pglDepthMask( GL_FALSE ); + pglDisable( GL_TEXTURE_2D ); + + // draw wireframe overlay + svgame.physFuncs.DrawDebugTriangles (); + + pglEnable( GL_TEXTURE_2D ); + pglDepthMask( GL_TRUE ); + pglEnable( GL_BLEND ); + } +} + +/* +================ +SV_DrawOrthoTriangles + +Called from renderer for debug purposes +================ +*/ +void SV_DrawOrthoTriangles( void ) +{ + if( host.type != HOST_NORMAL ) + return; + + if( svgame.physFuncs.DrawOrthoTriangles != NULL ) + { + // draw solid overlay + svgame.physFuncs.DrawOrthoTriangles (); + } +} + +void SV_UpdateFogSettings( unsigned int packed_fog ) +{ + svgame.movevars.fog_settings = packed_fog; + host.movevars_changed = true; // force to transmit +} + +/* +========= +pfnGetFilesList + +========= +*/ +static char **pfnGetFilesList( const char *pattern, int *numFiles, int gamedironly ) +{ + static search_t *t = NULL; + + if( t ) Mem_Free( t ); // release prev search + + t = FS_Search( pattern, true, gamedironly ); + + if( !t ) + { + if( numFiles ) *numFiles = 0; + return NULL; + } + + if( numFiles ) *numFiles = t->numfilenames; + return t->filenames; +} + +static void *pfnMem_Alloc( size_t cb, const char *filename, const int fileline ) +{ + return _Mem_Alloc( svgame.mempool, cb, filename, fileline ); +} + +static void pfnMem_Free( void *mem, const char *filename, const int fileline ) +{ + if( !mem ) return; + _Mem_Free( mem, filename, fileline ); +} + +/* +============= +pfnPointContents + +============= +*/ +static int pfnPointContents( const float *pos, int groupmask ) +{ + int oldmask, cont; + + if( !pos ) return CONTENTS_NONE; + oldmask = svs.groupmask; + + svs.groupmask = groupmask; + cont = SV_PointContents( pos ); + svs.groupmask = oldmask; // restore old mask + + return cont; +} + +const byte *pfnLoadImagePixels( const char *filename, int *width, int *height ) +{ + rgbdata_t *pic = FS_LoadImage( filename, NULL, 0 ); + byte *buffer; + + if( !pic ) return NULL; + + buffer = Mem_Alloc( svgame.mempool, pic->size ); + if( buffer ) memcpy( buffer, pic->buffer, pic->size ); + if( width ) *width = pic->width; + if( height ) *height = pic->height; + FS_FreeImage( pic ); + + return buffer; +} + +const char* pfnGetModelName( int modelindex ) +{ + if( modelindex < 0 || modelindex >= MAX_MODELS ) + return NULL; + return sv.model_precache[modelindex]; +} + +static server_physics_api_t gPhysicsAPI = +{ + SV_LinkEdict, + SV_GetServerTime, + SV_GetFrameTime, + SV_ModelHandle, + SV_GetHeadNode, + SV_ServerState, + Host_Error, + &gTriApi, // ouch! + pfnDrawConsoleString, + pfnDrawSetTextColor, + pfnDrawConsoleStringLen, + Con_NPrintf, + Con_NXPrintf, + SV_GetLightStyle, + SV_UpdateFogSettings, + pfnGetFilesList, + SV_TraceSurface, + GL_TextureData, + pfnMem_Alloc, + pfnMem_Free, + pfnPointContents, + SV_MoveNormal, + SV_MoveNoEnts, + SV_BoxInPVS, + pfnWriteBytes, + Mod_CheckLump, + Mod_ReadLump, + Mod_SaveLump, + COM_SaveFile, + pfnLoadImagePixels, + pfnGetModelName, +}; + +/* +=============== +SV_InitPhysicsAPI + +Initialize server external physics +=============== +*/ +qboolean SV_InitPhysicsAPI( void ) +{ + static PHYSICAPI pPhysIface; + + pPhysIface = (PHYSICAPI)COM_GetProcAddress( svgame.hInstance, "Server_GetPhysicsInterface" ); + if( pPhysIface ) + { + if( pPhysIface( SV_PHYSICS_INTERFACE_VERSION, &gPhysicsAPI, &svgame.physFuncs )) + { + MsgDev( D_REPORT, "SV_LoadProgs: ^2initailized extended PhysicAPI ^7ver. %i\n", SV_PHYSICS_INTERFACE_VERSION ); + + if( svgame.physFuncs.SV_CheckFeatures != NULL ) + { + // grab common engine features (it will be shared across the network) + host.features = svgame.physFuncs.SV_CheckFeatures(); + Host_PrintEngineFeatures (); + } + return true; + } + + // make sure what physic functions is cleared + memset( &svgame.physFuncs, 0, sizeof( svgame.physFuncs )); + + return false; // just tell user about problems + } + + // physic interface is missed + return true; +} \ No newline at end of file diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c new file mode 100644 index 00000000..86db26b5 --- /dev/null +++ b/engine/server/sv_pmove.c @@ -0,0 +1,1126 @@ +/* +sv_pmove.c - server-side player physic +Copyright (C) 2010 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "const.h" +#include "pm_local.h" +#include "event_flags.h" +#include "studio.h" + +static qboolean has_update = false; + +void SV_ClearPhysEnts( void ) +{ + svgame.pmove->numtouch = 0; + svgame.pmove->numvisent = 0; + svgame.pmove->nummoveent = 0; + svgame.pmove->numphysent = 0; +} + +qboolean SV_PlayerIsFrozen( edict_t *pClient ) +{ + if( FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + return false; + + if( FBitSet( pClient->v.flags, FL_FROZEN )) + return true; + return false; +} + +void SV_ClipPMoveToEntity( physent_t *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, pmtrace_t *tr ) +{ + Assert( tr != NULL ); + + if( svgame.physFuncs.ClipPMoveToEntity != NULL ) + { + // do custom sweep test + svgame.physFuncs.ClipPMoveToEntity( pe, start, mins, maxs, end, tr ); + } + else + { + // function is missed, so we didn't hit anything + tr->allsolid = false; + } +} + +qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) +{ + model_t *mod = SV_ModelHandle( ed->v.modelindex ); + + if( !mod ) return false; + pe->player = false; + + pe->info = NUM_FOR_EDICT( ed ); + VectorCopy( ed->v.origin, pe->origin ); + VectorCopy( ed->v.angles, pe->angles ); + + if( ed->v.flags & FL_CLIENT ) + { + // client + SV_GetTrueOrigin( &svs.clients[pe->info - 1], (pe->info - 1), pe->origin ); + Q_strncpy( pe->name, "player", sizeof( pe->name )); + pe->player = pe->info; + } + else if( ed->v.flags & FL_FAKECLIENT && ed->v.solid != MOVETYPE_PUSH ) + { + // bot + Q_strncpy( pe->name, "bot", sizeof( pe->name )); + pe->player = pe->info; + } + else + { + // otherwise copy the classname + Q_strncpy( pe->name, STRING( ed->v.classname ), sizeof( pe->name )); + } + + pe->model = pe->studiomodel = NULL; + + switch( ed->v.solid ) + { + case SOLID_NOT: + case SOLID_BSP: + pe->model = mod; + VectorClear( pe->mins ); + VectorClear( pe->maxs ); + break; + case SOLID_BBOX: + if( mod && mod->type == mod_studio && mod->flags & STUDIO_TRACE_HITBOX ) + pe->studiomodel = mod; + VectorCopy( ed->v.mins, pe->mins ); + VectorCopy( ed->v.maxs, pe->maxs ); + break; + case SOLID_CUSTOM: + pe->model = (mod->type == mod_brush) ? mod : NULL; + pe->studiomodel = (mod->type == mod_studio) ? mod : NULL; + VectorCopy( ed->v.mins, pe->mins ); + VectorCopy( ed->v.maxs, pe->maxs ); + break; + default: + pe->studiomodel = (mod->type == mod_studio) ? mod : NULL; + VectorCopy( ed->v.mins, pe->mins ); + VectorCopy( ed->v.maxs, pe->maxs ); + break; + } + + pe->solid = ed->v.solid; + pe->rendermode = ed->v.rendermode; + pe->skin = ed->v.skin; + pe->frame = ed->v.frame; + pe->sequence = ed->v.sequence; + + memcpy( &pe->controller[0], &ed->v.controller[0], 4 * sizeof( byte )); + memcpy( &pe->blending[0], &ed->v.blending[0], 2 * sizeof( byte )); + + pe->movetype = ed->v.movetype; + pe->takedamage = ed->v.takedamage; + pe->team = ed->v.team; + pe->classnumber = ed->v.playerclass; + pe->blooddecal = 0; // unused in GoldSrc + + // for mods + pe->iuser1 = ed->v.iuser1; + pe->iuser2 = ed->v.iuser2; + pe->iuser3 = ed->v.iuser3; + pe->iuser4 = ed->v.iuser4; + pe->fuser1 = ed->v.fuser1; + pe->fuser2 = ed->v.fuser2; + pe->fuser3 = ed->v.fuser3; + pe->fuser4 = ed->v.fuser4; + + VectorCopy( ed->v.vuser1, pe->vuser1 ); + VectorCopy( ed->v.vuser2, pe->vuser2 ); + VectorCopy( ed->v.vuser3, pe->vuser3 ); + VectorCopy( ed->v.vuser4, pe->vuser4 ); + + return true; +} + +void SV_GetTrueOrigin( sv_client_t *cl, int edictnum, vec3_t origin ) +{ + // don't allow unlag in singleplayer + if( svs.maxclients <= 1 ) return; + + if( cl->state < cs_connected || edictnum < 0 || edictnum >= svs.maxclients ) + return; + + if( !FBitSet( cl->flags, FCL_LAG_COMPENSATION ) || !sv_unlag.value ) + return; + + if( !svgame.interp[edictnum].active || !svgame.interp[edictnum].moving ) + return; + + VectorCopy( svgame.interp[edictnum].newpos, origin ); +} + +void SV_GetTrueMinMax( sv_client_t *cl, int edictnum, vec3_t mins, vec3_t maxs ) +{ + // don't allow unlag in singleplayer + if( svs.maxclients <= 1 ) return; + + if( cl->state < cs_connected || edictnum < 0 || edictnum >= svs.maxclients ) + return; + + if( !FBitSet( cl->flags, FCL_LAG_COMPENSATION ) || !sv_unlag.value ) + return; + + if( !svgame.interp[edictnum].active || !svgame.interp[edictnum].moving ) + return; + + VectorCopy( svgame.interp[edictnum].mins, mins ); + VectorCopy( svgame.interp[edictnum].maxs, maxs ); +} + +/* +==================== +SV_AddLinksToPmove + +collect solid entities +==================== +*/ +void SV_AddLinksToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) +{ + link_t *l, *next; + edict_t *check, *pl; + vec3_t mins, maxs; + physent_t *pe; + + pl = EDICT_NUM( svgame.pmove->player_index + 1 ); + Assert( SV_IsValidEdict( pl )); + + // touch linked edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + check = EDICT_FROM_AREA( l ); + + if( check->v.groupinfo != 0 ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( check->v.groupinfo, pl->v.groupinfo )) + continue; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( check->v.groupinfo, pl->v.groupinfo )) + continue; + } + + if( check->v.owner == pl || check->v.solid == SOLID_TRIGGER ) + continue; // player or player's own missile + + if( svgame.pmove->numvisent < MAX_PHYSENTS ) + { + pe = &svgame.pmove->visents[svgame.pmove->numvisent]; + if( SV_CopyEdictToPhysEnt( pe, check )) + svgame.pmove->numvisent++; + } + + if( check->v.solid == SOLID_NOT && ( check->v.skin == CONTENTS_NONE || check->v.modelindex == 0 )) + continue; + + // ignore monsterclip brushes + if( FBitSet( check->v.flags, FL_MONSTERCLIP ) && check->v.solid == SOLID_BSP ) + continue; + + if( check == pl ) continue; // himself + + // nehahra collision flags + if( check->v.movetype != MOVETYPE_PUSH ) + { + if(( FBitSet( check->v.flags, FL_CLIENT|FL_FAKECLIENT ) && check->v.health <= 0.0f ) || check->v.deadflag == DEAD_DEAD ) + continue; // dead body + } + + if( VectorIsNull( check->v.size )) + continue; + + VectorCopy( check->v.absmin, mins ); + VectorCopy( check->v.absmax, maxs ); + + if( FBitSet( check->v.flags, FL_CLIENT )) + { + int e = NUM_FOR_EDICT( check ) - 1; + + // trying to get interpolated values + SV_GetTrueMinMax( &svs.clients[e], e, mins, maxs ); + } + + if( !BoundsIntersect( pmove_mins, pmove_maxs, mins, maxs )) + continue; + + if( svgame.pmove->numphysent < MAX_PHYSENTS ) + { + pe = &svgame.pmove->physents[svgame.pmove->numphysent]; + + if( SV_CopyEdictToPhysEnt( pe, check )) + svgame.pmove->numphysent++; + } + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( pmove_maxs[node->axis] > node->dist ) + SV_AddLinksToPmove( node->children[0], pmove_mins, pmove_maxs ); + if( pmove_mins[node->axis] < node->dist ) + SV_AddLinksToPmove( node->children[1], pmove_mins, pmove_maxs ); +} + +/* +==================== +SV_AddLaddersToPmove +==================== +*/ +void SV_AddLaddersToPmove( areanode_t *node, const vec3_t pmove_mins, const vec3_t pmove_maxs ) +{ + link_t *l, *next; + edict_t *check; + model_t *mod; + physent_t *pe; + + // get water edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + check = EDICT_FROM_AREA( l ); + + if( check->v.solid != SOLID_NOT || check->v.skin != CONTENTS_LADDER ) + continue; + + mod = SV_ModelHandle( check->v.modelindex ); + + // only brushes can have special contents + if( !mod || mod->type != mod_brush ) + continue; + + if( !BoundsIntersect( pmove_mins, pmove_maxs, check->v.absmin, check->v.absmax )) + continue; + + if( svgame.pmove->nummoveent == MAX_MOVEENTS ) + return; + + pe = &svgame.pmove->moveents[svgame.pmove->nummoveent]; + if( SV_CopyEdictToPhysEnt( pe, check )) + svgame.pmove->nummoveent++; + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( pmove_maxs[node->axis] > node->dist ) + SV_AddLaddersToPmove( node->children[0], pmove_mins, pmove_maxs ); + if( pmove_mins[node->axis] < node->dist ) + SV_AddLaddersToPmove( node->children[1], pmove_mins, pmove_maxs ); +} + +static int pfnTestPlayerPosition( float *pos, pmtrace_t *ptrace ) +{ + return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, NULL ); +} + +static void pfnStuckTouch( int hitent, pmtrace_t *tr ) +{ + int i; + + for( i = 0; i < svgame.pmove->numtouch; i++ ) + { + if( svgame.pmove->touchindex[i].ent == hitent ) + return; + } + + if( svgame.pmove->numtouch >= MAX_PHYSENTS ) + { + MsgDev( D_ERROR, "PM_StuckTouch: MAX_TOUCHENTS limit exceeded\n" ); + return; + } + + VectorCopy( svgame.pmove->velocity, tr->deltavelocity ); + tr->ent = hitent; + + svgame.pmove->touchindex[svgame.pmove->numtouch++] = *tr; +} + +static int pfnPointContents( float *p, int *truecontents ) +{ + int cont, truecont; + + truecont = cont = SV_TruePointContents( p ); + if( truecontents ) *truecontents = truecont; + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +static int pfnTruePointContents( float *p ) +{ + return SV_TruePointContents( p ); +} + +static int pfnHullPointContents( struct hull_s *hull, int num, float *p ) +{ + return PM_HullPointContents( hull, num, p ); +} + +static pmtrace_t pfnPlayerTrace( float *start, float *end, int traceFlags, int ignore_pe ) +{ + return PM_PlayerTraceExt( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL ); +} + +static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = svgame.pmove->usehull; + svgame.pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, ignore_pe, NULL ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, ignore_pe, NULL ); + break; + } + + svgame.pmove->usehull = old_usehull; + + return &tr; +} + +static hull_t *pfnHullForBsp( physent_t *pe, float *offset ) +{ + return PM_HullForBsp( pe, svgame.pmove, offset ); +} + +static float pfnTraceModel( physent_t *pe, float *start, float *end, trace_t *trace ) +{ + int old_usehull; + vec3_t start_l, end_l; + vec3_t offset, temp; + qboolean rotated; + matrix4x4 matrix; + hull_t *hull; + + old_usehull = svgame.pmove->usehull; + svgame.pmove->usehull = 2; + + hull = PM_HullForBsp( pe, svgame.pmove, offset ); + + svgame.pmove->usehull = old_usehull; + + if( pe->solid == SOLID_BSP && !VectorIsNull( pe->angles )) + rotated = true; + else rotated = false; + + if( rotated ) + { + Matrix4x4_CreateFromEntity( matrix, pe->angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + else + { + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + } + + PM_RecursiveHullCheck( hull, hull->firstclipnode, 0, 1, start_l, end_l, (pmtrace_t *)trace ); + trace->ent = NULL; + + if( rotated ) + { + VectorCopy( trace->plane.normal, temp ); + Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); + } + + VectorLerp( start, trace->fraction, end, trace->endpos ); + + return trace->fraction; +} + +static const char *pfnTraceTexture( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= svgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &svgame.pmove->physents[ground]; + return PM_TraceTexture( pe, vstart, vend ); +} + +static void pfnPlaySound( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ) +{ + edict_t *ent; + + ent = EDICT_NUM( svgame.pmove->player_index + 1 ); + if( !SV_IsValidEdict( ent )) return; + + SV_StartSound( ent, channel, sample, volume, attenuation, fFlags|SND_FILTER_CLIENT, pitch ); +} + +static void pfnPlaybackEventFull( int flags, int clientindex, word eventindex, float delay, float *origin, + float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ) +{ + edict_t *ent; + + ent = EDICT_NUM( clientindex + 1 ); + if( !SV_IsValidEdict( ent )) return; + + if( host.type == HOST_DEDICATED ) + flags |= FEV_NOTHOST; // no local clients for dedicated server + + SV_PlaybackEventFull( flags, ent, eventindex, + delay, origin, angles, + fparam1, fparam2, + iparam1, iparam2, + bparam1, bparam2 ); +} + +static pmtrace_t pfnPlayerTraceEx( float *start, float *end, int traceFlags, pfnIgnore pmFilter ) +{ + return PM_PlayerTraceExt( svgame.pmove, start, end, traceFlags, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter ); +} + +static int pfnTestPlayerPositionEx( float *pos, pmtrace_t *ptrace, pfnIgnore pmFilter ) +{ + return PM_TestPlayerPosition( svgame.pmove, pos, ptrace, pmFilter ); +} + +static pmtrace_t *pfnTraceLineEx( float *start, float *end, int flags, int usehull, pfnIgnore pmFilter ) +{ + static pmtrace_t tr; + int old_usehull; + + old_usehull = svgame.pmove->usehull; + svgame.pmove->usehull = usehull; + + switch( flags ) + { + case PM_TRACELINE_PHYSENTSONLY: + tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numphysent, svgame.pmove->physents, -1, pmFilter ); + break; + case PM_TRACELINE_ANYVISIBLE: + tr = PM_PlayerTraceExt( svgame.pmove, start, end, 0, svgame.pmove->numvisent, svgame.pmove->visents, -1, pmFilter ); + break; + } + + svgame.pmove->usehull = old_usehull; + + return &tr; +} + +static struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend ) +{ + physent_t *pe; + + if( ground < 0 || ground >= svgame.pmove->numphysent ) + return NULL; // bad ground + + pe = &svgame.pmove->physents[ground]; + return PM_TraceSurface( pe, vstart, vend ); +} + +/* +=============== +SV_InitClientMove + +=============== +*/ +void SV_InitClientMove( void ) +{ + int i; + + Pmove_Init (); + + svgame.pmove->server = true; + svgame.pmove->movevars = &svgame.movevars; + svgame.pmove->runfuncs = false; + + // enumerate client hulls + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + if( svgame.dllFuncs.pfnGetHullBounds( i, host.player_mins[i], host.player_maxs[i] )) + MsgDev( D_NOTE, "SV: hull%i, player_mins: %g %g %g, player_maxs: %g %g %g\n", i, + host.player_mins[i][0], host.player_mins[i][1], host.player_mins[i][2], + host.player_maxs[i][0], host.player_maxs[i][1], host.player_maxs[i][2] ); + } + + memcpy( svgame.pmove->player_mins, host.player_mins, sizeof( host.player_mins )); + memcpy( svgame.pmove->player_maxs, host.player_maxs, sizeof( host.player_maxs )); + + // common utilities + svgame.pmove->PM_Info_ValueForKey = Info_ValueForKey; + svgame.pmove->PM_Particle = CL_Particle; // for local system only + svgame.pmove->PM_TestPlayerPosition = pfnTestPlayerPosition; + svgame.pmove->Con_NPrintf = Con_NPrintf; + svgame.pmove->Con_DPrintf = Con_DPrintf; + svgame.pmove->Con_Printf = Con_Printf; + svgame.pmove->Sys_FloatTime = Sys_DoubleTime; + svgame.pmove->PM_StuckTouch = pfnStuckTouch; + svgame.pmove->PM_PointContents = pfnPointContents; + svgame.pmove->PM_TruePointContents = pfnTruePointContents; + svgame.pmove->PM_HullPointContents = pfnHullPointContents; + svgame.pmove->PM_PlayerTrace = pfnPlayerTrace; + svgame.pmove->PM_TraceLine = pfnTraceLine; + svgame.pmove->RandomLong = COM_RandomLong; + svgame.pmove->RandomFloat = COM_RandomFloat; + svgame.pmove->PM_GetModelType = pfnGetModelType; + svgame.pmove->PM_GetModelBounds = pfnGetModelBounds; + svgame.pmove->PM_HullForBsp = pfnHullForBsp; + svgame.pmove->PM_TraceModel = pfnTraceModel; + svgame.pmove->COM_FileSize = COM_FileSize; + svgame.pmove->COM_LoadFile = COM_LoadFile; + svgame.pmove->COM_FreeFile = COM_FreeFile; + svgame.pmove->memfgets = COM_MemFgets; + svgame.pmove->PM_PlaySound = pfnPlaySound; + svgame.pmove->PM_TraceTexture = pfnTraceTexture; + svgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull; + svgame.pmove->PM_PlayerTraceEx = pfnPlayerTraceEx; + svgame.pmove->PM_TestPlayerPositionEx = pfnTestPlayerPositionEx; + svgame.pmove->PM_TraceLineEx = pfnTraceLineEx; + svgame.pmove->PM_TraceSurface = pfnTraceSurface; + + // initalize pmove + svgame.dllFuncs.pfnPM_Init( svgame.pmove ); +} + +static void PM_CheckMovingGround( edict_t *ent, float frametime ) +{ + if( svgame.physFuncs.SV_UpdatePlayerBaseVelocity != NULL ) + { + svgame.physFuncs.SV_UpdatePlayerBaseVelocity( ent ); + } + else + { + SV_UpdateBaseVelocity( ent ); + } + + if( !FBitSet( ent->v.flags, FL_BASEVELOCITY )) + { + // apply momentum (add in half of the previous frame of velocity first) + VectorMA( ent->v.velocity, 1.0f + (frametime * 0.5f), ent->v.basevelocity, ent->v.velocity ); + VectorClear( ent->v.basevelocity ); + } + + ClearBits( ent->v.flags, FL_BASEVELOCITY ); +} + +static void SV_SetupPMove( playermove_t *pmove, sv_client_t *cl, usercmd_t *ucmd, const char *physinfo ) +{ + vec3_t absmin, absmax; + edict_t *clent = cl->edict; + int i; + + svgame.globals->frametime = (ucmd->msec * 0.001f); + + pmove->player_index = NUM_FOR_EDICT( clent ) - 1; + pmove->multiplayer = (svs.maxclients > 1) ? true : false; + pmove->time = (float)(cl->timebase * 1000.0); + VectorCopy( clent->v.origin, pmove->origin ); + VectorCopy( clent->v.v_angle, pmove->angles ); + VectorCopy( clent->v.v_angle, pmove->oldangles ); + VectorCopy( clent->v.velocity, pmove->velocity ); + VectorCopy( clent->v.basevelocity, pmove->basevelocity ); + VectorCopy( clent->v.view_ofs, pmove->view_ofs ); + VectorCopy( clent->v.movedir, pmove->movedir ); + pmove->flDuckTime = clent->v.flDuckTime; + pmove->bInDuck = clent->v.bInDuck; + pmove->usehull = (clent->v.flags & FL_DUCKING) ? 1 : 0; // reset hull + pmove->flTimeStepSound = clent->v.flTimeStepSound; + pmove->iStepLeft = clent->v.iStepLeft; + pmove->flFallVelocity = clent->v.flFallVelocity; + pmove->flSwimTime = clent->v.flSwimTime; + VectorCopy( clent->v.punchangle, pmove->punchangle ); + pmove->flSwimTime = clent->v.flSwimTime; + pmove->flNextPrimaryAttack = 0.0f; // not used by PM_ code + pmove->effects = clent->v.effects; + pmove->flags = clent->v.flags; + pmove->gravity = clent->v.gravity; + pmove->friction = clent->v.friction; + pmove->oldbuttons = clent->v.oldbuttons; + pmove->waterjumptime = clent->v.teleport_time; + pmove->dead = (clent->v.health <= 0.0f ) ? true : false; + pmove->deadflag = clent->v.deadflag; + pmove->spectator = 0; // spectator physic all execute on client + pmove->movetype = clent->v.movetype; + if( pmove->multiplayer ) pmove->onground = -1; + pmove->waterlevel = clent->v.waterlevel; + pmove->watertype = clent->v.watertype; + pmove->maxspeed = svgame.movevars.maxspeed; + pmove->clientmaxspeed = clent->v.maxspeed; + pmove->iuser1 = clent->v.iuser1; + pmove->iuser2 = clent->v.iuser2; + pmove->iuser3 = clent->v.iuser3; + pmove->iuser4 = clent->v.iuser4; + pmove->fuser1 = clent->v.fuser1; + pmove->fuser2 = clent->v.fuser2; + pmove->fuser3 = clent->v.fuser3; + pmove->fuser4 = clent->v.fuser4; + VectorCopy( clent->v.vuser1, pmove->vuser1 ); + VectorCopy( clent->v.vuser2, pmove->vuser2 ); + VectorCopy( clent->v.vuser3, pmove->vuser3 ); + VectorCopy( clent->v.vuser4, pmove->vuser4 ); + pmove->cmd = *ucmd; // setup current cmds + pmove->runfuncs = true; + + Q_strncpy( pmove->physinfo, physinfo, MAX_INFO_STRING ); + + // setup physents + pmove->numvisent = 0; + pmove->numphysent = 0; + pmove->nummoveent = 0; + + for( i = 0; i < 3; i++ ) + { + absmin[i] = clent->v.origin[i] - 256.0f; + absmax[i] = clent->v.origin[i] + 256.0f; + } + + SV_CopyEdictToPhysEnt( &svgame.pmove->physents[0], &svgame.edicts[0] ); + svgame.pmove->visents[0] = svgame.pmove->physents[0]; + svgame.pmove->numphysent = 1; // always have world + svgame.pmove->numvisent = 1; + + SV_AddLinksToPmove( sv_areanodes, absmin, absmax ); + SV_AddLaddersToPmove( sv_areanodes, absmin, absmax ); +} + +static void SV_FinishPMove( playermove_t *pmove, sv_client_t *cl ) +{ + edict_t *clent = cl->edict; + + clent->v.teleport_time = pmove->waterjumptime; + VectorCopy( pmove->origin, clent->v.origin ); + VectorCopy( pmove->view_ofs, clent->v.view_ofs ); + VectorCopy( pmove->velocity, clent->v.velocity ); + VectorCopy( pmove->basevelocity, clent->v.basevelocity ); + VectorCopy( pmove->punchangle, clent->v.punchangle ); + VectorCopy( pmove->movedir, clent->v.movedir ); + clent->v.flTimeStepSound = pmove->flTimeStepSound; + clent->v.flFallVelocity = pmove->flFallVelocity; + clent->v.oldbuttons = pmove->oldbuttons; + clent->v.waterlevel = pmove->waterlevel; + clent->v.watertype = pmove->watertype; + clent->v.maxspeed = pmove->clientmaxspeed; + clent->v.flDuckTime = pmove->flDuckTime; + clent->v.flSwimTime = pmove->flSwimTime; + clent->v.iStepLeft = pmove->iStepLeft; + clent->v.movetype = pmove->movetype; + clent->v.friction = pmove->friction; + clent->v.deadflag = pmove->deadflag; + clent->v.effects = pmove->effects; + clent->v.bInDuck = pmove->bInDuck; + clent->v.flags = pmove->flags; + + // copy back user variables + clent->v.iuser1 = pmove->iuser1; + clent->v.iuser2 = pmove->iuser2; + clent->v.iuser3 = pmove->iuser3; + clent->v.iuser4 = pmove->iuser4; + clent->v.fuser1 = pmove->fuser1; + clent->v.fuser2 = pmove->fuser2; + clent->v.fuser3 = pmove->fuser3; + clent->v.fuser4 = pmove->fuser4; + VectorCopy( pmove->vuser1, clent->v.vuser1 ); + VectorCopy( pmove->vuser2, clent->v.vuser2 ); + VectorCopy( pmove->vuser3, clent->v.vuser3 ); + VectorCopy( pmove->vuser4, clent->v.vuser4 ); + + if( pmove->onground == -1 ) + { + clent->v.flags &= ~FL_ONGROUND; + } + else if( pmove->onground >= 0 && pmove->onground < pmove->numphysent ) + { + clent->v.flags |= FL_ONGROUND; + clent->v.groundentity = EDICT_NUM( pmove->physents[pmove->onground].info ); + } + + // angles + // show 1/3 the pitch angle and all the roll angle + if( !clent->v.fixangle ) + { + VectorCopy( pmove->angles, clent->v.v_angle ); + clent->v.angles[PITCH] = -( clent->v.v_angle[PITCH] / 3.0f ); + clent->v.angles[ROLL] = clent->v.v_angle[ROLL]; + clent->v.angles[YAW] = clent->v.v_angle[YAW]; + } + + SV_SetMinMaxSize( clent, pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], false ); + + // all next calls ignore footstep sounds + pmove->runfuncs = false; +} + +entity_state_t *SV_FindEntInPack( int index, client_frame_t *frame ) +{ + entity_state_t *state; + int i; + + for( i = 0; i < frame->num_entities; i++ ) + { + state = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities]; + + if( state->number == index ) + return state; + } + return NULL; +} + +qboolean SV_UnlagCheckTeleport( vec3_t old_pos, vec3_t new_pos ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( fabs( old_pos[i] - new_pos[i] ) > 64.0f ) + return true; + } + return false; +} + +void SV_SetupMoveInterpolant( sv_client_t *cl ) +{ + int i, j, clientnum; + float finalpush, lerp_msec; + float latency, lerpFrac; + client_frame_t *frame, *frame2; + entity_state_t *state, *lerpstate; + vec3_t curpos, newpos; + sv_client_t *check; + sv_interp_t *lerp; + + memset( svgame.interp, 0, sizeof( svgame.interp )); + has_update = false; + + // don't allow unlag in singleplayer + if( svs.maxclients <= 1 || cl->state != cs_spawned ) + return; + + // unlag disabled by game request + if( !svgame.dllFuncs.pfnAllowLagCompensation() || !sv_unlag.value ) + return; + + // unlag disabled for current client + if( !FBitSet( cl->flags, FCL_LAG_COMPENSATION )) + return; + + has_update = true; + + for( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ ) + { + if( check->state != cs_spawned || check == cl ) + continue; + + lerp = &svgame.interp[i]; + + VectorCopy( check->edict->v.origin, lerp->oldpos ); + VectorCopy( check->edict->v.absmin, lerp->mins ); + VectorCopy( check->edict->v.absmax, lerp->maxs ); + lerp->active = true; + } + + if( cl->latency > 1.5f ) + latency = 1.5f; + else latency = cl->latency; + + if( sv_maxunlag.value != 0.0f ) + { + if (sv_maxunlag.value < 0.0f ) + Cvar_SetValue( "sv_maxunlag", 0.0f ); + + if( latency >= sv_maxunlag.value ) + latency = sv_maxunlag.value; + } + + lerp_msec = cl->lastcmd.lerp_msec * 0.001f; + if( lerp_msec > 0.1f ) lerp_msec = 0.1f; + + if( lerp_msec < cl->cl_updaterate ) + lerp_msec = cl->cl_updaterate; + + finalpush = ( host.realtime - latency - lerp_msec ) + sv_unlagpush.value; + if( finalpush > host.realtime ) finalpush = host.realtime; // pushed too much ? + + frame = NULL; + + for( frame2 = NULL, i = 0; i < SV_UPDATE_BACKUP; i++, frame2 = frame ) + { + frame = &cl->frames[(cl->netchan.outgoing_sequence - (i + 1)) & SV_UPDATE_MASK]; + + for( j = 0; j < frame->num_entities; j++ ) + { + state = &svs.packet_entities[(frame->first_entity+j)%svs.num_client_entities]; + + if( state->number <= 0 || state->number >= svs.maxclients ) + continue; + + lerp = &svgame.interp[state->number-1]; + if( lerp->nointerp ) continue; + + if( state->health <= 0 || ( state->effects & EF_NOINTERP )) + lerp->nointerp = true; + + if( !lerp->firstframe ) + lerp->firstframe = true; + else if( SV_UnlagCheckTeleport( state->origin, lerp->finalpos )) + lerp->nointerp = true; + + VectorCopy( state->origin, lerp->finalpos ); + } + + if( finalpush > frame->senttime ) + break; + } + + if( i == SV_UPDATE_BACKUP || finalpush - frame->senttime > 1.0 ) + { + memset( svgame.interp, 0, sizeof( svgame.interp )); + has_update = false; + return; + } + + if( !frame2 ) + { + frame2 = frame; + lerpFrac = 0; + } + else + { + if( frame2->senttime - frame->senttime == 0.0 ) + { + lerpFrac = 0; + } + else + { + lerpFrac = (finalpush - frame->senttime) / (frame2->senttime - frame->senttime); + lerpFrac = bound( 0.0f, lerpFrac, 1.0f ); + } + } + + for( i = 0; i < frame->num_entities; i++ ) + { + state = &svs.packet_entities[(frame->first_entity+i)%svs.num_client_entities]; + + if( state->number <= 0 || state->number >= svs.maxclients ) + continue; + + clientnum = state->number - 1; + check = &svs.clients[clientnum]; + + if( check->state != cs_spawned || check == cl ) + continue; + + lerp = &svgame.interp[clientnum]; + + if( !lerp->active || lerp->nointerp ) + continue; + + lerpstate = SV_FindEntInPack( state->number, frame2 ); + + if( !lerpstate ) + { + VectorCopy( state->origin, curpos ); + } + else + { + VectorSubtract( lerpstate->origin, state->origin, newpos ); + VectorMA( state->origin, lerpFrac, newpos, curpos ); + } + + VectorCopy( curpos, lerp->curpos ); + VectorCopy( curpos, lerp->newpos ); + + if( !VectorCompare( curpos, check->edict->v.origin )) + { + VectorCopy( curpos, check->edict->v.origin ); + SV_LinkEdict( check->edict, false ); + lerp->moving = true; + } + } +} + +void SV_RestoreMoveInterpolant( sv_client_t *cl ) +{ + sv_client_t *check; + sv_interp_t *oldlerp; + int i; + + if( !has_update ) + { + has_update = true; + return; + } + + // don't allow unlag in singleplayer + if( svs.maxclients <= 1 || cl->state != cs_spawned ) + return; + + // unlag disabled by game request + if( !svgame.dllFuncs.pfnAllowLagCompensation() || !sv_unlag.value ) + return; + + // unlag disabled for current client + if( !FBitSet( cl->flags, FCL_LAG_COMPENSATION )) + return; + + for( i = 0, check = svs.clients; i < svs.maxclients; i++, check++ ) + { + if( check->state != cs_spawned || check == cl ) + continue; + + oldlerp = &svgame.interp[i]; + + if( VectorCompare( oldlerp->oldpos, oldlerp->newpos )) + continue; // they didn't actually move. + + if( !oldlerp->moving || !oldlerp->active ) + return; + + if( VectorCompare( oldlerp->curpos, check->edict->v.origin )) + { + VectorCopy( oldlerp->oldpos, check->edict->v.origin ); + SV_LinkEdict( check->edict, false ); + } + } +} + +/* +=========== +SV_RunCmd +=========== +*/ +void SV_RunCmd( sv_client_t *cl, usercmd_t *ucmd, int random_seed ) +{ + edict_t *clent, *touch; + double frametime; + int i, oldmsec; + pmtrace_t *pmtrace; + trace_t trace; + vec3_t oldvel; + usercmd_t cmd; + + clent = cl->edict; + cmd = *ucmd; + + if( cl->ignorecmdtime > host.realtime ) + { + cl->cmdtime += ((double)ucmd->msec / 1000.0 ); + return; + } + + cl->ignorecmdtime = 0.0; + + // chop up very long commands + if( cmd.msec > 50 ) + { + oldmsec = ucmd->msec; + cmd.msec = oldmsec / 2; + SV_RunCmd( cl, &cmd, random_seed ); + cmd.msec = oldmsec / 2; + cmd.impulse = 0; + SV_RunCmd( cl, &cmd, random_seed ); + return; + } + + if( !FBitSet( cl->flags, FCL_FAKECLIENT )) + { + SV_SetupMoveInterpolant( cl ); + } + + svgame.dllFuncs.pfnCmdStart( cl->edict, ucmd, random_seed ); + + frametime = ((double)ucmd->msec / 1000.0 ); + cl->timebase += frametime; + cl->cmdtime += frametime; + + PM_CheckMovingGround( clent, frametime ); + + VectorCopy( clent->v.v_angle, svgame.pmove->oldangles ); // save oldangles + if( !clent->v.fixangle ) VectorCopy( ucmd->viewangles, clent->v.v_angle ); + + VectorClear( clent->v.clbasevelocity ); + + // copy player buttons + clent->v.button = ucmd->buttons; + clent->v.light_level = ucmd->lightlevel; + if( ucmd->impulse ) clent->v.impulse = ucmd->impulse; + + if( ucmd->impulse == 204 ) + { + // force client.dll update + SV_RefreshUserinfo(); + } + + svgame.globals->time = cl->timebase; + svgame.dllFuncs.pfnPlayerPreThink( clent ); + SV_PlayerRunThink( clent, frametime, cl->timebase ); + + // If conveyor, or think, set basevelocity, then send to client asap too. + if( !VectorIsNull( clent->v.basevelocity )) + VectorCopy( clent->v.basevelocity, clent->v.clbasevelocity ); + + // setup playermove state + SV_SetupPMove( svgame.pmove, cl, ucmd, cl->physinfo ); + + // motor! + svgame.dllFuncs.pfnPM_Move( svgame.pmove, true ); + + // copy results back to client + SV_FinishPMove( svgame.pmove, cl ); + + if( clent->v.solid != SOLID_NOT && !sv.playersonly ) + { + if( svgame.physFuncs.PM_PlayerTouch != NULL ) + { + // run custom impact function + svgame.physFuncs.PM_PlayerTouch( svgame.pmove, clent ); + } + else + { + // link into place and touch triggers + SV_LinkEdict( clent, true ); + VectorCopy( clent->v.velocity, oldvel ); // save velocity + + // touch other objects + for( i = 0; i < svgame.pmove->numtouch; i++ ) + { + pmtrace = &svgame.pmove->touchindex[i]; + touch = EDICT_NUM( svgame.pmove->physents[pmtrace->ent].info ); + VectorCopy( pmtrace->deltavelocity, clent->v.velocity ); + PM_ConvertTrace( &trace, pmtrace, touch ); + SV_Impact( touch, clent, &trace ); + } + + // restore velocity + VectorCopy( oldvel, clent->v.velocity ); + } + } + + svgame.pmove->numtouch = 0; + svgame.globals->time = cl->timebase; + svgame.globals->frametime = frametime; + + // run post-think + svgame.dllFuncs.pfnPlayerPostThink( clent ); + svgame.dllFuncs.pfnCmdEnd( clent ); + + if( !FBitSet( cl->flags, FCL_FAKECLIENT )) + { + SV_RestoreMoveInterpolant( cl ); + } +} \ No newline at end of file diff --git a/engine/server/sv_save.c b/engine/server/sv_save.c new file mode 100644 index 00000000..9858c187 --- /dev/null +++ b/engine/server/sv_save.c @@ -0,0 +1,2324 @@ +/* +sv_save.c - save\restore implementation +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "library.h" +#include "const.h" +#include "render_api.h" // decallist_t +#include "sound.h" // S_GetDynamicSounds + +/* +============================================================================== +SAVE FILE + +half-life implementation of saverestore system +============================================================================== +*/ +#define SAVEFILE_HEADER (('V'<<24)+('L'<<16)+('A'<<8)+'V') // little-endian "VALV" +#define SAVEGAME_HEADER (('V'<<24)+('A'<<16)+('S'<<8)+'J') // little-endian "JSAV" +#define SAVEGAME_VERSION 0x0071 // Version 0.71 GoldSrc compatible +#define CLIENT_SAVEGAME_VERSION 0x0065 // Version 0.65 + +#define SAVE_HEAPSIZE 0x400000 // reserve 4Mb for now +#define SAVE_HASHSTRINGS 0xFFF // 4095 unique strings +#define SAVE_AGED_COUNT 2 + +// savedata headers +typedef struct +{ + char mapName[32]; + char comment[80]; + int mapCount; +} GAME_HEADER; + +typedef struct +{ + int skillLevel; + int entityCount; + int connectionCount; + int lightStyleCount; + float time; + char mapName[32]; + char skyName[32]; + int skyColor_r; + int skyColor_g; + int skyColor_b; + float skyVec_x; + float skyVec_y; + float skyVec_z; +} SAVE_HEADER; + +typedef struct +{ + int decalCount; // render decals count + int entityCount; // static entity count + int soundCount; // sounds count + int tempEntsCount; // not used + char introTrack[64]; + char mainTrack[64]; + int trackPosition; + short viewentity; // Xash3D added + float wateralpha; + float wateramp; // world waves +} SAVE_CLIENT; + +typedef struct +{ + int index; + char style[256]; + float time; +} SAVE_LIGHTSTYLE; + +void (__cdecl *pfnSaveGameComment)( char *buffer, int max_length ) = NULL; + +static TYPEDESCRIPTION gGameHeader[] = +{ + DEFINE_ARRAY( GAME_HEADER, mapName, FIELD_CHARACTER, 32 ), + DEFINE_ARRAY( GAME_HEADER, comment, FIELD_CHARACTER, 80 ), + DEFINE_FIELD( GAME_HEADER, mapCount, FIELD_INTEGER ), +}; + +static TYPEDESCRIPTION gSaveHeader[] = +{ + DEFINE_FIELD( SAVE_HEADER, skillLevel, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, entityCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, connectionCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, lightStyleCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, time, FIELD_TIME ), + DEFINE_ARRAY( SAVE_HEADER, mapName, FIELD_CHARACTER, 32 ), + DEFINE_ARRAY( SAVE_HEADER, skyName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( SAVE_HEADER, skyColor_r, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, skyColor_g, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, skyColor_b, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_HEADER, skyVec_x, FIELD_FLOAT ), + DEFINE_FIELD( SAVE_HEADER, skyVec_y, FIELD_FLOAT ), + DEFINE_FIELD( SAVE_HEADER, skyVec_z, FIELD_FLOAT ), +}; + +static TYPEDESCRIPTION gAdjacency[] = +{ + DEFINE_ARRAY( LEVELLIST, mapName, FIELD_CHARACTER, 32 ), + DEFINE_ARRAY( LEVELLIST, landmarkName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( LEVELLIST, pentLandmark, FIELD_EDICT ), + DEFINE_FIELD( LEVELLIST, vecLandmarkOrigin, FIELD_VECTOR ), +}; + +static TYPEDESCRIPTION gLightStyle[] = +{ + DEFINE_FIELD( SAVE_LIGHTSTYLE, index, FIELD_INTEGER ), + DEFINE_ARRAY( SAVE_LIGHTSTYLE, style, FIELD_CHARACTER, 256 ), + DEFINE_FIELD( SAVE_LIGHTSTYLE, time, FIELD_FLOAT ), +}; + +static TYPEDESCRIPTION gEntityTable[] = +{ + DEFINE_FIELD( ENTITYTABLE, id, FIELD_INTEGER ), + DEFINE_FIELD( ENTITYTABLE, location, FIELD_INTEGER ), + DEFINE_FIELD( ENTITYTABLE, size, FIELD_INTEGER ), + DEFINE_FIELD( ENTITYTABLE, flags, FIELD_INTEGER ), + DEFINE_FIELD( ENTITYTABLE, classname, FIELD_STRING ), +}; + +static TYPEDESCRIPTION gSaveClient[] = +{ + DEFINE_FIELD( SAVE_CLIENT, decalCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_CLIENT, entityCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_CLIENT, soundCount, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_CLIENT, tempEntsCount, FIELD_INTEGER ), + DEFINE_ARRAY( SAVE_CLIENT, introTrack, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( SAVE_CLIENT, mainTrack, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( SAVE_CLIENT, trackPosition, FIELD_INTEGER ), + DEFINE_FIELD( SAVE_CLIENT, viewentity, FIELD_SHORT ), + DEFINE_FIELD( SAVE_CLIENT, wateralpha, FIELD_FLOAT ), + DEFINE_FIELD( SAVE_CLIENT, wateramp, FIELD_FLOAT ), +}; + +static TYPEDESCRIPTION gDecalEntry[] = +{ + DEFINE_FIELD( decallist_t, position, FIELD_VECTOR ), + DEFINE_ARRAY( decallist_t, name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( decallist_t, entityIndex, FIELD_SHORT ), + DEFINE_FIELD( decallist_t, depth, FIELD_CHARACTER ), + DEFINE_FIELD( decallist_t, flags, FIELD_CHARACTER ), + DEFINE_FIELD( decallist_t, scale, FIELD_FLOAT ), + DEFINE_FIELD( decallist_t, impactPlaneNormal, FIELD_VECTOR ), + DEFINE_ARRAY( decallist_t, studio_state, FIELD_CHARACTER, sizeof( modelstate_t )), +}; + +static TYPEDESCRIPTION gStaticEntry[] = +{ + DEFINE_ARRAY( sv_static_entity_t, model, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( sv_static_entity_t, origin, FIELD_VECTOR ), + DEFINE_FIELD( sv_static_entity_t, angles, FIELD_VECTOR ), + DEFINE_FIELD( sv_static_entity_t, sequence, FIELD_SHORT ), + DEFINE_FIELD( sv_static_entity_t, frame, FIELD_SHORT ), + DEFINE_FIELD( sv_static_entity_t, colormap, FIELD_SHORT ), + DEFINE_FIELD( sv_static_entity_t, skin, FIELD_CHARACTER ), + DEFINE_FIELD( sv_static_entity_t, body, FIELD_CHARACTER ), + DEFINE_FIELD( sv_static_entity_t, scale, FIELD_FLOAT ), + DEFINE_FIELD( sv_static_entity_t, rendermode, FIELD_CHARACTER ), + DEFINE_FIELD( sv_static_entity_t, renderamt, FIELD_CHARACTER ), + DEFINE_ARRAY( sv_static_entity_t, rendercolor, FIELD_CHARACTER, sizeof( color24 )), + DEFINE_FIELD( sv_static_entity_t, renderfx, FIELD_CHARACTER ), +}; + +static TYPEDESCRIPTION gSoundEntry[] = +{ + DEFINE_ARRAY( soundlist_t, name, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( soundlist_t, entnum, FIELD_SHORT ), + DEFINE_FIELD( soundlist_t, origin, FIELD_VECTOR ), + DEFINE_FIELD( soundlist_t, volume, FIELD_FLOAT ), + DEFINE_FIELD( soundlist_t, attenuation, FIELD_FLOAT ), + DEFINE_FIELD( soundlist_t, looping, FIELD_BOOLEAN ), + DEFINE_FIELD( soundlist_t, channel, FIELD_CHARACTER ), + DEFINE_FIELD( soundlist_t, pitch, FIELD_CHARACTER ), + DEFINE_FIELD( soundlist_t, wordIndex, FIELD_CHARACTER ), + DEFINE_ARRAY( soundlist_t, samplePos, FIELD_CHARACTER, sizeof( double )), + DEFINE_ARRAY( soundlist_t, forcedEnd, FIELD_CHARACTER, sizeof( double )), +}; + +static TYPEDESCRIPTION gTempEntvars[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), +}; + +/* +============= +SaveBuildComment + +build commentary for each savegame +typically it writes world message and level time +============= +*/ +static void SaveBuildComment( char *text, int maxlength ) +{ + const char *pName; + + text[0] = '\0'; // clear + + if( pfnSaveGameComment != NULL ) + { + // get save comment from gamedll + pfnSaveGameComment( text, maxlength ); + } + else + { + if( svgame.edicts->v.message != 0 ) + { + // trying to extract message from the world + pName = STRING( svgame.edicts->v.message ); + } + else + { + // or use mapname + pName = STRING( svgame.globals->mapname ); + } + + Q_snprintf( text, maxlength, "%-64.64s %02d:%02d", pName, (int)(sv.time / 60.0 ), (int)fmod( sv.time, 60.0 )); + } +} + +/* +============= +DirectoryCount + +counting all the files with HL1-HL3 extension +in save folder +============= +*/ +static int DirectoryCount( const char *pPath ) +{ + int count; + search_t *t; + + t = FS_Search( pPath, true, true ); // lookup only in gamedir + if( !t ) return 0; // empty + + count = t->numfilenames; + Mem_Free( t ); + + return count; +} + +/* +============= +InitEntityTable + +reserve space for ETABLE's +============= +*/ +static void InitEntityTable( SAVERESTOREDATA *pSaveData, int entityCount ) +{ + ENTITYTABLE *pTable; + int i; + + pSaveData->pTable = Mem_Alloc( host.mempool, sizeof( ENTITYTABLE ) * entityCount ); + pSaveData->tableCount = entityCount; + + // setup entitytable + for( i = 0; i < entityCount; i++ ) + { + pTable = &pSaveData->pTable[i]; + pTable->pent = EDICT_NUM( i ); + pTable->id = i; + } +} + +/* +============= +EntryInTable + +check level in transition list +============= +*/ +static int EntryInTable( SAVERESTOREDATA *pSaveData, const char *pMapName, int index ) +{ + int i; + + for( i = index + 1; i < pSaveData->connectionCount; i++ ) + { + if ( !Q_stricmp( pSaveData->levelList[i].mapName, pMapName )) + return i; + } + + return -1; +} + +/* +============= +EdictFromTable + +get edict from table +============= +*/ +static edict_t *EdictFromTable( SAVERESTOREDATA *pSaveData, int entityIndex ) +{ + if( pSaveData && pSaveData->pTable ) + { + entityIndex = bound( 0, entityIndex, pSaveData->tableCount - 1 ); + return pSaveData->pTable[entityIndex].pent; + } + + return NULL; +} + +/* +============= +LandmarkOrigin + +find global offset for a given landmark +============= +*/ +static void LandmarkOrigin( SAVERESTOREDATA *pSaveData, vec3_t output, const char *pLandmarkName ) +{ + int i; + + for( i = 0; i < pSaveData->connectionCount; i++ ) + { + if( !Q_strcmp( pSaveData->levelList[i].landmarkName, pLandmarkName )) + { + VectorCopy( pSaveData->levelList[i].vecLandmarkOrigin, output ); + return; + } + } + + VectorClear( output ); +} + +/* +============= +EntityInSolid + +some moved edicts on a next level cause stuck +outside of world. Find them and remove +============= +*/ +static int EntityInSolid( edict_t *pent ) +{ + edict_t *aiment = pent->v.aiment; + vec3_t point; + + // if you're attached to a client, always go through + if( pent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( aiment ) && FBitSet( aiment->v.flags, FL_CLIENT )) + return 0; + + VectorAverage( pent->v.absmin, pent->v.absmax, point ); + svs.groupmask = pent->v.groupinfo; + + return (SV_PointContents( point ) == CONTENTS_SOLID); +} + +/* +============= +ClearSaveDir + +remove all the temp files HL1-HL3 +(it will be extracted again from another .sav file) +============= +*/ +static void ClearSaveDir( void ) +{ + search_t *t; + int i; + + // just delete all HL? files + t = FS_Search( va( "%s*.HL?", DEFAULT_SAVE_DIRECTORY ), true, true ); + if( !t ) return; // already empty + + for( i = 0; i < t->numfilenames; i++ ) + FS_Delete( t->filenames[i] ); + + Mem_Free( t ); +} + +/* +============= +IsValidSave + +savegame is allowed? +============= +*/ +static int IsValidSave( void ) +{ + if( !svs.initialized || sv.state != ss_active ) + { + Con_Printf( "Not playing a local game.\n" ); + return 0; + } + + // ignore autosave during background + if( sv.background ) + return 0; + + if( svgame.physFuncs.SV_AllowSaveGame != NULL ) + { + if( !svgame.physFuncs.SV_AllowSaveGame( )) + { + Con_Printf( "Savegame is not allowed.\n" ); + return 0; + } + } + + if( !CL_Active( )) + { + Con_Printf( "Can't save if not active.\n" ); + return 0; + } + + if( CL_IsIntermission( )) + { + Con_Printf( "Can't save during intermission.\n" ); + return 0; + } + + if( svs.maxclients != 1 ) + { + Con_Printf( "Can't save multiplayer games.\n" ); + return 0; + } + + if( svs.clients && svs.clients[0].state == cs_spawned ) + { + edict_t *pl = svs.clients[0].edict; + + if( !pl ) + { + Con_Printf( "Can't savegame without a player!\n" ); + return 0; + } + + if( pl->v.deadflag || pl->v.health <= 0.0f ) + { + Con_Printf( "Can't savegame with a dead player\n" ); + return 0; + } + + // Passed all checks, it's ok to save + return 1; + } + + Con_Printf( "Can't savegame without a client!\n" ); + + return 0; +} + +/* +============= +AgeSaveList + +scroll the name list down +============= +*/ +static void AgeSaveList( const char *pName, int count ) +{ + char newName[MAX_OSPATH], oldName[MAX_OSPATH]; + char newShot[MAX_OSPATH], oldShot[MAX_OSPATH]; + + // delete last quick/autosave (e.g. quick05.sav) + Q_snprintf( newName, sizeof( newName ), "%s%s%02d.sav", DEFAULT_SAVE_DIRECTORY, pName, count ); + Q_snprintf( newShot, sizeof( newShot ), "%s%s%02d.bmp", DEFAULT_SAVE_DIRECTORY, pName, count ); + + // only delete from game directory, basedir is read-only + FS_Delete( newName ); + FS_Delete( newShot ); + + // unloading the shot footprint + GL_FreeImage( newShot ); + + while( count > 0 ) + { + if( count == 1 ) + { + // quick.sav + Q_snprintf( oldName, sizeof( oldName ), "%s%s.sav", DEFAULT_SAVE_DIRECTORY, pName ); + Q_snprintf( oldShot, sizeof( oldShot ), "%s%s.bmp", DEFAULT_SAVE_DIRECTORY, pName ); + } + else + { + // quick04.sav, etc. + Q_snprintf( oldName, sizeof( oldName ), "%s%s%02d.sav", DEFAULT_SAVE_DIRECTORY, pName, count - 1 ); + Q_snprintf( oldShot, sizeof( oldShot ), "%s%s%02d.bmp", DEFAULT_SAVE_DIRECTORY, pName, count - 1 ); + } + + Q_snprintf( newName, sizeof( newName ), "%s%s%02d.sav", DEFAULT_SAVE_DIRECTORY, pName, count ); + Q_snprintf( newShot, sizeof( newShot ), "%s%s%02d.bmp", DEFAULT_SAVE_DIRECTORY, pName, count ); + + // unloading the oldshot footprint too + GL_FreeImage( oldShot ); + + // scroll the name list down (e.g. rename quick04.sav to quick05.sav) + FS_Rename( oldName, newName ); + FS_Rename( oldShot, newShot ); + count--; + } +} + +/* +================== +SaveGetName + +build the savename +================== +*/ +static qboolean SaveGetName( int lastnum, char *filename ) +{ + int a, b, c; + + if( !COM_CheckString( filename )) + return false; + + if( lastnum < 0 || lastnum > 999 ) + return false; + + a = lastnum / 100; + lastnum -= a * 100; + b = lastnum / 10; + c = lastnum % 10; + + Q_sprintf( filename, "save%i%i%i", a, b, c ); + return true; +} + +/* +============= +DirectoryCopy + +put the HL1-HL3 files into .sav file +============= +*/ +static void DirectoryCopy( const char *pPath, file_t *pFile ) +{ + char szName[MAX_OSPATH]; + int i, fileSize; + file_t *pCopy; + search_t *t; + + t = FS_Search( pPath, true, true ); + if( !t ) return; // nothing to copy ? + + for( i = 0; i < t->numfilenames; i++ ) + { + pCopy = FS_Open( t->filenames[i], "rb", true ); + fileSize = FS_FileLength( pCopy ); + + memset( szName, 0, sizeof( szName )); // clearing the string to prevent garbage in output file + Q_strncpy( szName, COM_FileWithoutPath( t->filenames[i] ), MAX_OSPATH ); + FS_Write( pFile, szName, MAX_OSPATH ); + FS_Write( pFile, &fileSize, sizeof( int )); + FS_FileCopy( pFile, pCopy, fileSize ); + FS_Close( pCopy ); + } + Mem_Free( t ); +} + +/* +============= +DirectoryExtract + +extract the HL1-HL3 files from the .sav file +============= +*/ +static void DirectoryExtract( file_t *pFile, int fileCount ) +{ + char szName[MAX_OSPATH]; + char fileName[MAX_OSPATH]; + int i, fileSize; + file_t *pCopy; + + for( i = 0; i < fileCount; i++ ) + { + // filename can only be as long as a map name + extension + FS_Read( pFile, szName, MAX_OSPATH ); + FS_Read( pFile, &fileSize, sizeof( int )); + Q_snprintf( fileName, sizeof( fileName ), "%s%s", DEFAULT_SAVE_DIRECTORY, szName ); + COM_FixSlashes( fileName ); + + pCopy = FS_Open( fileName, "wb", true ); + FS_FileCopy( pCopy, pFile, fileSize ); + FS_Close( pCopy ); + } +} + +/* +============= +SaveInit + +initialize global save-restore buffer +============= +*/ +static SAVERESTOREDATA *SaveInit( int size, int tokenCount ) +{ + SAVERESTOREDATA *pSaveData; + + pSaveData = Mem_Alloc( host.mempool, sizeof( SAVERESTOREDATA ) + size ); + pSaveData->pTokens = (char **)Mem_Alloc( host.mempool, tokenCount * sizeof( char* )); + pSaveData->tokenCount = tokenCount; + + pSaveData->pBaseData = (char *)(pSaveData + 1); // skip the save structure); + pSaveData->pCurrentData = pSaveData->pBaseData; // reset the pointer + pSaveData->bufferSize = size; + + pSaveData->time = svgame.globals->time; // Use DLL time + + // shared with dlls + svgame.globals->pSaveData = pSaveData; + + return pSaveData; +} + +/* +============= +SaveClear + +clearing buffer for reuse +============= +*/ +static void SaveClear( SAVERESTOREDATA *pSaveData ) +{ + memset( pSaveData->pTokens, 0, pSaveData->tokenCount * sizeof( char* )); + + pSaveData->pBaseData = (char *)(pSaveData + 1); // skip the save structure); + pSaveData->pCurrentData = pSaveData->pBaseData; // reset the pointer + pSaveData->time = svgame.globals->time; // Use DLL time + pSaveData->tokenSize = 0; // reset the hashtable + pSaveData->size = 0; // reset the pointer + + // shared with dlls + svgame.globals->pSaveData = pSaveData; +} + +/* +============= +SaveInit + +release global save-restore buffer +============= +*/ +static void SaveFinish( SAVERESTOREDATA *pSaveData ) +{ + if( !pSaveData ) return; + + if( pSaveData->pTokens ) + { + Mem_Free( pSaveData->pTokens ); + pSaveData->pTokens = NULL; + pSaveData->tokenCount = 0; + } + + if( pSaveData->pTable ) + { + Mem_Free( pSaveData->pTable ); + pSaveData->pTable = NULL; + pSaveData->tableCount = 0; + } + + svgame.globals->pSaveData = NULL; + Mem_Free( pSaveData ); +} + +/* +============= +DumpHashStrings + +debug thing +============= +*/ +static void DumpHashStrings( SAVERESTOREDATA *pSaveData, const char *pMessage ) +{ + int i, count = 0; + + if( pSaveData && pSaveData->pTokens ) + { + Con_Printf( "%s\n", pMessage ); + + for( i = 0; i < pSaveData->tokenCount; i++ ) + { + if( !pSaveData->pTokens[i] ) + continue; + + Con_Printf( "#%i %s\n", count, pSaveData->pTokens[i] ); + count++; + } + Con_Printf( "total %i actual %i\n", pSaveData->tokenCount, count ); + } +} + +/* +============= +StoreHashTable + +write the stringtable into file +============= +*/ +static char *StoreHashTable( SAVERESTOREDATA *pSaveData ) +{ + char *pTokenData = pSaveData->pCurrentData; + int i; + + // Write entity string token table + if( pSaveData->pTokens ) + { + for( i = 0; i < pSaveData->tokenCount; i++ ) + { + char *pszToken = pSaveData->pTokens[i] ? pSaveData->pTokens[i] : ""; + + // just copy the token byte-by-byte + while( *pszToken ) + *pSaveData->pCurrentData++ = *pszToken++; + *pSaveData->pCurrentData++ = 0; // Write the term + } + } + + pSaveData->tokenSize = pSaveData->pCurrentData - pTokenData; + + return pTokenData; +} + +/* +============= +BuildHashTable + +build the stringtable from buffer +============= +*/ +static void BuildHashTable( SAVERESTOREDATA *pSaveData, file_t *pFile ) +{ + char *pszTokenList = pSaveData->pBaseData; + int i; + + // Parse the symbol table + if( pSaveData->tokenSize > 0 ) + { + FS_Read( pFile, pszTokenList, pSaveData->tokenSize ); + + // make sure the token strings pointed to by the pToken hashtable. + for( i = 0; i < pSaveData->tokenCount; i++ ) + { + pSaveData->pTokens[i] = *pszTokenList ? pszTokenList : NULL; + while( *pszTokenList++ ); // Find next token (after next null) + } + } + + // rebase the data pointer + pSaveData->pBaseData = pszTokenList; // pszTokenList now points after token data + pSaveData->pCurrentData = pSaveData->pBaseData; +} + +/* +============= +GetClientDataSize + +g-cont: this routine is redundant +i'm write it just for more readable code +============= +*/ +static int GetClientDataSize( const char *level ) +{ + int tokenCount, tokenSize; + int size, id, version; + char name[MAX_QPATH]; + file_t *pFile; + + Q_snprintf( name, sizeof( name ), "%s%s.HL2", DEFAULT_SAVE_DIRECTORY, level ); + + if(( pFile = FS_Open( name, "rb", true )) == NULL ) + return 0; + + FS_Read( pFile, &id, sizeof( id )); + if( id != SAVEGAME_HEADER ) + { + FS_Close( pFile ); + return 0; + } + + FS_Read( pFile, &version, sizeof( version )); + if( version != CLIENT_SAVEGAME_VERSION ) + { + FS_Close( pFile ); + return 0; + } + + FS_Read( pFile, &size, sizeof( int )); + FS_Read( pFile, &tokenCount, sizeof( int )); + FS_Read( pFile, &tokenSize, sizeof( int )); + FS_Close( pFile ); + + return ( size + tokenSize ); +} + +/* +============= +LoadSaveData + +fill the save resore buffer +parse hash strings +============= +*/ +static SAVERESTOREDATA *LoadSaveData( const char *level ) +{ + int tokenSize, tableCount; + int size, tokenCount; + char name[MAX_OSPATH]; + int id, version; + int clientSize; + SAVERESTOREDATA *pSaveData; + int totalSize; + file_t *pFile; + + Q_snprintf( name, sizeof( name ), "%s%s.HL1", DEFAULT_SAVE_DIRECTORY, level ); + Con_Printf( "Loading game from %s...\n", name ); + + if(( pFile = FS_Open( name, "rb", true )) == NULL ) + { + Con_Printf( S_ERROR "couldn't open.\n" ); + return NULL; + } + + // Read the header + FS_Read( pFile, &id, sizeof( int )); + FS_Read( pFile, &version, sizeof( int )); + + // is this a valid save? + if( id != SAVEFILE_HEADER || version != SAVEGAME_VERSION ) + { + FS_Close( pFile ); + return NULL; + } + + // Read the sections info and the data + FS_Read( pFile, &size, sizeof( int )); // total size of all data to initialize read buffer + FS_Read( pFile, &tableCount, sizeof( int )); // entities count to right initialize entity table + FS_Read( pFile, &tokenCount, sizeof( int )); // num hash tokens to prepare token table + FS_Read( pFile, &tokenSize, sizeof( int )); // total size of hash tokens + + // determine highest size of seve-restore buffer + // because it's used twice: for HL1 and HL2 restore + clientSize = GetClientDataSize( level ); + totalSize = Q_max( clientSize, ( size + tokenSize )); + + // init the read buffer + pSaveData = SaveInit( totalSize, tokenCount ); + + Q_strncpy( pSaveData->szCurrentMapName, level, sizeof( pSaveData->szCurrentMapName )); + pSaveData->tableCount = tableCount; // count ETABLE entries + pSaveData->tokenCount = tokenCount; + pSaveData->tokenSize = tokenSize; + + // Parse the symbol table + BuildHashTable( pSaveData, pFile ); + + // Set up the restore basis + pSaveData->fUseLandmark = true; + pSaveData->time = 0.0f; + + // now reading all the rest of data + FS_Read( pFile, pSaveData->pBaseData, size ); + FS_Close( pFile ); // data is sucessfully moved into SaveRestore buffer (ETABLE will be init later) + + return pSaveData; +} + +/* +============= +ParseSaveTables + +reading global data, setup ETABLE's +============= +*/ +static void ParseSaveTables( SAVERESTOREDATA *pSaveData, SAVE_HEADER *pHeader, int updateGlobals ) +{ + SAVE_LIGHTSTYLE light; + int i; + + // Re-base the savedata since we re-ordered the entity/table / restore fields + InitEntityTable( pSaveData, pSaveData->tableCount ); + + for( i = 0; i < pSaveData->tableCount; i++ ) + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "ETABLE", &pSaveData->pTable[i], gEntityTable, ARRAYSIZE( gEntityTable )); + + pSaveData->pBaseData = pSaveData->pCurrentData; + pSaveData->size = 0; + + // process SAVE_HEADER + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "Save Header", pHeader, gSaveHeader, ARRAYSIZE( gSaveHeader )); + + pSaveData->connectionCount = pHeader->connectionCount; + VectorClear( pSaveData->vecLandmarkOffset ); + pSaveData->time = pHeader->time; + pSaveData->fUseLandmark = true; + + // read adjacency list + for( i = 0; i < pSaveData->connectionCount; i++ ) + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "ADJACENCY", &pSaveData->levelList[i], gAdjacency, ARRAYSIZE( gAdjacency )); + + if( updateGlobals ) + memset( sv.lightstyles, 0, sizeof( sv.lightstyles )); + + for( i = 0; i < pHeader->lightStyleCount; i++ ) + { + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "LIGHTSTYLE", &light, gLightStyle, ARRAYSIZE( gLightStyle )); + if( updateGlobals ) SV_SetLightStyle( light.index, light.style, light.time ); + } +} + +/* +============= +EntityPatchWrite + +write out the list of entities that are no longer in the save file for this level +(they've been moved to another level) +============= +*/ +static void EntityPatchWrite( SAVERESTOREDATA *pSaveData, const char *level ) +{ + char name[MAX_QPATH]; + int i, size = 0; + file_t *pFile; + + Q_snprintf( name, sizeof( name ), "%s%s.HL3", DEFAULT_SAVE_DIRECTORY, level ); + + if(( pFile = FS_Open( name, "wb", true )) == NULL ) + return; + + for( i = 0; i < pSaveData->tableCount; i++ ) + { + if( FBitSet( pSaveData->pTable[i].flags, FENTTABLE_REMOVED )) + size++; + } + + // patch count + FS_Write( pFile, &size, sizeof( int )); + + for( i = 0; i < pSaveData->tableCount; i++ ) + { + if( FBitSet( pSaveData->pTable[i].flags, FENTTABLE_REMOVED )) + FS_Write( pFile, &i, sizeof( int )); + } + + FS_Close( pFile ); +} + +/* +============= +EntityPatchRead + +read the list of entities that are no longer in the save file for this level +(they've been moved to another level) +============= +*/ +static void EntityPatchRead( SAVERESTOREDATA *pSaveData, const char *level ) +{ + char name[MAX_QPATH]; + int i, size, entityId; + file_t *pFile; + + Q_snprintf( name, sizeof( name ), "%s%s.HL3", DEFAULT_SAVE_DIRECTORY, level ); + + if(( pFile = FS_Open( name, "rb", true )) == NULL ) + return; + + // patch count + FS_Read( pFile, &size, sizeof( int )); + + for( i = 0; i < size; i++ ) + { + FS_Read( pFile, &entityId, sizeof( int )); + pSaveData->pTable[entityId].flags = FENTTABLE_REMOVED; + } + + FS_Close( pFile ); +} + +/* +============= +RestoreDecal + +restore decal\move across transition +============= +*/ +static void RestoreDecal( SAVERESTOREDATA *pSaveData, decallist_t *entry, qboolean adjacent ) +{ + int decalIndex, entityIndex = 0; + int flags = entry->flags; + int modelIndex = 0; + edict_t *pEdict; + + // never move permanent decals + if( adjacent && FBitSet( flags, FDECAL_PERMANENT )) + return; + + // restore entity and model index + pEdict = EdictFromTable( pSaveData, entry->entityIndex ); + + if( SV_RestoreCustomDecal( entry, pEdict, adjacent )) + return; // decal was sucessfully restored at the game-side + + // studio decals are handled at game-side + if( FBitSet( flags, FDECAL_STUDIO )) + return; + + if( SV_IsValidEdict( pEdict )) + modelIndex = pEdict->v.modelindex; + + if( SV_IsValidEdict( pEdict )) + entityIndex = NUM_FOR_EDICT( pEdict ); + + decalIndex = pfnDecalIndex( entry->name ); + + // this can happens if brush entity from previous level was turned into world geometry + if( adjacent && entry->entityIndex != 0 && !SV_IsValidEdict( pEdict )) + { + vec3_t testspot, testend; + trace_t tr; + + Con_Printf( S_ERROR "RestoreDecal: couldn't restore entity index %i\n", entry->entityIndex ); + + VectorCopy( entry->position, testspot ); + VectorMA( testspot, 5.0f, entry->impactPlaneNormal, testspot ); + + VectorCopy( entry->position, testend ); + VectorMA( testend, -5.0f, entry->impactPlaneNormal, testend ); + + tr = SV_Move( testspot, vec3_origin, vec3_origin, testend, MOVE_NOMONSTERS, NULL, false ); + + // NOTE: this code may does wrong result on moving brushes e.g. func_tracktrain + if( tr.fraction != 1.0f && !tr.allsolid ) + { + // check impact plane normal + float dot = DotProduct( entry->impactPlaneNormal, tr.plane.normal ); + + if( dot >= 0.95f ) + { + entityIndex = pfnIndexOfEdict( tr.ent ); + if( entityIndex > 0 ) modelIndex = tr.ent->v.modelindex; + SV_CreateDecal( &sv.signon, tr.endpos, decalIndex, entityIndex, modelIndex, flags, entry->scale ); + } + } + } + else + { + // global entity is exist on new level so we can apply decal in local space + SV_CreateDecal( &sv.signon, entry->position, decalIndex, entityIndex, modelIndex, flags, entry->scale ); + } +} + +/* +============= +RestoreSound + +continue playing sound from saved position +============= +*/ +static void RestoreSound( SAVERESTOREDATA *pSaveData, soundlist_t *snd ) +{ + edict_t *ent = EdictFromTable( pSaveData, snd->entnum ); + int flags = SND_RESTORE_POSITION; + + // this can happens if serialized map contain 4096 static decals... + if( MSG_GetNumBytesLeft( &sv.signon ) < 36 ) + return; + + if( !snd->looping ) + SetBits( flags, SND_STOP_LOOPING ); + + if( SV_BuildSoundMsg( &sv.signon, ent, snd->channel, snd->name, snd->volume * 255, snd->attenuation, flags, snd->pitch, snd->origin )) + { + // write extradata for svc_restoresound + MSG_WriteByte( &sv.signon, snd->wordIndex ); + MSG_WriteBytes( &sv.signon, &snd->samplePos, sizeof( snd->samplePos )); + MSG_WriteBytes( &sv.signon, &snd->forcedEnd, sizeof( snd->forcedEnd )); + } +} + +/* +============= +SaveClientState + +write out the list of premanent decals for this level +============= +*/ +static void SaveClientState( SAVERESTOREDATA *pSaveData, const char *level, int changelevel ) +{ + soundlist_t soundInfo[MAX_CHANNELS]; + sv_client_t *cl = svs.clients; + char name[MAX_QPATH]; + int i, id, version; + char *pTokenData; + decallist_t *decalList; + SAVE_CLIENT header; + file_t *pFile; + + // clearing the saving buffer to reuse + SaveClear( pSaveData ); + + memset( &header, 0, sizeof( header )); + + // g-cont. add space for studiodecals if present + decalList = (decallist_t *)Z_Malloc( sizeof( decallist_t ) * MAX_RENDER_DECALS * 2 ); + + // initialize client header + header.decalCount = R_CreateDecalList( decalList ); + header.entityCount = sv.num_static_entities; + + if( !changelevel ) + { + // sounds won't going across transition + header.soundCount = S_GetCurrentDynamicSounds( soundInfo, MAX_CHANNELS ); + // music not reqiured to save position: it's just continue playing on a next level + S_StreamGetCurrentState( header.introTrack, header.mainTrack, &header.trackPosition ); + } + + // save viewentity to allow camera works after save\restore + if( SV_IsValidEdict( cl->pViewEntity ) && cl->pViewEntity != cl->edict ) + header.viewentity = NUM_FOR_EDICT( cl->pViewEntity ); + + header.wateralpha = sv_wateralpha.value; + header.wateramp = sv_wateramp.value; + + // Store the client header + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "ClientHeader", &header, gSaveClient, ARRAYSIZE( gSaveClient )); + + // store decals + for( i = 0; i < header.decalCount; i++ ) + { + // NOTE: apply landmark offset only for brush entities without origin brushes + if( pSaveData->fUseLandmark && FBitSet( decalList[i].flags, FDECAL_USE_LANDMARK )) + VectorSubtract( decalList[i].position, pSaveData->vecLandmarkOffset, decalList[i].position ); + + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "DECALLIST", &decalList[i], gDecalEntry, ARRAYSIZE( gDecalEntry )); + } + Z_Free( decalList ); + + // write client entities + for( i = 0; i < header.entityCount; i++ ) + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "STATICENTITY", &sv.static_entities[i], gStaticEntry, ARRAYSIZE( gStaticEntry )); + + // write sounds + for( i = 0; i < header.soundCount; i++ ) + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "SOUNDLIST", &soundInfo[i], gSoundEntry, ARRAYSIZE( gSoundEntry )); + + // Write entity string token table + pTokenData = StoreHashTable( pSaveData ); + + Q_snprintf( name, sizeof( name ), "%s%s.HL2", DEFAULT_SAVE_DIRECTORY, level ); + + // output to disk + if(( pFile = FS_Open( name, "wb", true )) == NULL ) + return; // something bad is happens + + version = CLIENT_SAVEGAME_VERSION; + id = SAVEGAME_HEADER; + + FS_Write( pFile, &id, sizeof( id )); + FS_Write( pFile, &version, sizeof( version )); + FS_Write( pFile, &pSaveData->size, sizeof( int )); // does not include token table + + // write out the tokens first so we can load them before we load the entities + FS_Write( pFile, &pSaveData->tokenCount, sizeof( int )); + FS_Write( pFile, &pSaveData->tokenSize, sizeof( int )); + FS_Write( pFile, pTokenData, pSaveData->tokenSize ); + FS_Write( pFile, pSaveData->pBaseData, pSaveData->size ); // header and globals + FS_Close( pFile ); +} + +/* +============= +LoadClientState + +read the list of decals and reapply them again +============= +*/ +static void LoadClientState( SAVERESTOREDATA *pSaveData, const char *level, qboolean changelevel, qboolean adjacent ) +{ + int tokenCount, tokenSize; + int i, size, id, version; + sv_client_t *cl = svs.clients; + char name[MAX_QPATH]; + sv_static_entity_t staticEntry; + soundlist_t soundEntry; + decallist_t decalEntry; + SAVE_CLIENT header; + file_t *pFile; + + Q_snprintf( name, sizeof( name ), "%s%s.HL2", DEFAULT_SAVE_DIRECTORY, level ); + + if(( pFile = FS_Open( name, "rb", true )) == NULL ) + return; // something bad is happens + + FS_Read( pFile, &id, sizeof( id )); + if( id != SAVEGAME_HEADER ) + { + FS_Close( pFile ); + return; + } + + FS_Read( pFile, &version, sizeof( version )); + if( version != CLIENT_SAVEGAME_VERSION ) + { + FS_Close( pFile ); + return; + } + + FS_Read( pFile, &size, sizeof( int )); + FS_Read( pFile, &tokenCount, sizeof( int )); + FS_Read( pFile, &tokenSize, sizeof( int )); + + // sanity check + ASSERT( pSaveData->bufferSize >= ( size + tokenSize )); + + // clearing the restore buffer to reuse + SaveClear( pSaveData ); + pSaveData->tokenCount = tokenCount; + pSaveData->tokenSize = tokenSize; + + // Parse the symbol table + BuildHashTable( pSaveData, pFile ); + + FS_Read( pFile, pSaveData->pBaseData, size ); + FS_Close( pFile ); + + // Read the client header + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "ClientHeader", &header, gSaveClient, ARRAYSIZE( gSaveClient )); + + // restore decals + for( i = 0; i < header.decalCount; i++ ) + { + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "DECALLIST", &decalEntry, gDecalEntry, ARRAYSIZE( gDecalEntry )); + + // NOTE: apply landmark offset only for brush entities without origin brushes + if( pSaveData->fUseLandmark && FBitSet( decalEntry.flags, FDECAL_USE_LANDMARK )) + VectorAdd( decalEntry.position, pSaveData->vecLandmarkOffset, decalEntry.position ); + RestoreDecal( pSaveData, &decalEntry, adjacent ); + } + + // clear old entities + if( !adjacent ) + { + memset( sv.static_entities, 0, sizeof( sv.static_entities )); + sv.num_static_entities = 0; + } + + // restore client entities + for( i = 0; i < header.entityCount; i++ ) + { + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "STATICENTITY", &staticEntry, gStaticEntry, ARRAYSIZE( gStaticEntry )); + if( adjacent ) continue; // static entities won't loading from adjacent levels + + if( i >= MAX_STATIC_ENTITIES ) + continue; // silently overflowed + + SV_CreateStaticEntity( &sv.signon, &staticEntry ); + sv.static_entities[i] = staticEntry; + sv.num_static_entities++; + } + + // restore sounds + for( i = 0; i < header.soundCount; i++ ) + { + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "SOUNDLIST", &soundEntry, gSoundEntry, ARRAYSIZE( gSoundEntry )); + if( adjacent ) continue; // sounds don't going across the levels + + RestoreSound( pSaveData, &soundEntry ); + } + + if( !adjacent ) + { + // restore camera view here + edict_t *pent = pSaveData->pTable[bound( 0, (word)header.viewentity, pSaveData->tableCount )].pent; + + if( Q_strlen( header.introTrack )) + { + // NOTE: music is automatically goes across transition, never restore it on changelevel + MSG_BeginServerCmd( &sv.signon, svc_stufftext ); + MSG_WriteString( &sv.signon, va( "music \"%s\" \"%s\" %i\n", header.introTrack, header.mainTrack, header.trackPosition )); + } + + // don't go camera across the levels + if( header.viewentity > svs.maxclients && !changelevel ) + cl->pViewEntity = pent; + + // restore some client cvars + Cvar_SetValue( "sv_wateralpha", header.wateralpha ); + Cvar_SetValue( "sv_wateramp", header.wateramp ); + } +} + +/* +============= +CreateEntitiesInRestoreList + +alloc private data for restored entities +============= +*/ +static void CreateEntitiesInRestoreList( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world ) +{ + int i, active; + ENTITYTABLE *pTable; + edict_t *pent; + + // create entity list + if( svgame.physFuncs.pfnCreateEntitiesInRestoreList != NULL ) + { + svgame.physFuncs.pfnCreateEntitiesInRestoreList( pSaveData, levelMask, create_world ); + } + else + { + for( i = 0; i < pSaveData->tableCount; i++ ) + { + pTable = &pSaveData->pTable[i]; + pent = NULL; + + if( pTable->classname && pTable->size && ( !FBitSet( pTable->flags, FENTTABLE_REMOVED ) || !create_world )) + { + if( !create_world ) + active = FBitSet( pTable->flags, levelMask ) ? 1 : 0; + else active = 1; + + if( pTable->id == 0 && create_world ) // worldspawn + { + pent = EDICT_NUM( 0 ); + SV_InitEdict( pent ); + pent = SV_CreateNamedEntity( pent, pTable->classname ); + } + else if(( pTable->id > 0 ) && ( pTable->id < svs.maxclients + 1 )) + { + edict_t *ed = EDICT_NUM( pTable->id ); + + if( !FBitSet( pTable->flags, FENTTABLE_PLAYER )) + Con_Printf( S_ERROR "ENTITY IS NOT A PLAYER: %d\n", i ); + + // create the player + if( active && SV_IsValidEdict( ed )) + pent = SV_CreateNamedEntity( ed, pTable->classname ); + } + else if( active ) + { + pent = SV_CreateNamedEntity( NULL, pTable->classname ); + } + } + + pTable->pent = pent; + } + } +} + +/* +============= +SaveGameState + +save current game state +============= +*/ +static SAVERESTOREDATA *SaveGameState( int changelevel ) +{ + char name[MAX_QPATH]; + int i, id, version; + char *pTableData; + char *pTokenData; + SAVERESTOREDATA *pSaveData; + int tableSize; + int dataSize; + ENTITYTABLE *pTable; + SAVE_HEADER header; + SAVE_LIGHTSTYLE light; + file_t *pFile; + + if( !svgame.dllFuncs.pfnParmsChangeLevel ) + return NULL; + + pSaveData = SaveInit( SAVE_HEAPSIZE, SAVE_HASHSTRINGS ); + + Q_snprintf( name, sizeof( name ), "%s%s.HL1", DEFAULT_SAVE_DIRECTORY, sv.name ); + COM_FixSlashes( name ); + + // initialize entity table to count moved entities + InitEntityTable( pSaveData, svgame.numEntities ); + + // Build the adjacent map list + svgame.dllFuncs.pfnParmsChangeLevel(); + + // Write the global data + header.skillLevel = (int)skill.value; // this is created from an int even though it's a float + header.entityCount = pSaveData->tableCount; + header.connectionCount = pSaveData->connectionCount; + header.time = svgame.globals->time; // use DLL time + Q_strncpy( header.mapName, sv.name, sizeof( header.mapName )); + Q_strncpy( header.skyName, sv_skyname.string, sizeof( header.skyName )); + header.skyColor_r = sv_skycolor_r.value; + header.skyColor_g = sv_skycolor_g.value; + header.skyColor_b = sv_skycolor_b.value; + header.skyVec_x = sv_skyvec_x.value; + header.skyVec_y = sv_skyvec_y.value; + header.skyVec_z = sv_skyvec_z.value; + header.lightStyleCount = 0; + + // counting the lightstyles + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + if( sv.lightstyles[i].pattern[0] ) + header.lightStyleCount++; + } + + // Write the main header + pSaveData->time = 0.0f; // prohibits rebase of header.time (keep compatibility with old saves) + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "Save Header", &header, gSaveHeader, ARRAYSIZE( gSaveHeader )); + pSaveData->time = header.time; + + // Write the adjacency list + for( i = 0; i < pSaveData->connectionCount; i++ ) + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "ADJACENCY", &pSaveData->levelList[i], gAdjacency, ARRAYSIZE( gAdjacency )); + + // Write the lightstyles + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + if( !sv.lightstyles[i].pattern[0] ) + continue; + + Q_strncpy( light.style, sv.lightstyles[i].pattern, sizeof( light.style )); + light.time = sv.lightstyles[i].time; + light.index = i; + + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "LIGHTSTYLE", &light, gLightStyle, ARRAYSIZE( gLightStyle )); + } + + // build the table of entities + // this is used to turn pointers into savable indices + // build up ID numbers for each entity, for use in pointer conversions + // if an entity requires a certain edict number upon restore, save that as well + for( i = 0; i < svgame.numEntities; i++ ) + { + pTable = &pSaveData->pTable[i]; + pTable->location = pSaveData->size; + pSaveData->currentIndex = i; + pTable->size = 0; + + if( !SV_IsValidEdict( pTable->pent )) + continue; + + svgame.dllFuncs.pfnSave( pTable->pent, pSaveData ); + + if( FBitSet( pTable->pent->v.flags, FL_CLIENT )) + SetBits( pTable->flags, FENTTABLE_PLAYER ); + } + + // total data what includes: + // 1. save header + // 2. adjacency list + // 3. lightstyles + // 4. all the entity data + dataSize = pSaveData->size; + + // Write entity table + pTableData = pSaveData->pCurrentData; + + for( i = 0; i < pSaveData->tableCount; i++ ) + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "ETABLE", &pSaveData->pTable[i], gEntityTable, ARRAYSIZE( gEntityTable )); + + tableSize = pSaveData->size - dataSize; + + // Write entity string token table + pTokenData = StoreHashTable( pSaveData ); + + // output to disk + if(( pFile = FS_Open( name, "wb", true )) == NULL ) + { + // something bad is happens + SaveFinish( pSaveData ); + return NULL; + } + + // Write the header -- THIS SHOULD NEVER CHANGE STRUCTURE, USE SAVE_HEADER FOR NEW HEADER INFORMATION + // THIS IS ONLY HERE TO IDENTIFY THE FILE AND GET IT'S SIZE. + version = SAVEGAME_VERSION; + id = SAVEFILE_HEADER; + + // write the header + FS_Write( pFile, &id, sizeof( id )); + FS_Write( pFile, &version, sizeof( version )); + + // Write out the tokens and table FIRST so they are loaded in the right order, then write out the rest of the data in the file. + FS_Write( pFile, &pSaveData->size, sizeof( int )); // total size of all data to initialize read buffer + FS_Write( pFile, &pSaveData->tableCount, sizeof( int )); // entities count to right initialize entity table + FS_Write( pFile, &pSaveData->tokenCount, sizeof( int )); // num hash tokens to prepare token table + FS_Write( pFile, &pSaveData->tokenSize, sizeof( int )); // total size of hash tokens + FS_Write( pFile, pTokenData, pSaveData->tokenSize ); // write tokens into the file + FS_Write( pFile, pTableData, tableSize ); // dump ETABLE structures + FS_Write( pFile, pSaveData->pBaseData, dataSize ); // and finally store all the other data + FS_Close( pFile ); + + EntityPatchWrite( pSaveData, sv.name ); + + SaveClientState( pSaveData, sv.name, changelevel ); + + return pSaveData; +} + +/* +============= +LoadGameState + +load current game state +============= +*/ +static int LoadGameState( char const *level, qboolean changelevel ) +{ + SAVERESTOREDATA *pSaveData; + ENTITYTABLE *pTable; + SAVE_HEADER header; + edict_t *pent; + int i; + + pSaveData = LoadSaveData( level ); + if( !pSaveData ) return 0; // couldn't load the file + + ParseSaveTables( pSaveData, &header, true ); + EntityPatchRead( pSaveData, level ); + + // pause until all clients connect + sv.loadgame = sv.paused = true; + + Cvar_SetValue( "skill", header.skillLevel ); + Q_strncpy( sv.name, header.mapName, sizeof( sv.name )); + svgame.globals->mapname = MAKE_STRING( sv.name ); + Cvar_Set( "sv_skyname", header.skyName ); + + // restore sky parms + Cvar_SetValue( "sv_skycolor_r", header.skyColor_r ); + Cvar_SetValue( "sv_skycolor_g", header.skyColor_g ); + Cvar_SetValue( "sv_skycolor_b", header.skyColor_b ); + Cvar_SetValue( "sv_skyvec_x", header.skyVec_x ); + Cvar_SetValue( "sv_skyvec_y", header.skyVec_y ); + Cvar_SetValue( "sv_skyvec_z", header.skyVec_z ); + + // create entity list + CreateEntitiesInRestoreList( pSaveData, 0, true ); + + // now spawn entities + for( i = 0; i < pSaveData->tableCount; i++ ) + { + pTable = &pSaveData->pTable[i]; + pSaveData->pCurrentData = pSaveData->pBaseData + pTable->location; + pSaveData->size = pTable->location; + pSaveData->currentIndex = i; + pent = pTable->pent; + + if( pent != NULL ) + { + if( svgame.dllFuncs.pfnRestore( pent, pSaveData, false ) < 0 ) + { + SetBits( pent->v.flags, FL_KILLME ); + pTable->pent = NULL; + } + else + { + // force the entity to be relinked +// SV_LinkEdict( pent, false ); + } + } + } + + LoadClientState( pSaveData, level, changelevel, false ); + + SaveFinish( pSaveData ); + + // restore server time + sv.time = header.time; + + return 1; +} + +/* +============= +SaveGameSlot + +do a save game +============= +*/ +static int SaveGameSlot( const char *pSaveName, const char *pSaveComment ) +{ + char hlPath[MAX_QPATH]; + char name[MAX_QPATH]; + int id, version; + char *pTokenData; + SAVERESTOREDATA *pSaveData; + GAME_HEADER gameHeader; + file_t *pFile; + + pSaveData = SaveGameState( false ); + if( !pSaveData ) return 0; + + SaveFinish( pSaveData ); + pSaveData = SaveInit( SAVE_HEAPSIZE, SAVE_HASHSTRINGS ); // re-init the buffer + + Q_snprintf( hlPath, sizeof( hlPath ), "%s*.HL?", DEFAULT_SAVE_DIRECTORY ); + Q_strncpy( gameHeader.mapName, sv.name, sizeof( gameHeader.mapName )); // get the name of level where a player + Q_strncpy( gameHeader.comment, pSaveComment, sizeof( gameHeader.comment )); + gameHeader.mapCount = DirectoryCount( hlPath ); // counting all the adjacency maps + + // Store the game header + svgame.dllFuncs.pfnSaveWriteFields( pSaveData, "GameHeader", &gameHeader, gGameHeader, ARRAYSIZE( gGameHeader )); + + // Write the game globals + svgame.dllFuncs.pfnSaveGlobalState( pSaveData ); + + // Write entity string token table + pTokenData = StoreHashTable( pSaveData ); + + Q_snprintf( name, sizeof( name ), "%s%s.sav", DEFAULT_SAVE_DIRECTORY, pSaveName ); + COM_FixSlashes( name ); + + // output to disk + if( !Q_stricmp( pSaveName, "quick" ) || !Q_stricmp( pSaveName, "autosave" )) + AgeSaveList( pSaveName, SAVE_AGED_COUNT ); + + // output to disk + if(( pFile = FS_Open( name, "wb", true )) == NULL ) + { + // something bad is happens + SaveFinish( pSaveData ); + return 0; + } + + // pending the preview image for savegame + Cbuf_AddText( va( "saveshot \"%s\"\n", pSaveName )); + Con_Printf( "Saving game to %s...\n", name ); + + version = SAVEGAME_VERSION; + id = SAVEGAME_HEADER; + + FS_Write( pFile, &id, sizeof( id )); + FS_Write( pFile, &version, sizeof( version )); + FS_Write( pFile, &pSaveData->size, sizeof( int )); // does not include token table + + // write out the tokens first so we can load them before we load the entities + FS_Write( pFile, &pSaveData->tokenCount, sizeof( int )); + FS_Write( pFile, &pSaveData->tokenSize, sizeof( int )); + FS_Write( pFile, pTokenData, pSaveData->tokenSize ); + FS_Write( pFile, pSaveData->pBaseData, pSaveData->size ); // header and globals + + DirectoryCopy( hlPath, pFile ); + SaveFinish( pSaveData ); + FS_Close( pFile ); + + return 1; +} + +/* +============= +SaveReadHeader + +read header of .sav file +============= +*/ +static int SaveReadHeader( file_t *pFile, GAME_HEADER *pHeader ) +{ + int tokenCount, tokenSize; + int size, id, version; + SAVERESTOREDATA *pSaveData; + + FS_Read( pFile, &id, sizeof( id )); + if( id != SAVEGAME_HEADER ) + { + FS_Close( pFile ); + return 0; + } + + FS_Read( pFile, &version, sizeof( version )); + if( version != SAVEGAME_VERSION ) + { + FS_Close( pFile ); + return 0; + } + + FS_Read( pFile, &size, sizeof( int )); + FS_Read( pFile, &tokenCount, sizeof( int )); + FS_Read( pFile, &tokenSize, sizeof( int )); + + pSaveData = SaveInit( size + tokenSize, tokenCount ); + pSaveData->tokenCount = tokenCount; + pSaveData->tokenSize = tokenSize; + + // Parse the symbol table + BuildHashTable( pSaveData, pFile ); + + // Set up the restore basis + pSaveData->fUseLandmark = false; + pSaveData->time = 0.0f; + + FS_Read( pFile, pSaveData->pBaseData, size ); + + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "GameHeader", pHeader, gGameHeader, ARRAYSIZE( gGameHeader )); + + svgame.dllFuncs.pfnRestoreGlobalState( pSaveData ); + + SaveFinish( pSaveData ); + + return 1; +} + +/* +============= +CreateEntityTransitionList + +moving edicts to another level +============= +*/ +static int CreateEntityTransitionList( SAVERESTOREDATA *pSaveData, int levelMask ) +{ + int i, movedCount; + ENTITYTABLE *pTable; + edict_t *pent; + + movedCount = 0; + + // create entity list + CreateEntitiesInRestoreList( pSaveData, levelMask, false ); + + // now spawn entities + for( i = 0; i < pSaveData->tableCount; i++ ) + { + pTable = &pSaveData->pTable[i]; + pSaveData->pCurrentData = pSaveData->pBaseData + pTable->location; + pSaveData->size = pTable->location; + pSaveData->currentIndex = i; + pent = pTable->pent; + + if( SV_IsValidEdict( pent ) && FBitSet( pTable->flags, levelMask )) // screen out the player if he's not to be spawned + { + if( FBitSet( pTable->flags, FENTTABLE_GLOBAL )) + { + entvars_t tmpVars; + edict_t *pNewEnt; + + // NOTE: we need to update table pointer so decals on the global entities with brush models can be + // correctly moved. found the classname and the globalname for our globalentity + svgame.dllFuncs.pfnSaveReadFields( pSaveData, "ENTVARS", &tmpVars, gTempEntvars, ARRAYSIZE( gTempEntvars )); + + // reset the save pointers, so dll can read this too + pSaveData->pCurrentData = pSaveData->pBaseData + pTable->location; + pSaveData->size = pTable->location; + + // IMPORTANT: we should find the already spawned or local restored global entity + pNewEnt = SV_FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + + Con_DPrintf( "Merging changes for global: %s\n", STRING( pTable->classname )); + + // ------------------------------------------------------------------------- + // Pass the "global" flag to the DLL to indicate this entity should only override + // a matching entity, not be spawned + if( svgame.dllFuncs.pfnRestore( pent, pSaveData, 1 ) > 0 ) + { + movedCount++; + } + else + { + if( SV_IsValidEdict( pNewEnt )) // update the table so decals can find parent entity + pTable->pent = pNewEnt; + SetBits( pent->v.flags, FL_KILLME ); + } + } + else + { + Con_DPrintf( "Transferring %s (%d)\n", STRING( pTable->classname ), NUM_FOR_EDICT( pent )); + + if( svgame.dllFuncs.pfnRestore( pent, pSaveData, 0 ) < 0 ) + { + SetBits( pent->v.flags, FL_KILLME ); + } + else + { + if( !FBitSet( pTable->flags, FENTTABLE_PLAYER ) && EntityInSolid( pent )) + { + // this can happen during normal processing - PVS is just a guess, + // some map areas won't exist in the new map + Con_DPrintf( "Suppressing %s\n", STRING( pTable->classname )); + SetBits( pent->v.flags, FL_KILLME ); + } + else + { + pTable->flags = FENTTABLE_REMOVED; + movedCount++; + } + } + } + + // remove any entities that were removed using UTIL_Remove() + // as a result of the above calls to UTIL_RemoveImmediate() + SV_FreeOldEntities (); + } + } + + return movedCount; +} + +/* +============= +LoadAdjacentEnts + +loading edicts from adjacency levels +============= +*/ +static void LoadAdjacentEnts( const char *pOldLevel, const char *pLandmarkName ) +{ + SAVE_HEADER header; + SAVERESTOREDATA currentLevelData, *pSaveData; + int i, test, flags, index, movedCount = 0; + qboolean foundprevious = false; + vec3_t landmarkOrigin; + + memset( ¤tLevelData, 0, sizeof( SAVERESTOREDATA )); + svgame.globals->pSaveData = ¤tLevelData; + sv.loadgame = sv.paused = true; + + // build the adjacent map list + svgame.dllFuncs.pfnParmsChangeLevel(); + + for( i = 0; i < currentLevelData.connectionCount; i++ ) + { + // make sure the previous level is in the connection list so we can + // bring over the player. + if( !Q_stricmp( currentLevelData.levelList[i].mapName, pOldLevel )) + foundprevious = true; + + for( test = 0; test < i; test++ ) + { + // only do maps once + if( !Q_stricmp( currentLevelData.levelList[i].mapName, currentLevelData.levelList[test].mapName )) + break; + } + + // map was already in the list + if( test < i ) continue; + + pSaveData = LoadSaveData( currentLevelData.levelList[i].mapName ); + + if( pSaveData ) + { + ParseSaveTables( pSaveData, &header, false ); + EntityPatchRead( pSaveData, currentLevelData.levelList[i].mapName ); + + pSaveData->time = sv.time; // - header.time; + pSaveData->fUseLandmark = true; + flags = movedCount = 0; + index = -1; + + // calculate landmark offset + LandmarkOrigin( ¤tLevelData, landmarkOrigin, pLandmarkName ); + LandmarkOrigin( pSaveData, pSaveData->vecLandmarkOffset, pLandmarkName ); + VectorSubtract( landmarkOrigin, pSaveData->vecLandmarkOffset, pSaveData->vecLandmarkOffset ); + + if( !Q_stricmp( currentLevelData.levelList[i].mapName, pOldLevel )) + SetBits( flags, FENTTABLE_PLAYER ); + + while( 1 ) + { + index = EntryInTable( pSaveData, sv.name, index ); + if( index < 0 ) break; + SetBits( flags, BIT( index )); + } + + if( flags ) movedCount = CreateEntityTransitionList( pSaveData, flags ); + + // if ents were moved, rewrite entity table to save file + if( movedCount ) EntityPatchWrite( pSaveData, currentLevelData.levelList[i].mapName ); + + // move the decals from another level + LoadClientState( pSaveData, currentLevelData.levelList[i].mapName, true, true ); + + SaveFinish( pSaveData ); + } + } + + svgame.globals->pSaveData = NULL; + + if( !foundprevious ) + Host_Error( "Level transition ERROR\nCan't find connection to %s from %s\n", pOldLevel, sv.name ); +} + +/* +============= +SV_LoadGameState + +loading entities from the savegame +============= +*/ +int SV_LoadGameState( char const *level ) +{ + return LoadGameState( level, false ); +} + +/* +============= +SV_ClearGameState + +clear current game state +============= +*/ +void SV_ClearGameState( void ) +{ + ClearSaveDir(); + + if( svgame.dllFuncs.pfnResetGlobalState != NULL ) + svgame.dllFuncs.pfnResetGlobalState(); +} + +/* +============= +SV_ChangeLevel +============= +*/ +void SV_ChangeLevel( qboolean loadfromsavedgame, const char *mapname, const char *start, qboolean background ) +{ + char level[MAX_QPATH]; + char oldlevel[MAX_QPATH]; + char _startspot[MAX_QPATH]; + char *startspot = NULL; + SAVERESTOREDATA *pSaveData = NULL; + + if( sv.state != ss_active ) + { + Con_Printf( S_ERROR "server not running\n"); + return; + } + + if( start ) + { + Q_strncpy( _startspot, start, MAX_STRING ); + startspot = _startspot; + } + + Q_strncpy( level, mapname, MAX_STRING ); + Q_strncpy( oldlevel, sv.name, MAX_STRING ); + + if( loadfromsavedgame ) + { + // smooth transition in-progress + svgame.globals->changelevel = true; + + // save the current level's state + pSaveData = SaveGameState( true ); + } + + SV_InactivateClients (); + SV_FinalMessage( "", true ); + SV_DeactivateServer (); + + if( !SV_SpawnServer( level, startspot, background )) + return; // ??? + + if( loadfromsavedgame ) + { + // finish saving gamestate + SaveFinish( pSaveData ); + + if( !LoadGameState( level, true )) + SV_SpawnEntities( level ); + LoadAdjacentEnts( oldlevel, startspot ); + + if( sv_newunit.value ) + ClearSaveDir(); + SV_ActivateServer( false ); + } + else + { + // classic quake changelevel + svgame.dllFuncs.pfnResetGlobalState(); + SV_SpawnEntities( level ); + SV_ActivateServer( true ); + } +} + +/* +============= +SV_LoadGame +============= +*/ +qboolean SV_LoadGame( const char *pPath ) +{ + qboolean validload = false; + GAME_HEADER gameHeader; + file_t *pFile; + int flags; + + if( host.type == HOST_DEDICATED ) + return false; + + if( !COM_CheckString( pPath )) + return false; + + // silently ignore if missed + if( !FS_FileExists( pPath, true )) + return false; + + // initialize game if needs + if( !SV_InitGame( )) + return false; + + pFile = FS_Open( pPath, "rb", true ); + + if( pFile ) + { + SV_ClearGameState(); + + if( SaveReadHeader( pFile, &gameHeader )) + { + DirectoryExtract( pFile, gameHeader.mapCount ); + validload = true; + } + FS_Close( pFile ); + + if( validload ) + { + // now check for map problems + flags = SV_MapIsValid( gameHeader.mapName, GI->sp_entity, NULL ); + + if( FBitSet( flags, MAP_INVALID_VERSION )) + { + Con_Printf( S_ERROR "map %s is invalid or not supported\n", gameHeader.mapName ); + validload = false; + } + + if( !FBitSet( flags, MAP_IS_EXIST )) + { + Con_Printf( S_ERROR "map %s doesn't exist\n", gameHeader.mapName ); + validload = false; + } + } + } + + if( !validload ) + { + Con_Printf( S_ERROR "Couldn't load %s\n", pPath ); + return false; + } + + Con_Printf( "Loading game from %s...\n", pPath ); + Cvar_FullSet( "maxplayers", "1", FCVAR_LATCH ); + Cvar_SetValue( "deathmatch", 0 ); + Cvar_SetValue( "coop", 0 ); + COM_LoadGame( gameHeader.mapName ); + + return true; +} + +/* +================== +SV_SaveGame +================== +*/ +void SV_SaveGame( const char *pName ) +{ + char comment[80]; + int n, result; + string savename; + + if( !COM_CheckString( pName )) + return; + + // can we save at this point? + if( !IsValidSave( )) return; + + if( !Q_stricmp( pName, "new" )) + { + // scan for a free filename + for( n = 0; n < 1000; n++ ) + { + if( !SaveGetName( n, savename )) + return; + + if( !FS_FileExists( va( "%s%s.sav", DEFAULT_SAVE_DIRECTORY, savename ), true )) + break; + } + + if( n == 1000 ) + { + Con_Printf( S_ERROR "no free slots for savegame\n" ); + return; + } + } + else Q_strncpy( savename, pName, sizeof( savename )); + + // unload previous image from memory (it's will be overwritten) + GL_FreeImage( va( "%s%s.bmp", DEFAULT_SAVE_DIRECTORY, savename )); + + SaveBuildComment( comment, sizeof( comment )); + result = SaveGameSlot( savename, comment ); + + if( result && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + CL_HudMessage( "GAMESAVED" ); // defined in titles.txt +} + +/* +================== +SV_GetLatestSave + +used for reload game after player death +================== +*/ +const char *SV_GetLatestSave( void ) +{ + static char savename[MAX_QPATH]; + long newest = 0, ft; + int i, found = 0; + search_t *t; + + if(( t = FS_Search( va( "%s*.sav", DEFAULT_SAVE_DIRECTORY ), true, true )) == NULL ) + return NULL; + + for( i = 0; i < t->numfilenames; i++ ) + { + ft = FS_FileTime( t->filenames[i], true ); + + // found a match? + if( ft > 0 ) + { + // should we use the matched? + if( !found || Host_CompareFileTime( newest, ft ) < 0 ) + { + Q_strncpy( savename, t->filenames[i], sizeof( savename )); + newest = ft; + found = 1; + } + } + } + + Mem_Free( t ); // release search + + if( found ) + return savename; + return NULL; +} + +/* +================== +SV_GetSaveComment + +check savegame for valid +================== +*/ +qboolean SV_GetSaveComment( const char *savename, char *comment ) +{ + int i, tag, size, nNumberOfFields, nFieldSize, tokenSize, tokenCount; + char *pData, *pSaveData, *pFieldName, **pTokenList; + string name, description; + file_t *f; + + if(( f = FS_Open( savename, "rb", true )) == NULL ) + { + // just not exist - clear comment + Q_strncpy( comment, "", MAX_STRING ); + return 0; + } + + FS_Read( f, &tag, sizeof( int )); + if( tag != SAVEGAME_HEADER ) + { + // invalid header + Q_strncpy( comment, "", MAX_STRING ); + FS_Close( f ); + return 0; + } + + FS_Read( f, &tag, sizeof( int )); + + if( tag == 0x0065 ) + { + Q_strncpy( comment, "old version Xash3D ", MAX_STRING ); + FS_Close( f ); + return 0; + } + + if( tag < SAVEGAME_VERSION ) + { + Q_strncpy( comment, "", MAX_STRING ); + FS_Close( f ); + return 0; + } + + if( tag > SAVEGAME_VERSION ) + { + // old xash version ? + Q_strncpy( comment, "", MAX_STRING ); + FS_Close( f ); + return 0; + } + + name[0] = '\0'; + comment[0] = '\0'; + + FS_Read( f, &size, sizeof( int )); + FS_Read( f, &tokenCount, sizeof( int )); // These two ints are the token list + FS_Read( f, &tokenSize, sizeof( int )); + size += tokenSize; + + // sanity check. + if( tokenCount < 0 || tokenCount > SAVE_HASHSTRINGS ) + { + Q_strncpy( comment, "", MAX_STRING ); + FS_Close( f ); + return 0; + } + + if( tokenSize < 0 || tokenSize > SAVE_HEAPSIZE ) + { + Q_strncpy( comment, "", MAX_STRING ); + FS_Close( f ); + return 0; + } + + pSaveData = (char *)Mem_Alloc( host.mempool, size ); + FS_Read( f, pSaveData, size ); + pData = pSaveData; + + // allocate a table for the strings, and parse the table + if( tokenSize > 0 ) + { + pTokenList = Mem_Alloc( host.mempool, tokenCount * sizeof( char* )); + + // make sure the token strings pointed to by the pToken hashtable. + for( i = 0; i < tokenCount; i++ ) + { + pTokenList[i] = *pData ? pData : NULL; // point to each string in the pToken table + while( *pData++ ); // find next token (after next null) + } + } + else pTokenList = NULL; + + // short, short (size, index of field name) + nFieldSize = *(short *)pData; + pData += sizeof( short ); + pFieldName = pTokenList[*(short *)pData]; + + if( Q_stricmp( pFieldName, "GameHeader" )) + { + Q_strncpy( comment, "", MAX_STRING ); + if( pTokenList ) Mem_Free( pTokenList ); + if( pSaveData ) Mem_Free( pSaveData ); + FS_Close( f ); + return 0; + } + + // int (fieldcount) + pData += sizeof( short ); + nNumberOfFields = (int)*pData; + pData += nFieldSize; + + // each field is a short (size), short (index of name), binary string of "size" bytes (data) + for( i = 0; i < nNumberOfFields; i++ ) + { + // Data order is: + // Size + // szName + // Actual Data + nFieldSize = *(short *)pData; + pData += sizeof( short ); + + pFieldName = pTokenList[*(short *)pData]; + pData += sizeof( short ); + + if( !Q_stricmp( pFieldName, "comment" )) + { + Q_strncpy( description, pData, nFieldSize ); + } + else if( !Q_stricmp( pFieldName, "mapName" )) + { + Q_strncpy( name, pData, nFieldSize ); + } + + // move to start of next field. + pData += nFieldSize; + } + + // delete the string table we allocated + if( pTokenList ) Mem_Free( pTokenList ); + if( pSaveData ) Mem_Free( pSaveData ); + FS_Close( f ); + + // at least mapname should be filled + if( Q_strlen( name ) > 0 ) + { + time_t fileTime; + const struct tm *file_tm; + string timestring; + + fileTime = FS_FileTime( savename, true ); + file_tm = localtime( &fileTime ); + + // split comment to sections + if( Q_strstr( savename, "quick" )) + Q_strncat( comment, "[quick]", CS_SIZE ); + else if( Q_strstr( savename, "autosave" )) + Q_strncat( comment, "[autosave]", CS_SIZE ); + Q_strncat( comment, description, CS_SIZE ); + strftime( timestring, sizeof ( timestring ), "%b%d %Y", file_tm ); + Q_strncpy( comment + CS_SIZE, timestring, CS_TIME ); + strftime( timestring, sizeof( timestring ), "%H:%M", file_tm ); + Q_strncpy( comment + CS_SIZE + CS_TIME, timestring, CS_TIME ); + Q_strncpy( comment + CS_SIZE + (CS_TIME * 2), description + CS_SIZE, CS_SIZE ); + + return 1; + } + + Q_strncpy( comment, "", MAX_STRING ); + + return 0; +} + +void SV_InitSaveRestore( void ) +{ + pfnSaveGameComment = COM_GetProcAddress( svgame.hInstance, "SV_SaveGameComment" ); +} \ No newline at end of file diff --git a/engine/server/sv_world.c b/engine/server/sv_world.c new file mode 100644 index 00000000..159d75e3 --- /dev/null +++ b/engine/server/sv_world.c @@ -0,0 +1,1742 @@ +/* +sv_world.c - world query functions +Copyright (C) 2008 Uncle Mike + +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 +the Free Software Foundation, either version 3 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. +*/ + +#include "common.h" +#include "server.h" +#include "const.h" +#include "pm_local.h" +#include "studio.h" + +typedef struct moveclip_s +{ + vec3_t boxmins, boxmaxs; // enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + const float *start, *end; + edict_t *passedict; + trace_t trace; + int type; // move type + int flags; // trace flags +} moveclip_t; + +/* +=============================================================================== + +HULL BOXES + +=============================================================================== +*/ + +static hull_t box_hull; +static mclipnode_t box_clipnodes[6]; +static mplane_t box_planes[6]; + +/* +=================== +SV_InitBoxHull + +Set up the planes and clipnodes so that the six floats of a bounding box +can just be stored out and get a proper hull_t structure. +=================== +*/ +void SV_InitBoxHull( void ) +{ + int i, side; + + box_hull.clipnodes = box_clipnodes; + box_hull.planes = box_planes; + box_hull.firstclipnode = 0; + box_hull.lastclipnode = 5; + + for( i = 0; i < 6; i++ ) + { + box_clipnodes[i].planenum = i; + + side = i & 1; + + box_clipnodes[i].children[side] = CONTENTS_EMPTY; + if( i != 5 ) box_clipnodes[i].children[side^1] = i + 1; + else box_clipnodes[i].children[side^1] = CONTENTS_SOLID; + + box_planes[i].type = i>>1; + box_planes[i].normal[i>>1] = 1; + box_planes[i].signbits = 0; + } + +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void SV_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) +{ + // calc up/down pointing + *pBlend = (*pPitch * 3); + + if( *pBlend < pseqdesc->blendstart[0] ) + { + *pPitch -= pseqdesc->blendstart[0] / 3.0f; + *pBlend = 0; + } + else if( *pBlend > pseqdesc->blendend[0] ) + { + *pPitch -= pseqdesc->blendend[0] / 3.0f; + *pBlend = 255; + } + else + { + if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error + *pBlend = 127; + else *pBlend = 255.0f * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + *pPitch = 0; + } +} + +/* +==================== +SV_CheckSphereIntersection + +check clients only +==================== +*/ +qboolean SV_CheckSphereIntersection( edict_t *ent, const vec3_t start, const vec3_t end ) +{ + int i, sequence; + float radiusSquared; + vec3_t traceOrg, traceDir; + studiohdr_t *pstudiohdr; + mstudioseqdesc_t *pseqdesc; + model_t *mod; + + if( !FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) + return true; + + if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL ) + return true; + + if(( pstudiohdr = (studiohdr_t *)Mod_StudioExtradata( mod )) == NULL ) + return true; + + sequence = ent->v.sequence; + if( sequence < 0 || sequence >= pstudiohdr->numseq ) + sequence = 0; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + + VectorCopy( start, traceOrg ); + VectorSubtract( end, start, traceDir ); + radiusSquared = 0.0f; + + for ( i = 0; i < 3; i++ ) + radiusSquared += Q_max( fabs( pseqdesc->bbmin[i] ), fabs( pseqdesc->bbmax[i] )); + + return SphereIntersect( ent->v.origin, radiusSquared, traceOrg, traceDir ); +} + + +/* +=================== +SV_HullForBox + +To keep everything totally uniform, bounding boxes are turned into small +BSP trees instead of being compared directly. +=================== +*/ +hull_t *SV_HullForBox( const vec3_t mins, const vec3_t maxs ) +{ + box_planes[0].dist = maxs[0]; + box_planes[1].dist = mins[0]; + box_planes[2].dist = maxs[1]; + box_planes[3].dist = mins[1]; + box_planes[4].dist = maxs[2]; + box_planes[5].dist = mins[2]; + + return &box_hull; +} + +/* +================== +SV_HullAutoSelect + +select the apropriate hull automatically +================== +*/ +hull_t *SV_HullAutoSelect( model_t *model, const vec3_t mins, const vec3_t maxs, const vec3_t size, vec3_t offset ) +{ + float curdiff; + float lastdiff = 999; + int i, hullNumber = 0; // assume we fail + vec3_t clip_size; + hull_t *hull; + + // NOTE: this is not matched with hardcoded values in some cases... + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + VectorSubtract( model->hulls[i].clip_maxs, model->hulls[i].clip_mins, clip_size ); + curdiff = floor( VectorAvg( size )) - floor( VectorAvg( clip_size )); + curdiff = fabs( curdiff ); + + if( curdiff < lastdiff ) + { + hullNumber = i; + lastdiff = curdiff; + } + } + + // TraceHull stuff + hull = &model->hulls[hullNumber]; + + // calculate an offset value to center the origin + // NOTE: never get offset of drawing hull + if( !hullNumber ) VectorCopy( hull->clip_mins, offset ); + else VectorSubtract( hull->clip_mins, mins, offset ); + + return hull; +} + +/* +================== +SV_HullForBsp + +forcing to select BSP hull +================== +*/ +hull_t *SV_HullForBsp( edict_t *ent, const vec3_t mins, const vec3_t maxs, vec3_t offset ) +{ + hull_t *hull; + model_t *model; + vec3_t size; + + if( svgame.physFuncs.SV_HullForBsp != NULL ) + { + hull = svgame.physFuncs.SV_HullForBsp( ent, mins, maxs, offset ); + if( hull ) return hull; + } + + // decide which clipping hull to use, based on the size + model = SV_ModelHandle( ent->v.modelindex ); + + if( !model || model->type != mod_brush ) + Host_Error( "Entity %i (%s) SOLID_BSP with a non bsp model %s\n", NUM_FOR_EDICT( ent ), SV_ClassName( ent ), STRING( ent->v.model )); + + VectorSubtract( maxs, mins, size ); + +#ifdef RANDOM_HULL_NULLIZATION + // author: The FiEctro + hull = &model->hulls[COM_RandomLong( 0, 0 )]; +#endif + // g-cont: find a better method to detect quake-maps? + if( FBitSet( world.flags, FWORLD_SKYSPHERE )) + { + // alternate hull select for quake maps + if( size[0] < 3.0f || ent->v.solid == SOLID_PORTAL ) + hull = &model->hulls[0]; + else if( size[0] <= 32.0f ) + hull = &model->hulls[1]; + else hull = &model->hulls[2]; + + VectorSubtract( hull->clip_mins, mins, offset ); + } + else + { + if( size[0] <= 8.0f || ent->v.solid == SOLID_PORTAL ) + { + hull = &model->hulls[0]; + VectorCopy( hull->clip_mins, offset ); + } + else + { + if( size[0] <= 36.0f ) + { + if( size[2] <= 36.0f ) + hull = &model->hulls[3]; + else hull = &model->hulls[1]; + } + else hull = &model->hulls[2]; + + VectorSubtract( hull->clip_mins, mins, offset ); + } + } + + VectorAdd( offset, ent->v.origin, offset ); + + return hull; +} + +/* +================ +SV_HullForEntity + +Returns a hull that can be used for testing or clipping an object of mins/maxs +size. +Offset is filled in to contain the adjustment that must be added to the +testing object's origin to get a point to use with the returned hull. +================ +*/ +hull_t *SV_HullForEntity( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset ) +{ + hull_t *hull; + vec3_t hullmins, hullmaxs; + + if( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) + { + if( ent->v.solid != SOLID_PORTAL ) + { + if( ent->v.movetype != MOVETYPE_PUSH && ent->v.movetype != MOVETYPE_PUSHSTEP ) + Host_Error( "'%s' has SOLID_BSP without MOVETYPE_PUSH or MOVETYPE_PUSHSTEP\n", SV_ClassName( ent )); + } + hull = SV_HullForBsp( ent, mins, maxs, offset ); + } + else + { + // create a temp hull from bounding box sizes + VectorSubtract( ent->v.mins, maxs, hullmins ); + VectorSubtract( ent->v.maxs, mins, hullmaxs ); + hull = SV_HullForBox( hullmins, hullmaxs ); + + VectorCopy( ent->v.origin, offset ); + } + + return hull; +} + +/* +==================== +SV_HullForStudioModel + +==================== +*/ +hull_t *SV_HullForStudioModel( edict_t *ent, vec3_t mins, vec3_t maxs, vec3_t offset, int *numhitboxes ) +{ + qboolean useComplexHull; + float scale = 0.5f; + hull_t *hull = NULL; + vec3_t size; + model_t *mod; + + if(( mod = SV_ModelHandle( ent->v.modelindex )) == NULL ) + { + *numhitboxes = 1; + return SV_HullForEntity( ent, mins, maxs, offset ); + } + + VectorSubtract( maxs, mins, size ); + useComplexHull = false; + + if( VectorIsNull( size ) && !FBitSet( svgame.globals->trace_flags, FTRACE_SIMPLEBOX )) + { + useComplexHull = true; + + if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) + { + if( sv_clienttrace.value == 0.0f ) + { + // so no way to trace studiomodels by hitboxes + // use bbox instead + useComplexHull = false; + } + else + { + scale = sv_clienttrace.value * 0.5f; + VectorSet( size, 1.0f, 1.0f, 1.0f ); + } + } + } + + if( FBitSet( mod->flags, STUDIO_TRACE_HITBOX ) || useComplexHull ) + { + VectorScale( size, scale, size ); + VectorClear( offset ); + + if( FBitSet( ent->v.flags, FL_CLIENT|FL_FAKECLIENT )) + { + studiohdr_t *pstudio; + mstudioseqdesc_t *pseqdesc; + byte controller[4]; + byte blending[2]; + vec3_t angles; + int iBlend; + + pstudio = Mod_StudioExtradata( mod ); + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudio + pstudio->seqindex) + ent->v.sequence; + VectorCopy( ent->v.angles, angles ); + + SV_StudioPlayerBlend( pseqdesc, &iBlend, &angles[PITCH] ); + + controller[0] = controller[1] = 0x7F; + controller[2] = controller[3] = 0x7F; + blending[0] = (byte)iBlend; + blending[1] = 0; + + hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, angles, ent->v.origin, size, controller, blending, numhitboxes, ent ); + } + else + { + hull = Mod_HullForStudio( mod, ent->v.frame, ent->v.sequence, ent->v.angles, ent->v.origin, size, ent->v.controller, ent->v.blending, numhitboxes, ent ); + } + } + + if( hull ) return hull; + + *numhitboxes = 1; + return SV_HullForEntity( ent, mins, maxs, offset ); +} + +/* +=============================================================================== + +ENTITY AREA CHECKING + +=============================================================================== +*/ +static int iTouchLinkSemaphore = 0; // prevent recursion when SV_TouchLinks is active +areanode_t sv_areanodes[AREA_NODES]; +static int sv_numareanodes; + +/* +=============== +SV_CreateAreaNode + +builds a uniformly subdivided tree for the given world size +=============== +*/ +areanode_t *SV_CreateAreaNode( int depth, vec3_t mins, vec3_t maxs ) +{ + areanode_t *anode; + vec3_t size; + vec3_t mins1, maxs1; + vec3_t mins2, maxs2; + + anode = &sv_areanodes[sv_numareanodes++]; + + ClearLink( &anode->trigger_edicts ); + ClearLink( &anode->solid_edicts ); + ClearLink( &anode->portal_edicts ); + + if( depth == AREA_DEPTH ) + { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract( maxs, mins, size ); + if( size[0] > size[1] ) + anode->axis = 0; + else anode->axis = 1; + + anode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] ); + VectorCopy( mins, mins1 ); + VectorCopy( mins, mins2 ); + VectorCopy( maxs, maxs1 ); + VectorCopy( maxs, maxs2 ); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + anode->children[0] = SV_CreateAreaNode( depth+1, mins2, maxs2 ); + anode->children[1] = SV_CreateAreaNode( depth+1, mins1, maxs1 ); + + return anode; +} + +/* +=============== +SV_ClearWorld + +=============== +*/ +void SV_ClearWorld( void ) +{ + int i; + + SV_InitBoxHull(); // for box testing + + // clear lightstyles + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + sv.lightstyles[i].value = 256.0f; + sv.lightstyles[i].time = 0.0f; + } + + memset( sv_areanodes, 0, sizeof( sv_areanodes )); + iTouchLinkSemaphore = 0; + sv_numareanodes = 0; + + SV_CreateAreaNode( 0, sv.worldmodel->mins, sv.worldmodel->maxs ); +} + +/* +=============== +SV_UnlinkEdict +=============== +*/ +void SV_UnlinkEdict( edict_t *ent ) +{ + // not linked in anywhere + if( !ent->area.prev ) return; + + RemoveLink( &ent->area ); + ent->area.prev = NULL; + ent->area.next = NULL; +} + +/* +==================== +SV_TouchLinks +==================== +*/ +void SV_TouchLinks( edict_t *ent, areanode_t *node ) +{ + link_t *l, *next; + edict_t *touch; + hull_t *hull; + vec3_t test, offset; + model_t *mod; + + // touch linked edicts + for( l = node->trigger_edicts.next; l != &node->trigger_edicts; l = next ) + { + next = l->next; + touch = EDICT_FROM_AREA( l ); + + if( svgame.physFuncs.SV_TriggerTouch != NULL ) + { + // user dll can override trigger checking (Xash3D extension) + if( !svgame.physFuncs.SV_TriggerTouch( ent, touch )) + continue; + } + else + { + if( touch == ent || touch->v.solid != SOLID_TRIGGER ) // disabled ? + continue; + + if( touch->v.groupinfo && ent->v.groupinfo ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, ent->v.groupinfo )) + continue; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, ent->v.groupinfo )) + continue; + } + + if( !BoundsIntersect( ent->v.absmin, ent->v.absmax, touch->v.absmin, touch->v.absmax )) + continue; + + mod = SV_ModelHandle( touch->v.modelindex ); + + // check brush triggers accuracy + if( mod && mod->type == mod_brush ) + { + // force to select bsp-hull + hull = SV_HullForBsp( touch, ent->v.mins, ent->v.maxs, offset ); + + // support for rotational triggers + if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles )) + { + matrix4x4 matrix; + Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, ent->v.origin, test ); + } + else + { + // offset the test point appropriately for this hull. + VectorSubtract( ent->v.origin, offset, test ); + } + + // test hull for intersection with this model + if( PM_HullPointContents( hull, hull->firstclipnode, test ) != CONTENTS_SOLID ) + continue; + } + } + + // never touch the triggers when "playersonly" is active + if( !sv.playersonly ) + { + svgame.globals->time = sv.time; + svgame.dllFuncs.pfnTouch( touch, ent ); + } + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( ent->v.absmax[node->axis] > node->dist ) + SV_TouchLinks( ent, node->children[0] ); + if( ent->v.absmin[node->axis] < node->dist ) + SV_TouchLinks( ent, node->children[1] ); +} + +/* +=============== +SV_FindTouchedLeafs + +=============== +*/ +void SV_FindTouchedLeafs( edict_t *ent, mnode_t *node, int *headnode ) +{ + int sides; + mleaf_t *leaf; + + if( node->contents == CONTENTS_SOLID ) + return; + + // add an efrag if the node is a leaf + if( node->contents < 0 ) + { + if( ent->num_leafs > ( MAX_ENT_LEAFS - 1 )) + { + // continue counting leafs, + // so we know how many it's overrun + ent->num_leafs = (MAX_ENT_LEAFS + 1); + } + else + { + leaf = (mleaf_t *)node; + ent->leafnums[ent->num_leafs] = leaf->cluster; + ent->num_leafs++; + } + return; + } + + // NODE_MIXED + sides = BOX_ON_PLANE_SIDE( ent->v.absmin, ent->v.absmax, node->plane ); + + if(( sides == 3 ) && ( *headnode == -1 )) + *headnode = node - sv.worldmodel->nodes; + + // recurse down the contacted sides + if( sides & 1 ) SV_FindTouchedLeafs( ent, node->children[0], headnode ); + if( sides & 2 ) SV_FindTouchedLeafs( ent, node->children[1], headnode ); +} + +/* +=============== +SV_LinkEdict +=============== +*/ +void SV_LinkEdict( edict_t *ent, qboolean touch_triggers ) +{ + areanode_t *node; + int headnode; + + if( ent->area.prev ) SV_UnlinkEdict( ent ); // unlink from old position + if( ent == svgame.edicts ) return; // don't add the world + if( !SV_IsValidEdict( ent )) return; // never add freed ents + + // set the abs box + svgame.dllFuncs.pfnSetAbsBox( ent ); + + if( ent->v.movetype == MOVETYPE_FOLLOW && SV_IsValidEdict( ent->v.aiment )) + { + memcpy( ent->leafnums, ent->v.aiment->leafnums, sizeof( ent->leafnums )); + ent->num_leafs = ent->v.aiment->num_leafs; + ent->headnode = ent->v.aiment->headnode; + } + else + { + // link to PVS leafs + ent->num_leafs = 0; + ent->headnode = -1; + headnode = -1; + + if( ent->v.modelindex ) + SV_FindTouchedLeafs( ent, sv.worldmodel->nodes, &headnode ); + + if( ent->num_leafs > MAX_ENT_LEAFS ) + { + memset( ent->leafnums, -1, sizeof( ent->leafnums )); + ent->num_leafs = 0; // so we use headnode instead + ent->headnode = headnode; + } + } + + // ignore non-solid bodies + if( ent->v.solid == SOLID_NOT && ent->v.skin >= CONTENTS_EMPTY ) + return; + + // find the first node that the ent's box crosses + node = sv_areanodes; + + while( 1 ) + { + if( node->axis == -1 ) break; + if( ent->v.absmin[node->axis] > node->dist ) + node = node->children[0]; + else if( ent->v.absmax[node->axis] < node->dist ) + node = node->children[1]; + else break; // crosses the node + } + + // link it in + if( ent->v.solid == SOLID_TRIGGER ) + InsertLinkBefore( &ent->area, &node->trigger_edicts ); + else if( ent->v.solid == SOLID_PORTAL ) + InsertLinkBefore( &ent->area, &node->portal_edicts ); + else InsertLinkBefore( &ent->area, &node->solid_edicts ); + + if( touch_triggers && !iTouchLinkSemaphore ) + { + iTouchLinkSemaphore = true; + SV_TouchLinks( ent, sv_areanodes ); + iTouchLinkSemaphore = false; + } +} + +/* +=============================================================================== + +POINT TESTING IN HULLS + +=============================================================================== +*/ +void SV_WaterLinks( const vec3_t origin, int *pCont, areanode_t *node ) +{ + link_t *l, *next; + edict_t *touch; + hull_t *hull; + vec3_t test, offset; + model_t *mod; + + // get water edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + touch = EDICT_FROM_AREA( l ); + + if( touch->v.solid != SOLID_NOT ) // disabled ? + continue; + + if( touch->v.groupinfo ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, svs.groupmask )) + continue; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, svs.groupmask )) + continue; + } + + mod = SV_ModelHandle( touch->v.modelindex ); + + // only brushes can have special contents + if( !mod || mod->type != mod_brush ) + continue; + + if( !BoundsIntersect( origin, origin, touch->v.absmin, touch->v.absmax )) + continue; + + // check water brushes accuracy + hull = SV_HullForBsp( touch, vec3_origin, vec3_origin, offset ); + + // support for rotational water + if( FBitSet( mod->flags, MODEL_HAS_ORIGIN ) && !VectorIsNull( touch->v.angles )) + { + matrix4x4 matrix; + Matrix4x4_CreateFromEntity( matrix, touch->v.angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, origin, test ); + } + else + { + // offset the test point appropriately for this hull. + VectorSubtract( origin, offset, test ); + } + + // test hull for intersection with this model + if( PM_HullPointContents( hull, hull->firstclipnode, test ) == CONTENTS_EMPTY ) + continue; + + // compare contents ranking + if( RankForContents( touch->v.skin ) > RankForContents( *pCont )) + *pCont = touch->v.skin; // new content has more priority + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( origin[node->axis] > node->dist ) + SV_WaterLinks( origin, pCont, node->children[0] ); + if( origin[node->axis] < node->dist ) + SV_WaterLinks( origin, pCont, node->children[1] ); +} + +/* +============= +SV_TruePointContents + +============= +*/ +int SV_TruePointContents( const vec3_t p ) +{ + int cont; + + // sanity check + if( !p ) return CONTENTS_NONE; + + // get base contents from world + cont = PM_HullPointContents( &sv.worldmodel->hulls[0], 0, p ); + + // check all water entities + SV_WaterLinks( p, &cont, sv_areanodes ); + + return cont; +} + +/* +============= +SV_PointContents + +============= +*/ +int SV_PointContents( const vec3_t p ) +{ + int cont = SV_TruePointContents( p ); + + if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN ) + cont = CONTENTS_WATER; + return cont; +} + +//=========================================================================== + +/* +============ +SV_TestEntityPosition + +returns true if the entity is in solid currently +============ +*/ +qboolean SV_TestEntityPosition( edict_t *ent, edict_t *blocker ) +{ + trace_t trace; + qboolean monsterClip = FBitSet( ent->v.flags, FL_MONSTERCLIP ) ? true : false; + + if( ent->v.flags & (FL_CLIENT|FL_FAKECLIENT)) + { + // to avoid falling through tracktrain update client mins\maxs here + if( ent->v.flags & FL_DUCKING ) + SV_SetMinMaxSize( ent, svgame.pmove->player_mins[1], svgame.pmove->player_maxs[1], true ); + else SV_SetMinMaxSize( ent, svgame.pmove->player_mins[0], svgame.pmove->player_maxs[0], true ); + } + + trace = SV_Move( ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, MOVE_NORMAL|FMOVE_SIMPLEBOX, ent, monsterClip ); + + if( SV_IsValidEdict( blocker ) && SV_IsValidEdict( trace.ent )) + { + if( trace.ent->v.movetype == MOVETYPE_PUSH || trace.ent == blocker ) + return trace.startsolid; + return false; + } + return trace.startsolid; +} + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ +/* +================== +SV_ClipMoveToEntity + +Handles selection or creation of a clipping hull, and offseting (and +eventually rotation) of the end points +================== +*/ +void SV_ClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) +{ + hull_t *hull; + model_t *model; + vec3_t start_l, end_l; + vec3_t offset, temp; + int last_hitgroup; + trace_t trace_hitbox; + int i, j, hullcount; + qboolean rotated, transform_bbox; + matrix4x4 matrix; + + memset( trace, 0, sizeof( trace_t )); + VectorCopy( end, trace->endpos ); + trace->fraction = 1.0f; + trace->allsolid = 1; + + model = SV_ModelHandle( ent->v.modelindex ); + + if( model && model->type == mod_studio ) + { + hull = SV_HullForStudioModel( ent, mins, maxs, offset, &hullcount ); + } + else + { + hull = SV_HullForEntity( ent, mins, maxs, offset ); + hullcount = 1; + } + + // rotate start and end into the models frame of reference + if(( ent->v.solid == SOLID_BSP || ent->v.solid == SOLID_PORTAL ) && !VectorIsNull( ent->v.angles )) + rotated = true; + else rotated = false; + + if( FBitSet( host.features, ENGINE_PHYSICS_PUSHER_EXT )) + { + // keep untransformed bbox less than 45 degress or train on subtransit.bsp will stop working + if(( check_angles( ent->v.angles[0] ) || check_angles( ent->v.angles[2] )) && !VectorIsNull( mins )) + transform_bbox = true; + else transform_bbox = false; + } + else transform_bbox = false; + + if( rotated ) + { + vec3_t out_mins, out_maxs; + + if( transform_bbox ) + Matrix4x4_CreateFromEntity( matrix, ent->v.angles, ent->v.origin, 1.0f ); + else Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f ); + + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + + if( transform_bbox ) + { + World_TransformAABB( matrix, mins, maxs, out_mins, out_maxs ); + VectorSubtract( hull->clip_mins, out_mins, offset ); // calc new local offset + + for( j = 0; j < 3; j++ ) + { + if( start_l[j] >= 0.0f ) + start_l[j] -= offset[j]; + else start_l[j] += offset[j]; + if( end_l[j] >= 0.0f ) + end_l[j] -= offset[j]; + else end_l[j] += offset[j]; + } + } + } + else + { + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + } + + if( hullcount == 1 ) + { + PM_RecursiveHullCheck( hull, hull->firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)trace ); + } + else + { + last_hitgroup = 0; + + for( i = 0; i < hullcount; i++ ) + { + memset( &trace_hitbox, 0, sizeof( trace_t )); + VectorCopy( end, trace_hitbox.endpos ); + trace_hitbox.fraction = 1.0; + trace_hitbox.allsolid = 1; + + PM_RecursiveHullCheck( &hull[i], hull[i].firstclipnode, 0.0f, 1.0f, start_l, end_l, (pmtrace_t *)&trace_hitbox ); + + if( i == 0 || trace_hitbox.allsolid || trace_hitbox.startsolid || trace_hitbox.fraction < trace->fraction ) + { + if( trace->startsolid ) + { + *trace = trace_hitbox; + trace->startsolid = true; + } + else *trace = trace_hitbox; + + last_hitgroup = i; + } + } + + trace->hitgroup = Mod_HitgroupForStudioHull( last_hitgroup ); + } + + if( trace->fraction != 1.0f ) + { + // compute endpos (generic case) + VectorLerp( start, trace->fraction, end, trace->endpos ); + + if( rotated ) + { + // transform plane + VectorCopy( trace->plane.normal, temp ); + Matrix4x4_TransformPositivePlane( matrix, temp, trace->plane.dist, trace->plane.normal, &trace->plane.dist ); + } + else + { + trace->plane.dist = DotProduct( trace->endpos, trace->plane.normal ); + } + } + + if( trace->fraction < 1.0f || trace->startsolid ) + trace->ent = ent; +} + +/* +================== +SV_PortalCSG + +a portal is flush with a world surface behind it. this causes problems. namely that we can't pass through the portal plane +if the bsp behind it prevents out origin from getting through. so if the trace was clipped and ended infront of the portal, +continue the trace to the edges of the portal cutout instead. +================== +*/ +void SV_PortalCSG( edict_t *portal, const vec3_t trace_mins, const vec3_t trace_maxs, const vec3_t start, const vec3_t end, trace_t *trace ) +{ + vec4_t planes[6]; //far, near, right, left, up, down + int plane, k; + vec3_t worldpos; + float bestfrac; + int hitplane; + model_t *model; + float portalradius; + + // only run this code if we impacted on the portal's parent. + if( trace->fraction == 1.0f && !trace->startsolid ) + return; + + // decide which clipping hull to use, based on the size + model = SV_ModelHandle( portal->v.modelindex ); + + if( !model || model->type != mod_brush ) + return; + + // make sure we use a sane valid position. + if( trace->startsolid ) VectorCopy( start, worldpos ); + else VectorCopy( trace->endpos, worldpos ); + + // determine the csg area. normals should be facing in + AngleVectors( portal->v.angles, planes[1], planes[3], planes[5] ); + VectorNegate(planes[1], planes[0]); + VectorNegate(planes[3], planes[2]); + VectorNegate(planes[5], planes[4]); + + portalradius = model->radius * 0.5f; + planes[0][3] = DotProduct( portal->v.origin, planes[0] ) - (4.0f / 32.0f); + planes[1][3] = DotProduct( portal->v.origin, planes[1] ) - (4.0f / 32.0f); //an epsilon beyond the portal + planes[2][3] = DotProduct( portal->v.origin, planes[2] ) - portalradius; + planes[3][3] = DotProduct( portal->v.origin, planes[3] ) - portalradius; + planes[4][3] = DotProduct( portal->v.origin, planes[4] ) - portalradius; + planes[5][3] = DotProduct( portal->v.origin, planes[5] ) - portalradius; + + // if we're actually inside the csg region + for( plane = 0; plane < 6; plane++ ) + { + float d = DotProduct( worldpos, planes[plane] ); + vec3_t nearest; + + for( k = 0; k < 3; k++ ) + nearest[k] = (planes[plane][k]>=0) ? trace_maxs[k] : trace_mins[k]; + + // front plane gets further away with side + if( !plane ) + { + planes[plane][3] -= DotProduct( nearest, planes[plane] ); + } + else if( plane > 1 ) + { + // side planes get nearer with size + planes[plane][3] += 24; // DotProduct( nearest, planes[plane] ); + } + + if( d - planes[plane][3] >= 0 ) + continue; // endpos is inside + else return; // end is already outside + } + + // yup, we're inside, the trace shouldn't end where it actually did + bestfrac = 1; + hitplane = -1; + + for( plane = 0; plane < 6; plane++ ) + { + float ds = DotProduct( start, planes[plane] ) - planes[plane][3]; + float de = DotProduct( end, planes[plane] ) - planes[plane][3]; + float frac; + + if( ds >= 0 && de < 0 ) + { + frac = (ds) / (ds - de); + if( frac < bestfrac ) + { + if( frac < 0 ) + frac = 0; + bestfrac = frac; + hitplane = plane; + } + } + } + + trace->startsolid = trace->allsolid = false; + + // if we cross the front of the portal, don't shorten the trace, + // that will artificially clip us + if( hitplane == 0 && trace->fraction > bestfrac ) + return; + + // okay, elongate to clip to the portal hole properly. + VectorLerp( start, bestfrac, end, trace->endpos ); + trace->fraction = bestfrac; + + if( hitplane >= 0 ) + { + VectorCopy( planes[hitplane], trace->plane.normal ); + trace->plane.dist = planes[hitplane][3]; + if( hitplane == 1 ) trace->ent = portal; + } +} + +/* +================== +SV_CustomClipMoveToEntity + +A part of physics engine implementation +or custom physics implementation +================== +*/ +void SV_CustomClipMoveToEntity( edict_t *ent, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, trace_t *trace ) +{ + memset( trace, 0, sizeof( trace_t )); + VectorCopy( end, trace->endpos ); + trace->allsolid = true; + trace->fraction = 1.0f; + + if( svgame.physFuncs.ClipMoveToEntity != NULL ) + { + // do custom sweep test + svgame.physFuncs.ClipMoveToEntity( ent, start, mins, maxs, end, trace ); + } + else + { + // function is missed, so we didn't hit anything + trace->allsolid = false; + } +} + +/* +==================== +SV_ClipToEntity + +generic clip function +==================== +*/ +static qboolean SV_ClipToEntity( edict_t *touch, moveclip_t *clip ) +{ + trace_t trace; + model_t *mod; + + if( touch->v.groupinfo && SV_IsValidEdict( clip->passedict ) && clip->passedict->v.groupinfo != 0 ) + { + if( svs.groupop == GROUP_OP_AND && !FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo )) + return true; + + if( svs.groupop == GROUP_OP_NAND && FBitSet( touch->v.groupinfo, clip->passedict->v.groupinfo )) + return true; + } + + if( touch == clip->passedict || touch->v.solid == SOLID_NOT ) + return true; + + if( touch->v.solid == SOLID_TRIGGER ) + Host_Error( "trigger in clipping list\n" ); + + // custom user filter + if( svgame.dllFuncs2.pfnShouldCollide ) + { + if( !svgame.dllFuncs2.pfnShouldCollide( touch, clip->passedict )) + return true; // originally this was 'return' but is completely wrong! + } + + // monsterclip filter (solid custom is a static or dynamic bodies) + if( touch->v.solid == SOLID_BSP || touch->v.solid == SOLID_CUSTOM ) + { + if( FBitSet( touch->v.flags, FL_MONSTERCLIP )) + { + // func_monsterclip works only with monsters that have same flag! + if( !FBitSet( clip->flags, FMOVE_MONSTERCLIP )) + return true; + } + } + else + { + // ignore all monsters but pushables + if( clip->type == MOVE_NOMONSTERS && touch->v.movetype != MOVETYPE_PUSHSTEP ) + return true; + } + + mod = SV_ModelHandle( touch->v.modelindex ); + + if( mod && mod->type == mod_brush && FBitSet( clip->flags, FMOVE_IGNORE_GLASS )) + { + // we ignore brushes with rendermode != kRenderNormal and without FL_WORLDBRUSH set + if( touch->v.rendermode != kRenderNormal && !FBitSet( touch->v.flags, FL_WORLDBRUSH )) + return true; + } + + if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax )) + return true; + + // aditional check to intersects clients with sphere + if( touch->v.solid != SOLID_SLIDEBOX && !SV_CheckSphereIntersection( touch, clip->start, clip->end )) + return true; + + // Xash3D extension + if( SV_IsValidEdict( clip->passedict ) && clip->passedict->v.solid == SOLID_TRIGGER ) + { + // never collide items and player (because call "give" always stuck item in player + // and total trace returns fail (old half-life bug) + // items touch should be done in SV_TouchLinks not here + if( FBitSet( touch->v.flags, FL_CLIENT|FL_FAKECLIENT )) + return true; + } + + // g-cont. make sure what size is really zero - check all the components + if( SV_IsValidEdict( clip->passedict ) && !VectorIsNull( clip->passedict->v.size ) && VectorIsNull( touch->v.size )) + return true; // points never interact + + // might intersect, so do an exact clip + if( clip->trace.allsolid ) return false; + + if( SV_IsValidEdict( clip->passedict )) + { + if( touch->v.owner == clip->passedict ) + return true; // don't clip against own missiles + if( clip->passedict->v.owner == touch ) + return true; // don't clip against owner + } + + // make sure we don't hit the world if we're inside the portal + if( touch->v.solid == SOLID_PORTAL ) + SV_PortalCSG( touch, clip->mins, clip->maxs, clip->start, clip->end, &clip->trace ); + + if( touch->v.solid == SOLID_CUSTOM ) + SV_CustomClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); + else if( touch->v.flags & FL_MONSTER ) + SV_ClipMoveToEntity( touch, clip->start, clip->mins2, clip->maxs2, clip->end, &trace ); + else SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); + + clip->trace = World_CombineTraces( &clip->trace, &trace, touch ); + + return true; +} + +/* +==================== +SV_ClipToLinks + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +static void SV_ClipToLinks( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + edict_t *touch; + + // touch linked edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + + touch = EDICT_FROM_AREA( l ); + + if( !SV_ClipToEntity( touch, clip )) + return; // trace.allsoild + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( clip->boxmaxs[node->axis] > node->dist ) + SV_ClipToLinks( node->children[0], clip ); + if( clip->boxmins[node->axis] < node->dist ) + SV_ClipToLinks( node->children[1], clip ); +} + +/* +==================== +SV_ClipToPortals + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +static void SV_ClipToPortals( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + edict_t *touch; + + // touch linked edicts + for( l = node->portal_edicts.next; l != &node->portal_edicts; l = next ) + { + next = l->next; + + touch = EDICT_FROM_AREA( l ); + + if( !SV_ClipToEntity( touch, clip )) + return; // trace.allsoild + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( clip->boxmaxs[node->axis] > node->dist ) + SV_ClipToPortals( node->children[0], clip ); + if( clip->boxmins[node->axis] < node->dist ) + SV_ClipToPortals( node->children[1], clip ); +} + +/* +==================== +SV_ClipToWorldBrush + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +void SV_ClipToWorldBrush( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + edict_t *touch; + trace_t trace; + + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + + touch = EDICT_FROM_AREA( l ); + + if( touch->v.solid != SOLID_BSP || touch == clip->passedict || !( touch->v.flags & FL_WORLDBRUSH )) + continue; + + if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->v.absmin, touch->v.absmax )) + continue; + + if( clip->trace.allsolid ) return; + + SV_ClipMoveToEntity( touch, clip->start, clip->mins, clip->maxs, clip->end, &trace ); + + clip->trace = World_CombineTraces( &clip->trace, &trace, touch ); + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( clip->boxmaxs[node->axis] > node->dist ) + SV_ClipToWorldBrush( node->children[0], clip ); + + if( clip->boxmins[node->axis] < node->dist ) + SV_ClipToWorldBrush( node->children[1], clip ); +} + +/* +================== +SV_Move +================== +*/ +trace_t SV_Move( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e, qboolean monsterclip ) +{ + moveclip_t clip; + vec3_t trace_endpos; + float trace_fraction; + + memset( &clip, 0, sizeof( moveclip_t )); + SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace ); + + if( clip.trace.fraction != 0.0f ) + { + VectorCopy( clip.trace.endpos, trace_endpos ); + trace_fraction = clip.trace.fraction; + clip.trace.fraction = 1.0f; + clip.start = start; + clip.end = trace_endpos; + clip.type = (type & 0xFF); + clip.flags = (type & 0xFF00); + clip.passedict = (e) ? e : EDICT_NUM( 0 ); + clip.mins = mins; + clip.maxs = maxs; + + if( monsterclip && !FBitSet( host.features, ENGINE_QUAKE_COMPATIBLE )) + SetBits( clip.flags, FMOVE_MONSTERCLIP ); + + if( clip.type == MOVE_MISSILE ) + { + VectorSet( clip.mins2, -15.0f, -15.0f, -15.0f ); + VectorSet( clip.maxs2, 15.0f, 15.0f, 15.0f ); + } + else + { + VectorCopy( mins, clip.mins2 ); + VectorCopy( maxs, clip.maxs2 ); + } + + World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs ); + SV_ClipToLinks( sv_areanodes, &clip ); + SV_ClipToPortals( sv_areanodes, &clip ); + + clip.trace.fraction *= trace_fraction; + svgame.globals->trace_ent = clip.trace.ent; + } + + SV_CopyTraceToGlobal( &clip.trace ); + + return clip.trace; +} + +trace_t SV_MoveNormal( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) +{ + return SV_Move( start, mins, maxs, end, type, e, false ); +} + +/* +================== +SV_MoveNoEnts +================== +*/ +trace_t SV_MoveNoEnts( const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, int type, edict_t *e ) +{ + moveclip_t clip; + vec3_t trace_endpos; + float trace_fraction; + + memset( &clip, 0, sizeof( moveclip_t )); + SV_ClipMoveToEntity( EDICT_NUM( 0 ), start, mins, maxs, end, &clip.trace ); + + if( clip.trace.fraction != 0.0f ) + { + VectorCopy( clip.trace.endpos, trace_endpos ); + trace_fraction = clip.trace.fraction; + clip.trace.fraction = 1.0f; + clip.start = start; + clip.end = trace_endpos; + clip.type = (type & 0xFF); + clip.flags = (type & 0xFF00); + clip.passedict = (e) ? e : EDICT_NUM( 0 ); + clip.mins = mins; + clip.maxs = maxs; + + VectorCopy( mins, clip.mins2 ); + VectorCopy( maxs, clip.maxs2 ); + + World_MoveBounds( start, clip.mins2, clip.maxs2, trace_endpos, clip.boxmins, clip.boxmaxs ); + SV_ClipToWorldBrush( sv_areanodes, &clip ); + SV_ClipToPortals( sv_areanodes, &clip ); + + clip.trace.fraction *= trace_fraction; + svgame.globals->trace_ent = clip.trace.ent; + } + + SV_CopyTraceToGlobal( &clip.trace ); + + return clip.trace; +} + +/* +================== +SV_TraceSurface + +find the face where the traceline hit +assume pTextureEntity is valid +================== +*/ +msurface_t *SV_TraceSurface( edict_t *ent, const vec3_t start, const vec3_t end ) +{ + matrix4x4 matrix; + model_t *bmodel; + hull_t *hull; + vec3_t start_l, end_l; + vec3_t offset; + + bmodel = SV_ModelHandle( ent->v.modelindex ); + if( !bmodel || bmodel->type != mod_brush ) + return NULL; + + hull = SV_HullForBsp( ent, vec3_origin, vec3_origin, offset ); + + VectorSubtract( start, offset, start_l ); + VectorSubtract( end, offset, end_l ); + + // rotate start and end into the models frame of reference + if( !VectorIsNull( ent->v.angles )) + { + Matrix4x4_CreateFromEntity( matrix, ent->v.angles, offset, 1.0f ); + Matrix4x4_VectorITransform( matrix, start, start_l ); + Matrix4x4_VectorITransform( matrix, end, end_l ); + } + + return PM_RecursiveSurfCheck( bmodel, &bmodel->nodes[hull->firstclipnode], start_l, end_l ); +} + +/* +================== +SV_TraceTexture + +find the face where the traceline hit +assume pTextureEntity is valid +================== +*/ +const char *SV_TraceTexture( edict_t *ent, const vec3_t start, const vec3_t end ) +{ + msurface_t *surf = SV_TraceSurface( ent, start, end ); + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return NULL; + + return surf->texinfo->texture->name; +} + +/* +================== +SV_MoveToss +================== +*/ +trace_t SV_MoveToss( edict_t *tossent, edict_t *ignore ) +{ + float gravity; + vec3_t move, end; + vec3_t original_origin; + vec3_t original_velocity; + vec3_t original_angles; + vec3_t original_avelocity; + trace_t trace; + int i; + + VectorCopy( tossent->v.origin, original_origin ); + VectorCopy( tossent->v.velocity, original_velocity ); + VectorCopy( tossent->v.angles, original_angles ); + VectorCopy( tossent->v.avelocity, original_avelocity ); + gravity = tossent->v.gravity * svgame.movevars.gravity * 0.05f; + + for( i = 0; i < 200; i++ ) + { + SV_CheckVelocity( tossent ); + tossent->v.velocity[2] -= gravity; + VectorMA( tossent->v.angles, 0.05f, tossent->v.avelocity, tossent->v.angles ); + VectorScale( tossent->v.velocity, 0.05f, move ); + VectorAdd( tossent->v.origin, move, end ); + trace = SV_Move( tossent->v.origin, tossent->v.mins, tossent->v.maxs, end, MOVE_NORMAL, tossent, false ); + VectorCopy( trace.endpos, tossent->v.origin ); + if( trace.fraction < 1.0f ) break; + } + + VectorCopy( original_origin, tossent->v.origin ); + VectorCopy( original_velocity, tossent->v.velocity ); + VectorCopy( original_angles, tossent->v.angles ); + VectorCopy( original_avelocity, tossent->v.avelocity ); + + return trace; +} + +/* +=============================================================================== + + LIGHTING INFO + +=============================================================================== +*/ + +static vec3_t sv_pointColor; + +/* +================= +SV_RecursiveLightPoint +================= +*/ +static qboolean SV_RecursiveLightPoint( model_t *model, mnode_t *node, const vec3_t start, const vec3_t end ) +{ + float front, back, scale, frac; + int i, map, side, size; + float ds, dt, s, t; + int sample_size; + msurface_t *surf; + mtexinfo_t *tex; + mextrasurf_t *info; + color24 *lm; + vec3_t mid; + + // didn't hit anything + if( !node || node->contents < 0 ) + return false; + + // calculate mid point + front = PlaneDiff( start, node->plane ); + back = PlaneDiff( end, node->plane ); + + side = front < 0.0f; + if(( back < 0.0f ) == side ) + return SV_RecursiveLightPoint( model, node->children[side], start, end ); + + frac = front / ( front - back ); + + VectorLerp( start, frac, end, mid ); + + // co down front side + if( SV_RecursiveLightPoint( model, node->children[side], start, mid )) + return true; // hit something + + if(( back < 0.0f ) == side ) + return false;// didn't hit anything + + // check for impact on this node + surf = model->surfaces + node->firstsurface; + + for( i = 0; i < node->numsurfaces; i++, surf++ ) + { + int smax, tmax; + + tex = surf->texinfo; + info = surf->info; + + if( FBitSet( surf->flags, SURF_DRAWTILED )) + continue; // no lightmaps + + s = DotProduct( mid, info->lmvecs[0] ) + info->lmvecs[0][3]; + t = DotProduct( mid, info->lmvecs[1] ) + info->lmvecs[1][3]; + + if( s < info->lightmapmins[0] || t < info->lightmapmins[1] ) + continue; + + ds = s - info->lightmapmins[0]; + dt = t - info->lightmapmins[1]; + + if ( ds > info->lightextents[0] || dt > info->lightextents[1] ) + continue; + + if( !surf->samples ) + return true; + + sample_size = Mod_SampleSizeForFace( surf ); + smax = (info->lightextents[0] / sample_size) + 1; + tmax = (info->lightextents[1] / sample_size) + 1; + ds /= sample_size; + dt /= sample_size; + + VectorClear( sv_pointColor ); + + lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); + size = smax * tmax; + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + scale = sv.lightstyles[surf->styles[map]].value; + + sv_pointColor[0] += lm->r * scale; + sv_pointColor[1] += lm->g * scale; + sv_pointColor[2] += lm->b * scale; + + lm += size; // skip to next lightmap + } + return true; + } + + // go down back side + return SV_RecursiveLightPoint( model, node->children[!side], mid, end ); +} + +void SV_RunLightStyles( void ) +{ + int i, ofs; + lightstyle_t *ls; + float scale; + + scale = sv_lighting_modulate->value; + + // run lightstyles animation + for( i = 0, ls = sv.lightstyles; i < MAX_LIGHTSTYLES; i++, ls++ ) + { + ls->time += sv.frametime; + ofs = (ls->time * 10); + + if( ls->length == 0 ) ls->value = scale; // disable this light + else if( ls->length == 1 ) ls->value = ( ls->map[0] / 12.0f ) * scale; + else ls->value = ( ls->map[ofs % ls->length] / 12.0f ) * scale; + } +} + +/* +================== +SV_SetLightStyle + +needs to get correct working SV_LightPoint +================== +*/ +void SV_SetLightStyle( int style, const char* s, float f ) +{ + int j, k; + + Q_strncpy( sv.lightstyles[style].pattern, s, sizeof( sv.lightstyles[0].pattern )); + sv.lightstyles[style].time = f; + + j = Q_strlen( s ); + sv.lightstyles[style].length = j; + + for( k = 0; k < j; k++ ) + sv.lightstyles[style].map[k] = (float)(s[k] - 'a'); + + if( sv.state != ss_active ) return; + + // tell the clients about changed lightstyle + MSG_BeginServerCmd( &sv.reliable_datagram, svc_lightstyle ); + MSG_WriteByte( &sv.reliable_datagram, style ); + MSG_WriteString( &sv.reliable_datagram, sv.lightstyles[style].pattern ); + MSG_WriteFloat( &sv.reliable_datagram, sv.lightstyles[style].time ); +} + +/* +================== +SV_GetLightStyle + +needs to get correct working SV_LightPoint +================== +*/ +const char *SV_GetLightStyle( int style ) +{ + if( style < 0 ) style = 0; + if( style >= MAX_LIGHTSTYLES ) + Host_Error( "SV_GetLightStyle: style: %i >= %d", style, MAX_LIGHTSTYLES ); + + return sv.lightstyles[style].pattern; +} + +/* +================== +SV_LightForEntity + +grab the ambient lighting color for current point +================== +*/ +int SV_LightForEntity( edict_t *pEdict ) +{ + vec3_t start, end; + + if( FBitSet( pEdict->v.effects, EF_FULLBRIGHT ) || !sv.worldmodel->lightdata ) + return 255; + + // player has more precision light level that come from client-side + if( FBitSet( pEdict->v.flags, FL_CLIENT )) + return pEdict->v.light_level; + + VectorCopy( pEdict->v.origin, start ); + VectorCopy( pEdict->v.origin, end ); + + if( FBitSet( pEdict->v.effects, EF_INVLIGHT )) + end[2] = start[2] + world.size[2]; + else end[2] = start[2] - world.size[2]; + VectorSet( sv_pointColor, 1.0f, 1.0f, 1.0f ); + + SV_RecursiveLightPoint( sv.worldmodel, sv.worldmodel->nodes, start, end ); + + return VectorAvg( sv_pointColor ); +} \ No newline at end of file diff --git a/engine/shake.h b/engine/shake.h new file mode 100644 index 00000000..5a1da46a --- /dev/null +++ b/engine/shake.h @@ -0,0 +1,50 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef SHAKE_H +#define SHAKE_H + +// Screen / View effects + +// screen shake +extern int gmsgShake; + +// This structure is sent over the net to describe a screen shake event +typedef struct +{ + unsigned short amplitude; // FIXED 4.12 amount of shake + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short frequency; // FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble) +} ScreenShake; + +// Fade in/out +extern int gmsgFade; + +#define FFADE_IN 0x0000 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0001 // Fade out (not in) +#define FFADE_MODULATE 0x0002 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0004 // ignores the duration, stays faded out until new ScreenFade message received +#define FFADE_LONGFADE 0x0008 // used to indicate the fade can be longer than 16 seconds (added for czero) + +// This structure is sent over the net to describe a screen fade event +typedef struct +{ + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short holdTime; // FIXED 4.12 seconds duration until reset (fade & hold) + short fadeFlags; // flags + byte r, g, b, a; // fade to color ( max alpha ) +} ScreenFade; + +#endif // SHAKE_H \ No newline at end of file diff --git a/engine/sprite.h b/engine/sprite.h new file mode 100644 index 00000000..ba06ece0 --- /dev/null +++ b/engine/sprite.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef SPRITE_H +#define SPRITE_H + +/* +============================================================================== + +SPRITE MODELS + +.spr extended version (Half-Life compatible sprites with some Xash3D extensions) +============================================================================== +*/ + +#define IDSPRITEHEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSP" + +#define SPRITE_VERSION_Q1 1 // Quake sprites +#define SPRITE_VERSION_HL 2 // Half-Life sprites +#define SPRITE_VERSION_32 32 // Captain Obvious mode on + +// must match definition in alias.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum +{ + ST_SYNC = 0, + ST_RAND +} synctype_t; +#endif + +typedef enum +{ + FRAME_SINGLE = 0, + FRAME_GROUP, + FRAME_ANGLED // Xash3D ext +} frametype_t; + +typedef enum +{ + SPR_NORMAL = 0, + SPR_ADDITIVE, + SPR_INDEXALPHA, + SPR_ALPHTEST, +} drawtype_t; + +typedef enum +{ + SPR_FWD_PARALLEL_UPRIGHT = 0, + SPR_FACING_UPRIGHT, + SPR_FWD_PARALLEL, + SPR_ORIENTED, + SPR_FWD_PARALLEL_ORIENTED, +} angletype_t; + +typedef enum +{ + SPR_CULL_FRONT = 0, // oriented sprite will be draw with one face + SPR_CULL_NONE, // oriented sprite will be draw back face too +} facetype_t; + +// generic helper +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 +} dsprite_t; + +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 + int type; // camera align + float boundingradius; // quick face culling + int bounds[2]; // mins\maxs + int numframes; // including groups + float beamlength; // ??? + synctype_t synctype; // animation synctype +} dsprite_q1_t; + +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 + angletype_t type; // camera align + drawtype_t texFormat; // rendering mode + int boundingradius; // quick face culling + int bounds[2]; // mins\maxs + int numframes; // including groups + facetype_t facetype; // cullface (Xash3D ext) + synctype_t synctype; // animation synctype +} dsprite_hl_t; + +typedef struct +{ + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct +{ + int numframes; +} dspritegroup_t; + +typedef struct +{ + float interval; +} dspriteinterval_t; + +typedef struct +{ + frametype_t type; +} dframetype_t; + +#endif//SPRITE_H \ No newline at end of file diff --git a/engine/studio.h b/engine/studio.h new file mode 100644 index 00000000..2442b397 --- /dev/null +++ b/engine/studio.h @@ -0,0 +1,413 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef STUDIO_H +#define STUDIO_H + +/* +============================================================================== + +STUDIO MODELS + +Studio models are position independent, so the cache manager can move them. +============================================================================== +*/ + +// header +#define STUDIO_VERSION 10 +#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDST" +#define IDSEQGRPHEADER (('Q'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSQ" + +// studio limits +#define MAXSTUDIOTRIANGLES 65536 // max triangles per model +#define MAXSTUDIOVERTS 16384 // max vertices per submodel +#define MAXSTUDIOSEQUENCES 256 // total animation sequences +#define MAXSTUDIOSKINS 256 // total textures +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOBONES 128 // total bones actually used +#define MAXSTUDIOMODELS 32 // sub-models per model +#define MAXSTUDIOBODYPARTS 32 // body parts per submodel +#define MAXSTUDIOGROUPS 16 // sequence groups (e.g. barney01.mdl, barney02.mdl, e.t.c) +#define MAXSTUDIOANIMATIONS 512 // max frames per sequence +#define MAXSTUDIOMESHES 256 // max textures per model +#define MAXSTUDIOEVENTS 1024 // events per model +#define MAXSTUDIOPIVOTS 256 // pivot points +#define MAXSTUDIOBLENDS 16 // max anim blends +#define MAXSTUDIOBONEWEIGHTS 4 // absolute hardware limit! +#define MAXSTUDIOCONTROLLERS 8 // max controllers per model +#define MAXSTUDIOATTACHMENTS 4 // max attachments per model + +// client-side model flags +#define STUDIO_ROCKET (1<<0) // leave a trail +#define STUDIO_GRENADE (1<<1) // leave a trail +#define STUDIO_GIB (1<<2) // leave a trail +#define STUDIO_ROTATE (1<<3) // rotate (bonus items) +#define STUDIO_TRACER (1<<4) // green split trail +#define STUDIO_ZOMGIB (1<<5) // small blood trail +#define STUDIO_TRACER2 (1<<6) // orange split trail + rotate +#define STUDIO_TRACER3 (1<<7) // purple trail +#define STUDIO_AMBIENT_LIGHT (1<<8) // force to use ambient shading +#define STUDIO_TRACE_HITBOX (1<<9) // always use hitbox trace instead of bbox +#define STUDIO_FORCE_SKYLIGHT (1<<10) // always grab lightvalues from the sky settings (even if sky is invisible) + +#define STUDIO_STATIC_PROP (1<<29) // hint for engine +#define STUDIO_HAS_BONEINFO (1<<30) // extra info about bones (pose matrix, procedural index etc) +#define STUDIO_HAS_BONEWEIGHTS (1<<31) // yes we got support of bone weighting + +// lighting & rendermode options +#define STUDIO_NF_FLATSHADE 0x0001 +#define STUDIO_NF_CHROME 0x0002 +#define STUDIO_NF_FULLBRIGHT 0x0004 +#define STUDIO_NF_NOMIPS 0x0008 // ignore mip-maps + +#define STUDIO_NF_ADDITIVE 0x0020 // rendering with additive mode +#define STUDIO_NF_MASKED 0x0040 // use texture with alpha channel +#define STUDIO_NF_NORMALMAP 0x0080 // indexed normalmap + +#define STUDIO_NF_COLORMAP (1<<30) // internal system flag +#define STUDIO_NF_UV_COORDS (1<<31) // using half-float coords instead of ST + +// motion flags +#define STUDIO_X 0x0001 +#define STUDIO_Y 0x0002 +#define STUDIO_Z 0x0004 +#define STUDIO_XR 0x0008 +#define STUDIO_YR 0x0010 +#define STUDIO_ZR 0x0020 +#define STUDIO_LX 0x0040 +#define STUDIO_LY 0x0080 +#define STUDIO_LZ 0x0100 +#define STUDIO_AX 0x0200 +#define STUDIO_AY 0x0400 +#define STUDIO_AZ 0x0800 +#define STUDIO_AXR 0x1000 +#define STUDIO_AYR 0x2000 +#define STUDIO_AZR 0x4000 +#define STUDIO_TYPES 0x7FFF +#define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance + +// bonecontroller types +#define STUDIO_MOUTH 4 // hardcoded + +// sequence flags +#define STUDIO_LOOPING 0x0001 +#define STUDIO_LIGHT_FROM_ROOT 0x8000 // get lighting point from root bonepos not from entity origin + +// bone flags +#define STUDIO_HAS_NORMALS 0x0001 +#define STUDIO_HAS_VERTICES 0x0002 +#define STUDIO_HAS_BBOX 0x0004 +#define STUDIO_HAS_CHROME 0x0008 // if any of the textures have chrome on them + +typedef struct +{ + int ident; + int version; + + char name[64]; + int length; + + vec3_t eyeposition; // ideal eye position + vec3_t min; // ideal movement hull size + vec3_t max; + + vec3_t bbmin; // clipping bounding box + vec3_t bbmax; + + int flags; + + int numbones; // bones + int boneindex; + + int numbonecontrollers; // bone controllers + int bonecontrollerindex; + + int numhitboxes; // complex bounding boxes + int hitboxindex; + + int numseq; // animation sequences + int seqindex; + + int numseqgroups; // demand loaded sequences + int seqgroupindex; + + int numtextures; // raw textures + int textureindex; + int texturedataindex; + + int numskinref; // replaceable textures + int numskinfamilies; + int skinindex; + + int numbodyparts; + int bodypartindex; + + int numattachments; // queryable attachable points + int attachmentindex; + + int studiohdr2index; + int soundindex; + + int soundgroups; + int soundgroupindex; + + int numtransitions; // animation node to animation node transition graph + int transitionindex; +} studiohdr_t; + +// extra header to hold more offsets +typedef struct +{ + int numposeparameters; + int poseparamindex; + + int numikautoplaylocks; + int ikautoplaylockindex; + + int numikchains; + int ikchainindex; + + int keyvalueindex; + int keyvaluesize; + + int numhitboxsets; + int hitboxsetindex; + + int unused[6]; // for future expansions +} studiohdr2_t; + +// header for demand loaded sequence group data +typedef struct +{ + int id; + int version; + + char name[64]; + int length; +} studioseqhdr_t; + +// bones +typedef struct +{ + char name[32]; // bone name for symbolic links + int parent; // parent bone + int flags; // ?? + int bonecontroller[6]; // bone controller index, -1 == none + float value[6]; // default DoF values + float scale[6]; // scale for delta DoF values +} mstudiobone_t; + +// extra info for bones +typedef struct +{ + float poseToBone[3][4]; // boneweighting reqiures + vec4_t qAlignment; + int proctype; + int procindex; // procedural rule + vec4_t quat; // aligned bone rotation + int reserved[10]; // for future expansions +} mstudioboneinfo_t; + +// bone controllers +typedef struct +{ + int bone; // -1 == 0 + int type; // X, Y, Z, XR, YR, ZR, M + float start; + float end; + int rest; // byte index value at rest + int index; // 0-3 user set controller, 4 mouth +} mstudiobonecontroller_t; + +// intersection boxes +typedef struct +{ + int bone; + int group; // intersection group + vec3_t bbmin; // bounding box + vec3_t bbmax; +} mstudiobbox_t; + +#ifndef CACHE_USER +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; // extradata +} cache_user_t; +#endif + +// demand loaded sequence groups +typedef struct +{ + char label[32]; // textual name + char name[64]; // file name + cache_user_t cache; // cache index pointer + int data; // hack for group 0 +} mstudioseqgroup_t; + +// sequence descriptions +typedef struct +{ + char label[32]; // sequence label + + float fps; // frames per second + int flags; // looping/non-looping flags + + int activity; + int actweight; + + int numevents; + int eventindex; + + int numframes; // number of frames per sequence + + int numpivots; // number of foot pivots + int pivotindex; + + int motiontype; + int motionbone; + vec3_t linearmovement; + int automoveposindex; + int automoveangleindex; + + vec3_t bbmin; // per sequence bounding box + vec3_t bbmax; + + int numblends; + int animindex; // mstudioanim_t pointer relative to start of sequence group data + // [blend][bone][X, Y, Z, XR, YR, ZR] + + int blendtype[2]; // X, Y, Z, XR, YR, ZR + float blendstart[2]; // starting value + float blendend[2]; // ending value + int blendparent; + + int seqgroup; // sequence group for demand loading + + int entrynode; // transition node at entry + int exitnode; // transition node at exit + int nodeflags; // transition rules + + int nextseq; // auto advancing sequences +} mstudioseqdesc_t; + +// events +#include "studio_event.h" + +// pivots +typedef struct +{ + vec3_t org; // pivot point + int start; + int end; +} mstudiopivot_t; + +// attachment +typedef struct +{ + char name[32]; + int type; + int bone; + vec3_t org; // attachment point + vec3_t vectors[3]; +} mstudioattachment_t; + +typedef struct +{ + unsigned short offset[6]; +} mstudioanim_t; + +// animation frames +typedef union +{ + struct + { + byte valid; + byte total; + } num; + short value; +} mstudioanimvalue_t; + +// body part index +typedef struct +{ + char name[64]; + int nummodels; + int base; + int modelindex; // index into models array +} mstudiobodyparts_t; + +// skin info +typedef struct mstudiotex_s +{ + char name[64]; + unsigned int flags; + int width; + int height; + int index; +} mstudiotexture_t; + +typedef struct +{ + byte weight[4]; + char bone[4]; +} mstudioboneweight_t; + +// skin families +// short index[skinfamilies][skinref] + +// studio models +typedef struct +{ + char name[64]; + + int type; + float boundingradius; + + int nummesh; + int meshindex; + + int numverts; // number of unique vertices + int vertinfoindex; // vertex bone info + int vertindex; // vertex vec3_t + int numnorms; // number of unique surface normals + int norminfoindex; // normal bone info + int normindex; // normal vec3_t + + int blendvertinfoindex; // boneweighted vertex info + int blendnorminfoindex; // boneweighted normal info +} mstudiomodel_t; + +// vec3_t boundingbox[model][bone][2]; // complex intersection info + +// meshes +typedef struct +{ + int numtris; + int triindex; + int skinref; + int numnorms; // per mesh normals + int normindex; // normal vec3_t +} mstudiomesh_t; + +// triangles +typedef struct +{ + short vertindex; // index into vertex array + short normindex; // index into normal array + short s,t; // s,t position on skin +} mstudiotrivert_t; + +#endif//STUDIO_H \ No newline at end of file diff --git a/engine/warpsin.h b/engine/warpsin.h new file mode 100644 index 00000000..e46b44d7 --- /dev/null +++ b/engine/warpsin.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 1997-2001 Id Software, 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 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. + +*/ + + +0.000000, 0.098165, 0.196270, 0.294259, 0.392069, 0.489643, 0.586920, 0.683850, +0.780360, 0.876405, 0.971920, 1.066850, 1.161140, 1.254725, 1.347560, 1.439580, +1.530735, 1.620965, 1.710220, 1.798445, 1.885585, 1.971595, 2.056410, 2.139990, +2.222280, 2.303235, 2.382795, 2.460925, 2.537575, 2.612690, 2.686235, 2.758160, +2.828425, 2.896990, 2.963805, 3.028835, 3.092040, 3.153385, 3.212830, 3.270340, +3.325880, 3.379415, 3.430915, 3.480350, 3.527685, 3.572895, 3.615955, 3.656840, +3.695520, 3.731970, 3.766175, 3.798115, 3.827760, 3.855105, 3.880125, 3.902810, +3.923140, 3.941110, 3.956705, 3.969920, 3.980740, 3.989160, 3.995180, 3.998795, +4.000000, 3.998795, 3.995180, 3.989160, 3.980740, 3.969920, 3.956705, 3.941110, +3.923140, 3.902810, 3.880125, 3.855105, 3.827760, 3.798115, 3.766175, 3.731970, +3.695520, 3.656840, 3.615955, 3.572895, 3.527685, 3.480350, 3.430915, 3.379415, +3.325880, 3.270340, 3.212830, 3.153385, 3.092040, 3.028835, 2.963805, 2.896990, +2.828425, 2.758160, 2.686235, 2.612690, 2.537575, 2.460925, 2.382795, 2.303235, +2.222280, 2.139990, 2.056410, 1.971595, 1.885585, 1.798445, 1.710220, 1.620965, +1.530735, 1.439580, 1.347560, 1.254725, 1.161140, 1.066850, 0.971920, 0.876405, +0.780360, 0.683850, 0.586920, 0.489643, 0.392069, 0.294259, 0.196270, 0.098165, +0.000000, -0.098165, -0.196270, -0.294259, -0.392069, -0.489643, -0.586920, -0.683850, +-0.780360, -0.876405, -0.971920, -1.066850, -1.161140, -1.254725, -1.347560, -1.439580, +-1.530735, -1.620965, -1.710220, -1.798445, -1.885585, -1.971595, -2.056410, -2.139990, +-2.222280, -2.303235, -2.382795, -2.460925, -2.537575, -2.612690, -2.686235, -2.758160, +-2.828425, -2.896990, -2.963805, -3.028835, -3.092040, -3.153385, -3.212830, -3.270340, +-3.325880, -3.379415, -3.430915, -3.480350, -3.527685, -3.572895, -3.615955, -3.656840, +-3.695520, -3.731970, -3.766175, -3.798115, -3.827760, -3.855105, -3.880125, -3.902810, +-3.923140, -3.941110, -3.956705, -3.969920, -3.980740, -3.989160, -3.995180, -3.998795, +-4.000000, -3.998795, -3.995180, -3.989160, -3.980740, -3.969920, -3.956705, -3.941110, +-3.923140, -3.902810, -3.880125, -3.855105, -3.827760, -3.798115, -3.766175, -3.731970, +-3.695520, -3.656840, -3.615955, -3.572895, -3.527685, -3.480350, -3.430915, -3.379415, +-3.325880, -3.270340, -3.212830, -3.153385, -3.092040, -3.028835, -2.963805, -2.896990, +-2.828425, -2.758160, -2.686235, -2.612690, -2.537575, -2.460925, -2.382795, -2.303235, +-2.222280, -2.139990, -2.056410, -1.971595, -1.885585, -1.798445, -1.710220, -1.620965, +-1.530735, -1.439580, -1.347560, -1.254725, -1.161140, -1.066850, -0.971920, -0.876405, +-0.780360, -0.683850, -0.586920, -0.489643, -0.392069, -0.294259, -0.196270, -0.098165, diff --git a/pm_shared/pm_debug.h b/pm_shared/pm_debug.h new file mode 100644 index 00000000..5e48ee54 --- /dev/null +++ b/pm_shared/pm_debug.h @@ -0,0 +1,23 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H + +void PM_ViewEntity( void ); +void PM_DrawBBox(vec3_t mins, vec3_t maxs, vec3_t origin, int pcolor, float life); +void PM_ParticleLine(vec3_t start, vec3_t end, int pcolor, float life, float vert); +void PM_ShowClipBox( void ); + +#endif//PM_DEBUG_H \ No newline at end of file diff --git a/pm_shared/pm_defs.h b/pm_shared/pm_defs.h new file mode 100644 index 00000000..7c04d22d --- /dev/null +++ b/pm_shared/pm_defs.h @@ -0,0 +1,221 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PM_DEFS_H +#define PM_DEFS_H + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world +#define PM_CUSTOM_IGNORE 0x00000010 // Ignore entities with SOLID_CUSTOM mode + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_PHYSENTSONLY 0 +#define PM_TRACELINE_ANYVISIBLE 1 + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + + +#include "usercmd.h" + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; // also contains pev->scale when "sv_allow_studio_scaling" is "1" + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[256]; + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + + // world state + + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[MAX_PHYSENTS]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[MAX_PHYSINFO_STRING]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[4]; + vec3_t player_maxs[4]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel ); + int (*PM_TestPlayerPosition)( float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents)( float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents)( float *p ); + int (*PM_HullPointContents)( struct hull_s *hull, int num, float *p ); + pmtrace_t (*PM_PlayerTrace)( float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + long (*RandomLong)( long lLow, long lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)( char *filename ); + byte *(*COM_LoadFile)( char *path, int usehunk, int *pLength ); + void (*COM_FreeFile)( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound)( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture)( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull)( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe )); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe )); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe )); + struct msurface_s *(*PM_TraceSurface)( int ground, float *vstart, float *vend ); +} playermove_t; +#endif//PM_DEFS_H \ No newline at end of file diff --git a/pm_shared/pm_info.h b/pm_shared/pm_info.h new file mode 100644 index 00000000..ea54e6db --- /dev/null +++ b/pm_shared/pm_info.h @@ -0,0 +1,20 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_INFO_H +#define PM_INFO_H + +#define MAX_PHYSINFO_STRING 256 + +#endif//PM_INFO_H \ No newline at end of file diff --git a/pm_shared/pm_materials.h b/pm_shared/pm_materials.h new file mode 100644 index 00000000..8e3b7792 --- /dev/null +++ b/pm_shared/pm_materials.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_MATERIALS_H +#define PM_MATERIALS_H + +#define CBTEXTURENAMEMAX 13 // only load first n chars of name + +#define CHAR_TEX_CONCRETE 'C' // texture types +#define CHAR_TEX_METAL 'M' +#define CHAR_TEX_DIRT 'D' +#define CHAR_TEX_VENT 'V' +#define CHAR_TEX_GRATE 'G' +#define CHAR_TEX_TILE 'T' +#define CHAR_TEX_SLOSH 'S' +#define CHAR_TEX_WOOD 'W' +#define CHAR_TEX_COMPUTER 'P' +#define CHAR_TEX_GLASS 'Y' +#define CHAR_TEX_FLESH 'F' + +#endif//PM_MATERIALS_H \ No newline at end of file diff --git a/pm_shared/pm_movevars.h b/pm_shared/pm_movevars.h new file mode 100644 index 00000000..4cf2ba3f --- /dev/null +++ b/pm_shared/pm_movevars.h @@ -0,0 +1,50 @@ +//========= Copyright (C) 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // + int features; // engine features that shared across network + int fog_settings; // Global fog settings (packed color+density) + float wateralpha; // World water alpha 1.0 - solid 0.0 - transparent +}; + +extern movevars_t movevars; + +#endif \ No newline at end of file diff --git a/pm_shared/pm_shared.h b/pm_shared/pm_shared.h new file mode 100644 index 00000000..9743044c --- /dev/null +++ b/pm_shared/pm_shared.h @@ -0,0 +1,32 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PM_SHARED_H +#define PM_SHARED_H + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif//PM_SHARED_H \ No newline at end of file