From a6875e7cf19fda1e62f93d93d6a01680f11a0eca Mon Sep 17 00:00:00 2001 From: g-cont Date: Sat, 8 Aug 2009 00:00:00 +0400 Subject: [PATCH] 08 Aug 2009 --- client/global/utils.cpp | 1 - engine/client/cl_cmds.c | 6 +- engine/client/cl_parse.c | 4 +- launch/imagelib/imagelib.h | 55 +- launch/imagelib/img_jpg.c | 1058 ++--- launch/imagelib/img_jpg.old | 498 +++ launch/imagelib/img_main.c | 16 +- launch/imagelib/img_tga.c | 1 + launch/imagelib/img_utils.c | 4 + launch/imagelib/jpg.lib | Bin 0 -> 239582 bytes launch/launch.dsp | 4 +- launch/launch.plg | 63 + physic/cm_collision.c | 6 +- physic/cm_trace.c | 8 +- public/mathlib.h | 4 +- public/matrix_lib.h | 23 +- public/quatlib.h | 165 +- render/r_alias.c | 2 +- render/r_backend.c | 70 +- render/r_backend.h | 3 +- render/r_draw.c | 13 + render/r_image.c | 80 +- render/r_light.c | 35 +- render/r_local.h | 15 +- render/r_main.c | 69 +- render/r_math.c | 19 - render/r_math.h | 2 - render/r_mesh.c | 64 +- render/r_mesh.h | 6 +- render/r_model.c | 25 +- render/r_poly.c | 12 +- render/r_shader.c | 253 +- render/r_shader.h | 3 + render/r_shadow.c | 8 +- render/r_sky.c | 205 +- render/r_sky.old | 665 +++ render/r_surf.c | 12 +- render/render.plg | 63 + server/ents/basefunc.cpp | 1 + server/ents/baseinfo.cpp | 1 - server/ents/basetrigger.cpp | 32 +- server/global/utils.cpp | 3072 ++++++++++++++ server/monsters/activity.h | 132 + server/monsters/activitymap.h | 121 + server/monsters/ai_sound.cpp | 379 ++ server/monsters/animating.cpp | 342 ++ server/monsters/animation.cpp | 562 +++ server/monsters/animation.h | 52 + server/monsters/apache.cpp | 942 +++++ server/monsters/barnacle.cpp | 434 ++ server/monsters/barney.cpp | 929 +++++ server/monsters/baseanimating.h | 50 + server/monsters/basemonster.cpp | 6238 +++++++++++++++++++++++++++++ server/monsters/basemonster.h | 352 ++ server/monsters/combat.cpp | 261 ++ server/monsters/damage.h | 21 + server/monsters/defaultai.cpp | 1260 ++++++ server/monsters/defaultai.h | 98 + server/monsters/flyingmonster.cpp | 281 ++ server/monsters/flyingmonster.h | 53 + server/monsters/generic.cpp | 395 ++ server/monsters/gman.cpp | 243 ++ server/monsters/hassassin.cpp | 1068 +++++ server/monsters/headcrab.cpp | 562 +++ server/monsters/hgrunt.cpp | 2618 ++++++++++++ server/monsters/leech.cpp | 743 ++++ server/monsters/monsterevent.h | 29 + server/monsters/monsters.h | 163 + server/monsters/nodes.cpp | 3653 +++++++++++++++++ server/monsters/nodes.h | 374 ++ server/monsters/osprey.cpp | 823 ++++ server/monsters/player.cpp | 5137 ++++++++++++++++++++++++ server/monsters/player.h | 376 ++ server/monsters/rat.cpp | 104 + server/monsters/roach.cpp | 466 +++ server/monsters/schedule.h | 292 ++ server/monsters/scientist.cpp | 1458 +++++++ server/monsters/scripted.cpp | 1246 ++++++ server/monsters/scripted.h | 128 + server/monsters/scriptevent.h | 29 + server/monsters/squad.h | 20 + server/monsters/squadmonster.cpp | 642 +++ server/monsters/squadmonster.h | 121 + server/monsters/talkmonster.cpp | 1557 +++++++ server/monsters/talkmonster.h | 186 + server/monsters/turret.cpp | 1336 ++++++ server/monsters/zombie.cpp | 381 ++ todo.log | 8 +- vprogs/pr_comp.c | 6199 ++++++++++++++++++++++++++++ vprogs/pr_edict.c | 2058 ++++++++++ vprogs/pr_exec.c | 1497 +++++++ vprogs/pr_lex.c | 2806 +++++++++++++ vprogs/pr_local.h | 712 ++++ vprogs/pr_main.c | 392 ++ vprogs/pr_utils.c | 1055 +++++ vprogs/vprogs.dsp | 153 + vprogs/vprogs.h | 467 +++ vsound/libogg.lib | Bin 0 -> 27254 bytes vsound/s_export.c | 51 + vsound/s_load.c | 710 ++++ vsound/s_main.c | 819 ++++ vsound/s_openal.c | 399 ++ vsound/s_openal.h | 294 ++ vsound/s_stream.c | 364 ++ vsound/s_stream.h | 190 + vsound/sound.h | 194 + vsound/vorbis.lib | Bin 0 -> 266902 bytes vsound/vsound.dsp | 149 + xtools/bsplib/ambient.c | 134 + xtools/bsplib/brushbsp.c | 1169 ++++++ xtools/bsplib/bspfile.c | 666 +++ xtools/bsplib/bsplib.c | 165 + xtools/bsplib/bsplib.h | 694 ++++ xtools/bsplib/csg.c | 533 +++ xtools/bsplib/faces.c | 1011 +++++ xtools/bsplib/flow.c | 803 ++++ xtools/bsplib/leakfile.c | 74 + xtools/bsplib/lightmap.c | 1540 +++++++ xtools/bsplib/map.c | 1065 +++++ xtools/bsplib/patches.c | 474 +++ xtools/bsplib/portals.c | 1061 +++++ xtools/bsplib/prtfile.c | 260 ++ xtools/bsplib/qbsp3.c | 302 ++ xtools/bsplib/qrad3.c | 429 ++ xtools/bsplib/qvis3.c | 617 +++ xtools/bsplib/shaders.c | 214 + xtools/bsplib/textures.c | 214 + xtools/bsplib/trace.c | 284 ++ xtools/bsplib/tree.c | 198 + xtools/bsplib/winding.c | 543 +++ xtools/bsplib/writebsp.c | 540 +++ xtools/dpvencoder.c | 22 + xtools/mdllib.h | 347 ++ xtools/ripper/conv_bsplumps.c | 266 ++ xtools/ripper/conv_doom.c | 702 ++++ xtools/ripper/conv_image.c | 183 + xtools/ripper/conv_main.c | 355 ++ xtools/ripper/conv_shader.c | 517 +++ xtools/ripper/conv_sprite.c | 454 +++ xtools/ripper/ripper.h | 78 + xtools/spritegen.c | 641 +++ xtools/studio.c | 2641 ++++++++++++ xtools/studio_utils.c | 1100 +++++ xtools/utils.c | 54 + xtools/utils.h | 44 + xtools/wadlib.c | 569 +++ xtools/xtools.c | 240 ++ xtools/xtools.dsp | 189 + xtools/xtools.h | 33 + 149 files changed, 82215 insertions(+), 1030 deletions(-) create mode 100644 launch/imagelib/img_jpg.old create mode 100644 launch/imagelib/jpg.lib create mode 100644 launch/launch.plg create mode 100644 render/r_sky.old create mode 100644 render/render.plg create mode 100644 server/global/utils.cpp create mode 100644 server/monsters/activity.h create mode 100644 server/monsters/activitymap.h create mode 100644 server/monsters/ai_sound.cpp create mode 100644 server/monsters/animating.cpp create mode 100644 server/monsters/animation.cpp create mode 100644 server/monsters/animation.h create mode 100644 server/monsters/apache.cpp create mode 100644 server/monsters/barnacle.cpp create mode 100644 server/monsters/barney.cpp create mode 100644 server/monsters/baseanimating.h create mode 100644 server/monsters/basemonster.cpp create mode 100644 server/monsters/basemonster.h create mode 100644 server/monsters/combat.cpp create mode 100644 server/monsters/damage.h create mode 100644 server/monsters/defaultai.cpp create mode 100644 server/monsters/defaultai.h create mode 100644 server/monsters/flyingmonster.cpp create mode 100644 server/monsters/flyingmonster.h create mode 100644 server/monsters/generic.cpp create mode 100644 server/monsters/gman.cpp create mode 100644 server/monsters/hassassin.cpp create mode 100644 server/monsters/headcrab.cpp create mode 100644 server/monsters/hgrunt.cpp create mode 100644 server/monsters/leech.cpp create mode 100644 server/monsters/monsterevent.h create mode 100644 server/monsters/monsters.h create mode 100644 server/monsters/nodes.cpp create mode 100644 server/monsters/nodes.h create mode 100644 server/monsters/osprey.cpp create mode 100644 server/monsters/player.cpp create mode 100644 server/monsters/player.h create mode 100644 server/monsters/rat.cpp create mode 100644 server/monsters/roach.cpp create mode 100644 server/monsters/schedule.h create mode 100644 server/monsters/scientist.cpp create mode 100644 server/monsters/scripted.cpp create mode 100644 server/monsters/scripted.h create mode 100644 server/monsters/scriptevent.h create mode 100644 server/monsters/squad.h create mode 100644 server/monsters/squadmonster.cpp create mode 100644 server/monsters/squadmonster.h create mode 100644 server/monsters/talkmonster.cpp create mode 100644 server/monsters/talkmonster.h create mode 100644 server/monsters/turret.cpp create mode 100644 server/monsters/zombie.cpp create mode 100644 vprogs/pr_comp.c create mode 100644 vprogs/pr_edict.c create mode 100644 vprogs/pr_exec.c create mode 100644 vprogs/pr_lex.c create mode 100644 vprogs/pr_local.h create mode 100644 vprogs/pr_main.c create mode 100644 vprogs/pr_utils.c create mode 100644 vprogs/vprogs.dsp create mode 100644 vprogs/vprogs.h create mode 100644 vsound/libogg.lib create mode 100644 vsound/s_export.c create mode 100644 vsound/s_load.c create mode 100644 vsound/s_main.c create mode 100644 vsound/s_openal.c create mode 100644 vsound/s_openal.h create mode 100644 vsound/s_stream.c create mode 100644 vsound/s_stream.h create mode 100644 vsound/sound.h create mode 100644 vsound/vorbis.lib create mode 100644 vsound/vsound.dsp create mode 100644 xtools/bsplib/ambient.c create mode 100644 xtools/bsplib/brushbsp.c create mode 100644 xtools/bsplib/bspfile.c create mode 100644 xtools/bsplib/bsplib.c create mode 100644 xtools/bsplib/bsplib.h create mode 100644 xtools/bsplib/csg.c create mode 100644 xtools/bsplib/faces.c create mode 100644 xtools/bsplib/flow.c create mode 100644 xtools/bsplib/leakfile.c create mode 100644 xtools/bsplib/lightmap.c create mode 100644 xtools/bsplib/map.c create mode 100644 xtools/bsplib/patches.c create mode 100644 xtools/bsplib/portals.c create mode 100644 xtools/bsplib/prtfile.c create mode 100644 xtools/bsplib/qbsp3.c create mode 100644 xtools/bsplib/qrad3.c create mode 100644 xtools/bsplib/qvis3.c create mode 100644 xtools/bsplib/shaders.c create mode 100644 xtools/bsplib/textures.c create mode 100644 xtools/bsplib/trace.c create mode 100644 xtools/bsplib/tree.c create mode 100644 xtools/bsplib/winding.c create mode 100644 xtools/bsplib/writebsp.c create mode 100644 xtools/dpvencoder.c create mode 100644 xtools/mdllib.h create mode 100644 xtools/ripper/conv_bsplumps.c create mode 100644 xtools/ripper/conv_doom.c create mode 100644 xtools/ripper/conv_image.c create mode 100644 xtools/ripper/conv_main.c create mode 100644 xtools/ripper/conv_shader.c create mode 100644 xtools/ripper/conv_sprite.c create mode 100644 xtools/ripper/ripper.h create mode 100644 xtools/spritegen.c create mode 100644 xtools/studio.c create mode 100644 xtools/studio_utils.c create mode 100644 xtools/utils.c create mode 100644 xtools/utils.h create mode 100644 xtools/wadlib.c create mode 100644 xtools/xtools.c create mode 100644 xtools/xtools.dsp create mode 100644 xtools/xtools.h diff --git a/client/global/utils.cpp b/client/global/utils.cpp index ff0777ec..11a6b3a8 100644 --- a/client/global/utils.cpp +++ b/client/global/utils.cpp @@ -564,7 +564,6 @@ void V_RenderPlaque( void ) const char *levelshot; levelshot = CVAR_GET_STRING( "cl_levelshot_name" ); - if( !strcmp( levelshot, "" )) levelshot = "*black"; // logo that shows up while upload next level DrawImageRectangle( SPR_Load( levelshot )); diff --git a/engine/client/cl_cmds.c b/engine/client/cl_cmds.c index 3ff1292e..25f38305 100644 --- a/engine/client/cl_cmds.c +++ b/engine/client/cl_cmds.c @@ -6,7 +6,7 @@ #include "common.h" #include "client.h" -#define SCRSHOT_TYPE "tga" +#define SCRSHOT_TYPE "jpg" /* ================ @@ -193,7 +193,7 @@ void CL_LevelShot_f( void ) if( !cl.need_levelshot ) return; // check for exist - com.sprintf( checkname, "levelshots/%s.png", cl.configstrings[CS_NAME] ); + com.sprintf( checkname, "levelshots/%s.jpg", cl.configstrings[CS_NAME] ); if( !FS_FileExists( checkname )) re->ScrShot( checkname, true ); cl.need_levelshot = false; // done } @@ -207,7 +207,7 @@ Set a specific sky and rotation speed */ void CL_SetSky_f( void ) { - if(Cmd_Argc() < 2) + if( Cmd_Argc() < 2 ) { Msg( "Usage: sky \n" ); return; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index 60f486a3..35bbfaef 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -261,8 +261,8 @@ void CL_ParseServerData( sizebuf_t *msg ) break; if( i == 3 ) { - Cvar_Set( "cl_levelshot_name", "" ); - cl.need_levelshot = true; // make levelshot + Cvar_Set( "cl_levelshot_name", "*black" ); // render a black screen + cl.need_levelshot = true; // make levelshot } // seperate the printfs so the server message can have a color Msg("\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"); diff --git a/launch/imagelib/imagelib.h b/launch/imagelib/imagelib.h index 229735f9..c8867917 100644 --- a/launch/imagelib/imagelib.h +++ b/launch/imagelib/imagelib.h @@ -218,55 +218,16 @@ typedef struct tga_s ======================================================================== */ -typedef struct huffman_table_s -{ - // Huffman coding tables - byte bits[16]; - byte hval[256]; - byte size[256]; - word code[256]; -} huffman_table_t; +// defined in image_jpg.c -typedef struct jpg_s -{ - // not a real header - file_t *file; // file - byte *buffer; // jpg buffer - - int width; // width image - int height; // height image - byte *data; // image - int data_precision; // bit per component - int num_components; // number component - int restart_interval; // restart interval - bool progressive_mode; // progressive format +/* +======================================================================== - struct - { - int id; // identifier - int h; // horizontal sampling factor - int v; // vertical sampling factor - int t; // quantization table selector - int td; // DC table selector - int ta; // AC table selector - } component_info[3]; // RGB (alpha not supported) - - huffman_table_t hac[4]; // AC table - huffman_table_t hdc[4]; // DC table +.PNG image format - int qtable[4][64]; // quantization table - - struct - { - int ss,se; // progressive jpeg spectral selection - int ah,al; // progressive jpeg successive approx - } scan; - - int dc[3]; - int curbit; - byte curbyte; - -} jpg_t; +======================================================================== +*/ +// defined in image_png.c /* ======================================================================== @@ -494,6 +455,7 @@ extern imglib_t image; extern cvar_t *img_oldformats; extern cvar_t *fs_wadsupport; extern cvar_t *png_compression; +extern cvar_t *jpg_quality; extern byte *fs_mempool; extern const bpc_desc_t PFDesc[]; @@ -546,6 +508,7 @@ bool Image_SaveTGA( const char *name, rgbdata_t *pix ); bool Image_SaveDDS( const char *name, rgbdata_t *pix ); bool Image_SaveBMP( const char *name, rgbdata_t *pix ); bool Image_SavePNG( const char *name, rgbdata_t *pix ); +bool Image_SaveJPG( const char *name, rgbdata_t *pix ); bool Image_SavePCX( const char *name, rgbdata_t *pix ); // diff --git a/launch/imagelib/img_jpg.c b/launch/imagelib/img_jpg.c index 34d2d75b..7c2ac401 100644 --- a/launch/imagelib/img_jpg.c +++ b/launch/imagelib/img_jpg.c @@ -1,470 +1,426 @@ //======================================================================= -// Copyright XashXT Group 2007 © +// Copyright XashXT Group 2009 © // img_jpg.c - jpg format load & save //======================================================================= +#include #include "imagelib.h" -jpg_t jpg_file; // jpeg read struct +// jboolean is unsigned char instead of int on Win32 +#ifdef WIN32 +typedef byte jboolean; +#else +typedef int jboolean; +#endif -int jpeg_read_byte( void ) -{ - // read byte - jpg_file.curbyte = *jpg_file.buffer++; - jpg_file.curbit = 0; - return jpg_file.curbyte; -} +#define JPEG_LIB_VERSION 62 // version 6b +#define JPEG_OUTPUT_BUF_SIZE 4096 -int jpeg_read_word( void ) -{ - // read word - word i = BuffLittleShort( jpg_file.buffer); - i = ((i << 8) & 0xFF00) + ((i >> 8) & 0x00FF); - jpg_file.buffer += 2; - - return i; -} +typedef void *j_common_ptr; +typedef struct jpeg_compress_struct *j_compress_ptr; +typedef struct jpeg_decompress_struct *j_decompress_ptr; -int jpeg_read_bit( void ) +typedef enum { - // read bit - register int i; - if(jpg_file.curbit == 0) + JCS_UNKNOWN, + JCS_GRAYSCALE, + JCS_RGB, + JCS_YCbCr, + JCS_CMYK, + JCS_YCCK +} J_COLOR_SPACE; +typedef enum { JPEG_DUMMY1 } J_DCT_METHOD; +typedef enum { JPEG_DUMMY2 } J_DITHER_MODE; +typedef uint JDIMENSION; + +#define JPOOL_PERMANENT 0 // lasts until master record is destroyed +#define JPOOL_IMAGE 1 // lasts until done with image/datastream +#define JPEG_EOI 0xD9 // EOI marker code +#define JMSG_STR_PARM_MAX 80 +#define DCTSIZE2 64 +#define NUM_QUANT_TBLS 4 +#define NUM_HUFF_TBLS 4 +#define NUM_ARITH_TBLS 16 +#define MAX_COMPS_IN_SCAN 4 +#define C_MAX_BLOCKS_IN_MCU 10 +#define D_MAX_BLOCKS_IN_MCU 10 + +struct jpeg_memory_mgr +{ + void* (*alloc_small)( j_common_ptr cinfo, int pool_id, size_t sizeofobject ); + void (*alloc_large)( void ); + void (*alloc_sarray)( void ); + void (*alloc_barray)( void ); + void (*request_virt_sarray)( void ); + void (*request_virt_barray)( void ); + void (*realize_virt_arrays)( void ); + void (*access_virt_sarray)( void ); + void (*access_virt_barray)( void ); + void (*free_pool)( void ); + void (*self_destruct)( void ); + long max_memory_to_use; + long max_alloc_chunk; +}; + +struct jpeg_error_mgr +{ + void (*error_exit)( j_common_ptr cinfo ); + void (*emit_message)( j_common_ptr cinfo, int msg_level ); + void (*output_message)( j_common_ptr cinfo ); + void (*format_message)( j_common_ptr cinfo, char *buffer ); + void (*reset_error_mgr)( j_common_ptr cinfo ); + int msg_code; + + union { - jpeg_read_byte(); - if(jpg_file.curbyte == 0xFF) - { - while(jpg_file.curbyte == 0xFF) jpeg_read_byte(); - if(jpg_file.curbyte >= 0xD0 && jpg_file.curbyte <= 0xD7) - Mem_Set(jpg_file.dc, 0, sizeof(int) * 3); - if(jpg_file.curbyte == 0) jpg_file.curbyte = 0xFF; - else jpeg_read_byte(); - } + int i[8]; + char s[JMSG_STR_PARM_MAX]; + } msg_parm; + + int trace_level; + long num_warnings; + const char *const *jpeg_message_table; + int last_jpeg_message; + const char *const *addon_message_table; + int first_addon_message; + int last_addon_message; +}; + +struct jpeg_source_mgr +{ + const byte *next_input_byte; + size_t bytes_in_buffer; + + void (*init_source)( j_decompress_ptr cinfo ); + jboolean (*fill_input_buffer)( j_decompress_ptr cinfo ); + void (*skip_input_data)( j_decompress_ptr cinfo, long num_bytes ); + jboolean (*resync_to_restart)( j_decompress_ptr cinfo, int desired ); + void (*term_source)( j_decompress_ptr cinfo ); +}; + +typedef struct +{ + // these values are fixed over the whole image. + // for compression, they must be supplied by parameter setup; + // for decompression, they are read from the SOF marker. + int component_id; // identifier for this component (0..255) + int component_index; // its index in SOF or cinfo->comp_info[] + int h_samp_factor; // horizontal sampling factor (1..4) + int v_samp_factor; // vertical sampling factor (1..4) + int quant_tbl_no; // quantization table selector (0..3) + + // these values may vary between scans. + // for compression, they must be supplied by parameter setup; + // for decompression, they are read from the SOS marker. + // the decompressor output side may not use these variables. + int dc_tbl_no; // DC entropy table selector (0..3) + int ac_tbl_no; // AC entropy table selector (0..3) + + // Remaining fields should be treated as private by applications. + + // these values are computed during compression or decompression startup: + // component's size in DCT blocks. + // any dummy blocks added to complete an MCU are not counted; + // therefore these values do not depend on whether a scan is interleaved or not. + JDIMENSION width_in_blocks; + JDIMENSION height_in_blocks; + + // size of a DCT block in samples. always DCTSIZE for compression. + // for decompression this is the size of the output from one DCT block, + // reflecting any scaling we choose to apply during the IDCT step. + // values of 1,2,4,8 are likely to be supported. note that different + // components may receive different IDCT scalings. + + int DCT_scaled_size; + // The downsampled dimensions are the component's actual, unpadded number + // of samples at the main buffer (preprocessing/compression interface), thus + // downsampled_width = ceil(image_width * Hi/Hmax) + // and similarly for height. for decompression, IDCT scaling is included, so + // downsampled_width = ceil(image_width * Hi/Hmax * DCT_scaled_size/DCTSIZE) + JDIMENSION downsampled_width; // actual width in samples + JDIMENSION downsampled_height; // actual height in samples + + // This flag is used only for decompression. In cases where some of the + // components will be ignored (eg grayscale output from YCbCr image), + // we can skip most computations for the unused components. + jboolean component_needed; // do we need the value of this component? + + // these values are computed before starting a scan of the component. + // the decompressor output side may not use these variables. + int MCU_width; // number of blocks per MCU, horizontally + int MCU_height; // number of blocks per MCU, vertically + int MCU_blocks; // MCU_width * MCU_height + int MCU_sample_width; // MCU width in samples, MCU_width*DCT_scaled_size + int last_col_width; // # of non-dummy blocks across in last MCU + int last_row_height; // # of non-dummy blocks down in last MCU + + // saved quantization table for component; NULL if none yet saved. + // see jdinput.c comments about the need for this information. + // this field is currently used only for decompression. + void *quant_table; + + // private per-component storage for DCT or IDCT subsystem. + void *dct_table; +} jpeg_component_info; + +struct jpeg_decompress_struct +{ + struct jpeg_error_mgr *err; // USED + struct jpeg_memory_mgr *mem; // USED + + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + struct jpeg_source_mgr *src; // USED + JDIMENSION image_width; // USED + JDIMENSION image_height; // USED + + int num_components; + J_COLOR_SPACE jpeg_color_space; + J_COLOR_SPACE out_color_space; + uint scale_num; + uint scale_denom; + double output_gamma; + jboolean buffered_image; + jboolean raw_data_out; + J_DCT_METHOD dct_method; + jboolean do_fancy_upsampling; + jboolean do_block_smoothing; + jboolean quantize_colors; + J_DITHER_MODE dither_mode; + jboolean two_pass_quantize; + int desired_number_of_colors; + jboolean enable_1pass_quant; + jboolean enable_external_quant; + jboolean enable_2pass_quant; + JDIMENSION output_width; // USED + JDIMENSION output_height; // USED + int out_color_components; + int output_components; // USED + int rec_outbuf_height; + int actual_number_of_colors; + void *colormap; + JDIMENSION output_scanline; // USED + int input_scan_number; + JDIMENSION input_iMCU_row; + int output_scan_number; + JDIMENSION output_iMCU_row; + int (*coef_bits)[DCTSIZE2]; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + int data_precision; + jpeg_component_info *comp_info; + jboolean progressive_mode; + jboolean arith_code; + byte arith_dc_L[NUM_ARITH_TBLS]; + byte arith_dc_U[NUM_ARITH_TBLS]; + byte arith_ac_K[NUM_ARITH_TBLS]; + uint restart_interval; + jboolean saw_JFIF_marker; + byte JFIF_major_version; + byte JFIF_minor_version; + byte density_unit; + word X_density; + word Y_density; + jboolean saw_Adobe_marker; + byte Adobe_transform; + jboolean CCIR601_sampling; + void *marker_list; + int max_h_samp_factor; + int max_v_samp_factor; + int min_DCT_scaled_size; + JDIMENSION total_iMCU_rows; + void *sample_range_limit; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[D_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + int unread_marker; + void *master; + void *main; + void *coef; + void *post; + void *inputctl; + void *marker; + void *entropy; + void *idct; + void *upsample; + void *cconvert; + void *cquantize; +}; + +struct jpeg_compress_struct +{ + struct jpeg_error_mgr *err; + struct jpeg_memory_mgr *mem; + void *progress; + void *client_data; + jboolean is_decompressor; + int global_state; + + void *dest; + JDIMENSION image_width; + JDIMENSION image_height; + int input_components; + J_COLOR_SPACE in_color_space; + double input_gamma; + int data_precision; + int num_components; + J_COLOR_SPACE jpeg_color_space; + jpeg_component_info *comp_info; + void *quant_tbl_ptrs[NUM_QUANT_TBLS]; + void *dc_huff_tbl_ptrs[NUM_HUFF_TBLS]; + void *ac_huff_tbl_ptrs[NUM_HUFF_TBLS]; + byte arith_dc_L[NUM_ARITH_TBLS]; + byte arith_dc_U[NUM_ARITH_TBLS]; + byte arith_ac_K[NUM_ARITH_TBLS]; + int num_scans; + const void *scan_info; + jboolean raw_data_in; + jboolean arith_code; + jboolean optimize_coding; + jboolean CCIR601_sampling; + int smoothing_factor; + J_DCT_METHOD dct_method; + uint restart_interval; + int restart_in_rows; + jboolean write_JFIF_header; + byte JFIF_major_version; + byte JFIF_minor_version; + byte density_unit; + word X_density; + word Y_density; + jboolean write_Adobe_marker; + JDIMENSION next_scanline; + jboolean progressive_mode; + int max_h_samp_factor; + int max_v_samp_factor; + JDIMENSION total_iMCU_rows; + int comps_in_scan; + jpeg_component_info *cur_comp_info[MAX_COMPS_IN_SCAN]; + JDIMENSION MCUs_per_row; + JDIMENSION MCU_rows_in_scan; + int blocks_in_MCU; + int MCU_membership[C_MAX_BLOCKS_IN_MCU]; + int Ss, Se, Ah, Al; + void *master; + void *main; + void *prep; + void *coef; + void *marker; + void *cconvert; + void *downsample; + void *fdct; + void *entropy; + void *script_space; + int script_space_size; +}; + +struct jpeg_destination_mgr +{ + byte *next_output_byte; + size_t free_in_buffer; + + void (*init_destination)( j_compress_ptr cinfo ); + jboolean (*empty_output_buffer)( j_compress_ptr cinfo ); + void (*term_destination)( j_compress_ptr cinfo ); +}; + +// Functions exported from libjpeg +#define jpeg_create_compress( cinfo ) \ + jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof( struct jpeg_compress_struct )) +#define jpeg_create_decompress( cinfo ) \ + jpeg_CreateDecompress((cinfo), JPEG_LIB_VERSION, (size_t) sizeof( struct jpeg_decompress_struct )) + +extern void jpeg_CreateCompress( j_compress_ptr cinfo, int version, size_t structsize ); +extern void jpeg_CreateDecompress( j_decompress_ptr cinfo, int version, size_t structsize ); +extern void jpeg_destroy_compress( j_compress_ptr cinfo ); +extern void jpeg_destroy_decompress( j_decompress_ptr cinfo ); +extern void jpeg_finish_compress( j_compress_ptr cinfo ); +extern jboolean jpeg_finish_decompress( j_decompress_ptr cinfo ); +extern jboolean jpeg_resync_to_restart( j_decompress_ptr cinfo, int desired ); +extern int jpeg_read_header( j_decompress_ptr cinfo, jboolean require_image ); +extern JDIMENSION jpeg_read_scanlines( j_decompress_ptr cinfo, byte** scanlines, JDIMENSION max_lines ); +extern void jpeg_set_defaults( j_compress_ptr cinfo ); +extern void jpeg_set_quality( j_compress_ptr cinfo, int quality, jboolean force_baseline ); +extern jboolean jpeg_start_compress( j_compress_ptr cinfo, jboolean write_all_tables ); +extern jboolean jpeg_start_decompress( j_decompress_ptr cinfo ); +extern struct jpeg_error_mgr* jpeg_std_error( struct jpeg_error_mgr *err ); +extern JDIMENSION jpeg_write_scanlines( j_compress_ptr cinfo, byte** scanlines, JDIMENSION num_lines ); + +static byte jpeg_eoi_marker [2] = {0xFF, JPEG_EOI}; +static jmp_buf error_in_jpeg; +static bool jpeg_toolarge; + +// our own output manager for JPEG compression +typedef struct +{ + struct jpeg_destination_mgr pub; + + file_t *outfile; + byte *buffer; + size_t bufsize; // used if outfile is NULL +} dest_mgr; + +typedef dest_mgr *dest_ptr; + +/* +================================================================= + + JPEG decompression + +================================================================= +*/ +static void JPEG_Noop( j_decompress_ptr cinfo ) +{ +} + +static jboolean JPEG_FillInputBuffer( j_decompress_ptr cinfo ) +{ + // insert a fake EOI marker + cinfo->src->next_input_byte = jpeg_eoi_marker; + cinfo->src->bytes_in_buffer = 2; + return TRUE; +} + +static void JPEG_SkipInputData( j_decompress_ptr cinfo, long num_bytes ) +{ + if( cinfo->src->bytes_in_buffer <= (dword)num_bytes ) + { + cinfo->src->bytes_in_buffer = 0; + return; } - i = (jpg_file.curbyte >> (7 - jpg_file.curbit++)) & 0x01; - if(jpg_file.curbit == 8) jpg_file.curbit = 0; - - return i; + cinfo->src->next_input_byte += num_bytes; + cinfo->src->bytes_in_buffer -= num_bytes; } -int jpeg_read_bits( int num ) +static void JPEG_MemSrc( j_decompress_ptr cinfo, const byte *buffer, size_t filesize ) { - // read num bit - register int i, j; + cinfo->src = (struct jpeg_source_mgr *)cinfo->mem->alloc_small((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof( struct jpeg_source_mgr )); - for(i = 0, j = 0; i < num; i++) - { - j <<= 1; - j |= jpeg_read_bit(); - } - return j; + cinfo->src->next_input_byte = buffer; + cinfo->src->bytes_in_buffer = filesize; + + cinfo->src->init_source = JPEG_Noop; + cinfo->src->fill_input_buffer = JPEG_FillInputBuffer; + cinfo->src->skip_input_data = JPEG_SkipInputData; + cinfo->src->resync_to_restart = jpeg_resync_to_restart; // use the default method + cinfo->src->term_source = JPEG_Noop; } -int jpeg_bit2int( int bit, int i ) +static void JPEG_ErrorExit( j_common_ptr cinfo ) { - // convert bit code to int - if((i & (1 << (bit - 1))) > 0) return i; - return -(i ^ ((1 << bit) - 1)); + ((struct jpeg_decompress_struct*)cinfo)->err->output_message( cinfo ); + longjmp( error_in_jpeg, 1 ); } -int jpeg_huffmancode( huffman_table_t *table ) -{ - // get Huffman code - register int i,size,code; - for(size = 1, code = 0, i = 0; size < 17; size++) - { - code <<= 1; - code |= jpeg_read_bit(); - while(table->size[i] <= size) - { - if( table->code[i] == code ) - return table->hval[i]; - i++; - } - } - return code; -} - -void jpeg_idct( float *data ) -{ - // aa&n algorithm inverse DCT - float t0, t1, t2, t3, t4, t5, t6, t7; - float t10, t11, t12, t13; - float z5, z10, z11, z12, z13; - float *dataptr; - int i; - - dataptr = data; - - for(i = 0; i < 8; i++) - { - t0 = dataptr[8 * 0]; - t1 = dataptr[8 * 2]; - t2 = dataptr[8 * 4]; - t3 = dataptr[8 * 6]; - - t10 = t0 + t2; - t11 = t0 - t2; - t13 = t1 + t3; - t12 = - t13 + (t1 - t3) * 1.414213562;//?? - - t0 = t10 + t13; - t3 = t10 - t13; - t1 = t11 + t12; - t2 = t11 - t12; - t4 = dataptr[8 * 1]; - t5 = dataptr[8 * 3]; - t6 = dataptr[8 * 5]; - t7 = dataptr[8 * 7]; - - z13 = t6 + t5; - z10 = t6 - t5; - z11 = t4 + t7; - z12 = t4 - t7; - - t7 = z11 + z13; - t11 = (z11 - z13) * 1.414213562; - z5 = (z10 + z12) * 1.847759065; - t10 = - z5 + z12 * 1.082392200; - t12 = z5 - z10 * 2.613125930; - t6 = t12 - t7; - t5 = t11 - t6; - t4 = t10 + t5; - - dataptr[8 * 0] = t0 + t7; - dataptr[8 * 7] = t0 - t7; - dataptr[8 * 1] = t1 + t6; - dataptr[8 * 6] = t1 - t6; - dataptr[8 * 2] = t2 + t5; - dataptr[8 * 5] = t2 - t5; - dataptr[8 * 4] = t3 + t4; - dataptr[8 * 3] = t3 - t4; - dataptr++; - } - - dataptr = data; - - for(i = 0; i < 8; i++) - { - t10 = dataptr[0] + dataptr[4]; - t11 = dataptr[0] - dataptr[4]; - t13 = dataptr[2] + dataptr[6]; - t12 = - t13 + (dataptr[2] - dataptr[6]) * 1.414213562;//?? - - t0 = t10 + t13; - t3 = t10 - t13; - t1 = t11 + t12; - t2 = t11 - t12; - - z13 = dataptr[5] + dataptr[3]; - z10 = dataptr[5] - dataptr[3]; - z11 = dataptr[1] + dataptr[7]; - z12 = dataptr[1] - dataptr[7]; - - t7 = z11 + z13; - t11 = (z11 - z13) * 1.414213562; - z5 = (z10 + z12) * 1.847759065; - t10 = - z5 + z12 * 1.082392200; - t12 = z5 - z10 * 2.613125930; - - t6 = t12 - t7; - t5 = t11 - t6; - t4 = t10 + t5; - - dataptr[0] = t0 + t7; - dataptr[7] = t0 - t7; - dataptr[1] = t1 + t6; - dataptr[6] = t1 - t6; - dataptr[2] = t2 + t5; - dataptr[5] = t2 - t5; - dataptr[4] = t3 + t4; - dataptr[3] = t3 - t4; - dataptr += 8;//move ptr - } -} - -int jpeg_readmarkers( void ) -{ - // read jpeg markers - int marker, length, i, j, k, l, m; - huffman_table_t *hptr; - - while( 1 ) - { - marker = jpeg_read_byte(); - if( marker != 0xFF ) return 0; - - marker = jpeg_read_byte(); - if( marker != 0xD8 ) - { - length = jpeg_read_word(); - length -= 2; - - switch( marker ) - { - case 0xC0: // baseline - jpg_file.data_precision = jpeg_read_byte(); - jpg_file.height = jpeg_read_word(); - jpg_file.width = jpeg_read_word(); - jpg_file.num_components = jpeg_read_byte(); - if(length - 6 != jpg_file.num_components * 3) return 0; - - for(i = 0; i < jpg_file.num_components; i++) - { - jpg_file.component_info[i].id = jpeg_read_byte(); - j = jpeg_read_byte(); - jpg_file.component_info[i].h = (j >> 4) & 0x0F; - jpg_file.component_info[i].v = j & 0x0F; - jpg_file.component_info[i].t = jpeg_read_byte(); - } - break; - case 0xC1: // extended sequetial, Huffman - case 0xC2: // progressive, Huffman - case 0xC3: // lossless, Huffman - case 0xC5: // differential sequential, Huffman - case 0xC6: // differential progressive, Huffman - case 0xC7: // differential lossless, Huffman - case 0xC8: // reserved for JPEG extensions - case 0xC9: // extended sequential, arithmetic - case 0xCA: // progressive, arithmetic - case 0xCB: // lossless, arithmetic - case 0xCD: // differential sequential, arithmetic - case 0xCE: // differential progressive, arithmetic - case 0xCF: // differential lossless, arithmetic - return 0; // not supported yet - case 0xC4: // huffman table - while( length > 0 ) - { - k = jpeg_read_byte(); - if(k & 0x10) hptr = &jpg_file.hac[k & 0x0F]; - else hptr = &jpg_file.hdc[k & 0x0F]; - for(i = 0, j = 0; i < 16; i++) - { - hptr->bits[i] = jpeg_read_byte(); - j += hptr->bits[i]; - } - length -= 17; - for(i = 0; i < j; i++) hptr->hval[i] = jpeg_read_byte(); - length -= j; - - for(i = 0, k = 0, l = 0; i < 16; i++) - { - for(j = 0; j < hptr->bits[i]; j++, k++) - { - hptr->size[k] = i + 1; - hptr->code[k] = l++; - } - l <<= 1; - } - } - break; - case 0xDB: // quantization table - while( length > 0 ) - { - j = jpeg_read_byte(); - k = (j >> 4) & 0x0F; - for(i = 0; i < 64; i++) - { - if( k )jpg_file.qtable[j][i] = jpeg_read_word(); - else jpg_file.qtable[j][i] = jpeg_read_byte(); - } - length -= 65; - if( k )length -= 64; - } - break; - case 0xD9: // end of image (EOI) - return 0; - case 0xDA: // start of scan (SOS) - j = jpeg_read_byte(); - for(i = 0; i < j; i++) - { - k = jpeg_read_byte(); - m = jpeg_read_byte(); - for( l = 0; l < jpg_file.num_components; l++ ) - { - if( jpg_file.component_info[l].id == k ) - { - jpg_file.component_info[l].td = (m >> 4) & 0x0F; - jpg_file.component_info[l].ta = m & 0x0F; - } - } - } - jpg_file.scan.ss = jpeg_read_byte(); - jpg_file.scan.se = jpeg_read_byte(); - k = jpeg_read_byte(); - jpg_file.scan.ah = (k >> 4) & 0x0F; - jpg_file.scan.al = k & 0x0F; - return 1; - case 0xDD: // restart interval - jpg_file.restart_interval = jpeg_read_word(); - break; - default: - jpg_file.buffer += length; // move ptr - break; - } - } - } -} - - -void jpeg_decompress( void ) -{ - // decompress jpeg file (baseline algorithm) - register int x, y, i, j, k, l, c; - int X, Y, H, V, plane, scaleh[3], scalev[3]; - static float vector[64], dct[64]; - - static const int jpeg_zigzag[64] = - { - 0, 1, 5, 6, 14, 15, 27, 28, - 2, 4, 7, 13, 16, 26, 29, 42, - 3, 8, 12, 17, 25, 30, 41, 43, - 9, 11, 18, 24, 31, 40, 44, 53, - 10, 19, 23, 32, 39, 45, 52, 54, - 20, 22, 33, 38, 46, 51, 55, 60, - 21, 34, 37, 47, 50, 56, 59, 61, - 35, 36, 48, 49, 57, 58, 62, 63 - }; - - // 1.0, k = 0; cos(k * PI / 16) * sqrt(2), k = 1...7 - static const float aanscale[8] = - { - 1.0, 1.387039845, 1.306562965, 1.175875602, - 1.0, 0.785694958, 0.541196100, 0.275899379 - }; - - scaleh[0] = 1; - scalev[0] = 1; - - if(jpg_file.num_components == 3) - { - scaleh[1] = jpg_file.component_info[0].h / jpg_file.component_info[1].h; - scalev[1] = jpg_file.component_info[0].v / jpg_file.component_info[1].v; - scaleh[2] = jpg_file.component_info[0].h / jpg_file.component_info[2].h; - scalev[2] = jpg_file.component_info[0].v / jpg_file.component_info[2].v; - } - Mem_Set( jpg_file.dc, 0, sizeof(int) * 3); - - for( Y = 0; Y < jpg_file.height; Y += jpg_file.component_info[0].v << 3 ) - { - if( jpg_file.restart_interval > 0 ) jpg_file.curbit = 0; - for( X = 0; X < jpg_file.width; X += jpg_file.component_info[0].h << 3 ) - { - for(plane = 0; plane < jpg_file.num_components; plane++) - { - for(V = 0; V < jpg_file.component_info[plane].v; V++) - { - for(H = 0; H < jpg_file.component_info[plane].h; H++) - { - i = jpeg_huffmancode(&jpg_file.hdc[jpg_file.component_info[plane].td]); - i &= 0x0F; - vector[0] = jpg_file.dc[plane] + jpeg_bit2int(i,jpeg_read_bits(i)); - jpg_file.dc[plane] = vector[0]; - i = 1; - - while(i < 64) - { - j = jpeg_huffmancode(&jpg_file.hac[jpg_file.component_info[plane].ta]); - if(j == 0) while(i < 64) vector[i++] = 0; - else - { - k = i + ((j >> 4) & 0x0F); - while(i < k) vector[i++] = 0; - j &= 0x0F; - vector[i++] = jpeg_bit2int(j,jpeg_read_bits(j)); - } - } - - k = jpg_file.component_info[plane].t; - for(y = 0, i = 0; y < 8; y++) - { - for(x = 0; x < 8; x++, i++) - { - j = jpeg_zigzag[i]; - dct[i] = vector[j] * jpg_file.qtable[k][j] * aanscale[x] * aanscale[y]; - } - } - - jpeg_idct(dct); - for(y = 0; y < 8; y++) - { - for(x = 0; x < 8; x++) - { - c = ((int)dct[(y << 3) + x] >> 3) + 128; - if(c < 0) c = 0; - else if(c > 255) c = 255; - - if(scaleh[plane] == 1 && scalev[plane] == 1) - { - i = X + x + (H << 3); - j = Y + y + (V << 3); - if(i < jpg_file.width && j < jpg_file.height) - jpg_file.data[((j * jpg_file.width + i) << 2) + plane] = c; - } - else for(l = 0; l < scalev[plane]; l++)//else for, heh... - { - for(k = 0; k < scaleh[plane]; k++) - { - i = X + (x + (H << 3)) * scaleh[plane] + k; - j = Y + (y + (V << 3)) * scalev[plane] + l; - if(i < jpg_file.width && j < jpg_file.height) - jpg_file.data[((j * jpg_file.width + i) << 2) + plane] = c; - } - } - } - } - } - } - } - } - } -} - -void jpeg_ycbcr2rgba( void ) -{ - int i, Y, Cb, Cr, R, G, B; - - // convert YCbCr image to RGBA - for( i = 0; i < jpg_file.width * jpg_file.height << 2; i += 4 ) - { - Y = jpg_file.data[i+0]; - Cb = jpg_file.data[i+1] - 128; - Cr = jpg_file.data[i+2] - 128; - - R = Y + 1.40200 * Cr; - G = Y - 0.34414 * Cb - 0.71414 * Cr; - B = Y + 1.77200 * Cb; - - // bound colors - R = bound( 0, R, 255 ); - G = bound( 0, G, 255 ); - B = bound( 0, B, 255 ); - - jpg_file.data[i+0] = R; - jpg_file.data[i+1] = G; - jpg_file.data[i+2] = B; - jpg_file.data[i+3] = 0xff; // no alpha channel - } - image.flags |= IMAGE_HAS_COLOR; -} - -void jpeg_gray2rgba( void ) -{ - int i, j; - - // grayscale image to RGBA - for(i = 0; i < jpg_file.width * jpg_file.height << 2; i += 4) - { - j = jpg_file.data[i]; - jpg_file.data[i+0] = j; - jpg_file.data[i+1] = j; - jpg_file.data[i+2] = j; - jpg_file.data[i+3] = 0xff; - } -} - - /* ============= Image_LoadJPG @@ -472,27 +428,215 @@ Image_LoadJPG */ bool Image_LoadJPG( const char *name, const byte *buffer, size_t filesize ) { - Mem_Set( &jpg_file, 0, sizeof( jpg_file )); - jpg_file.buffer = (byte *)buffer; + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + byte *image_buffer = NULL; + byte *scanline = NULL; + uint line; - if(!jpeg_readmarkers()) - return false; // it's not a jpg file, just skip it + cinfo.err = jpeg_std_error( &jerr ); + jpeg_create_decompress( &cinfo ); + if( setjmp( error_in_jpeg )) goto error_caught; + cinfo.err = jpeg_std_error( &jerr ); + cinfo.err->error_exit = JPEG_ErrorExit; + JPEG_MemSrc( &cinfo, buffer, filesize ); + jpeg_read_header( &cinfo, TRUE ); + jpeg_start_decompress( &cinfo ); - image.width = jpg_file.width; - image.height = jpg_file.height; + image.width = cinfo.image_width; + image.height = cinfo.image_height; if(!Image_ValidSize( name )) return false; - image.size = jpg_file.width * jpg_file.height * 4; - jpg_file.data = Mem_Alloc( Sys.imagepool, image.size ); + image.size = image.width * image.height * 4; + image.rgba = (byte *)Mem_Alloc( Sys.imagepool, image.size ); + scanline = (byte *)Mem_Alloc( Sys.imagepool, image.width * cinfo.output_components ); - jpeg_decompress(); - if( jpg_file.num_components == 1 ) jpeg_gray2rgba(); - if( jpg_file.num_components == 3 ) jpeg_ycbcr2rgba(); + // decompress the image, line by line + line = 0; + while( cinfo.output_scanline < cinfo.output_height ) + { + byte *buffer_ptr; + int ind; + + jpeg_read_scanlines( &cinfo, &scanline, 1 ); + + // convert the image to RGBA + switch( cinfo.output_components ) + { + case 3:// RGB images + buffer_ptr = &image.rgba[image.width * line * 4]; + for( ind = 0; ind < image.width * 3; ind += 3, buffer_ptr += 4 ) + { + buffer_ptr[0] = scanline[ind+0]; + buffer_ptr[1] = scanline[ind+1]; + buffer_ptr[2] = scanline[ind+2]; + buffer_ptr[3] = 0xFF; + } + break; + case 1: + default: // greyscale images (default to it, just in case) + buffer_ptr = &image.rgba[image.width * line * 4]; + for( ind = 0; ind < image.width; ind++, buffer_ptr += 4 ) + { + buffer_ptr[0] = scanline[ind]; + buffer_ptr[1] = scanline[ind]; + buffer_ptr[2] = scanline[ind]; + buffer_ptr[3] = 255; + } + } + line++; + } + Mem_Free( scanline ); + + if( cinfo.output_components == 3 ) + image.flags |= IMAGE_HAS_COLOR; + + jpeg_finish_decompress( &cinfo ); + jpeg_destroy_decompress( &cinfo ); - image.rgba = jpg_file.data; image.type = PF_RGBA_32; image.depth = 1; image.num_mips = 1; return true; +error_caught: + if( scanline ) Mem_Free( scanline ); + if( image.rgba ) Mem_Free( image.rgba ); + jpeg_destroy_decompress( &cinfo ); + + return false; +} + +/* +================================================================= + + JPEG compression + +================================================================= +*/ +static void JPEG_InitDestination( j_compress_ptr cinfo ) +{ + dest_ptr dest = (dest_ptr)cinfo->dest; + + dest->buffer = (byte*)cinfo->mem->alloc_small ((j_common_ptr) cinfo, JPOOL_IMAGE, JPEG_OUTPUT_BUF_SIZE * sizeof( byte )); + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; +} + +static jboolean JPEG_EmptyOutputBuffer( j_compress_ptr cinfo ) +{ + dest_ptr dest = (dest_ptr)cinfo->dest; + + if( FS_Write( dest->outfile, dest->buffer, JPEG_OUTPUT_BUF_SIZE ) != (size_t)JPEG_OUTPUT_BUF_SIZE ) + longjmp( error_in_jpeg, 1 ); + + dest->pub.next_output_byte = dest->buffer; + dest->pub.free_in_buffer = JPEG_OUTPUT_BUF_SIZE; + return true; +} + +static void JPEG_TermDestination( j_compress_ptr cinfo ) +{ + dest_ptr dest = (dest_ptr)cinfo->dest; + size_t datacount = JPEG_OUTPUT_BUF_SIZE - dest->pub.free_in_buffer; + + // write any data remaining in the buffer + if( datacount > 0 ) + if( FS_Write( dest->outfile, dest->buffer, datacount ) != (fs_offset_t)datacount ) + longjmp( error_in_jpeg, 1 ); +} + +static void JPEG_FileDest( j_compress_ptr cinfo, file_t *outfile ) +{ + dest_ptr dest; + + // first time for this JPEG object? + if( cinfo->dest == NULL ) + cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof( dest_mgr )); + + dest = (dest_ptr)cinfo->dest; + dest->pub.init_destination = JPEG_InitDestination; + dest->pub.empty_output_buffer = JPEG_EmptyOutputBuffer; + dest->pub.term_destination = JPEG_TermDestination; + dest->outfile = outfile; +} + +/* +============= +Image_SaveJPG +============= +*/ +bool Image_SaveJPG( const char *name, rgbdata_t *pix ) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + byte *scanline; + uint linesize; + file_t *file; + + if( FS_FileExists( name ) && !(image.cmd_flags & IL_ALLOW_OVERWRITE )) + return false; // already existed + + // Open the file + file = FS_Open( name, "wb" ); + if( !file ) return false; + + if( setjmp( error_in_jpeg )) + goto error_caught; + cinfo.err = jpeg_std_error( &jerr ); + cinfo.err->error_exit = JPEG_ErrorExit; + + jpeg_create_compress( &cinfo ); + JPEG_FileDest( &cinfo, file ); + + // Set the parameters for compression + cinfo.image_width = pix->width; + cinfo.image_height = pix->height; + + // get image description + switch( pix->type ) + { + case PF_RGB_24: cinfo.input_components = 3; break; + case PF_RGBA_32: cinfo.input_components = 4; break; + default: + MsgDev( D_ERROR, "Image_SaveJPG: unsupported image type %s\n", PFDesc[pix->type].name ); + goto error_caught; + } + + if( pix->flags & IMAGE_HAS_COLOR ) + cinfo.in_color_space = JCS_RGB; + else cinfo.in_color_space = JCS_GRAYSCALE; + + jpeg_set_defaults( &cinfo ); + jpeg_set_quality( &cinfo, (jpg_quality->integer * 10), TRUE ); + + // turn off subsampling (to make text look better) + cinfo.optimize_coding = 1; + cinfo.comp_info[0].h_samp_factor = 1; + cinfo.comp_info[0].v_samp_factor = 1; + cinfo.comp_info[1].h_samp_factor = 1; + cinfo.comp_info[1].v_samp_factor = 1; + cinfo.comp_info[2].h_samp_factor = 1; + cinfo.comp_info[2].v_samp_factor = 1; + + jpeg_start_compress( &cinfo, true ); + + // compress each scanline + linesize = cinfo.image_width * cinfo.input_components; + while( cinfo.next_scanline < cinfo.image_height ) + { + scanline = &pix->buffer[cinfo.next_scanline * linesize]; + jpeg_write_scanlines( &cinfo, &scanline, 1 ); + } + + jpeg_finish_compress( &cinfo ); + jpeg_destroy_compress( &cinfo ); + + FS_Close( file ); + return true; + +error_caught: + jpeg_destroy_compress( &cinfo ); + FS_Close( file ); + return false; } \ No newline at end of file diff --git a/launch/imagelib/img_jpg.old b/launch/imagelib/img_jpg.old new file mode 100644 index 00000000..34d2d75b --- /dev/null +++ b/launch/imagelib/img_jpg.old @@ -0,0 +1,498 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// img_jpg.c - jpg format load & save +//======================================================================= + +#include "imagelib.h" + +jpg_t jpg_file; // jpeg read struct + +int jpeg_read_byte( void ) +{ + // read byte + jpg_file.curbyte = *jpg_file.buffer++; + jpg_file.curbit = 0; + return jpg_file.curbyte; +} + +int jpeg_read_word( void ) +{ + // read word + word i = BuffLittleShort( jpg_file.buffer); + i = ((i << 8) & 0xFF00) + ((i >> 8) & 0x00FF); + jpg_file.buffer += 2; + + return i; +} + +int jpeg_read_bit( void ) +{ + // read bit + register int i; + if(jpg_file.curbit == 0) + { + jpeg_read_byte(); + if(jpg_file.curbyte == 0xFF) + { + while(jpg_file.curbyte == 0xFF) jpeg_read_byte(); + if(jpg_file.curbyte >= 0xD0 && jpg_file.curbyte <= 0xD7) + Mem_Set(jpg_file.dc, 0, sizeof(int) * 3); + if(jpg_file.curbyte == 0) jpg_file.curbyte = 0xFF; + else jpeg_read_byte(); + } + } + + i = (jpg_file.curbyte >> (7 - jpg_file.curbit++)) & 0x01; + if(jpg_file.curbit == 8) jpg_file.curbit = 0; + + return i; +} + +int jpeg_read_bits( int num ) +{ + // read num bit + register int i, j; + + for(i = 0, j = 0; i < num; i++) + { + j <<= 1; + j |= jpeg_read_bit(); + } + return j; +} + +int jpeg_bit2int( int bit, int i ) +{ + // convert bit code to int + if((i & (1 << (bit - 1))) > 0) return i; + return -(i ^ ((1 << bit) - 1)); +} + +int jpeg_huffmancode( huffman_table_t *table ) +{ + // get Huffman code + register int i,size,code; + for(size = 1, code = 0, i = 0; size < 17; size++) + { + code <<= 1; + code |= jpeg_read_bit(); + while(table->size[i] <= size) + { + if( table->code[i] == code ) + return table->hval[i]; + i++; + } + } + return code; +} + +void jpeg_idct( float *data ) +{ + // aa&n algorithm inverse DCT + float t0, t1, t2, t3, t4, t5, t6, t7; + float t10, t11, t12, t13; + float z5, z10, z11, z12, z13; + float *dataptr; + int i; + + dataptr = data; + + for(i = 0; i < 8; i++) + { + t0 = dataptr[8 * 0]; + t1 = dataptr[8 * 2]; + t2 = dataptr[8 * 4]; + t3 = dataptr[8 * 6]; + + t10 = t0 + t2; + t11 = t0 - t2; + t13 = t1 + t3; + t12 = - t13 + (t1 - t3) * 1.414213562;//?? + + t0 = t10 + t13; + t3 = t10 - t13; + t1 = t11 + t12; + t2 = t11 - t12; + t4 = dataptr[8 * 1]; + t5 = dataptr[8 * 3]; + t6 = dataptr[8 * 5]; + t7 = dataptr[8 * 7]; + + z13 = t6 + t5; + z10 = t6 - t5; + z11 = t4 + t7; + z12 = t4 - t7; + + t7 = z11 + z13; + t11 = (z11 - z13) * 1.414213562; + z5 = (z10 + z12) * 1.847759065; + t10 = - z5 + z12 * 1.082392200; + t12 = z5 - z10 * 2.613125930; + t6 = t12 - t7; + t5 = t11 - t6; + t4 = t10 + t5; + + dataptr[8 * 0] = t0 + t7; + dataptr[8 * 7] = t0 - t7; + dataptr[8 * 1] = t1 + t6; + dataptr[8 * 6] = t1 - t6; + dataptr[8 * 2] = t2 + t5; + dataptr[8 * 5] = t2 - t5; + dataptr[8 * 4] = t3 + t4; + dataptr[8 * 3] = t3 - t4; + dataptr++; + } + + dataptr = data; + + for(i = 0; i < 8; i++) + { + t10 = dataptr[0] + dataptr[4]; + t11 = dataptr[0] - dataptr[4]; + t13 = dataptr[2] + dataptr[6]; + t12 = - t13 + (dataptr[2] - dataptr[6]) * 1.414213562;//?? + + t0 = t10 + t13; + t3 = t10 - t13; + t1 = t11 + t12; + t2 = t11 - t12; + + z13 = dataptr[5] + dataptr[3]; + z10 = dataptr[5] - dataptr[3]; + z11 = dataptr[1] + dataptr[7]; + z12 = dataptr[1] - dataptr[7]; + + t7 = z11 + z13; + t11 = (z11 - z13) * 1.414213562; + z5 = (z10 + z12) * 1.847759065; + t10 = - z5 + z12 * 1.082392200; + t12 = z5 - z10 * 2.613125930; + + t6 = t12 - t7; + t5 = t11 - t6; + t4 = t10 + t5; + + dataptr[0] = t0 + t7; + dataptr[7] = t0 - t7; + dataptr[1] = t1 + t6; + dataptr[6] = t1 - t6; + dataptr[2] = t2 + t5; + dataptr[5] = t2 - t5; + dataptr[4] = t3 + t4; + dataptr[3] = t3 - t4; + dataptr += 8;//move ptr + } +} + +int jpeg_readmarkers( void ) +{ + // read jpeg markers + int marker, length, i, j, k, l, m; + huffman_table_t *hptr; + + while( 1 ) + { + marker = jpeg_read_byte(); + if( marker != 0xFF ) return 0; + + marker = jpeg_read_byte(); + if( marker != 0xD8 ) + { + length = jpeg_read_word(); + length -= 2; + + switch( marker ) + { + case 0xC0: // baseline + jpg_file.data_precision = jpeg_read_byte(); + jpg_file.height = jpeg_read_word(); + jpg_file.width = jpeg_read_word(); + jpg_file.num_components = jpeg_read_byte(); + if(length - 6 != jpg_file.num_components * 3) return 0; + + for(i = 0; i < jpg_file.num_components; i++) + { + jpg_file.component_info[i].id = jpeg_read_byte(); + j = jpeg_read_byte(); + jpg_file.component_info[i].h = (j >> 4) & 0x0F; + jpg_file.component_info[i].v = j & 0x0F; + jpg_file.component_info[i].t = jpeg_read_byte(); + } + break; + case 0xC1: // extended sequetial, Huffman + case 0xC2: // progressive, Huffman + case 0xC3: // lossless, Huffman + case 0xC5: // differential sequential, Huffman + case 0xC6: // differential progressive, Huffman + case 0xC7: // differential lossless, Huffman + case 0xC8: // reserved for JPEG extensions + case 0xC9: // extended sequential, arithmetic + case 0xCA: // progressive, arithmetic + case 0xCB: // lossless, arithmetic + case 0xCD: // differential sequential, arithmetic + case 0xCE: // differential progressive, arithmetic + case 0xCF: // differential lossless, arithmetic + return 0; // not supported yet + case 0xC4: // huffman table + while( length > 0 ) + { + k = jpeg_read_byte(); + if(k & 0x10) hptr = &jpg_file.hac[k & 0x0F]; + else hptr = &jpg_file.hdc[k & 0x0F]; + for(i = 0, j = 0; i < 16; i++) + { + hptr->bits[i] = jpeg_read_byte(); + j += hptr->bits[i]; + } + length -= 17; + for(i = 0; i < j; i++) hptr->hval[i] = jpeg_read_byte(); + length -= j; + + for(i = 0, k = 0, l = 0; i < 16; i++) + { + for(j = 0; j < hptr->bits[i]; j++, k++) + { + hptr->size[k] = i + 1; + hptr->code[k] = l++; + } + l <<= 1; + } + } + break; + case 0xDB: // quantization table + while( length > 0 ) + { + j = jpeg_read_byte(); + k = (j >> 4) & 0x0F; + for(i = 0; i < 64; i++) + { + if( k )jpg_file.qtable[j][i] = jpeg_read_word(); + else jpg_file.qtable[j][i] = jpeg_read_byte(); + } + length -= 65; + if( k )length -= 64; + } + break; + case 0xD9: // end of image (EOI) + return 0; + case 0xDA: // start of scan (SOS) + j = jpeg_read_byte(); + for(i = 0; i < j; i++) + { + k = jpeg_read_byte(); + m = jpeg_read_byte(); + for( l = 0; l < jpg_file.num_components; l++ ) + { + if( jpg_file.component_info[l].id == k ) + { + jpg_file.component_info[l].td = (m >> 4) & 0x0F; + jpg_file.component_info[l].ta = m & 0x0F; + } + } + } + jpg_file.scan.ss = jpeg_read_byte(); + jpg_file.scan.se = jpeg_read_byte(); + k = jpeg_read_byte(); + jpg_file.scan.ah = (k >> 4) & 0x0F; + jpg_file.scan.al = k & 0x0F; + return 1; + case 0xDD: // restart interval + jpg_file.restart_interval = jpeg_read_word(); + break; + default: + jpg_file.buffer += length; // move ptr + break; + } + } + } +} + + +void jpeg_decompress( void ) +{ + // decompress jpeg file (baseline algorithm) + register int x, y, i, j, k, l, c; + int X, Y, H, V, plane, scaleh[3], scalev[3]; + static float vector[64], dct[64]; + + static const int jpeg_zigzag[64] = + { + 0, 1, 5, 6, 14, 15, 27, 28, + 2, 4, 7, 13, 16, 26, 29, 42, + 3, 8, 12, 17, 25, 30, 41, 43, + 9, 11, 18, 24, 31, 40, 44, 53, + 10, 19, 23, 32, 39, 45, 52, 54, + 20, 22, 33, 38, 46, 51, 55, 60, + 21, 34, 37, 47, 50, 56, 59, 61, + 35, 36, 48, 49, 57, 58, 62, 63 + }; + + // 1.0, k = 0; cos(k * PI / 16) * sqrt(2), k = 1...7 + static const float aanscale[8] = + { + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + }; + + scaleh[0] = 1; + scalev[0] = 1; + + if(jpg_file.num_components == 3) + { + scaleh[1] = jpg_file.component_info[0].h / jpg_file.component_info[1].h; + scalev[1] = jpg_file.component_info[0].v / jpg_file.component_info[1].v; + scaleh[2] = jpg_file.component_info[0].h / jpg_file.component_info[2].h; + scalev[2] = jpg_file.component_info[0].v / jpg_file.component_info[2].v; + } + Mem_Set( jpg_file.dc, 0, sizeof(int) * 3); + + for( Y = 0; Y < jpg_file.height; Y += jpg_file.component_info[0].v << 3 ) + { + if( jpg_file.restart_interval > 0 ) jpg_file.curbit = 0; + for( X = 0; X < jpg_file.width; X += jpg_file.component_info[0].h << 3 ) + { + for(plane = 0; plane < jpg_file.num_components; plane++) + { + for(V = 0; V < jpg_file.component_info[plane].v; V++) + { + for(H = 0; H < jpg_file.component_info[plane].h; H++) + { + i = jpeg_huffmancode(&jpg_file.hdc[jpg_file.component_info[plane].td]); + i &= 0x0F; + vector[0] = jpg_file.dc[plane] + jpeg_bit2int(i,jpeg_read_bits(i)); + jpg_file.dc[plane] = vector[0]; + i = 1; + + while(i < 64) + { + j = jpeg_huffmancode(&jpg_file.hac[jpg_file.component_info[plane].ta]); + if(j == 0) while(i < 64) vector[i++] = 0; + else + { + k = i + ((j >> 4) & 0x0F); + while(i < k) vector[i++] = 0; + j &= 0x0F; + vector[i++] = jpeg_bit2int(j,jpeg_read_bits(j)); + } + } + + k = jpg_file.component_info[plane].t; + for(y = 0, i = 0; y < 8; y++) + { + for(x = 0; x < 8; x++, i++) + { + j = jpeg_zigzag[i]; + dct[i] = vector[j] * jpg_file.qtable[k][j] * aanscale[x] * aanscale[y]; + } + } + + jpeg_idct(dct); + for(y = 0; y < 8; y++) + { + for(x = 0; x < 8; x++) + { + c = ((int)dct[(y << 3) + x] >> 3) + 128; + if(c < 0) c = 0; + else if(c > 255) c = 255; + + if(scaleh[plane] == 1 && scalev[plane] == 1) + { + i = X + x + (H << 3); + j = Y + y + (V << 3); + if(i < jpg_file.width && j < jpg_file.height) + jpg_file.data[((j * jpg_file.width + i) << 2) + plane] = c; + } + else for(l = 0; l < scalev[plane]; l++)//else for, heh... + { + for(k = 0; k < scaleh[plane]; k++) + { + i = X + (x + (H << 3)) * scaleh[plane] + k; + j = Y + (y + (V << 3)) * scalev[plane] + l; + if(i < jpg_file.width && j < jpg_file.height) + jpg_file.data[((j * jpg_file.width + i) << 2) + plane] = c; + } + } + } + } + } + } + } + } + } +} + +void jpeg_ycbcr2rgba( void ) +{ + int i, Y, Cb, Cr, R, G, B; + + // convert YCbCr image to RGBA + for( i = 0; i < jpg_file.width * jpg_file.height << 2; i += 4 ) + { + Y = jpg_file.data[i+0]; + Cb = jpg_file.data[i+1] - 128; + Cr = jpg_file.data[i+2] - 128; + + R = Y + 1.40200 * Cr; + G = Y - 0.34414 * Cb - 0.71414 * Cr; + B = Y + 1.77200 * Cb; + + // bound colors + R = bound( 0, R, 255 ); + G = bound( 0, G, 255 ); + B = bound( 0, B, 255 ); + + jpg_file.data[i+0] = R; + jpg_file.data[i+1] = G; + jpg_file.data[i+2] = B; + jpg_file.data[i+3] = 0xff; // no alpha channel + } + image.flags |= IMAGE_HAS_COLOR; +} + +void jpeg_gray2rgba( void ) +{ + int i, j; + + // grayscale image to RGBA + for(i = 0; i < jpg_file.width * jpg_file.height << 2; i += 4) + { + j = jpg_file.data[i]; + jpg_file.data[i+0] = j; + jpg_file.data[i+1] = j; + jpg_file.data[i+2] = j; + jpg_file.data[i+3] = 0xff; + } +} + + +/* +============= +Image_LoadJPG +============= +*/ +bool Image_LoadJPG( const char *name, const byte *buffer, size_t filesize ) +{ + Mem_Set( &jpg_file, 0, sizeof( jpg_file )); + jpg_file.buffer = (byte *)buffer; + + if(!jpeg_readmarkers()) + return false; // it's not a jpg file, just skip it + + image.width = jpg_file.width; + image.height = jpg_file.height; + if(!Image_ValidSize( name )) return false; + + image.size = jpg_file.width * jpg_file.height * 4; + jpg_file.data = Mem_Alloc( Sys.imagepool, image.size ); + + jpeg_decompress(); + if( jpg_file.num_components == 1 ) jpeg_gray2rgba(); + if( jpg_file.num_components == 3 ) jpeg_ycbcr2rgba(); + + image.rgba = jpg_file.data; + image.type = PF_RGBA_32; + image.depth = 1; + image.num_mips = 1; + + return true; +} \ No newline at end of file diff --git a/launch/imagelib/img_main.c b/launch/imagelib/img_main.c index 590e5667..31ed6bee 100644 --- a/launch/imagelib/img_main.c +++ b/launch/imagelib/img_main.c @@ -16,7 +16,17 @@ typedef struct suffix_s image_hint_t hint; } suffix_t; -static const suffix_t skybox_3ds[6] = +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 }, @@ -54,7 +64,8 @@ typedef struct cubepack_s static const cubepack_t load_cubemap[] = { -{ "3Ds Sky ", skybox_3ds }, +{ "3Ds Sky1", skybox_qv1 }, +{ "3Ds Sky2", skybox_qv2 }, { "3Ds Cube", cubemap_v2 }, { "Tenebrae", cubemap_v1 }, // FIXME: remove this ? { NULL, NULL }, @@ -312,7 +323,6 @@ rgbdata_t *FS_LoadImage( const char *filename, const byte *buffer, size_t size ) if( !com.strnicmp( suffix, cmap->type[i].suf, suflen )) { com.strncpy( path, loadname, com.strlen( loadname ) - suflen + 1 ); - Msg( "path %s - %s\n", path, suffix ); FS_DefaultExtension( path, ".dds" ); image.filter = cmap->type[i].hint; // install side hint f = FS_LoadFile( path, &filesize ); diff --git a/launch/imagelib/img_tga.c b/launch/imagelib/img_tga.c index ab7e3322..edca312f 100644 --- a/launch/imagelib/img_tga.c +++ b/launch/imagelib/img_tga.c @@ -241,6 +241,7 @@ bool Image_SaveTGA( const char *name, rgbdata_t *pix ) 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; } diff --git a/launch/imagelib/img_utils.c b/launch/imagelib/img_utils.c index 866667b9..f6f8ea43 100644 --- a/launch/imagelib/img_utils.c +++ b/launch/imagelib/img_utils.c @@ -10,6 +10,7 @@ cvar_t *image_profile; cvar_t *gl_round_down; cvar_t *fs_textures; cvar_t *png_compression; +cvar_t *jpg_quality; #define LERPBYTE(i) r = resamplerow1[i];out[i] = (byte)((((resamplerow2[i] - r) * lerp)>>16) + r) @@ -285,6 +286,7 @@ static const saveformat_t save_xash048[] = static const saveformat_t save_xash051[] = { { "%s%s.%s", "tga", Image_SaveTGA }, // tga screenshots +{ "%s%s.%s", "jpg", Image_SaveJPG }, // tga levelshots or screenshots { "%s%s.%s", "png", Image_SavePNG }, // png levelshots { "%s%s.%s", "dds", Image_SaveDDS }, // dds envshots { NULL, NULL, NULL } @@ -298,7 +300,9 @@ void Image_Init( void ) gl_round_down = Cvar_Get( "gl_round_down", "0", CVAR_SYSTEMINFO, "down size non-power of two textures" ); fs_textures = Cvar_Get( "fs_textures_path", "textures", CVAR_SYSTEMINFO, "textures default folder" ); png_compression = Cvar_Get( "png_compression", "9", CVAR_SYSTEMINFO, "pnglib compression level" ); + jpg_quality = Cvar_Get( "jpg_quality", "7", CVAR_SYSTEMINFO, "jpglib quality level" ); Cvar_SetValue( "png_compression", bound( 0, png_compression->integer, 9 )); + Cvar_SetValue( "jpg_quality", bound( 0, jpg_quality->integer, 10 )); // install image formats (can be re-install later by Image_Setup) switch( Sys.app_name ) diff --git a/launch/imagelib/jpg.lib b/launch/imagelib/jpg.lib new file mode 100644 index 0000000000000000000000000000000000000000..3d05e7a503ce73566c3f2853d2d16ec839f3c6bf GIT binary patch literal 239582 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

m;;Di0s6ps2Dg0?yYoOqtIH};D{N|Z6 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 + +

+

Build Log

+

+--------------------Configuration: launch - Win32 Debug-------------------- +

+

Command Lines

+Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75C.tmp" with contents +[ +/nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "imagelib" /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"..\temp\launch\!debug/" /Fo"..\temp\launch\!debug/" /Fd"..\temp\launch\!debug/" /FD /GZ /c +"D:\Xash3D\src_main\launch\imagelib\img_main.c" +] +Creating command line "cl.exe @C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75C.tmp" +Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75D.tmp" with contents +[ +zlib.lib png.lib jpg.lib user32.lib gdi32.lib advapi32.lib winmm.lib /nologo /dll /incremental:yes /pdb:"..\temp\launch\!debug/launch.pdb" /debug /machine:I386 /nodefaultlib:"libc.lib" /out:"..\temp\launch\!debug/launch.dll" /implib:"..\temp\launch\!debug/launch.lib" /pdbtype:sept /libpath:"./imagelib" +"\Xash3D\src_main\temp\launch\!debug\cmd.obj" +"\Xash3D\src_main\temp\launch\!debug\console.obj" +"\Xash3D\src_main\temp\launch\!debug\cpuinfo.obj" +"\Xash3D\src_main\temp\launch\!debug\crclib.obj" +"\Xash3D\src_main\temp\launch\!debug\cvar.obj" +"\Xash3D\src_main\temp\launch\!debug\export.obj" +"\Xash3D\src_main\temp\launch\!debug\filesystem.obj" +"\Xash3D\src_main\temp\launch\!debug\img_bmp.obj" +"\Xash3D\src_main\temp\launch\!debug\img_dds.obj" +"\Xash3D\src_main\temp\launch\!debug\img_jpg.obj" +"\Xash3D\src_main\temp\launch\!debug\img_main.obj" +"\Xash3D\src_main\temp\launch\!debug\img_pcx.obj" +"\Xash3D\src_main\temp\launch\!debug\img_png.obj" +"\Xash3D\src_main\temp\launch\!debug\img_tga.obj" +"\Xash3D\src_main\temp\launch\!debug\img_utils.obj" +"\Xash3D\src_main\temp\launch\!debug\img_vtf.obj" +"\Xash3D\src_main\temp\launch\!debug\img_wad.obj" +"\Xash3D\src_main\temp\launch\!debug\memlib.obj" +"\Xash3D\src_main\temp\launch\!debug\network.obj" +"\Xash3D\src_main\temp\launch\!debug\parselib.obj" +"\Xash3D\src_main\temp\launch\!debug\patch.obj" +"\Xash3D\src_main\temp\launch\!debug\stdlib.obj" +"\Xash3D\src_main\temp\launch\!debug\system.obj" +"\Xash3D\src_main\temp\launch\!debug\utils.obj" +] +Creating command line "link.exe @C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75D.tmp" +Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75E.bat" with contents +[ +@echo off +copy \Xash3D\src_main\temp\launch\!debug\launch.dll "D:\Xash3D\bin\launch.dll" +] +Creating command line "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP75E.bat" +Compiling... +img_main.c +Linking... +

Output Window

+Performing Custom Build Step on \Xash3D\src_main\temp\launch\!debug\launch.dll +‘ª®¯¨à®¢ ­® ä ©«®¢: 1. + + + +

Results

+launch.dll - 0 error(s), 0 warning(s) +
+ + diff --git a/physic/cm_collision.c b/physic/cm_collision.c index 1b5d9fa0..34c32aba 100644 --- a/physic/cm_collision.c +++ b/physic/cm_collision.c @@ -1355,9 +1355,9 @@ void CM_CollisionClipTrace_BrushBox( trace_t *trace, const vec3_t cmins, const v VectorAdd( end, mins, endmins ); VectorAdd( end, maxs, endmaxs ); - boxbrush = CM_CollisionBrushForBox( identitymatrix, cmins, cmaxs, supercontents, surfaceflags, surface ); - thisbrush_start = CM_CollisionBrushForBox( identitymatrix, startmins, startmaxs, 0, 0, NULL ); - thisbrush_end = CM_CollisionBrushForBox( identitymatrix, endmins, endmaxs, 0, 0, NULL ); + boxbrush = CM_CollisionBrushForBox( matrix4x4_identity, cmins, cmaxs, supercontents, surfaceflags, surface ); + thisbrush_start = CM_CollisionBrushForBox( matrix4x4_identity, startmins, startmaxs, 0, 0, NULL ); + thisbrush_end = CM_CollisionBrushForBox( matrix4x4_identity, endmins, endmaxs, 0, 0, NULL ); Mem_Set( trace, 0, sizeof(trace_t)); trace->contentsmask = hitsupercontentsmask; diff --git a/physic/cm_trace.c b/physic/cm_trace.c index a3cac8cf..f88a04b2 100644 --- a/physic/cm_trace.c +++ b/physic/cm_trace.c @@ -275,8 +275,8 @@ void CM_TraceBmodel( const vec3_t start, const vec3_t end, const vec3_t mins, co VectorAdd(end, mins, boxendmins); VectorAdd(end, maxs, boxendmaxs); - thisbrush_start = CM_CollisionBrushForBox( identitymatrix, boxstartmins, boxstartmaxs, 0, 0, NULL ); - thisbrush_end = CM_CollisionBrushForBox( identitymatrix, boxendmins, boxendmaxs, 0, 0, NULL ); + thisbrush_start = CM_CollisionBrushForBox( matrix4x4_identity, boxstartmins, boxstartmaxs, 0, 0, NULL ); + thisbrush_end = CM_CollisionBrushForBox( matrix4x4_identity, boxendmins, boxendmaxs, 0, 0, NULL ); if( model && model->type == mod_brush ) { @@ -333,8 +333,8 @@ void CM_TraceStudio( const vec3_t start, const vec3_t end, const vec3_t mins, co VectorAdd(end, mins, boxendmins); VectorAdd(end, maxs, boxendmaxs); - thisbrush_start = CM_CollisionBrushForBox( identitymatrix, boxstartmins, boxstartmaxs, 0, 0, NULL ); - thisbrush_end = CM_CollisionBrushForBox( identitymatrix, boxendmins, boxendmaxs, 0, 0, NULL ); + thisbrush_start = CM_CollisionBrushForBox( matrix4x4_identity, boxstartmins, boxstartmaxs, 0, 0, NULL ); + thisbrush_end = CM_CollisionBrushForBox( matrix4x4_identity, boxendmins, boxendmaxs, 0, 0, NULL ); CM_CollisionTraceBrushTriangleMeshFloat( trace, thisbrush_start, thisbrush_end, mod->col[0]->numtris, mod->col[0]->indices, (const float *)mod->col[0]->verts, CONTENTS_SOLID, 0, NULL, segmentmins, segmentmaxs ); } } \ No newline at end of file diff --git a/public/mathlib.h b/public/mathlib.h index 312b5e2f..19163849 100644 --- a/public/mathlib.h +++ b/public/mathlib.h @@ -106,6 +106,9 @@ _inline 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? @@ -792,7 +795,6 @@ _inline int NearestPOW( int value, bool roundDown ) return n; } -static vec3_t axis_identity[3] = { { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, 1 } }; static quat_t quat_identity = { 0, 0, 0, 1 }; static vec3_t vec3_origin = { 0, 0, 0 }; static vec3_t vec3_angles = { 0, 0, 0 }; diff --git a/public/matrix_lib.h b/public/matrix_lib.h index 028fb2de..396257e4 100644 --- a/public/matrix_lib.h +++ b/public/matrix_lib.h @@ -6,7 +6,6 @@ #define MATRIX_LIB_H //#define OPENGL_STYLE // TODO: enable OpenGL style someday -//#define RHAND_STYLE // right hand style euler engles /* Quake engine tranformation matrix @@ -130,7 +129,7 @@ _inline void Matrix3x3_FromMatrix4x4( matrix3x3 out, const matrix4x4 in ) #endif } -_inline void Matrix3x3_ToAngles( const matrix3x3 matrix, vec3_t out ) +_inline void Matrix3x3_ToAngles( const matrix3x3 matrix, vec3_t out, bool rhand ) { double pitch, cpitch, yaw, roll; @@ -141,22 +140,16 @@ _inline void Matrix3x3_ToAngles( const matrix3x3 matrix, vec3_t out ) { cpitch = 1.0f / cpitch; pitch = RAD2DEG( pitch ); -#ifdef RHAND_STYLE - yaw = RAD2DEG( com.atan2( matrix[0][1] * cpitch, matrix[0][0] * cpitch )); -#else - yaw = RAD2DEG( com.atan2((-1)*-matrix[0][1] * cpitch, matrix[0][0] * cpitch )); -#endif + if( rhand ) yaw = RAD2DEG( com.atan2( matrix[0][1] * cpitch, matrix[0][0] * cpitch )); + else yaw = RAD2DEG( com.atan2((-1)*-matrix[0][1] * cpitch, matrix[0][0] * cpitch )); roll = RAD2DEG( com.atan2( -matrix[1][2] * cpitch, matrix[2][2] * cpitch )); } else { pitch = matrix[0][2] > 0 ? -90.0f : 90.0f; yaw = RAD2DEG( atan2( matrix[1][0], -matrix[1][1] )); -#ifdef RHAND_STYLE - roll = 180; -#else - roll = 0; -#endif + if( rhand ) roll = 180; + else roll = 0; } out[PITCH] = pitch; @@ -283,9 +276,9 @@ _inline void Matrix3x3_ConcatRotate( matrix3x3 out, double angle, double x, doub ======================================================================== */ -#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, identitymatrix ) +#define Matrix4x4_LoadIdentity( mat ) Matrix4x4_Copy( mat, matrix4x4_identity ) -static const matrix4x4 identitymatrix = +static const matrix4x4 matrix4x4_identity = { { 1, 0, 0, 0 }, // PITCH { 0, 1, 0, 0 }, // YAW @@ -304,7 +297,7 @@ static const matrix4x4 matrix4x4_halfidentity = _inline void Matrix4x4_Copy( matrix4x4 out, const matrix4x4 in ) { // FIXME: replace with Mem_Copy - memcpy( out, in, sizeof(matrix4x4)); + memcpy( out, in, sizeof( matrix4x4 )); } _inline void Matrix4x4_TransformPoint( const matrix4x4 in, vec3_t point ) diff --git a/public/quatlib.h b/public/quatlib.h index 449f7e6d..d1387715 100644 --- a/public/quatlib.h +++ b/public/quatlib.h @@ -5,29 +5,10 @@ #ifndef QUATLIB_H #define QUATLIB_H -// FIXME: move out to mathlib.h -_inline float Q_RSqrt( float number ) -{ - int i; - float x2, y; - - if( number == 0.0f ) - return 0.0f; - - x2 = number * 0.5f; - y = number; - i = * (int *) &y; // evil floating point bit level hacking - i = 0x5f3759df - (i >> 1); // what the fuck? - y = * (float *) &i; - y = y * (1.5f - (x2 * y * y)); // this can be done a second time - - return y; -} - // The expression a * rsqrt(b) is intended as a higher performance alternative to a / sqrt(b). // The two expressions are comparably accurate, but do not compute exactly the same value in every case. // For example, a * rsqrt(a*a + b*b) can be just slightly greater than 1, in rare cases. -#define SQRTFAST( x ) (( x ) * Q_RSqrt( x )) +#define SQRTFAST( x ) (( x ) * rsqrt( x )) #define VectorLengthFast(v) (SQRTFAST(DotProduct((v),(v)))) #define DistanceFast(v1,v2) (SQRTFAST(VectorDistance2(v1,v2))) @@ -85,7 +66,7 @@ _inline float Quat_Inverse( const quat_t q1, quat_t q2 ) return Quat_Normalize( q2 ); } -_inline void Matrix_Quat( vec3_t m[3], quat_t q ) +_inline void Quat_FromAxis( vec3_t m[3], quat_t q ) { float tr, s; @@ -153,7 +134,7 @@ _inline void Quat_Lerp( const quat_t q1, const quat_t q2, float t, quat_t out ) if( cosom < 1.0 - 0.0001 ) { sinsqr = 1.0 - cosom * cosom; - sinom = Q_RSqrt( sinsqr ); + sinom = rsqrt( sinsqr ); omega = atan2( sinsqr * sinom, cosom ); scale0 = sin( (1.0 - t) * omega ) * sinom; scale1 = sin( t * omega ) * sinom; @@ -216,144 +197,4 @@ _inline void Quat_ConcatTransforms( const quat_t q1,const vec3_t v1,const quat_t v[1] += v1[1]; v[2] += v1[2]; } - -_inline void Matrix_Identity( vec3_t m[3] ) -{ - int i, j; - - for( i = 0; i < 3; i++ ) - { - for( j = 0; j < 3; j++ ) - { - if( i == j ) - m[i][j] = 1.0f; - else m[i][j] = 0.0f; - } - } -} - -_inline void Matrix_Copy( vec3_t m1[3], vec3_t m2[3] ) -{ - Mem_Copy( m2, m1, sizeof( m2 )); -} - -_inline bool Matrix_Compare( vec3_t m1[3], vec3_t m2[3] ) -{ - int i, j; - - for( i = 0; i < 3; i++ ) - for( j = 0; j < 3; j++ ) - if( m1[i][j] != m2[i][j] ) - return false; - return true; -} - -_inline void Matrix_Multiply( vec3_t m1[3], vec3_t m2[3], vec3_t out[3] ) -{ - out[0][0] = m1[0][0]*m2[0][0] + m1[0][1]*m2[1][0] + m1[0][2]*m2[2][0]; - out[0][1] = m1[0][0]*m2[0][1] + m1[0][1]*m2[1][1] + m1[0][2]*m2[2][1]; - out[0][2] = m1[0][0]*m2[0][2] + m1[0][1]*m2[1][2] + m1[0][2]*m2[2][2]; - out[1][0] = m1[1][0]*m2[0][0] + m1[1][1]*m2[1][0] + m1[1][2]*m2[2][0]; - out[1][1] = m1[1][0]*m2[0][1] + m1[1][1]*m2[1][1] + m1[1][2]*m2[2][1]; - out[1][2] = m1[1][0]*m2[0][2] + m1[1][1]*m2[1][2] + m1[1][2]*m2[2][2]; - out[2][0] = m1[2][0]*m2[0][0] + m1[2][1]*m2[1][0] + m1[2][2]*m2[2][0]; - out[2][1] = m1[2][0]*m2[0][1] + m1[2][1]*m2[1][1] + m1[2][2]*m2[2][1]; - out[2][2] = m1[2][0]*m2[0][2] + m1[2][1]*m2[1][2] + m1[2][2]*m2[2][2]; -} - -_inline void Matrix_TransformVector( vec3_t m[3], vec3_t v, vec3_t out ) -{ - out[0] = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]*v[2]; - out[1] = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]*v[2]; - out[2] = m[2][0]*v[0] + m[2][1]*v[1] + m[2][2]*v[2]; -} - -_inline void Matrix_Transpose( vec3_t in[3], vec3_t out[3] ) -{ - out[0][0] = in[0][0]; - out[1][1] = in[1][1]; - out[2][2] = in[2][2]; - - out[0][1] = in[1][0]; - out[0][2] = in[2][0]; - out[1][0] = in[0][1]; - out[1][2] = in[2][1]; - out[2][0] = in[0][2]; - out[2][1] = in[1][2]; -} - -_inline void Matrix_EulerAngles( vec3_t m[3], vec3_t angles ) -{ - float c; - float pitch, yaw, roll; - - pitch = -asin( m[0][2] ); - c = cos( pitch ); - if( fabs( c ) > 5*10e-6 ) // Gimball lock? - { - // no - c = 1.0f / c; - pitch = RAD2DEG( pitch ); - yaw = RAD2DEG( atan2( m[0][1] * c, m[0][0] * c )); - roll = RAD2DEG( atan2( -m[1][2] * c, m[2][2] * c )); - } - else - { // yes - pitch = m[0][2] > 0 ? -90 : 90; - yaw = RAD2DEG( atan2 ( m[1][0], -m[1][1] ) ); - roll = 180; - } - - angles[PITCH] = pitch; - angles[YAW] = yaw; - angles[ROLL] = roll; -} - -_inline void Matrix_Rotate( vec3_t m[3], float angle, float x, float y, float z ) -{ - vec3_t t[3], b[3]; - float c = cos( DEG2RAD( angle )); - float s = sin( DEG2RAD( angle )); - float mc = 1 - c, t1, t2; - - t[0][0] = (x * x * mc) + c; - t[1][1] = (y * y * mc) + c; - t[2][2] = (z * z * mc) + c; - - t1 = y * x * mc; - t2 = z * s; - t[0][1] = t1 + t2; - t[1][0] = t1 - t2; - - t1 = x * z * mc; - t2 = y * s; - t[0][2] = t1 - t2; - t[2][0] = t1 + t2; - - t1 = y * z * mc; - t2 = x * s; - t[1][2] = t1 + t2; - t[2][1] = t1 - t2; - - Matrix_Copy( m, b ); - Matrix_Multiply( b, t, m ); -} - -_inline void Matrix_FromPoints( vec3_t v1, vec3_t v2, vec3_t v3, vec3_t m[3] ) -{ - float d; - - m[2][0] = (v1[1] - v2[1]) * (v3[2] - v2[2]) - (v1[2] - v2[2]) * (v3[1] - v2[1]); - m[2][1] = (v1[2] - v2[2]) * (v3[0] - v2[0]) - (v1[0] - v2[0]) * (v3[2] - v2[2]); - m[2][2] = (v1[0] - v2[0]) * (v3[1] - v2[1]) - (v1[1] - v2[1]) * (v3[0] - v2[0]); - VectorNormalizeFast( m[2] ); - - // this rotate and negate guarantees a vector not colinear with the original - VectorSet( m[1], m[2][2], -m[2][0], m[2][1] ); - d = -DotProduct( m[1], m[2] ); - VectorMA( m[1], d, m[2], m[1] ); - VectorNormalizeFast( m[1] ); - CrossProduct( m[1], m[2], m[0] ); -} - #endif//QUATLIB_H \ No newline at end of file diff --git a/render/r_alias.c b/render/r_alias.c index 8b6d7f59..5e84ee9c 100644 --- a/render/r_alias.c +++ b/render/r_alias.c @@ -182,7 +182,7 @@ void Mod_AliasLoadModel( ref_model_t *mod, ref_model_t *parent, const void *buff pouttag->origin[j] = LittleFloat( pintag->origin[j] ); } - Matrix_Quat( axis, pouttag->quat ); + Quat_FromAxis( axis, pouttag->quat ); Quat_Normalize( pouttag->quat ); com.strncpy( pouttag->name, pintag->name, MD3_MAX_PATH ); diff --git a/render/r_backend.c b/render/r_backend.c index c215083a..05d9c9e7 100644 --- a/render/r_backend.c +++ b/render/r_backend.c @@ -20,7 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" #define FTABLE_SIZE_POW 10 #define FTABLE_SIZE ( 1<axis, RI.vright, v_right ); - Matrix_TransformVector( RI.currententity->axis, RI.vup, v_up ); + Matrix3x3_Transform( RI.currententity->axis, RI.vright, v_right ); + Matrix3x3_Transform( RI.currententity->axis, RI.vup, v_up ); } else { @@ -781,7 +781,7 @@ void R_DeformVertices( void ) if( !len[long_axis] ) break; - len[long_axis] = Q_RSqrt( len[long_axis] ); + len[long_axis] = rsqrt( len[long_axis] ); VectorScale( m0[long_axis], len[long_axis], axis ); if( DotProduct( m0[long_axis], m0[short_axis] ) ) @@ -796,7 +796,7 @@ void R_DeformVertices( void ) { if( !len[short_axis] ) break; - len[short_axis] = Q_RSqrt( len[short_axis] ); + len[short_axis] = rsqrt( len[short_axis] ); VectorScale( m0[short_axis], len[short_axis], m0[0] ); VectorCopy( axis, m0[1] ); CrossProduct( m0[0], m0[1], m0[2] ); @@ -805,11 +805,11 @@ void R_DeformVertices( void ) for( j = 0; j < 3; j++ ) rot_centre[j] = ( quad[0][j] + quad[1][j] + quad[2][j] + quad[3][j] ) * 0.25; - if( RI.currententity && ( RI.currentmodel != r_worldmodel ) ) + if( RI.currententity && ( RI.currentmodel != r_worldmodel )) { VectorAdd( RI.currententity->origin, rot_centre, tv ); VectorSubtract( RI.viewOrigin, tv, tmp ); - Matrix_TransformVector( RI.currententity->axis, tmp, tv ); + Matrix3x3_Transform( RI.currententity->axis, tmp, tv ); } else { @@ -825,13 +825,13 @@ void R_DeformVertices( void ) VectorCopy( axis, m1[1] ); CrossProduct( m1[1], m1[2], m1[0] ); - Matrix_Transpose( m1, m2 ); - Matrix_Multiply( m2, m0, result ); + Matrix3x3_Transpose( m2, m1 ); + Matrix3x3_Concat( result, m2, m0 ); for( j = 0; j < 4; j++ ) { VectorSubtract( quad[j], rot_centre, tv ); - Matrix_TransformVector( result, tv, quad[j] ); + Matrix3x3_Transform( result, tv, quad[j] ); VectorAdd( rot_centre, quad[j], quad[j] ); } } @@ -847,12 +847,11 @@ void R_DeformVertices( void ) if( r_backacc.numElems % 6 ) break; - if( RI.currententity && ( RI.currentmodel != r_worldmodel ) ) + if( RI.currententity && ( RI.currentmodel != r_worldmodel )) Matrix4_Matrix( RI.modelviewMatrix, m1 ); - else - Matrix4_Matrix( RI.worldviewMatrix, m1 ); + else Matrix4_Matrix( RI.worldviewMatrix, m1 ); - Matrix_Transpose( m1, m2 ); + Matrix3x3_Transpose( m2, m1 ); for( k = 0; k < r_backacc.numElems; k += 6 ) { @@ -866,21 +865,19 @@ void R_DeformVertices( void ) if( !VectorCompare( quad[3], quad[0] ) && !VectorCompare( quad[3], quad[1] ) && - !VectorCompare( quad[3], quad[2] ) ) + !VectorCompare( quad[3], quad[2] )) { break; } } - Matrix_FromPoints( quad[0], quad[1], quad[2], m0 ); - Matrix_Multiply( m2, m0, result ); + Matrix3x3_FromPoints( quad[0], quad[1], quad[2], m0 ); + Matrix3x3_Concat( result, m2, m0 ); // hack a scale up to keep particles from disappearing scale = ( quad[0][0] - RI.viewOrigin[0] ) * RI.vpn[0] + ( quad[0][1] - RI.viewOrigin[1] ) * RI.vpn[1] + ( quad[0][2] - RI.viewOrigin[2] ) * RI.vpn[2]; - if( scale < 20 ) - scale = 1.5; - else - scale = 1.5 + scale * 0.006f; + if( scale < 20 ) scale = 1.5; + else scale = 1.5 + scale * 0.006f; for( j = 0; j < 3; j++ ) rot_centre[j] = ( quad[0][j] + quad[1][j] + quad[2][j] + quad[3][j] ) * 0.25; @@ -888,7 +885,7 @@ void R_DeformVertices( void ) for( j = 0; j < 4; j++ ) { VectorSubtract( quad[j], rot_centre, tv ); - Matrix_TransformVector( result, tv, quad[j] ); + Matrix3x3_Transform( result, tv, quad[j] ); VectorMA( rot_centre, scale, quad[j], quad[j] ); } } @@ -941,10 +938,10 @@ static bool R_VertexTCBase( const ref_stage_t *pass, int unit, mat4x4_t matrix ) if( glState.in2DMode ) return true; - if( !( RI.params & RP_SHADOWMAPVIEW ) ) + if( !( RI.params & RP_SHADOWMAPVIEW )) { VectorSubtract( RI.viewOrigin, RI.currententity->origin, projection ); - Matrix_TransformVector( RI.currententity->axis, projection, transform ); + Matrix3x3_Transform( RI.currententity->axis, projection, transform ); outCoords = tUnitCoordsArray[unit][0]; for( i = 0, n = normalsArray[0]; i < r_backacc.numVerts; i++, outCoords += 2, n += 4 ) @@ -953,14 +950,14 @@ static bool R_VertexTCBase( const ref_stage_t *pass, int unit, mat4x4_t matrix ) VectorNormalizeFast( projection ); depth = DotProduct( n, projection ); depth += depth; - outCoords[0] = 0.5 + ( n[1] * depth - projection[1] ) * 0.5; - outCoords[1] = 0.5 - ( n[2] * depth - projection[2] ) * 0.5; + outCoords[0] = 0.5 + ( n[1] * depth - projection[1] ) * 0.5f; + outCoords[1] = 0.5 - ( n[2] * depth - projection[2] ) * 0.5f; } } GL_DisableAllTexGens(); - R_UpdateVertexBuffer( tr.tcoordBuffer[unit], tUnitCoordsArray, r_backacc.numVerts * sizeof( vec2_t )); + R_UpdateVertexBuffer( tr.tcoordBuffer[unit], tUnitCoordsArray[unit], r_backacc.numVerts * sizeof( vec2_t )); pglTexCoordPointer( 2, GL_FLOAT, 0, tr.tcoordBuffer[unit]->pointer ); return true; } @@ -1043,7 +1040,7 @@ static bool R_VertexTCBase( const ref_stage_t *pass, int unit, mat4x4_t matrix ) Matrix4_Identity( m ); // rotate direction - Matrix_TransformVector( RI.currententity->axis, dir, &m[0] ); + Matrix3x3_Transform( RI.currententity->axis, dir, &m[0] ); VectorNormalizeLength( &m[0] ); VectorVectors( &m[0], &m[4], &m[8] ); @@ -1718,8 +1715,8 @@ void R_ModifyColor( const ref_stage_t *pass ) break; case ALPHAGEN_SPECULAR: VectorSubtract( RI.viewOrigin, RI.currententity->origin, t ); - if( !Matrix_Compare( RI.currententity->axis, axis_identity )) - Matrix_TransformVector( RI.currententity->axis, t, v ); + if( !Matrix3x3_Compare( RI.currententity->axis, matrix3x3_identity )) + Matrix3x3_Transform( RI.currententity->axis, t, v ); else VectorCopy( t, v ); for( i = 0; i < r_backacc.numColors; i++, bArray += 4 ) @@ -1732,8 +1729,8 @@ void R_ModifyColor( const ref_stage_t *pass ) } break; case ALPHAGEN_DOT: - if( !Matrix_Compare( RI.currententity->axis, axis_identity ) ) - Matrix_TransformVector( RI.currententity->axis, RI.vpn, v ); + if( !Matrix3x3_Compare( RI.currententity->axis, matrix3x3_identity )) + Matrix3x3_Transform( RI.currententity->axis, RI.vpn, v ); else VectorCopy( RI.vpn, v ); for( i = 0; i < r_backacc.numColors; i++, bArray += 4 ) @@ -1743,10 +1740,9 @@ void R_ModifyColor( const ref_stage_t *pass ) } break; case ALPHAGEN_ONE_MINUS_DOT: - if( !Matrix_Compare( RI.currententity->axis, axis_identity ) ) - Matrix_TransformVector( RI.currententity->axis, RI.vpn, v ); - else - VectorCopy( RI.vpn, v ); + if( !Matrix3x3_Compare( RI.currententity->axis, matrix3x3_identity )) + Matrix3x3_Transform( RI.currententity->axis, RI.vpn, v ); + else VectorCopy( RI.vpn, v ); for( i = 0; i < r_backacc.numColors; i++, bArray += 4 ) { @@ -2253,7 +2249,7 @@ static void R_RenderMeshGLSL_Material( void ) } // rotate direction - Matrix_TransformVector( RI.currententity->axis, temp, lightDir ); + Matrix3x3_Transform( RI.currententity->axis, temp, lightDir ); } } } diff --git a/render/r_backend.h b/render/r_backend.h index 3431ddb7..3d98368c 100644 --- a/render/r_backend.h +++ b/render/r_backend.h @@ -107,8 +107,9 @@ typedef struct texture_t *shadowmapTextures[MAX_SHADOWGROUPS]; texture_t *lightmapTextures[MAX_TEXTURES]; - // builtin shaders + // utility shaders ref_shader_t *defaultShader; // generic black texture + ref_shader_t *currentSkyShader; // ponter to sky shader for current map } ref_globals_t; extern ref_globals_t tr; diff --git a/render/r_draw.c b/render/r_draw.c index 03d1da5f..fe24bd3f 100644 --- a/render/r_draw.c +++ b/render/r_draw.c @@ -12,6 +12,19 @@ static rgba_t pic_colors[4]; static mesh_t pic_mesh = { 4, pic_xyz, pic_xyz, NULL, pic_st, { 0, 0, 0, 0 }, { pic_colors, pic_colors, pic_colors, pic_colors }, 6, NULL }; meshbuffer_t pic_mbuffer; +/* +=============== +R_DrawSetColor +=============== +*/ +void R_DrawSetColor( const void *data ) +{ + float *color = (float *)data; + + if( color ) Vector4Copy( color, glState.draw_color ); + else Vector4Set( glState.draw_color, 1.0f, 1.0f, 1.0f, 1.0f ); +} + /* =============== R_DrawStretchPic diff --git a/render/r_image.c b/render/r_image.c index df639214..aadff67a 100644 --- a/render/r_image.c +++ b/render/r_image.c @@ -6,6 +6,7 @@ #include "r_local.h" #include "byteorder.h" #include "mathlib.h" +#include "matrix_lib.h" #include "const.h" #define TEXTURES_HASH_SIZE 64 @@ -28,24 +29,30 @@ static byte *r_texpool; // texture_t permanent chain static byte data2D[256*256*4]; static rgbdata_t r_image; // generic pixelbuffer used for internal textures -const vec3_t r_cubeMapAngles[6] = +typedef struct envmap_s { -{ 0, 0, 90 }, // px -{ 0, 180, -90 }, // nx -{ 0, 90, 0 }, // py -{ 0, 270, 180 }, // ny -{ -90, 180, -90 }, // pz -{ 90, 180, 90 }, // nz + 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 vec3_t r_skyBoxAngles[6] = +const envmap_t r_envMapInfo[6] = { -{ 0, 90, 180}, // ft -{ 0, 270, 180}, // bk -{ -90, 0, 180}, // up -{ 90, 0, 180}, // dn -{ 0, 0, 180}, // rt -{ 0, 180, 180}, // lf +{{ 0, 0, 90}, 0 }, +{{ 0, 180, -90}, 0 }, +{{ 0, 90, 0}, 0 }, +{{ 0, 270, 180}, 0 }, +{{-90, 180, -90}, 0 }, +{{ 90, 180, 90}, 0 } }; static struct @@ -126,6 +133,14 @@ void GL_LoadTexMatrix( const mat4x4_t m ) glState.texIdentityMatrix[glState.activeTMU] = false; } +void GL_LoadMatrix( matrix4x4 source ) +{ + GLfloat dest[16]; + + Matrix4x4_ToArrayFloatGL( source, dest ); + pglLoadMatrixf( dest ); +} + void GL_LoadIdentityTexMatrix( void ) { if( glState.texIdentityMatrix[glState.activeTMU] ) @@ -3079,6 +3094,7 @@ bool VID_ScreenShot( const char *filename, bool levelshot ) r_shot = Mem_Alloc( r_temppool, sizeof( rgbdata_t )); r_shot->width = glState.width; r_shot->height = glState.height; + 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; @@ -3106,11 +3122,11 @@ VID_CubemapShot */ bool VID_CubemapShot( const char *base, uint size, bool skyshot ) { - rgbdata_t *r_shot; + rgbdata_t *r_shot, *r_side; byte *temp = NULL; byte *buffer = NULL; string basename; - int i = 1, result; + int i = 1, flags, result; if( RI.refdef.onlyClientDraw || !r_worldmodel ) return false; @@ -3128,20 +3144,36 @@ bool VID_CubemapShot( const char *base, uint size, bool skyshot ) // 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 )); for( i = 0; i < 6; i++ ) { - if( skyshot ) R_DrawCubemapView( r_lastRefdef.vieworg, r_skyBoxAngles[i], size ); - else R_DrawCubemapView( r_lastRefdef.vieworg, r_cubeMapAngles[i], size ); - + if( skyshot ) + { + R_DrawCubemapView( r_lastRefdef.vieworg, r_skyBoxInfo[i].angles, size ); + flags = r_skyBoxInfo[i].flags; + } + else + { + R_DrawCubemapView( r_lastRefdef.vieworg, r_envMapInfo[i].angles, size ); + flags = r_envMapInfo[i].flags; + } pglReadPixels( 0, glState.height - size, size, size, GL_RGB, GL_UNSIGNED_BYTE, temp ); - Mem_Copy( buffer + (size * size * 3 * i), temp, size * size * 3 ); + 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->depth = r_side->numMips = 1; + r_side->buffer = temp; + + if( flags ) Image_Process( &r_side, 0, 0, flags ); + Mem_Copy( buffer + (size * size * 3 * i), r_side->buffer, size * size * 3 ); } RI.params &= ~RP_ENVVIEW; - r_shot = Mem_Alloc( r_temppool, sizeof( rgbdata_t )); - r_shot->flags |= IMAGE_CUBEMAP; + r_shot->flags |= IMAGE_CUBEMAP|IMAGE_HAS_COLOR; r_shot->width = size; r_shot->height = size; r_shot->type = PF_RGB_24; @@ -3155,11 +3187,11 @@ bool VID_CubemapShot( const char *base, uint size, bool skyshot ) com.strncpy( basename, base, MAX_STRING ); FS_StripExtension( basename ); FS_DefaultExtension( basename, ".dds" ); - + // write image as dds packet result = FS_SaveImage( basename, r_shot ); FS_FreeImage( r_shot ); - Mem_Free( temp ); + FS_FreeImage( r_side ); return result; } diff --git a/render/r_light.c b/render/r_light.c index bdfb0a23..ba62c9a4 100644 --- a/render/r_light.c +++ b/render/r_light.c @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" /* ============================================================================= @@ -68,7 +68,7 @@ void R_LightBounds( const vec3_t origin, float intensity, vec3_t mins, vec3_t ma R_AddSurfDlighbits ============= */ -unsigned int R_AddSurfDlighbits( msurface_t *surf, unsigned int dlightbits ) +uint R_AddSurfDlighbits( msurface_t *surf, unsigned int dlightbits ) { unsigned int k, bit; dlight_t *lt; @@ -99,7 +99,7 @@ unsigned int R_AddSurfDlighbits( msurface_t *surf, unsigned int dlightbits ) R_AddDynamicLights ================= */ -void R_AddDynamicLights( unsigned int dlightbits, int state ) +void R_AddDynamicLights( uint dlightbits, int state ) { unsigned int i, j, numTempElems; bool cullAway; @@ -121,7 +121,7 @@ void R_AddDynamicLights( unsigned int dlightbits, int state ) { GL_SelectTexture( i ); GL_TexEnv( GL_MODULATE ); - GL_SetState( state | ( i ? 0 : GLSTATE_BLEND_MTEX ) ); + GL_SetState( state | ( i ? 0 : GLSTATE_BLEND_MTEX )); GL_SetTexCoordArrayMode( 0 ); GL_EnableTexGen( GL_S, GL_OBJECT_LINEAR ); GL_EnableTexGen( GL_T, GL_OBJECT_LINEAR ); @@ -146,33 +146,32 @@ void R_AddDynamicLights( unsigned int dlightbits, int state ) continue; // not lit by this light VectorSubtract( light->origin, RI.currententity->origin, dlorigin ); - if( !Matrix_Compare( RI.currententity->axis, axis_identity ) ) + if( !Matrix3x3_Compare( RI.currententity->axis, matrix3x3_identity )) { VectorCopy( dlorigin, tvec ); - Matrix_TransformVector( RI.currententity->axis, tvec, dlorigin ); + Matrix3x3_Transform( RI.currententity->axis, tvec, dlorigin ); } shader = light->shader; if( shader && ( shader->flags & SHADER_CULL_BACK ) ) cullAway = true; - else - cullAway = false; + else cullAway = false; numTempElems = 0; if( cullAway ) { for( j = 0; j < r_backacc.numElems; j += 3 ) { - v1 = ( float * )( vertsArray + elemsArray[j+0] ); - v2 = ( float * )( vertsArray + elemsArray[j+1] ); - v3 = ( float * )( vertsArray + elemsArray[j+2] ); + v1 = (float *)( vertsArray + elemsArray[j+0] ); + v2 = (float *)( vertsArray + elemsArray[j+1] ); + v3 = (float *)( vertsArray + elemsArray[j+2] ); normal[0] = ( v1[1] - v2[1] ) * ( v3[2] - v2[2] ) - ( v1[2] - v2[2] ) * ( v3[1] - v2[1] ); normal[1] = ( v1[2] - v2[2] ) * ( v3[0] - v2[0] ) - ( v1[0] - v2[0] ) * ( v3[2] - v2[2] ); normal[2] = ( v1[0] - v2[0] ) * ( v3[1] - v2[1] ) - ( v1[1] - v2[1] ) * ( v3[0] - v2[0] ); dist = ( dlorigin[0] - v1[0] ) * normal[0] + ( dlorigin[1] - v1[1] ) * normal[1] + ( dlorigin[2] - v1[2] ) * normal[2]; - if( dist <= 0 || dist * Q_RSqrt( DotProduct( normal, normal ) ) >= light->intensity ) + if( dist <= 0 || dist * rsqrt( DotProduct( normal, normal ) ) >= light->intensity ) continue; tempElemsArray[numTempElems++] = elemsArray[j+0]; @@ -217,15 +216,13 @@ void R_AddDynamicLights( unsigned int dlightbits, int state ) { if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) pglDrawRangeElementsEXT( GL_TRIANGLES, 0, r_backacc.numVerts, numTempElems, GL_UNSIGNED_INT, tempElemsArray ); - else - pglDrawElements( GL_TRIANGLES, numTempElems, GL_UNSIGNED_INT, tempElemsArray ); + else pglDrawElements( GL_TRIANGLES, numTempElems, GL_UNSIGNED_INT, tempElemsArray ); } else { if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) pglDrawRangeElementsEXT( GL_TRIANGLES, 0, r_backacc.numVerts, r_backacc.numElems, GL_UNSIGNED_INT, elemsArray ); - else - pglDrawElements( GL_TRIANGLES, r_backacc.numElems, GL_UNSIGNED_INT, elemsArray ); + else pglDrawElements( GL_TRIANGLES, r_backacc.numElems, GL_UNSIGNED_INT, elemsArray ); } } @@ -507,7 +504,7 @@ void R_LightForEntity( ref_entity_t *e, byte *bArray ) } // rotate direction - Matrix_TransformVector( e->axis, temp, direction ); + Matrix3x3_Transform( e->axis, temp, direction ); // see if we are affected by dynamic lights dlightbits = 0; @@ -566,7 +563,7 @@ void R_LightForEntity( ref_entity_t *e, byte *bArray ) dir = lightDirs[lnum]; // rotate - Matrix_TransformVector( e->axis, dir, dlorigin ); + Matrix3x3_Transform( e->axis, dir, dlorigin ); intensity8 = dl->intensity * 8 * e->scale; cArray = tempColorsArray[0]; @@ -578,7 +575,7 @@ void R_LightForEntity( ref_entity_t *e, byte *bArray ) if( add > 0 ) { dot = DotProduct( dir, dir ); - add *= ( intensity8 / dot ) *Q_RSqrt( dot ); + add *= ( intensity8 / dot ) * rsqrt( dot ); VectorMA( cArray, add, dl->color, cArray ); } } diff --git a/render/r_local.h b/render/r_local.h index 1ed4572a..6750833b 100644 --- a/render/r_local.h +++ b/render/r_local.h @@ -477,6 +477,7 @@ void R_ShutdownOcclusionQueries( void ); // extern meshbuffer_t pic_mbuffer; +void R_DrawSetColor( const void *data ); void R_DrawStretchPic( float x, float y, float w, float h, float s1, float t1, float s2, float t2, shader_t shadernum ); void R_DrawStretchRaw( int x, int y, int w, int h, int cols, int rows, const byte *data, bool redraw ); void R_DrawSetParms( shader_t handle, kRenderMode_t rendermode, int frame ); @@ -489,6 +490,7 @@ void R_DrawFill( float x, float y, float w, float h ); void GL_SelectTexture( GLenum tmu ); void GL_Bind( GLenum tmu, texture_t *tex ); void GL_TexEnv( GLenum mode ); +void GL_LoadMatrix( matrix4x4 source ); void GL_LoadTexMatrix( const mat4x4_t m ); void GL_LoadIdentityTexMatrix( void ); void GL_EnableTexGen( int coord, int mode ); @@ -709,13 +711,14 @@ struct skinfile_s *R_RegisterSkinFile( const char *name ); ref_shader_t *R_FindShaderForSkinFile( const struct skinfile_s *skinfile, const char *meshname ); // -// r_warp.c +// r_sky.c // -skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farboxShaders, ref_shader_t **nearboxShaders ); -void R_FreeSkydome( skydome_t *skydome ); -void R_ClearSkyBox( void ); -void R_DrawSky( ref_shader_t *shader ); -bool R_AddSkySurface( msurface_t *fa ); +skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farboxShaders, ref_shader_t **nearboxShaders ); +void R_FreeSkydome( skydome_t *skydome ); +void R_ClearSkyBox( void ); +void R_DrawSky( ref_shader_t *shader ); +bool R_AddSkySurface( msurface_t *fa ); +ref_shader_t *R_SetupSky( const char *name ); //==================================================================== diff --git a/render/r_main.c b/render/r_main.c index b22d1932..f764a197 100644 --- a/render/r_main.c +++ b/render/r_main.c @@ -24,7 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include // sscanf #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" render_imp_t ri; stdlib_api_t com; @@ -277,26 +277,24 @@ R_TransformEntityBBox */ void R_TransformEntityBBox( ref_entity_t *e, vec3_t mins, vec3_t maxs, vec3_t bbox[8], bool local ) { - int i; - vec3_t axis[3], tmp; + int i; + vec3_t axis[3], tmp; - if( e == r_worldent ) - local = false; - if( local ) - Matrix_Transpose( e->axis, axis ); // switch row-column order + if( e == r_worldent ) local = false; + if( local ) Matrix3x3_Transpose( axis, e->axis ); // switch row-column order // rotate local bounding box and compute the full bounding box for( i = 0; i < 8; i++ ) { vec_t *corner = bbox[i]; - corner[0] = ( ( i & 1 ) ? mins[0] : maxs[0] ); - corner[1] = ( ( i & 2 ) ? mins[1] : maxs[1] ); - corner[2] = ( ( i & 4 ) ? mins[2] : maxs[2] ); + corner[0] = (( i & 1 ) ? mins[0] : maxs[0] ); + corner[1] = (( i & 2 ) ? mins[1] : maxs[1] ); + corner[2] = (( i & 4 ) ? mins[2] : maxs[2] ); if( local ) { - Matrix_TransformVector( axis, corner, tmp ); + Matrix3x3_Transform( axis, corner, tmp ); VectorAdd( tmp, e->origin, corner ); } } @@ -398,10 +396,9 @@ bool R_LerpTag( orientation_t *orient, const ref_model_t *mod, int oldframe, int return false; VectorClear( orient->origin ); - Matrix_Identity( orient->axis ); + Matrix3x3_LoadIdentity( orient->axis ); - if( !name ) - return false; + if( !name ) return false; if( mod->type == mod_alias ) return R_AliasModelLerpTag( orient, mod->extradata, oldframe, frame, lerpfrac, name ); @@ -625,24 +622,21 @@ R_PushFlareSurf */ static void R_PushFlareSurf( const meshbuffer_t *mb ) { - int i; - vec4_t color; - vec3_t origin, point, v; - float radius = r_flaresize->value, colorscale, depth; - float up = radius, down = -radius, left = -radius, right = radius; - mbrushmodel_t *bmodel = ( mbrushmodel_t * )RI.currentmodel->extradata; - msurface_t *surf = &bmodel->surfaces[mb->infokey - 1]; - ref_shader_t *shader; + int i; + vec4_t color; + vec3_t origin, point, v; + float radius = r_flaresize->value, colorscale, depth; + float up = radius, down = -radius, left = -radius, right = radius; + mbrushmodel_t *bmodel = ( mbrushmodel_t * )RI.currentmodel->extradata; + msurface_t *surf = &bmodel->surfaces[mb->infokey - 1]; + ref_shader_t *shader; if( RI.currentmodel != r_worldmodel ) { - Matrix_TransformVector( RI.currententity->axis, surf->origin, origin ); + Matrix3x3_Transform( RI.currententity->axis, surf->origin, origin ); VectorAdd( origin, RI.currententity->origin, origin ); } - else - { - VectorCopy( surf->origin, origin ); - } + else VectorCopy( surf->origin, origin ); R_TransformToScreen_Vec3( origin, v ); if( v[0] < RI.refdef.viewport[0] || v[0] > RI.refdef.viewport[0] + RI.refdef.viewport[2] ) @@ -650,9 +644,8 @@ static void R_PushFlareSurf( const meshbuffer_t *mb ) if( v[1] < RI.refdef.viewport[1] || v[1] > RI.refdef.viewport[1] + RI.refdef.viewport[3] ) return; - pglReadPixels( (int)( v[0] /* + 0.5f*/ ), (int)( v[1] /* + 0.5f*/ ), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); - if( depth + 1e-4 < v[2] ) - return; // occluded + pglReadPixels((int)( v[0] ), (int)( v[1] ), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth ); + if( depth + 1e-4 < v[2] ) return; // occluded VectorCopy( origin, origin ); @@ -2169,6 +2162,8 @@ shader_t Mod_RegisterShader( const char *name, int shaderType ) src = R_LoadShader( name, shaderType, false, 0, SHADER_INVALID ); break; case SHADER_SKY: + src = R_SetupSky( name ); + break; default: MsgDev( D_WARN, "Mod_RegisterShader: invalid shader type (%i)\n", shaderType ); return 0; @@ -2285,7 +2280,7 @@ bool R_AddGenericEntity( edict_t *pRefEntity, ref_entity_t *refent, int ed_type, // interpolate origin for( i = 0; i < 3; i++ ) refent->origin[i] = LerpPoint( pRefEntity->v.oldorigin[i], pRefEntity->v.origin[i], lerpfrac ); - AngleVectorsFLU( refent->angles, refent->axis[0], refent->axis[1], refent->axis[2] ); + Matrix3x3_FromAngles( refent->angles, refent->axis ); VectorClear( refent->origin2 ); if( refent->ent_type == ED_VIEWMODEL ) @@ -2351,7 +2346,7 @@ bool R_AddPortalEntity( edict_t *pRefEntity, ref_entity_t *refent, int ed_type, } // calculate angles - AngleVectorsFLU( refent->angles, refent->axis[0], refent->axis[1], refent->axis[2] ); + Matrix3x3_FromAngles( refent->angles, refent->axis ); return true; } @@ -2438,14 +2433,6 @@ bool R_AddDynamicLight( vec3_t org, vec3_t color, float intensity, shader_t hand return true; } -void GL_SetColor( const void *data ) -{ - float *color = (float *)data; - - if( color ) Vector4Copy( color, glState.draw_color ); - else Vector4Set( glState.draw_color, 1.0f, 1.0f, 1.0f, 1.0f ); -} - void R_LightForPoint( const vec3_t point, vec3_t ambientLight ) { vec4_t ambient; @@ -2492,7 +2479,7 @@ render_exp_t DLLEXPORT *CreateAPI(stdlib_api_t *input, render_imp_t *engfuncs ) re.RenderFrame = R_RenderScene; re.EndFrame = R_EndFrame; - re.SetColor = GL_SetColor; + re.SetColor = R_DrawSetColor; re.GetParms = R_DrawGetParms; re.SetParms = R_DrawSetParms; re.ScrShot = VID_ScreenShot; diff --git a/render/r_math.c b/render/r_math.c index 77ecc14b..51a37716 100644 --- a/render/r_math.c +++ b/render/r_math.c @@ -104,25 +104,6 @@ void Matrix4_MultiplyFast( const mat4x4_t m1, const mat4x4_t m2, mat4x4_t out ) out[15] = 1.0f; } -void Matrix_FromQuaternion( const quat_t q, mat4x4_t out ) -{ - vec_t wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; - - x2 = q[0] + q[0]; y2 = q[1] + q[1]; z2 = q[2] + q[2]; - - xx = q[0] * x2; yy = q[1] * y2; zz = q[2] * z2; - out[0] = 1.0f - yy - zz; out[5] = 1.0f - xx - zz; out[10] = 1.0f - xx - yy; - - yz = q[1] * z2; wx = q[3] * x2; - out[9] = yz - wx; out[6] = yz + wx; - - xy = q[0] * y2; wz = q[3] * z2; - out[4] = xy - wz; out[1] = xy + wz; - - xz = q[0] * z2; wy = q[3] * y2; - out[8] = xz + wy; out[2] = xz - wy; -} - void Matrix4_Rotate( mat4x4_t m, vec_t angle, vec_t x, vec_t y, vec_t z ) { mat4x4_t t, b; diff --git a/render/r_math.h b/render/r_math.h index fd9a20f9..c8861994 100644 --- a/render/r_math.h +++ b/render/r_math.h @@ -35,8 +35,6 @@ void Matrix4_Scale( mat4x4_t m, vec_t x, vec_t y, vec_t z ); void Matrix4_Transpose( const mat4x4_t m, mat4x4_t out ); void Matrix4_Matrix( const mat4x4_t in, vec3_t out[3] ); void Matrix4_Multiply_Vector( const mat4x4_t m, const vec4_t v, vec4_t out ); -void Matrix_FromQuaternion( const quat_t q, mat4x4_t out ); - void Matrix4_Copy2D( const mat4x4_t m1, mat4x4_t m2 ); void Matrix4_Multiply2D( const mat4x4_t m1, const mat4x4_t m2, mat4x4_t out ); void Matrix4_Scale2D( mat4x4_t m, vec_t x, vec_t y ); diff --git a/render/r_mesh.c b/render/r_mesh.c index 1d46d60d..1a03ff27 100644 --- a/render/r_mesh.c +++ b/render/r_mesh.c @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" #define QSORT_MAX_STACKDEPTH 2048 @@ -418,8 +418,9 @@ static void R_BatchMeshBuffer( const meshbuffer_t *mb, const meshbuffer_t *nextm MB_NUM2SHADER( mb->shaderkey, shader ); if( shader->flags & SHADER_SKYPARMS ) - { // draw sky - if( !( RI.params & RP_NOSKY ) ) + { + // draw sky + if(!( RI.params & RP_NOSKY )) R_DrawSky( shader ); return; } @@ -792,15 +793,15 @@ static vec3_t r_portal_mins, r_portal_maxs, r_portal_centre; static bool R_AddPortalSurface( const meshbuffer_t *mb ) { - int i; - float dist; - ref_entity_t *ent; - ref_shader_t *shader; - msurface_t *surf; - cplane_t plane, oplane; - mesh_t *mesh; - vec3_t mins, maxs, centre; - vec3_t v[3], entity_rotation[3]; + int i; + float dist; + ref_entity_t *ent; + ref_shader_t *shader; + msurface_t *surf; + cplane_t plane, oplane; + mesh_t *mesh; + vec3_t mins, maxs, centre; + vec3_t v[3], entity_rotation[3]; if( !mb ) { @@ -812,8 +813,7 @@ static bool R_AddPortalSurface( const meshbuffer_t *mb ) } MB_NUM2ENTITY( mb->sortkey, ent ); - if( !ent->model ) - return false; + if( !ent->model ) return false; surf = mb->infokey > 0 ? &r_worldbrushmodel->surfaces[mb->infokey-1] : NULL; if( !surf || !( mesh = surf->mesh ) || !mesh->xyzArray ) @@ -828,14 +828,14 @@ static bool R_AddPortalSurface( const meshbuffer_t *mb ) oplane.dist += DotProduct( ent->origin, oplane.normal ); CategorizePlane( &oplane ); - if( !Matrix_Compare( ent->axis, axis_identity )) + if( !Matrix3x3_Compare( ent->axis, matrix3x3_identity )) { - Matrix_Transpose( ent->axis, entity_rotation ); - Matrix_TransformVector( entity_rotation, mesh->xyzArray[mesh->elems[0]], v[0] ); + Matrix3x3_Transpose( entity_rotation, ent->axis ); + Matrix3x3_Transform( entity_rotation, mesh->xyzArray[mesh->elems[0]], v[0] ); VectorMA( ent->origin, ent->scale, v[0], v[0] ); - Matrix_TransformVector( entity_rotation, mesh->xyzArray[mesh->elems[1]], v[1] ); + Matrix3x3_Transform( entity_rotation, mesh->xyzArray[mesh->elems[1]], v[1] ); VectorMA( ent->origin, ent->scale, v[1], v[1] ); - Matrix_TransformVector( entity_rotation, mesh->xyzArray[mesh->elems[2]], v[2] ); + Matrix3x3_Transform( entity_rotation, mesh->xyzArray[mesh->elems[2]], v[2] ); VectorMA( ent->origin, ent->scale, v[2], v[2] ); PlaneFromPoints( v, &plane ); CategorizePlane( &plane ); @@ -882,7 +882,7 @@ static bool R_AddPortalSurface( const meshbuffer_t *mb ) } r_portal_shader = shader; - if( !Matrix_Compare( ent->axis, axis_identity ) ) + if( !Matrix3x3_Compare( ent->axis, matrix3x3_identity )) { r_portal_ent = ent; r_portal_plane = plane; @@ -894,7 +894,7 @@ static bool R_AddPortalSurface( const meshbuffer_t *mb ) if( !VectorCompare( r_portal_plane.normal, vec3_origin ) && !( VectorCompare( plane.normal, r_portal_plane.normal ) && plane.dist == r_portal_plane.dist ) ) { - if( VectorDistance2( RI.viewOrigin, centre ) > VectorDistance2( RI.viewOrigin, r_portal_centre ) ) + if( VectorDistance2( RI.viewOrigin, centre ) > VectorDistance2( RI.viewOrigin, r_portal_centre )) return true; VectorClear( r_portal_plane.normal ); ClearBounds( r_portal_mins, r_portal_maxs ); @@ -1046,7 +1046,7 @@ setup_and_render: VectorNormalize( M[i] ); } - Matrix_EulerAngles( M, angles ); + Matrix3x3_ToAngles( M, angles, true ); angles[ROLL] = -angles[ROLL]; RI.params = RP_MIRRORVIEW|RP_FLIPFRONTFACE; @@ -1055,8 +1055,8 @@ setup_and_render: } else { - vec3_t tvec; - vec3_t A[3], B[3], C[3], rot[3]; + vec3_t tvec; + vec3_t A[3], B[3], C[3], rot[3]; // build world-to-portal rotation matrix VectorNegate( portal_plane->normal, A[0] ); @@ -1065,21 +1065,19 @@ setup_and_render: // build portal_dest-to-world rotation matrix VectorCopy( ent->movedir, portal_plane->normal ); NormalVectorToAxis( portal_plane->normal, B ); - Matrix_Transpose( B, C ); + Matrix3x3_Transpose( C, B ); // multiply to get world-to-world rotation matrix - Matrix_Multiply( C, A, rot ); + Matrix3x3_Concat( rot, C, A ); // translate view origin VectorSubtract( RI.viewOrigin, ent->origin, tvec ); - Matrix_TransformVector( rot, tvec, origin ); + Matrix3x3_Transform( rot, tvec, origin ); VectorAdd( origin, ent->origin2, origin ); - for( i = 0; i < 3; i++ ) - Matrix_TransformVector( A, RI.viewAxis[i], rot[i] ); - Matrix_Multiply( ent->axis, rot, B ); - for( i = 0; i < 3; i++ ) - Matrix_TransformVector( C, B[i], A[i] ); + for( i = 0; i < 3; i++ ) Matrix3x3_Transform( A, RI.viewAxis[i], rot[i] ); + Matrix3x3_Concat( B, ent->axis, rot ); + for( i = 0; i < 3; i++ ) Matrix3x3_Transform( C, B[i], A[i] ); // set up portal_plane // VectorCopy( A[0], portal_plane->normal ); @@ -1087,7 +1085,7 @@ setup_and_render: CategorizePlane( portal_plane ); // calculate Euler angles for our rotation matrix - Matrix_EulerAngles( A, angles ); + Matrix3x3_ToAngles( A, angles, true ); // for portals, vis data is taken from portal origin, not // view origin, because the view point moves around and diff --git a/render/r_mesh.h b/render/r_mesh.h index 667e0ebc..f4d77731 100644 --- a/render/r_mesh.h +++ b/render/r_mesh.h @@ -118,9 +118,9 @@ enum typedef struct { - mesh_t *meshes; - vec2_t *sphereStCoords[5]; - vec2_t *linearStCoords[6]; + mesh_t *meshes; + vec2_t *sphereStCoords[6]; + vec2_t *linearStCoords[6]; struct ref_shader_s *farboxShaders[6]; struct ref_shader_s *nearboxShaders[6]; diff --git a/render/r_model.c b/render/r_model.c index 71a884bb..4585fc85 100644 --- a/render/r_model.c +++ b/render/r_model.c @@ -24,7 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "stdio.h" // sscanf #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" #include "byteorder.h" #define Q_rint(x) ((x) < 0 ? ((int)((x)-0.5f)) : ((int)((x)+0.5f))) @@ -808,7 +808,7 @@ static void Mod_LoadShaderrefs( const lump_t *l ) if( l->filelen % sizeof( *in ) ) Host_Error( "Mod_LoadShaderrefs: funny lump size in %s\n", loadmodel->name ); count = l->filelen / sizeof( *in ); - out = Mod_Malloc( loadmodel, count*sizeof( *out ) ); + out = Mod_Malloc( loadmodel, count*sizeof( *out )); loadmodel->shaders = Mod_Malloc( loadmodel, count * sizeof( ref_shader_t* )); loadmodel->numshaders = count; @@ -824,7 +824,7 @@ static void Mod_LoadShaderrefs( const lump_t *l ) com.strncpy( out->name, in->name, sizeof( out->name ) ); out->flags = LittleLong( in->surfaceFlags ); contents = LittleLong( in->contentFlags ); - if( contents & ( MASK_WATER|CONTENTS_FOG ) ) + if( contents & ( MASK_WATER|CONTENTS_FOG )) out->flags |= SURF_NOMARKS; out->shader = NULL; } @@ -1124,13 +1124,17 @@ static _inline void Mod_LoadFaceCommon( const dsurfacer_t *in, msurface_t *out ) if( ( shaderType == SHADER_VERTEX && ( shaderref->shader->flags & SHADER_HASLIGHTMAP ) && ( shaderref->shader->stages[0].flags & SHADERSTAGE_LIGHTMAP ))) out->shader = R_LoadShader( shaderref->name, shaderType, false, 0, shaderref->shader->type ); - else - out->shader = shaderref->shader; + else out->shader = shaderref->shader; } out->flags = shaderref->flags; - R_DeformvBBoxForShader( out->shader, ebbox ); + if( tr.currentSkyShader == NULL && (out->flags & SURF_SKY || out->shader->flags & SHADER_SKYPARMS )) + { + // because sky shader may missing skyParms, but always has surfaceparm 'sky' + tr.currentSkyShader = out->shader; + } + R_DeformvBBoxForShader( out->shader, ebbox ); fognum = LittleLong( in->fognum ); if( fognum != -1 && ( fognum < loadbmodel->numfogs ) ) { @@ -2062,6 +2066,7 @@ void R_BeginRegistration( const char *mapname, const dvis_t *visData ) // explicitly free the old map if different if( com.strcmp( r_models[0].name, fullname )) { + tr.currentSkyShader = NULL; // invalidate sky shader Mod_FreeModel( &r_models[0] ); R_NewMap (); } @@ -2106,7 +2111,7 @@ void R_BeginRegistration( const char *mapname, const dvis_t *visData ) r_worldent->rtype = RT_MODEL; r_worldent->ent_type = ED_NORMAL; r_worldent->renderamt = 255; // i'm hope we don't want to see semisolid world :) - Matrix_Identity( r_worldent->axis ); + Matrix3x3_LoadIdentity( r_worldent->axis ); Mod_UpdateShaders( r_worldmodel ); r_framecount = 1; @@ -2118,6 +2123,12 @@ void R_EndRegistration( const char *skyname ) int i; ref_model_t *mod; + if( skyname && com.strncmp( skyname, "", 8 )) + { + // half-life or quake2 skybox-style + R_SetupSky( skyname ); + } + for( i = 0, mod = r_models; i < r_nummodels; i++, mod++ ) { if( !mod->name ) continue; diff --git a/render/r_poly.c b/render/r_poly.c index d37340dc..b36a46a5 100644 --- a/render/r_poly.c +++ b/render/r_poly.c @@ -22,7 +22,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" static mesh_t poly_mesh; @@ -766,7 +766,7 @@ msurface_t *R_TransformedTraceLine( trace_t *tr, const vec3_t start, const vec3_ { mbrushmodel_t *bmodel = ( mbrushmodel_t * )model->extradata; vec3_t temp, start_l, end_l, axis[3]; - bool rotated = !Matrix_Compare( test->axis, axis_identity ); + bool rotated = !Matrix3x3_Compare( test->axis, matrix3x3_identity ); // transform VectorSubtract( start, test->origin, start_l ); @@ -774,9 +774,9 @@ msurface_t *R_TransformedTraceLine( trace_t *tr, const vec3_t start, const vec3_ if( rotated ) { VectorCopy( start_l, temp ); - Matrix_TransformVector( test->axis, temp, start_l ); + Matrix3x3_Transform( test->axis, temp, start_l ); VectorCopy( end_l, temp ); - Matrix_TransformVector( test->axis, temp, end_l ); + Matrix3x3_Transform( test->axis, temp, end_l ); } VectorCopy( start_l, trace_start ); @@ -792,9 +792,9 @@ msurface_t *R_TransformedTraceLine( trace_t *tr, const vec3_t start, const vec3_ // transform back if( rotated && trace_fraction != 1 ) { - Matrix_Transpose( test->axis, axis ); + Matrix3x3_Transpose( axis, test->axis ); VectorCopy( tr->plane.normal, temp ); - Matrix_TransformVector( axis, temp, trace_plane.normal ); + Matrix3x3_Transform( axis, temp, trace_plane.normal ); } } } diff --git a/render/r_shader.c b/render/r_shader.c index 5e0906db..67d8c270 100644 --- a/render/r_shader.c +++ b/render/r_shader.c @@ -611,6 +611,48 @@ static bool Shader_SkipConditionBlock( script_t *script ) } //=========================================================================== +static bool Shader_CheckSkybox( const char *name ) +{ + const char *skybox_ext[4] = { "tga", "jpg", "png", "dds" }; + int i, j, num_checked_sides; + const char *sidename; + string loadname; + + com.strncpy( loadname, name, sizeof( loadname )); + FS_StripExtension( loadname ); + if( loadname[com.strlen( loadname ) - 1] == '_' ) + loadname[com.strlen( loadname ) - 1] = '\0'; + + if( FS_FileExists( va( "%s.dds", loadname ))) + return true; + + if( FS_FileExists( va( "%s_.dds", loadname ))) + return true; + + // complex cubemap pack not found, search for skybox images + for( i = 0; i < 4; i++ ) + { + num_checked_sides = 0; + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s_%s.%s", loadname, r_skyBoxSuffix[j], skybox_ext[i] ); + if( FS_FileExists( sidename )) num_checked_sides++; + + } + if( num_checked_sides == 6 ) + return true; // image exists + for( j = 0; j < 6; j++ ) + { + // build side name + sidename = va( "%s%s.%s", loadname, r_skyBoxSuffix[j], skybox_ext[i] ); + if( FS_FileExists( sidename )) num_checked_sides++; + } + if( num_checked_sides == 6 ) + return true; // images exists + } + return false; +} static bool Shader_ParseSkySides( script_t *script, ref_shader_t *shader, ref_shader_t **shaders, bool farbox ) { @@ -640,6 +682,9 @@ static bool Shader_ParseSkySides( script_t *script, ref_shader_t *shader, ref_sh if( com.stricmp( tok.string, "-" ) && com.stricmp( tok.string, "full" )) { shaderType = ( farbox ? SHADER_FARBOX : SHADER_NEARBOX ); + if( tok.string[com.strlen( tok.string ) - 1] == '_' ) + tok.string[com.strlen( tok.string ) - 1] = '\0'; + for( i = 0; i < 6; i++ ) { com.snprintf( name, sizeof( name ), "%s_%s", tok.string, r_skyBoxSuffix[i] ); @@ -647,10 +692,24 @@ static bool Shader_ParseSkySides( script_t *script, ref_shader_t *shader, ref_sh if( !image ) break; shaders[i] = R_LoadShader( image->name, shaderType, true, image->flags, SHADER_INVALID ); } - if( i == 6 ) return true; - Mem_Set( shaders, 0, sizeof( ref_shader_t * ) * 6 ); - return false; + + for( i = 0; i < 6; i++ ) + { + com.snprintf( name, sizeof( name ), "%s%s", tok.string, r_skyBoxSuffix[i] ); + image = R_FindTexture( name, NULL, 0, TF_CLAMP|TF_NOMIPMAP|TF_SKYSIDE ); + if( !image ) break; + shaders[i] = R_LoadShader( image->name, shaderType, true, image->flags, SHADER_INVALID ); + } + if( i == 6 ) return true; + + // create default skybox + for( i = 0; i < 6; i++ ) + { + image = tr.skyTexture; + shaders[i] = R_LoadShader( image->name, shaderType, true, image->flags, SHADER_INVALID ); + } + return true; } return true; } @@ -995,6 +1054,31 @@ static bool Shader_FogParms( ref_shader_t *shader, ref_stage_t *pass, script_t * return true; } +static bool Shader_SkyRotate( ref_shader_t *shader, ref_stage_t *pass, script_t *script ) +{ + VectorSet( shader->skyAxis, 0.0f, 0.0f, 1.0f ); + shader->skySpeed = 0.0f; + + // clear dist is optionally parm + if( !Com_ReadFloat( script, false, &shader->skySpeed )) + { + MsgDev( D_ERROR, "missing sky speed for 'skyRotate' in shader '%s'\n", shader->name ); + return false; + } + + if( !Shader_ParseVector( script, shader->skyAxis, 3 )) // skyAxis is optionally + { + VectorSet( shader->skyAxis, 0.0f, 0.0f, 1.0f ); + return true; + } + + if( VectorIsNull( shader->skyAxis )) + VectorSet( shader->skyAxis, 0.0f, 0.0f, 1.0f ); + VectorNormalize( shader->skyAxis ); + + return true; +} + static bool Shader_Sort( ref_shader_t *shader, ref_stage_t *pass, script_t *script ) { token_t tok; @@ -1111,7 +1195,8 @@ static const ref_parsekey_t shaderkeys[] = { "endif", Shader_Endif }, { "portal", Shader_Portal }, { "fogvars", Shader_FogParms }, // RTCW fog params -{ "skyparms", Shader_SkyParms }, +{ "skyParms", Shader_SkyParms }, +{ "skyRotate", Shader_SkyRotate }, { "fogparms", Shader_FogParms }, { "tessSize", Shader_TessSize }, { "nopicmip", Shader_shaderNoPicMip }, @@ -2340,10 +2425,14 @@ void R_ShaderList_f( void ) switch( shader->type ) { case SHADER_SKY: - case SHADER_FARBOX: - case SHADER_NEARBOX: Msg( "sky " ); break; + case SHADER_FARBOX: + Msg( "far " ); + break; + case SHADER_NEARBOX: + Msg( "near " ); + break; case SHADER_TEXTURE: Msg( "bsp " ); break; @@ -2468,6 +2557,7 @@ void R_InitShaders( void ) } // parse this file + MsgDev( D_LOAD, "loading shaderfile '%s'\n", t->filenames[i] ); Shader_ParseFile( script, t->filenames[i] ); Com_CloseScript( script ); } @@ -2561,6 +2651,13 @@ void Shader_FreeShader( ref_shader_t *shader ) handle = shader - r_shaders; if(( shader->flags & SHADER_SKYPARMS ) && shader->skyParms ) { + for( i = 0; i < 6; i++ ) + { + if( shader->skyParms->farboxShaders[i] ) + Shader_FreeShader( shader->skyParms->farboxShaders[i] ); + if( shader->skyParms->nearboxShaders[i] ) + Shader_FreeShader( shader->skyParms->nearboxShaders[i] ); + } R_FreeSkydome( shader->skyParms ); shader->skyParms = NULL; } @@ -3151,6 +3248,8 @@ static ref_shader_t *Shader_CreateDefault( ref_shader_t *shader, int type, int a { ref_stage_t *pass; texture_t *materialImages[MAX_STAGE_TEXTURES]; + script_t *script; + char *skyParms; uint i, hashKey; // make a default shader @@ -3379,6 +3478,17 @@ static ref_shader_t *Shader_CreateDefault( ref_shader_t *shader, int type, int a pass->tcgen = TCGEN_BASE; pass->num_textures++; break; + case SHADER_SKY: + shader->type = SHADER_SKY; + shader->name = Shader_Malloc( length + 1 ); + strcpy( shader->name, shortname ); + // create simple sky parms, to do Shader_SkyParms parsing it properly + skyParms = va( "%s - -", shortname ); + script = Com_OpenScript( "skybox", skyParms, com.strlen( skyParms )); + Shader_SkyParms( shader, NULL, script ); + Com_Assert( shader->skyParms == NULL ); + Com_CloseScript( script ); + break; case SHADER_FARBOX: shader->type = SHADER_FARBOX; shader->features = MF_STCOORDS; @@ -3408,7 +3518,7 @@ static ref_shader_t *Shader_CreateDefault( ref_shader_t *shader, int type, int a shader->stages = ( ref_stage_t * )( ( byte * )shader->name + length + 1 ); pass = &shader->stages[0]; pass->flags = SHADERSTAGE_NOCOLORARRAY|SHADERSTAGE_BLEND_DECAL; - pass->glState = GLSTATE_ALPHAFUNC|GLSTATE_SRCBLEND_SRC_ALPHA|GLSTATE_DSTBLEND_ONE_MINUS_SRC_ALPHA; + pass->glState = GLSTATE_ALPHAFUNC|GLSTATE_SRCBLEND_DST_COLOR|GLSTATE_DSTBLEND_SRC_COLOR; pass->textures[0] = R_FindTexture( shortname, NULL, 0, addFlags|TF_CLAMP|TF_NOMIPMAP ); pass->rgbGen.type = RGBGEN_IDENTITY_LIGHTING; pass->alphaGen.type = ALPHAGEN_IDENTITY; @@ -3705,4 +3815,133 @@ void R_ShaderSetRenderMode( kRenderMode_t mode ) void R_ShaderAddSpriteIntervals( float interval ) { r_spriteFrequency += interval; +} + +/* +================= +R_SetupSky +================= +*/ +ref_shader_t *R_SetupSky( const char *name ) +{ + string loadname; + bool shader_valid = false; + bool force_default = false; + int index; + + if( name && name[0] ) + { + ref_script_t *cache; + ref_shader_t *shader; + uint hashKey; + + com.strncpy( loadname, name, sizeof( loadname )); + + // make sure what new shader it's a skyShader and existing + hashKey = Com_HashKey( loadname, SHADERS_HASH_SIZE ); + + for( shader = r_shadersHash[hashKey]; shader; shader = shader->nextHash ) + { + if( !com.stricmp( shader->name, loadname )) + break; + } + if( shader ) + { + // already loaded, check parms + if( shader->flags & SHADER_SKYPARMS && shader->skyParms ) + shader_valid = true; + } + else + { + cache = Shader_GetCache( loadname, SHADER_INVALID, hashKey ); + if( cache ) + { + script_t *script = Com_OpenScript( cache->name, cache->buffer, cache->size ); + token_t tok; + + while( Com_ReadToken( script, SC_ALLOW_NEWLINES, &tok )) + { + if( !com.stricmp( "skyParms", tok.string )) + { + // check only far skybox images for existing + // because near skybox without far will be ignored by engine + if( Com_ReadToken( script, SC_ALLOW_PATHNAMES2, &tok )) + { + if( com.stricmp( "-", tok.string )) + { + if( Shader_CheckSkybox( tok.string )) + { + shader_valid = true; + break; + } + } + else + { + shader_valid = true; + break; // new shader just reset skybox + } + } + } + else if( !com.stricmp( "surfaceParm", tok.string )) + { + // check only far skybox images for existing + // because near skybox without far will be ignored by engine + if( Com_ReadToken( script, SC_ALLOW_PATHNAMES2, &tok )) + { + if( !com.stricmp( "sky", tok.string )) + { + shader_valid = true; + break; // yes it's q3-style skyshader + } + } + } + } + Com_CloseScript( script ); + } + else + { + if( !Shader_CheckSkybox( loadname )) + { + com.strncpy( loadname, va( "env/%s", name ), sizeof( loadname )); + if(!Shader_CheckSkybox( loadname )) + { + com.strncpy( loadname, va( "gfx/env/%s", name ), sizeof( loadname )); + if( Shader_CheckSkybox( loadname )) + shader_valid = true; + } + else shader_valid = true; + } + else shader_valid = true; + force_default = true; + } + } + } + else + { + // NULL names force to get current sky shader by user requesting + return (tr.currentSkyShader) ? tr.currentSkyShader : tr.defaultShader; + } + + if( !shader_valid ) + { + MsgDev( D_ERROR, "R_SetupSky: 'couldn't find shader '%s'\n", name ); + return (tr.currentSkyShader) ? tr.currentSkyShader : tr.defaultShader; + } + + if( tr.currentSkyShader == NULL ) + { + if( !r_worldmodel ) MsgDev( D_ERROR, "R_SetupSky: map not loaded\n" ); + else MsgDev( D_ERROR, "R_SetupSky: map %s not contain sky surfaces\n", r_worldmodel->name ); + return (tr.currentSkyShader) ? tr.currentSkyShader : tr.defaultShader; + } + + index = tr.currentSkyShader->shadernum; + Shader_FreeShader( tr.currentSkyShader ); // release old sky + + // new sky shader + tr.currentSkyShader = R_LoadShader( loadname, SHADER_SKY, force_default, 0, SHADER_INVALID ); + if( index != tr.currentSkyShader->shadernum ) + MsgDev( D_ERROR, "R_SetupSky: mismatch shader indexes %i != %i\n", index, tr.currentSkyShader->shadernum ); + + return tr.currentSkyShader; } \ No newline at end of file diff --git a/render/r_shader.h b/render/r_shader.h index 4c46147f..a4d0d116 100644 --- a/render/r_shader.h +++ b/render/r_shader.h @@ -325,6 +325,9 @@ typedef struct ref_shader_s deform_t *deforms; skydome_t *skyParms; + vec3_t skyAxis; + float skySpeed; + rgba_t fog_color; float fog_dist; float fog_clearDist; diff --git a/render/r_shadow.c b/render/r_shadow.c index f77855f6..ee42039f 100644 --- a/render/r_shadow.c +++ b/render/r_shadow.c @@ -20,7 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" /* ============================================================= @@ -135,8 +135,8 @@ void R_DeformVPlanarShadow( int numV, float *v ) R_GetShadowImpactAndDir( e, &tr, lightdir ); - Matrix_TransformVector( e->axis, lightdir, lightdir2 ); - Matrix_TransformVector( e->axis, tr.plane.normal, planenormal ); + Matrix3x3_Transform( e->axis, lightdir, lightdir2 ); + Matrix3x3_Transform( e->axis, tr.plane.normal, planenormal ); VectorScale( planenormal, e->scale, planenormal ); VectorSubtract( tr.endpos, e->origin, point ); @@ -468,7 +468,7 @@ void R_DrawShadowmaps( void ) VectorNormalizeFast( lightdir ); NormalVectorToAxis( lightdir, M ); - Matrix_EulerAngles( M, angles ); + Matrix3x3_ToAngles( M, angles, true ); for( j = 0; j < 3; j++ ) RI.refdef.viewangles[j] = anglemod( angles[j] ); diff --git a/render/r_sky.c b/render/r_sky.c index 73629769..0f07210b 100644 --- a/render/r_sky.c +++ b/render/r_sky.c @@ -24,25 +24,28 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#define SIDE_SIZE 9 -#define POINTS_LEN ( SIDE_SIZE * SIDE_SIZE ) -#define ELEM_LEN (( SIDE_SIZE-1 ) * ( SIDE_SIZE-1 ) * 6 ) +#define MAX_CLIP_VERTS 64 +#define SIDE_SIZE 9 +#define POINTS_LEN ( SIDE_SIZE * SIDE_SIZE ) +#define ELEM_LEN (( SIDE_SIZE-1 ) * ( SIDE_SIZE-1 ) * 6 ) -#define SPHERE_RAD 10.0f -#define EYE_RAD 9.0f +#define SPHERE_RAD 10.0f +#define EYE_RAD 9.0f -#define SCALE_S 4.0f // arbitrary (?) texture scaling factors -#define SCALE_T 4.0f +#define SCALE_S 4.0f // arbitrary (?) texture scaling factors +#define SCALE_T 4.0f +#define ST_MIN 1.0f / 512f +#define ST_MAX 511.0f / 512f -#define BOX_SIZE 1.0f -#define BOX_STEP BOX_SIZE / ( SIDE_SIZE-1 ) * 2.0f +#define BOX_SIZE 1.0f +#define BOX_STEP BOX_SIZE / ( SIDE_SIZE-1 ) * 2.0f -elem_t r_skydome_elems[6][ELEM_LEN]; -meshbuffer_t r_skydome_mbuffer; +elem_t r_skydome_elems[6][ELEM_LEN]; +meshbuffer_t r_skydome_mbuffer; -static mfog_t *r_skyfog; -static msurface_t *r_warpface; -static bool r_warpfacevis; +static mfog_t *r_skyfog; +static msurface_t *r_warpface; +static bool r_warpfacevis; static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight ); static void MakeSkyVec( float x, float y, float z, int axis, vec3_t v ); @@ -61,7 +64,7 @@ skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farbo byte *buffer; size = sizeof( skydome_t ) + sizeof( mesh_t ) * 6 + sizeof( vec4_t ) * POINTS_LEN * 6 + - sizeof( vec4_t ) * POINTS_LEN * 6 + sizeof( vec2_t ) * POINTS_LEN * 11; + sizeof( vec4_t ) * POINTS_LEN * 6 + sizeof( vec2_t ) * POINTS_LEN * 12; buffer = Mem_Alloc( mempool, size ); skydome = ( skydome_t * )buffer; @@ -77,10 +80,7 @@ skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farbo mesh->numVertexes = POINTS_LEN; mesh->xyzArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN; mesh->normalsArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN; - if( i != 5 ) - { - skydome->sphereStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN; - } + skydome->sphereStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN; skydome->linearStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN; mesh->numElems = ELEM_LEN; @@ -134,10 +134,10 @@ through the box verts to the sphere to find the texture coordinates. */ static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight ) { - vec3_t pos, w, row, norm; - float *v, *n, *st = NULL, *st2; - int r, c; - float t, d, d2, b, b2, q[2], s; + vec3_t pos, w, row, norm; + float *v, *n, *st, *st2; + float t, d, d2, b, b2, q[2], s; + int r, c; s = 1.0 / ( SIDE_SIZE-1 ); d = EYE_RAD; // sphere center to camera distance @@ -149,14 +149,10 @@ static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, v = skydome->meshes[side].xyzArray[0]; n = skydome->meshes[side].normalsArray[0]; - if( side != 5 ) - st = skydome->sphereStCoords[side][0]; + st = skydome->sphereStCoords[side][0]; st2 = skydome->linearStCoords[side][0]; VectorCopy( orig, row ); - -// CrossProduct( dcol, drow, norm ); -// VectorNormalize( norm ); VectorClear( norm ); for( r = 0; r < SIDE_SIZE; r++ ) @@ -176,17 +172,14 @@ static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, w[0] *= t; w[1] *= t; - if( st ) - { - // use x and y on sphere as s and t - // minus is here so skies scoll in correct (Q3A's) direction - st[0] = -w[0] * q[0]; - st[1] = -w[1] * q[1]; + // use x and y on sphere as s and t + // minus is here so skies scoll in correct (Q3A's) direction + st[0] = -w[0] * q[0]; + st[1] = -w[1] * q[1]; - // avoid bilerp seam - st[0] = ( bound( -1, st[0], 1 ) + 1.0 ) * 0.5; - st[1] = ( bound( -1, st[1], 1 ) + 1.0 ) * 0.5; - } + // avoid bilerp seam + st[0] = ( bound( -1, st[0], 1 ) + 1.0 ) * 0.5; + st[1] = ( bound( -1, st[1], 1 ) + 1.0 ) * 0.5; st2[0] = c * s; st2[1] = 1.0 - r * s; @@ -196,10 +189,9 @@ static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, v += 4; n += 4; - if( st ) st += 2; + st += 2; st2 += 2; } - VectorAdd( row, drow, row ); } } @@ -213,8 +205,7 @@ static void R_DrawSkySide( skydome_t *skydome, int side, ref_shader_t *shader, i { meshbuffer_t *mbuffer = &r_skydome_mbuffer; - if( RI.skyMins[0][side] >= RI.skyMaxs[0][side] || - RI.skyMins[1][side] >= RI.skyMaxs[1][side] ) + if( RI.skyMins[0][side] >= RI.skyMaxs[0][side] || RI.skyMins[1][side] >= RI.skyMaxs[1][side] ) return; mbuffer->shaderkey = shader->sortkey; @@ -233,8 +224,8 @@ R_DrawSkyBox */ static void R_DrawSkyBox( skydome_t *skydome, ref_shader_t **shaders ) { - int i, features; - const int skytexorder[6] = { SKYBOX_RIGHT, SKYBOX_FRONT, SKYBOX_LEFT, SKYBOX_BACK, SKYBOX_TOP, SKYBOX_BOTTOM }; + int i, features; + const int skytexorder[6] = { SKYBOX_RIGHT, SKYBOX_FRONT, SKYBOX_LEFT, SKYBOX_BACK, SKYBOX_TOP, SKYBOX_BOTTOM }; features = shaders[0]->features; if( r_shownormals->integer ) @@ -272,11 +263,13 @@ void R_DrawSky( ref_shader_t *shader ) vec3_t mins, maxs; mat4x4_t m, oldm; elem_t *elem; - skydome_t *skydome = shader->skyParms ? shader->skyParms : NULL; + skydome_t *skydome; meshbuffer_t *mbuffer = &r_skydome_mbuffer; int u, v, umin, umax, vmin, vmax; - if( !skydome ) return; + if( !shader ) return; + skydome = shader->skyParms ? shader->skyParms : NULL; + if( !skydome) return; ClearBounds( mins, maxs ); for( i = 0; i < 6; i++ ) @@ -284,17 +277,17 @@ void R_DrawSky( ref_shader_t *shader ) if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) continue; - umin = (int)( ( RI.skyMins[0][i]+1.0f )*0.5f*(float)( SIDE_SIZE-1 ) ); - umax = (int)( ( RI.skyMaxs[0][i]+1.0f )*0.5f*(float)( SIDE_SIZE-1 ) ) + 1; - vmin = (int)( ( RI.skyMins[1][i]+1.0f )*0.5f*(float)( SIDE_SIZE-1 ) ); - vmax = (int)( ( RI.skyMaxs[1][i]+1.0f )*0.5f*(float)( SIDE_SIZE-1 ) ) + 1; + umin = (int)(( RI.skyMins[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )); + umax = (int)(( RI.skyMaxs[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1; + vmin = (int)(( RI.skyMins[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )); + vmax = (int)(( RI.skyMaxs[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1; umin = bound( 0, umin, SIDE_SIZE-1 ); umax = bound( 0, umax, SIDE_SIZE-1 ); vmin = bound( 0, vmin, SIDE_SIZE-1 ); vmax = bound( 0, vmax, SIDE_SIZE-1 ); - // Box elems in tristrip order + // box elems in tristrip order elem = skydome->meshes[i].elems; for( v = vmin; v < vmax; v++ ) { @@ -311,7 +304,7 @@ void R_DrawSky( ref_shader_t *shader ) AddPointToBounds( skydome->meshes[i].xyzArray[vmin*SIDE_SIZE+umin], mins, maxs ); AddPointToBounds( skydome->meshes[i].xyzArray[vmax*SIDE_SIZE+umax], mins, maxs ); - skydome->meshes[i].numElems = ( vmax-vmin )*( umax-umin )*6; + skydome->meshes[i].numElems = ( vmax-vmin )*( umax-umin ) * 6; } VectorAdd( mins, RI.viewOrigin, mins ); @@ -327,9 +320,13 @@ void R_DrawSky( ref_shader_t *shader ) Matrix4_Copy( RI.modelviewMatrix, oldm ); Matrix4_Copy( RI.worldviewMatrix, RI.modelviewMatrix ); Matrix4_Copy( RI.worldviewMatrix, m ); - m[12] = 0; - m[13] = 0; - m[14] = 0; + + if( shader->skySpeed ) + { + float angle = shader->skySpeed * RI.refdef.time; + Matrix4_Rotate( m, angle, shader->skyAxis[0], shader->skyAxis[1], shader->skyAxis[2] ); + } + m[12] = m[13] = m[14] = 0.0f; m[15] = 1.0; pglLoadMatrixf( m ); @@ -342,13 +339,11 @@ void R_DrawSky( ref_shader_t *shader ) // it can happen that sky surfaces have no fog hull specified // yet there's a global fog hull (see wvwq3dm7) - if( !r_skyfog ) - r_skyfog = r_worldbrushmodel->globalfog; + if( !r_skyfog ) r_skyfog = r_worldbrushmodel->globalfog; if( skydome->farboxShaders[0] ) R_DrawSkyBox( skydome, skydome->farboxShaders ); - else - R_DrawBlackBottom( skydome ); + else R_DrawBlackBottom( skydome ); if( shader->num_stages ) { @@ -358,7 +353,7 @@ void R_DrawSky( ref_shader_t *shader ) if( r_shownormals->integer ) features |= MF_NORMALS; - for( i = 0; i < 5; i++ ) + for( i = 0; i < 6; i++ ) { if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) continue; @@ -434,11 +429,11 @@ DrawSkyPolygon */ void DrawSkyPolygon( int nump, vec3_t vecs ) { - int i, j; - vec3_t v, av; - float s, t, dv; - int axis; - float *vp; + int i, j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; // decide which face it maps to VectorClear( v ); @@ -450,12 +445,11 @@ void DrawSkyPolygon( int nump, vec3_t vecs ) av[1] = fabs( v[1] ); av[2] = fabs( v[2] ); - if( ( av[0] > av[1] ) && ( av[0] > av[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] ) ) + else if(( av[1] > av[2] ) && ( av[1] > av[0] )) axis = ( v[1] < 0 ) ? 3 : 2; - else - axis = ( v[2] < 0 ) ? 5 : 4; + else axis = ( v[2] < 0 ) ? 5 : 4; if( !r_skyfog ) r_skyfog = r_warpface->fog; @@ -467,7 +461,7 @@ void DrawSkyPolygon( int nump, vec3_t vecs ) j = vec_to_st[axis][2]; dv = ( j > 0 ) ? vecs[j - 1] : -vecs[-j - 1]; - if( dv < 0.001 ) + if( dv < 0.001f ) continue; // don't divide by zero dv = 1.0f / dv; @@ -478,19 +472,13 @@ void DrawSkyPolygon( int nump, vec3_t vecs ) 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; + 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; } } -#define MAX_CLIP_VERTS 64 - /* ============== ClipSkyPolygon @@ -498,22 +486,23 @@ ClipSkyPolygon */ void ClipSkyPolygon( int nump, vec3_t vecs, int stage ) { - float *norm; - float *v; - bool front, back; - float d, e; - 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; + float *norm; + float *v; + bool front, back; + float d, e; + 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 + { + // fully clipped, so draw it DrawSkyPolygon( nump, vecs ); return; } @@ -541,7 +530,8 @@ loc1: } if( !front || !back ) - { // not clipped + { + // not clipped stage++; goto loc1; } @@ -598,16 +588,26 @@ R_AddSkySurface */ bool R_AddSkySurface( msurface_t *fa ) { - int i; - vec4_t *vert; + int i; + vec4_t *vert; elem_t *elem; - mesh_t *mesh; - vec3_t verts[4]; + mesh_t *mesh; + vec3_t verts[4]; // calculate vertex values for sky box r_warpface = fa; r_warpfacevis = false; + if( fa->shader->skySpeed ) + { + // HACK: force full sky to draw when rotating + for( i = 0; i < 6; i++ ) + { + RI.skyMins[0][i] = RI.skyMins[1][i] = -1; + RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = 1; + } + } + mesh = fa->mesh; elem = mesh->elems; vert = mesh->xyzArray; @@ -618,7 +618,6 @@ bool R_AddSkySurface( msurface_t *fa ) VectorSubtract( vert[elem[2]], RI.viewOrigin, verts[2] ); ClipSkyPolygon( 3, verts[0], 0 ); } - return r_warpfacevis; } @@ -629,7 +628,7 @@ R_ClearSkyBox */ void R_ClearSkyBox( void ) { - int i; + int i; RI.params |= RP_NOSKY; for( i = 0; i < 6; i++ ) @@ -641,8 +640,8 @@ void R_ClearSkyBox( void ) void MakeSkyVec( float x, float y, float z, int axis, vec3_t v ) { - int j, k; - vec3_t b; + int j, k; + vec3_t b; b[0] = x; b[1] = y; @@ -651,9 +650,7 @@ void MakeSkyVec( float x, float y, float z, int axis, vec3_t v ) for( j = 0; j < 3; j++ ) { k = st_to_vec[axis][j]; - if( k < 0 ) - v[j] = -b[-k - 1]; - else - v[j] = b[k - 1]; + if( k < 0 ) v[j] = -b[-k-1]; + else v[j] = b[k-1]; } -} +} \ No newline at end of file diff --git a/render/r_sky.old b/render/r_sky.old new file mode 100644 index 00000000..530a0499 --- /dev/null +++ b/render/r_sky.old @@ -0,0 +1,665 @@ +/* +Copyright (C) 1999 Stephen C. Taylor +Copyright (C) 2002-2007 Victor Luchits + +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. + +*/ + +// r_sky.c + +#include "r_local.h" +#include "mathlib.h" + +#define MAX_CLIP_VERTS 64 +#define SIDE_SIZE 9 +#define POINTS_LEN ( SIDE_SIZE * SIDE_SIZE ) +#define ELEM_LEN (( SIDE_SIZE-1 ) * ( SIDE_SIZE-1 ) * 6 ) + +#define SPHERE_RAD 10.0f +#define EYE_RAD 9.0f + +#define SCALE_S 4.0f // arbitrary (?) texture scaling factors +#define SCALE_T 4.0f + +#define BOX_SIZE 1.0f +#define BOX_STEP BOX_SIZE / ( SIDE_SIZE-1 ) * 2.0f + +elem_t r_skydome_elems[6][ELEM_LEN]; +meshbuffer_t r_skydome_mbuffer; + +static mfog_t *r_skyfog; +static msurface_t *r_warpface; +static bool r_warpfacevis; + +static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight ); +static void MakeSkyVec( float x, float y, float z, int axis, vec3_t v ); +static void Gen_Box( skydome_t *skydome, float skyheight ); + +/* +============== +R_CreateSkydome +============== +*/ +skydome_t *R_CreateSkydome( byte *mempool, float skyheight, ref_shader_t **farboxShaders, ref_shader_t **nearboxShaders ) +{ + int i, size; + mesh_t *mesh; + skydome_t *skydome; + byte *buffer; + + size = sizeof( skydome_t ) + sizeof( mesh_t ) * 6 + sizeof( vec4_t ) * POINTS_LEN * 6 + + sizeof( vec4_t ) * POINTS_LEN * 6 + sizeof( vec2_t ) * POINTS_LEN * 11; + buffer = Mem_Alloc( mempool, size ); + + skydome = ( skydome_t * )buffer; + Mem_Copy( skydome->farboxShaders, farboxShaders, sizeof( ref_shader_t* ) * 6 ); + Mem_Copy( skydome->nearboxShaders, nearboxShaders, sizeof( ref_shader_t* ) * 6 ); + buffer += sizeof( skydome_t ); + + skydome->meshes = ( mesh_t * )buffer; + buffer += sizeof( mesh_t ) * 6; + + for( i = 0, mesh = skydome->meshes; i < 6; i++, mesh++ ) + { + mesh->numVertexes = POINTS_LEN; + mesh->xyzArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN; + mesh->normalsArray = ( vec4_t * )buffer; buffer += sizeof( vec4_t ) * POINTS_LEN; + if( i != 5 ) + { + skydome->sphereStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN; + } + skydome->linearStCoords[i] = ( vec2_t * )buffer; buffer += sizeof( vec2_t ) * POINTS_LEN; + + mesh->numElems = ELEM_LEN; + mesh->elems = r_skydome_elems[i]; + } + + Gen_Box( skydome, skyheight ); + + return skydome; +} + +/* +============== +R_FreeSkydome +============== +*/ +void R_FreeSkydome( skydome_t *skydome ) +{ + if( skydome ) Mem_Free( skydome ); +} + +/* +============== +Gen_Box +============== +*/ +static void Gen_Box( skydome_t *skydome, float skyheight ) +{ + int axis; + vec3_t orig, drow, dcol; + + for( axis = 0; axis < 6; axis++ ) + { + MakeSkyVec( -BOX_SIZE, -BOX_SIZE, BOX_SIZE, axis, orig ); + MakeSkyVec( 0, BOX_STEP, 0, axis, drow ); + MakeSkyVec( BOX_STEP, 0, 0, axis, dcol ); + Gen_BoxSide( skydome, axis, orig, drow, dcol, skyheight ); + } +} + +/* +================ +Gen_BoxSide + +I don't know exactly what Q3A does for skybox texturing, but +this is at least fairly close. We tile the texture onto the +inside of a large sphere, and put the camera near the top of +the sphere. We place the box around the camera, and cast rays +through the box verts to the sphere to find the texture coordinates. +================ +*/ +static void Gen_BoxSide( skydome_t *skydome, int side, vec3_t orig, vec3_t drow, vec3_t dcol, float skyheight ) +{ + vec3_t pos, w, row, norm; + float *v, *n, *st = NULL, *st2; + int r, c; + float t, d, d2, b, b2, q[2], s; + + s = 1.0 / ( SIDE_SIZE-1 ); + d = EYE_RAD; // sphere center to camera distance + d2 = d * d; + b = SPHERE_RAD; // sphere radius + b2 = b * b; + q[0] = 1.0 / ( 2.0 * SCALE_S ); + q[1] = 1.0 / ( 2.0 * SCALE_T ); + + v = skydome->meshes[side].xyzArray[0]; + n = skydome->meshes[side].normalsArray[0]; + if( side != 5 ) st = skydome->sphereStCoords[side][0]; + st2 = skydome->linearStCoords[side][0]; + + VectorCopy( orig, row ); + +// CrossProduct( dcol, drow, norm ); +// VectorNormalize( norm ); + VectorClear( norm ); + + for( r = 0; r < SIDE_SIZE; r++ ) + { + VectorCopy( row, pos ); + for( c = 0; c < SIDE_SIZE; c++ ) + { + // pos points from eye to vertex on box + VectorScale( pos, skyheight, v ); + VectorCopy( pos, w ); + + // Normalize pos -> w + VectorNormalize( w ); + + // Find distance along w to sphere + t = sqrt( d2 * ( w[2] * w[2] - 1.0 ) + b2 ) - d * w[2]; + w[0] *= t; + w[1] *= t; + + if( st ) + { + // use x and y on sphere as s and t + // minus is here so skies scoll in correct (Q3A's) direction + st[0] = -w[0] * q[0]; + st[1] = -w[1] * q[1]; + + // avoid bilerp seam + st[0] = ( bound( -1, st[0], 1 ) + 1.0 ) * 0.5; + st[1] = ( bound( -1, st[1], 1 ) + 1.0 ) * 0.5; + } + + st2[0] = c * s; + st2[1] = 1.0 - r * s; + + VectorAdd( pos, dcol, pos ); + VectorCopy( norm, n ); + + v += 4; + n += 4; + if( st ) st += 2; + st2 += 2; + } + + VectorAdd( row, drow, row ); + } +} + +/* +============== +R_DrawSkySide +============== +*/ +static void R_DrawSkySide( skydome_t *skydome, int side, ref_shader_t *shader, int features ) +{ + meshbuffer_t *mbuffer = &r_skydome_mbuffer; + + if( RI.skyMins[0][side] >= RI.skyMaxs[0][side] || RI.skyMins[1][side] >= RI.skyMaxs[1][side] ) + return; + + mbuffer->shaderkey = shader->sortkey; + mbuffer->dlightbits = 0; + mbuffer->sortkey = MB_FOG2NUM( r_skyfog ); + + skydome->meshes[side].stArray = skydome->linearStCoords[side]; + R_PushMesh( &skydome->meshes[side], features ); + R_RenderMeshBuffer( mbuffer ); +} + +/* +============== +R_DrawSkyBox +============== +*/ +static void R_DrawSkyBox( skydome_t *skydome, ref_shader_t **shaders ) +{ + int i, features; + const int skytexorder[6] = { SKYBOX_RIGHT, SKYBOX_FRONT, SKYBOX_LEFT, SKYBOX_BACK, SKYBOX_TOP, SKYBOX_BOTTOM }; + + features = shaders[0]->features; + if( r_shownormals->integer ) + features |= MF_NORMALS; + + for( i = 0; i < 6; i++ ) + R_DrawSkySide( skydome, i, shaders[skytexorder[i]], features ); +} + +/* +============== +R_DrawBlackBottom + +Draw dummy skybox side to prevent the HOM effect +============== +*/ +static void R_DrawBlackBottom( skydome_t *skydome ) +{ + int features; + + features = tr.defaultShader->features; + if( r_shownormals->integer ) + features |= MF_NORMALS; + R_DrawSkySide( skydome, 5, tr.defaultShader, features ); +} + +/* +============== +R_DrawSky +============== +*/ +void R_DrawSky( ref_shader_t *shader ) +{ + int i; + vec3_t mins, maxs; + mat4x4_t m, oldm; + elem_t *elem; + skydome_t *skydome; + meshbuffer_t *mbuffer = &r_skydome_mbuffer; + int u, v, umin, umax, vmin, vmax; + + if( !shader ) return; + skydome = shader->skyParms ? shader->skyParms : NULL; + if( !skydome) return; + + ClearBounds( mins, maxs ); + for( i = 0; i < 6; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + + umin = (int)(( RI.skyMins[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )); + umax = (int)(( RI.skyMaxs[0][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1; + vmin = (int)(( RI.skyMins[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )); + vmax = (int)(( RI.skyMaxs[1][i] + 1.0f ) * 0.5f * (float)( SIDE_SIZE-1 )) + 1; + + umin = bound( 0, umin, SIDE_SIZE-1 ); + umax = bound( 0, umax, SIDE_SIZE-1 ); + vmin = bound( 0, vmin, SIDE_SIZE-1 ); + vmax = bound( 0, vmax, SIDE_SIZE-1 ); + + // box elems in tristrip order + elem = skydome->meshes[i].elems; + for( v = vmin; v < vmax; v++ ) + { + for( u = umin; u < umax; u++ ) + { + elem[0] = v * SIDE_SIZE + u; + elem[1] = elem[4] = elem[0] + SIDE_SIZE; + elem[2] = elem[3] = elem[0] + 1; + elem[5] = elem[1] + 1; + elem += 6; + } + } + + AddPointToBounds( skydome->meshes[i].xyzArray[vmin*SIDE_SIZE+umin], mins, maxs ); + AddPointToBounds( skydome->meshes[i].xyzArray[vmax*SIDE_SIZE+umax], mins, maxs ); + + skydome->meshes[i].numElems = ( vmax-vmin )*( umax-umin ) * 6; + } + + VectorAdd( mins, RI.viewOrigin, mins ); + VectorAdd( maxs, RI.viewOrigin, maxs ); + + if( RI.refdef.rdflags & RDF_SKYPORTALINVIEW ) + { + R_DrawSkyPortal( &RI.refdef.skyportal, mins, maxs ); + return; + } + + // center skydome on camera to give the illusion of a larger space + Matrix4_Copy( RI.modelviewMatrix, oldm ); + Matrix4_Copy( RI.worldviewMatrix, RI.modelviewMatrix ); + Matrix4_Copy( RI.worldviewMatrix, m ); + + if( shader->skySpeed ) + { + float angle = shader->skySpeed * RI.refdef.time; + Matrix4_Rotate( m, angle, shader->skyAxis[0], shader->skyAxis[1], shader->skyAxis[2] ); + } + m[12] = m[13] = m[14] = 0.0f; + m[15] = 1.0; + pglLoadMatrixf( m ); + + gldepthmin = 1; + gldepthmax = 1; + pglDepthRange( gldepthmin, gldepthmax ); + + if( RI.params & RP_CLIPPLANE ) + pglDisable( GL_CLIP_PLANE0 ); + + // it can happen that sky surfaces have no fog hull specified + // yet there's a global fog hull (see wvwq3dm7) + if( !r_skyfog ) + r_skyfog = r_worldbrushmodel->globalfog; + + if( skydome->farboxShaders[0] ) + R_DrawSkyBox( skydome, skydome->farboxShaders ); + else R_DrawBlackBottom( skydome ); + + if( shader->num_stages ) + { + bool flush = false; + int features = shader->features; + + if( r_shownormals->integer ) + features |= MF_NORMALS; + + for( i = 0; i < 5; i++ ) + { + if( RI.skyMins[0][i] >= RI.skyMaxs[0][i] || RI.skyMins[1][i] >= RI.skyMaxs[1][i] ) + continue; + + flush = true; + mbuffer->shaderkey = shader->sortkey; + mbuffer->dlightbits = 0; + mbuffer->sortkey = MB_FOG2NUM( r_skyfog ); + + skydome->meshes[i].stArray = skydome->sphereStCoords[i]; + R_PushMesh( &skydome->meshes[i], features ); + } + if( flush ) R_RenderMeshBuffer( mbuffer ); + } + + if( skydome->nearboxShaders[0] ) + R_DrawSkyBox( skydome, skydome->nearboxShaders ); + + if( RI.params & RP_CLIPPLANE ) + pglEnable( GL_CLIP_PLANE0 ); + + Matrix4_Copy( oldm, RI.modelviewMatrix ); + pglLoadMatrixf( RI.worldviewMatrix ); + + gldepthmin = 0; + gldepthmax = 1; + pglDepthRange( gldepthmin, gldepthmax ); + + r_skyfog = NULL; +} + +//=================================================================== + +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 +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] +int vec_to_st[6][3] = +{ + { -2, 3, 1 }, + { 2, 3, -1 }, + + { 1, 3, 2 }, + { -1, 3, -2 }, + + { -2, -1, 3 }, + { -2, 1, -3 } +}; + +/* +============== +DrawSkyPolygon +============== +*/ +void DrawSkyPolygon( int nump, vec3_t vecs ) +{ + int i, j; + vec3_t v, av; + float s, t, dv; + int axis; + float *vp; + + // 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; + + if( !r_skyfog ) + r_skyfog = r_warpface->fog; + r_warpfacevis = true; + + // 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.001f ) + continue; // don't divide by zero + + dv = 1.0f / dv; + + 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 ) +{ + float *norm; + float *v; + bool front, back; + float d, e; + 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 ); +} + +/* +================= +R_AddSkySurface +================= +*/ +bool R_AddSkySurface( msurface_t *fa ) +{ + int i; + vec4_t *vert; + elem_t *elem; + mesh_t *mesh; + vec3_t verts[4]; + + // calculate vertex values for sky box + r_warpface = fa; + r_warpfacevis = false; + + if( fa->shader->skySpeed ) + { + // HACK: force full sky to draw when rotating + for( i = 0; i < 6; i++ ) + { + RI.skyMins[0][i] = RI.skyMins[1][i] = -1; + RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = 1; + } + } + + mesh = fa->mesh; + elem = mesh->elems; + vert = mesh->xyzArray; + for( i = 0; i < mesh->numElems; i += 3, elem += 3 ) + { + VectorSubtract( vert[elem[0]], RI.viewOrigin, verts[0] ); + VectorSubtract( vert[elem[1]], RI.viewOrigin, verts[1] ); + VectorSubtract( vert[elem[2]], RI.viewOrigin, verts[2] ); + ClipSkyPolygon( 3, verts[0], 0 ); + } + return r_warpfacevis; +} + +/* +============== +R_ClearSkyBox +============== +*/ +void R_ClearSkyBox( void ) +{ + int i; + + RI.params |= RP_NOSKY; + for( i = 0; i < 6; i++ ) + { + RI.skyMins[0][i] = RI.skyMins[1][i] = 9999999; + RI.skyMaxs[0][i] = RI.skyMaxs[1][i] = -9999999; + } +} + +void MakeSkyVec( float x, float y, float z, int axis, vec3_t v ) +{ + int j, k; + vec3_t b; + + b[0] = x; + b[1] = y; + b[2] = z; + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + if( k < 0 ) v[j] = -b[-k-1]; + else v[j] = b[k-1]; + } +} \ No newline at end of file diff --git a/render/r_surf.c b/render/r_surf.c index b4472930..e5314283 100644 --- a/render/r_surf.c +++ b/render/r_surf.c @@ -23,7 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_local.h" #include "mathlib.h" -#include "quatlib.h" +#include "matrix_lib.h" static vec3_t modelorg; // relative to viewpoint static vec3_t modelmins; @@ -78,7 +78,7 @@ bool R_CullSurface( msurface_t *surf, uint clipflags ) if( RI.currentmodel != r_worldmodel ) { - Matrix_TransformVector( RI.currententity->axis, surf->origin, origin ); + Matrix3x3_Transform( RI.currententity->axis, surf->origin, origin ); VectorAdd( origin, RI.currententity->origin, origin ); } else @@ -138,7 +138,7 @@ static meshbuffer_t *R_AddSurfaceToList( msurface_t *surf, unsigned int clipflag if( shader->flags & SHADER_SKYPARMS ) { bool vis = R_AddSkySurface( surf ); - if( ( RI.params & RP_NOSKY ) && vis ) + if(( RI.params & RP_NOSKY ) && vis ) { R_AddMeshToList( MB_MODEL, surf->fog, shader, surf - r_worldbrushmodel->surfaces + 1 ); RI.params &= ~RP_NOSKY; @@ -176,7 +176,7 @@ bool R_CullBrushModel( ref_entity_t *e ) if( bmodel->nummodelsurfaces == 0 ) return true; - if( !Matrix_Compare( e->axis, axis_identity ) ) + if( !Matrix3x3_Compare( e->axis, matrix3x3_identity )) { rotated = true; for( i = 0; i < 3; i++ ) @@ -231,14 +231,14 @@ void R_AddBrushModelToList( ref_entity_t *e ) e->outlineHeight = r_worldent->outlineHeight; Vector4Copy( r_worldent->outlineColor, e->outlineColor ); - rotated = !Matrix_Compare( e->axis, axis_identity ); + rotated = !Matrix3x3_Compare( e->axis, matrix3x3_identity ); VectorSubtract( RI.refdef.vieworg, e->origin, modelorg ); if( rotated ) { vec3_t temp; VectorCopy( modelorg, temp ); - Matrix_TransformVector( e->axis, temp, modelorg ); + Matrix3x3_Transform( e->axis, temp, modelorg ); } dlightbits = 0; diff --git a/render/render.plg b/render/render.plg new file mode 100644 index 00000000..b83a49cf --- /dev/null +++ b/render/render.plg @@ -0,0 +1,63 @@ + + +
+

Build Log

+

+--------------------Configuration: render - Win32 Debug-------------------- +

+

Command Lines

+Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7B9.tmp" with contents +[ +/nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR"..\temp\render\!debug/" /Fo"..\temp\render\!debug/" /Fd"..\temp\render\!debug/" /FD /c +"D:\Xash3D\src_main\render\r_shader.c" +] +Creating command line "cl.exe @C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7B9.tmp" +Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7BA.tmp" with contents +[ +msvcrtd.lib user32.lib gdi32.lib /nologo /subsystem:windows /dll /incremental:yes /pdb:"..\temp\render\!debug/render.pdb" /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /out:"..\temp\render\!debug/render.dll" /implib:"..\temp\render\!debug/render.lib" /pdbtype:sept +"\Xash3D\src_main\temp\render\!debug\cin.obj" +"\Xash3D\src_main\temp\render\!debug\r_alias.obj" +"\Xash3D\src_main\temp\render\!debug\r_backend.obj" +"\Xash3D\src_main\temp\render\!debug\r_bloom.obj" +"\Xash3D\src_main\temp\render\!debug\r_cin.obj" +"\Xash3D\src_main\temp\render\!debug\r_cull.obj" +"\Xash3D\src_main\temp\render\!debug\r_draw.obj" +"\Xash3D\src_main\temp\render\!debug\r_image.obj" +"\Xash3D\src_main\temp\render\!debug\r_light.obj" +"\Xash3D\src_main\temp\render\!debug\r_main.obj" +"\Xash3D\src_main\temp\render\!debug\r_math.obj" +"\Xash3D\src_main\temp\render\!debug\r_mesh.obj" +"\Xash3D\src_main\temp\render\!debug\r_model.obj" +"\Xash3D\src_main\temp\render\!debug\r_opengl.obj" +"\Xash3D\src_main\temp\render\!debug\r_poly.obj" +"\Xash3D\src_main\temp\render\!debug\r_program.obj" +"\Xash3D\src_main\temp\render\!debug\r_register.obj" +"\Xash3D\src_main\temp\render\!debug\r_shader.obj" +"\Xash3D\src_main\temp\render\!debug\r_shadow.obj" +"\Xash3D\src_main\temp\render\!debug\r_skin.obj" +"\Xash3D\src_main\temp\render\!debug\r_sky.obj" +"\Xash3D\src_main\temp\render\!debug\r_sprite.obj" +"\Xash3D\src_main\temp\render\!debug\r_surf.obj" +] +Creating command line "link.exe @C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7BA.tmp" +Creating temporary file "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7BB.bat" with contents +[ +@echo off +copy \Xash3D\src_main\temp\render\!debug\render.dll "D:\Xash3D\bin\render.dll" +] +Creating command line "C:\DOCUME~1\MIKE~1.MIK\LOCALS~1\Temp\RSP7BB.bat" +Compiling... +r_shader.c +Linking... + Creating library ..\temp\render\!debug/render.lib and object ..\temp\render\!debug/render.exp +

Output Window

+Performing Custom Build Step on \Xash3D\src_main\temp\render\!debug\render.dll +‘ª®¯¨à®¢ ­® ä ©«®¢: 1. + + + +

Results

+render.dll - 0 error(s), 0 warning(s) +
+ + diff --git a/server/ents/basefunc.cpp b/server/ents/basefunc.cpp index 44086735..9977b13a 100644 --- a/server/ents/basefunc.cpp +++ b/server/ents/basefunc.cpp @@ -34,6 +34,7 @@ public: virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } }; LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); +LINK_ENTITY_TO_CLASS( func_static, CFuncWall ); LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWall ); LINK_ENTITY_TO_CLASS( func_illusionary, CFuncWall ); diff --git a/server/ents/baseinfo.cpp b/server/ents/baseinfo.cpp index 9e05fb18..d6a3a77c 100644 --- a/server/ents/baseinfo.cpp +++ b/server/ents/baseinfo.cpp @@ -182,7 +182,6 @@ void CPortalSurface :: Think( void ) void CPortalSurface :: PostActivate( void ) { - Vector dir; CBaseEntity *pTarget, *pOwner; SetNextThink( 0 ); diff --git a/server/ents/basetrigger.cpp b/server/ents/basetrigger.cpp index 724a6baa..b2d79afd 100644 --- a/server/ents/basetrigger.cpp +++ b/server/ents/basetrigger.cpp @@ -281,8 +281,8 @@ class CTriggerPush : public CBaseTrigger { void Spawn( void ) { - if ( pev->angles == g_vecZero ) pev->angles.y = 360; - if (pev->speed == 0) pev->speed = 100; + if( pev->angles == g_vecZero ) pev->angles.y = 360; + if( pev->speed == 0 ) pev->speed = 100; UTIL_LinearVector( this ); if ( FBitSet (pev->spawnflags, 2) ) pev->solid = SOLID_NOT; @@ -293,7 +293,25 @@ class CTriggerPush : public CBaseTrigger SetBits( pev->effects, EF_NODRAW ); UTIL_SetOrigin( this, pev->origin ); } - void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + void PostActivate( void ) + { + Vector dir; + CBaseEntity *pOwner; + + if( FStringNull( pev->target )) + return; // dir set with angles + + pOwner = UTIL_FindEntityByTargetname( NULL, STRING( pev->target )); + if( !pOwner ) return; // dir set with angles + + if( FClassnameIs( pOwner->pev, "target_position" )) + { + pev->owner = pOwner->edict(); + pev->movedir = pOwner->pev->origin - ((pev->absmin + pev->absmax) * 0.5f); + pev->movedir.Normalize(); + } + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if( pev->solid == SOLID_NOT ) { @@ -314,13 +332,13 @@ class CTriggerPush : public CBaseTrigger return; } - if ( pOther->pev->solid != SOLID_NOT && pOther->pev->solid != SOLID_BSP ) + if( pOther->pev->solid != SOLID_NOT && pOther->pev->solid != SOLID_BSP ) { - // Instant trigger, just transfer velocity and remove - if (FBitSet(pev->spawnflags, 1)) + // instant trigger, just transfer velocity and remove + if( FBitSet( pev->spawnflags, 1 )) { pOther->pev->velocity = pOther->pev->velocity + (pev->speed * pev->movedir); - if ( pOther->pev->velocity.z > 0 ) pOther->pev->flags &= ~FL_ONGROUND; + if( pOther->pev->velocity.z > 0 ) pOther->pev->flags &= ~FL_ONGROUND; UTIL_Remove( this ); } else diff --git a/server/global/utils.cpp b/server/global/utils.cpp new file mode 100644 index 00000000..2aa3be28 --- /dev/null +++ b/server/global/utils.cpp @@ -0,0 +1,3072 @@ +//======================================================================= +// Copyright (C) Shambler Team 2005 +// utils.cpp - Utility code. +// Really not optional after all. +//======================================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "defaults.h" +#include "saverestore.h" +#include +#include "shake.h" +#include "decals.h" +#include "player.h" +#include "baseweapon.h" +#include "gamerules.h" +#include "client.h" + +FILE_GLOBAL char st_szNextMap[MAP_MAX_NAME]; +FILE_GLOBAL char st_szNextSpot[MAP_MAX_NAME]; +extern DLL_GLOBAL BOOL NewLevel; +int giAmmoIndex = 0; +BOOL CanAffect; + +//========================================================= +// COM_Functions (text files parsing) +//========================================================= +#define COM_Format(x) va_list szCommand; \ + static char value[1024]; \ + va_start( szCommand, x ); \ + vsprintf( value, x, szCommand ); \ + va_end( szCommand ); + +void Msg( char *message, ... ) +{ + COM_Format( message ); + g_engfuncs.pfnAlertMessage( at_console, "%s", value ); +} + +void DevMsg( char *message, ... ) +{ + COM_Format( message ); + g_engfuncs.pfnAlertMessage( at_aiconsole, "%s", value ); +} + +struct +{ + char token[1024]; + int length; + int symbol; +} parse; + +char *COM_Parse( char *data ) +{ + parse.length = 0; + parse.token[0] = 0; + + if( !data ) return NULL; +skip: + while((parse.symbol = *data) <= ' ') + { + if( parse.symbol == 0 ) return NULL; + data++; + } + if( parse.symbol == '/' && data[1] == '/' ) + { + while( *data && *data != '\n' ) data++; + goto skip; + } + if( parse.symbol == '\"' ) + { + data++; + while(true) + { + parse.symbol = *data++; + if( parse.symbol == '\"' || !parse.symbol ) + { + parse.token[parse.length] = 0; + return data; + } + parse.token[parse.length] = parse.symbol; + parse.length++; + } + } + + if( parse.symbol == '{' || parse.symbol == '}' || parse.symbol == ')' || parse.symbol == '(' || parse.symbol == '\'' || parse.symbol == ',' ) + { + parse.token[parse.length] = parse.symbol; + parse.length++; + parse.token[parse.length] = 0; + return data+1; + } + do + { + parse.token[parse.length] = parse.symbol; + data++; + parse.length++; + parse.symbol = *data; + if( parse.symbol == '{' || parse.symbol == '}' || parse.symbol == ')' || parse.symbol == '(' || parse.symbol == '\'' || parse.symbol == ',' ) break; + } while( parse.symbol > 32 ); + + parse.token[parse.length] = 0; + return data; +} + +char *COM_ParseFile( char *data, char *token ) +{ + char *return_data = COM_Parse( data ); + strcpy( token, parse.token ); + return return_data; +} + +void COM_FreeFile( char *buffer ) +{ + if( buffer ) FREE_FILE( buffer ); +} + +//========================================================= +// check for null ents +//========================================================= + +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } +inline BOOL FNullEnt( CBaseEntity *ent ) { return ent == NULL || FNullEnt( ent->edict()); } + +//========================================================= +// calculate brush model origin +//========================================================= +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return (pevBModel->absmin + pevBModel->absmax) * 0.5; +} + +BOOL FClassnameIs(CBaseEntity *pEnt, const char* szClassname) +{ + return FStrEq(STRING(pEnt->pev->classname), szClassname); +} + +//======================================================================== +// UTIL_FireTargets - supported prefix "+", "-", "!", ">", "<", "?". +// supported also this and self pointers - e.g. "fadein(mywall)" +//======================================================================== +void UTIL_FireTargets( int targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + //overload version UTIL_FireTargets + if (!targetName) return;//no execute code, if target blank + + UTIL_FireTargets( STRING(targetName), pActivator, pCaller, useType, value); +} + +void UTIL_FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + + const char *inputTargetName = targetName; + CBaseEntity *inputActivator = pActivator; + CBaseEntity *pTarget = NULL; + int i,j, found = false; + char szBuf[80]; + + if ( !targetName )return; + + //HACKHACK + if(FStrEq(targetName, "tr_endchange" )) + { + SERVER_COMMAND("game_over\n"); + return; + } + + if (targetName[0] == '+') + { + targetName++; + useType = USE_ON; + } + else if (targetName[0] == '-') + { + targetName++; + useType = USE_OFF; + } + else if (targetName[0] == '!') + { + targetName++; + useType = USE_REMOVE; + } + else if (targetName[0] == '<') + { + targetName++; + useType = USE_SET; + } + else if (targetName[0] == '>') + { + targetName++; + useType = USE_RESET; + } + else if (targetName[0] == '?') + { + targetName++; + useType = USE_SHOWINFO; + } + + pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, pActivator); + + if( !pTarget )//smart field name ? + { + //try to extract value from name (it's more usefully than "locus" specifier) + for (i = 0; targetName[i]; i++) + { + if (targetName[i] == '.')//value specifier + { + value = atof(&targetName[i+1]); + sprintf(szBuf, targetName); + szBuf[i] = 0; + targetName = szBuf; + pTarget = UTIL_FindEntityByTargetname(NULL, targetName, inputActivator); + break; + } + } + if( !pTarget )//try to extract activator specified + { + for (i = 0; targetName[i]; i++) + { + if (targetName[i] == '(') + { + i++; + for (j = i; targetName[j]; j++) + { + if (targetName[j] == ')') + { + strncpy(szBuf, targetName+i, j-i); + szBuf[j-i] = 0; + pActivator = UTIL_FindEntityByTargetname(NULL, szBuf, inputActivator); + if (!pActivator) return; //it's a locus specifier, but the locus is invalid. + found = true; + break; + } + } + if (!found) ALERT(at_error, "Missing ')' in targetname: %s", inputTargetName); + break; + } + } + if (!found) return; // no, it's not a locus specifier. + + strncpy(szBuf, targetName, i-1); + szBuf[i-1] = 0; + targetName = szBuf; + pTarget = UTIL_FindEntityByTargetname(NULL, targetName, inputActivator); + + if (!pTarget)return; // it's a locus specifier all right, but the target's invalid. + } + } + + DevMsg( "Firing: (%s) with %s and value %g\n", targetName, GetStringForUseType( useType ), value ); + + do //start firing targets + { + if ( !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + if (useType == USE_REMOVE) UTIL_Remove( pTarget ); + else pTarget->Use( pActivator, pCaller, useType, value ); + } + pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, inputActivator); + + } while (pTarget); +} + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +char* GetStringForUseType( USE_TYPE useType ) +{ + switch(useType) + { + case USE_ON: return "USE_ON"; + case USE_OFF: return "USE_OFF"; + case USE_TOGGLE: return "USE_TOGGLE"; + case USE_REMOVE: return "USE_REMOVE"; + case USE_SET: return "USE_SET"; + case USE_RESET: return "USE_RESET"; + case USE_SHOWINFO: return "USE_SHOWINFO"; + default: + return "UNKNOWN USE_TYPE!"; + } +} + +char* GetStringForState( STATE state ) +{ + switch(state) + { + case STATE_ON: return "ON"; + case STATE_OFF: return "OFF"; + case STATE_TURN_ON: return "TURN ON"; + case STATE_TURN_OFF: return "TURN OFF"; + case STATE_IN_USE: return "IN USE"; + case STATE_DEAD: return "DEAD"; + default: + return "UNKNOWN STATE!"; + } +} + +char* GetStringForDecalName( int decalname ) +{ + char *name = ""; + int m_decal = UTIL_LoadDecalPreset( decalname ); + switch(m_decal) + { + case 1: name = gDecals[ DECAL_GUNSHOT1 + RANDOM_LONG(0,4)].name; break; + case 2: name = gDecals[ DECAL_BLOOD1 + RANDOM_LONG(0,5)].name; break; + case 3: name = gDecals[ DECAL_YBLOOD1 + RANDOM_LONG(0,5)].name; break; + case 4: name = gDecals[ DECAL_GLASSBREAK1 + RANDOM_LONG(0,2)].name; break; + case 5: name = gDecals[ DECAL_BIGSHOT1 + RANDOM_LONG(0,4)].name; break; + case 6: name = gDecals[ DECAL_SCORCH1 + RANDOM_LONG(0,1)].name; break; + case 7: name = gDecals[ DECAL_SPIT1 + RANDOM_LONG(0,1)].name; break; + default: name = (char *)STRING( decalname ); break; + } + return name; +} + +void PrintStringForDamage( int dmgbits ) +{ + char szDmgBits[256]; + strcat(szDmgBits, "DAMAGE: " ); + if( dmgbits & DMG_GENERIC) strcat(szDmgBits, "Generic " ); + if( dmgbits & DMG_CRUSH) strcat(szDmgBits, "Crush " ); + if( dmgbits & DMG_BULLET) strcat(szDmgBits, "Bullet " ); + if( dmgbits & DMG_SLASH) strcat(szDmgBits, "Slash " ); + if( dmgbits & DMG_BURN) strcat(szDmgBits, "Burn " ); + if( dmgbits & DMG_FREEZE) strcat(szDmgBits, "Freeze " ); + if( dmgbits & DMG_FALL) strcat(szDmgBits, "Fall " ); + if( dmgbits & DMG_BLAST) strcat(szDmgBits, "Blast " ); + if( dmgbits & DMG_CLUB) strcat(szDmgBits, "Club " ); + if( dmgbits & DMG_SHOCK) strcat(szDmgBits, "Shock " ); + if( dmgbits & DMG_SONIC) strcat(szDmgBits, "Sonic " ); + if( dmgbits & DMG_ENERGYBEAM) strcat(szDmgBits, "Energy Beam " ); + if( dmgbits & DMG_NEVERGIB) strcat(szDmgBits, "Never Gib " ); + if( dmgbits & DMG_ALWAYSGIB) strcat(szDmgBits, "Always Gib " ); + if( dmgbits & DMG_DROWN) strcat(szDmgBits, "Drown " ); + if( dmgbits & DMG_PARALYZE) strcat(szDmgBits, "Paralyze Gas " ); + if( dmgbits & DMG_NERVEGAS) strcat(szDmgBits, "Nerve Gas " ); + if( dmgbits & DMG_POISON) strcat(szDmgBits, "Poison " ); + if( dmgbits & DMG_RADIATION) strcat(szDmgBits, "Radiation " ); + if( dmgbits & DMG_DROWNRECOVER) strcat(szDmgBits, "Drown Recover " ); + if( dmgbits & DMG_ACID) strcat(szDmgBits, "Acid " ); + if( dmgbits & DMG_SLOWBURN) strcat(szDmgBits, "Slow Burn " ); + if( dmgbits & DMG_SLOWFREEZE) strcat(szDmgBits, "Slow Freeze " ); + if( dmgbits & DMG_MORTAR) strcat(szDmgBits, "Mortar " ); + if( dmgbits & DMG_NUCLEAR) strcat(szDmgBits, "Nuclear Explode " ); + Msg("%s\n", szDmgBits ); +} +char* GetContentsString( int contents ) +{ + switch(contents) + { + case -1: return "EMPTY"; + case -2: return "SOLID"; + case -3: return "WATER"; + case -4: return "SLIME"; + case -5: return "LAVA"; + case -6: return "SKY"; + case -16: return "LADDER"; + case -17: return "FLYFIELD"; + case -18: return "GRAVITY_FLYFIELD"; + case -19: return "FOG"; + case -20: return "SPECIAL 1"; + case -21: return "SPECIAL 2"; + case -22: return "SPECIAL 3"; + default: + return "NO CONTENTS!"; + } +} + +char* GetStringForGlobalState( GLOBALESTATE state ) +{ + switch(state) + { + case GLOBAL_ON: return "GLOBAL ON"; + case GLOBAL_OFF: return "GLOBAL OFF"; + case GLOBAL_DEAD: return "GLOBAL DEAD"; + default: + return "UNKNOWN STATE!"; + } +} + +int UTIL_ConvertContents( int hl_contents ) +{ + switch( hl_contents ) + { + case -1: return CONTENTS_NONE; + case -2: return CONTENTS_SOLID; + case -3: return CONTENTS_WATER; + case -4: return CONTENTS_SLIME; + case -5: return CONTENTS_LAVA; + case -6: return CONTENTS_SKY; + case -19: return CONTENTS_FOG; + } + return CONTENTS_NONE; +} + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if( !pEntity ) return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; + pEntity->pev->health = 0; +} + +void UTIL_AngularVector( CBaseEntity *pEnt ) +{ + Vector movedir; + + UTIL_MakeVectors( pEnt->pev->angles ); + movedir.z = fabs(gpGlobals->v_forward.x); + movedir.x = fabs(gpGlobals->v_forward.y); + movedir.y = fabs(gpGlobals->v_forward.z); + + pEnt->pev->movedir = movedir; + pEnt->pev->angles = g_vecZero; +} + +void UTIL_LinearVector( CBaseEntity *pEnt ) +{ + if( pEnt->pev->angles == Vector( 0, -1, 0 )) + { + pEnt->pev->movedir = Vector( 0, 0, 1 ); + } + else if( pEnt->pev->angles == Vector( 0, -2, 0 )) + { + pEnt->pev->movedir = Vector( 0, 0, -1); + } + else + { + UTIL_MakeVectors( pEnt->pev->angles ); + pEnt->pev->movedir = gpGlobals->v_forward; + } + pEnt->pev->angles = g_vecZero; +} + +Vector UTIL_GetAngleDistance( Vector vecAngles, float distance ) +{ + //set one length vector + if(vecAngles.x != 0) vecAngles.x = 1; + if(vecAngles.y != 0) vecAngles.y = 1; + if(vecAngles.z != 0) vecAngles.z = 1; + return vecAngles * distance; +} + +Vector UTIL_RandomVector(void) +{ + Vector out; + out.x = RANDOM_FLOAT(-1.0, 1.0); + out.y = RANDOM_FLOAT(-1.0, 1.0); + out.z = RANDOM_FLOAT(-1.0, 1.0); + return out; +} + +Vector UTIL_RandomVector( Vector vmin, Vector vmax ) +{ + Vector out; + out.x = RANDOM_FLOAT( vmin.x, vmax.x ); + out.y = RANDOM_FLOAT( vmin.y, vmax.y ); + out.z = RANDOM_FLOAT( vmin.z, vmax.z ); + return out; +} + +CBaseEntity *UTIL_FindGlobalEntity( string_t classname, string_t globalname ) +{ + CBaseEntity *pReturn = UTIL_FindEntityByString( NULL, "globalname", STRING(globalname) ); + if ( pReturn ) + { + if( !FClassnameIs( pReturn->pev, STRING( classname ))) + { + ALERT( at_console, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + +void UTIL_FindBreakable( CBaseEntity *Brush ) +{ + Vector mins = Brush->pev->absmin; + Vector maxs = Brush->pev->absmax; + mins.z = Brush->pev->absmax.z; + maxs.z += 8; + + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } +} + +CBaseEntity *UTIL_FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors( pMe->pev->viewangles ); + UTIL_TraceLine( pMe->pev->origin + pMe->pev->view_ofs, pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192, dont_ignore_monsters, pMe->edict(), &tr ); + if( tr.flFraction != 1.0 && !FNullEnt( tr.pHit )) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + +void UTIL_FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + int i, j, k; + float distance; + float *minmaxs[2] = {mins, maxs}; + TraceResult tmpTrace; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + +edict_t *UTIL_FindLandmark( string_t iLandmarkName ) +{ + return UTIL_FindLandmark( STRING( iLandmarkName)); +} + +edict_t *UTIL_FindLandmark( const char *pLandmarkName ) +{ + CBaseEntity *pLandmark; + + pLandmark = UTIL_FindEntityByTargetname( NULL, pLandmarkName ); + while ( pLandmark ) + { + // Found the landmark + if ( FClassnameIs( pLandmark->pev, "info_landmark" ) ) + return ENT(pLandmark->pev); + else pLandmark = UTIL_FindEntityByTargetname( pLandmark, pLandmarkName ); + } + Msg("ERROR: Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + +int UTIL_FindTransition( CBaseEntity *pEntity, string_t iVolumeName ) +{ + return UTIL_FindTransition( pEntity, (char *)STRING( iVolumeName )); +} + +int UTIL_FindTransition( CBaseEntity *pEntity, char *pVolumeName ) +{ + CBaseEntity *pVolume; + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW && pEntity->pev->aiment != NULL) + { + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pVolume = UTIL_FindEntityByTargetname( NULL, pVolumeName ); + while ( pVolume ) + { + if ( FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else inVolume = 0; // Found a trigger_transition, but I don't intersect it + } + pVolume = UTIL_FindEntityByTargetname( pVolume, pVolumeName ); + } + return inVolume; +} + +//======================================================================== +// UTIL_ClearPTR - clear all pointers before changelevel +//======================================================================== +void UTIL_ClearPTR( void ) +{ + CBaseEntity *pEntity = NULL; + + for ( int i = 1; i <= gpGlobals->maxEntities; i++ ) + { + edict_t *pEntityEdict = INDEXENT( i ); + if ( pEntityEdict && !pEntityEdict->free && !FStringNull(pEntityEdict->v.globalname) ) + { + pEntity = CBaseEntity::Instance( pEntityEdict ); + } + if (!pEntity) continue; + else pEntity->ClearPointers(); + } +} + +//======================================================================== +// UTIL_ChangeLevel - used for loading next level +//======================================================================== +void UTIL_ChangeLevel( string_t mapname, string_t spotname ) +{ + UTIL_ChangeLevel((char *)STRING( mapname ), (char *)STRING( spotname )); +} + +void UTIL_ChangeLevel( const char *szNextMap, const char *szNextSpot ) +{ + edict_t *pentLandmark; + LEVELLIST levels[16]; + + ASSERT(!FStrEq(szNextMap, "")); + + // Don't work in deathmatch + if ( IsMultiplayer()) return; + // Some people are firing these multiple times in a frame, disable + if ( NewLevel ) return; + + CBaseEntity *pPlayer = UTIL_PlayerByIndex( 1 ); + if (!UTIL_FindTransition( pPlayer, (char *)szNextSpot )) + { + DevMsg( "Player isn't in the transition volume %s, aborting\n", szNextSpot ); + return; + } + + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, szNextMap); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = UTIL_FindLandmark( szNextSpot ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, szNextSpot); + gpGlobals->spotOffset = VARS(pentLandmark)->origin; + } + + //try to found bsp file before loading nextlevel + char path[128]; + + sprintf(path, "maps/%s.bsp", st_szNextMap); + + if( FILE_EXISTS( path )) + { + UTIL_ClearPTR(); + DevMsg( "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); + } + else Msg("Warning! Map %s not found!\n", st_szNextMap ); + NewLevel = TRUE;//bit who indiactes new level +} + +//======================================================================== +// Parent system utils +// Used for physics code too +//======================================================================== +void UTIL_MarkChild ( CBaseEntity *pEnt, BOOL correctSpeed, BOOL desired ) +{ + if(desired) //post affect + { + SetBits(pEnt->pFlags, PF_DESIRED); + if (CanAffect) + { //apply post affect now + PostFrameAffect( pEnt ); + return; + } + } + else + { + SetBits(pEnt->pFlags, PF_AFFECT); + + if (correctSpeed) + SetBits (pEnt->pFlags, PF_CORECTSPEED); + else ClearBits (pEnt->pFlags, PF_CORECTSPEED); + } + LinkChild( pEnt );//link children +} + +void UTIL_SetThink ( CBaseEntity *pEnt ) +{ + SetBits (pEnt->pFlags, PF_SETTHINK); + pEnt->DontThink(); + UTIL_MarkChild( pEnt ); +} + +void UTIL_SetPostAffect( CBaseEntity *pEnt ) +{ + SetBits(pEnt->pFlags, PF_POSTAFFECT); + UTIL_MarkChild( pEnt ); +} + +void UTIL_SetAction( CBaseEntity *pEnt ) +{ + SetBits(pEnt->pFlags, PF_ACTION); + UTIL_MarkChild( pEnt ); +} +//======================================================================== +// Assign origin and angles +//======================================================================== +void UTIL_AssignOrigin( CBaseEntity *pEntity, const Vector vecOrigin, BOOL bInitiator) +{ + Vector vecDiff = vecOrigin - pEntity->pev->origin; + + UTIL_SetOrigin(pEntity, vecOrigin ); + + if (bInitiator && pEntity->m_pParent) + { + pEntity->OffsetOrigin = pEntity->pev->origin - pEntity->m_pParent->pev->origin; + } + if (pEntity->m_pChild) + { + CBaseEntity* pChild = pEntity->m_pChild; + + Vector vecTemp; + while (pChild) + { + if (pChild->pev->movetype != MOVETYPE_PUSH || pChild->pev->velocity == pEntity->pev->velocity) + { + UTIL_AssignOrigin( pChild, vecOrigin + pChild->OffsetOrigin, FALSE ); + } + else + { + vecTemp = vecDiff + pChild->pev->origin; + UTIL_AssignOrigin( pChild, vecTemp, FALSE ); + } + pChild = pChild->m_pNextChild; + } + } +} +void UTIL_AssignAngles( CBaseEntity *pEntity, const Vector vecAngles, BOOL bInitiator) +{ + Vector vecDiff = vecAngles - pEntity->pev->angles; + + UTIL_SetAngles(pEntity, vecAngles); + + if (bInitiator && pEntity->m_pParent) + pEntity->OffsetAngles = vecAngles - pEntity->m_pParent->pev->angles; + + if (pEntity->m_pChild) // now I've moved pEntity, does anything else have to move with it? + { + CBaseEntity* pChild = pEntity->m_pChild; + Vector vecTemp; + while (pChild) + { + if (pChild->pev->avelocity == pEntity->pev->avelocity) + UTIL_AssignAngles( pChild, vecAngles + pChild->OffsetAngles, FALSE ); + else + { + vecTemp = vecDiff + pChild->pev->angles; + UTIL_AssignAngles( pChild, vecTemp, FALSE ); + } + pChild = pChild->m_pNextChild; + } + } +} + +//======================================================================== +// Set origin and angles +//======================================================================== +void UTIL_MergePos ( CBaseEntity *pEnt, int loopbreaker ) +{ + if (loopbreaker <= 0)return; + if (!pEnt->m_pParent)return; + + Vector forward, right, up, vecOrg, vecAngles; + + UTIL_MakeVectorsPrivate( pEnt->m_pParent->pev->angles, forward, right, up ); + + if(pEnt->m_pParent->pev->flags & FL_MONSTER) + vecOrg = pEnt->PostOrigin = pEnt->m_pParent->pev->origin + (forward * pEnt->OffsetOrigin.x) + ( right * pEnt->OffsetOrigin.y) + (up * pEnt->OffsetOrigin.z); + else vecOrg = pEnt->PostOrigin = pEnt->m_pParent->pev->origin + (forward * pEnt->OffsetOrigin.x) + (-right * pEnt->OffsetOrigin.y) + (up * pEnt->OffsetOrigin.z); + + vecAngles = pEnt->PostAngles = pEnt->m_pParent->pev->angles + pEnt->OffsetAngles; + SetBits(pEnt->pFlags, PF_POSTAFFECT | PF_DESIRED ); + + if ( pEnt->m_pChild ) + { + CBaseEntity *pMoving = pEnt->m_pChild; + int sloopbreaker = MAX_CHILDS; + while (pMoving) + { + UTIL_MergePos(pMoving, loopbreaker - 1 ); + pMoving = pMoving->m_pNextChild; + sloopbreaker--; + if (sloopbreaker <= 0)break; + } + } + + if(pEnt->pFlags & PF_MERGEPOS) + { + UTIL_AssignOrigin( pEnt, vecOrg ); + UTIL_AssignAngles( pEnt, vecAngles ); + ClearBits(pEnt->pFlags, PF_MERGEPOS); + } + if(pEnt->pFlags & PF_POSTORG) + { + pEnt->pev->origin = vecOrg; + pEnt->pev->angles = vecAngles; + } +} + +void UTIL_ComplexRotate( CBaseEntity *pParent, CBaseEntity *pChild, const Vector &dest, float m_flTravelTime ) +{ + float time = m_flTravelTime; + Vector vel = pParent->pev->velocity; + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) return; + + Vector offset = pChild->pev->origin - pParent->pev->origin; + Vector delta = dest - pParent->pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + pChild->pev->velocity = vel + (local * (1.0 / time)); + pChild->pev->avelocity = pParent->pev->avelocity; +} + +void UTIL_SetOrigin( CBaseEntity *pEntity, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pEntity->pev), vecOrigin ); +} + +void UTIL_SetAngles( CBaseEntity *pEntity, const Vector &vecAngles ) +{ + pEntity->pev->angles = vecAngles; +} + +void UTIL_SynchDoors( CBaseEntity *pEntity ) +{ + CBaseDoor *pDoor = NULL; + + if ( !FStringNull( pEntity->pev->targetname ) ) + { + while(1) + { + pDoor = (CBaseDoor *)UTIL_FindEntityByTargetname (pDoor, STRING(pEntity->pev->targetname)); + + if ( pDoor != pEntity ) + { + if (FNullEnt(pDoor)) break; + + if ( FClassnameIs ( pDoor, "func_door" ) && pDoor->m_flWait >= 0 ) + { + if (pDoor->pev->velocity == pEntity->pev->velocity) + { + UTIL_SetOrigin( pDoor, pEntity->pev->origin ); + UTIL_SetVelocity( pDoor, g_vecZero ); + } + if (pDoor->GetState() == STATE_TURN_ON) pDoor->DoorGoDown(); + else if (pDoor->GetState() == STATE_TURN_OFF) pDoor->DoorGoUp(); + } + if( FClassnameIs ( pDoor, "func_door_rotating" ) && pDoor->m_flWait >= 0 ) + { + if(pDoor->pev->avelocity == pEntity->pev->avelocity) + { + UTIL_SetAngles( pDoor, pEntity->pev->angles ); + UTIL_SetAvelocity( pDoor, g_vecZero ); + } + if (pDoor->GetState() == STATE_TURN_ON) pDoor->DoorGoDown(); + else if (pDoor->GetState() == STATE_TURN_OFF) pDoor->DoorGoUp(); + } + } + } + } +} + +//======================================================================== +// Set childs velocity and avelocity +//======================================================================== +void UTIL_SetChildVelocity ( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ) +{ + if (loopbreaker <= 0)return; + if (!pEnt->m_pParent)return; + + Vector vecNew; + vecNew = (pEnt->pev->velocity - pEnt->m_pParent->pev->velocity) + vecSet; + + if ( pEnt->m_pChild ) + { + CBaseEntity *pMoving = pEnt->m_pChild; + int sloopbreaker = MAX_CHILDS; + while (pMoving) + { + UTIL_SetChildVelocity(pMoving, vecNew, loopbreaker - 1 ); + pMoving = pMoving->m_pNextChild; + sloopbreaker--; + if (sloopbreaker <= 0)break; + } + } + pEnt->pev->velocity = vecNew; +} + +void UTIL_SetVelocity ( CBaseEntity *pEnt, const Vector vecSet ) +{ + Vector vecNew; + if (pEnt->m_pParent) + vecNew = vecSet + pEnt->m_pParent->pev->velocity; + else vecNew = vecSet; + + if ( pEnt->m_pChild ) + { + CBaseEntity *pMoving = pEnt->m_pChild; + int sloopbreaker = MAX_CHILDS; + while (pMoving) + { + UTIL_SetChildVelocity(pMoving, vecNew, MAX_CHILDS ); + if(vecSet != g_vecZero)SetBits(pMoving->pFlags, PF_PARENTMOVE); + else ClearBits(pMoving->pFlags, PF_PARENTMOVE); + + pMoving = pMoving->m_pNextChild; + sloopbreaker--; + if (sloopbreaker <= 0)break; + } + } + pEnt->pev->velocity = vecNew; +} + +void UTIL_SetChildAvelocity ( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ) +{ + if (loopbreaker <= 0)return; + if (!pEnt->m_pParent)return; + + Vector vecNew = (pEnt->pev->avelocity - pEnt->m_pParent->pev->avelocity) + vecSet; + + if ( pEnt->m_pChild ) + { + CBaseEntity *pMoving = pEnt->m_pChild; + int sloopbreaker = MAX_CHILDS; + while (pMoving) + { + UTIL_SetChildAvelocity(pMoving, vecNew, loopbreaker - 1 ); + pMoving = pMoving->m_pNextChild; + sloopbreaker--; + if (sloopbreaker <= 0)break; + } + } + + pEnt->pev->avelocity = vecNew; +} + +void UTIL_SetAvelocity ( CBaseEntity *pEnt, const Vector vecSet ) +{ + Vector vecNew; + + if (pEnt->m_pParent) + vecNew = vecSet + pEnt->m_pParent->pev->avelocity; + else vecNew = vecSet; + + if ( pEnt->m_pChild ) + { + CBaseEntity *pMoving = pEnt->m_pChild; + int sloopbreaker = MAX_CHILDS; + while (pMoving) + { + UTIL_SetChildAvelocity(pMoving, vecNew, MAX_CHILDS ); + UTIL_MergePos( pMoving ); + if(vecSet != g_vecZero)SetBits(pMoving->pFlags, PF_PARENTMOVE); + else ClearBits(pMoving->pFlags, PF_PARENTMOVE); + + pMoving = pMoving->m_pNextChild; + sloopbreaker--; + if (sloopbreaker <= 0)break; + } + } + pEnt->pev->avelocity = vecNew; +} + +//======================================================================== +// Precache and set resources - add check for present or invalid name +// NOTE: game will not crashed if model not specified, this code is legacy +//======================================================================== +void UTIL_SetModel( edict_t *e, string_t s, const char *c ) // set default model if not found +{ + if( FStringNull( s )) UTIL_SetModel( e, c ); + else UTIL_SetModel( e, s ); +} + +void UTIL_SetModel( edict_t *e, string_t model ) +{ + UTIL_SetModel( e, STRING( model )); +} + +void UTIL_SetModel( edict_t *e, const char *model ) +{ + if( !model || !*model ) + { + g_engfuncs.pfnSetModel( e, "models/common/null.mdl" ); + return; + } + + // is this brush model? + if( model[0] == '*' ) + { + g_engfuncs.pfnSetModel( e, model ); + return; + } + + // verify file exists + if( FILE_EXISTS( model )) + { + g_engfuncs.pfnSetModel( e, model ); + return; + } + + if( !strcmp( UTIL_FileExtension( model ), "mdl" )) + { + // this is model + g_engfuncs.pfnSetModel(e, "models/common/error.mdl" ); + } + else if( !strcmp( UTIL_FileExtension( model ), "spr" )) + { + // this is sprite + g_engfuncs.pfnSetModel( e, "sprites/error.spr" ); + } + else + { + // set null model + g_engfuncs.pfnSetModel( e, "models/common/null.mdl" ); + } +} + +int UTIL_PrecacheModel( string_t s, const char *e ) // precache default model if not found +{ + if( FStringNull( s )) + return UTIL_PrecacheModel( e ); + return UTIL_PrecacheModel( s ); +} + +int UTIL_PrecacheModel( string_t s ){ return UTIL_PrecacheModel( STRING( s )); } +int UTIL_PrecacheModel( const char* s ) +{ + if( !s || !*s ) + { + ALERT( at_warning, "modelname not specified\n" ); + return g_sModelIndexNullModel; // set null model + } + + // no need to precache brush + if( s[0] == '*' ) return 0; + + if( FILE_EXISTS( s )) + { + return g_engfuncs.pfnPrecacheModel( s ); + } + + if( !strcmp( UTIL_FileExtension( s ), "mdl" )) + { + ALERT( at_warning, "model \"%s\" not found!\n", s ); + return g_sModelIndexErrorModel; + } + else if( !strcmp( UTIL_FileExtension( s ), "spr" )) + { + ALERT( at_warning, "sprite \"%s\" not found!\n", s ); + return g_sModelIndexErrorSprite; + } + else + { + ALERT( at_error, "invalid name \"%s\"!\n", s ); + return g_sModelIndexNullModel; + } +} + +//======================================================================== +// Precaches the ammo and queues the ammo info for sending to clients +//======================================================================== +void AddAmmoName( string_t iAmmoName ) +{ + // make sure it's not already in the registry + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerWeapon::AmmoInfoArray[i].iszName ) continue; + if( CBasePlayerWeapon::AmmoInfoArray[i].iszName == iAmmoName ) + return; // ammo already in registry, just quite + } + + giAmmoIndex++; + ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); + if( giAmmoIndex >= MAX_AMMO_SLOTS ) + giAmmoIndex = 0; + + CBasePlayerWeapon::AmmoInfoArray[giAmmoIndex].iszName = iAmmoName; + CBasePlayerWeapon::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant +} + +//======================================================================== +// Precaches entity from other entity +//======================================================================== +void UTIL_PrecacheEntity( string_t szClassname ) { UTIL_PrecacheEntity( STRING( szClassname )); } +void UTIL_PrecacheEntity( const char *szClassname ) +{ + edict_t *pent; + int istr = ALLOC_STRING( szClassname ); + + pent = CREATE_NAMED_ENTITY( istr ); + if( FNullEnt( pent )) return; + + CBaseEntity *pEntity = CBaseEntity::Instance( VARS( pent )); + if( pEntity ) pEntity->Precache(); + REMOVE_ENTITY( pent ); +} + +//======================================================================== +// Precaches aurora particle and set it +//======================================================================== +int UTIL_PrecacheAurora( string_t s ) { return UTIL_PrecacheAurora( STRING( s )); } +int UTIL_PrecacheAurora( const char *s ) +{ + char path[256]; + sprintf( path, "scripts/aurora/%s.aur", s ); + + if( FILE_EXISTS( path )) + { + return ALLOC_STRING( path ); + } + else // otherwise + { + if( !s || !*s )Msg( "Warning: Aurora not specified!\n", s); + else ALERT( at_warning, "aurora %s not found!\n", s ); + return MAKE_STRING( "scripts/aurora/error.aur" ); + } +} + +void UTIL_SetAurora( CBaseEntity *pAttach, int aur, int attachment ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsg.Particle ); + WRITE_BYTE( pAttach->entindex() ); + WRITE_STRING( STRING(aur) ); + MESSAGE_END(); +} +//======================================================================== +// Set client beams +//======================================================================== +void UTIL_SetBeams( char *szFile, CBaseEntity *pStart, CBaseEntity *pEnd ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsg.Beams ); + WRITE_STRING( szFile ); + WRITE_BYTE( pStart->entindex()); // beam start entity + WRITE_BYTE( pEnd->entindex() ); // beam end entity + MESSAGE_END(); +} + +//======================================================================== +// Precaches and play sound +//======================================================================== +int UTIL_PrecacheSound( string_t s, const char *e ) // precache default model if not found +{ + if (FStringNull( s )) + return UTIL_PrecacheSound( e ); + return UTIL_PrecacheSound( s ); +} +int UTIL_PrecacheSound( string_t s ){ return UTIL_PrecacheSound( STRING( s )); } +int UTIL_PrecacheSound( const char* s ) +{ + if( !s || !*s ) return MAKE_STRING( "common/null.wav" ); // set null sound + if( *s == '!' ) return MAKE_STRING( s ); // sentence - just make string + + char path[256]; + sprintf( path, "sound/%s", s ); + + // check file for existing + if( FILE_EXISTS( path )) + { + g_engfuncs.pfnPrecacheSound( s ); + return MAKE_STRING( s ); + } + + if( !strcmp( UTIL_FileExtension( s ), "wav" )) + { + // this is sound + ALERT( at_warning, "sound \"%s\" not found!\n", s ); + } + else if( !strcmp( UTIL_FileExtension( s ), "ogg" )) + { + // this is sound + ALERT( at_warning, "sound \"%s\" not found!\n", s ); + } + else + { + // unknown format + ALERT( at_error, "invalid name \"%s\"!\n", s ); + } + + g_engfuncs.pfnPrecacheSound("common/null.wav"); + return MAKE_STRING( "common/null.wav" ); +} + +int UTIL_LoadSoundPreset( string_t pString ) { return UTIL_LoadSoundPreset( STRING( pString )); } +int UTIL_LoadSoundPreset( const char *pString ) +{ + // try to load direct sound path + // so we supported 99 presets... + int m_sound, namelen = strlen( pString ) - 1; + + if( namelen > 2 ) // yes, it's sound path + m_sound = ALLOC_STRING( pString ); + else if( pString[0] == '!' ) // sentence + m_sound = ALLOC_STRING( pString ); + else m_sound = atoi( pString ); // no, it's preset + return m_sound; +} + +int UTIL_LoadDecalPreset( string_t pString ) { return UTIL_LoadDecalPreset( STRING( pString )); } +int UTIL_LoadDecalPreset( const char *pString ) +{ + // try to load direct sound path + // so we supported 9 decal groups... + int m_decal, namelen = strlen(pString) - 1; + + if( namelen > 1 ) // yes, it's decal name + m_decal = ALLOC_STRING( pString ); + else m_decal = atoi( pString ); // no, it's preset + return m_decal; +} + +float UTIL_CalcDistance( Vector vecAngles ) +{ + for(int i = 0; i < 3; i++ ) + { + if(vecAngles[i] < -180) vecAngles[i] += 360; + else if(vecAngles[i] > 180) vecAngles[i] -= 360; + } + return vecAngles.Length(); +} + +//======================================================================== +// Watches for pWatching enetity and rotate pWatcher entity angles +//======================================================================== +void UTIL_WatchTarget( CBaseEntity *pWatcher, CBaseEntity *pTarget) +{ + Vector vecGoal = UTIL_VecToAngles( (pTarget->pev->origin - pWatcher->pev->origin).Normalize() ); + vecGoal.x = -vecGoal.x; + + if (pWatcher->pev->angles.y > 360) pWatcher->pev->angles.y -= 360; + if (pWatcher->pev->angles.y < 0) pWatcher->pev->angles.y += 360; + + float dx = vecGoal.x - pWatcher->pev->angles.x; + float dy = vecGoal.y - pWatcher->pev->angles.y; + + if (dx < -180) dx += 360; + if (dx > 180) dx = dx - 360; + + if (dy < -180) dy += 360; + if (dy > 180) dy = dy - 360; + + pWatcher->pev->avelocity.x = dx * pWatcher->pev->speed * gpGlobals->frametime; + pWatcher->pev->avelocity.y = dy * pWatcher->pev->speed * gpGlobals->frametime; +} + +float UTIL_Approach( 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; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + + while ( delta < -180 ) + delta += 360; + while ( delta > 180 ) + delta -= 360; + + return delta; +} + +BOOL UTIL_EntIsVisible( entvars_t* pev, entvars_t* pevTarget) +{ + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine( vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr ); + + // FIXME: rewrote this relationship + if( tr.iContents & MASK_WATER ) + return FALSE; // sight line crossed contents + if( tr.flFraction == 1 ) return TRUE; + return FALSE; +} + +//======================================================================== +// Place all system resourses here +//======================================================================== +void UTIL_PrecacheResourse( void ) +{ + // null and errors stuff + g_sModelIndexErrorModel = UTIL_PrecacheModel("models/common/error.mdl");//last crash point + g_sModelIndexErrorSprite = UTIL_PrecacheModel("sprites/error.spr"); + g_sModelIndexNullModel = UTIL_PrecacheModel("models/common/null.mdl"); + g_sModelIndexNullSprite = UTIL_PrecacheModel("sprites/null.spr"); + + // global sprites and models + g_sModelIndexFireball = UTIL_PrecacheModel ("sprites/explode.spr");// fireball + g_sModelIndexWExplosion = UTIL_PrecacheModel ("sprites/wxplode.spr");// underwater fireball + g_sModelIndexSmoke = UTIL_PrecacheModel ("sprites/steam1.spr");// smoke + g_sModelIndexBubbles = UTIL_PrecacheModel ("sprites/bubble.spr");//bubbles + g_sModelIndexLaser = UTIL_PrecacheModel( "sprites/laserbeam.spr" ); + g_sModelIndexBloodSpray = UTIL_PrecacheModel ("sprites/bloodspray.spr"); + g_sModelIndexBloodDrop = UTIL_PrecacheModel ("sprites/blood.spr"); + + // player items and weapons + memset( CBasePlayerWeapon::ItemInfoArray, 0, sizeof( CBasePlayerWeapon::ItemInfoArray )); + memset( CBasePlayerWeapon::AmmoInfoArray, 0, sizeof( CBasePlayerWeapon::AmmoInfoArray )); + giAmmoIndex = 0; + + // custom precaches + char token[256]; + char *pfile = (char *)LOAD_FILE( "scripts/precache.txt", NULL ); + if( pfile ) + { + char *afile = pfile; + while( pfile ) + { + if( !stricmp( token, "entity" )) + { + pfile = COM_ParseFile( pfile, token ); + UTIL_PrecacheEntity( ALLOC_STRING( token )); + } + else if( !stricmp( token, "dmentity" )) + { + pfile = COM_ParseFile( pfile, token ); + if( IsDeatchmatch()) UTIL_PrecacheEntity( ALLOC_STRING( token )); + } + else if( !stricmp( token, "model" )) + { + pfile = COM_ParseFile( pfile, token ); + UTIL_PrecacheModel( ALLOC_STRING( token )); + } + else if( !stricmp( token, "dmmodel" )) + { + pfile = COM_ParseFile( pfile, token ); + if( IsDeatchmatch()) UTIL_PrecacheModel( ALLOC_STRING( token )); + } + else if( !stricmp( token, "sound" )) + { + pfile = COM_ParseFile( pfile, token ); + UTIL_PrecacheSound( ALLOC_STRING( token )); + } + else if( !stricmp( token, "dmsound" )) + { + pfile = COM_ParseFile( pfile, token ); + if( IsDeatchmatch()) UTIL_PrecacheSound( ALLOC_STRING( token )); + } + else if( !stricmp( token, "aurora" )) + { + pfile = COM_ParseFile( pfile, token ); + UTIL_PrecacheAurora( ALLOC_STRING( token )); + } + pfile = COM_ParseFile( pfile, token ); + } + COM_FreeFile( afile ); + } +} + +BOOL IsMultiplayer( void ) +{ + if( g_pGameRules->IsMultiplayer()) + return TRUE; + return FALSE; +} + +BOOL IsDeatchmatch( void ) +{ + if( g_pGameRules->IsDeathmatch() ) + return TRUE; + return FALSE; +} + +float UTIL_WeaponTimeBase( void ) +{ + return gpGlobals->time; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if( pev->pContainingEntity != NULL ) + return pev->pContainingEntity; + + ALERT( at_console, "entvars_t pContainingEntity is NULL, calling into engine" ); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + + if( pent == NULL ) ALERT( at_console, "DAMN! Even the engine couldn't FindEntityByVars!" ); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG +void DBG_AssertFunction( BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ) +{ + if( fExpr ) return; + + char szOut[512]; + if( szMessage != NULL ) + sprintf( szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage ); + else sprintf( szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine ); + HOST_ERROR( szOut ); +} +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerWeapon *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +//LRC - pass in a normalised axis vector and a number of degrees, and this returns the corresponding +// angles value for an entity. +inline Vector UTIL_AxisRotationToAngles( const Vector &vecAxis, float flDegs ) +{ + Vector vecTemp = UTIL_AxisRotationToVec( vecAxis, flDegs ); + float rgflVecOut[3]; + //ugh, mathsy. + rgflVecOut[0] = asin(vecTemp.z) * (-180.0 / M_PI); + rgflVecOut[1] = acos(vecTemp.x) * (180.0 / M_PI); + if (vecTemp.y < 0) + rgflVecOut[1] = -rgflVecOut[1]; + rgflVecOut[2] = 0; //for now + return Vector(rgflVecOut); +} + +//LRC - as above, but returns the position of point 1 0 0 under the given rotation +Vector UTIL_AxisRotationToVec( const Vector &vecAxis, float flDegs ) +{ + float rgflVecOut[3]; + float flRads = flDegs * (M_PI / 180.0); + float c = cos(flRads); + float s = sin(flRads); + float v = vecAxis.x * (1-c); + //ugh, more maths. Thank goodness for internet geometry sites... + rgflVecOut[0] = vecAxis.x*v + c; + rgflVecOut[1] = vecAxis.y*v + vecAxis.z*s; + rgflVecOut[2] = vecAxis.z*v - vecAxis.y*s; + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBaseEntity *UTIL_FindPlayerInSphere( const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + while(!FNullEnt(pentEntity)) + { + if(pentEntity->v.flags & FL_CLIENT) break; //found player + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + } + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + +CBasePlayer *UTIL_FindPlayerInPVS( edict_t *pent ) +{ + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pent ); + CBasePlayer *pPlayer = NULL; + + if(!FNullEnt(pentPlayer)) //get pointer to player + pPlayer = GetClassPtr((CBasePlayer *)VARS(pentPlayer)); + return pPlayer; +} + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + CBaseEntity *pEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + for (;;) + { + // Don't change this to use UTIL_FindEntityByString! + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + // if pentEntity (the edict) is null, we're at the end of the entities. Give up. + if (FNullEnt(pentEntity)) + { + return NULL; + } + else + { + // ...but if only pEntity (the classptr) is null, we've just got one dud, so we try again. + pEntity = CBaseEntity::Instance(pentEntity); + if (pEntity) + return pEntity; + } + } +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pActivator ) +{ + return UTIL_FindEntityByTargetname( pStartEntity, szName ); +} + +CBaseEntity *UTIL_FindEntityByTarget( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "target", szName ); +} + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +//LRC UNDONE: Work during trigger_camera? +//----------------------------------------------------------------------------- +// Compute shake amplitude +//----------------------------------------------------------------------------- +float ComputeShakeAmplitude( const Vector ¢er, const Vector &shakePt, float amplitude, float radius ) +{ + if( radius <= 0 ) + return amplitude; + + float localAmplitude = -1; + Vector delta = center - shakePt; + float distance = delta.Length(); + + if( distance <= radius ) + { + // make the amplitude fall off over distance + float flPerc = 1.0 - (distance / radius); + localAmplitude = amplitude * flPerc; + } + + return localAmplitude; +} + +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius, ShakeCommand_t eCommand, BOOL bAirShake ) +{ + int i; + float localAmplitude; + + for( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + // + // Only shake players that are on the ground. + // + if( !pPlayer || (!bAirShake && !FBitSet( pPlayer->pev->flags, FL_ONGROUND ))) + { + continue; + } + + localAmplitude = ComputeShakeAmplitude( center, pPlayer->pev->origin, amplitude, radius ); + + // This happens if the player is outside the radius, in which case we should ignore + // all commands + if( localAmplitude < 0 ) continue; + + if (( localAmplitude > 0 ) || ( eCommand == SHAKE_STOP )) + { + if ( eCommand == SHAKE_STOP ) localAmplitude = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsg.Shake, NULL, pPlayer->edict() ); + WRITE_BYTE( eCommand ); // shake command (SHAKE_START, STOP, FREQUENCY, AMPLITUDE) + WRITE_FLOAT( localAmplitude );// shake magnitude/amplitude + WRITE_FLOAT( frequency ); // shake noise frequency + WRITE_FLOAT( duration ); // shake lasts this long + MESSAGE_END(); + } + } +} + +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, radius, SHAKE_START, FALSE ); +} + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0, SHAKE_START, FALSE ); +} + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + if(IsMultiplayer()) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + UTIL_ScreenFade( color, fadeTime, fadeHold, alpha, flags, i ); + } + else UTIL_ScreenFade( color, fadeTime, fadeHold, alpha, flags ); +} + + +void UTIL_ScreenFade( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags, int playernum ) +{ + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex( playernum ); + if ( pPlayer ) + { + if(flags & FFADE_CUSTOMVIEW) + { + if(pPlayer->viewFlags == 0) return; + ClearBits( flags, FFADE_CUSTOMVIEW ); //don't send this flag to engine!!! + } + if(flags & FFADE_OUT && fadeHold == 0) SetBits( flags, FFADE_STAYOUT ); + else ClearBits( flags, FFADE_STAYOUT ); + + pPlayer->m_FadeColor = color; + pPlayer->m_FadeAlpha = alpha; + pPlayer->m_iFadeFlags = flags; + pPlayer->m_iFadeTime = fadeTime; + pPlayer->m_iFadeHold = fadeHold; + pPlayer->fadeNeedsUpdate = TRUE; + } +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsg.TempEntity, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsg.TextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsg.TextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsg.SayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsg.SayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsg.HudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + // loop through all players + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) UTIL_ShowMessage( pString, pPlayer ); + } +} +void UTIL_SetFogAll( Vector color, int iFadeTime, int iStartDist, int iEndDist ) +{ + // loop through all players + + if(IsMultiplayer()) + { + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + UTIL_SetFog( color, iFadeTime, iStartDist, iEndDist, i ); + } + else UTIL_SetFog( color, iFadeTime, iStartDist, iEndDist ); +} + +void UTIL_SetFog( Vector color, int iFadeTime, int iStartDist, int iEndDist, int playernum ) +{ + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex( playernum ); + if ( pPlayer ) + { + if(pPlayer->m_FogFadeTime != 0) return;//fading in progress !!!TODO: make smooth re-fading + if(IsMultiplayer()) iFadeTime = 0; //disable fading in multiplayer + if( iFadeTime > 0 ) + { + pPlayer->m_iFogEndDist = FOG_LIMIT; + pPlayer->m_iFogFinalEndDist = iEndDist; + } + else if( iFadeTime < 0 ) + { + pPlayer->m_iFogEndDist = iEndDist; + pPlayer->m_iFogFinalEndDist = iEndDist; + } + else pPlayer->m_iFogEndDist = iEndDist; + pPlayer->m_iFogStartDist = iStartDist; + pPlayer->m_FogColor = color; + pPlayer->m_FogFadeTime = iFadeTime; + pPlayer->m_flStartTime = gpGlobals->time; + pPlayer->fogNeedsUpdate = TRUE; + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + Vector mins, maxs; + + switch( hullNumber ) + { + case human_hull: + mins = Vector( -16, -16, 0 ); + maxs = Vector( 16, 16, 72 ); + break; + case large_hull: + mins = Vector( -32, -32,-32 ); + maxs = Vector( 32, 32, 32 ); + break; + case head_hull: // ducked + mins = Vector( -16, -16,-18 ); + maxs = Vector( 16, 16, 18 ); + break; + case point_hull: + default: + mins = g_vecZero; + maxs = g_vecZero; + break; + } + + TRACE_HULL( vecStart, mins, maxs, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( void ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fStartStuck = gpGlobals->trace_startstuck; + tr.iContents = gpGlobals->trace_contents; + tr.iStartContents = gpGlobals->trace_start_contents; + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.pTexName = gpGlobals->trace_texture; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + +void UTIL_SetEdictOrigin( edict_t *pEdict, const Vector &vecOrigin ) +{ + SET_ORIGIN(pEdict, vecOrigin ); +} + +// 'links' the entity into the world + + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +BOOL UTIL_IsMasterTriggered(string_t iszMaster, CBaseEntity *pActivator) +{ + int i, j, found = false; + const char *szMaster; + char szBuf[80]; + CBaseEntity *pMaster; + int reverse = false; + + + if (iszMaster) + { + //Msg( "IsMasterTriggered(%s, %s \"%s\")\n", STRING(iszMaster), STRING(pActivator->pev->classname), STRING(pActivator->pev->targetname)); + szMaster = STRING(iszMaster); + if (szMaster[0] == '~') //inverse master + { + reverse = true; + szMaster++; + } + + pMaster = UTIL_FindEntityByTargetname( NULL, szMaster ); + if ( !pMaster ) + { + for (i = 0; szMaster[i]; i++) + { + if (szMaster[i] == '(') + { + for (j = i+1; szMaster[j]; j++) + { + if (szMaster[j] == ')') + { + strncpy(szBuf, szMaster+i+1, (j-i)-1); + szBuf[(j-i)-1] = 0; + pActivator = UTIL_FindEntityByTargetname( NULL, szBuf ); + found = true; + break; + } + } + if (!found) // no ) found + { + ALERT(at_error, "Missing ')' in master \"%s\"\n", szMaster); + return FALSE; + } + break; + } + } + + if( !found ) // no ( found + { + ALERT( at_console, "Master \"%s\" not found!\n", szMaster ); + return TRUE; + } + + strncpy(szBuf, szMaster, i); + szBuf[i] = 0; + pMaster = UTIL_FindEntityByTargetname( NULL, szBuf ); + } + + if( pMaster ) + { + if( reverse ) + return (pMaster->GetState( pActivator ) != STATE_ON ); + return (pMaster->GetState( pActivator ) == STATE_ON); + } + } + + // if the entity has no master (or the master is missing), just say yes. + return TRUE; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS( vec ); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models + WRITE_BYTE( color ); // color index into host_basepal + WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size + MESSAGE_END(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_DecalTrace( pTrace, DECAL_BLOOD1 + RANDOM_LONG(0,5) ); + else + UTIL_DecalTrace( pTrace, DECAL_YBLOOD1 + RANDOM_LONG(0,5) ); + } +} + + +void UTIL_DecalTrace( TraceResult *pTrace, int decalNumber ) +{ + short entityIndex; + int index; + int message; + + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +/* +============== +UTIL_PlayerDecalTrace + +A player is trying to apply his custom decal for the spray can. +Tell connected clients to display it, or use the default spray can decal +if the custom can't be loaded. +============== +*/ +void UTIL_PlayerDecalTrace( TraceResult *pTrace, int playernum, int decalNumber, BOOL bIsCustom ) +{ + int index; + + if (!bIsCustom) + { + if ( decalNumber < 0 ) + return; + + index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + } + else + index = decalNumber; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_PLAYERDECAL ); + WRITE_BYTE ( playernum ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, int decalNumber ) +{ + if ( decalNumber < 0 ) + return; + + int index = gDecals[ decalNumber ].index; + if ( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + MESSAGE_BEGIN( MSG_PAS, gmsg.TempEntity, pTrace->vecEndPos ); + WRITE_BYTE( TE_GUNSHOTDECAL ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_SHORT( (short)ENTINDEX(pTrace->pHit) ); + WRITE_BYTE( index ); + MESSAGE_END(); +} + + +void UTIL_Sparks( const Vector &position ) +{ + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +} + +void UTIL_Explode( const Vector ¢er, edict_t *pOwner, int radius, int name ) +{ + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, g_vecZero, pOwner ); + if(pExplosion) + { + if(name) pExplosion->pev->classname = name; + pExplosion->pev->dmg = radius; + pExplosion->pev->owner = pOwner; + pExplosion->pev->spawnflags |= SF_FIREONCE; //remove entity after explode + pExplosion->Use( NULL, NULL, USE_ON, 0 ); + } +} + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + +BOOL UTIL_IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->viewangles; + angle.x = 0; + + UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); + // He's facing me, he meant it + if( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so + { + return TRUE; + } + return FALSE; +} + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +//LRC - randomized vectors of the form "0 0 0 .. 1 0 0" +void UTIL_StringToRandomVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + float pAltVec[3]; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) pstr++; + if (!*pstr) break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } + else if (*pstr == '.') + { + pstr++; + if (*pstr != '.') return; + pstr++; + if (*pstr != ' ') return; + + UTIL_StringToVector(pAltVec, pstr); + + pVector[0] = RANDOM_FLOAT( pVector[0], pAltVec[0] ); + pVector[1] = RANDOM_FLOAT( pVector[1], pAltVec[1] ); + pVector[2] = RANDOM_FLOAT( pVector[2], pAltVec[2] ); + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if(!( UTIL_PointContents( midUp ) & MASK_WATER )) + return minz; + + midUp.z = maxz; + if( UTIL_PointContents( midUp ) & MASK_WATER ) + return maxz; + + float diff = maxz - minz; + while( diff > 1.0 ) + { + midUp.z = minz + diff/2.0; + if( UTIL_PointContents( midUp ) & MASK_WATER ) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + return midUp.z; +} + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, gmsg.TempEntity, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//============ +// UTIL_FileExtension +// returns file extension +//============ +const char *UTIL_FileExtension( const char *in ) +{ + const char *separator, *backslash, *colon, *dot; + + separator = strrchr( in, '/' ); + backslash = strrchr( in, '\\' ); + if( !separator || separator < backslash ) + separator = backslash; + colon = strrchr( in, ':' ); + if( !separator || separator < colon ) + separator = colon; + dot = strrchr( in, '.' ); + if( dot == NULL || (separator && ( dot < separator ))) + return ""; + return dot + 1; +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + +//for trigger_viewset +int HaveCamerasInPVS( edict_t* edict ) +{ + CBaseEntity *pViewEnt = NULL; + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEntity = UTIL_PlayerByIndex( i ); + if (!pEntity) continue; + CBasePlayer *pPlayer = (CBasePlayer *)pEntity; + if (pPlayer && pPlayer->viewFlags & 1) // custom view active + { + pViewEnt = pPlayer->pViewEnt; + if (!pViewEnt->edict())return 0; + + edict_t *view = pViewEnt->edict(); + edict_t *pent = UTIL_EntitiesInPVS( edict ); + + while ( !FNullEnt( pent ) ) + { + if (pent == view)return TRUE; + pent = pent->v.chain; + } + } + } + return 0; +} +void UTIL_SetView( int ViewEntity, int flags ) +{ + //Light version SetView + //Please don't use this in multiplayer + CBaseEntity *m_pPlayer = UTIL_PlayerByIndex( 1 ); + UTIL_SetView( m_pPlayer, ViewEntity, flags ); +} + +void UTIL_SetView( CBaseEntity *pActivator, int ViewEntity, int flags ) +{ + CBaseEntity *pViewEnt = 0; + + if(ViewEntity) + { + //try to find by targetname + pViewEnt = UTIL_FindEntityByString( NULL, "targetname", STRING(ViewEntity)); + //try to find by classname + if(FNullEnt(pViewEnt)) + pViewEnt = UTIL_FindEntityByString( NULL, "classname", STRING(ViewEntity)); + if(pViewEnt && pViewEnt->pev->flags & FL_MONSTER) flags |= MONSTER_VIEW;//detect monster view + } + UTIL_SetView( pActivator, pViewEnt, flags ); +} + +void UTIL_SetView( CBaseEntity *pActivator, CBaseEntity *pViewEnt, int flags ) +{ + ((CBasePlayer *)pActivator)->pViewEnt = pViewEnt; + ((CBasePlayer *)pActivator)->viewFlags = flags; + ((CBasePlayer *)pActivator)->viewNeedsUpdate = 1; +} \ No newline at end of file diff --git a/server/monsters/activity.h b/server/monsters/activity.h new file mode 100644 index 00000000..a6ba9a8c --- /dev/null +++ b/server/monsters/activity.h @@ -0,0 +1,132 @@ +/*** +* +* 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 ACTIVITY_H +#define ACTIVITY_H + +typedef enum +{ + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever + ACT_SPECIAL_ATTACK1,// very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, + ACT_VM_NONE, // weapon viewmodel animations + ACT_VM_DEPLOY, // deploy + ACT_VM_DEPLOY_EMPTY,// deploy empty weapon + ACT_VM_HOLSTER, // holster empty weapon + ACT_VM_HOLSTER_EMPTY, + ACT_VM_IDLE1, + ACT_VM_IDLE2, + ACT_VM_IDLE3, + ACT_VM_RANGE_ATTACK1, + ACT_VM_RANGE_ATTACK2, + ACT_VM_RANGE_ATTACK3, + ACT_VM_MELEE_ATTACK1, + ACT_VM_MELEE_ATTACK2, + ACT_VM_MELEE_ATTACK3, + ACT_VM_SHOOT_EMPTY, + ACT_VM_START_RELOAD, + ACT_VM_RELOAD, + ACT_VM_RELOAD_EMPTY, + ACT_VM_TURNON, + ACT_VM_TURNOFF, + ACT_VM_PUMP, // pumping gun + ACT_VM_PUMP_EMPTY, + ACT_VM_START_CHARGE, + ACT_VM_CHARGE, + ACT_VM_OVERLOAD, + ACT_VM_IDLE_EMPTY, +} Activity; + +// studio activity map conversion +typedef struct { int type; char *name; } activity_map_t; + +extern activity_map_t activity_map[]; + + +#endif//ACTIVITY_H \ No newline at end of file diff --git a/server/monsters/activitymap.h b/server/monsters/activitymap.h new file mode 100644 index 00000000..af156fde --- /dev/null +++ b/server/monsters/activitymap.h @@ -0,0 +1,121 @@ +/*** +* +* 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. +* +****/ + +activity_map_t activity_map[] = +{ +{ACT_IDLE, "ACT_IDLE" }, +{ACT_GUARD, "ACT_GUARD" }, +{ACT_WALK, "ACT_WALK" }, +{ACT_RUN, "ACT_RUN" }, +{ACT_FLY, "ACT_FLY" }, +{ACT_SWIM, "ACT_SWIM", }, +{ACT_HOP, "ACT_HOP", }, +{ACT_LEAP, "ACT_LEAP" }, +{ACT_FALL, "ACT_FALL" }, +{ACT_LAND, "ACT_LAND" }, +{ACT_STRAFE_LEFT, "ACT_STRAFE_LEFT" }, +{ACT_STRAFE_RIGHT, "ACT_STRAFE_RIGHT" }, +{ACT_ROLL_LEFT, "ACT_ROLL_LEFT" }, +{ACT_ROLL_RIGHT, "ACT_ROLL_RIGHT" }, +{ACT_TURN_LEFT, "ACT_TURN_LEFT" }, +{ACT_TURN_RIGHT, "ACT_TURN_RIGHT" }, +{ACT_CROUCH, "ACT_CROUCH" }, +{ACT_CROUCHIDLE, "ACT_CROUCHIDLE" }, +{ACT_STAND, "ACT_STAND" }, +{ACT_USE, "ACT_USE" }, +{ACT_SIGNAL1, "ACT_SIGNAL1" }, +{ACT_SIGNAL2, "ACT_SIGNAL2" }, +{ACT_SIGNAL3, "ACT_SIGNAL3" }, +{ACT_TWITCH, "ACT_TWITCH" }, +{ACT_COWER, "ACT_COWER" }, +{ACT_SMALL_FLINCH, "ACT_SMALL_FLINCH" }, +{ACT_BIG_FLINCH, "ACT_BIG_FLINCH" }, +{ACT_RANGE_ATTACK1, "ACT_RANGE_ATTACK1" }, +{ACT_RANGE_ATTACK2, "ACT_RANGE_ATTACK2" }, +{ACT_MELEE_ATTACK1, "ACT_MELEE_ATTACK1" }, +{ACT_MELEE_ATTACK2, "ACT_MELEE_ATTACK2" }, +{ACT_RELOAD, "ACT_RELOAD" }, +{ACT_ARM, "ACT_ARM" }, +{ACT_DISARM, "ACT_DISARM" }, +{ACT_EAT, "ACT_EAT" }, +{ACT_DIESIMPLE, "ACT_DIESIMPLE" }, +{ACT_DIEBACKWARD, "ACT_DIEBACKWARD" }, +{ACT_DIEFORWARD, "ACT_DIEFORWARD" }, +{ACT_DIEVIOLENT, "ACT_DIEVIOLENT" }, +{ACT_BARNACLE_HIT, "ACT_BARNACLE_HIT" }, +{ACT_BARNACLE_PULL, "ACT_BARNACLE_PULL" }, +{ACT_BARNACLE_CHOMP, "ACT_BARNACLE_CHOMP" }, +{ACT_BARNACLE_CHEW, "ACT_BARNACLE_CHEW" }, +{ACT_SLEEP, "ACT_SLEEP" }, +{ACT_INSPECT_FLOOR, "ACT_INSPECT_FLOOR" }, +{ACT_INSPECT_WALL, "ACT_INSPECT_WALL" }, +{ACT_IDLE_ANGRY, "ACT_IDLE_ANGRY" }, +{ACT_WALK_HURT, "ACT_WALK_HURT" }, +{ACT_RUN_HURT, "ACT_RUN_HURT" }, +{ACT_HOVER, "ACT_HOVER" }, +{ACT_GLIDE, "ACT_GLIDE" }, +{ACT_FLY_LEFT, "ACT_FLY_LEFT" }, +{ACT_FLY_RIGHT, "ACT_FLY_RIGHT" }, +{ACT_DETECT_SCENT, "ACT_DETECT_SCENT" }, +{ACT_SNIFF, "ACT_SNIFF" }, +{ACT_BITE, "ACT_BITE" }, +{ACT_THREAT_DISPLAY, "ACT_THREAT_DISPLAY" }, +{ACT_FEAR_DISPLAY, "ACT_FEAR_DISPLAY" }, +{ACT_EXCITED, "ACT_EXCITED" }, +{ACT_SPECIAL_ATTACK1, "ACT_SPECIAL_ATTACK1" }, +{ACT_SPECIAL_ATTACK2, "ACT_SPECIAL_ATTACK2" }, +{ACT_COMBAT_IDLE, "ACT_COMBAT_IDLE" }, +{ACT_WALK_SCARED, "ACT_WALK_SCARED" }, +{ACT_RUN_SCARED, "ACT_RUN_SCARED" }, +{ACT_VICTORY_DANCE, "ACT_VICTORY_DANCE" }, +{ACT_DIE_HEADSHOT, "ACT_DIE_HEADSHOT" }, +{ACT_DIE_CHESTSHOT, "ACT_DIE_CHESTSHOT" }, +{ACT_DIE_GUTSHOT, "ACT_DIE_GUTSHOT" }, +{ACT_DIE_BACKSHOT, "ACT_DIE_BACKSHOT" }, +{ACT_FLINCH_HEAD, "ACT_FLINCH_HEAD" }, +{ACT_FLINCH_CHEST, "ACT_FLINCH_CHEST" }, +{ACT_FLINCH_STOMACH, "ACT_FLINCH_STOMACH" }, +{ACT_FLINCH_LEFTARM, "ACT_FLINCH_LEFTARM" }, +{ACT_FLINCH_RIGHTARM, "ACT_FLINCH_RIGHTARM" }, +{ACT_FLINCH_LEFTLEG, "ACT_FLINCH_LEFTLEG" }, +{ACT_FLINCH_RIGHTLEG, "ACT_FLINCH_RIGHTLEG" }, +{ACT_VM_NONE, "ACT_VM_NONE" }, // invalid animation +{ACT_VM_DEPLOY, "ACT_VM_DEPLOY" }, // deploy +{ACT_VM_DEPLOY_EMPTY, "ACT_VM_DEPLOY_EMPTY" }, // deploy empty weapon +{ACT_VM_HOLSTER, "ACT_VM_HOLSTER" }, // holster empty weapon +{ACT_VM_HOLSTER_EMPTY, "ACT_VM_HOLSTER_EMPTY" }, +{ACT_VM_IDLE1, "ACT_VM_IDLE1" }, +{ACT_VM_IDLE2, "ACT_VM_IDLE2" }, +{ACT_VM_IDLE3, "ACT_VM_IDLE3" }, +{ACT_VM_RANGE_ATTACK1, "ACT_VM_RANGE_ATTACK1" }, +{ACT_VM_RANGE_ATTACK2, "ACT_VM_RANGE_ATTACK2" }, +{ACT_VM_RANGE_ATTACK3, "ACT_VM_RANGE_ATTACK3" }, +{ACT_VM_MELEE_ATTACK1, "ACT_VM_MELEE_ATTACK1" }, +{ACT_VM_MELEE_ATTACK2, "ACT_VM_MELEE_ATTACK2" }, +{ACT_VM_MELEE_ATTACK3, "ACT_VM_MELEE_ATTACK3" }, +{ACT_VM_SHOOT_EMPTY, "ACT_VM_SHOOT_EMPTY" }, +{ACT_VM_START_RELOAD, "ACT_VM_START_RELOAD" }, +{ACT_VM_RELOAD, "ACT_VM_RELOAD" }, +{ACT_VM_RELOAD_EMPTY, "ACT_VM_RELOAD_EMPTY" }, +{ACT_VM_TURNON, "ACT_VM_TURNON" }, +{ACT_VM_TURNOFF, "ACT_VM_TURNOFF" }, +{ACT_VM_PUMP, "ACT_VM_PUMP" }, // user animations +{ACT_VM_PUMP_EMPTY, "ACT_VM_PUMP_EMPTY" }, +{ACT_VM_START_CHARGE, "ACT_VM_START_CHARGE" }, +{ACT_VM_CHARGE, "ACT_VM_CHARGE" }, +{ACT_VM_OVERLOAD, "ACT_VM_OVERLOAD" }, +{ACT_VM_IDLE_EMPTY, "ACT_VM_IDLE_EMPTY" }, +{0, NULL }, +}; diff --git a/server/monsters/ai_sound.cpp b/server/monsters/ai_sound.cpp new file mode 100644 index 00000000..88577ff6 --- /dev/null +++ b/server/monsters/ai_sound.cpp @@ -0,0 +1,379 @@ +/*** +* +* 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. +* +****/ +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + SetNextThink( 1 ); +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + SetNextThink( 0.3 );// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_console, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT( at_console, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT( at_console, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if( iIndex > ( MAX_WORLD_SOUNDS - 1 )) + { + ALERT( at_console, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT( at_console, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT( at_console, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} diff --git a/server/monsters/animating.cpp b/server/monsters/animating.cpp new file mode 100644 index 00000000..cfdb9250 --- /dev/null +++ b/server/monsters/animating.cpp @@ -0,0 +1,342 @@ +/*** +* +* 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. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseLogic ); + + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +float CBaseAnimating :: SequenceDuration( int iSequence ) +{ + dstudiohdr_t *pstudiohdr; + void *pmodel = GET_MODEL_PTR(ENT(pev)); + pstudiohdr = (dstudiohdr_t *)pmodel; + + if (!pstudiohdr) + return 0; + + dstudioseqdesc_t *pseqdesc; + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + iSequence; + return (float)pseqdesc->numframes/(float)pseqdesc->fps; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivity( pmodel, pev, activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupActivityHeaviest( pmodel, pev, activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupSequence( pmodel, label ); +} + + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetSequenceInfo( pmodel, pev, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + + + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetSequenceFlags( pmodel, pev ); +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return SetController( pmodel, pev, iController, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev, 0, 0.0 ); + SetController( pmodel, pev, 1, 0.0 ); + SetController( pmodel, pev, 2, 0.0 ); + SetController( pmodel, pev, 3, 0.0 ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::SetBlending( pmodel, pev, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); + return TRUE; +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GET_MODEL_PTR( ENT(pev) ), pev, iGroup ); +} + +int CBaseAnimating :: GetBoneCount( void ) +{ + return ::GetBoneCount( GET_MODEL_PTR(ENT(pev)) ); +} + +void CBaseAnimating :: SetBones( float (*data)[3], int datasize ) +{ + ::SetBones( GET_MODEL_PTR( ENT(pev) ), data, datasize ); +} + +int CBaseAnimating :: ExtractBbox( int sequence, float *mins, float *maxs ) +{ + return ::ExtractBbox( GET_MODEL_PTR( ENT(pev) ), sequence, mins, maxs ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + if (transformed.x < rmin.x) + rmin.x = transformed.x; + if (transformed.x > rmax.x) + rmax.x = transformed.x; + if (transformed.y < rmin.y) + rmin.y = transformed.y; + if (transformed.y > rmax.y) + rmax.y = transformed.y; + if (transformed.z < rmin.z) + rmin.z = transformed.z; + if (transformed.z > rmax.z) + rmax.z = transformed.z; + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/server/monsters/animation.cpp b/server/monsters/animation.cpp new file mode 100644 index 00000000..a2c595a4 --- /dev/null +++ b/server/monsters/animation.cpp @@ -0,0 +1,562 @@ +/*** +* +* 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. +* +****/ +#include +#include +#include + +#include "extdll.h" +#include "utils.h" +#include "const.h" +#include "studio_ref.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#include "activitymap.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +extern globalvars_t *gpGlobals; + +#pragma warning( disable : 4244 ) + + + +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + dstudioseqdesc_t *pseqdesc; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins[0] = pseqdesc[ sequence ].bbmin[0]; + mins[1] = pseqdesc[ sequence ].bbmin[1]; + mins[2] = pseqdesc[ sequence ].bbmin[2]; + + maxs[0] = pseqdesc[ sequence ].bbmax[0]; + maxs[1] = pseqdesc[ sequence ].bbmax[1]; + maxs[2] = pseqdesc[ sequence ].bbmax[2]; + + return 1; +} + + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + dstudioseqdesc_t *pseqdesc; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + weighttotal += pseqdesc[i].actweight; + if (!weighttotal || RANDOM_LONG(0,weighttotal-1) < pseqdesc[i].actweight) + seq = i; + } + } + + return seq; +} + + +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if ( !pstudiohdr ) + return 0; + + dstudioseqdesc_t *pseqdesc; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].activity == activity) + { + if ( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +void GetEyePosition ( void *pmodel, float *vecEyePosition ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + + if ( !pstudiohdr ) + { + ALERT ( at_console, "GetEyePosition() Can't get pstudiohdr ptr!\n" ); + return; + } + + vecEyePosition = pstudiohdr->eyeposition; +} + +int LookupSequence( void *pmodel, const char *label ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + dstudioseqdesc_t *pseqdesc; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for (int i = 0; i < pstudiohdr->numseq; i++) + { + if (stricmp( pseqdesc[i].label, label ) == 0) + return i; + } + + return -1; +} + + +int IsSoundEvent( int eventNumber ) +{ + if ( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + if ( index >= 0 ) + { + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if ( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + dstudioseqdesc_t *pseqdesc; + dstudioevent_t *pevent; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (dstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for (int i = 0; i < pseqdesc->numevents; i++) + { + // Don't send client-side events to the server AI + if ( pevent[i].event >= EVENT_CLIENT ) + continue; + + // UNDONE: Add a callback to check to see if a sound is precached yet and don't allocate a copy + // of it's name if it is. + if ( IsSoundEvent( pevent[i].event ) ) + { + if ( !strlen(pevent[i].options) ) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( STRING( ALLOC_STRING(pevent[i].options)) ); + } + } + } +} + + + +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + dstudioseqdesc_t *pseqdesc; + + if (pev->sequence >= pstudiohdr->numseq) + { + *pflFrameRate = 0.0; + *pflGroundSpeed = 0.0; + return; + } + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + + +int GetSequenceFlags( void *pmodel, entvars_t *pev ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq ) + return 0; + + dstudioseqdesc_t *pseqdesc; + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + return pseqdesc->flags; +} + + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if ( !pstudiohdr || pev->sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + dstudioseqdesc_t *pseqdesc; + dstudioevent_t *pevent; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + pevent = (dstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if (pseqdesc->numframes > 1) + { + flStart *= (pseqdesc->numframes - 1) / 256.0; + flEnd *= (pseqdesc->numframes - 1) / 256.0; + } + else + { + flStart = 0; + flEnd = 1.0; + } + + for (; index < pseqdesc->numevents; index++) + { + // Don't send client-side events to the server AI + if ( pevent[index].event >= EVENT_CLIENT ) + continue; + + if ( (pevent[index].frame >= flStart && pevent[index].frame < flEnd) || + ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + return 0; +} + +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + dstudiobonecontroller_t *pbonecontroller = (dstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + int i = 0; + for (i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + pev->controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return flValue; + + dstudioseqdesc_t *pseqdesc; + + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + (int)pev->sequence; + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + pev->blending[iBlender] = setting; + + return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + + + + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return iGoalAnim; + + dstudioseqdesc_t *pseqdesc; + pseqdesc = (dstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if (pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if (*piDir > 0) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if (iEndNode == pseqdesc[iGoalAnim].entrynode) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if (iInternNode == 0) + return iGoalAnim; + + int i; + + // look for someone going + for (i = 0; i < pstudiohdr->numseq; i++) + { + if (pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode) + { + *piDir = 1; + return i; + } + if (pseqdesc[i].nodeflags) + { + if (pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph\n" ); + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return; + + if (iGroup > pstudiohdr->numbodyparts) + return; + + dstudiobodyparts_t *pbodypart = (dstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (iValue >= pbodypart->nummodels) + return; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + pev->body = (pev->body - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + + +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (! pstudiohdr) + return 0; + + if (iGroup > pstudiohdr->numbodyparts) + return 0; + + dstudiobodyparts_t *pbodypart = (dstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if (pbodypart->nummodels <= 1) + return 0; + + int iCurrent = (pev->body / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} + +//LRC +int GetBoneCount( void *pmodel ) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (!pstudiohdr) + { + ALERT(at_error, "Bad header in SetBones!\n"); + return 0; + } + + return pstudiohdr->numbones; +} + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +//LRC +void SetBones( void *pmodel, float (*data)[3], int datasize) +{ + dstudiohdr_t *pstudiohdr; + + pstudiohdr = (dstudiohdr_t *)pmodel; + if (!pstudiohdr) + { + ALERT(at_error, "Bad header in SetBones!\n"); + return; + } + + dstudiobone_t *pbone = (dstudiobone_t *)((byte *)pstudiohdr + pstudiohdr->boneindex); + +// ALERT(at_console, "List begins:\n"); + int j; + int limit = min(pstudiohdr->numbones, datasize); + // go through the bones + for (int i = 0; i < limit; i++, pbone++) + { +// ALERT(at_console, " %s\n", pbone->name); + for (j = 0; j < 3; j++) + pbone->value[j] = data[i][j]; + } +// ALERT(at_console, "List ends.\n"); +} diff --git a/server/monsters/animation.h b/server/monsters/animation.h new file mode 100644 index 00000000..cf90952a --- /dev/null +++ b/server/monsters/animation.h @@ -0,0 +1,52 @@ +/*** +* +* 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 ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, entvars_t *pev, int activity ); +int LookupActivityHeaviest( void *pmodel, entvars_t *pev, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, entvars_t *pev, float *pflFrameRate, float *pflGroundSpeed ); +int GetSequenceFlags( void *pmodel, entvars_t *pev ); +int LookupAnimationEvents( void *pmodel, entvars_t *pev, float flStart, float flEnd ); +float SetController( void *pmodel, entvars_t *pev, int iController, float flValue ); +float SetBlending( void *pmodel, entvars_t *pev, int iBlender, float flValue ); +void GetEyePosition( void *pmodel, float *vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, entvars_t *pev, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, entvars_t *pev, int iGroup ); + +//LRC +void SetBones( void *pmodel, float (*data)[3], int datasize ); +int GetBoneCount( void *pmodel ); +int GetSequenceFrames( void *pmodel, entvars_t *pev ); //LRC + +int GetAnimationEvent( void *pmodel, entvars_t *pev, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, float *mins, float *maxs ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/server/monsters/apache.cpp b/server/monsters/apache.cpp new file mode 100644 index 00000000..d0161bdf --- /dev/null +++ b/server/monsters/apache.cpp @@ -0,0 +1,942 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "baseweapon.h" +#include "nodes.h" +#include "basebeams.h" +#include "defaults.h" + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +class CApache : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -300, -300, -172); + pev->absmax = pev->origin + Vector(300, 300, 8); + } + + void EXPORT HuntThink( void ); + void EXPORT FlyTouch( CBaseEntity *pOther ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + + void ShowDamage( void ); + void Flight( void ); + void FireRocket( void ); + BOOL FireGun( void ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + int m_iRockets; + float m_flForce; + float m_flNextRocket; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + Vector m_vecGoal; + + Vector m_angGun; + float m_flLastSeen; + float m_flPrevSeen; + + int m_iSoundState; // don't save this + + int m_iSpriteTexture; + int m_iExplode; + int m_iBodyGibs; + + float m_flGoalSpeed; + + int m_iDoSmokePuff; + CBeam *m_pBeam; +}; +LINK_ENTITY_TO_CLASS( monster_apache, CApache ); + +TYPEDESCRIPTION CApache::m_SaveData[] = +{ + DEFINE_FIELD( CApache, m_iRockets, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_flNextRocket, FIELD_TIME ), + DEFINE_FIELD( CApache, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_angGun, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CApache, m_flPrevSeen, FIELD_TIME ), +// DEFINE_FIELD( CApache, m_iSoundState, FIELD_INTEGER ), // Don't save, precached +// DEFINE_FIELD( CApache, m_iSpriteTexture, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iExplode, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iBodyGibs, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CApache, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_iDoSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CApache, CBaseMonster ); + + +void CApache :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/apache.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( this, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + if (pev->health == 0) + pev->health = APACHE_HEALTH; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse(&CApache :: StartupUse ); + } + else + { + SetThink(&CApache :: HuntThink ); + SetTouch(&CApache :: FlyTouch ); + SetNextThink( 1.0 ); + } + + m_iRockets = 10; +} + + +void CApache::Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/apache.mdl"); + + PRECACHE_SOUND("apache/ap_rotor1.wav"); + PRECACHE_SOUND("apache/ap_rotor2.wav"); + PRECACHE_SOUND("apache/ap_rotor3.wav"); + PRECACHE_SOUND("apache/ap_whine1.wav"); + + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); + + PRECACHE_SOUND("turret/tu_fire1.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iBodyGibs = PRECACHE_MODEL( "models/metalplategibs_green.mdl" ); + + UTIL_PrecacheEntity( "hvr_rocket" ); +} + + + +void CApache::NullThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.5 ); +} + + +void CApache::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink(&CApache:: HuntThink ); + SetTouch(&CApache:: FlyTouch ); + SetNextThink( 0.1 ); + SetUse( NULL ); +} + +void CApache :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink(&CApache :: DyingThink ); + SetTouch(&CApache :: CrashTouch ); + SetNextThink( 0.1 ); + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + if (pev->spawnflags & SF_NOWRECKAGE) + { + m_flNextRocket = gpGlobals->time + 4.0; + } + else + { + m_flNextRocket = gpGlobals->time + 15.0; + } +} + +void CApache :: DyingThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flNextRocket > gpGlobals->time ) + { + // random explosions + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 4 ); // let client decide + + // duration + WRITE_BYTE( 30 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + SetNextThink( 0.2 ); + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + */ + + // fireball + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 256 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 120 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + // big smoke + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 5 ); // framerate + MESSAGE_END(); + + // blast circle + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + if (pev->flags & FL_ONGROUND) + { + CBaseEntity *pWreckage = Create( "smokeent", pev->origin, pev->angles ); + UTIL_SetModel( ENT(pWreckage->pev), pev->model ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->SetNextThink( 0.1 ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->impulse = 50; + pWreckage->pev->dmgtime = gpGlobals->time + 5; + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 200 ); + + // randomization + WRITE_BYTE( 30 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 200 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + SetThink( Remove ); + SetNextThink( 0.1 ); + } +} + + +void CApache::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CApache::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_flNextRocket = gpGlobals->time; + SetNextThink( 0 ); + } +} + + + +void CApache :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + +void CApache :: HuntThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + ShowDamage( ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + } + } + + // if (m_hEnemy == NULL) + { + Look( 4092 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // generic speed up + if (m_flGoalSpeed < 800) + m_flGoalSpeed += 5; + + if (m_hEnemy != NULL) + { + // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->Center( ); + } + else + { + m_hEnemy = NULL; + } + } + + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + + float flLength = (pev->origin - m_posDesired).Length(); + + if (m_pGoalEnt) + { + // ALERT( at_console, "%.0f\n", flLength ); + + if (flLength < 128) + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + flLength = (pev->origin - m_posDesired).Length(); + } + } + } + else + { + m_posDesired = pev->origin; + } + + if (flLength > 250) // 500 + { + // float flLength2 = (m_posTarget - pev->origin).Length() * (1.5 - DotProduct((m_posTarget - pev->origin).Normalize(), pev->velocity.Normalize() )); + // if (flLength2 < flLength) + if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) + { + m_vecDesired = (m_posTarget - pev->origin).Normalize( ); + } + else + { + m_vecDesired = (m_posDesired - pev->origin).Normalize( ); + } + } + else + { + m_vecDesired = m_vecGoal; + } + + Flight( ); + + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); + if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) + { + if (FireGun( )) + { + // slow down if we're fireing + if (m_flGoalSpeed > 400) + m_flGoalSpeed = 400; + } + + } + + UTIL_MakeAimVectors( pev->angles ); + Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); + // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); + + if ((m_iRockets % 2) == 1) + { + FireRocket( ); + m_flNextRocket = gpGlobals->time + 0.5; + if (m_iRockets <= 0) + { + m_flNextRocket = gpGlobals->time + 10; + m_iRockets = 10; + } + } + else if (pev->angles.x < 0 && DotProduct( pev->velocity, gpGlobals->v_forward ) > -100 && m_flNextRocket < gpGlobals->time) + { + if (m_flLastSeen + 60 > gpGlobals->time) + { + if (m_hEnemy != NULL) + { + // make sure it's a good shot + if (DotProduct( m_vecTarget, vecEst) > .965) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, ignore_monsters, edict(), &tr ); + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + else + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, dont_ignore_monsters, edict(), &tr ); + // just fire when close + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + } +} + + +void CApache :: Flight( void ) +{ + // tilt model 5 degrees + Vector vecAdj = Vector( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); + // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (pev->avelocity.y < 60) + { + pev->avelocity.y += 8; // 9 * (3.0/2.0); + } + } + else + { + if (pev->avelocity.y > -60) + { + pev->avelocity.y -= 8; // 9 * (3.0/2.0); + } + } + pev->avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); + Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + UTIL_MakeAimVectors( pev->angles + vecAdj); + pev->velocity.x += gpGlobals->v_up.x * m_flForce; + pev->velocity.y += gpGlobals->v_up.y * m_flForce; + pev->velocity.z += gpGlobals->v_up.z * m_flForce; + // add gravity + pev->velocity.z -= 38.4; // 32ft/sec + + + float flSpeed = pev->velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); + float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); + + // fly sideways + if (flSlip > 0) + { + if (pev->angles.z > -30 && pev->avelocity.z > -15) + pev->avelocity.z -= 4; + else + pev->avelocity.z += 2; + } + else + { + + if (pev->angles.z < 30 && pev->avelocity.z < 15) + pev->avelocity.z += 4; + else + pev->avelocity.z -= 2; + } + + // sideways drag + pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + pev->velocity = pev->velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 80 && vecEst.z < m_posDesired.z) + { + m_flForce += 12; + } + else if (m_flForce > 30) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 8; + } + + // pitch forward or back to get to target + if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && pev->angles.x + pev->avelocity.x > -40) + { + // ALERT( at_console, "F " ); + // lean forward + pev->avelocity.x -= 12.0; + } + else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) + { + // ALERT( at_console, "B " ); + // lean backward + pev->avelocity.x += 12.0; + } + else if (pev->angles.x + pev->avelocity.x > 0) + { + // ALERT( at_console, "f " ); + pev->avelocity.x -= 4.0; + } + else if (pev->angles.x + pev->avelocity.x < 0) + { + // ALERT( at_console, "b " ); + pev->avelocity.x += 4.0; + } + + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); + + // make rotor, engine sounds + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + + float pitch = DotProduct( pev->velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 50.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + if (pitch == 100) + pitch = 101; + + float flVol = (m_flForce / 100.0) + .1; + if (flVol > 1.0) + flVol = 1.0; + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + + // ALERT( at_console, "%.0f %.2f\n", pitch, flVol ); + } +} + + +void CApache :: FireRocket( void ) +{ + static float side = 1.0; + static int count; + + if (m_iRockets <= 0) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + 1.5 * (gpGlobals->v_forward * 21 + gpGlobals->v_right * 70 * side + gpGlobals->v_up * -79); + + switch( m_iRockets % 5) + { + case 0: vecSrc = vecSrc + gpGlobals->v_right * 10; break; + case 1: vecSrc = vecSrc - gpGlobals->v_right * 10; break; + case 2: vecSrc = vecSrc + gpGlobals->v_up * 10; break; + case 3: vecSrc = vecSrc - gpGlobals->v_up * 10; break; + case 4: break; + } + + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + + CBaseEntity *pRocket = CBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() ); + if (pRocket) + pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100; + + m_iRockets--; + + side = - side; +} + + + +BOOL CApache :: FireGun( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector posGun, angGun; + GetAttachment( 1, posGun, angGun ); + + Vector vecTarget = (m_posTarget - posGun).Normalize( ); + + Vector vecOut; + + vecOut.x = DotProduct( gpGlobals->v_forward, vecTarget ); + vecOut.y = -DotProduct( gpGlobals->v_right, vecTarget ); + vecOut.z = DotProduct( gpGlobals->v_up, vecTarget ); + + Vector angles = UTIL_VecToAngles (vecOut); + + angles.x = -angles.x; + if (angles.y > 180) + angles.y = angles.y - 360; + if (angles.y < -180) + angles.y = angles.y + 360; + if (angles.x > 180) + angles.x = angles.x - 360; + if (angles.x < -180) + angles.x = angles.x + 360; + + if (angles.x > m_angGun.x) + m_angGun.x = min( angles.x, m_angGun.x + 12 ); + if (angles.x < m_angGun.x) + m_angGun.x = max( angles.x, m_angGun.x - 12 ); + if (angles.y > m_angGun.y) + m_angGun.y = min( angles.y, m_angGun.y + 12 ); + if (angles.y < m_angGun.y) + m_angGun.y = max( angles.y, m_angGun.y - 12 ); + + m_angGun.y = SetBoneController( 0, m_angGun.y ); + m_angGun.x = SetBoneController( 1, m_angGun.x ); + + Vector posBarrel, angBarrel; + GetAttachment( 0, posBarrel, angBarrel ); + Vector vecGun = (posBarrel - posGun).Normalize( ); + + if (DotProduct( vecGun, vecTarget ) > 0.98) + { +#if 1 + FireBullets( 1, posGun, vecGun, VECTOR_CONE_4DEGREES, 8192, BULLET_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.3); +#else + static float flNext; + TraceResult tr; + UTIL_TraceLine( posGun, posGun + vecGun * 8192, dont_ignore_monsters, ENT( pev ), &tr ); + + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/lgtning.spr", 80 ); + m_pBeam->PointEntInit( pev->origin, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } + + if (flNext < gpGlobals->time) + { + flNext = gpGlobals->time + 0.5; + m_pBeam->SetStartPos( tr.vecEndPos ); + } +#endif + return TRUE; + } + else + { + if (m_pBeam) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + } + return FALSE; +} + + + +void CApache :: ShowDamage( void ) +{ + if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health) + { + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + if (m_iDoSmokePuff > 0) + m_iDoSmokePuff--; +} + + +int CApache :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (bitsDamageType & DMG_BLAST) + { + flDamage *= 2; + } + + /* + if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) + { + // clip bullet damage at 50 + flDamage = 50; + } + */ + + // ALERT( at_console, "%.0f\n", flDamage ); + return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + + +void CApache::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // ignore blades + if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) + return; + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + m_iDoSmokePuff = 3 + (flDamage / 5.0); + } + else + { + // do half damage in the body + // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); + UTIL_Ricochet( ptr->vecEndPos, 2.0 ); + } +} + +#endif diff --git a/server/monsters/barnacle.cpp b/server/monsters/barnacle.cpp new file mode 100644 index 00000000..5a8a3e3a --- /dev/null +++ b/server/monsters/barnacle.cpp @@ -0,0 +1,434 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// barnacle - stationary ceiling mounted 'fishing' monster +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +#define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is. +#define BARNACLE_PULL_SPEED 8 +#define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them. + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BARNACLE_AE_PUKEGIB 2 + +class CBarnacle : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + CBaseEntity *TongueTouchEnt ( float *pflLength ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void EXPORT BarnacleThink ( void ); + void EXPORT WaitTillDead ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flAltitude; + float m_flKillVictimTime; + int m_cGibs;// barnacle loads up on gibs each time it kills something. + BOOL m_fTongueExtended; + BOOL m_fLiftingPrey; + float m_flTongueAdj; +}; +LINK_ENTITY_TO_CLASS( monster_barnacle, CBarnacle ); + +TYPEDESCRIPTION CBarnacle::m_SaveData[] = +{ + DEFINE_FIELD( CBarnacle, m_flAltitude, FIELD_FLOAT ), + DEFINE_FIELD( CBarnacle, m_flKillVictimTime, FIELD_TIME ), + DEFINE_FIELD( CBarnacle, m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something. + DEFINE_FIELD( CBarnacle, m_fTongueExtended, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_fLiftingPrey, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_flTongueAdj, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarnacle, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarnacle :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarnacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNACLE_AE_PUKEGIB: + CGib::SpawnRandomGibs( this, 1, 1 ); + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarnacle :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/barnacle.mdl"); + UTIL_SetSize( pev, Vector(-16, -16, -32), Vector(16, 16, 0) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_AIM; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = EF_INVLIGHT; // take light from the ceiling + pev->health = 25; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flKillVictimTime = 0; + m_cGibs = 0; + m_fLiftingPrey = FALSE; + m_flTongueAdj = -100; + + InitBoneControllers(); + + SetActivity ( ACT_IDLE ); + + SetThink(&CBarnacle :: BarnacleThink ); + SetNextThink( 0.5 ); + + UTIL_SetOrigin ( this, pev->origin ); +} + +int CBarnacle::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_CLUB ) + { + flDamage = pev->health; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CBarnacle :: BarnacleThink ( void ) +{ + CBaseEntity *pTouchEnt; + CBaseMonster *pVictim; + float flLength; + + SetNextThink( 0.1 ); + + if ( m_hEnemy != NULL ) + { +// barnacle has prey. + + if ( !m_hEnemy->IsAlive() ) + { + // someone (maybe even the barnacle) killed the prey. Reset barnacle. + m_fLiftingPrey = FALSE;// indicate that we're not lifting prey. + m_hEnemy = NULL; + return; + } + + if ( m_fLiftingPrey ) + { + if ( m_hEnemy != NULL && m_hEnemy->pev->deadflag != DEAD_NO ) + { + // crap, someone killed the prey on the way up. + m_hEnemy = NULL; + m_fLiftingPrey = FALSE; + return; + } + + // still pulling prey. + Vector vecNewEnemyOrigin = m_hEnemy->pev->origin; + vecNewEnemyOrigin.x = pev->origin.x; + vecNewEnemyOrigin.y = pev->origin.y; + + // guess as to where their neck is + vecNewEnemyOrigin.x -= 6 * cos(m_hEnemy->pev->angles.y * M_PI/180.0); + vecNewEnemyOrigin.y -= 6 * sin(m_hEnemy->pev->angles.y * M_PI/180.0); + + m_flAltitude -= BARNACLE_PULL_SPEED; + vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED; + + if ( fabs( pev->origin.z - ( vecNewEnemyOrigin.z + m_hEnemy->pev->view_ofs.z - 8 ) ) < BARNACLE_BODY_HEIGHT ) + { + // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher + // than the bottom of the barnacle, it is assumed that the head is within barnacle's body ) + m_fLiftingPrey = FALSE; + + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_bite3.wav", 1, ATTN_NORM ); + + pVictim = m_hEnemy->MyMonsterPointer(); + + m_flKillVictimTime = gpGlobals->time + 10;// now that the victim is in place, the killing bite will be administered in 10 seconds. + + if ( pVictim ) + { + pVictim->BarnacleVictimBitten( pev ); + SetActivity ( ACT_EAT ); + } + } + + UTIL_SetOrigin ( m_hEnemy, vecNewEnemyOrigin ); + } + else + { + // prey is lifted fully into feeding position and is dangling there. + + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( m_flKillVictimTime != -1 && gpGlobals->time > m_flKillVictimTime ) + { + // kill! + if ( pVictim ) + { + pVictim->TakeDamage ( pev, pev, pVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); + m_cGibs = 3; + } + + return; + } + + // bite prey every once in a while + if ( pVictim && ( RANDOM_LONG(0,49) == 0 ) ) + { + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + + pVictim->BarnacleVictimBitten( pev ); + } + + } + } + else + { +// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue. + + // If idle and no nearby client, don't think so often + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) && !HaveCamerasInPVS( edict() ) ) + SetNextThink( RANDOM_FLOAT(1,1.5) ); // Stagger a bit to keep barnacles from thinking on the same frame + + if ( m_fSequenceFinished ) + {// this is done so barnacle will fidget. + SetActivity ( ACT_IDLE ); + m_flTongueAdj = -100; + } + + if ( m_cGibs && RANDOM_LONG(0,99) == 1 ) + { + // cough up a gib. + CGib::SpawnRandomGibs( this, 1, 1 ); + m_cGibs--; + + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + } + + pTouchEnt = TongueTouchEnt( &flLength ); + + if ( pTouchEnt != NULL && m_fTongueExtended ) + { + // tongue is fully extended, and is touching someone. + if ( pTouchEnt->FBecomeProne() ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_alert2.wav", 1, ATTN_NORM ); + + SetSequenceByName ( "attack1" ); + m_flTongueAdj = -20; + + m_hEnemy = pTouchEnt; + + pTouchEnt->pev->movetype = MOVETYPE_FLY; + pTouchEnt->pev->velocity = pev->velocity; //LRC- make him come _with_ me + pTouchEnt->pev->origin.x = pev->origin.x; + pTouchEnt->pev->origin.y = pev->origin.y; + + m_fLiftingPrey = TRUE;// indicate that we should be lifting prey. + m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted. + + m_flAltitude = (pev->origin.z - pTouchEnt->EyePosition().z); + } + } + else + { + // calculate a new length for the tongue to be clear of anything else that moves under it. + if ( m_flAltitude < flLength ) + { + // if tongue is higher than is should be, lower it kind of slowly. + m_flAltitude += BARNACLE_PULL_SPEED; + m_fTongueExtended = FALSE; + } + else + { + m_flAltitude = flLength; + m_fTongueExtended = TRUE; + } + + } + + } + + // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj ); + SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) ); + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +// Killed. +//========================================================= +void CBarnacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster *pVictim; + + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + + if ( m_hEnemy != NULL ) + { + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( pVictim ) + { + pVictim->BarnacleVictimReleased(); + } + } + +// CGib::SpawnRandomGibs( pev, 4, 1 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die3.wav", 1, ATTN_NORM ); break; + } + + SetActivity ( ACT_DIESIMPLE ); + SetBoneController( 0, 0 ); + + StudioFrameAdvance( 0.1 ); + + SetNextThink( 0.1 ); + SetThink(&CBarnacle :: WaitTillDead ); +} + +//========================================================= +//========================================================= +void CBarnacle :: WaitTillDead ( void ) +{ + SetNextThink( 0.1 ); + + float flInterval = StudioFrameAdvance( 0.1 ); + DispatchAnimEvents ( flInterval ); + + if ( m_fSequenceFinished ) + { + // death anim finished. + StopAnimation(); + SetThink ( NULL ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarnacle :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/barnacle.mdl"); + + PRECACHE_SOUND("barnacle/bcl_alert2.wav");//happy, lifting food up + PRECACHE_SOUND("barnacle/bcl_bite3.wav");//just got food to mouth + PRECACHE_SOUND("barnacle/bcl_chew1.wav"); + PRECACHE_SOUND("barnacle/bcl_chew2.wav"); + PRECACHE_SOUND("barnacle/bcl_chew3.wav"); + PRECACHE_SOUND("barnacle/bcl_die1.wav" ); + PRECACHE_SOUND("barnacle/bcl_die3.wav" ); +} + +//========================================================= +// TongueTouchEnt - does a trace along the barnacle's tongue +// to see if any entity is touching it. Also stores the length +// of the trace in the int pointer provided. +//========================================================= +#define BARNACLE_CHECK_SPACING 8 +CBaseEntity *CBarnacle :: TongueTouchEnt ( float *pflLength ) +{ + TraceResult tr; + float length; + + // trace once to hit architecture and see if the tongue needs to change position. + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0 , 0 , 2048 ), ignore_monsters, ENT(pev), &tr ); + length = fabs( pev->origin.z - tr.vecEndPos.z ); + if ( pflLength ) + { + *pflLength = length; + } + + Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 ); + Vector mins = pev->origin - delta; + Vector maxs = pev->origin + delta; + maxs.z = pev->origin.z; + mins.z -= length; + + CBaseEntity *pList[10]; + int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_MONSTER) ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + // only clients and monsters + if ( pList[i] != this && IRelationship( pList[i] ) > R_NO && pList[ i ]->pev->deadflag == DEAD_NO ) // this ent is one of our enemies. Barnacle tries to eat it. + { + return pList[i]; + } + } + } + + return NULL; +} diff --git a/server/monsters/barney.cpp b/server/monsters/barney.cpp new file mode 100644 index 00000000..a6f03c60 --- /dev/null +++ b/server/monsters/barney.cpp @@ -0,0 +1,929 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +// UNDONE: Holster weapon? + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "baseweapon.h" +#include "soundent.h" +#include "defaults.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +// first flag is barney dying for scripted sequences? +#define BARNEY_AE_DRAW ( 2 ) +#define BARNEY_AE_SHOOT ( 3 ) +#define BARNEY_AE_HOLSTER ( 4 ) + +#define BARNEY_BODY_GUNHOLSTERED 0 +#define BARNEY_BODY_GUNDRAWN 1 +#define BARNEY_BODY_GUNGONE 2 + +class CBarney : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void BarneyFirePistol( void ); + void AlertSound( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + virtual int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + void DeclineFollowing( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iBaseBody; //LRC - for barneys with different bodies + BOOL m_fGunDrawn; + float m_painTime; + float m_checkAttackTime; + BOOL m_lastAttackCheck; + + // UNDONE: What is this for? It isn't used? + float m_flPlayerDamage;// how much pain has the player inflicted on me? + + CUSTOM_SCHEDULES; +}; + +LINK_ENTITY_TO_CLASS( monster_barney, CBarney ); + +TYPEDESCRIPTION CBarney::m_SaveData[] = +{ + DEFINE_FIELD( CBarney, m_iBaseBody, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CBarney, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_lastAttackCheck, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_flPlayerDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarney, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlBaFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slBaFollow[] = +{ + { + tlBaFollow, + ARRAYSIZE ( tlBaFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +//========================================================= +// BarneyDraw- much better looking draw schedule for when +// barney knows who he's gonna attack. +//========================================================= +Task_t tlBarneyEnemyDraw[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, 0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float) ACT_ARM }, +}; + +Schedule_t slBarneyEnemyDraw[] = +{ + { + tlBarneyEnemyDraw, + ARRAYSIZE ( tlBarneyEnemyDraw ), + 0, + 0, + "Barney Enemy Draw" + } +}; + +Task_t tlBaFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slBaFaceTarget[] = +{ + { + tlBaFaceTarget, + ARRAYSIZE ( tlBaFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlIdleBaStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleBaStand[] = +{ + { + tlIdleBaStand, + ARRAYSIZE ( tlIdleBaStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBarney ) +{ + slBaFollow, + slBarneyEnemyDraw, + slBaFaceTarget, + slIdleBaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CBarney, CTalkMonster ); + +void CBarney :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CBarney :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) + { + pev->framerate = 1.5; + } + CTalkMonster::RunTask( pTask ); + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + + + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CBarney :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarney :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - barney says "Freeze!" +//========================================================= +void CBarney :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_ATTACK"); + PlaySentence( szBuf, RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + else + { + PlaySentence( "BA_ATTACK", RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + } + } + +} +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBarney :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 70; + break; + case ACT_WALK: + ys = 70; + break; + case ACT_RUN: + ys = 90; + break; + default: + ys = 70; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBarney :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 1024 && flDot >= 0.5 ) + { + if ( gpGlobals->time > m_checkAttackTime ) + { + TraceResult tr; + + Vector shootOrigin = pev->origin + Vector( 0, 0, 55 ); + CBaseEntity *pEnemy = m_hEnemy; + Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP ); + UTIL_TraceLine( shootOrigin, shootTarget, dont_ignore_monsters, ENT(pev), &tr ); + m_checkAttackTime = gpGlobals->time + 1; + if ( tr.flFraction == 1.0 || (tr.pHit != NULL && CBaseEntity::Instance(tr.pHit) == pEnemy) ) + m_lastAttackCheck = TRUE; + else + m_lastAttackCheck = FALSE; + m_checkAttackTime = gpGlobals->time + 1.5; + } + return m_lastAttackCheck; + } + return FALSE; +} + + +//========================================================= +// BarneyFirePistol - shoots one round from the pistol at +// the enemy barney is facing. +//========================================================= +void CBarney :: BarneyFirePistol ( void ) +{ + Vector vecShootOrigin; + + UTIL_MakeVectors(pev->angles); + vecShootOrigin = pev->origin + Vector( 0, 0, 55 ); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects = EF_MUZZLEFLASH; + + if (pev->frags) + { + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_357); + if (RANDOM_LONG(0, 1)) + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "weapons/357_shot1.wav", 1, ATTN_NORM, 0, 100 ); + else + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "weapons/357_shot2.wav", 1, ATTN_NORM, 0, 100 ); + } + else + { + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_9MM ); + + int pitchShift = RANDOM_LONG( 0, 20 ); + + // Only shift about half the time + if ( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "barney/ba_attack2.wav", 1, ATTN_NORM, 0, 100 + pitchShift ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + // UNDONE: Reload? + m_cAmmoLoaded--;// take away a bullet! + + // Teh_Freak: World Lighting! + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_DLIGHT ); + WRITE_COORD( vecShootOrigin.x ); // origin + WRITE_COORD( vecShootOrigin.y ); + WRITE_COORD( vecShootOrigin.z ); + WRITE_BYTE( 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 128 ); // B + WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 0 ); // decay + MESSAGE_END(); + // Teh_Freak: World Lighting! +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarney :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNEY_AE_SHOOT: + BarneyFirePistol(); + break; + + case BARNEY_AE_DRAW: + // barney's bodygroup switches here so he can pull gun from holster + pev->body = m_iBaseBody + BARNEY_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case BARNEY_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = m_iBaseBody + BARNEY_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarney :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/barney.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) //LRC + pev->health = BARNEY_HEALTH; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + + m_iBaseBody = pev->body; //LRC + pev->body = m_iBaseBody + BARNEY_BODY_GUNHOLSTERED; // gun in holster + m_fGunDrawn = FALSE; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); + SetUse(&CBarney :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarney :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/barney.mdl"); + + PRECACHE_SOUND("barney/ba_attack1.wav" ); + PRECACHE_SOUND("barney/ba_attack2.wav" ); + + PRECACHE_SOUND("barney/ba_pain1.wav"); + PRECACHE_SOUND("barney/ba_pain2.wav"); + PRECACHE_SOUND("barney/ba_pain3.wav"); + + PRECACHE_SOUND("barney/ba_die1.wav"); + PRECACHE_SOUND("barney/ba_die2.wav"); + PRECACHE_SOUND("barney/ba_die3.wav"); + + // every new barney must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CBarney :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // barney speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "BA_PFOLLOW"; + else + m_szGrp[TLK_USE] = "BA_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "BA_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "BA_POK"; + else + m_szGrp[TLK_DECLINE] = "BA_NOTOK"; + m_szGrp[TLK_STOP] = "BA_STOP"; + + m_szGrp[TLK_NOSHOOT] = "BA_SCARED"; + m_szGrp[TLK_HELLO] = "BA_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!BA_CUREA"; + m_szGrp[TLK_PLHURT2] = "!BA_CUREB"; + m_szGrp[TLK_PLHURT3] = "!BA_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; //"BA_PHELLO"; // UNDONE + m_szGrp[TLK_PIDLE] = NULL; //"BA_PIDLE"; // UNDONE + m_szGrp[TLK_PQUESTION] = "BA_PQUEST"; // UNDONE + + m_szGrp[TLK_SMELL] = "BA_SMELL"; + + m_szGrp[TLK_WOUND] = "BA_WOUND"; + m_szGrp[TLK_MORTAL] = "BA_MORTAL"; + } + + // get voice for head - just one barney voice for now + m_voicePitch = 100; +} + +int CBarney :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + // LRC - if my reaction to the player has been overridden, don't do this stuff + if (m_iPlayerReact) return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + m_flPlayerDamage += flDamage; + + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if(( m_afMemory & bits_MEMORY_SUSPICIOUS) || UTIL_IsFacing( pevAttacker, pev->origin )) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CBarney :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CBarney :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CBarney::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + switch( ptr->iHitgroup) + { + case HITGROUP_CHEST: + case HITGROUP_STOMACH: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) + { + flDamage = flDamage / 2; + } + break; + case 10: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) + { + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // always a head shot + ptr->iHitgroup = HITGROUP_HEAD; + break; + } + + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +void CBarney::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < m_iBaseBody + BARNEY_BODY_GUNGONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + { // drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = m_iBaseBody + BARNEY_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + pGun = DropItem( "weapon_glock", vecGunPos, vecGunAngles ); + } + + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CBarney :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + case SCHED_ARM_WEAPON: + if ( m_hEnemy != NULL ) + { + // face enemy, then draw. + return slBarneyEnemyDraw; + } + break; + + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that barney will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slBaFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slBaFollow; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + { + // just look straight ahead. + return slIdleBaStand; + } + else + return psched; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBarney :: GetSchedule ( void ) +{ + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_KILL", 4, VOL_NORM, ATTN_NORM ); + } + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // always act surprized with a new enemy + if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + + // wait for one schedule to draw gun + if (!m_fGunDrawn ) + return GetScheduleOfType( SCHED_ARM_WEAPON ); + + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + if ( m_hEnemy == NULL && IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + else + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY ); + } + + // try to say something about smells + TrySmellTalk(); + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CBarney :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CBarney::DeclineFollowing( void ) +{ + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + + + + +//========================================================= +// DEAD BARNEY PROP +// +// Designer selects a pose in worldcraft, 0 through num_poses-1 +// this value is added to what is selected as the 'first dead pose' +// among the monster's normal animations. All dead poses must +// appear sequentially in the model file. Be sure and set +// the m_iFirstPose properly! +// +//========================================================= +class CDeadBarney : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadBarney::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_barney_dead, CDeadBarney ); + +//========================================================= +// ********** DeadBarney SPAWN ********** +//========================================================= +void CDeadBarney :: Spawn( ) +{ + PRECACHE_MODEL("models/barney.mdl"); + SET_MODEL(ENT(pev), "models/barney.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if( pev->sequence == -1 ) + { + ALERT ( at_console, "Dead barney with bad pose\n" ); + } + // Corpses have less health + pev->health = DEAD_BARNEY_HEALTH; + + MonsterInitDead(); +} + + diff --git a/server/monsters/baseanimating.h b/server/monsters/baseanimating.h new file mode 100644 index 00000000..d27a710d --- /dev/null +++ b/server/monsters/baseanimating.h @@ -0,0 +1,50 @@ +//======================================================================= +// Copyright (C) XashXT Group 2006 +//======================================================================= + +#ifndef BASEANIMATING_H +#define BASEANIMATING_H + +class CBaseAnimating : public CBaseLogic +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + float SequenceDuration( int iSequence ); + void ResetSequenceInfo ( ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); + virtual CBaseAnimating* GetBaseAnimating() { return this; } + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + BOOL GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int GetBoneCount( void ); + void SetBones( float (*data)[3], int datasize ); + + int ExtractBbox( int sequence, float *mins, float *maxs ); + void SetSequenceBox( void ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops +}; + +#endif //BASEANIMATING_H \ No newline at end of file diff --git a/server/monsters/basemonster.cpp b/server/monsters/basemonster.cpp new file mode 100644 index 00000000..6098b5e6 --- /dev/null +++ b/server/monsters/basemonster.cpp @@ -0,0 +1,6238 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "nodes.h" +#include "client.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "baseweapon.h" +#include "scripted.h" +#include "squadmonster.h" +#include "defaultai.h" +#include "decals.h" +#include "soundent.h" +#include "gamerules.h" +#include "game.h" + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC +extern CGraph WorldGraph; + +Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; +extern DLL_GLOBAL BOOL g_fDrawLines; +extern CGraph WorldGraph;// the world node graph +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + + +// Global Savedata for monster +// UNDONE: Save schedule data? Can this be done? We may +// lose our enemy pointer or other data (goal ent, target, etc) +// that make the current schedule invalid, perhaps it's best +// to just pick a new one when we start up again. +TYPEDESCRIPTION CBaseMonster::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseMonster, m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_ARRAY( CBaseMonster, m_hOldEnemy, FIELD_EHANDLE, MAX_OLD_ENEMIES ), + DEFINE_ARRAY( CBaseMonster, m_vecOldEnemy, FIELD_POSITION_VECTOR, MAX_OLD_ENEMIES ), + + DEFINE_FIELD( CBaseMonster, m_iClass, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iPlayerReact, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flFieldOfView, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flMoveWaitFinished, FIELD_TIME ), + + DEFINE_FIELD( CBaseMonster, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealActivity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_LastHitGroup, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_MonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealMonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iTaskStatus, FIELD_INTEGER ), + + //Schedule_t *m_pSchedule; + + DEFINE_FIELD( CBaseMonster, m_iScheduleIndex, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afConditions, FIELD_INTEGER ), + //WayPoint_t m_Route[ ROUTE_SIZE ]; +// DEFINE_FIELD( CBaseMonster, m_movementGoal, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_iRouteIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_moveWaitTime, FIELD_FLOAT ), + + DEFINE_FIELD( CBaseMonster, m_vecMoveGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_movementActivity, FIELD_INTEGER ), + + // int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. +// DEFINE_FIELD( CBaseMonster, m_afSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_iHintNode, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iMaxHealth, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_vecEnemyLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afCapability, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_bitsDamageType, FIELD_INTEGER ), + DEFINE_ARRAY( CBaseMonster, m_rgbTimeBasedDamage, FIELD_CHARACTER, CDMG_TIMEBASED ), + DEFINE_FIELD( CBaseMonster, m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_failSchedule, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flHungryTime, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flDistLook, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_iTriggerCondition, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iszTriggerTarget, FIELD_STRING ), + + DEFINE_FIELD( CBaseMonster, m_HackedGunPos, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_pCine, FIELD_CLASSPTR ), +}; + +//IMPLEMENT_SAVERESTORE( CBaseMonster, CBaseAnimating ); +int CBaseMonster::Save( CSave &save ) +{ + if ( !CBaseAnimating::Save(save) ) + return 0; + if ( pev->targetname ) + return save.WriteFields( STRING(pev->targetname), "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + else + return save.WriteFields( STRING(pev->classname), "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CBaseMonster::Restore( CRestore &restore ) +{ + if ( !CBaseAnimating::Restore(restore) ) + return 0; + int status = restore.ReadFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + // We don't save/restore routes yet + RouteClear(); + + // We don't save/restore schedules yet + m_pSchedule = NULL; + m_iTaskStatus = TASKSTATUS_NEW; + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( m_hEnemy == NULL ) + m_afConditions = 0; + + return status; +} + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CBaseMonster :: FShouldEat ( void ) +{ + if ( m_flHungryTime > gpGlobals->time ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - called +// by Barnacle victims when the barnacle pulls their head +// into its mouth +//========================================================= +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( SCHED_BARNACLE_VICTIM_CHOMP ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } +} + +//========================================================= +// BarnacleVictimReleased - called by barnacle victims when +// the host barnacle is killed. +//========================================================= +void CBaseMonster :: BarnacleVictimReleased ( void ) +{ + m_IdealMonsterState = MONSTERSTATE_IDLE; + + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_STEP; +} + +//========================================================= +// Listen - monsters dig through the active sound list for +// any sounds that may interest them. (smells, too!) +//========================================================= +void CBaseMonster :: Listen ( void ) +{ + int iSound; + int iMySounds; + float hearingSensitivity; + CSound *pCurrentSound; + + m_iAudibleList = SOUNDLIST_EMPTY; + ClearConditions(bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_SMELL_FOOD); + m_afSoundTypes = 0; + + iMySounds = ISoundMask(); + + if ( m_pSchedule ) + { + //!!!WATCH THIS SPOT IF YOU ARE HAVING SOUND RELATED BUGS! + // Make sure your schedule AND personal sound masks agree! + iMySounds &= m_pSchedule->iSoundMask; + } + + iSound = CSoundEnt::ActiveList(); + + // UNDONE: Clear these here? + ClearConditions( bits_COND_HEAR_SOUND | bits_COND_SMELL_FOOD | bits_COND_SMELL ); + hearingSensitivity = HearingSensitivity( ); + + while ( iSound != SOUNDLIST_EMPTY ) + { + pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && + ( pCurrentSound->m_iType & iMySounds ) && + ( pCurrentSound->m_vecOrigin - EarPosition() ).Length() <= pCurrentSound->m_iVolume * hearingSensitivity ) + + //if ( ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & iMySounds ) && ( g_pSoundEnt->m_SoundPool[ iSound ].m_vecOrigin - EarPosition()).Length () <= g_pSoundEnt->m_SoundPool[ iSound ].m_iVolume * hearingSensitivity ) + { + // the monster cares about this sound, and it's close enough to hear. + //g_pSoundEnt->m_SoundPool[ iSound ].m_iNextAudible = m_iAudibleList; + pCurrentSound->m_iNextAudible = m_iAudibleList; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + SetConditions( bits_COND_HEAR_SOUND ); + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent +// if ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + if ( pCurrentSound->m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + { + // the detected scent is a food item, so set both conditions. + // !!!BUGBUG - maybe a virtual function to determine whether or not the scent is food? + SetConditions( bits_COND_SMELL_FOOD ); + SetConditions( bits_COND_SMELL ); + } + else + { + // just a normal scent. + SetConditions( bits_COND_SMELL ); + } + } + +// m_afSoundTypes |= g_pSoundEnt->m_SoundPool[ iSound ].m_iType; + m_afSoundTypes |= pCurrentSound->m_iType; + + m_iAudibleList = iSound; + } + +// iSound = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; + iSound = pCurrentSound->m_iNext; + } +} + +//========================================================= +// FLSoundVolume - subtracts the volume of the given sound +// from the distance the sound source is from the caller, +// and returns that value, which is considered to be the 'local' +// volume of the sound. +//========================================================= +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) +{ + return ( pSound->m_iVolume - ( ( pSound->m_vecOrigin - pev->origin ).Length() ) ); +} + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CBaseMonster :: FValidateHintType ( short sHint ) +{ + return FALSE; +} + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + // See no evil if prisoner is set + if ( !FBitSet( pev->spawnflags, SF_MONSTER_PRISONER ) ) + { + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + // !!!temporarily only considering other monsters and clients, don't see prisoners + + if ( pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + if ( pev->spawnflags & SF_MONSTER_WAIT_TILL_SEEN ) + { + CBaseMonster *pClient; + + pClient = pSightEnt->MyMonsterPointer(); + // don't link this client in the list if the monster is wait till seen and the player isn't facing the monster + if ( pSightEnt && !pClient->FInViewCone( this ) ) + { + // we're not in the player's view cone. + continue; + } + else + { + // player sees us, become normal now. + pev->spawnflags &= ~SF_MONSTER_WAIT_TILL_SEEN; + } + } + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + } + + SetConditions( iSighted ); +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBaseMonster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// PBestSound - returns a pointer to the sound the monster +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CBaseMonster :: PBestSound ( void ) +{ + int iThisSound; + int iBestSound = -1; + float flBestDist = 8192;// so first nearby sound will become best so far. + float flDist; + CSound *pSound; + + iThisSound = m_iAudibleList; + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! monster %s has no audible sounds!\n", STRING(pev->classname) ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisSound ); + + if ( pSound && pSound->FIsSound() ) + { + flDist = ( pSound->m_vecOrigin - EarPosition()).Length(); + + if ( flDist < flBestDist ) + { + iBestSound = iThisSound; + flBestDist = flDist; + } + } + + iThisSound = pSound->m_iNextAudible; + } + if ( iBestSound >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestSound ); + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the monster +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CBaseMonster :: PBestScent ( void ) +{ + int iThisScent; + int iBestScent = -1; + float flBestDist = 8192;// so first nearby smell will become best so far. + float flDist; + CSound *pSound; + + iThisScent = m_iAudibleList;// smells are in the sound list. + + if ( iThisScent == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! PBestScent() has empty soundlist!\n" ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisScent != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisScent ); + + if ( pSound->FIsScent() ) + { + flDist = ( pSound->m_vecOrigin - pev->origin ).Length(); + + if ( flDist < flBestDist ) + { + iBestScent = iThisScent; + flBestDist = flDist; + } + } + + iThisScent = pSound->m_iNextAudible; + } + if ( iBestScent >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestScent ); + + return pSound; + } + return NULL; +} + + + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CBaseMonster :: MonsterThink ( void ) +{ + SetNextThink( 0.05 );// keep monster thinking. + + RunAI(); + + float flInterval = StudioFrameAdvance( ); // animate + +// start or end a fidget +// This needs a better home -- switching animations over time should be encapsulated on a per-activity basis +// perhaps MaintainActivity() or a ShiftAnimationOverTime() or something. + + if ( m_MonsterState != MONSTERSTATE_SCRIPT && m_MonsterState != MONSTERSTATE_DEAD && m_Activity == ACT_IDLE && m_fSequenceFinished ) + { + int iSequence; + + if ( m_fSequenceLoops ) + { + // animation does loop, which means we're playing subtle idle. Might need to + // fidget. + iSequence = LookupActivity ( m_Activity ); + } + else + { + // animation that just ended doesn't loop! That means we just finished a fidget + // and should return to our heaviest weighted idle (the subtle one) + iSequence = LookupActivityHeaviest ( m_Activity ); + } + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to new anim (if it's there) + ResetSequenceInfo( ); + } + } + + DispatchAnimEvents( flInterval ); + + if ( !MovementIsComplete())Move( flInterval ); +} + +//========================================================= +// CBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_IdealMonsterState = MONSTERSTATE_ALERT; +} + +//========================================================= +// Ignore conditions - before a set of conditions is allowed +// to interrupt a monster's schedule, this function removes +// conditions that we have flagged to interrupt the current +// schedule, but may not want to interrupt the schedule every +// time. (Pain, for instance) +//========================================================= +int CBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + if ( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) + iIgnoreConditions |= m_pCine->IgnoreConditions(); + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CBaseMonster :: RouteClear ( void ) +{ + RouteNew(); + m_movementGoal = MOVEGOAL_NONE; + m_movementActivity = ACT_IDLE; + Forget( bits_MEMORY_MOVE_FAILED ); +} + +//========================================================= +// Route New - clears out a route to be changed, but keeps +// goal intact. +//========================================================= +void CBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE if the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CBaseMonster :: FRouteClear ( void ) +{ + if ( m_Route[ m_iRouteIndex ].iType == 0 || m_movementGoal == MOVEGOAL_NONE ) + return TRUE; + + return FALSE; +} + +//========================================================= +// FRefreshRoute - after calculating a path to the monster's +// target, this function copies as many waypoints as possible +// from that path to the monster's Route array +//========================================================= +BOOL CBaseMonster :: FRefreshRoute ( void ) +{ + CBaseEntity *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = GetNext(); + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->pev->origin; + + pPathCorner = pPathCorner->GetNext(); + + // Last path_corner in list? + if ( !pPathCorner ) + m_Route[i].iType |= bits_MF_IS_GOAL; + + i++; + } + } + returnCode = TRUE; + break; + + case MOVEGOAL_ENEMY: + returnCode = BuildRoute( m_vecEnemyLKP, bits_MF_TO_ENEMY, m_hEnemy ); + break; + + case MOVEGOAL_LOCATION: + returnCode = BuildRoute( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ); + break; + + case MOVEGOAL_TARGETENT: + if (m_hTargetEnt != NULL) + { + returnCode = BuildRoute( m_hTargetEnt->pev->origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +#ifdef _DEBUG +void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ) +{ + int i; + + if ( m_Route[m_iRouteIndex].iType == 0 ) + { + ALERT( at_aiconsole, "Can't draw route!\n" ); + return; + } + +// UTIL_ParticleEffect ( m_Route[ m_iRouteIndex ].vecLocation, g_vecZero, 255, 25 ); + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.x ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.y ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.z ); + + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + for ( i = m_iRouteIndex ; i < ROUTE_SIZE - 1; i++ ) + { + if ( (m_Route[ i ].iType & bits_MF_IS_GOAL) || (m_Route[ i+1 ].iType == 0) ) + break; + + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( m_Route[ i ].vecLocation.x ); + WRITE_COORD( m_Route[ i ].vecLocation.y ); + WRITE_COORD( m_Route[ i ].vecLocation.z ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.x ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.y ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 8 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + +// UTIL_ParticleEffect ( m_Route[ i ].vecLocation, g_vecZero, 255, 25 ); + } +} +#endif + + +int ShouldSimplify( int routeType ) +{ + routeType &= ~bits_MF_IS_GOAL; + + if ( (routeType == bits_MF_TO_PATHCORNER) || (routeType & bits_MF_DONT_SIMPLIFY) ) + return FALSE; + return TRUE; +} + +//========================================================= +// RouteSimplify +// +// Attempts to make the route more direct by cutting out +// unnecessary nodes & cutting corners. +// +//========================================================= +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) +{ + // BUGBUG: this doesn't work 100% yet + int i, count, outCount; + Vector vecStart; + WayPoint_t outRoute[ ROUTE_SIZE * 2 ]; // Any points except the ends can turn into 2 points in the simplified route + + count = 0; + + for ( i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( !m_Route[i].iType ) + break; + else + count++; + if ( m_Route[i].iType & bits_MF_IS_GOAL ) + break; + } + // Can't simplify a direct route! + if ( count < 2 ) + { +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); + return; + } + + outCount = 0; + vecStart = pev->origin; + for ( i = 0; i < count-1; i++ ) + { + // Don't eliminate path_corners + if ( !ShouldSimplify( m_Route[m_iRouteIndex+i].iType ) ) + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + } + else if ( CheckLocalMove ( vecStart, m_Route[m_iRouteIndex+i+1].vecLocation, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + // Skip vert + continue; + } + else + { + Vector vecTest, vecSplit; + + // Halfway between this and next + vecTest = (m_Route[m_iRouteIndex+i+1].vecLocation + m_Route[m_iRouteIndex+i].vecLocation) * 0.5; + + // Halfway between this and previous + vecSplit = (m_Route[m_iRouteIndex+i].vecLocation + vecStart) * 0.5; + + int iType = (m_Route[m_iRouteIndex+i].iType | bits_MF_TO_DETOUR) & ~bits_MF_NOT_TO_MASK; + if ( CheckLocalMove ( vecStart, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecTest; + } + else if ( CheckLocalMove ( vecSplit, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecSplit; + outRoute[outCount+1].iType = iType; + outRoute[outCount+1].vecLocation = vecTest; + outCount++; // Adding an extra point + } + else + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + } + } + // Get last point + vecStart = outRoute[ outCount ].vecLocation; + outCount++; + } + ASSERT( i < count ); + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + + // Terminate + outRoute[outCount].iType = 0; + ASSERT( outCount < (ROUTE_SIZE*2) ); + +// Copy the simplified route, disable for testing + m_iRouteIndex = 0; + for ( i = 0; i < ROUTE_SIZE && i < outCount; i++ ) + { + m_Route[i] = outRoute[i]; + } + + // Terminate route + if ( i < ROUTE_SIZE ) + m_Route[i].iType = 0; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT( "simplify" ) != 0 ) + DrawRoute( pev, outRoute, 0, 255, 0, 0 ); +// else + DrawRoute( pev, m_Route, m_iRouteIndex, 0, 255, 0 ); +#endif +} + +//========================================================= +// FBecomeProne - tries to send a monster into PRONE state. +// right now only used when a barnacle snatches someone, so +// may have some special case stuff for that. +//========================================================= +BOOL CBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckAttacks - sets all of the bits for attacks that the +// monster is capable of carrying out on the passed entity. +//========================================================= +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + // we know the enemy is in front now. We'll find which attacks the monster is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + // Clear all attack conditions + ClearConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 |bits_COND_CAN_MELEE_ATTACK2 ); + + if ( m_afCapability & bits_CAP_RANGE_ATTACK1 ) + { + if ( CheckRangeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_RANGE_ATTACK2 ) + { + if ( CheckRangeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK2 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK1 ) + { + if ( CheckMeleeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK2 ) + { + if ( CheckMeleeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK2 ); + } +} + +//========================================================= +// CanCheckAttacks - prequalifies a monster to do more fine +// checking of potential attacks. +//========================================================= +BOOL CBaseMonster :: FCanCheckAttacks ( void ) +{ + if ( HasConditions(bits_COND_SEE_ENEMY) && !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckEnemy - part of the Condition collection process, +// gets and stores data and conditions pertaining to a monster's +// enemy. Returns TRUE if Enemy LKP was updated. +//========================================================= +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + float flDistToEnemy; + int iUpdatedLKP;// set this to TRUE if you update the EnemyLKP in this function. + + iUpdatedLKP = FALSE; + ClearConditions ( bits_COND_ENEMY_FACING_ME ); + + if ( !FVisible( pEnemy ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !pEnemy->IsAlive() ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + Vector vecEnemyPos = pEnemy->pev->origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->pev->size.z * 0.5; + // distance to enemy's head + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + else + { + // distance to enemy's feet + vecEnemyPos.z -= pEnemy->pev->size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + CBaseMonster *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + + pEnemyMonster = pEnemy->MyMonsterPointer(); + + if ( pEnemyMonster ) + { + if ( pEnemyMonster->FInViewCone ( this ) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->pev->velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->pev->velocity * RANDOM_FLOAT( -0.05, 0 ); + } + else + { + // UNDONE: use pev->oldorigin? + } + } + else if ( !HasConditions(bits_COND_ENEMY_OCCLUDED|bits_COND_SEE_ENEMY) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the monster. + // if the enemy is near enough the monster, we go ahead and let the monster know where the + // enemy is. + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + } + + if ( flDistToEnemy >= m_flDistTooFar ) + { + // enemy is very far away from monster + SetConditions( bits_COND_ENEMY_TOOFAR ); + } + else + ClearConditions( bits_COND_ENEMY_TOOFAR ); + + if ( FCanCheckAttacks() ) + { + CheckAttacks ( m_hEnemy, flDistToEnemy ); + } + + if ( m_movementGoal == MOVEGOAL_ENEMY ) + { + for ( int i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( m_Route[ i ].iType == (bits_MF_IS_GOAL|bits_MF_TO_ENEMY) ) + { + // UNDONE: Should we allow monsters to override this distance (80?) + if ( (m_Route[ i ].vecLocation - m_vecEnemyLKP).Length() > 80 ) + { + // Refresh + FRefreshRoute(); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +//========================================================= +// PushEnemy - remember the last few enemies, always remember the player +//========================================================= +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) +{ + int i; + + if (pEnemy == NULL) + return; + + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (i = 0; i < MAX_OLD_ENEMIES; i++) + { + if (m_hOldEnemy[i] == pEnemy) + return; + if (m_hOldEnemy[i] == NULL) // someone died, reuse their slot + break; + } + if (i >= MAX_OLD_ENEMIES) + return; + + m_hOldEnemy[i] = pEnemy; + m_vecOldEnemy[i] = vecLastKnownPos; +} + +//========================================================= +// PopEnemy - try remembering the last few enemies +//========================================================= +BOOL CBaseMonster :: PopEnemy( ) +{ + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (int i = MAX_OLD_ENEMIES - 1; i >= 0; i--) + { + if (m_hOldEnemy[i] != NULL) + { + if (m_hOldEnemy[i]->IsAlive( )) // cheat and know when they die + { + m_hEnemy = m_hOldEnemy[i]; + m_vecEnemyLKP = m_vecOldEnemy[i]; + // ALERT( at_console, "remembering\n"); + return TRUE; + } + else + { + m_hOldEnemy[i] = NULL; + } + } + } + return FALSE; +} + +//========================================================= +// SetActivity +//========================================================= +void CBaseMonster :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( NewActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CBaseMonster :: SetSequenceByName ( char *szSequence ) +{ + int iSequence; + + iSequence = LookupSequence ( szSequence ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence named:%f\n", STRING(pev->classname), szSequence ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// CheckLocalMove - returns TRUE if the caller can walk a +// straight line from its current origin to the given +// location. If so, don't use the node graph! +// +// if a valid pointer to a int is passed, the function +// will fill that int with the distance that the check +// reached before hitting something. THIS ONLY HAPPENS +// IF THE LOCAL MOVE CHECK FAILS! +// +// !!!PERFORMANCE - should we try to load balance this? +// DON"T USE SETORIGIN! +//========================================================= +#define LOCAL_STEP_SIZE 16 +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + Vector vecStartPos;// record monster's position before trying the move + float flYaw; + float flDist; + float flStep, stepSize; + int iReturn; + + vecStartPos = pev->origin; + + + flYaw = UTIL_VecToYaw ( vecEnd - vecStart );// build a yaw that points to the goal. + flDist = ( vecEnd - vecStart ).Length2D();// get the distance. + iReturn = LOCALMOVE_VALID;// assume everything will be ok. + + // move the monster to the start of the local move that's to be checked. + UTIL_SetOrigin( this, vecStart );// !!!BUGBUG - won't this fire triggers? - nope, SetOrigin doesn't fire + + if ( !(pev->flags & (FL_FLY|FL_SWIM)) ) + { + DROP_TO_FLOOR( ENT( pev ) );//make sure monster is on the floor! + } + + //pev->origin.z = vecStartPos.z;//!!!HACKHACK + +// pev->origin = vecStart; + +/* + if ( flDist > 1024 ) + { + // !!!PERFORMANCE - this operation may be too CPU intensive to try checks this large. + // We don't lose much here, because a distance this great is very likely + // to have something in the way. + + // since we've actually moved the monster during the check, undo the move. + pev->origin = vecStartPos; + return FALSE; + } +*/ + // this loop takes single steps to the goal. + for ( flStep = 0 ; flStep < flDist ; flStep += LOCAL_STEP_SIZE ) + { + stepSize = LOCAL_STEP_SIZE; + + if ( (flStep + LOCAL_STEP_SIZE) >= (flDist-1) ) + stepSize = (flDist - flStep) - 1; + +// UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, WALKMOVE_CHECKONLY ) ) + {// can't take the next step, fail! + + if ( pflDist != NULL ) + { + *pflDist = flStep; + } + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + { + // if this step hits target ent, the move is legal. + iReturn = LOCALMOVE_VALID; + break; + } + else + { + // If we're going toward an entity, and we're almost getting there, it's OK. +// if ( pTarget && fabs( flDist - iStep ) < LOCAL_STEP_SIZE ) +// fReturn = TRUE; +// else + iReturn = LOCALMOVE_INVALID; + break; + } + + } + } + + if ( iReturn == LOCALMOVE_VALID && !(pev->flags & (FL_FLY|FL_SWIM) ) && (!pTarget || (pTarget->pev->flags & FL_ONGROUND)) ) + { + // The monster can move to a spot UNDER the target, but not to it. Don't try to triangulate, go directly to the node graph. + // UNDONE: Magic # 64 -- this used to be pev->size.z but that won't work for small creatures like the headcrab + if ( fabs(vecEnd.z - pev->origin.z) > 64 ) + { + iReturn = LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + /* + // uncommenting this block will draw a line representing the nearest legal move. + WRITE_BYTE(MSG_BROADCAST, gmsg.TempEntity); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, pev->origin.x); + WRITE_COORD(MSG_BROADCAST, pev->origin.y); + WRITE_COORD(MSG_BROADCAST, pev->origin.z); + WRITE_COORD(MSG_BROADCAST, vecStart.x); + WRITE_COORD(MSG_BROADCAST, vecStart.y); + WRITE_COORD(MSG_BROADCAST, vecStart.z); + */ + + // since we've actually moved the monster during the check, undo the move. + UTIL_SetOrigin( this, vecStartPos ); + + return iReturn; +} + + +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) +{ + float flTravelTime = 0; + + //ALERT(at_aiconsole, "A door. "); + CBaseEntity *pcbeDoor = CBaseEntity::Instance(pevDoor); + if (pcbeDoor && !pcbeDoor->IsLockedByMaster()) + { + //ALERT(at_aiconsole, "unlocked! "); + pcbeDoor->Use(this, this, USE_ON, 0.0); + //ALERT(at_aiconsole, "pevDoor->nextthink = %d ms\n", (int)(1000*pevDoor->nextthink)); + //ALERT(at_aiconsole, "pevDoor->ltime = %d ms\n", (int)(1000*pevDoor->ltime)); + //ALERT(at_aiconsole, "pev-> nextthink = %d ms\n", (int)(1000*pev->nextthink)); + //ALERT(at_aiconsole, "pev->ltime = %d ms\n", (int)(1000*pev->ltime)); + + flTravelTime = pcbeDoor->m_fNextThink - pevDoor->ltime; + + //ALERT(at_aiconsole, "Waiting %d ms\n", (int)(1000*flTravelTime)); + if ( pcbeDoor->pev->targetname ) + { + CBaseEntity *pTarget = NULL; + for (;;) + { + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pcbeDoor->pev->targetname)); + if (!pTarget) + break; + + if ( VARS( pTarget->pev ) != pcbeDoor->pev && + FClassnameIs ( pTarget->pev, STRING(pcbeDoor->pev->classname) ) ) + { + pTarget->Use(this, this, USE_ON, 0.0); + } + } + } + } + + return gpGlobals->time + flTravelTime; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CBaseMonster :: AdvanceRoute ( float distance ) +{ + + if ( m_iRouteIndex == ROUTE_SIZE - 1 ) + { + // time to refresh the route. + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Refresh Route!!\n" ); + } + } + else + { + if ( ! (m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL) ) + { + // If we've just passed a path_corner, advance m_pGoalEnt + if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_PATHCORNER ) + m_pGoalEnt = m_pGoalEnt->GetNext(); + + // IF both waypoints are nodes, then check for a link for a door and operate it. + // + if ( (m_Route[m_iRouteIndex].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE + && (m_Route[m_iRouteIndex+1].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE) + { + //ALERT(at_aiconsole, "SVD: Two nodes. "); + + int iSrcNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex].vecLocation, this ); + int iDestNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex+1].vecLocation, this ); + + int iLink; + WorldGraph.HashSearch(iSrcNode, iDestNode, iLink); + + if ( iLink >= 0 && WorldGraph.m_pLinkPool[iLink].m_pLinkEnt != NULL ) + { + //ALERT(at_aiconsole, "A link. "); + if ( WorldGraph.HandleLinkEnt ( iSrcNode, WorldGraph.m_pLinkPool[iLink].m_pLinkEnt, m_afCapability, CGraph::NODEGRAPH_DYNAMIC ) ) + { + //ALERT(at_aiconsole, "usable."); + entvars_t *pevDoor = WorldGraph.m_pLinkPool[iLink].m_pLinkEnt; + if (pevDoor) + { + m_flMoveWaitFinished = OpenDoorAndWait( pevDoor ); +// ALERT( at_aiconsole, "Wating for door %.2f\n", m_flMoveWaitFinished-gpGlobals->time ); + } + } + } + //ALERT(at_aiconsole, "\n"); + } + m_iRouteIndex++; + } + else // At goal!!! + { + if ( distance < m_flGroundSpeed * 0.2 /* FIX */ ) + { + MovementComplete(); + } + } + } +} + + +int CBaseMonster :: RouteClassify( int iMoveFlag ) +{ + int movementGoal; + + movementGoal = MOVEGOAL_NONE; + + if ( iMoveFlag & bits_MF_TO_TARGETENT ) + movementGoal = MOVEGOAL_TARGETENT; + else if ( iMoveFlag & bits_MF_TO_ENEMY ) + movementGoal = MOVEGOAL_ENEMY; + else if ( iMoveFlag & bits_MF_TO_PATHCORNER ) + movementGoal = MOVEGOAL_PATHCORNER; + else if ( iMoveFlag & bits_MF_TO_NODE ) + movementGoal = MOVEGOAL_NODE; + else if ( iMoveFlag & bits_MF_TO_LOCATION ) + movementGoal = MOVEGOAL_LOCATION; + + return movementGoal; +} + +//========================================================= +// BuildRoute +//========================================================= +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + float flDist; + Vector vecApex; + int iLocalMove; + + RouteNew(); + m_movementGoal = RouteClassify( iMoveFlag ); + +// so we don't end up with no moveflags + m_Route[ 0 ].vecLocation = vecGoal; + m_Route[ 0 ].iType = iMoveFlag | bits_MF_IS_GOAL; + +// check simple local move + iLocalMove = CheckLocalMove( pev->origin, vecGoal, pTarget, &flDist ); + + if ( iLocalMove == LOCALMOVE_VALID ) + { + // monster can walk straight there! + return TRUE; + } +// try to triangulate around any obstacles. + else if ( iLocalMove != LOCALMOVE_INVALID_DONT_TRIANGULATE && FTriangulate( pev->origin, vecGoal, flDist, pTarget, &vecApex ) ) + { + // there is a slightly more complicated path that allows the monster to reach vecGoal + m_Route[ 0 ].vecLocation = vecApex; + m_Route[ 0 ].iType = (iMoveFlag | bits_MF_TO_DETOUR); + + m_Route[ 1 ].vecLocation = vecGoal; + m_Route[ 1 ].iType = iMoveFlag | bits_MF_IS_GOAL; + + /* + WRITE_BYTE(MSG_BROADCAST, gmsg.TempEntity); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z ); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z + 128 ); + */ + + RouteSimplify( pTarget ); + return TRUE; + } + +// last ditch, try nodes + if ( FGetNodeRoute( vecGoal ) ) + { +// ALERT ( at_console, "Can get there on nodes\n" ); + m_vecMoveGoal = vecGoal; + RouteSimplify( pTarget ); + return TRUE; + } + + // b0rk + return FALSE; +} + + +//========================================================= +// InsertWaypoint - Rebuilds the existing route so that the +// supplied vector and moveflags are the first waypoint in +// the route, and fills the rest of the route with as much +// of the pre-existing route as possible +//========================================================= +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) +{ + int i, type; + + + // we have to save some Index and Type information from the real + // path_corner or node waypoint that the monster was trying to reach. This makes sure that data necessary + // to refresh the original path exists even in the new waypoints that don't correspond directy to a path_corner + // or node. + type = afMoveFlags | (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK); + + for ( i = ROUTE_SIZE-1; i > 0; i-- ) + m_Route[i] = m_Route[i-1]; + + m_Route[ m_iRouteIndex ].vecLocation = vecLocation; + m_Route[ m_iRouteIndex ].iType = type; +} + +//========================================================= +// FTriangulate - tries to overcome local obstacles by +// triangulating a path around them. +// +// iApexDist is how far the obstruction that we are trying +// to triangulate around is from the monster. +//========================================================= +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + Vector vecDir; + Vector vecForward; + Vector vecLeft;// the spot we'll try to triangulate to on the left + Vector vecRight;// the spot we'll try to triangulate to on the right + Vector vecTop;// the spot we'll try to triangulate to on the top + Vector vecBottom;// the spot we'll try to triangulate to on the bottom + Vector vecFarSide;// the spot that we'll move to after hitting the triangulated point, before moving on to our normal goal. + int i; + float sizeX, sizeZ; + + // If the hull width is less than 24, use 24 because CheckLocalMove uses a min of + // 24. + sizeX = pev->size.x; + if (sizeX < 24.0) + sizeX = 24.0; + else if (sizeX > 48.0) + sizeX = 48.0; + sizeZ = pev->size.z; + //if (sizeZ < 24.0) + // sizeZ = 24.0; + + vecForward = ( vecEnd - vecStart ).Normalize(); + + Vector vecDirUp(0,0,1); + vecDir = CrossProduct ( vecForward, vecDirUp); + + // start checking right about where the object is, picking two equidistant starting points, one on + // the left, one on the right. As we progress through the loop, we'll push these away from the obstacle, + // hoping to find a way around on either side. pev->size.x is added to the ApexDist in order to help select + // an apex point that insures that the monster is sufficiently past the obstacle before trying to turn back + // onto its original course. + + vecLeft = pev->origin + ( vecForward * ( flDist + sizeX ) ) - vecDir * ( sizeX * 3 ); + vecRight = pev->origin + ( vecForward * ( flDist + sizeX ) ) + vecDir * ( sizeX * 3 ); + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = pev->origin + (vecForward * flDist) + (vecDirUp * sizeZ * 3); + vecBottom = pev->origin + (vecForward * flDist) - (vecDirUp * sizeZ * 3); + } + + vecFarSide = m_Route[ m_iRouteIndex ].vecLocation; + + vecDir = vecDir * sizeX * 2; + if (pev->movetype == MOVETYPE_FLY) + vecDirUp = vecDirUp * sizeZ * 2; + + for ( i = 0 ; i < 8; i++ ) + { +// Debug, Draw the triangulation +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecRight.x ); + WRITE_COORD( vecRight.y ); + WRITE_COORD( vecRight.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecLeft.x ); + WRITE_COORD( vecLeft.y ); + WRITE_COORD( vecLeft.z ); + MESSAGE_END(); +#endif + +#if 0 + if (pev->movetype == MOVETYPE_FLY) + { + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecTop.x ); + WRITE_COORD( vecTop.y ); + WRITE_COORD( vecTop.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecBottom.x ); + WRITE_COORD( vecBottom.y ); + WRITE_COORD( vecBottom.z ); + MESSAGE_END(); + } +#endif + + if ( CheckLocalMove( pev->origin, vecRight, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecRight, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecRight; + } + + return TRUE; + } + } + if ( CheckLocalMove( pev->origin, vecLeft, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecLeft, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecLeft; + } + + return TRUE; + } + } + + if (pev->movetype == MOVETYPE_FLY) + { + if ( CheckLocalMove( pev->origin, vecTop, pTargetEnt, NULL ) == LOCALMOVE_VALID) + { + if ( CheckLocalMove ( vecTop, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecTop; + //ALERT(at_aiconsole, "triangulate over\n"); + } + + return TRUE; + } + } +#if 1 + if ( CheckLocalMove( pev->origin, vecBottom, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecBottom, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecBottom; + //ALERT(at_aiconsole, "triangulate under\n"); + } + + return TRUE; + } + } +#endif + } + + vecRight = vecRight + vecDir; + vecLeft = vecLeft - vecDir; + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = vecTop + vecDirUp; + vecBottom = vecBottom - vecDirUp; + } + } + + return FALSE; +} + +//========================================================= +// Move - take a single step towards the next ROUTE location +//========================================================= +#define DIST_TO_CHECK 200 + +void CBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + // If we still have a movement goal, then this is probably a route truncated by SimplifyRoute() + // so refresh it. + if ( m_movementGoal == MOVEGOAL_NONE || !FRefreshRoute() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 200, 0 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // No, Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + return; + } + // Ok, still enough room to take a step + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { +// ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + // Only do this once until your route is cleared + if ( m_moveWaitTime > 0 && !(m_afMemory & bits_MEMORY_MOVE_FAILED) ) + { + FRefreshRoute(); + if ( FRouteClear() ) + { + TaskFail(); + } + else + { + // Don't get stuck + if ( (gpGlobals->time - m_flMoveWaitFinished) < 0.2 ) + Remember( bits_MEMORY_MOVE_FAILED ); + + m_flMoveWaitFinished = gpGlobals->time + 0.1; + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to move (%d)!\n", STRING(pev->classname), HasMemory( bits_MEMORY_MOVE_FAILED ) ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // close enough to the target, now advance to the next target. This is done before actually reaching + // the target so that we get a nice natural turn while moving. + if ( ShouldAdvanceRoute( flWaypointDist ) )///!!!BUGBUG- magic number + { + AdvanceRoute( flWaypointDist ); + } + + // Might be waiting for a door + if ( m_flMoveWaitFinished > gpGlobals->time ) + { + Stop(); + return; + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < m_flGroundSpeed * flInterval) + { + flInterval = flCheckDist / m_flGroundSpeed; + // ALERT( at_console, "%.02f\n", flInterval ); + } + MoveExecute( pTargetEnt, vecDir, flInterval ); + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } +} + + +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ +// float flYaw = UTIL_VecToYaw ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin );// build a yaw that points to the goal. +// WALK_MOVE( ENT(pev), flYaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_NORMAL ); + flTotal -= flStep; + } + // ALERT( at_console, "dist %f\n", m_flGroundSpeed * pev->framerate * flInterval ); +} + + +//========================================================= +// MonsterInit - after a monster is spawned, it needs to +// be dropped into the world, checked for mobility problems, +// and put on the proper path, if any. This function does +// all of those things after the monster spawns. Any +// initialization that should take place for all monsters +// goes here. +//========================================================= +void CBaseMonster :: MonsterInit ( void ) +{ + if (!g_pGameRules->FAllowMonsters()) + { + pev->flags |= FL_KILLME; // Post this because some monster code modifies class data after calling this function + return; + } + + // Set fields common to all monsters + pev->effects = 0; + pev->takedamage = DAMAGE_AIM; + pev->ideal_yaw = pev->angles.y; + pev->max_health = pev->health; + pev->deadflag = DEAD_NO; + m_IdealMonsterState = MONSTERSTATE_IDLE;// Assume monster will be idle, until proven otherwise + + m_IdealActivity = ACT_IDLE; + + SetBits (pev->flags, FL_MONSTER); + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink(&CBaseMonster :: MonsterInitThink ); + SetNextThink( 0.1 ); + SetUse(&CBaseMonster :: MonsterUse ); +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + + +void CBaseMonster :: StartPatrol ( CBaseEntity* path ) +{ + m_pGoalEnt = path; + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target \"%s\"\n", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination +// MakeIdealYaw ( m_pGoalEnt->pev->origin ); + + // set the monster up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + m_movementGoal = MOVEGOAL_PATHCORNER; + + if ( pev->movetype == MOVETYPE_FLY ) + m_movementActivity = ACT_FLY; + else + m_movementActivity = ACT_WALK; + + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Create Route!\n" ); + } + SetState( MONSTERSTATE_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_IDLE_WALK ) ); + } +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CBaseMonster :: StartMonster ( void ) +{ + // update capabilities + if ( LookupActivity ( ACT_RANGE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK1; + } + if ( LookupActivity ( ACT_RANGE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK2; + } + if ( LookupActivity ( ACT_MELEE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK1; + } + if ( LookupActivity ( ACT_MELEE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK2; + } + + // Raise monster off the floor one unit, then drop to floor + if ( pev->movetype != MOVETYPE_FLY && !FBitSet( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ) ) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + // Try to move the monster to make sure it's not stuck in a brush. + //LRC- there are perfectly good reasons for making a monster stuck, so it shouldn't always be an error. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) && !FBitSet( pev->spawnflags, SF_MONSTER_NO_YELLOW_BLOBS)) + { + Msg("%s \"%s\" stuck in wall--level design error\n", STRING(pev->classname), STRING(pev->targetname)); + pev->effects = EF_BRIGHTFIELD; + + // HACKHACK: this is for pre-alpha version + // remove stucked zombies on a start.bsp + UTIL_Remove( this ); + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + StartPatrol(UTIL_FindEntityByTargetname( NULL, STRING( pev->target ))); + } + + //SetState ( m_IdealMonsterState ); + //SetActivity ( m_IdealActivity ); + + // Delay drop to floor to make sure each door in the level has had its chance to spawn + // Spread think times so that they don't all happen at the same time (Carmack) + SetThink(&CBaseMonster :: CallMonsterThink ); + AbsoluteNextThink( m_fNextThink + RANDOM_FLOAT(0.1, 0.4) ); // spread think times. + + if ( !FStringNull(pev->targetname) )// wait until triggered + { + SetState( MONSTERSTATE_IDLE ); + // UNDONE: Some scripted sequence monsters don't have an idle? + SetActivity( ACT_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_WAIT_TRIGGER ) ); + } +} + +void CBaseMonster :: MovementComplete( void ) +{ + switch( m_iTaskStatus ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUNNING: + m_iTaskStatus = TASKSTATUS_RUNNING_TASK; + break; + + case TASKSTATUS_RUNNING_MOVEMENT: + TaskComplete(); + break; + + case TASKSTATUS_RUNNING_TASK: + ALERT( at_error, "Movement completed twice!\n" ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + m_movementGoal = MOVEGOAL_NONE; +} + + +int CBaseMonster::TaskIsRunning( void ) +{ + if ( m_iTaskStatus != TASKSTATUS_COMPLETE && + m_iTaskStatus != TASKSTATUS_RUNNING_MOVEMENT ) + return 1; + + return 0; +} + +//========================================================= +// IRelationship - returns an integer that describes the +// relationship between two types of monster. +//========================================================= +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[17][17] = + { //NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN FACT_A FACT_B FACT_C + /*NONE*/ { R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO, R_NO, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL }, + /*HUMANPASSIVE*/ { R_NO, R_NO, R_AL, R_AL, R_HT, R_FR, R_NO, R_HT, R_DL, R_FR, R_NO, R_AL, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*HUMANMILITAR*/ { R_NO, R_NO, R_HT, R_DL, R_NO, R_HT, R_DL, R_DL, R_DL, R_DL, R_NO, R_HT, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*ALIENMILITAR*/ { R_NO, R_DL, R_HT, R_DL, R_HT, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*ALIENPASSIVE*/ { R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*ALIENMONSTER*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*ALIENPREY */ { R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_FR, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*ALIENPREDATO*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_HT, R_DL, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*INSECT*/ { R_FR, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_NO, R_NO, R_FR, R_FR, R_FR }, + /*PLAYERALLY*/ { R_NO, R_DL, R_AL, R_AL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_DL, R_DL, R_DL }, + /*PBIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*ABIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_AL, R_NO, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL }, + /*FACTION_A*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_AL, R_DL, R_DL }, + /*FACTION_B*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_AL, R_DL }, + /*FACTION_C*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_AL } + }; + + int iTargClass = pTarget->Classify(); + + if (iTargClass == CLASS_PLAYER && m_iPlayerReact) //LRC + { + if (m_iPlayerReact == 1) // Ignore player + return R_NO; + else if (m_iPlayerReact == 4) + return R_HT; + else if (m_afMemory & bits_MEMORY_PROVOKED) + return R_HT; + else + return R_NO; + } + + return iEnemy[ Classify() ][ iTargClass ]; +} + +//========================================================= +// FindCover - tries to find a nearby node that will hide +// the caller from its enemy. +// +// If supplied, search will return a node at least as far +// away as MinDist, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +// UNDONE: Should this find the nearest node? + +//float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) + +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + int iThreatNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for findcover!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iThreatNode = WorldGraph.FindNearestNode ( vecThreat, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "FindCover() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + if ( iThreatNode == NO_NODE ) + { + // ALERT ( at_aiconsole, "FindCover() - Threat has no nearest node!\n" ); + iThreatNode = iMyNode; + // return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // could use an optimization here!! + flDist = ( pev->origin - node.m_vecOrigin ).Length(); + + // DON'T do the trace check on a node that is farther away than a node that we've already found to + // provide cover! Also make sure the node is within the mins/maxs of the search. + if ( flDist >= flMinDist && flDist < flMaxDist ) + { + UTIL_TraceLine ( node.m_vecOrigin + vecViewOffset, vecLookersOffset, ignore_monsters, ignore_glass, ENT(pev), &tr ); + + // if this node will block the threat's line of sight to me... + if ( tr.flFraction != 1.0 ) + { + // ..and is also closer to me than the threat, or the same distance from myself and the threat the node is good. + if ( ( iMyNode == iThreatNode ) || WorldGraph.PathLength( iMyNode, nodeNumber, iMyHullIndex, m_afCapability ) <= WorldGraph.PathLength( iThreatNode, nodeNumber, iMyHullIndex, m_afCapability ) ) + { + if ( FValidateCover ( node.m_vecOrigin ) && MoveToLocation( ACT_RUN, 0, node.m_vecOrigin ) ) + { + /* + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( node.m_vecOrigin.x ); + WRITE_COORD( node.m_vecOrigin.y ); + WRITE_COORD( node.m_vecOrigin.z ); + + WRITE_COORD( vecLookersOffset.x ); + WRITE_COORD( vecLookersOffset.y ); + WRITE_COORD( vecLookersOffset.z ); + MESSAGE_END(); + */ + + return TRUE; + } + } + } + } + } + return FALSE; +} + + +//========================================================= +// BuildNearestRoute - tries to build a route as close to the target +// as possible, even if there isn't a path to the final point. +// +// If supplied, search will return a node at least as far +// away as MinDist from vecThreat, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for BuildNearestRoute!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "BuildNearestRoute() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // can I get there? + if (WorldGraph.NextNodeInRoute( iMyNode, nodeNumber, iMyHullIndex, 0 ) != iMyNode) + { + flDist = ( vecThreat - node.m_vecOrigin ).Length(); + + // is it close? + if ( flDist > flMinDist && flDist < flMaxDist) + { + // can I see where I want to be from there? + UTIL_TraceLine( node.m_vecOrigin + pev->view_ofs, vecLookersOffset, ignore_monsters, edict(), &tr ); + + if (tr.flFraction == 1.0) + { + // try to actually get there + if ( BuildRoute ( node.m_vecOrigin, bits_MF_TO_LOCATION, NULL ) ) + { + flMaxDist = flDist; + m_vecMoveGoal = node.m_vecOrigin; + return TRUE; // UNDONE: keep looking for something closer! + } + } + } + } + } + + return FALSE; +} + + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} + + +//========================================================= +// MakeIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the monster's +// ideal_yaw +//========================================================= +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) +{ + Vector vecProjection; + + // strafing monster needs to face 90 degrees away from its goal + if ( m_movementActivity == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else if ( m_movementActivity == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else + { + pev->ideal_yaw = UTIL_VecToYaw ( vecTarget - pev->origin ); + } +} + +//========================================================= +// FlYawDiff - returns the difference ( in degrees ) between +// monster's current yaw and ideal_yaw +// +// Positive result is left turn, negative is right turn +//========================================================= +float CBaseMonster::FlYawDiff ( void ) +{ + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + + if ( flCurrentYaw == pev->ideal_yaw ) + { + return 0; + } + + + return UTIL_AngleDiff( pev->ideal_yaw, flCurrentYaw ); +} + + +//========================================================= +// Changeyaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster::ChangeYaw ( int yawSpeed ) +{ + float ideal, current, move, speed; + + current = UTIL_AngleMod( pev->angles.y ); + ideal = pev->ideal_yaw; + if (current != ideal) + { + speed = (float)yawSpeed * gpGlobals->frametime * 10; + move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + + if (move > 0) + {// turning to the monster's left + if (move > speed) + move = speed; + } + else + {// turning to the monster's right + if (move < -speed) + move = -speed; + } + + pev->angles.y = UTIL_AngleMod (current + move); + + // turn head in desired direction only if they have a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } + else + move = 0; + + return move; +} + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CBaseMonster::VecToYaw ( Vector vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return pev->angles.y; + + return UTIL_VecToYaw( vecDir ); +} + + +//========================================================= +// SetEyePosition +// +// queries the monster's model for $eyeposition and copies +// that vector to the monster's view_ofs +// +//========================================================= +void CBaseMonster :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + ALERT ( at_aiconsole, "%s has no view_ofs!\n", STRING ( pev->classname ) ); + } +} + +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + ALERT( at_aiconsole, "Death event: %s\n", STRING(pev->classname) ); +#endif + pev->health = 0; + } +#if _DEBUG + else + ALERT( at_aiconsole, "INVALID death event:%s\n", STRING(pev->classname) ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_NO; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + pev->health = pev->max_health; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND( edict(), CHAN_BODY, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SOUND_VOICE: + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND( edict(), CHAN_VOICE, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (RANDOM_LONG(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, ATTN_IDLE, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: // Fire a trigger + UTIL_FireTargets( pEvent->options, this, this, USE_TOGGLE ); + break; + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_pCine ) + m_pCine->AllowInterrupt( FALSE ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_pCine ) + m_pCine->AllowInterrupt( TRUE ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't DROP_TO_FLOOR() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + + case MONSTER_EVENT_BODYDROP_HEAVY: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM, 0, 90 ); + } + else + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM, 0, 90 ); + } + } + break; + + case MONSTER_EVENT_BODYDROP_LIGHT: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM ); + } + } + break; + + case MONSTER_EVENT_SWISHSOUND: + { + // NO MONSTER may use this anim event unless that monster's precache precaches this sound!!! + EMIT_SOUND( ENT(pev), CHAN_BODY, "zombie/claw_miss2.wav", 1, ATTN_NORM ); + break; + } + + default: + ALERT( at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname) ); + break; + + } +} + + +// Combat + +Vector CBaseMonster :: GetGunPosition( ) +{ + UTIL_MakeVectors(pev->angles); + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + Vector vecSrc = pev->origin + + gpGlobals->v_forward * m_HackedGunPos.y + + gpGlobals->v_right * m_HackedGunPos.x + + gpGlobals->v_up * m_HackedGunPos.z; + + return vecSrc; +} + + + + + +//========================================================= +// NODE GRAPH +//========================================================= + + + + + +//========================================================= +// FGetNodeRoute - tries to build an entire node path from +// the callers origin to the passed vector. If this is +// possible, ROUTE_SIZE waypoints will be copied into the +// callers m_Route. TRUE is returned if the operation +// succeeds (path is valid) or FALSE if failed (no path +// exists ) +//========================================================= +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) +{ + int iPath[ MAX_PATH_SIZE ]; + int iSrcNode, iDestNode; + int iResult; + int i; + int iNumToCopy; + + iSrcNode = WorldGraph.FindNearestNode ( pev->origin, this ); + iDestNode = WorldGraph.FindNearestNode ( vecDest, this ); + + if ( iSrcNode == -1 ) + { + // no node nearest self +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near self!\n" ); + return FALSE; + } + else if ( iDestNode == -1 ) + { + // no node nearest target +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near target!\n" ); + return FALSE; + } + + // valid src and dest nodes were found, so it's safe to proceed with + // find shortest path + int iNodeHull = WorldGraph.HullIndex( this ); // make this a monster virtual function + iResult = WorldGraph.FindShortestPath ( iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability ); + + if ( !iResult ) + { +#if 1 + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; +#else + BOOL bRoutingSave = WorldGraph.m_fRoutingComplete; + WorldGraph.m_fRoutingComplete = FALSE; + iResult = WorldGraph.FindShortestPath(iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability); + WorldGraph.m_fRoutingComplete = bRoutingSave; + if ( !iResult ) + { + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; + } + else + { + ALERT ( at_aiconsole, "Routing is inconsistent!" ); + } +#endif + } + + // there's a valid path within iPath now, so now we will fill the route array + // up with as many of the waypoints as it will hold. + + // don't copy ROUTE_SIZE entries if the path returned is shorter + // than ROUTE_SIZE!!! + if ( iResult < ROUTE_SIZE ) + { + iNumToCopy = iResult; + } + else + { + iNumToCopy = ROUTE_SIZE; + } + + for ( i = 0 ; i < iNumToCopy; i++ ) + { + m_Route[ i ].vecLocation = WorldGraph.m_pNodes[ iPath[ i ] ].m_vecOrigin; + m_Route[ i ].iType = bits_MF_TO_NODE; + } + + if ( iNumToCopy < ROUTE_SIZE ) + { + m_Route[ iNumToCopy ].vecLocation = vecDest; + m_Route[ iNumToCopy ].iType |= bits_MF_IS_GOAL; + } + + return TRUE; +} + +//========================================================= +// FindHintNode +//========================================================= +int CBaseMonster :: FindHintNode ( void ) +{ + int i; + TraceResult tr; + + if ( !WorldGraph.m_fGraphPresent ) + { + ALERT ( at_aiconsole, "find_hintnode: graph not ready!\n" ); + return NO_NODE; + } + + if ( WorldGraph.m_iLastActiveIdleSearch >= WorldGraph.m_cNodes ) + { + WorldGraph.m_iLastActiveIdleSearch = 0; + } + + for ( i = 0; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastActiveIdleSearch) % WorldGraph.m_cNodes; + CNode &node = WorldGraph.Node( nodeNumber ); + + if ( node.m_sHintType ) + { + // this node has a hint. Take it if it is visible, the monster likes it, and the monster has an animation to match the hint's activity. + if ( FValidateHintType ( node.m_sHintType ) ) + { + if ( !node.m_sHintActivity || LookupActivity ( node.m_sHintActivity ) != ACTIVITY_NOT_AVAILABLE ) + { + UTIL_TraceLine ( pev->origin + pev->view_ofs, node.m_vecOrigin + pev->view_ofs, ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 ) + { + WorldGraph.m_iLastActiveIdleSearch = nodeNumber + 1; // next monster that searches for hint nodes will start where we left off. + return nodeNumber;// take it! + } + } + } + } + } + + WorldGraph.m_iLastActiveIdleSearch = 0;// start at the top of the list for the next search. + + return NO_NODE; +} + + +void CBaseMonster::ReportAIState( void ) +{ + ALERT_TYPE level = at_console; + + static const char *pStateNames[] = { "None", "Idle", "Combat", "Alert", "Hunt", "Prone", "Scripted", "Dead" }; + + ALERT( level, "%s: ", STRING(pev->classname) ); + if ( (int)m_MonsterState < ARRAYSIZE(pStateNames) ) + ALERT( level, "State: %s, ", pStateNames[m_MonsterState] ); + int i = 0; + while ( activity_map[i].type != 0 ) + { + if ( activity_map[i].type == (int)m_Activity ) + { + ALERT( level, "Activity %s, ", activity_map[i].name ); + break; + } + i++; + } + + if ( m_pSchedule ) + { + const char *pName = NULL; + pName = m_pSchedule->pName; + if ( !pName ) + pName = "Unknown"; + ALERT( level, "Schedule %s, ", pName ); + Task_t *pTask = GetTask(); + if ( pTask ) + ALERT( level, "Task %d (#%d), ", pTask->iTask, m_iScheduleIndex ); + } + else + ALERT( level, "No Schedule, " ); + + if ( m_hEnemy != NULL ) + ALERT( level, "\nEnemy is %s", STRING(m_hEnemy->pev->classname) ); + else + ALERT( level, "No enemy" ); + + if ( IsMoving() ) + { + ALERT( level, " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->time ) + ALERT( level, ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->time ); + else if ( m_IdealActivity == GetStoppedActivity() ) + ALERT( level, ": In stopped anim. " ); + } + + CSquadMonster *pSquadMonster = MySquadMonsterPointer(); + + if ( pSquadMonster ) + { + if ( !pSquadMonster->InSquad() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "In Squad, " ); + + if ( !pSquadMonster->IsLeader() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "Leader." ); + } + + ALERT( level, "\n" ); + ALERT( level, "Yaw speed:%3.1f,Health: %3.1f\n", pev->yaw_speed, pev->health ); + if ( pev->spawnflags & SF_MONSTER_PRISONER ) + ALERT( level, " PRISONER! " ); + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + ALERT( level, " Pre-Disaster! " ); + ALERT( level, "\n" ); +} + +//========================================================= +// KeyValue +// +// !!! netname entvar field is used in squadmonster for groupname!!! +//========================================================= +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "TriggerTarget")) + { + m_iszTriggerTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TriggerCondition") ) + { + m_iTriggerCondition = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iClass") ) //LRC + { + m_iClass = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPlayerReact") ) + { + m_iPlayerReact = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "type") ) + { + pev->button = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseAnimating::KeyValue( pkvd ); + } +} + +//========================================================= +// FCheckAITrigger - checks the monster's AI Trigger Conditions, +// if there is a condition, then checks to see if condition is +// met. If yes, the monster's TriggerTarget is fired. +// +// Returns TRUE if the target is fired. +//========================================================= +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + BOOL fFireTarget; + + if ( m_iTriggerCondition == AITRIGGER_NONE ) + { + // no conditions, so this trigger is never fired. + return FALSE; + } + + fFireTarget = FALSE; + + switch ( m_iTriggerCondition ) + { + case AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER: + if ( m_hEnemy != NULL && m_hEnemy->IsPlayer() && HasConditions ( bits_COND_SEE_ENEMY ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_UNCONDITIONAL: + if ( HasConditions ( bits_COND_SEE_CLIENT ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_NOT_IN_COMBAT: + if ( HasConditions ( bits_COND_SEE_CLIENT ) && + m_MonsterState != MONSTERSTATE_COMBAT && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_SCRIPT) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_TAKEDAMAGE: + if ( m_afConditions & ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_DEATH: + if ( pev->deadflag != DEAD_NO ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HALFHEALTH: + if ( IsAlive() && pev->health <= ( pev->max_health / 2 ) ) + { + fFireTarget = TRUE; + } + break; +/* + + // !!!UNDONE - no persistant game state that allows us to track these two. + + case AITRIGGER_SQUADMEMBERDIE: + break; + case AITRIGGER_SQUADLEADERDIE: + break; +*/ + case AITRIGGER_HEARWORLD: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_WORLD ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARPLAYER: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_PLAYER ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARCOMBAT: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_COMBAT ) + { + fFireTarget = TRUE; + } + break; + } + + if ( fFireTarget ) + { + // fire the target, then set the trigger conditions to NONE so we don't fire again + ALERT ( at_aiconsole, "AI Trigger Fire Target\n" ); + UTIL_FireTargets( m_iszTriggerTarget , this, this, USE_TOGGLE ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CanPlaySequence - determines whether or not the monster +// can play the scripted sequence or AI sequence that is +// trying to possess it. If DisregardState is set, the monster +// will be sucked into the script no matter what state it is +// in. ONLY Scripted AI ents should allow this. +//========================================================= + +//LRC - to help debug when sequences won't play... +#define DEBUG_CANTPLAY + +int CBaseMonster :: CanPlaySequence( int interruptFlags ) +{ + if ( m_pCine ) + { + if ( interruptFlags & SS_INTERRUPT_SCRIPTS ) + { + return true; + } + else + { +#ifdef DEBUG_CANTPLAY + ALERT(at_console, "CANTPLAY: Already playing %s \"%s\"!\n", STRING(m_pCine->pev->classname), STRING(m_pCine->pev->targetname)); +#endif + return false; + } + } + else if ( !IsAlive() || m_MonsterState == MONSTERSTATE_PRONE ) + { +#ifdef DEBUG_CANTPLAY + ALERT(at_console, "CANTPLAY: Dead/Barnacled!\n"); +#endif + // monster is already running a scripted sequence or dead! + return FALSE; + } + + if ( interruptFlags & SS_INTERRUPT_ANYSTATE ) + { + // ok to go, no matter what the monster state. (scripted AI) + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE ) + { + // ok to go, but only in these states + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_ALERT && interruptFlags & SS_INTERRUPT_ALERT ) + return TRUE; + + // unknown situation +#ifdef DEBUG_CANTPLAY + ALERT(at_console, "CANTPLAY: non-interruptable state.\n"); +#endif + return FALSE; +} + + +//========================================================= +// FindLateralCover - attempts to locate a spot in the world +// directly to the left or right of the caller that will +// conceal them from view of pSightEnt +//========================================================= +#define COVER_CHECKS 5// how many checks are made +#define COVER_DELTA 48// distance between checks + +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) +{ + TraceResult tr; + Vector vecBestOnLeft; + Vector vecBestOnRight; + Vector vecLeftTest; + Vector vecRightTest; + Vector vecStepRight; + int i; + + UTIL_MakeVectors ( pev->angles ); + vecStepRight = gpGlobals->v_right * COVER_DELTA; + vecStepRight.z = 0; + + vecLeftTest = vecRightTest = pev->origin; + + for ( i = 0 ; i < COVER_CHECKS ; i++ ) + { + vecLeftTest = vecLeftTest - vecStepRight; + vecRightTest = vecRightTest + vecStepRight; + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine( vecThreat + vecViewOffset, vecLeftTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + if ( FValidateCover ( vecLeftTest ) && CheckLocalMove( pev->origin, vecLeftTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecLeftTest ) ) + { + return TRUE; + } + } + } + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine(vecThreat + vecViewOffset, vecRightTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if ( tr.flFraction != 1.0 ) + { + if ( FValidateCover ( vecRightTest ) && CheckLocalMove( pev->origin, vecRightTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecRightTest ) ) + { + return TRUE; + } + } + } + } + + return FALSE; +} + + +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + if (m_pCine != NULL && m_hTargetEnt != NULL && (m_pCine->m_fTurnType == 1)) + { + Vector vecDest = ( m_hTargetEnt->pev->absmin + m_hTargetEnt->pev->absmax ) / 2; + return ( vecDest - shootOrigin ).Normalize(); + } + else if ( m_hEnemy ) + { + return ( (m_hEnemy->BodyTarget( shootOrigin ) - m_hEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + } + else return gpGlobals->v_forward; +} + + + +//========================================================= +// FacingIdeal - tells us if a monster is facing its ideal +// yaw. Created this function because many spots in the +// code were checking the yawdiff against this magic +// number. Nicer to have it in one place if we're gonna +// be stuck with it. +//========================================================= +BOOL CBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( FlYawDiff() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( pszSentence && IsAlive() ) + { + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM ); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + //SetSequenceBox( ); + UTIL_SetOrigin( this, pev->origin );// link into world. + } + else + SetNextThink( 0.1 ); +} + +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( this, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink(&CBaseMonster :: CorpseFallThink ); + SetNextThink( 0.5 ); +} + +//========================================================= +// BBoxIsFlat - check to see if the monster's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +BOOL CBaseMonster :: BBoxFlat ( void ) +{ + TraceResult tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = pev->size.x / 2; + flYSize = pev->size.y / 2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y + flYSize; + vecPoint.z = pev->origin.z; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength = (vecPoint - tr.vecEndPos).Length(); + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y - flYSize; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y + flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y - flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + return TRUE; +} + +//========================================================= +// Get Enemy - tries to find the best suitable enemy for the monster. +//========================================================= +BOOL CBaseMonster :: GetEnemy ( void ) +{ + CBaseEntity *pNewEnemy; + + if ( HasConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_NEMESIS) ) + { + pNewEnemy = BestVisibleEnemy(); + + if ( pNewEnemy != m_hEnemy && pNewEnemy != NULL) + { + // DO NOT mess with the monster's m_hEnemy pointer unless the schedule the monster is currently running will be interrupted + // by COND_NEW_ENEMY. This will eliminate the problem of monsters getting a new enemy while they are in a schedule that doesn't care, + // and then not realizing it by the time they get to a schedule that does. I don't feel this is a good permanent fix. + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + PushEnemy( m_hEnemy, m_vecEnemyLKP ); + SetConditions(bits_COND_NEW_ENEMY); + m_hEnemy = pNewEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + } + // if the new enemy has an owner, take that one as well + if (pNewEnemy->pev->owner != NULL) + { + CBaseEntity *pOwner = GetMonsterPointer( pNewEnemy->pev->owner ); + if ( pOwner && (pOwner->pev->flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) + PushEnemy( pOwner, m_vecEnemyLKP ); + } + } + } + } + + // remember old enemies + if (m_hEnemy == NULL && PopEnemy( )) + { + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + } + + if ( m_hEnemy != NULL ) + { + // monster has an enemy. + return TRUE; + } + + return FALSE;// monster has no enemy +} + +//========================================================= +// SetState +//========================================================= +void CBaseMonster :: SetState ( MONSTERSTATE State ) +{ +/* + if ( State != m_MonsterState ) + { + ALERT ( at_aiconsole, "State Changed to %d\n", State ); + } +*/ + + switch( State ) + { + + // Drop enemy pointers when going to idle + case MONSTERSTATE_IDLE: + + if ( m_hEnemy != NULL ) + { + m_hEnemy = NULL;// not allowed to have an enemy anymore. + ALERT ( at_aiconsole, "Stripped\n" ); + } + break; + } + + m_MonsterState = State; + m_IdealMonsterState = State; +} + +//========================================================= +// RunAI +//========================================================= +void CBaseMonster :: RunAI ( void ) +{ + // to test model's eye height + //UTIL_ParticleEffect ( pev->origin + pev->view_ofs, g_vecZero, 255, 10 ); + + // IDLE sound permitted in ALERT state is because monsters were silent in ALERT state. Only play IDLE sound in IDLE state + // once we have sounds for that state. + if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) == 0 && !(pev->flags & SF_MONSTER_GAG) ) + { + IdleSound(); + } + + if ( m_MonsterState != MONSTERSTATE_NONE && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_DEAD )// don't bother with this crap if monster is prone. + { + // collect some sensory Condition information. + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + // UPDATE: We now let COMBAT state monsters think and act fully outside of player PVS. This allows the player to leave + // an area where monsters are fighting, and the fight will continue. + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) || ( m_MonsterState == MONSTERSTATE_COMBAT ) || HaveCamerasInPVS( edict() )) + { + Look( m_flDistLook ); + Listen();// check for audible sounds. + + // now filter conditions. + ClearConditions( IgnoreConditions() ); + + GetEnemy(); + } + + // do these calculations if monster has an enemy. + if ( m_hEnemy != NULL ) + { + CheckEnemy( m_hEnemy ); + } + + CheckAmmo(); + } + + FCheckAITrigger(); + + PrescheduleThink(); + + MaintainSchedule(); + + // if the monster didn't use these conditions during the above call to MaintainSchedule() or CheckAITrigger() + // we throw them out cause we don't want them sitting around through the lifespan of a schedule + // that doesn't use them. + m_afConditions &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + + /* + IDLE goes to ALERT upon hearing a sound + -IDLE goes to ALERT upon being injured + IDLE goes to ALERT upon seeing food + -IDLE goes to COMBAT upon sighting an enemy + IDLE goes to HUNT upon smelling food + */ + { + if ( iConditions & bits_COND_NEW_ENEMY ) + { + // new enemy! This means an idle monster has seen someone it dislikes, or + // that a monster in combat has found a more suitable target to attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_LIGHT_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAVY_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + CSound *pSound; + + pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + { + MakeIdealYaw ( pSound->m_vecOrigin ); + if ( pSound->m_iType & (bits_SOUND_COMBAT|bits_SOUND_DANGER) ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + } + else if ( iConditions & (bits_COND_SMELL | bits_COND_SMELL_FOOD) ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + + break; + } + case MONSTERSTATE_ALERT: + /* + ALERT goes to IDLE upon becoming bored + -ALERT goes to COMBAT upon sighting an enemy + ALERT goes to HUNT upon hearing a noise + */ + { + if ( iConditions & (bits_COND_NEW_ENEMY|bits_COND_SEE_ENEMY) ) + { + // see an enemy we MUST attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + CSound *pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + MakeIdealYaw ( pSound->m_vecOrigin ); + } + break; + } + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to HUNT upon losing sight of enemy + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy == NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + // pev->effects = EF_BRIGHTFIELD; + ALERT ( at_aiconsole, "***Combat state with no enemy!\n" ); + } + break; + } + case MONSTERSTATE_HUNT: + /* + HUNT goes to ALERT upon seeing food + HUNT goes to ALERT upon being injured + HUNT goes to IDLE if goal touched + HUNT goes to COMBAT upon seeing enemy + */ + { + break; + } + case MONSTERSTATE_SCRIPT: + if ( iConditions & (bits_COND_TASK_FAILED|bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) ) + { + ExitScriptedSequence(); // This will set the ideal state + } + break; + + case MONSTERSTATE_DEAD: + m_IdealMonsterState = MONSTERSTATE_DEAD; + break; + } + + return m_IdealMonsterState; +} + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + // if we're here, then either we're being told to do something (besides dying or playing a script) + // or our current schedule (besides dying) is invalid. -- LRC + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + { + pNewSchedule = CBaseMonster::GetSchedule(); + } + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) || HaveCamerasInPVS( edict() )) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + } + break; + } + case TASK_PLAY_SCRIPT: + { +// ALERT(at_console, "Play Script\n"); + if (m_fSequenceFinished) + { +// ALERT(at_console, "Anim Finished\n"); + if (m_pCine->m_iRepeatsLeft > 0) + { +// ALERT(at_console, "Frame %f; Repeat %d from %f\n", pev->frame, m_pCine->m_iRepeatsLeft, m_pCine->m_fRepeatFrame); + m_pCine->m_iRepeatsLeft--; + pev->frame = m_pCine->m_fRepeatFrame; + ResetSequenceInfo( ); + } + else + { + TaskComplete(); + } + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_SCRIPT: + case TASK_WALK_TO_SCRIPT: + { + Activity newActivity; + + if ( !m_pGoalEnt || (m_pGoalEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_SCRIPT ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_pGoalEnt != NULL ) + { + Vector vecDest; + vecDest = m_pGoalEnt->pev->origin; + + if ( !MoveToLocation( newActivity, 2, vecDest ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach script!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s: MoveTarget is missing!?!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_SCRIPT: + { + RouteClear(); + if ( m_pCine != NULL && MoveToLocation( m_movementActivity, 1, m_pCine->pev->origin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); //LRC - start playing immediately + } + else if (!m_pCine->IsAction() && m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_pCine->IsAction()) + { + //ALERT(at_console,"PlayScript: setting idealactivity %d\n",m_pCine->m_fAction); + switch(m_pCine->m_fAction) + { + case 0: + m_IdealActivity = ACT_RANGE_ATTACK1; break; + case 1: + m_IdealActivity = ACT_RANGE_ATTACK2; break; + case 2: + m_IdealActivity = ACT_MELEE_ATTACK1; break; + case 3: + m_IdealActivity = ACT_MELEE_ATTACK2; break; + case 4: + m_IdealActivity = ACT_SPECIAL_ATTACK1; break; + case 5: + m_IdealActivity = ACT_SPECIAL_ATTACK2; break; + case 6: + m_IdealActivity = ACT_RELOAD; break; + case 7: + m_IdealActivity = ACT_HOP; break; + } + pev->framerate = 1.0; // shouldn't be needed, but just in case + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + } + else + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } +//LRC + case TASK_END_SCRIPT: + { + m_pCine->SequenceDone( this ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_pCine != NULL ) + { + // Plant on script + // LRC - if it's a teleport script, do the turn too + if (m_pCine->m_fMoveTo == 4 || m_pCine->m_fMoveTo == 6) + { + if (m_pCine->m_fTurnType == 0) //LRC + pev->angles.y = m_hTargetEnt->pev->angles.y; + else if (m_pCine->m_fTurnType == 1) + pev->angles.y = UTIL_VecToYaw(m_hTargetEnt->pev->origin - pev->origin); + pev->ideal_yaw = pev->angles.y; + pev->avelocity = Vector( 0, 0, 0 ); + pev->velocity = Vector( 0, 0, 0 ); + pev->effects |= EF_NOINTERP; + } + + if (m_pCine->m_fMoveTo != 6) + pev->origin = m_pGoalEnt->pev->origin; + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_pCine != NULL && m_pCine->m_fMoveTo != 0) // movetype "no move" makes us ignore turntype + { + switch (m_pCine->m_fTurnType) + { + case 0: + pev->ideal_yaw = UTIL_AngleMod( m_pCine->pev->angles.y ); + break; + case 1: + // yes, this is inconsistent- turn to face uses the "target" and turn to angle uses the "cine". + if (m_hTargetEnt) + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + else + MakeIdealYaw ( m_pCine->pev->origin ); + break; + // default: don't turn + } + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); +// ALERT( at_console, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} + +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= DMG_HEAD; + break; + case HITGROUP_CHEST: + flDamage *= DMG_CHEST; + break; + case HITGROUP_STOMACH: + flDamage *= DMG_STOMACH; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= DMG_ARM; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= DMG_LEG; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + return TRUE; + return FALSE; +} + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK + +Time-based damage: only occurs while the monster is within the trigger_hurt. +When a monster is poisoned via an arrow etc it takes all the poison damage at once. +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // do the damage + pev->health -= flTake; + + + // HACKHACK Don't kill monsters in a script. Let them break their scripts first + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + else if ( bitsDamageType & DMG_NEVERGIB ) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + //LRC - new behaviours, for m_iPlayerReact. + if (pevAttacker->flags & FL_CLIENT) + { + if (m_iPlayerReact == 2) + { + // just get angry. + Remember( bits_MEMORY_PROVOKED ); + } + else if (m_iPlayerReact == 3) + { + // try to decide whether it was deliberate... if I have an enemy, assume it was just crossfire. + if ( m_hEnemy == NULL ) + { + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || UTIL_IsFacing( pevAttacker, pev->origin ) ) + Remember( bits_MEMORY_PROVOKED ); + else + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + } + + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + MakeIdealYaw( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +int CBaseMonster :: TakeArmor (float flArmor, int suit) +{ + if (!pev->takedamage) return 0; + return CBaseEntity::TakeArmor(flArmor, suit); +} + +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + // these types of monster don't use gibs + if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE || myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON) + { + return FALSE; + } + else + { + return (this->m_bloodColor == BLOOD_COLOR_RED); + } +} + + +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + // these types of monster don't use gibs + if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE || myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON) + { + return FALSE; + } + else + { + return (this->m_bloodColor == BLOOD_COLOR_GREEN); + } +} + + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + int iszCustomGibs; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + if ( iszCustomGibs = HasCustomGibs() ) + { + CGib::SpawnHeadGib( this, iszCustomGibs ); + CGib::SpawnRandomGibs( this, 4, 1, iszCustomGibs ); + CGib::SpawnStickyGibs (this, 4, iszCustomGibs ); + gibbed = TRUE; + } + else if ( HasHumanGibs() ) + { + CGib::SpawnHeadGib( this ); + CGib::SpawnRandomGibs( this, 4, 1 ); // throw some human gibs. + CGib::SpawnStickyGibs (this, 4 ); + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + CGib::SpawnRandomGibs( this, 4, 0 ); // Throw alien gibs + gibbed = TRUE; + } + if ( !IsPlayer() && gibbed ) UTIL_Remove (this); + else + { + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < -30 ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + + +void CBaseMonster::CallGibMonster( void ) +{ + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + + pev->effects = EF_NODRAW; // make the model invisible. + GibMonster(); + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) pev->health = 0; +} + + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) pev->health = 0; + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ) * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + + +//========================================================= +// DropItem - dead monster drops named item +//========================================================= +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) +{ + if ( !pszItemName ) + { + ALERT ( at_console, "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, edict() ); + + if ( pItem ) + { + // do we want this behavior to be default?! (sjb) + pItem->pev->velocity = pev->velocity; + pItem->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 ); + return pItem; + } + else + { + ALERT ( at_console, "DropItem() - Didn't create!\n" ); + return FALSE; + } + +} + +//LRC - an entity for monsters to shoot at. +#define SF_MONSTERTARGET_OFF 1 +class CMonsterTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify( void ) { return pev->frags; }; + STATE GetState( void ) + { + return pev->health?STATE_ON:STATE_OFF; + }; +}; +LINK_ENTITY_TO_CLASS( monster_target, CMonsterTarget ); + +void CMonsterTarget :: Spawn ( void ) +{ + if (pev->spawnflags & SF_MONSTERTARGET_OFF) + pev->health = 0; + else pev->health = 1; // Don't ignore me, I'm not dead. I'm quite well really. I think I'll go for a walk... + SetBits (pev->flags, FL_MONSTER); +} + +void CMonsterTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (useType == USE_TOGGLE) + { + if(pev->health) useType = USE_OFF; + else useType = USE_ON; + } + if (useType == USE_ON)pev->health = 1; + else if(useType == USE_OFF)pev->health = 0; +} \ No newline at end of file diff --git a/server/monsters/basemonster.h b/server/monsters/basemonster.h new file mode 100644 index 00000000..33b18cda --- /dev/null +++ b/server/monsters/basemonster.h @@ -0,0 +1,352 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +// +// generic Monster +// +class CBaseMonster : public CBaseAnimating +{ +private: + int m_afConditions; + +public: + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual STATE GetState( void ) { return (pev->deadflag == DEAD_DEAD)?STATE_OFF:STATE_ON; }; + + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + // LRC- to allow level-designers to change monster allegiances + int m_iClass; + int m_iPlayerReact; + virtual int Classify( void ) { return m_iClass?m_iClass:CLASS_NONE; } + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + virtual void RunAI ( void );// core ai function! + void Listen ( void ); + + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + +// Basic Monster AI functions + virtual float ChangeYaw ( int speed ); + float VecToYaw( Vector vecDir ); + float FlYawDiff ( void ); + + float DamageForce( float damage ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + virtual void ScheduleChange( void ) {} +// virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( int interruptFlags ); +// virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + void MakeIdealYaw( Vector vecTarget ); + virtual void SetYawSpeed ( void ) { return; };// allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( void ); + + void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + BOOL GetEnemy ( void ); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + virtual int HasCustomGibs( void ) { return FALSE; } //LRC + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + + Vector ShootAtEnemy( const Vector &shootOrigin ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + + virtual Vector GetGunPosition( void ); + + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeArmor( float flArmor, int suit = 0 ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } + + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + + virtual void StopFollowing( BOOL clearSchedule ) {} + + inline void Remember( int iMemory ) { m_afMemory |= iMemory; } + inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } + inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } + inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } + + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + + void StartPatrol( CBaseEntity *path ); + + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. +}; + + + +#endif // BASEMONSTER_H diff --git a/server/monsters/combat.cpp b/server/monsters/combat.cpp new file mode 100644 index 00000000..a3295adb --- /dev/null +++ b/server/monsters/combat.cpp @@ -0,0 +1,261 @@ +/*** +* +* 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. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "baseweapon.h" +#include "basebrush.h" +#include "defaults.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +MULTIDAMAGE gMultiDamage; + + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType ) +{ + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + if ( VARS(pTrace->pHit)->solid == SOLID_BSP || VARS(pTrace->pHit)->movetype == MOVETYPE_PUSHSTEP ) + { + CBaseEntity *pEntity = NULL; + // Decal the wall with a gunshot + if ( !FNullEnt(pTrace->pHit) ) + pEntity = CBaseEntity::Instance(pTrace->pHit); + + switch( iBulletType ) + { + case BULLET_9MM: + case BULLET_MP5: + case BULLET_BUCKSHOT: + case BULLET_357: + default: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_12MM: + // smoke and decal + UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); + break; + case BULLET_CROWBAR: + // wall decal + UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); + break; + } + } +} + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 25 );// 2.5 seconds + MESSAGE_END(); +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if ( !pEntity ) + return (DECAL_GUNSHOT1 + RANDOM_LONG(0,4)); + + return pEntity->DamageDecal( bitsDamageType ); +} + + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) & MASK_WATER ); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + + + diff --git a/server/monsters/damage.h b/server/monsters/damage.h new file mode 100644 index 00000000..0761e7a1 --- /dev/null +++ b/server/monsters/damage.h @@ -0,0 +1,21 @@ +//======================================================================= +// Copyright (C) XashXT Group 2006 +//======================================================================= + +extern void ClearMultiDamage(void); +extern void ApplyMultiDamage(entvars_t* pevInflictor, entvars_t* pevAttacker ); +extern void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType); + +extern void DecalGunshot( TraceResult *pTrace, int iBulletType ); +extern void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage); +extern int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ); +extern void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ); + +typedef struct +{ + CBaseEntity *pEntity; + float amount; + int type; +} MULTIDAMAGE; + +extern MULTIDAMAGE gMultiDamage; \ No newline at end of file diff --git a/server/monsters/defaultai.cpp b/server/monsters/defaultai.cpp new file mode 100644 index 00000000..2e17c687 --- /dev/null +++ b/server/monsters/defaultai.cpp @@ -0,0 +1,1260 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "soundent.h" +#include "nodes.h" +#include "scripted.h" + +//========================================================= +// Fail +//========================================================= +Task_t tlFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slFail[] = +{ + { + tlFail, + ARRAYSIZE ( tlFail ), + bits_COND_CAN_ATTACK, + 0, + "Fail" + }, +}; + +//========================================================= +// Idle Schedules +//========================================================= +Task_t tlIdleStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)5 },// repick IDLESTAND every five seconds. gives us a chance to pick an active idle, fidget, etc. +}; + +Schedule_t slIdleStand[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +Schedule_t slIdleTrigger[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Trigger" + }, +}; + + +Task_t tlIdleWalk1[] = +{ + { TASK_WALK_PATH, (float)9999 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slIdleWalk[] = +{ + { + tlIdleWalk1, + ARRAYSIZE ( tlIdleWalk1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Idle Walk" + }, +}; + +//========================================================= +// Ambush - monster stands in place and waits for a new +// enemy, or chance to attack an existing enemy. +//========================================================= +Task_t tlAmbush[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slAmbush[] = +{ + { + tlAmbush, + ARRAYSIZE ( tlAmbush ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + + 0, + "Ambush" + }, +}; + +//========================================================= +// ActiveIdle schedule - !!!BUGBUG - if this schedule doesn't +// complete on its own, the monster's HintNode will not be +// cleared, and the rest of the monster's group will avoid +// that node because they think the group member that was +// previously interrupted is still using that node to active +// idle. +///========================================================= +Task_t tlActiveIdle[] = +{ + { TASK_FIND_HINTNODE, (float)0 }, + { TASK_GET_PATH_TO_HINTNODE, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_HINTNODE, (float)0 }, + { TASK_PLAY_ACTIVE_IDLE, (float)0 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, + { TASK_CLEAR_HINTNODE, (float)0 }, +}; + +Schedule_t slActiveIdle[] = +{ + { + tlActiveIdle, + ARRAYSIZE( tlActiveIdle ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT | + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER, + "Active Idle" + } +}; + +//========================================================= +// Wake Schedules +//========================================================= +Task_t tlWakeAngry1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slWakeAngry[] = +{ + { + tlWakeAngry1, + ARRAYSIZE ( tlWakeAngry1 ), + 0, + 0, + "Wake Angry" + } +}; + +//========================================================= +// AlertFace Schedules +//========================================================= +Task_t tlAlertFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slAlertFace[] = +{ + { + tlAlertFace1, + ARRAYSIZE ( tlAlertFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + 0, + "Alert Face" + }, +}; + +//========================================================= +// AlertSmallFlinch Schedule - shot, but didn't see attacker, +// flinch then face +//========================================================= +Task_t tlAlertSmallFlinch[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_SMALL_FLINCH, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_ALERT_FACE }, +}; + +Schedule_t slAlertSmallFlinch[] = +{ + { + tlAlertSmallFlinch, + ARRAYSIZE ( tlAlertSmallFlinch ), + 0, + 0, + "Alert Small Flinch" + }, +}; + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAlertStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)20 }, + { TASK_SUGGEST_STATE, (float)MONSTERSTATE_IDLE }, +}; + +Schedule_t slAlertStand[] = +{ + { + tlAlertStand1, + ARRAYSIZE ( tlAlertStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_SMELL | + bits_COND_SMELL_FOOD | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scent flags + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Alert Stand" + }, +}; + +//========================================================= +// InvestigateSound - sends a monster to the location of the +// sound that was just heard, to check things out. +//========================================================= +Task_t tlInvestigateSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSOUND, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE }, + { TASK_WAIT, (float)10 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slInvestigateSound[] = +{ + { + tlInvestigateSound, + ARRAYSIZE ( tlInvestigateSound ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "InvestigateSound" + }, +}; + +//========================================================= +// CombatIdle Schedule +//========================================================= +Task_t tlCombatStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slCombatStand[] = +{ + { + tlCombatStand1, + ARRAYSIZE ( tlCombatStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_ATTACK, + 0, + "Combat Stand" + }, +}; + +//========================================================= +// CombatFace Schedule +//========================================================= +Task_t tlCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slCombatFace[] = +{ + { + tlCombatFace1, + ARRAYSIZE ( tlCombatFace1 ), + bits_COND_CAN_ATTACK | + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slStandoff[] = +{ + { + tlStandoff, + ARRAYSIZE ( tlStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_ENEMY_DEAD | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Standoff" + } +}; + +//========================================================= +// Arm weapon (draw gun) +//========================================================= +Task_t tlArmWeapon[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float) ACT_ARM } +}; + +Schedule_t slArmWeapon[] = +{ + { + tlArmWeapon, + ARRAYSIZE ( tlArmWeapon ), + 0, + 0, + "Arm Weapon" + } +}; + +//========================================================= +// reload schedule +//========================================================= +Task_t tlReload[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, float(ACT_RELOAD) }, +}; + +Schedule_t slReload[] = +{ + { + tlReload, + ARRAYSIZE ( tlReload ), + bits_COND_HEAVY_DAMAGE, + 0, + "Reload" + } +}; + +//========================================================= +// Attack Schedules +//========================================================= + +// primary range attack +Task_t tlRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slRangeAttack1[] = +{ + { + tlRangeAttack1, + ARRAYSIZE ( tlRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1" + }, +}; + +// secondary range attack +Task_t tlRangeAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, +}; + +Schedule_t slRangeAttack2[] = +{ + { + tlRangeAttack2, + ARRAYSIZE ( tlRangeAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack2" + }, +}; + +// primary melee attack +Task_t tlPrimaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slPrimaryMeleeAttack[] = +{ + { + tlPrimaryMeleeAttack1, + ARRAYSIZE ( tlPrimaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Primary Melee Attack" + }, +}; + +// secondary melee attack +Task_t tlSecondaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK2, (float)0 }, +}; + +Schedule_t slSecondaryMeleeAttack[] = +{ + { + tlSecondaryMeleeAttack1, + ARRAYSIZE ( tlSecondaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Secondary Melee Attack" + }, +}; + +// special attack1 +Task_t tlSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, +}; + +Schedule_t slSpecialAttack1[] = +{ + { + tlSpecialAttack1, + ARRAYSIZE ( tlSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack1" + }, +}; + +// special attack2 +Task_t tlSpecialAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK2, (float)0 }, +}; + +Schedule_t slSpecialAttack2[] = +{ + { + tlSpecialAttack2, + ARRAYSIZE ( tlSpecialAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack2" + }, +}; + +// Chase enemy schedule +Task_t tlChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slChaseEnemy[] = +{ + { + tlChaseEnemy1, + ARRAYSIZE ( tlChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Chase Enemy" + }, +}; + + +// Chase enemy failure schedule +Task_t tlChaseEnemyFailed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slChaseEnemyFailed[] = +{ + { + tlChaseEnemyFailed, + ARRAYSIZE ( tlChaseEnemyFailed ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "tlChaseEnemyFailed" + }, +}; + + +//========================================================= +// small flinch, played when minor damage is taken. +//========================================================= +Task_t tlSmallFlinch[] = +{ + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_STOP_MOVING, 0 }, + { TASK_SMALL_FLINCH, 0 }, +}; + +Schedule_t slSmallFlinch[] = +{ + { + tlSmallFlinch, + ARRAYSIZE ( tlSmallFlinch ), + 0, + 0, + "Small Flinch" + }, +}; + +//========================================================= +// Die! +//========================================================= +Task_t tlDie1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slDie[] = +{ + { + tlDie1, + ARRAYSIZE( tlDie1 ), + 0, + 0, + "Die" + }, +}; + +//========================================================= +// Victory Dance +//========================================================= +Task_t tlVictoryDance[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_WAIT, (float)0 }, +}; + +Schedule_t slVictoryDance[] = +{ + { + tlVictoryDance, + ARRAYSIZE( tlVictoryDance ), + 0, + 0, + "Victory Dance" + }, +}; + +//========================================================= +// BarnacleVictimGrab - barnacle tongue just hit the monster, +// so play a hit animation, then play a cycling pull animation +// as the creature is hoisting the monster. +//========================================================= +Task_t tlBarnacleVictimGrab[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_HIT }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_PULL }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimGrab[] = +{ + { + tlBarnacleVictimGrab, + ARRAYSIZE ( tlBarnacleVictimGrab ), + 0, + 0, + "Barnacle Victim" + } +}; + +//========================================================= +// BarnacleVictimChomp - barnacle has pulled the prey to its +// mouth. Victim should play the BARNCLE_CHOMP animation +// once, then loop the BARNACLE_CHEW animation indefinitely +//========================================================= +Task_t tlBarnacleVictimChomp[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_CHOMP }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_CHEW }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimChomp[] = +{ + { + tlBarnacleVictimChomp, + ARRAYSIZE ( tlBarnacleVictimChomp ), + 0, + 0, + "Barnacle Chomp" + } +}; + + +// Universal Error Schedule +Task_t tlError[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slError[] = +{ + { + tlError, + ARRAYSIZE ( tlError ), + 0, + 0, + "Error" + }, +}; + +//LRC +Task_t tlScriptedTeleport[] = +{ + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +//LRC +Schedule_t slTeleportToScript[] = +{ + { + tlScriptedTeleport, + ARRAYSIZE ( tlScriptedTeleport ), + SCRIPT_BREAK_CONDITIONS, + 0, + "TeleportToScript" + }, +}; + +Task_t tlScriptedWalk[] = +{ + { TASK_WALK_TO_SCRIPT, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slWalkToScript[] = +{ + { + tlScriptedWalk, + ARRAYSIZE ( tlScriptedWalk ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WalkToScript" + }, +}; + + +Task_t tlScriptedRun[] = +{ + { TASK_RUN_TO_SCRIPT, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slRunToScript[] = +{ + { + tlScriptedRun, + ARRAYSIZE ( tlScriptedRun ), + SCRIPT_BREAK_CONDITIONS, + 0, + "RunToScript" + }, +}; + +Task_t tlScriptedWait[] = +{ + { TASK_STOP_MOVING, 0 }, +// { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slWaitScript[] = +{ + { + tlScriptedWait, + ARRAYSIZE ( tlScriptedWait ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WaitForScript" + }, +}; + +Task_t tlScriptedFace[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slFaceScript[] = +{ + { + tlScriptedFace, + ARRAYSIZE ( tlScriptedFace ), + SCRIPT_BREAK_CONDITIONS, + 0, + "FaceScript" + }, +}; + +//========================================================= +// Cower - this is what is usually done when attempts +// to escape danger fail. +//========================================================= +Task_t tlCower[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_COWER }, +}; + +Schedule_t slCower[] = +{ + { + tlCower, + ARRAYSIZE ( tlCower ), + 0, + 0, + "Cower" + }, +}; + +//========================================================= +// move away from where you're currently standing. +//========================================================= +Task_t tlTakeCoverFromOrigin[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ORIGIN, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromOrigin[] = +{ + { + tlTakeCoverFromOrigin, + ARRAYSIZE ( tlTakeCoverFromOrigin ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromOrigin" + }, +}; + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlTakeCoverFromBestSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromBestSound[] = +{ + { + tlTakeCoverFromBestSound, + ARRAYSIZE ( tlTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromBestSound" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slTakeCoverFromEnemy[] = +{ + { + tlTakeCoverFromEnemy, + ARRAYSIZE ( tlTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "tlTakeCoverFromEnemy" + }, +}; + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ + slIdleStand, + slIdleTrigger, + slIdleWalk, + slAmbush, + slActiveIdle, + slWakeAngry, + slAlertFace, + slAlertSmallFlinch, + slAlertStand, + slInvestigateSound, + slCombatStand, + slCombatFace, + slStandoff, + slArmWeapon, + slReload, + slRangeAttack1, + slRangeAttack2, + slPrimaryMeleeAttack, + slSecondaryMeleeAttack, + slSpecialAttack1, + slSpecialAttack2, + slChaseEnemy, + slChaseEnemyFailed, + slSmallFlinch, + slDie, + slVictoryDance, + slBarnacleVictimGrab, + slBarnacleVictimChomp, + slError, + slWalkToScript, + slRunToScript, + slWaitScript, + slFaceScript, + slCower, + slTakeCoverFromOrigin, + slTakeCoverFromBestSound, + slTakeCoverFromEnemy, + slFail +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_console, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_console, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI. // LRC- And scripted actions, too. + case SCHED_AISCRIPT: + { +// ALERT(at_console, "Doing AISCRIPT\n"); + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + return slWaitScript; + case 4: case 6: + return slTeleportToScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + default: + { + ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/server/monsters/defaultai.h b/server/monsters/defaultai.h new file mode 100644 index 00000000..652d1085 --- /dev/null +++ b/server/monsters/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/server/monsters/flyingmonster.cpp b/server/monsters/flyingmonster.cpp new file mode 100644 index 00000000..cc475463 --- /dev/null +++ b/server/monsters/flyingmonster.cpp @@ -0,0 +1,281 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" + +#define FLYING_AE_FLAP (8) +#define FLYING_AE_FLAPSOUND (9) + + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +int CFlyingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + // UNDONE: need to check more than the endpoint + if (FBitSet( pev->flags, FL_SWIM) && (!(UTIL_PointContents( vecEnd ) & MASK_WATER ))) + { + // ALERT(at_aiconsole, "can't swim out of water\n"); + return FALSE; + } + + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32 ), vecEnd + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +BOOL CFlyingMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + return CBaseMonster::FTriangulate( vecStart, vecEnd, flDist, pTargetEnt, pApex ); +} + + +Activity CFlyingMonster :: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + + return ACT_HOVER; +} + + +void CFlyingMonster :: Stop( void ) +{ + Activity stopped = GetStoppedActivity(); + if ( m_IdealActivity != stopped ) + { + m_flightSpeed = 0; + m_IdealActivity = stopped; + } + pev->angles.z = 0; + pev->angles.x = 0; + m_vecTravel = g_vecZero; +} + + +float CFlyingMonster :: ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlYawDiff(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 90; + else if ( diff > 20 ) + target = -90; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * gpGlobals->frametime ); + } + return CBaseMonster::ChangeYaw( speed ); +} + + +void CFlyingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + CBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CFlyingMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case FLYING_AE_FLAP: + m_flightSpeed = 400; + break; + + case FLYING_AE_FLAPSOUND: + if ( m_pFlapSound ) + EMIT_SOUND( edict(), CHAN_BODY, m_pFlapSound, 1, ATTN_NORM ); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CFlyingMonster :: Move( float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_flightSpeed; + CBaseMonster::Move( flInterval ); +} + + +BOOL CFlyingMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + // Get true 3D distance to the goal so we actually reach the correct height + if ( m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL ) + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + if ( flWaypointDist <= 64 + (m_flGroundSpeed * gpGlobals->frametime) ) + return TRUE; + + return FALSE; +} + + +void CFlyingMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + if ( gpGlobals->time - m_stopTime > 1.0 ) + { + if ( m_IdealActivity != m_movementActivity ) + { + m_IdealActivity = m_movementActivity; + m_flGroundSpeed = m_flightSpeed = 200; + } + } + Vector vecMove = pev->origin + (( vecDir + (m_vecTravel * m_momentum) ).Normalize() * (m_flGroundSpeed * flInterval)); + + if ( m_IdealActivity != m_movementActivity ) + { + m_flightSpeed = UTIL_Approach( 100, m_flightSpeed, 75 * gpGlobals->frametime ); + if ( m_flightSpeed < 100 ) + m_stopTime = gpGlobals->time; + } + else + m_flightSpeed = UTIL_Approach( 20, m_flightSpeed, 300 * gpGlobals->frametime ); + + if ( CheckLocalMove ( pev->origin, vecMove, pTargetEnt, NULL ) ) + { + m_vecTravel = (vecMove - pev->origin); + m_vecTravel = m_vecTravel.Normalize(); + UTIL_MoveToOrigin(ENT(pev), vecMove, (m_flGroundSpeed * flInterval), MOVE_STRAFE); + } + else + { + m_IdealActivity = GetStoppedActivity(); + m_stopTime = gpGlobals->time; + m_vecTravel = g_vecZero; + } + } + else + CBaseMonster::MoveExecute( pTargetEnt, vecDir, flInterval ); +} + + +float CFlyingMonster::CeilingZ( const Vector &position ) +{ + TraceResult tr; + + Vector minUp = position; + Vector maxUp = position; + maxUp.z += 4096.0; + + UTIL_TraceLine(position, maxUp, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + maxUp.z = tr.vecEndPos.z; + + if( pev->flags & FL_SWIM ) + { + return UTIL_WaterLevel( position, minUp.z, maxUp.z ); + } + return maxUp.z; +} + +BOOL CFlyingMonster::ProbeZ( const Vector &position, const Vector &probe, float *pFraction) +{ + int conPosition = UTIL_PointContents( position ); + if ( ((pev->flags & FL_SWIM) == FL_SWIM) ^ (conPosition & CONTENTS_WATER)) + { + // SWIMING & !WATER + // or FLYING & WATER + // + *pFraction = 0.0; + return TRUE; // We hit a water boundary because we are where we don't belong. + } + int conProbe = UTIL_PointContents(probe); + if( conProbe == conPosition ) + { + // The probe is either entirely inside the water (for fish) or entirely + // outside the water (for birds). + // + *pFraction = 1.0; + return FALSE; + } + + Vector ProbeUnit = (probe-position).Normalize(); + float ProbeLength = (probe-position).Length(); + float maxProbeLength = ProbeLength; + float minProbeLength = 0; + + float diff = maxProbeLength - minProbeLength; + while (diff > 1.0) + { + float midProbeLength = minProbeLength + diff/2.0; + Vector midProbeVec = midProbeLength * ProbeUnit; + if( UTIL_PointContents( position + midProbeVec ) == conPosition ) + { + minProbeLength = midProbeLength; + } + else + { + maxProbeLength = midProbeLength; + } + diff = maxProbeLength - minProbeLength; + } + *pFraction = minProbeLength/ProbeLength; + + return TRUE; +} + +float CFlyingMonster::FloorZ( const Vector &position ) +{ + TraceResult tr; + + Vector down = position; + down.z -= 2048; + + UTIL_TraceLine( position, down, ignore_monsters, NULL, &tr ); + + if ( tr.flFraction != 1.0 ) + return tr.vecEndPos.z; + + return down.z; +} + diff --git a/server/monsters/flyingmonster.h b/server/monsters/flyingmonster.h new file mode 100644 index 00000000..616194db --- /dev/null +++ b/server/monsters/flyingmonster.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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +// Base class for flying monsters. This overrides the movement test & execution code from CBaseMonster + +#ifndef FLYINGMONSTER_H +#define FLYINGMONSTER_H + +class CFlyingMonster : public CBaseMonster +{ +public: + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + Activity GetStoppedActivity( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Stop( void ); + float ChangeYaw( int speed ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void Move( float flInterval = 0.1 ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + + inline void SetFlyingMomentum( float momentum ) { m_momentum = momentum; } + inline void SetFlyingFlapSound( const char *pFlapSound ) { m_pFlapSound = pFlapSound; } + inline void SetFlyingSpeed( float speed ) { m_flightSpeed = speed; } + float CeilingZ( const Vector &position ); + float FloorZ( const Vector &position ); + BOOL ProbeZ( const Vector &position, const Vector &probe, float *pFraction ); + + + // UNDONE: Save/restore this stuff!!! +protected: + Vector m_vecTravel; // Current direction + float m_flightSpeed; // Current flight speed (decays when not flapping or gliding) + float m_stopTime; // Last time we stopped (to avoid switching states too soon) + float m_momentum; // Weight for desired vs. momentum velocity + const char *m_pFlapSound; +}; + + +#endif //FLYINGMONSTER_H + diff --git a/server/monsters/generic.cpp b/server/monsters/generic.cpp new file mode 100644 index 00000000..e50a63d0 --- /dev/null +++ b/server/monsters/generic.cpp @@ -0,0 +1,395 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Generic Monster - purely for scripted sequence work. +//========================================================= +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "talkmonster.h" +#include "basebeams.h" + +// For holograms, make them not solid so the player can walk through them +//LRC- this seems to interfere with SF_MONSTER_CLIP +#define SF_GENERICMONSTER_NOTSOLID 4 +#define SF_HEAD_CONTROLLER 8 +#define SF_GENERICMONSTER_INVULNERABLE 32 +#define SF_GENERICMONSTER_PLAYERMODEL 64 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +//G-Cont. This code - support for htorch model from Op4 ;) +#define HTORCH_AE_SHOWGUN ( 17) +#define HTORCH_AE_SHOWTORCH ( 18) +#define HTORCH_AE_HIDETORCH ( 19) +#define HTORCH_AE_ONGAS ( 20) +#define HTORCH_AE_OFFGAS ( 21) +#define GUN_DEAGLE 0 +#define GUN_TORCH 1 +#define GUN_NONE 2 + +class CGenericMonster : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + void KeyValue( KeyValueData *pkvd ); + void Torch ( void ); + void MakeGas( void ); + void UpdateGas( void ); + void KillGas( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + CBeam *m_pBeam; + int m_iszGibModel; +}; +LINK_ENTITY_TO_CLASS( monster_generic, CGenericMonster ); + +TYPEDESCRIPTION CGenericMonster::m_SaveData[] = +{ + DEFINE_FIELD( CGenericMonster, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGenericMonster, CBaseMonster ); + +void CGenericMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_bloodColor")) + { + m_bloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszGibModel")) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGenericMonster :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGenericMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGenericMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HTORCH_AE_SHOWTORCH: + pev->body = GUN_NONE; + pev->body = GUN_TORCH; + break; + + case HTORCH_AE_SHOWGUN: + pev->body = GUN_NONE; + pev->body = GUN_DEAGLE; + break; + + case HTORCH_AE_HIDETORCH: + pev->body = GUN_NONE; + break; + + case HTORCH_AE_ONGAS: + { + int gas = 1; + MakeGas(); + UpdateGas(); + }; + break; + + case HTORCH_AE_OFFGAS: + { + int gas = 0; + KillGas(); + }; + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGenericMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGenericMonster :: Spawn() +{ + Precache(); + + UTIL_SetModel( ENT(pev), STRING(pev->model) ); + + if ( FStrEq( STRING(pev->model), "models/player.mdl" ) || FStrEq( STRING(pev->model), "models/holo.mdl" )) + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + else UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + //UTIL_AutoSetSize(); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + //pev->weaponmodel = MAKE_STRING("materials/weapons/pulserifle/wp_prifle.mdl"); + if (!m_bloodColor) m_bloodColor = BLOOD_COLOR_RED; + if (!pev->health) pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + m_afCapability = bits_CAP_TURN_HEAD; + + if ( pev->spawnflags & SF_GENERICMONSTER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } + else if ( pev->spawnflags & SF_GENERICMONSTER_INVULNERABLE ) + { + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGenericMonster :: Precache() +{ + //PRECACHE_MODEL("materials/weapons/pulserifle/wp_prifle.mdl"); + CTalkMonster::Precache(); + TalkInit(); + UTIL_PrecacheModel((char *)STRING(pev->model) ); + if (m_iszGibModel) + PRECACHE_MODEL( (char*)STRING(m_iszGibModel) ); //LRC +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + TASK_TORCH_CHECK_FIRE = LAST_COMMON_SCHEDULE + 1, + TASK_GAS, +}; + +// ========================================================= +// TORCH SUPPORT +// ========================================================= +void CGenericMonster :: Torch ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + Vector vecShootDir; + + GetAttachment( 4, vecGunPos, vecGunAngles ); + pev->effects |= EF_MUZZLEFLASH; + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +void CGenericMonster::UpdateGas( void ) { } + +void CGenericMonster::MakeGas( void ) +{ + Vector posGun, angleGun; + TraceResult tr; + UTIL_MakeVectors( pev->angles ); + { + KillGas(); + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 7 ); + if ( m_pBeam ) + { + GetAttachment( 4, posGun, angleGun ); + GetAttachment( 3, posGun, angleGun ); + + Vector vecEnd = (gpGlobals->v_forward * 5) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &tr ); + + m_pBeam->EntsInit( entindex(), entindex() ); + m_pBeam->SetColor( 24, 121, 239 ); + m_pBeam->SetBrightness( 190 ); + m_pBeam->SetScrollRate( 20 ); + m_pBeam->SetStartAttachment( 4 ); + m_pBeam->SetEndAttachment( 3 ); + m_pBeam->DamageDecal( 28 ); + m_pBeam->DoSparks( tr.vecEndPos, posGun ); + m_pBeam->SetFlags( BEAM_FSHADEIN ); + m_pBeam->RelinkBeam(); + + UTIL_Sparks( tr.vecEndPos ); + UTIL_DecalTrace(&tr, 28 + RANDOM_LONG(0,4)); + } + } + // m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); + if ( int gas = 1 ) + { + pev->nextthink = gpGlobals->time; + } +} + +void CGenericMonster :: KillGas( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } +} + +//========================================================= +// GENERIC DEAD MONSTER, PROP +//========================================================= +class CDeadGenericMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; +}; + +LINK_ENTITY_TO_CLASS( monster_generic_dead, CDeadGenericMonster ); + +TYPEDESCRIPTION CDeadGenericMonster::m_SaveData[] = +{ + DEFINE_FIELD( CDeadGenericMonster, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CDeadGenericMonster, CBaseMonster ); + +void CDeadGenericMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_bloodColor")) + { + m_bloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszGibModel")) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// ********** DeadGenericMonster SPAWN ********** +//========================================================= +void CDeadGenericMonster :: Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->effects = 0; + pev->yaw_speed = 8; //LRC -- what? + pev->sequence = 0; + + if( pev->netname ) + { + pev->sequence = LookupSequence( STRING( pev->netname )); + + if( pev->sequence == -1 ) + { + ALERT ( at_console, "Invalid sequence name \"%s\" in monster_generic_dead\n", STRING(pev->netname) ); + } + } + else + { + pev->sequence = LookupActivity( pev->frags ); +// if (pev->sequence == -1) +// { +// ALERT ( at_error, "monster_generic_dead - specify a sequence name or choose a different death type: model \"%s\" has no available death sequences.\n", STRING(pev->model) ); +// } + //...and if that doesn't work, forget it. + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); + + ResetSequenceInfo( ); + pev->frame = 255; // pose at the _end_ of its death sequence. +} + +void CDeadGenericMonster :: Precache() +{ + PRECACHE_MODEL( (char*)STRING(pev->model) ); + if (m_iszGibModel) + PRECACHE_MODEL( (char*)STRING(m_iszGibModel) ); //LRC +} diff --git a/server/monsters/gman.cpp b/server/monsters/gman.cpp new file mode 100644 index 00000000..98ebcace --- /dev/null +++ b/server/monsters/gman.cpp @@ -0,0 +1,243 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// GMan - misunderstood servant of the people +//========================================================= +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "baseweapon.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGMan : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + EHANDLE m_hPlayer; + EHANDLE m_hTalkTarget; + float m_flTalkTime; +}; +LINK_ENTITY_TO_CLASS( monster_gman, CGMan ); + + +TYPEDESCRIPTION CGMan::m_SaveData[] = +{ + DEFINE_FIELD( CGMan, m_hTalkTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CGMan, m_flTalkTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CGMan, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGMan :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGMan :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGMan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGMan :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGMan :: Spawn() +{ + Precache(); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL( ENT(pev), "models/gman.mdl" ); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + pev->health = 100; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGMan :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/gman.mdl" ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +void CGMan :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + if (m_hPlayer == NULL) + { + m_hPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + } + break; + } + CBaseMonster::StartTask( pTask ); +} + +void CGMan :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + // look at who I'm talking to + if (m_flTalkTime > gpGlobals->time && m_hTalkTarget != NULL) + { + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + // look at player, but only if playing a "safe" idle animation + else if (m_hPlayer != NULL && pev->sequence == 0) + { + float yaw = VecToYaw(m_hPlayer->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + break; + default: + SetBoneController( 0, 0 ); + CBaseMonster::RunTask( pTask ); + break; + } +} + + +//========================================================= +// Override all damage +//========================================================= +int CGMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->health = pev->max_health / 2; // always trigger the 50% damage aitrigger + + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + return TRUE; +} + + +void CGMan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + +void CGMan::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + CBaseMonster::PlayScriptedSentence( pszSentence, duration, volume, attenuation, bConcurrent, pListener ); + + m_flTalkTime = gpGlobals->time + duration; + m_hTalkTarget = pListener; +} diff --git a/server/monsters/hassassin.cpp b/server/monsters/hassassin.cpp new file mode 100644 index 00000000..29ec650b --- /dev/null +++ b/server/monsters/hassassin.cpp @@ -0,0 +1,1068 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// hassassin - Human assassin, fast and stealthy +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "baseweapon.h" +#include "soundent.h" +#include "scripted.h" +#include "game.h" +#include "defaults.h" + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_ASSASSIN_EXPOSED = LAST_COMMON_SCHEDULE + 1,// cover was blown. + SCHED_ASSASSIN_JUMP, // fly through the air + SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot + SCHED_ASSASSIN_JUMP_LAND, // hit and run away +}; + +//========================================================= +// monster-specific tasks +//========================================================= + +enum +{ + TASK_ASSASSIN_FALL_TO_GROUND = LAST_COMMON_TASK + 1, // falling and waiting to hit ground +}; + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ASSASSIN_AE_SHOOT1 1 +#define ASSASSIN_AE_TOSS1 2 +#define ASSASSIN_AE_JUMP 3 + + +#define bits_MEMORY_BADJUMP (bits_MEMORY_CUSTOM1) + +class CHAssassin : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void); + void Shoot( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // jump + // BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // shoot + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // throw grenade + void StartTask ( Task_t *pTask ); + void RunAI( void ); + void RunTask ( Task_t *pTask ); + void DeathSound ( void ); + void IdleSound ( void ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flLastShot; + float m_flDiviation; + + float m_flNextJump; + Vector m_vecJumpVelocity; + + float m_flNextGrenadeCheck; + Vector m_vecTossVelocity; + BOOL m_fThrowGrenade; + + int m_iTargetRanderamt; + + int m_iFrustration; + + int m_iShell; +}; +LINK_ENTITY_TO_CLASS( monster_human_assassin, CHAssassin ); + + +TYPEDESCRIPTION CHAssassin::m_SaveData[] = +{ + DEFINE_FIELD( CHAssassin, m_flLastShot, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_flDiviation, FIELD_FLOAT ), + + DEFINE_FIELD( CHAssassin, m_flNextJump, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecJumpVelocity, FIELD_VECTOR ), + + DEFINE_FIELD( CHAssassin, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHAssassin, m_fThrowGrenade, FIELD_BOOLEAN ), + + DEFINE_FIELD( CHAssassin, m_iTargetRanderamt, FIELD_INTEGER ), + DEFINE_FIELD( CHAssassin, m_iFrustration, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHAssassin, CBaseMonster ); + + +//========================================================= +// DieSound +//========================================================= +void CHAssassin :: DeathSound ( void ) +{ +} + +//========================================================= +// IdleSound +//========================================================= +void CHAssassin :: IdleSound ( void ) +{ +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CHAssassin :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHAssassin :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHAssassin :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 360; + break; + default: + ys = 360; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// Shoot +//========================================================= +void CHAssassin :: Shoot ( void ) +{ + if (m_hEnemy == NULL && !m_pCine) //LRC + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_flLastShot + 2 < gpGlobals->time) + { + m_flDiviation = 0.10; + } + else + { + m_flDiviation -= 0.01; + if (m_flDiviation < 0.02) + m_flDiviation = 0.02; + } + m_flLastShot = gpGlobals->time; + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, BULLET_9MM ); // shoot +-8 degrees + + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + } + + pev->effects |= EF_MUZZLEFLASH; + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + m_cAmmoLoaded--; +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CHAssassin :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ASSASSIN_AE_SHOOT1: + Shoot( ); + break; + case ASSASSIN_AE_TOSS1: + { + Vector vecGunPosition = pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32); + UTIL_MakeVectors( pev->angles ); + //LRC + if (m_pCine && m_pCine->IsAction()) + { + Vector vecToss; + if (m_pCine->PreciseAttack() && m_hTargetEnt != NULL) + { + vecToss = VecCheckToss( pev, vecGunPosition, m_hTargetEnt->pev->origin, 0.5 ); + //if (vecToss != g_vecZero) + // ALERT(at_console,"Assassin %s throws precise grenade\n",STRING(pev->targetname)); + } + else + { + //ALERT(at_console,"Assassin %s throws nonprecise grenade\n",STRING(pev->targetname)); + // what speed would be best to use, here? Borrowing the hgrunt grenade speed seems silly... + vecToss = ((gpGlobals->v_forward*0.5)+(gpGlobals->v_up*0.5)).Normalize()*HGRUNT_GRENADE_SPEED; + } + CGrenade::ShootTimed( pev, vecGunPosition, vecToss, 2.0 ); + } + else + CGrenade::ShootTimed( pev, vecGunPosition, m_vecTossVelocity, 2.0 ); + + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + m_fThrowGrenade = FALSE; + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case ASSASSIN_AE_JUMP: + { + // ALERT( at_console, "jumping"); + UTIL_MakeAimVectors( pev->angles ); + pev->movetype = MOVETYPE_TOSS; + pev->flags &= ~FL_ONGROUND; + if (m_pCine) //LRC... + { + pev->velocity = g_vecZero; + if (m_pCine->PreciseAttack() && m_hTargetEnt != NULL) + { + Vector vecTemp = m_hTargetEnt->pev->origin; + vecTemp.y = vecTemp.y + 50; // put her feet on the target. + pev->velocity = VecCheckToss( pev, pev->origin, vecTemp, 0.5 ); + //if (pev->velocity != g_vecZero) + // ALERT(at_console,"Precise jump for assassin %s\n",STRING(pev->targetname)); + //else + // ALERT(at_console,"Precise jump failed. "); + } + if (pev->velocity == g_vecZero) + { // just jump, it doesn't matter where to. + //ALERT(at_console,"Nonprecise jump for assassin %s\n",STRING(pev->targetname)); + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ); + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + UTIL_MakeVectors(pev->angles); + Vector vecDest = pev->origin + (gpGlobals->v_forward * 32); + vecDest.z += 160; // don't forget to jump into the air, now... + pev->velocity= (vecDest - pev->origin) * speed; + } + } + else + pev->velocity = m_vecJumpVelocity; + m_flNextJump = gpGlobals->time + 3.0; + } + return; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHAssassin :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/hassassin.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = HASSASSIN_HEALTH; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP; + pev->friction = 1; + + m_HackedGunPos = Vector( 0, 24, 48 ); + + m_iTargetRanderamt = 20; + pev->renderamt = 20; + pev->rendermode = kRenderTransTexture; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHAssassin :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/hassassin.mdl"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); + PRECACHE_SOUND("weapons/pl_gun2.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_iShell = UTIL_PrecacheModel ("models/shell556.mdl");// brass shell +} + + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAssassinFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + // { TASK_WAIT_PVS, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinFail[] = +{ + { + tlAssassinFail, + ARRAYSIZE ( tlAssassinFail ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "AssassinFail" + }, +}; + + +//========================================================= +// Enemy exposed Agrunt's cover +//========================================================= +Task_t tlAssassinExposed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slAssassinExposed[] = +{ + { + tlAssassinExposed, + ARRAYSIZE ( tlAssassinExposed ), + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AssassinExposed", + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy[] = +{ + { + tlAssassinTakeCoverFromEnemy, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy" + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK2 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy2[] = +{ + { + tlAssassinTakeCoverFromEnemy2, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy2 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy2" + }, +}; + + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlAssassinTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slAssassinTakeCoverFromBestSound[] = +{ + { + tlAssassinTakeCoverFromBestSound, + ARRAYSIZE ( tlAssassinTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "AssassinTakeCoverFromBestSound" + }, +}; + + + + + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAssassinHide[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinHide[] = +{ + { + tlAssassinHide, + ARRAYSIZE ( tlAssassinHide ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHide" + }, +}; + + + +//========================================================= +// HUNT Schedules +//========================================================= +Task_t tlAssassinHunt[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slAssassinHunt[] = +{ + { + tlAssassinHunt, + ARRAYSIZE ( tlAssassinHunt ), + bits_COND_NEW_ENEMY | + // bits_COND_SEE_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHunt" + }, +}; + + +//========================================================= +// Jumping Schedules +//========================================================= +Task_t tlAssassinJump[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_SET_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_ATTACK }, +}; + +Schedule_t slAssassinJump[] = +{ + { + tlAssassinJump, + ARRAYSIZE ( tlAssassinJump ), + 0, + 0, + "AssassinJump" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpAttack[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_LAND }, + // { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_ASSASSIN_FALL_TO_GROUND, (float)0 }, +}; + + +Schedule_t slAssassinJumpAttack[] = +{ + { + tlAssassinJumpAttack, + ARRAYSIZE ( tlAssassinJumpAttack ), + 0, + 0, + "AssassinJumpAttack" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpLand[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_EXPOSED }, + // { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_REMEMBER, (float)bits_MEMORY_BADJUMP }, + { TASK_FIND_NODE_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_FORGET, (float)bits_MEMORY_BADJUMP }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, +}; + +Schedule_t slAssassinJumpLand[] = +{ + { + tlAssassinJumpLand, + ARRAYSIZE ( tlAssassinJumpLand ), + 0, + 0, + "AssassinJumpLand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHAssassin ) +{ + slAssassinFail, + slAssassinExposed, + slAssassinTakeCoverFromEnemy, + slAssassinTakeCoverFromEnemy2, + slAssassinTakeCoverFromBestSound, + slAssassinHide, + slAssassinHunt, + slAssassinJump, + slAssassinJumpAttack, + slAssassinJumpLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHAssassin, CBaseMonster ); + + +//========================================================= +// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. +//========================================================= +BOOL CHAssassin :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_flNextJump < gpGlobals->time && (flDist <= 128 || HasMemory( bits_MEMORY_BADJUMP )) && m_hEnemy != NULL ) + { + TraceResult tr; + + Vector vecDest = pev->origin + Vector( RANDOM_FLOAT( -64, 64), RANDOM_FLOAT( -64, 64 ), 160 ); + + UTIL_TraceHull( pev->origin + Vector( 0, 0, 36 ), vecDest + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, ENT(pev), &tr); + + if ( tr.fStartSolid || tr.flFraction < 1.0) + { + return FALSE; + } + + float flGravity = CVAR_GET_FLOAT( "sv_gravity" ); + + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + m_vecJumpVelocity = (vecDest - pev->origin) * speed; + + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - drop a cap in their ass +// +//========================================================= +BOOL CHAssassin :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + TraceResult tr; + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1 || tr.pHit == m_hEnemy->edict() ) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. +//========================================================= +BOOL CHAssassin :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + m_fThrowGrenade = FALSE; + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + // don't throw grenades at anything that isn't on the ground! + return FALSE; + } + + // don't get grenade happy unless the player starts to piss you off + if ( m_iFrustration <= 2) + return FALSE; + + if ( m_flNextGrenadeCheck < gpGlobals->time && !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 512 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition( ), m_hEnemy->Center(), flDist, 0.5 ); // use dist as speed to get there in 1 second + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + + return TRUE; + } + } + + return FALSE; +} + + +//========================================================= +// RunAI +//========================================================= +void CHAssassin :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + + // always visible if moving + // always visible is not on hard + if (m_hEnemy == NULL || pev->deadflag != DEAD_NO || m_Activity == ACT_RUN || m_Activity == ACT_WALK || !(pev->flags & FL_ONGROUND)) + m_iTargetRanderamt = 255; + else + m_iTargetRanderamt = 20; + + if (pev->renderamt > m_iTargetRanderamt) + { + if (pev->renderamt == 255) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM ); + } + + pev->renderamt = max( pev->renderamt - 50, m_iTargetRanderamt ); + pev->rendermode = kRenderTransTexture; + } + else if (pev->renderamt < m_iTargetRanderamt) + { + pev->renderamt = min( pev->renderamt + 50, m_iTargetRanderamt ); + if (pev->renderamt == 255) + pev->rendermode = kRenderNormal; + } + + if (m_Activity == ACT_RUN || m_Activity == ACT_WALK) + { + static int iStep = 0; + iStep = ! iStep; + if (iStep) + { + switch( RANDOM_LONG( 0, 3 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM); break; + } + } + } +} + + +//========================================================= +// StartTask +//========================================================= +void CHAssassin :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK2: + if (!m_fThrowGrenade) + { + TaskComplete( ); + } + else + { + CBaseMonster :: StartTask ( pTask ); + } + break; + case TASK_ASSASSIN_FALL_TO_GROUND: + break; + default: + CBaseMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// RunTask +//========================================================= +void CHAssassin :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ASSASSIN_FALL_TO_GROUND: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + if (pev->velocity.z > 0) + { + pev->sequence = LookupSequence( "fly_up" ); + } + else if (HasConditions ( bits_COND_SEE_ENEMY )) + { + pev->sequence = LookupSequence( "fly_attack" ); + pev->frame = 0; + } + else + { + pev->sequence = LookupSequence( "fly_down" ); + pev->frame = 0; + } + + ResetSequenceInfo( ); + SetYawSpeed(); + } + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "on ground\n"); + TaskComplete( ); + } + break; + default: + CBaseMonster :: RunTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CHAssassin :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + { + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( pSound && (pSound->m_iType & bits_SOUND_COMBAT) ) + { + return GetScheduleOfType( SCHED_INVESTIGATE_SOUND ); + } + } + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // flying? + if ( pev->movetype == MOVETYPE_TOSS) + { + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "landed\n"); + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_LAND ); + } + else + { + // ALERT( at_console, "jump\n"); + // jump or jump/shoot + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP ); + else + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_ATTACK ); + } + } + + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + m_iFrustration++; + } + + // jump player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + // ALERT( at_console, "melee attack 1\n"); + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + // throw grenade + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + // ALERT( at_console, "range attack 2\n"); + return GetScheduleOfType ( SCHED_RANGE_ATTACK2 ); + } + + // spotted + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + m_iFrustration++; + return GetScheduleOfType ( SCHED_ASSASSIN_EXPOSED ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + // ALERT( at_console, "range attack 1\n"); + m_iFrustration = 0; + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // ALERT( at_console, "face\n"); + return GetScheduleOfType ( SCHED_COMBAT_FACE ); + } + + // new enemy + if ( HasConditions ( bits_COND_NEW_ENEMY ) ) + { + // ALERT( at_console, "take cover\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + // ALERT( at_console, "stand\n"); + return GetScheduleOfType ( SCHED_ALERT_STAND ); + } + break; + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHAssassin :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + if (pev->health > 30) + return slAssassinTakeCoverFromEnemy; + else + return slAssassinTakeCoverFromEnemy2; + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + return slAssassinTakeCoverFromBestSound; + case SCHED_ASSASSIN_EXPOSED: + return slAssassinExposed; + case SCHED_FAIL: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinFail; + break; + case SCHED_ALERT_STAND: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinHide; + break; + case SCHED_CHASE_ENEMY: + return slAssassinHunt; + case SCHED_MELEE_ATTACK1: + if (pev->flags & FL_ONGROUND) + { + if (m_flNextJump > gpGlobals->time) + { + // can't jump yet, go ahead and fail + return slAssassinFail; + } + else + { + return slAssassinJump; + } + } + else + { + return slAssassinJumpAttack; + } + case SCHED_ASSASSIN_JUMP: + case SCHED_ASSASSIN_JUMP_ATTACK: + return slAssassinJumpAttack; + case SCHED_ASSASSIN_JUMP_LAND: + return slAssassinJumpLand; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + +#endif diff --git a/server/monsters/headcrab.cpp b/server/monsters/headcrab.cpp new file mode 100644 index 00000000..c90cb7ac --- /dev/null +++ b/server/monsters/headcrab.cpp @@ -0,0 +1,562 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// headcrab.cpp - tiny, jumpy alien parasite +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" +#include "defaults.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HC_AE_JUMPATTACK ( 2 ) + +Task_t tlHCRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slHCRangeAttack1[] = +{ + { + tlHCRangeAttack1, + ARRAYSIZE ( tlHCRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRangeAttack1" + }, +}; + +Task_t tlHCRangeAttack1Fast[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slHCRangeAttack1Fast[] = +{ + { + tlHCRangeAttack1Fast, + ARRAYSIZE ( tlHCRangeAttack1Fast ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRAFast" + }, +}; + +class CHeadCrab : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed ( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + void PainSound( void ); + void DeathSound( void ); + void IdleSound( void ); + void AlertSound( void ); + void PrescheduleThink( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual float GetDamageAmount( void ) { return HEADCRAB_DMG_BITE; } + virtual int GetVoicePitch( void ) { return 100; } + virtual float GetSoundVolue( void ) { return 1.0; } + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pDeathSounds[]; + static const char *pBiteSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_headcrab, CHeadCrab ); + +DEFINE_CUSTOM_SCHEDULES( CHeadCrab ) +{ + slHCRangeAttack1, + slHCRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster ); + +const char *CHeadCrab::pIdleSounds[] = +{ + "headcrab/hc_idle1.wav", + "headcrab/hc_idle2.wav", + "headcrab/hc_idle3.wav", +}; +const char *CHeadCrab::pAlertSounds[] = +{ + "headcrab/hc_alert1.wav", +}; +const char *CHeadCrab::pPainSounds[] = +{ + "headcrab/hc_pain1.wav", + "headcrab/hc_pain2.wav", + "headcrab/hc_pain3.wav", +}; +const char *CHeadCrab::pAttackSounds[] = +{ + "headcrab/hc_attack1.wav", + "headcrab/hc_attack2.wav", + "headcrab/hc_attack3.wav", +}; + +const char *CHeadCrab::pDeathSounds[] = +{ + "headcrab/hc_die1.wav", + "headcrab/hc_die2.wav", +}; + +const char *CHeadCrab::pBiteSounds[] = +{ + "headcrab/hc_headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHeadCrab :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREY; +} + +//========================================================= +// Center - returns the real center of the headcrab. The +// bounding box is much larger than the actual creature so +// this is needed for targeting +//========================================================= +Vector CHeadCrab :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CHeadCrab :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHeadCrab :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 30; + break; + case ACT_RUN: + case ACT_WALK: + ys = 20; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 60; + break; + case ACT_RANGE_ATTACK1: + ys = 30; + break; + default: + ys = 30; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HC_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = CVAR_GET_FLOAT( "sv_gravity" ); + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 16) + height = 16; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + int iSound = RANDOM_LONG(0,2); + if ( iSound != 0 ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHeadCrab :: Spawn() +{ + Precache( ); + + UTIL_SetModel( ENT( pev ), pev->model, "models/monsters/headcrab.mdl" ); + UTIL_SetSize( pev, Vector( -12, -12, 0 ), Vector( 12, 12, 24 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + if( pev->health == 0 ) pev->health = HEADCRAB_HEALTH; + pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. + pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHeadCrab :: Precache() +{ + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pAttackSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pBiteSounds); + + UTIL_PrecacheModel( pev->model, "models/monsters/headcrab.mdl" ); +} + + +//========================================================= +// RunTask +//========================================================= +void CHeadCrab :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + SetTouch( NULL ); + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CHeadCrab :: LeapTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->pev->takedamage ) + { + return; + } + + if ( pOther->Classify() == Classify() ) + { + return; + } + + // Don't hit if back on ground + if ( !FBitSet( pev->flags, FL_ONGROUND ) ) + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHeadCrab :: PrescheduleThink ( void ) +{ + // make the crab coo a little bit in combat state + if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) + { + IdleSound(); + } +} + +void CHeadCrab :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + m_IdealActivity = ACT_RANGE_ATTACK1; + SetTouch(&CHeadCrab :: LeapTouch ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. +#if 0 + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +#endif +} + +int CHeadCrab :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define CRAB_ATTN_IDLE (float)1.5 +void CHeadCrab :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CHeadCrab :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CHeadCrab :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slHCRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +class CBabyCrab : public CHeadCrab +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + float GetDamageAmount( void ) { return HEADCRAB_HEALTH * 0.3; } + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + Schedule_t* GetScheduleOfType ( int Type ); + virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } + virtual float GetSoundVolue( void ) { return 0.8; } +}; +LINK_ENTITY_TO_CLASS( monster_babycrab, CBabyCrab ); + +void CBabyCrab :: Spawn( void ) +{ + CHeadCrab::Spawn(); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/baby_headcrab.mdl"); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 192; + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->health = HEADCRAB_HEALTH * 0.25; // less health than full grown +} + +void CBabyCrab :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/baby_headcrab.mdl" ); + CHeadCrab::Precache(); +} + + +void CBabyCrab :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 120; +} + + +BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) ) + return TRUE; + + // A little less accurate, but jump from closer + if ( flDist <= 180 && flDot >= 0.55 ) + return TRUE; + } + + return FALSE; +} + + +Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_FAIL: // If you fail, try to jump! + if ( m_hEnemy != NULL ) + return slHCRangeAttack1Fast; + break; + + case SCHED_RANGE_ATTACK1: + { + return slHCRangeAttack1Fast; + } + break; + } + + return CHeadCrab::GetScheduleOfType( Type ); +} diff --git a/server/monsters/hgrunt.cpp b/server/monsters/hgrunt.cpp new file mode 100644 index 00000000..43ecf67d --- /dev/null +++ b/server/monsters/hgrunt.cpp @@ -0,0 +1,2618 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// hgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "plane.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "baseweapon.h" +#include "talkmonster.h" +#include "soundent.h" +#include "basebeams.h" +#include "scripted.h" //LRC +#include "defaults.h" //LRC + +int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HGRUNT_LIMP_HEALTH 20 +#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? +#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences + +#define HGRUNT_9MMAR ( 1 << 0) +#define HGRUNT_HANDGRENADE ( 1 << 1) +#define HGRUNT_GRENADELAUNCHER ( 1 << 2) +#define HGRUNT_SHOTGUN ( 1 << 3) + +#define HEAD_GROUP 1 +#define HEAD_GRUNT 0 +#define HEAD_COMMANDER 1 +#define HEAD_SHOTGUN 2 +#define HEAD_M203 3 + +#define GUN_GROUP 2 +#define GUN_MP5 0 +#define GUN_SHOTGUN 1 +#define GUN_NONE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_AE_RELOAD ( 2 ) +#define HGRUNT_AE_KICK ( 3 ) +#define HGRUNT_AE_BURST1 ( 4 ) +#define HGRUNT_AE_BURST2 ( 5 ) +#define HGRUNT_AE_BURST3 ( 6 ) +#define HGRUNT_AE_GREN_TOSS ( 7 ) +#define HGRUNT_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_AE_GREN_DROP ( 9 ) +#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +class CHGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + Vector GetGunPosition( void ); + void Shoot ( void ); + void Shotgun ( void ); + void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + int IRelationship ( CBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; + + Vector m_vecTossVelocity; + + BOOL m_fThrowGrenade; + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; + + int m_voicePitch; + + int m_iBrassShell; + int m_iShotgunShell; + + int m_iSentence; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ); + +TYPEDESCRIPTION CHGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CHGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHGrunt, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CHGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CHGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHGrunt, CSquadMonster ); + +const char *CHGrunt::pGruntSentences[] = +{ + "HG_GREN", // grenade scared grunt + "HG_ALERT", // sees player + "HG_MONSTER", // sees monster + "HG_COVER", // running to cover + "HG_THROW", // about to throw grenade + "HG_CHARGE", // running out to get the enemy + "HG_TAUNT", // say rude things +}; + +enum +{ + HGRUNT_SENT_NONE = -1, + HGRUNT_SENT_GREN = 0, + HGRUNT_SENT_ALERT, + HGRUNT_SENT_MONSTER, + HGRUNT_SENT_COVER, + HGRUNT_SENT_THROW, + HGRUNT_SENT_CHARGE, + HGRUNT_SENT_TAUNT, +} HGRUNT_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CHGrunt :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CHGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + //LRC- only hate alien grunts if my behaviour hasn't been overridden + if (!m_iClass && FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CHGrunt :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( 2 ) != 2 && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the grunt has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_mp5", vecGunPos, vecGunAngles ); + } + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CHGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CHGrunt :: FOkToSpeak( void ) +{ +// if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // no talking outside of combat if gagged. + return FALSE; + } + } + + // if player is not in pvs, don't speak +// if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +void CHGrunt :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = HGRUNT_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CHGrunt :: PrescheduleThink ( void ) +{ + if ( InSquad() && m_hEnemy != NULL ) + { + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // update the squad's last enemy sighting time. + MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; + } + else + { + if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) + { + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; + } + } + } +} + +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CHGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CHGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if ( flDist <= 64 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) + { + // kick nonclients who are close enough, but don't shoot at them. + return FALSE; + } + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 ) + { + return TRUE; + } + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (! FBitSet(pev->weapons, (HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER))) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && (m_hEnemy->pev->waterlevel == 0 || m_hEnemy->pev->watertype == CONTENTS_FOG) && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; + } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / HGRUNT_GRENADE_SPEED) * m_hEnemy->pev->velocity; + } + + // are any of my squad members near the intended grenade impact area? + if ( InSquad() ) + { + if (SquadMemberInRange( vecTarget, 256 )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; //AJH need this or it is overridden later. + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, HGRUNT_GRENADE_SPEED, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + + + + return m_fThrowGrenade; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CHGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // make sure we're wearing one + if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the grunt because the grunt +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CHGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +void CHGrunt :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fGruntQuestion || RANDOM_LONG(0,1))) + { + if (!g_fGruntQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "HG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fGruntQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CHGrunt :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHGrunt :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CHGrunt :: Kick( void ) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + return pEntity; + } + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CHGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shoot ( void ) +{ + if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_MP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + } + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + // Teh_Freak: World Lighting! + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_DLIGHT ); + WRITE_COORD( vecShootOrigin.x ); // origin + WRITE_COORD( vecShootOrigin.y ); + WRITE_COORD( vecShootOrigin.z ); + WRITE_BYTE( 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 128 ); // B + WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 0 ); // decay + MESSAGE_END(); + // Teh_Freak: World Lighting! + +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shotgun ( void ) +{ + if (m_hEnemy == NULL && m_pCine == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iShotgunShell, TE_BOUNCE_SHOTSHELL); + FireBullets(HGRUNT_SHOTGUN_PELLETS, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_BUCKSHOT, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + // Teh_Freak: World Lighting! + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_DLIGHT ); + WRITE_COORD( vecShootOrigin.x ); // origin + WRITE_COORD( vecShootOrigin.y ); + WRITE_COORD( vecShootOrigin.z ); + WRITE_BYTE( 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 128 ); // B + WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 0 ); // decay + MESSAGE_END(); + // Teh_Freak: World Lighting! + +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_mp5", vecGunPos, vecGunAngles ); + } + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + + } + break; + + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); + //LRC - a bit of a hack. Ideally the grunts would work out in advance whether it's ok to throw. + if (m_pCine) + { + Vector vecToss = g_vecZero; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + { + vecToss = VecCheckToss( pev, GetGunPosition(), m_hTargetEnt->pev->origin, 0.5 ); + } + if (vecToss == g_vecZero) + { + vecToss = (gpGlobals->v_forward*0.5+gpGlobals->v_up*0.5).Normalize()*HGRUNT_GRENADE_SPEED; + } + CGrenade::ShootTimed( pev, GetGunPosition(), vecToss, 3.5 ); + } + else + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + //LRC: firing due to a script? + if (m_pCine) + { + Vector vecToss; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + vecToss = VecCheckThrow( pev, GetGunPosition(), m_hTargetEnt->pev->origin, HGRUNT_GRENADE_SPEED, 0.5 ); + else + { + // just shoot diagonally up+forwards + UTIL_MakeVectors(pev->angles); + vecToss = (gpGlobals->v_forward*0.5 + gpGlobals->v_up*0.5).Normalize() * HGRUNT_GRENADE_SPEED; + } + CGrenade::ShootContact( pev, GetGunPosition(), vecToss ); + } + else + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 4, 6 );// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + + case HGRUNT_AE_BURST1: + { + if ( FBitSet( pev->weapons, HGRUNT_9MMAR )) + { + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (m_cAmmoLoaded > 0) + { + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + } + + Shoot(); + } + else + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, HGRUNT_DMG_KICK, DMG_CLUB ); + } + } + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHGrunt :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = HGRUNT_HEALTH; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if (RANDOM_LONG( 0, 99 ) < 80) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN); + } + else if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHGrunt :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/hgrunt.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_pain1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain5.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = UTIL_PrecacheModel ("models/shell556.mdl");// brass shell + m_iShotgunShell = UTIL_PrecacheModel ("models/shellBuck.mdl"); +} + +//========================================================= +// start task +//========================================================= +void CHGrunt :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GRUNT_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_GRUNT_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_GRUNT_FACE_TOSS_DIR: + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CSquadMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CSquadMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CHGrunt :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_GRUNT_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CHGrunt :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if ( RANDOM_LONG(0,99) < 5 ) + { + // pain sentences are rare + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM); + JustSpoke(); + return; + } + } +#endif + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHGrunt :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE ( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE ( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE ( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE ( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE ( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE ( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE ( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE ( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE ( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE ( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE ( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE ( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE ( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE ( tlGruntSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE ( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE ( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE ( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE ( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE ( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE ( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CHGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CHGrunt :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & HGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + // LRC: added a test to stop a marine without a launcher from firing. + else if ( pev->weapons & HGRUNT_GRENADELAUNCHER ) + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + else + { + ALERT( at_console, "No grenades available. "); // flow into the error message we get at the end... + } + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CHGrunt :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + ALERT(at_aiconsole,"leader spotted player!\n"); + //!!!KELLY - the leader of a squad of grunts has just seen the player or a + // monster and has made it the squad's enemy. You + // can check pev->flags for FL_CLIENT to determine whether this is the player + // or a monster. He's going to immediately start + // firing, though. If you'd like, we can make an alternate "first sight" + // schedule where the leader plays a handsign anim + // that gives us enough time to hear a short sentence or spoken command + // before he starts pluggin away. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if ((m_hEnemy != NULL) && m_hEnemy->IsPlayer()) + // player + SENTENCEG_PlayRndSz( ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + else if ((m_hEnemy != NULL) && + (m_hEnemy->Classify() != CLASS_PLAYER_ALLY) && + (m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE) && + (m_hEnemy->Classify() != CLASS_MACHINE)) + // monster + SENTENCEG_PlayRndSz( ENT(pev), "HG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!KELLY - this individual just realized he's out of bullet ammo. + // He's going to try to find cover to run to and reload, but rarely, if + // none is available, he'll drop and reload in the open here. + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + if (HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) && RANDOM_LONG(0,1) ) + { + return &slGruntGrenadeCover[ 0 ]; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if (RANDOM_LONG(0,9) == 0) + m_fStanding = RANDOM_LONG(0,1); + + if (m_fStanding) + return &slGruntRangeAttack1B[ 0 ]; + else + return &slGruntRangeAttack1A[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[ 0 ]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[ 0 ]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[ 0 ]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[ 0 ]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slGruntFail[ 0 ]; + } + } + + return &slGruntVictoryDance[ 0 ]; + } + case SCHED_GRUNT_SUPPRESS: + { + if ( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[ 0 ]; + } + else + { + return &slGruntSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[ 0 ]; + } + + return &slGruntFail[ 0 ]; + } + case SCHED_GRUNT_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepel[ 0 ]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepelAttack[ 0 ]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +class CHGruntRepel : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int m_iSpriteTexture; // Don't save, precache +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_repel, CHGruntRepel ); + +void CHGruntRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse(&CHGruntRepel:: RepelUse ); +} + +void CHGruntRepel::Precache( void ) +{ + UTIL_PrecacheEntity( "monster_human_grunt" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CHGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5 ); + + UTIL_Remove( this ); +} + + + +//========================================================= +// DEAD HGRUNT PROP +//========================================================= +class CDeadHGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadHGrunt::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CDeadHGrunt ); + +//========================================================= +// ********** DeadHGrunt SPAWN ********** +//========================================================= +void CDeadHGrunt :: Spawn( void ) +{ + int oldBody; + + PRECACHE_MODEL("models/hgrunt.mdl"); + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if( pev->sequence == -1 ) + { + ALERT ( at_console, "Dead hgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + oldBody = pev->body; + pev->body = 0; + + if (oldBody >= 5 && oldBody <= 7) + pev->skin = 1; + else + pev->skin = 0; + + switch( pev->weapons ) + { + case 0: // MP5 + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 1: // Shotgun + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + break; + case 2: // No gun + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + switch( oldBody ) + { + case 2: // Gasmask, no gun + SetBodygroup( GUN_GROUP, GUN_NONE ); //fall through + case 0: case 6: // Gasmask (white/black) + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + break; + case 3: // Commander, no gun + SetBodygroup( GUN_GROUP, GUN_NONE ); //fall through + case 1: // Commander + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + break; + case 4: case 7: // Skimask (white/black) + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + break; + case 5: // Commander + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + break; + } + + MonsterInitDead(); +} diff --git a/server/monsters/leech.cpp b/server/monsters/leech.cpp new file mode 100644 index 00000000..95c002d0 --- /dev/null +++ b/server/monsters/leech.cpp @@ -0,0 +1,743 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// leech - basic little swimming monster +//========================================================= +// +// UNDONE: +// DONE:Steering force model for attack +// DONE:Attack animation control / damage +// DONE:Establish range of up/down motion and steer around vertical obstacles +// DONE:Re-evaluate height periodically +// DONE:Fall (MOVETYPE_TOSS) and play different anim if out of water +// Test in complex room (c2a3?) +// DONE:Sounds? - Kelly will fix +// Blood cloud? Hurt effect? +// Group behavior? +// DONE:Save/restore +// Flop animation - just bind to ACT_TWITCH +// Fix fatal push into wall case +// +// Try this on a bird +// Try this on a model with hulls/tracehull? +// + + +#include "float.h" +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "defaults.h" + + + +// Animation events +#define LEECH_AE_ATTACK 1 +#define LEECH_AE_FLOP 2 + + +// Movement constants + +#define LEECH_ACCELERATE 10 +#define LEECH_CHECK_DIST 45 +#define LEECH_SWIM_SPEED 50 +#define LEECH_SWIM_ACCEL 80 +#define LEECH_SWIM_DECEL 10 +#define LEECH_TURN_RATE 90 +#define LEECH_SIZEX 10 +#define LEECH_FRAMETIME 0.1 + + + +#define DEBUG_BEAMS 0 + +#if DEBUG_BEAMS +#include "basebeams.h" +#endif + + +class CLeech : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SwimThink( void ); + void EXPORT DeadThink( void ); + void Touch( CBaseEntity *pOther ) + { + if ( pOther->IsPlayer() ) + { + // If the client is pushing me, give me some velocity + if( gpGlobals->trace_ent && gpGlobals->trace_ent == edict() ) + { + pev->velocity = pOther->pev->velocity; + } + } + } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-8,-8,0); + pev->absmax = pev->origin + Vector(8,8,2); + } + + void AttackSound( void ); + void AlertSound( void ); + void UpdateMotion( void ); + float ObstacleDistance( CBaseEntity *pTarget ); + void MakeVectors( void ); + void RecalculateWaterlevel( void ); + void SwitchLeechState( void ); + + // Base entity functions + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int Classify( void ) { return CLASS_INSECT; } + int IRelationship( CBaseEntity *pTarget ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackSounds[]; + static const char *pAlertSounds[]; + +private: + // UNDONE: Remove unused boid vars, do group behavior + float m_flTurning;// is this boid turning? + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + float m_flAccelerate; + float m_obstacle; + float m_top; + float m_bottom; + float m_height; + float m_waterTime; + float m_sideTime; // Timer to randomly check clearance on sides + float m_zTime; + float m_stateTime; + float m_attackSoundTime; + +#if DEBUG_BEAMS + CBeam *m_pb; + CBeam *m_pt; +#endif +}; + + + +LINK_ENTITY_TO_CLASS( monster_leech, CLeech ); + +TYPEDESCRIPTION CLeech::m_SaveData[] = +{ + DEFINE_FIELD( CLeech, m_flTurning, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CLeech, m_flAccelerate, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_obstacle, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_top, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_bottom, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_waterTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_sideTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_zTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_stateTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_attackSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CLeech, CBaseMonster ); + + +const char *CLeech::pAttackSounds[] = +{ + "leech/leech_bite1.wav", + "leech/leech_bite2.wav", + "leech/leech_bite3.wav", +}; + +const char *CLeech::pAlertSounds[] = +{ + "leech/leech_alert1.wav", + "leech/leech_alert2.wav", +}; + + +void CLeech::Spawn( void ) +{ + Precache(); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/leech.mdl"); + // Just for fun + // SET_MODEL(ENT(pev), "models/icky.mdl"); + +// UTIL_SetSize( pev, g_vecZero, g_vecZero ); + UTIL_SetSize( pev, Vector(-1,-1,0), Vector(1,1,2)); + // Don't push the minz down too much or the water check will fail because this entity is really point-sized + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + SetBits( pev->flags, FL_SWIM ); + if (pev->health == 0) + pev->health = LEECH_HEALTH; + + m_flFieldOfView = -0.5; // 180 degree FOV + m_flDistLook = 750; + MonsterInit(); + SetThink(&CLeech:: SwimThink ); + SetUse( NULL ); + SetTouch( NULL ); + pev->view_ofs = g_vecZero; + + m_flTurning = 0; + m_fPathBlocked = FALSE; + SetActivity( ACT_SWIM ); + SetState( MONSTERSTATE_IDLE ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 1, 5 ); +} + + +void CLeech::Activate( void ) +{ + RecalculateWaterlevel(); + CBaseMonster::Activate(); +} + + + +void CLeech::RecalculateWaterlevel( void ) +{ + // Calculate boundaries + Vector vecTest = pev->origin - Vector(0,0,400); + + TraceResult tr; + + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if ( tr.flFraction != 1.0 ) + m_bottom = tr.vecEndPos.z + 1; + else + m_bottom = vecTest.z; + + m_top = UTIL_WaterLevel( pev->origin, pev->origin.z, pev->origin.z + 400 ) - 1; + + // Chop off 20% of the outside range + float newBottom = m_bottom * 0.8 + m_top * 0.2; + m_top = m_bottom * 0.2 + m_top * 0.8; + m_bottom = newBottom; + m_height = RANDOM_FLOAT( m_bottom, m_top ); + m_waterTime = gpGlobals->time + RANDOM_FLOAT( 5, 7 ); +} + + +void CLeech::SwitchLeechState( void ) +{ + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 3, 6 ); + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + m_hEnemy = NULL; + SetState( MONSTERSTATE_IDLE ); + // We may be up against the player, so redo the side checks + m_sideTime = 0; + } + else + { + Look( m_flDistLook ); + CBaseEntity *pEnemy = BestVisibleEnemy(); + if ( pEnemy && pEnemy->pev->waterlevel != 0 && pEnemy->pev->watertype != CONTENTS_FOG ) + { + m_hEnemy = pEnemy; + SetState( MONSTERSTATE_COMBAT ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 18, 25 ); + AlertSound(); + } + } +} + + +int CLeech::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + return R_DL; + return CBaseMonster::IRelationship( pTarget ); +} + + + +void CLeech::AttackSound( void ) +{ + if ( gpGlobals->time > m_attackSoundTime ) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_attackSoundTime = gpGlobals->time + 0.5; + } +} + + +void CLeech::AlertSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM * 0.5, 0, PITCH_NORM ); +} + + +void CLeech::Precache( void ) +{ + int i; + + //PRECACHE_MODEL("models/icky.mdl"); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/leech.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); +} + + +int CLeech::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->velocity = g_vecZero; + + // Nudge the leech away from the damage + if ( pevInflictor ) + { + pev->velocity = (pev->origin - pevInflictor->origin).Normalize() * 25; + } + else if ( pev->movetype == MOVETYPE_TOSS ) + { + ALERT(at_console, "Waterlevel is out\n" ); +// if ( RANDOM_LONG( 0, 99 ) < 1 ) + pev->dmg += 2; + + + } + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CLeech::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case LEECH_AE_ATTACK: + AttackSound(); + CBaseEntity *pEnemy; + + pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + Vector dir, face; + + UTIL_MakeVectorsPrivate( pev->angles, face, NULL, NULL ); + face.z = 0; + dir = (pEnemy->pev->origin - pev->origin); + dir.z = 0; + dir = dir.Normalize(); + face = face.Normalize(); + + + if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey + pEnemy->TakeDamage( pev, pev, LEECH_DMG_BITE, DMG_SLASH ); + } + m_stateTime -= 2; + break; + + case LEECH_AE_FLOP: + // Play flop sound + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CLeech::MakeVectors( void ) +{ + Vector tmp = pev->angles; + tmp.x = -tmp.x; + UTIL_MakeVectors ( tmp ); +} + + +// +// ObstacleDistance - returns normalized distance to obstacle +// +float CLeech::ObstacleDistance( CBaseEntity *pTarget ) +{ + TraceResult tr; + Vector vecTest; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //Vector vecDir = UTIL_VecToAngles( pev->velocity ); + MakeVectors(); + + // check for obstacle ahead + vecTest = pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + + if ( tr.fStartSolid ) + { + pev->speed = -LEECH_SWIM_SPEED * 0.5; +// ALERT( at_console, "Stuck from (%f %f %f) to (%f %f %f)\n", pev->oldorigin.x, pev->oldorigin.y, pev->oldorigin.z, pev->origin.x, pev->origin.y, pev->origin.z ); +// UTIL_SetOrigin( pev, pev->oldorigin ); + } + + if ( tr.flFraction != 1.0 ) + { + if ( (pTarget == NULL || tr.pHit != pTarget->edict()) ) + { + return tr.flFraction; + } + else + { + if ( fabs(m_height - pev->origin.z) > 10 ) + return tr.flFraction; + } + } + + if ( m_sideTime < gpGlobals->time ) + { + // extra wide checks + vecTest = pev->origin + gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + vecTest = pev->origin - gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + // Didn't hit either side, so stop testing for another 0.5 - 1 seconds + m_sideTime = gpGlobals->time + RANDOM_FLOAT(0.5,1); + } + return 1.0; +} + + +void CLeech::DeadThink( void ) +{ + if ( m_fSequenceFinished ) + { + if ( m_Activity == ACT_DIEFORWARD ) + { + SetThink( NULL ); + StopAnimation(); + return; + } + else if ( pev->flags & FL_ONGROUND ) + { + pev->solid = SOLID_NOT; + SetActivity(ACT_DIEFORWARD); + } + } + StudioFrameAdvance(); + SetNextThink( 0.1 ); + + // Apply damage velocity, but keep out of the walls + if ( pev->velocity.x != 0 || pev->velocity.y != 0 ) + { + TraceResult tr; + + // Look 0.5 seconds ahead + UTIL_TraceLine(pev->origin, pev->origin + pev->velocity * 0.5, missile, edict(), &tr); + if (tr.flFraction != 1.0) + { + pev->velocity.x = 0; + pev->velocity.y = 0; + } + } +} + + + +void CLeech::UpdateMotion( void ) +{ + float flapspeed = (pev->speed - m_flAccelerate) / LEECH_ACCELERATE; + m_flAccelerate = m_flAccelerate * 0.8 + pev->speed * 0.2; + + if (flapspeed < 0) + flapspeed = -flapspeed; + flapspeed += 1.0; + if (flapspeed < 0.5) + flapspeed = 0.5; + if (flapspeed > 1.9) + flapspeed = 1.9; + + pev->framerate = flapspeed; + + if ( !m_fPathBlocked ) + pev->avelocity.y = pev->ideal_yaw; + else + pev->avelocity.y = pev->ideal_yaw * m_obstacle; + + if ( pev->avelocity.y > 150 ) + m_IdealActivity = ACT_TURN_LEFT; + else if ( pev->avelocity.y < -150 ) + m_IdealActivity = ACT_TURN_RIGHT; + else + m_IdealActivity = ACT_SWIM; + + // lean + float targetPitch, delta; + delta = m_height - pev->origin.z; + + if ( delta < -10 ) + targetPitch = -30; + else if ( delta > 10 ) + targetPitch = 30; + else + targetPitch = 0; + + pev->angles.x = UTIL_Approach( targetPitch, pev->angles.x, 60 * LEECH_FRAMETIME ); + + // bank + pev->avelocity.z = - (pev->angles.z + (pev->avelocity.y * 0.25)); + + if ( m_MonsterState == MONSTERSTATE_COMBAT && HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + m_IdealActivity = ACT_MELEE_ATTACK1; + + // Out of water check + if ( !pev->waterlevel || pev->watertype == CONTENTS_FOG ) + { + pev->movetype = MOVETYPE_TOSS; + m_IdealActivity = ACT_TWITCH; + pev->velocity = g_vecZero; + + // Animation will intersect the floor if either of these is non-zero + pev->angles.z = 0; + pev->angles.x = 0; + + if ( pev->framerate < 1.0 ) + pev->framerate = 1.0; + } + else if ( pev->movetype == MOVETYPE_TOSS ) + { + pev->movetype = MOVETYPE_FLY; + pev->flags &= ~FL_ONGROUND; + RecalculateWaterlevel(); + ALERT(at_console, "Waterlevel is out\n" ); + if ( RANDOM_LONG( 0, 99 ) < 1 ) + { + pev->gravity = 0.02; + pev->takedamage += 2; + } + m_waterTime = gpGlobals->time + 2; // Recalc again soon, water may be rising + } + + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + float flInterval = StudioFrameAdvance(); + DispatchAnimEvents ( flInterval ); + +#if DEBUG_BEAMS + if ( !m_pb ) + m_pb = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + if ( !m_pt ) + m_pt = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + m_pb->PointsInit( pev->origin, pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST ); + m_pt->PointsInit( pev->origin, pev->origin - gpGlobals->v_right * (pev->avelocity.y*0.25) ); + if ( m_fPathBlocked ) + { + float color = m_obstacle * 30; + if ( m_obstacle == 1.0 ) + color = 0; + if ( color > 255 ) + color = 255; + m_pb->SetColor( 255, (int)color, (int)color ); + } + else + m_pb->SetColor( 255, 255, 0 ); + m_pt->SetColor( 0, 0, 255 ); +#endif +} + + +void CLeech::SwimThink( void ) +{ + TraceResult tr; + float flLeftSide; + float flRightSide; + float targetSpeed; + float targetYaw = 0; + CBaseEntity *pTarget; + + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) && !HaveCamerasInPVS( edict() )) + { + SetNextThink( RANDOM_FLOAT(1,1.5) ); + pev->velocity = g_vecZero; + return; + } + else + SetNextThink( 0.1 ); + + targetSpeed = LEECH_SWIM_SPEED; + + if ( m_waterTime < gpGlobals->time ) + RecalculateWaterlevel(); + + if ( m_stateTime < gpGlobals->time ) + SwitchLeechState(); + + ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + pTarget = m_hEnemy; + if ( !pTarget ) + SwitchLeechState(); + else + { + // Chase the enemy's eyes + m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; + // Clip to viable water area + if ( m_height < m_bottom ) + m_height = m_bottom; + else if ( m_height > m_top ) + m_height = m_top; + Vector location = pTarget->pev->origin - pev->origin; + location.z += (pTarget->pev->view_ofs.z); + if ( location.Length() < 40 ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + // Turn towards target ent + targetYaw = UTIL_VecToYaw( location ); + + targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); + + if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) + targetYaw = (-LEECH_TURN_RATE*0.75); + else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) + targetYaw = (LEECH_TURN_RATE*0.75); + else + targetSpeed *= 2; + } + + break; + + default: + if ( m_zTime < gpGlobals->time ) + { + float newHeight = RANDOM_FLOAT( m_bottom, m_top ); + m_height = 0.5 * m_height + 0.5 * newHeight; + m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); + } + if ( RANDOM_LONG( 0, 100 ) < 10 ) + targetYaw = RANDOM_LONG( -30, 30 ); + pTarget = NULL; + // oldorigin test + if ( (pev->origin - pev->oldorigin).Length() < 1 ) + { + // If leech didn't move, there must be something blocking it, so try to turn + m_sideTime = 0; + } + + break; + } + + m_obstacle = ObstacleDistance( pTarget ); + pev->oldorigin = pev->origin; + if ( m_obstacle < 0.1 ) + m_obstacle = 0.1; + + // is the way ahead clear? + if ( m_obstacle == 1.0 ) + { + // if the leech is turning, stop the trend. + if ( m_flTurning != 0 ) + { + m_flTurning = 0; + } + + m_fPathBlocked = FALSE; + pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); + pev->velocity = gpGlobals->v_forward * pev->speed; + + } + else + { + m_obstacle = 1.0 / m_obstacle; + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid + { + Vector vecTest; + // measure clearance on left and right to pick the best dir to turn + vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flRightSide = tr.flFraction; + + vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flLeftSide = tr.flFraction; + + // turn left, right or random depending on clearance ratio + float delta = (flRightSide - flLeftSide); + if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) + m_flTurning = -LEECH_TURN_RATE; + else + m_flTurning = LEECH_TURN_RATE; + } + pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); + pev->velocity = gpGlobals->v_forward * pev->speed; + } + pev->ideal_yaw = m_flTurning + targetYaw; + UpdateMotion(); +} + + +void CLeech::Killed(entvars_t *pevAttacker, int iGib) +{ + Vector vecSplatDir; + TraceResult tr; + + ALERT(at_aiconsole, "Leech: killed\n"); + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOwner) + pOwner->DeathNotice(pev); + + // When we hit the ground, play the "death_end" activity + if( pev->waterlevel && pev->watertype != CONTENTS_FOG ) + { + pev->angles.z = 0; + pev->angles.x = 0; + pev->origin.z += 1; + pev->avelocity = g_vecZero; + if ( RANDOM_LONG( 0, 99 ) < 70 ) + pev->avelocity.y = RANDOM_LONG( -720, 720 ); + + pev->gravity = 0.02; + ClearBits(pev->flags, FL_ONGROUND); + SetActivity( ACT_DIESIMPLE ); + } + else + SetActivity( ACT_DIEFORWARD ); + + pev->movetype = MOVETYPE_TOSS; + pev->takedamage = DAMAGE_NO; + SetThink(&CLeech:: DeadThink ); +} + + diff --git a/server/monsters/monsterevent.h b/server/monsters/monsterevent.h new file mode 100644 index 00000000..b9639aca --- /dev/null +++ b/server/monsters/monsterevent.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 MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/server/monsters/monsters.h b/server/monsters/monsters.h new file mode 100644 index 00000000..5d1beafa --- /dev/null +++ b/server/monsters/monsters.h @@ -0,0 +1,163 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef MONSTERS_H +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +// 64 +#define SF_MONSTER_NO_YELLOW_BLOBS 128 //LRC- if the monster is stuck, don't give errors or show yellow blobs. +//LRC- wasn't implemented. #define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_NO_WPN_DROP 1024 //LRC- never drop your weapon (player can't pick it up.) +//LRC - this clashes with 'not in deathmatch'. Replaced with m_iPlayerReact. +//#define SF_MONSTER_INVERT_PLAYERREACT 2048 //LRC- if this monster would usually attack the player, don't attack unless provoked. If you would usually NOT attack the player, attack him. +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#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 + +// spawn flags 256 and above are already taken by the engine +extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); +extern void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ + +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + +#endif //MONSTERS_H diff --git a/server/monsters/nodes.cpp b/server/monsters/nodes.cpp new file mode 100644 index 00000000..0c62dcb0 --- /dev/null +++ b/server/monsters/nodes.cpp @@ -0,0 +1,3653 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include +#include + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); +#ifdef __linux__ +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + FREE ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + FREE ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + FREE ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + FREE ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + FREE( m_pHashLinks ); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)CALLOC ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + CBaseEntity *pSearch; + CBaseEntity *pTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pTrigger = UTIL_FindEntityByTarget ( pSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( !pTrigger ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pSearch = pTrigger; + pevTrigger = pTrigger->pev; + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetState() == STATE_ON && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN )) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetState() == STATE_ON && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN )) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, gmsg.TempEntity); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if( pEntity->pev->waterlevel != 0 && pEntity->pev->watertype != CONTENTS_FOG ) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while( iCurrentNode != iDest ) + { + if( iMaxLoop-- <= 0 ) + { + ALERT( at_console, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch( iCurrentNode, iNext, iLink ); + if( iLink < 0 ) + { + ALERT( at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest ); + return 0; + } + CLink &link = Link( iLink ); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + int i = 0; + for ( i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline USHORT Hash( void *p, int len ) +{ + word usCrc; + + CRC_INIT( &usCrc ); + CRC_PROCESS_BUFFER( &usCrc, p, len ); + + return CRC_FINAL( usCrc ); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "LinkVisibleNodes - Initial Connections\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + fprintf ( file, "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + {// clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + fprintf ( file, "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + {// record info about the ent in the way, if any. + fprintf ( file, " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + fprintf ( file, "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + fprintf ( file, "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + {// this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 ) + { + fprintf ( file, "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + } + + fprintf ( file, "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + fprintf ( file, "----------------------------------------------------------------------------\n\n\n" ); + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, FILE *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "InLine Rejection:\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + fprintf ( file, "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + {// don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + fprintf ( file, "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + pev->yaw_speed = 8; + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so we don't need the test hull + SetThink( Remove ); + SetNextThink( 0 ); + } + else + { + SetThink(&CTestHull :: DropDelay ); + SetNextThink( 1 ); + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( this, WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink(&CTestHull:: CallBuildNodeGraph ); + + SetNextThink( 1 ); +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + {// this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + SetNextThink( 0.1 ); +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + FILE *file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink( Remove );// no matter what happens, the hull gets rid of itself. + SetNextThink( 0 ); + +// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)CALLOC ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + + // make sure directories have been made + GET_GAME_DIR( szNrpFilename ); + strcat( szNrpFilename, "/maps" ); + CreateDirectory( szNrpFilename, NULL ); + strcat( szNrpFilename, "/graphs" ); + CreateDirectory( szNrpFilename, NULL ); + + strcat( szNrpFilename, "/" ); + strcat( szNrpFilename, STRING( gpGlobals->mapname ) ); + strcat( szNrpFilename, ".nrp" ); + + //disable nrp files + file = fopen ( szNrpFilename, "w+" ); + + if ( !file ) + {// file error + ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); + + if ( pTempPool ) + { + FREE( pTempPool ); + } + + return; + } + + fprintf( file, "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + fprintf ( file, "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + {// print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + fprintf ( file, "Node# %4d\n", i ); + fprintf ( file, "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + fprintf ( file, "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + fprintf ( file, "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + fprintf ( file, "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + fprintf ( file, "-------------------------------------------------------------------------------\n" ); + } + fprintf ( file, "\n\n" ); + + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if( WorldGraph.m_pNodes[i].m_afNodeInfo & bits_NODE_AIR ) + { + // do nothing + } + else if( UTIL_PointContents( WorldGraph.m_pNodes[i].m_vecOrigin) & MASK_WATER ) + { + WorldGraph.m_pNodes[i].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[i].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink(&CTestHull :: ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + FREE( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( this, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + FREE( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + //vecSpot.z = pev->origin.z; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + // pev->angles.y = flYaw; + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + fprintf ( file, "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + fprintf ( file, "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + fprintf ( file, "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + fprintf ( file, "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + fprintf ( file, "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + fprintf ( file, "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, file ); + +// now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) CALLOC ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + {// couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + FREE( pTempPool ); + } + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + WorldGraph.m_cLinks = cPoolLinks; + +//copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + fprintf ( file, "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + fprintf ( file, "\nAll Connections are Paired!\n"); + } + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Total Number of Connections in Pool: %d\n", cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + + if ( pTempPool ) + { + // free the temp pool + FREE( pTempPool ); + } + + if ( file ) + { + fclose ( file ); + } + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + + // save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname )); + ALERT( at_console, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int length; + byte *aMemFile; + byte *pMemFile; + + // make sure the directories have been made + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/maps" ); + CreateDirectory( szDirName, NULL ); + strcat( szDirName, "/graphs" ); + CreateDirectory( szDirName, NULL ); + + strcpy ( szFilename, "maps/graphs/" ); + strcat ( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + pMemFile = aMemFile = LOAD_FILE(szFilename, &length); + + if ( !aMemFile ) + { + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )CALLOC ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )CALLOC ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)CALLOC( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)CALLOC( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)CALLOC(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE( aMemFile ); + + if( length != 0 ) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE( aMemFile ); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph( char *szMapName ) +{ + + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + void *file; + + if( !m_fGraphPresent || !m_fGraphPointersSet ) + { + // protect us in the case that the node graph isn't available or built + ALERT( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + sprintf( szFilename, "maps/graphs/%s.nod", szMapName ); + file = g_engfuncs.pfnFOpen( szFilename, "wb" ); + + ALERT( at_aiconsole, "Created: %s\n", szFilename ); + + if( !file ) + { + // couldn't create + ALERT( at_aiconsole, "Couldn't Create: %s\n", szFilename ); + return FALSE; + } + else + { + // write the version + g_engfuncs.pfnFWrite( file, &iVersion, sizeof( int )); + + // write the CGraph class + g_engfuncs.pfnFWrite( file, this, sizeof( CGraph )); + + // write the nodes + g_engfuncs.pfnFWrite( file, m_pNodes, sizeof( CNode ) * m_cNodes ); + + // write the links + g_engfuncs.pfnFWrite( file, m_pLinkPool, sizeof( CLink ) * m_cLinks ); + + g_engfuncs.pfnFWrite( file, m_di, sizeof( DIST_INFO ) * m_cNodes ); + + // write the route info. + if( m_pRouteInfo && m_nRouteInfo ) + { + g_engfuncs.pfnFWrite( file, m_pRouteInfo, sizeof( char ) * m_nRouteInfo ); + } + + if( m_pHashLinks && m_nHashLinks ) + { + g_engfuncs.pfnFWrite( file, m_pHashLinks, sizeof( short ) * m_nHashLinks ); + } + g_engfuncs.pfnFClose( file ); + return TRUE; + } +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + CBaseEntity *pLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pLinkEnt = UTIL_FindEntityByString( NULL, "model", name ); + + if ( !pLinkEnt ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = pLinkEnt->pev; + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + + strcpy ( szBspFilename, "maps/" ); + strcat ( szBspFilename, szMapName ); + strcat ( szBspFilename, ".bsp" ); + + strcpy ( szGraphFilename, "maps/graphs/" ); + strcat ( szGraphFilename, szMapName ); + strcat ( szGraphFilename, ".nod" ); + + retValue = TRUE; + + int iCompare; + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + {// BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + + word usHash; + CRC_INIT( &usHash ); + CRC_PROCESS_BUFFER( &usHash, &np, sizeof( np )); + usHash = CRC_FINAL( usHash ); + + int di = m_HashPrimes[usHash & 15]; + int i = (usHash>>4) % m_nHashLinks; + + while( m_pHashLinks[i] != ENTRY_STATE_EMPTY ) + { + i += di; + if( i >= m_nHashLinks ) + i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch( int iSrcNode, int iDestNode, int &iKey ) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + + word usHash; + CRC_INIT( &usHash ); + CRC_PROCESS_BUFFER( &usHash, &np, sizeof( np )); + usHash = CRC_FINAL( usHash ); + + int di = m_HashPrimes[usHash & 15]; + int i = (usHash >> 4) % m_nHashLinks; + + while( m_pHashLinks[i] != ENTRY_STATE_EMPTY ) + { + CLink &link = Link(m_pHashLinks[i]); + if( iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode ) + { + break; + } + else + { + i += di; + if( i >= m_nHashLinks ) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + int iZone = 0, iPrime = 0; + for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + int i = 0; + for (i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)CALLOC(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + int i = 0; + for (i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if( m_di ) FREE( m_di ); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)CALLOC(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + int i = 0; + for (i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + int j = 0; + for ( j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + int iFrom = 0; + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + int i = 0; + for (i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)CALLOC(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + FREE( m_pRouteInfo ); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)CALLOC(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + int i = 0; + for (i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + int i = 0; + for (i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + // protect us in the case that the node graph isn't available or built + ALERT ( at_console, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if( m_iBaseNode < 0 ) + { + ALERT( at_console, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink(&CNodeViewer:: DrawThink ); + SetNextThink( 0 ); +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + SetNextThink( 0 ); + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 250 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/server/monsters/nodes.h b/server/monsters/nodes.h new file mode 100644 index 00000000..269fe420 --- /dev/null +++ b/server/monsters/nodes.h @@ -0,0 +1,374 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 + +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 + +class CLink +{ +public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment +}; + + +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. +class CGraph +{ +public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? + + CNode *m_pNodes;// pointer to the memory block that contains all node info + CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, FILE *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); + int FSetGraphPointers(void); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); + +}; + +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/server/monsters/osprey.cpp b/server/monsters/osprey.cpp new file mode 100644 index 00000000..a4815124 --- /dev/null +++ b/server/monsters/osprey.cpp @@ -0,0 +1,823 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "baseweapon.h" +#include "nodes.h" +#include "soundent.h" +#include "basebeams.h" + +typedef struct +{ + int isValid; + EHANDLE hGrunt; + Vector vecOrigin; + Vector vecAngles; +} t_ospreygrunt; + + + +#define SF_WAITFORTRIGGER 0x40 + + +#define MAX_CARRY 24 + +class COsprey : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_MACHINE; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + + void UpdateGoal( void ); + BOOL HasDead( void ); + void EXPORT FlyThink( void ); + void EXPORT DeployThink( void ); + void Flight( void ); + void EXPORT HitTouch( CBaseEntity *pOther ); + void EXPORT FindAllThink( void ); + void EXPORT HoverThink( void ); + CBaseMonster *MakeGrunt( Vector vecSrc ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void ShowDamage( void ); + + CBaseEntity *m_pGoalEnt; + Vector m_vel1; + Vector m_vel2; + Vector m_pos1; + Vector m_pos2; + Vector m_ang1; + Vector m_ang2; + float m_startTime; + float m_dTime; + + Vector m_velocity; + + float m_flIdealtilt; + float m_flRotortilt; + + float m_flRightHealth; + float m_flLeftHealth; + + int m_iUnits; + EHANDLE m_hGrunt[MAX_CARRY]; + Vector m_vecOrigin[MAX_CARRY]; + EHANDLE m_hRepel[4]; + + int m_iSoundState; + int m_iSpriteTexture; + + int m_iPitch; + + int m_iExplode; + int m_iTailGibs; + int m_iBodyGibs; + int m_iEngineGibs; + + int m_iDoLeftSmokePuff; + int m_iDoRightSmokePuff; +}; + +LINK_ENTITY_TO_CLASS( monster_osprey, COsprey ); + +TYPEDESCRIPTION COsprey::m_SaveData[] = +{ + DEFINE_FIELD( COsprey, m_pGoalEnt, FIELD_CLASSPTR ), + DEFINE_FIELD( COsprey, m_vel1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_vel2, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_pos1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_pos2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_ang1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_ang2, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_startTime, FIELD_TIME ), + DEFINE_FIELD( COsprey, m_dTime, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_velocity, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_flIdealtilt, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flRotortilt, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_flRightHealth, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flLeftHealth, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_iUnits, FIELD_INTEGER ), + DEFINE_ARRAY( COsprey, m_hGrunt, FIELD_EHANDLE, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_hRepel, FIELD_EHANDLE, 4 ), + + // DEFINE_FIELD( COsprey, m_iSoundState, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iSpriteTexture, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iPitch, FIELD_INTEGER ), + + DEFINE_FIELD( COsprey, m_iDoLeftSmokePuff, FIELD_INTEGER ), + DEFINE_FIELD( COsprey, m_iDoRightSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( COsprey, CBaseMonster ); + +void COsprey :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/osprey.mdl"); + UTIL_SetSize(pev, Vector( -400, -400, -100), Vector(400, 400, 32)); + UTIL_SetOrigin( this, pev->origin ); + + // ALERT( at_console, "Osprey origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + m_flRightHealth = 200; + m_flLeftHealth = 200; + pev->health = 400; + + pev->speed = 80; //LRC - default speed, in case path corners don't give a speed. + + m_flFieldOfView = 0; // 180 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0,0xFF); + + InitBoneControllers(); + + SetThink(&COsprey :: FindAllThink ); + SetUse(&COsprey :: CommandUse ); + + if (!(pev->spawnflags & SF_WAITFORTRIGGER)) + { + SetNextThink( 1.0 ); + } + + m_pos2 = pev->origin; + m_ang2 = pev->angles; + m_vel2 = pev->velocity; +} + + +void COsprey::Precache( void ) +{ + UTIL_PrecacheEntity( "monster_human_grunt" ); + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/osprey.mdl"); + PRECACHE_MODEL("models/HVR.mdl"); + + PRECACHE_SOUND("apache/ap_rotor4.wav"); + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iTailGibs = PRECACHE_MODEL( "models/osprey_tailgibs.mdl" ); + m_iBodyGibs = PRECACHE_MODEL( "models/osprey_bodygibs.mdl" ); + m_iEngineGibs = PRECACHE_MODEL( "models/osprey_enginegibs.mdl" ); +} + +void COsprey::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetNextThink( 0.1 ); +} + +void COsprey :: FindAllThink( void ) +{ + CBaseEntity *pEntity = NULL; + + m_iUnits = 0; + while (m_iUnits < MAX_CARRY && (pEntity = UTIL_FindEntityByClassname( pEntity, "monster_human_grunt" )) != NULL) + { + if (pEntity->IsAlive()) + { + m_hGrunt[m_iUnits] = pEntity; + m_vecOrigin[m_iUnits] = pEntity->pev->origin; + m_iUnits++; + } + } + + if (m_iUnits == 0) + { + m_iUnits = 4; //LRC - stop whining, just make the damn grunts... + +// ALERT( at_console, "osprey error: no grunts to resupply\n"); +// UTIL_Remove( this ); +// return; + } + SetThink(&COsprey :: FlyThink ); + SetNextThink( 0.1 ); + m_startTime = gpGlobals->time; +} + + +void COsprey :: DeployThink( void ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector vecForward = gpGlobals->v_forward; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + Vector vecSrc; + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), ignore_monsters, ENT(pev), &tr); + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * 100 + vecUp * -96; + m_hRepel[0] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * 100 + vecUp * -96; + m_hRepel[1] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * -100 + vecUp * -96; + m_hRepel[2] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * -100 + vecUp * -96; + m_hRepel[3] = MakeGrunt( vecSrc ); + + SetThink(&COsprey :: HoverThink ); + SetNextThink( 0.1 ); +} + + + +BOOL COsprey :: HasDead( ) +{ + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + return TRUE; + } + else + { + m_vecOrigin[i] = m_hGrunt[i]->pev->origin; // send them to where they died + } + } + return FALSE; +} + + +CBaseMonster *COsprey :: MakeGrunt( Vector vecSrc ) +{ + CBaseEntity *pEntity; + CBaseMonster *pGrunt; + + TraceResult tr; + UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + pEntity = Create( "monster_human_grunt", vecSrc, pev->angles ); + pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5 ); + + // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); + pGrunt->m_vecLastPosition = m_vecOrigin[i]; + m_hGrunt[i] = pGrunt; + return pGrunt; + } + } + // ALERT( at_console, "none dead\n"); + return NULL; +} + + +void COsprey :: HoverThink( void ) +{ + int i; + for (i = 0; i < 4; i++) + { + if (m_hRepel[i] != NULL && m_hRepel[i]->pev->health > 0 && !(m_hRepel[i]->pev->flags & FL_ONGROUND)) + { + break; + } + } + + if (i == 4) + { + m_startTime = gpGlobals->time; + SetThink(&COsprey :: FlyThink ); + } + + SetNextThink( 0.1 ); + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); +} + + +void COsprey::UpdateGoal( ) +{ + if (m_pGoalEnt) + { + m_pos1 = m_pos2; + m_ang1 = m_ang2; + m_vel1 = m_vel2; + m_pos2 = m_pGoalEnt->pev->origin; + m_ang2 = m_pGoalEnt->pev->angles; + UTIL_MakeAimVectors( Vector( 0, m_ang2.y, 0 ) ); + + //LRC - ugh. we shouldn't require our path corners to specify a speed! + if (m_pGoalEnt->pev->speed) + pev->speed = m_pGoalEnt->pev->speed; + + m_vel2 = gpGlobals->v_forward * pev->speed; //LRC + + m_startTime = m_startTime + m_dTime; + m_dTime = 2.0 * (m_pos1 - m_pos2).Length() / (m_vel1.Length() + pev->speed); + + // ALERT(at_console, "osprey m_dTime = %f / %f + %f\n", (m_pos1 - m_pos2).Length(), m_vel1.Length(), m_pGoalEnt->pev->speed); + + if (m_ang1.y - m_ang2.y < -180) + { + m_ang1.y += 360; + } + else if (m_ang1.y - m_ang2.y > 180) + { + m_ang1.y -= 360; + } + + if (pev->speed < 400) + m_flIdealtilt = 0; + else + m_flIdealtilt = -90; + } + else + { + ALERT( at_console, "osprey missing target"); + } +} + + +void COsprey::FlyThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + UpdateGoal( ); + } + + if (gpGlobals->time > m_startTime + m_dTime) + { + if (m_pGoalEnt->pev->speed == 0) + { + SetThink(&COsprey:: DeployThink ); + } + int loopbreaker = 100; //LRC - don't loop indefinitely! + do { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + loopbreaker--; //LRC + } while (m_pGoalEnt->pev->speed < 400 && !HasDead() && loopbreaker > 0); + UpdateGoal( ); + } + + Flight( ); + ShowDamage( ); +} + + +void COsprey::Flight( ) +{ + float t = (gpGlobals->time - m_startTime); + float scale = 1.0 / m_dTime; + + float f = UTIL_SplineFraction( t * scale, 1.0 ); + +// ALERT(at_console, "Osprey setorigin m_pos1 %f, m_vel1 %f, m_pos2 %f, m_vel2 %f, m_dTime %f, t %f, f %f\n", m_pos1.x, m_vel1.x, m_pos2.x, m_vel2.x, m_dTime, t, f); + + Vector pos = (m_pos1 + m_vel1 * t) * (1.0 - f) + (m_pos2 - m_vel2 * (m_dTime - t)) * f; + Vector ang = (m_ang1) * (1.0 - f) + (m_ang2) * f; + m_velocity = m_vel1 * (1.0 - f) + m_vel2 * f; + + UTIL_SetOrigin( this, pos ); + pev->angles = ang; + UTIL_MakeAimVectors( pev->angles ); + float flSpeed = DotProduct( gpGlobals->v_forward, m_velocity ); + + // float flSpeed = DotProduct( gpGlobals->v_forward, pev->velocity ); + + float m_flIdealtilt = (160 - flSpeed) / 10.0; + + // ALERT( at_console, "%f %f\n", flSpeed, flIdealtilt ); + if (m_flRotortilt < m_flIdealtilt) + { + m_flRotortilt += 0.5; + if (m_flRotortilt > 0) + m_flRotortilt = 0; + } + if (m_flRotortilt > m_flIdealtilt) + { + m_flRotortilt -= 0.5; + if (m_flRotortilt < -90) + m_flRotortilt = -90; + } + SetBoneController( 0, m_flRotortilt ); + + + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + float pitch = DotProduct( m_velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 75.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + + if (pitch == 100) + pitch = 101; + + if (pitch != m_iPitch) + { + m_iPitch = pitch; + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + // ALERT( at_console, "%.0f\n", pitch ); + } + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + +} + + +void COsprey::HitTouch( CBaseEntity *pOther ) +{ + SetNextThink( 2.0 ); +} + + +/* +int COsprey::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_flRotortilt <= -90) + { + m_flRotortilt = 0; + } + else + { + m_flRotortilt -= 45; + } + SetBoneController( 0, m_flRotortilt ); + return 0; +} +*/ + + + +void COsprey :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + pev->velocity = m_velocity; + pev->avelocity = Vector( RANDOM_FLOAT( -20, 20 ), 0, RANDOM_FLOAT( -50, 50 ) ); + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink(&COsprey :: DyingThink ); + SetTouch(&COsprey :: CrashTouch ); + SetNextThink( 0.1 ); + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + m_startTime = gpGlobals->time + 4.0; +} + +void COsprey::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_startTime = gpGlobals->time; + SetNextThink( 0 ); + m_velocity = pev->velocity; + } +} + + +void COsprey :: DyingThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_startTime > gpGlobals->time ) + { + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); + + Vector vecSpot = pev->origin + pev->velocity * 0.2; + + // random explosions + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iTailGibs ); //model id# + + // # of shards + WRITE_BYTE( 8 ); // let client decide + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + SetNextThink( 0.2 ); + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + */ + + // gibs + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + /* + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 6 ); // framerate + MESSAGE_END(); + */ + + // blast circle + MESSAGE_BEGIN( MSG_PAS, gmsg.TempEntity, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PAS, gmsg.TempEntity, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( m_velocity.x ); + WRITE_COORD( m_velocity.y ); + WRITE_COORD( fabs( m_velocity.z ) * 0.25 ); + + // randomization + WRITE_BYTE( 40 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 128 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + UTIL_Remove( this ); + } +} + + +void COsprey :: ShowDamage( void ) +{ + if (m_iDoLeftSmokePuff > 0 || RANDOM_LONG(0,99) > m_flLeftHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * -340; + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoLeftSmokePuff > 0) + m_iDoLeftSmokePuff--; + } + if (m_iDoRightSmokePuff > 0 || RANDOM_LONG(0,99) > m_flRightHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * 340; + MESSAGE_BEGIN( MSG_PVS, gmsg.TempEntity, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoRightSmokePuff > 0) + m_iDoRightSmokePuff--; + } +} + + +void COsprey::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // only so much per engine + if (ptr->iHitgroup == 3) + { + if (m_flRightHealth < 0) + return; + else + m_flRightHealth -= flDamage; + m_iDoLeftSmokePuff = 3 + (flDamage / 5.0); + } + + if (ptr->iHitgroup == 2) + { + if (m_flLeftHealth < 0) + return; + else + m_flLeftHealth -= flDamage; + m_iDoRightSmokePuff = 3 + (flDamage / 5.0); + } + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2 || ptr->iHitgroup == 3) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } + else + { + UTIL_Sparks( ptr->vecEndPos ); + } +} + + + + + diff --git a/server/monsters/player.cpp b/server/monsters/player.cpp new file mode 100644 index 00000000..af8f7727 --- /dev/null +++ b/server/monsters/player.cpp @@ -0,0 +1,5137 @@ +/*** +* +* 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. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "utils.h" + +#include "cbase.h" +#include "client.h" +#include "player.h" +#include "nodes.h" +#include "baseweapon.h" +#include "soundent.h" +#include "monsters.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "basebeams.h" +#include "defaults.h" + + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +float g_flWeaponCheat; +int gEvilImpulse101; +BOOL GiveOnlyAmmo; +BOOL g_markFrameBounds = 0; //LRC +BOOL gInitHUD = TRUE; + +int MapTextureTypeStepType(char chTextureType); +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); +extern void LinkUserMessages( void ); + +// the world node graph +extern CGraph WorldGraph; + +#define PLAYER_WALLJUMP_SPEED 300 // how fast we can spring off walls +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 0.1 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_fAirFinished, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_fPainFinished, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_szAnimExtention, FIELD_CHARACTER, 32), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pNextItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), // NB: this points to a CFuncTank*Controls* now. --LRC + DEFINE_FIELD( CBasePlayer, m_pMonitor, FIELD_EHANDLE ), + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, pViewEnt, FIELD_CLASSPTR), + DEFINE_FIELD( CBasePlayer, viewFlags, FIELD_INTEGER), + DEFINE_FIELD( CBasePlayer, m_iSndRoomtype, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFogStartDist, FIELD_INTEGER), + DEFINE_FIELD( CBasePlayer, m_iFogEndDist, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFogFinalEndDist, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_FogColor, FIELD_VECTOR ), + DEFINE_FIELD( CBasePlayer, m_FogFadeTime, FIELD_INTEGER), + DEFINE_FIELD( CBasePlayer, m_iWarHUD, FIELD_INTEGER), + + DEFINE_FIELD( CBasePlayer, m_FadeColor, FIELD_VECTOR ), + DEFINE_FIELD( CBasePlayer, m_FadeAlpha, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFadeFlags, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFadeHold, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flStartTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, Rain_dripsPerSecond, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, Rain_windX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_windY, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_randX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_randY, FIELD_FLOAT ), + + DEFINE_FIELD( CBasePlayer, Rain_ideal_dripsPerSecond, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_windX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_windY, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_randX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_randY, FIELD_FLOAT ), + + DEFINE_FIELD( CBasePlayer, Rain_endFade, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, Rain_nextFadeUpdate, FIELD_TIME ), + + //LRC + //DEFINE_FIELD( CBasePlayer, m_iFogStartDist, FIELD_INTEGER ), + //DEFINE_FIELD( CBasePlayer, m_iFogEndDist, FIELD_INTEGER ), + //DEFINE_FIELD( CBasePlayer, m_vecFogColor, FIELD_VECTOR ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_ARRAY( CBasePlayer, m_szTextureName, FIELD_CHARACTER, CBTEXTURENAMEMAX ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_chTextureType, FIELD_CHARACTER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't need to restore + +}; +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + + + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ + // water death sounds + /* + if (pev->waterlevel == 3) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/h2odeath.wav", 1, ATTN_NONE); + return; + } + */ + + // temporarily using pain sounds for death sounds + switch (RANDOM_LONG(1,5)) + { + case 1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + break; + case 2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + break; + case 3: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); + break; + } + + // play one of the suit death alarms + //LRC- if no suit, then no flatline sound. (unless it's a deathmatch.) + if( !(pev->weapons & ITEM_SUIT) && !g_pGameRules->IsDeathmatch()) return; + EMIT_GROUPNAME_SUIT(ENT(pev), "HEV_DEAD"); +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster::TakeHealth (flHealth, bitsDamageType); +} + +int CBasePlayer :: TakeArmor( float flArmor, int suit ) +{ + if(!(pev->weapons & ITEM_SUIT)) return 0; + + if(CBaseMonster::TakeArmor(flArmor )) + { + if(!suit) return 1; //silent take armor + int pct; + char szcharge[64]; + + // Suit reports new power level + pct = (int)( (float)(pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + + return 1; + } + return 0; +} + +int CBasePlayer :: TakeItem( int iItem ) +{ + if ( m_iHideHUD & iItem ) + return 0; + return 1; +} + +Vector CBasePlayer :: GetGunPosition( ) +{ + return pev->origin + pev->view_ofs; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + flDamage *= DMG_HEAD; + break; + case HITGROUP_CHEST: + flDamage *= DMG_CHEST; + break; + case HITGROUP_STOMACH: + flDamage *= DMG_STOMACH; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= DMG_ARM; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= DMG_LEG; + break; + default: + break; + } + + if(bitsDamageType & DMG_NUCLEAR) + { + m_FadeColor = Vector(255, 255, 255); + m_FadeAlpha = 240; + m_iFadeFlags = 0; + m_iFadeTime = 25; + fadeNeedsUpdate = TRUE; + } + else SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // have suit diagnose the problem - ie: report damage type + int bitsDamage = bitsDamageType; + int ffound = TRUE; + int fmajor; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = pev->health; + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + if ( ( bitsDamageType & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = flDamage; + + // Armor. + if (pev->armorvalue && !(bitsDamageType & (DMG_FALL | DMG_DROWN)) )// armor doesn't protect against fall or drown damage! + { + float flNew = flDamage * flRatio; + + float flArmor; + + flArmor = (flDamage - flNew) * flBonus; + + // Does this use more armor than we have? + if (flArmor > pev->armorvalue) + { + flArmor = pev->armorvalue; + flArmor *= (1/flBonus); + flNew = flDamage - flArmor; + pev->armorvalue = 0; + } + else + pev->armorvalue -= flArmor; + + flDamage = flNew; + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + fTookDamage = CBaseMonster::TakeDamage(pevInflictor, pevAttacker, (int)flDamage, bitsDamageType); + + // reset damage time countdown for each type of time based damage player just sustained + + { + for (int i = 0; i < CDMG_TIMEBASED; i++) + if (bitsDamageType & (DMG_PARALYZE << i)) + m_rgbTimeBasedDamage[i] = 0; + } + + + // how bad is it, doc? + + ftrivial = (pev->health > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + fcritical = (pev->health < 30); + + // handle all bits set in this damage message, + // let the suit give player the diagnosis + + // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) + + // UNDONE: still need to record damage and heal messages for the following types + + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) + { + ffound = FALSE; + + if (bitsDamage & DMG_CLUB) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + bitsDamage &= ~DMG_CLUB; + ffound = TRUE; + } + if (bitsDamage & (DMG_FALL | DMG_CRUSH)) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG5", FALSE, SUIT_NEXT_IN_30SEC); // major fracture + else + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + + bitsDamage &= ~(DMG_FALL | DMG_CRUSH); + ffound = TRUE; + } + + if (bitsDamage & DMG_BULLET) + { + if (m_lastDamageAmount > 5) + SetSuitUpdate("!HEV_DMG6", FALSE, SUIT_NEXT_IN_30SEC); // blood loss detected + //else + // SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_BULLET; + ffound = TRUE; + } + + if (bitsDamage & DMG_SLASH) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG1", FALSE, SUIT_NEXT_IN_30SEC); // major laceration + else + SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_SLASH; + ffound = TRUE; + } + if (bitsDamage & DMG_SONIC) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG2", FALSE, SUIT_NEXT_IN_1MIN); // internal bleeding + bitsDamage &= ~DMG_SONIC; + ffound = TRUE; + } + + if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) + { + SetSuitUpdate("!HEV_DMG3", FALSE, SUIT_NEXT_IN_1MIN); // blood toxins detected + bitsDamage &= ~(DMG_POISON | DMG_PARALYZE); + ffound = TRUE; + } + + if (bitsDamage & DMG_ACID) + { + SetSuitUpdate("!HEV_DET1", FALSE, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; + ffound = TRUE; + } + + if (bitsDamage & DMG_NERVEGAS) + { + SetSuitUpdate("!HEV_DET0", FALSE, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; + ffound = TRUE; + } + + if (bitsDamage & DMG_RADIATION) + { + SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; + ffound = TRUE; + } + if (bitsDamage & DMG_NUCLEAR) + { + SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_NUCLEAR; + ffound = TRUE; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = TRUE; + } + } + + pev->punchangle.x = -2; + + if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) + { + // first time we take major damage... + // turn automedic on if not on + SetSuitUpdate("!HEV_MED1", FALSE, SUIT_NEXT_IN_30MIN); // automedic on + + // give morphine shot if not given recently + SetSuitUpdate("!HEV_HEAL7", FALSE, SUIT_NEXT_IN_30MIN); // morphine shot + } + + if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) + { + + // already took major damage, now it's critical... + if (pev->health < 6) + SetSuitUpdate("!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN); // near death + else if (pev->health < 20) + SetSuitUpdate("!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN); // health critical + + // give critical health warnings + if (!RANDOM_LONG(0,3) && flHealthPrev < 50) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + + // if we're taking time based damage, warn about its continuing effects + if (fTookDamage && (bitsDamageType & DMG_TIMEBASED) && flHealthPrev < 75) + { + if (flHealthPrev < 50) + { + if (!RANDOM_LONG(0,3)) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + else + SetSuitUpdate("!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN); // health dropping + } + + return fTookDamage; +} + +//========================================================= +// PackDeadPlayerItems - call this when a player dies to +// pack up the appropriate weapons and ammo items, and to +// destroy anything that shouldn't be packed. +// +// This is pretty brute force :( +//========================================================= +void CBasePlayer::PackDeadPlayerItems( void ) +{ + int iWeaponRules; + int iAmmoRules; + int i; + CBasePlayerWeapon *rgpPackWeapons[ 20 ];// 20 hardcoded for now. How to determine exactly how many weapons we have? + int iPackAmmo[ MAX_AMMO_SLOTS + 1]; + int iPW = 0;// index into packweapons array + int iPA = 0;// index into packammo array + + memset(rgpPackWeapons, NULL, sizeof(rgpPackWeapons) ); + memset(iPackAmmo, -1, sizeof(iPackAmmo) ); + + // get the game rules + iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); + iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); + + if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) + { + // nothing to pack. Remove the weapons and return. Don't call create on the box! + RemoveAllItems( TRUE ); + return; + } + +// go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerWeapon *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + switch( iWeaponRules ) + { + case GR_PLR_DROP_GUN_ACTIVE: + if ( m_pActiveItem && pPlayerItem == m_pActiveItem ) + { + // this is the active item. Pack it. + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + } + break; + + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = (CBasePlayerWeapon *)pPlayerItem; + break; + + default: + break; + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + +// now go through ammo and make a list of which types to pack. + if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) + { + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( m_rgAmmo[ i ] > 0 ) + { + // player has some ammo of this type. + switch ( iAmmoRules ) + { + case GR_PLR_DROP_AMMO_ALL: + iPackAmmo[ iPA++ ] = i; + break; + + case GR_PLR_DROP_AMMO_ACTIVE: + if ( m_pActiveItem && i == m_pActiveItem->m_iPrimaryAmmoType ) + { + // this is the primary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + else if ( m_pActiveItem && i == m_pActiveItem->m_iSecondaryAmmoType ) + { + // this is the secondary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + break; + + default: + break; + } + } + } + } + +// create a box to pack the stuff into. + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "item_weaponbox", pev->origin, pev->angles, edict() ); + + pWeaponBox->pev->angles.x = 0;// don't let weaponbox tilt. + pWeaponBox->pev->angles.z = 0; + + pWeaponBox->SetThink(& CWeaponBox::Kill ); + pWeaponBox->SetNextThink( 120 ); + + // back these two lists up to their first elements + iPA = 0; + iPW = 0; + + // pack the ammo + while( iPackAmmo[ iPA ] != -1 ) + { + pWeaponBox->PackAmmo( CBasePlayerWeapon::AmmoInfoArray[ iPackAmmo[ iPA ] ].iszName, m_rgAmmo[iPackAmmo[iPA]] ); + iPA++; + } + + // now pack all of the items in the lists + while( rgpPackWeapons[ iPW ] ) + { + // weapon unhooked from the player. Pack it into der box. + pWeaponBox->PackWeapon( rgpPackWeapons[ iPW ] ); + + iPW++; + } + + pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. + + RemoveAllItems( TRUE );// now strip off everything that wasn't handled by the code above. +} + +void CBasePlayer::RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerWeapon *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + pev->weapons = 0; + + if ( removeSuit ) pev->weapons &= ~ITEM_SUIT; + + for ( i = 0; i < MAX_AMMO_SLOTS; i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsg.CurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0); + WRITE_BYTE(0); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSound *pSound; + + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + m_pNextItem = NULL; + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + if( m_pMonitor != NULL ) + { + m_pMonitor->Use( this, this, USE_SET, 1 ); + m_pMonitor = NULL; + } + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits( pev->flags, FL_ONGROUND ); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsg.Health, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsg.CurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0xFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + pev->fov = 90.0f; + + pViewEnt = 0; + viewFlags = 0; + viewNeedsUpdate = 1; + + // death fading + m_FadeColor = Vector( 128, 0, 0 ); + m_FadeAlpha = 240; + m_iFadeFlags = FFADE_OUT|FFADE_MODULATE|FFADE_STAYOUT; + m_iFadeTime = 6; + fadeNeedsUpdate = TRUE; + + if ( ( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS ) + { + pev->solid = SOLID_NOT; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(PlayerDeathThink); + SetNextThink( 0.1 ); +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if( pev->flags & FL_FROZEN ) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch( playerAnim ) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 && !FBitSet( pev->watertype, CONTENTS_FOG )) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 220 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3 || pev->watertype & MASK_WATER ) + { + // not underwater + + // play 'up for air' sound + if (m_fAirFinished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (m_fAirFinished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + m_fAirFinished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else if (pev->watertype & MASK_WATER ) // FLYFIELD, FLYFIELD_GRAVITY & FOG aren't really water... + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (m_fAirFinished < gpGlobals->time) // drown! + { + if (m_fPainFinished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + m_fPainFinished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += pev->dmg; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel || pev->watertype & MASK_WATER ) + { + if (FBitSet(pev->flags, FL_INWATER)) + { + // play leave water sound + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(m_fAirFinished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + } + + if( FBitSet( pev->watertype, CONTENTS_LAVA )) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if( FBitSet( pev->watertype, CONTENTS_SLIME )) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet( pev->flags, FL_INWATER )) + { + // player enter water sound + if( FBitSet( pev->watertype, CONTENTS_WATER )) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } + + SetBits( pev->flags, FL_INWATER ); + pev->dmgtime = 0; + } + + if( !FBitSet( pev->flags, FL_WATERJUMP )) + pev->velocity = pev->velocity - 0.8 * pev->waterlevel * gpGlobals->frametime * pev->velocity; +} + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return ( pev->movetype == MOVETYPE_FLY ); +} + +// +// check for a jump-out-of-water +// +void CBasePlayer::CheckWaterJump( ) +{ + if ( IsOnLadder() ) // Don't water jump if on a ladders? (This prevents the bug where + return; // you bounce off water when you are facing it on a ladder). + + Vector vecStart = pev->origin; + vecStart.z += 8; + + UTIL_MakeVectors(pev->angles); + gpGlobals->v_forward.z = 0; + gpGlobals->v_forward = gpGlobals->v_forward.Normalize(); + + Vector vecEnd = vecStart + gpGlobals->v_forward * 24; + + TraceResult tr; + UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + if (tr.flFraction < 1) // solid at waist + { + vecStart.z += pev->maxs.z - 8; + vecEnd = vecStart + gpGlobals->v_forward * 24; + pev->movedir = tr.vecPlaneNormal * -50; + UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + if (tr.flFraction == 1) // open at eye level + { + SetBits( pev->flags, FL_WATERJUMP ); + pev->velocity.z = 225; + pev->teleport_time = gpGlobals->time + 2; // safety net + } + } +} + + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( HasWeapons() ) + { + // we drop the guns here because weapons that have an area effect and can kill their user + // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the + // player class sometimes is freed. It's safer to manipulate the weapons once we know + // we aren't calling into any of their code anymore through the player pointer. + PackDeadPlayerItems(); + } + + //disable redeemer HUD + MESSAGE_BEGIN( MSG_ONE, gmsg.WarHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore + // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn + if ( pev->movetype != MOVETYPE_NONE && FBitSet(pev->flags, FL_ONGROUND) ) + pev->movetype = MOVETYPE_NONE; + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && CVAR_GET_FLOAT( "mp_forcerespawn" ) > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + DontThink(); +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + CBaseEntity *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = UTIL_FindEntityByClassname( NULL, "info_intermission"); + + if ( pSpot ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = UTIL_FindEntityByTargetname( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->pev->origin, pSpot->pev->viewangles ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + m_afPhysicsFlags |= PFLAG_OBSERVER; + + pev->view_ofs = g_vecZero; + pev->angles = pev->viewangles = vecViewAngle; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + pev->modelindex = 0; + UTIL_SetOrigin( this, vecPosition ); +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != NULL ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + else if (m_pMonitor != NULL ) + { + m_pMonitor->Use( this, this, USE_SET, 1 ); + m_pMonitor = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; + EMIT_SOUND( ENT(pev), CHAN_ITEM, "trains/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + TraceResult tr; + int caps; + + UTIL_MakeVectors ( pev->viewangles );// so we know which way we are facing + + //LRC- try to get an exact entity to use. + // (is this causing "use-buttons-through-walls" problems? Surely not!) + UTIL_TraceLine( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + (gpGlobals->v_forward * PLAYER_SEARCH_RADIUS), dont_ignore_monsters, ENT(pev), &tr ); + if (tr.pHit) + { + pObject = CBaseEntity::Instance(tr.pHit); + if (!pObject || !(pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE))) + { + pObject = NULL; + } + } + + if (!pObject) //LRC- couldn't find a direct solid object to use, try the normal method + { + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + caps = pObject->ObjectCaps(); + if (caps & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE) && !(caps & FCAP_ONLYDIRECT_USE)) //LRC - we can't see 'direct use' entities in this section + { + // !!!PERFORMANCE- should this check be done on a per case basis AFTER we've determined that + // this object is actually usable? This dot is being done for every object within PLAYER_SEARCH_RADIUS + // when player hits the use key. How many objects can be in that area, anyway? (sjb) + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + +// ALERT(at_console, "absmin %f %f %f, absmax %f %f %f, mins %f %f %f, maxs %f %f %f, size %f %f %f\n", pObject->pev->absmin.x, pObject->pev->absmin.y, pObject->pev->absmin.z, pObject->pev->absmax.x, pObject->pev->absmax.y, pObject->pev->absmax.z, pObject->pev->mins.x, pObject->pev->mins.y, pObject->pev->mins.z, pObject->pev->maxs.x, pObject->pev->maxs.y, pObject->pev->maxs.z, pObject->pev->size.x, pObject->pev->size.y, pObject->pev->size.z);//LRCTEMP + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot || vecLOS == g_vecZero ) // LRC - if the player is standing inside this entity, it's also ok to use it. + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + } + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + // (actually, nothing uses on/off. They're either continuous - rechargers and momentary + // buttons - or they're impulse - buttons, doors, tanks, trains, etc.) --LRC + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer :: Jump( void ) +{ + float flScale; + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if( FBitSet( pev->flags, FL_WATERJUMP )) + return; + + if( pev->waterlevel >= 2 && !FBitSet( pev->watertype, CONTENTS_FOG )) + { + if( pev->watertype & CONTENTS_WATER ) + flScale = 100; + else if( pev->watertype & CONTENTS_SLIME ) + flScale = 80; + else flScale = 50; + pev->velocity.z = flScale; + + // play swiming sound + if (m_flSwimTime < gpGlobals->time) + { + m_flSwimTime = gpGlobals->time + 1; + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM); break; + } + } + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + + // many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + ClearBits(pev->flags, FL_ONGROUND);// don't stairwalk + + SetAnimation( PLAYER_JUMP ); + + // play step sound for current texture at full volume + + PlayStepSound(MapTextureTypeStepType(m_chTextureType), 1.0); + + if ( FBitSet( pev->flags, FL_DUCKING ) || FBitSet(m_afPhysicsFlags, PFLAG_DUCKING) ) + { + if ( m_fLongJump && (pev->button & IN_DUCK) && ( gpGlobals->time - m_flDuckTime < 1 ) && pev->velocity.Length() > 50 ) + { + Msg( "LongJump!\n" ); + + // play longjump 'servo' sound + if ( RANDOM_LONG(0,1) ) EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain2.wav", 1, ATTN_NORM); + else EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain4.wav", 1, ATTN_NORM); + + pev->punchangle.x = -5; + pev->velocity = gpGlobals->v_forward * (PLAYER_LONGJUMP_SPEED * 1.6); + pev->velocity.z = sqrt( 2 * 800 * 56.0 ); // jump 56 units + SetAnimation( PLAYER_SUPERJUMP ); + } + else + { // ducking jump + pev->velocity.z = sqrt( 2 * 800 * 45.0 ); // jump 45 units + } + } + else + { + pev->velocity.z = sqrt( 2 * 800 * 45.0 ); // jump 45 units + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +// It takes 0.4 seconds to duck +#define TIME_TO_DUCK 0.4 + +// This is a little more complicated now than it used to be, but I think for the better - +// The player's origin no longer moves when ducking. If you start ducking while standing on ground, +// your view offset is non-linearly blended to the ducking position and then your hull is changed. +// In air, duck works just like it used to - you pick up your feet (more like tucking). +// PFLAG_DUCKING was added to track the state where the player is in the process of ducking, but hasn't +// reached the crouched position yet. +// +// A few of nice side effects of this are: +// a) On ground status does not change when ducking (good on trains, etc) +// b) origin stays constant +// c) Superjump can begin while still in run animation (nice when looking at others in multiplayer) +// +void CBasePlayer::Duck( ) +{ + float time, duckFraction; + + if (pev->button & IN_DUCK) + { + if(( m_afButtonPressed & IN_DUCK ) && !(pev->flags & FL_DUCKING) ) + { + m_flDuckTime = gpGlobals->time; + SetBits(m_afPhysicsFlags,PFLAG_DUCKING); // We are in the process of ducking + } + time = (gpGlobals->time - m_flDuckTime); + + // if we're not already done ducking + if ( FBitSet( m_afPhysicsFlags, PFLAG_DUCKING ) ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( time >= TIME_TO_DUCK || !(pev->flags & FL_ONGROUND) ) + { + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pev->flags & FL_ONGROUND ) + { + pev->origin = pev->origin - (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + FixPlayerCrouchStuck( edict() ); + } + + + UTIL_SetOrigin( this, pev->origin ); + + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + pev->view_ofs = VEC_DUCK_VIEW; + SetBits( pev->flags, FL_DUCKING ); // Hull is duck hull + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); // Done ducking (don't ease-in when the player hits the ground) + } + else + { + // Calc parametric time + duckFraction = UTIL_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pev->view_ofs = ((VEC_DUCK_VIEW - (VEC_DUCK_HULL_MIN - VEC_HULL_MIN)) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + SetAnimation( PLAYER_WALK ); + } + else + { + TraceResult trace; + Vector newOrigin = pev->origin; + + if ( pev->flags & FL_ONGROUND ) + newOrigin = newOrigin + (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + UTIL_TraceHull( newOrigin, newOrigin, dont_ignore_monsters, human_hull, ENT(pev), &trace ); + + if ( !trace.fStartSolid ) + { + ClearBits( pev->flags, FL_DUCKING ); + ClearBits( m_afPhysicsFlags, PFLAG_DUCKING ); + pev->view_ofs = VEC_VIEW; + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + pev->origin = newOrigin; + } + } +} + + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = -pev->frags; // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsg.ScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( pev->frags ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +//Player ID +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + char sbuf0[ SBAR_STRING_SIZE ]; + char sbuf1[ SBAR_STRING_SIZE ]; + + memset( newSBarState, 0, sizeof(newSBarState) ); + strcpy( sbuf0, m_SbarString0 ); + strcpy( sbuf1, m_SbarString1 ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->viewangles + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if (pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + strcpy( sbuf1, "1 %p1\n2 Health: %i2%%\n3 Armor: %i3%%" ); + + // allies and medics get to see the targets health + if ( g_pGameRules->PlayerRelationship( this, pEntity ) == GR_TEAMMATE ) + { + newSBarState[ SBAR_ID_TARGETHEALTH ] = 100 * (pEntity->pev->health / pEntity->pev->max_health); + newSBarState[ SBAR_ID_TARGETARMOR ] = pEntity->pev->armorvalue; //No need to get it % based since 100 it's the max. + } + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + } + } + + BOOL bForceResend = FALSE; + + if ( strcmp( sbuf0, m_SbarString0 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.StatusText, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_STRING( sbuf0 ); + MESSAGE_END(); + + strcpy( m_SbarString0, sbuf0 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + if ( strcmp( sbuf1, m_SbarString1 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.StatusText, NULL, pev ); + WRITE_BYTE( 1 ); + WRITE_STRING( sbuf1 ); + MESSAGE_END(); + + strcpy( m_SbarString1, sbuf1 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] || bForceResend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.StatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + + + + + +// play a footstep if it's time - this will eventually be frame-based. not time based. + +#define STEP_CONCRETE 0 // default step sound +#define STEP_METAL 1 // metal floor +#define STEP_DIRT 2 // dirt, sand, rock +#define STEP_VENT 3 // ventillation duct +#define STEP_GRATE 4 // metal grating +#define STEP_TILE 5 // floor tiles +#define STEP_SLOSH 6 // shallow liquid puddle +#define STEP_WADE 7 // wading in liquid +#define STEP_LADDER 8 // climbing ladder + +// Play correct step sound for material we're on or in + +void CBasePlayer :: PlayStepSound(int step, float fvol) +{ + static int iSkipStep = 0; + + if ( !g_pGameRules->PlayFootstepSounds( this, fvol ) ) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + int irand = RANDOM_LONG(0,1) + (m_iStepLeft * 2); + + m_iStepLeft = !m_iStepLeft; + + switch (step) + { + default: + case STEP_CONCRETE: + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_METAL: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_metal4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_DIRT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_dirt4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_VENT: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_duct4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_GRATE: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_grate4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_TILE: + if (!RANDOM_LONG(0,4)) + irand = 4; + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile4.wav", fvol, ATTN_NORM); break; + case 4: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_tile5.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_SLOSH: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch (irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM); break; + } + break; + case STEP_LADDER: + switch(irand) + { + // right foot + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM); break; + // left foot + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM); break; + } + break; + } +} + +// Simple mapping from texture type character to step type + +int MapTextureTypeStepType(char chTextureType) +{ + switch (chTextureType) + { + default: + case CHAR_TEX_CONCRETE: return STEP_CONCRETE; + case CHAR_TEX_METAL: return STEP_METAL; + case CHAR_TEX_DIRT: return STEP_DIRT; + case CHAR_TEX_VENT: return STEP_VENT; + case CHAR_TEX_GRATE: return STEP_GRATE; + case CHAR_TEX_TILE: return STEP_TILE; + case CHAR_TEX_SLOSH: return STEP_SLOSH; + } +} + +// Play left or right footstep based on material player is on or in + +void CBasePlayer :: UpdateStepSound( void ) +{ + int fWalking; + float fvol; + char szbuffer[64]; + const char *pTextureName; + Vector start, end; + float rgfl1[3]; + float rgfl2[3]; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if (gpGlobals->time <= m_flTimeStepSound) + return; + + if( pev->flags & FL_FROZEN ) + return; + + speed = pev->velocity.Length(); + + // determine if we are on a ladder + fLadder = IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if( FBitSet( pev->flags, FL_DUCKING ) || fLadder ) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 0.1; + } + else + { + velwalk = 120; + velrun = 210; + flduck = 0.0; + } + + // ALERT (at_console, "vel: %f\n", vecVel.Length()); + + // if we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if m_flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + + if ((fLadder || FBitSet (pev->flags, FL_ONGROUND)) && pev->velocity != g_vecZero + && (speed >= velwalk || !m_flTimeStepSound)) + { + SetAnimation( PLAYER_WALK ); + + fWalking = speed < velrun; + + center = knee = feet = (pev->absmin + pev->absmax) * 0.5; + height = pev->absmax.z - pev->absmin.z; + + knee.z = pev->absmin.z + height * 0.2; + feet.z = pev->absmin.z; + + // find out what we're stepping in or on... + if( fLadder ) + { + step = STEP_LADDER; + fvol = 0.35; + m_flTimeStepSound = gpGlobals->time + 0.35; + } + else if( UTIL_PointContents( knee ) & MASK_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + m_flTimeStepSound = gpGlobals->time + 0.6; + } + else if( UTIL_PointContents( feet ) & MASK_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + } + else + { + // find texture under player, if different from current texture, + // get material type + + start = end = center; // center point of player BB + start.z = end.z = pev->absmin.z; // copy zmin + start.z += 4.0; // extend start up + end.z -= 24.0; // extend end down + + start.CopyToArray(rgfl1); + end.CopyToArray(rgfl2); + + pTextureName = TRACE_TEXTURE( ENT( pev->groundentity), rgfl1, rgfl2 ); + if ( pTextureName ) + { + // strip leading '-0' or '{' or '!' + if (*pTextureName == '-') + pTextureName += 2; + if (*pTextureName == '{' || *pTextureName == '!') + pTextureName++; + + if (_strnicmp(pTextureName, m_szTextureName, CBTEXTURENAMEMAX-1)) + { + // current texture is different from texture player is on... + // set current texture + strcpy(szbuffer, pTextureName); + szbuffer[CBTEXTURENAMEMAX - 1] = 0; + strcpy(m_szTextureName, szbuffer); + + // ALERT ( at_aiconsole, "texture: %s\n", m_szTextureName ); + + // get texture type + m_chTextureType = TEXTURETYPE_Find(m_szTextureName); + } + } + + step = MapTextureTypeStepType(m_chTextureType); + + switch (m_chTextureType) + { + default: + case CHAR_TEX_CONCRETE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_METAL: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_DIRT: + fvol = fWalking ? 0.25 : 0.55; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_VENT: + fvol = fWalking ? 0.4 : 0.7; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_GRATE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_TILE: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + + case CHAR_TEX_SLOSH: + fvol = fWalking ? 0.2 : 0.5; + m_flTimeStepSound = fWalking ? gpGlobals->time + 0.4 : gpGlobals->time + 0.3; + break; + } + } + + m_flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + + // 35% volume if ducking + if ( pev->flags & FL_DUCKING ) + fvol *= 0.35; + + PlayStepSound(step, fvol); + } +} + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The ones that changed and are now down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones that changed and aren't down are "released" + + g_pGameRules->PlayerThink( this ); + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->viewangles); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + if (pev->waterlevel == 2) + CheckWaterJump(); + else if ( pev->waterlevel == 0 ) + { + pev->flags &= ~FL_WATERJUMP; // Clear this if out of water + } + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, 0x2 ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if( pev->button & IN_JUMP ) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet( pev->flags, FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + // play a footstep if it's time - this will eventually be frame-based. not time based. + + UpdateStepSound(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + #define DMG_NUCLEAR (1 << 24) + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + static float gtbdPrev = 0.0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->time - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsg.GeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer::CheckSuitUpdate() +{ + int i; + int isentence = 0; + int isearch = m_iSuitPlayNext; + + // Ignore suit updates if no suit + if (!(pev->weapons & ITEM_SUIT)) + return; + + // if in range of radiation source, ping geiger counter + UpdateGeigerCounter(); + + if ( g_pGameRules->IsMultiplayer() ) + { + // don't bother updating HEV voice in multiplayer. + return; + } + + if ( gpGlobals->time >= m_flSuitUpdate && m_flSuitUpdate > 0) + { + // play a sentence off of the end of the queue + for (i = 0; i < CSUITPLAYLIST; i++) + { + if (isentence = m_rgSuitPlayList[isearch]) + break; + + if (++isearch == CSUITPLAYLIST) + isearch = 0; + } + + if (isentence) + { + m_rgSuitPlayList[isearch] = 0; + if (isentence > 0) + { + // play sentence number + + char sentence[CBSENTENCENAME_MAX+1]; + strcpy(sentence, "!"); + strcat(sentence, gszallsentencenames[isentence]); + EMIT_SOUND_SUIT(ENT(pev), sentence); + } + else + { + // play sentence group + EMIT_GROUPID_SUIT(ENT(pev), -isentence); + } + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + else + // queue is empty, don't check + m_flSuitUpdate = 0; + } +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if (!(pev->weapons & ITEM_SUIT)) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup( name, NULL ); + if( isentence < 0 ) + { + ALERT( at_console, "HEV couldn't find sentence %s\n", name ); + return; + } + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->time) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + +} + +/* +================ +CheckPowerups + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if( !pSound ) + { + ALERT ( at_console, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = pev->velocity.Length(); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= 250 * gpGlobals->frametime; + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= 250 * gpGlobals->frametime; + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= 256 * gpGlobals->frametime; + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + //UTIL_MakeVectors ( pev->angles ); + //gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) return; // intermission or finale + if ( !IsAlive () ) return; + + if ( m_pTank != NULL ) + { + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ); + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + + if ( m_pMonitor != NULL ) + { + if ( !m_pMonitor->OnControls( pev ) ) + { // they've moved off the platform + m_pMonitor->Use( this, this, USE_SET, 1 ); + m_pMonitor = NULL; + } + } + + // do weapon stuff + ItemPostFrame( ); + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if( FBitSet( pev->watertype, CONTENTS_WATER )) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // after this point, we start doing damage + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM); + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM); + } + + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + { + // splat + // NOTE: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + } + + fvol = 1.0; + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + // EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_jumpland2.wav", 1, ATTN_NORM); + fvol = 0.85; + } + else if ( m_flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // get current texture under player right away + m_flTimeStepSound = 0; + UpdateStepSound(); + + // play step sound for current texture + PlayStepSound(MapTextureTypeStepType(m_chTextureType), fvol); + + // knock the screen around a little bit, temporary effect + pev->punchangle.x = m_flFallVelocity * 0.018; // punch x axis + + if ( pev->punchangle.x > 8 ) + { + pev->punchangle.x = 8; + } + + if ( RANDOM_LONG(0,1) ) + { + pev->punchangle.z = m_flFallVelocity * 0.009; + } + + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, m_flFallVelocity, 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + StudioFrameAdvance( ); + CheckPowerups(pev); + + UpdatePlayerSound(); + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( pSpot->GetState( pPlayer ) != STATE_ON ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + return FALSE; + } + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + + // q3a maps doesn't contain info_player_start + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_deathmatch"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT( at_error, "PutClientInServer: no info_player_start on level\n" ); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ +// ALERT(at_console, "PLAYER spawns at time %f\n", gpGlobals->time); + + pev->classname = MAKE_STRING( "player" ); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags = FL_CLIENT; + m_fAirFinished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->effects = 0; + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + pev->renderfx = 0; + pev->rendercolor = g_vecZero; + pev->mass = 90; // lbs + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = 1; // no longjump module. + Rain_dripsPerSecond = 0; + Rain_windX = 0; + Rain_windY = 0; + Rain_randX = 0; + Rain_randY = 0; + Rain_ideal_dripsPerSecond = 0; + Rain_ideal_windX = 0; + Rain_ideal_windY = 0; + Rain_ideal_randX = 0; + Rain_ideal_randY = 0; + Rain_endFade = 0; + Rain_nextFadeUpdate = 0; + GiveOnlyAmmo = FALSE; + + pev->fov = 90.0f; // init field of view. + //m_iAcessLevel = 2; + + m_flNextDecalTime = 0; // let this player decal as soon as he spawns. + + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + +// dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->SetDefaultPlayerTeam( this ); + g_pGameRules->GetPlayerSpawnSpot( this ); + + UTIL_SetModel(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if( FBitSet( pev->flags, FL_DUCKING )) + UTIL_SetSize( pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX ); + else UTIL_SetSize( pev, VEC_HULL_MIN, VEC_HULL_MAX ); + + pev->view_ofs = VEC_VIEW; + pViewEnt = 0; + viewFlags = 0; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_console, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + m_flNextChatTime = gpGlobals->time; + + g_pGameRules->PlayerSpawn( this ); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + GiveOnlyAmmo = FALSE; + + m_iClientBattery = -1; + + m_iTrain = TRAIN_NEW; + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + viewNeedsUpdate = 1; + hearNeedsUpdate = 1; + fogNeedsUpdate = 1; + fadeNeedsUpdate = 1; + m_iClientWarHUD = -1; + + m_iUpdateTime = 5; // won't update for 1/2 a second + + if ( gInitHUD ) m_fInitHUD = TRUE; + rainNeedsUpdate = 1; + + //clear fade effects + if(IsMultiplayer()) + { + m_FadeColor = Vector(255, 255, 255); + m_FadeAlpha = 0; + m_iFadeFlags = 0; + m_iFadeTime = 0; + m_iFadeHold = 0; + fadeNeedsUpdate = TRUE; + } +} + + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "cPLAYER", "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if( !pSaveData->fUseLandmark ) + { + ALERT( at_error, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->viewangles.z = 0; // Clear out roll + pev->angles = pev->viewangles; + + SET_FIXANGLE( ENT( pev ), pev->angles ); + pev->fixangle = TRUE; // turn this way immediately + + // Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if( FBitSet( pev->flags, FL_DUCKING )) + { + // Use the crouch HACK + FixPlayerCrouchStuck( edict() ); + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + RenewItems(); + + return status; +} + + + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerWeapon *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerWeapon *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + if (m_pActiveItem) m_pActiveItem->Holster( ); + QueueItem(pItem); + if (m_pActiveItem) m_pActiveItem->Deploy( ); +} + +void CBasePlayer::QueueItem(CBasePlayerWeapon *pItem) +{ + if(!m_pActiveItem)// no active weapon + { + m_pActiveItem = pItem; + return;// just set this item as active + } + else + { + m_pLastItem = m_pActiveItem; + m_pActiveItem = NULL;// clear current + } + m_pNextItem = pItem;// add item to queue +} + +void CBasePlayer::SelectItem(const char *pstr) +{ + if (!pstr) return; + + CBasePlayerWeapon *pItem = NULL; + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + + while (pItem) + { + if(FStrEq( STRING( pItem->pev->netname), pstr )) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + if (m_pActiveItem) m_pActiveItem->Holster( ); + QueueItem(pItem); + if (m_pActiveItem) m_pActiveItem->Deploy( ); +} + + +void CBasePlayer::SelectLastItem(void) +{ + if (!m_pLastItem) + { + return; + } + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + ResetAutoaim( ); + + // FIX, this needs to queue them up and delay + if (m_pActiveItem) m_pActiveItem->Holster( ); + QueueItem(m_pLastItem); + if (m_pActiveItem) m_pActiveItem->Deploy( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->viewangles; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + SetNextThink( 0.1 ); + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + int playernum; + int nFrames; + CBasePlayer *pPlayer; + + pPlayer = (CBasePlayer *)GET_PRIVATE(pev->owner); + + if (pPlayer) + nFrames = pPlayer->GetCustomDecalFrames(); + else + nFrames = -1; + + playernum = ENTINDEX(pev->owner); + + // ALERT(at_console, "Spray by player %i, %i of %i\n", playernum, (int)(pev->frame + 1), nFrames); + + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + // No customization present. + if (nFrames == -1) + { + UTIL_DecalTrace( &tr, DECAL_LAMBDA6 ); + UTIL_Remove( this ); + } + else + { + UTIL_PlayerDecalTrace( &tr, playernum, pev->frame, TRUE ); + // Just painted last custom frame. + if ( pev->frame++ >= (nFrames - 1)) + UTIL_Remove( this ); + } + + SetNextThink( 0.1 ); +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->viewangles; + pev->owner = ENT(pevOwner); + + SetThink(&CBloodSplat:: Spray ); + SetNextThink( 0.1 ); +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 128, ignore_monsters, pev->owner, & tr); + + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink( Remove ); + SetNextThink( 0.1 ); +} + +//============================================== + +void CBasePlayer::GiveNamedItem( const char *pszName ) +{ + edict_t *pent; + int istr = MAKE_STRING( pszName ); + + pent = CREATE_NAMED_ENTITY( istr ); + if( FNullEnt( pent )) return; + + ALERT( at_aiconsole, "Give %s\n", STRING( pent->v.classname )); + + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev )); +} + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet( pev->effects, EF_DIMLIGHT ); +} + + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight()) return; + + if (m_iFlashBattery == 0) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + return; + } + + if( pev->weapons & ITEM_SUIT ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + SetBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsg.Flashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + } +} + + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsg.Flashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientBattery = -1; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsg.ResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + switch (iImpulse) + { + case 100: //enable flashlight + if ( FlashlightIsOn()) FlashlightTurnOff(); + else FlashlightTurnOn(); + break; + case 201: // paint decal + if( gpGlobals->time < m_flNextDecalTime ) break; + + UTIL_MakeVectors(pev->viewangles); + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + { + // line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + CVAR_GET_FLOAT( "decalfrequency" ); + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + break; + case 204: //Demo recording, update client dll specific data again. + ForceClientDllUpdate(); + break; + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ + if ( !g_flWeaponCheat) return; + + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + + case 101: + { + if( GiveOnlyAmmo ) + { + gEvilImpulse101 = TRUE; + GiveNamedItem( "item_suit" ); //player may don't give suit at first time + GiveNamedItem("weapon_handgrenade"); + GiveNamedItem( "ammo_buckshot" ); + GiveNamedItem( "ammo_9mmclip" ); + GiveNamedItem( "ammo_357" ); + GiveNamedItem( "ammo_556" ); + GiveNamedItem( "ammo_762" ); + GiveNamedItem( "ammo_9mmAR" ); + GiveNamedItem( "ammo_m203" ); + GiveNamedItem( "ammo_rpgclip" ); + gEvilImpulse101 = FALSE; + } + else + { + gEvilImpulse101 = TRUE; + char *pfile = (char *)LOAD_FILE( "scripts/impulse101.txt", NULL ); + int ItemName[256]; + int count = 0; + char token[32]; + + if( pfile ) + { + char *afile = pfile; + while( pfile ) + { + // parsing impulse101.txt + pfile = COM_ParseFile( pfile, token ); + if(strlen( token )) + { + ItemName[count] = ALLOC_STRING( (char *)token ); + count++; + } + } + COM_FreeFile( afile ); + for( int i = 0; i < count; i++ ) + GiveNamedItem( (char *)STRING( ItemName[i]) ); + } + GiveOnlyAmmo = TRUE;//no need to give all weapons again + gEvilImpulse101 = FALSE; + } + } + break; + + case 102: + { + pEntity = UTIL_FindEntityForward( this ); + if (pEntity) pEntity->Use( this, this, USE_TOGGLE, 0); + break; + } + case 103: // print global info about current entity or texture + { + TraceResult tr; + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, ignore_monsters, edict(), &tr ); + if ( tr.pHit ) pWorld = tr.pHit; + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + pEntity = UTIL_FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) pMonster->ReportAIState(); + else pEntity->Use( this, this, USE_SHOWINFO, 0 ); + } + else if ( pTextureName ) ALERT( at_console, "Texture: %s\n", pTextureName ); + } + break; + + case 104:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 105:// remove any solid entity. + pEntity = UTIL_FindEntityForward( this ); + if ( pEntity ) UTIL_Remove (pEntity); + break; + case 106://give weapon_debug + GiveNamedItem( "weapon_debug" ); + break; + } +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer::AddPlayerItem( CBasePlayerWeapon *pItem ) +{ + CBasePlayerWeapon *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while (pInsert) + { + if (FClassnameIs( pInsert->pev, STRING( pItem->pev->netname))) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + pItem->Drop( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Drop(); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Drop(); + } + + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerWeapon *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( ); + pItem->DontThink();// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerWeapon *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, char *szName, int iMax ) +{ + if ( !szName ) return -1; + + if ( !g_pGameRules->CanHaveAmmo( this, szName, iMax ) ) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) return -1; + + int iAdd = min( iCount, iMax - m_rgAmmo[i] ); + if ( iAdd < 1 ) return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsg.AmmoPickup )// make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsg.AmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex(szName) ); + WRITE_BYTE( iAdd ); + MESSAGE_END(); + } + + return i; +} + + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( gpGlobals->time < m_flNextAttack ) + { + return; + } + + if (!m_pActiveItem)// XWider + { + if(m_pNextItem) + { + m_pActiveItem = m_pNextItem; + m_pActiveItem->Deploy(); + m_pNextItem = NULL; + } + } + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + static int fInSelect = FALSE; + + // check if the player is using a tank + if ( m_pTank != NULL || m_pMonitor != NULL)return; + + if ( gpGlobals->time < m_flNextAttack ) + { + return; + } + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer :: GetAmmoIndex( const char *psz ) +{ + int i; + + if( !psz || !stricmp( psz, "none" )) return -1; + + for( i = 1; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerWeapon::AmmoInfoArray[i].iszName ) + continue; + + if( !stricmp( psz, STRING( CBasePlayerWeapon::AmmoInfoArray[i].iszName ))) + return i; + } + return -1; +} + +const char *CBasePlayer::GetAmmoName( int index ) +{ + return STRING( CBasePlayerWeapon::AmmoInfoArray[index].iszName ); +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsg.AmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by Player::PreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ + +void CBasePlayer :: UpdateClientData( void ) +{ + // check for cheats here + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? + + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsg.ResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.InitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + UTIL_FireTargets( "game_playerjoin", this, this, USE_TOGGLE ); + } + m_iStartMessage = 1; //send player init messages + } + + UTIL_FireTargets( "game_playerspawn", this, this, USE_TOGGLE ); + InitStatusBar(); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.HideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if( viewNeedsUpdate != 0 ) + { + int indexToSend; + + //we can entity to look at + if(pViewEnt)indexToSend = pViewEnt->entindex(); + else //just reset camera + { + indexToSend = 0; + viewFlags = 0; //clear possibly ACTIVE flag + } + + MESSAGE_BEGIN(MSG_ONE, gmsg.CamData, NULL, pev); + WRITE_SHORT( indexToSend ); + WRITE_SHORT( viewFlags ); + MESSAGE_END(); + + viewNeedsUpdate = 0; + } + + if( hearNeedsUpdate != 0 ) + { + // update dsp sound + MESSAGE_BEGIN( MSG_ONE, gmsg.RoomType, NULL, pev ); + WRITE_SHORT( m_iSndRoomtype ); + MESSAGE_END(); + hearNeedsUpdate = 0; + } + + if( fadeNeedsUpdate != 0 ) + { + // update screenfade + MESSAGE_BEGIN( MSG_ONE, gmsg.Fade, NULL, pev ); + WRITE_SHORT( FixedUnsigned16( m_iFadeTime, 1<<12 )); + WRITE_SHORT( FixedUnsigned16( m_iFadeHold, 1<<12 )); + WRITE_SHORT( m_iFadeFlags ); // fade flags + WRITE_BYTE( (byte)m_FadeColor.x ); // fade red + WRITE_BYTE( (byte)m_FadeColor.y ); // fade green + WRITE_BYTE( (byte)m_FadeColor.z ); // fade blue + WRITE_BYTE( (byte)m_FadeAlpha ); // fade alpha + MESSAGE_END(); + fadeNeedsUpdate = 0; + } + + if (m_iStartMessage != 0) + { + SendStartMessages(); + m_iStartMessage = 0; + } + + if(m_FogFadeTime != 0) + { + float flDegree = (gpGlobals->time - m_flStartTime) / m_FogFadeTime; + m_iFogEndDist -= (FOG_LIMIT - m_iFogFinalEndDist)*(flDegree / m_iFogFinalEndDist); + + // cap it + if (m_iFogEndDist > FOG_LIMIT) + { + m_iFogEndDist = FOG_LIMIT; + m_FogFadeTime = 0; + } + if (m_iFogEndDist < m_iFogFinalEndDist) + { + m_iFogEndDist = m_iFogFinalEndDist; + m_FogFadeTime = 0; + } + fogNeedsUpdate = TRUE; + } + + if(fogNeedsUpdate != 0) + { + //update fog + MESSAGE_BEGIN( MSG_ONE, gmsg.SetFog, NULL, pev ); + WRITE_BYTE ( m_FogColor.x ); + WRITE_BYTE ( m_FogColor.y ); + WRITE_BYTE ( m_FogColor.z ); + WRITE_SHORT ( m_iFogStartDist ); + WRITE_SHORT ( m_iFogEndDist ); + MESSAGE_END(); + fogNeedsUpdate = 0; + } + + if( m_iWarHUD != m_iClientWarHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsg.WarHUD, NULL, pev ); + WRITE_BYTE( m_iWarHUD );//make static screen + MESSAGE_END(); + m_iClientWarHUD = m_iWarHUD; + } + + if (pev->health != m_iClientHealth) + { + int iHealth = max( pev->health, 0 ); // make sure that no negative health values are sent + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsg.Health, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = pev->health; + } + + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = pev->armorvalue; + + ASSERT( gmsg.Battery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsg.Battery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsg.Damage, NULL, pev ); + WRITE_BYTE( pev->dmg_save ); + WRITE_BYTE( pev->dmg_take ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsg.FlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + // calculate and update rain fading + if (Rain_endFade > 0) + { + if (gpGlobals->time < Rain_endFade) + { // we're in fading process + if (Rain_nextFadeUpdate <= gpGlobals->time) + { + int secondsLeft = Rain_endFade - gpGlobals->time + 1; + + Rain_dripsPerSecond += (Rain_ideal_dripsPerSecond - Rain_dripsPerSecond) / secondsLeft; + Rain_windX += (Rain_ideal_windX - Rain_windX) / (float)secondsLeft; + Rain_windY += (Rain_ideal_windY - Rain_windY) / (float)secondsLeft; + Rain_randX += (Rain_ideal_randX - Rain_randX) / (float)secondsLeft; + Rain_randY += (Rain_ideal_randY - Rain_randY) / (float)secondsLeft; + + Rain_nextFadeUpdate = gpGlobals->time + 1; // update once per second + rainNeedsUpdate = 1; + + ALERT(at_aiconsole, "Rain fading: curdrips: %i, idealdrips %i\n", Rain_dripsPerSecond, Rain_ideal_dripsPerSecond); + } + } + else + { // finish fading process + Rain_nextFadeUpdate = 0; + Rain_endFade = 0; + + Rain_dripsPerSecond = Rain_ideal_dripsPerSecond; + Rain_windX = Rain_ideal_windX; + Rain_windY = Rain_ideal_windY; + Rain_randX = Rain_ideal_randX; + Rain_randY = Rain_ideal_randY; + rainNeedsUpdate = 1; + + ALERT(at_aiconsole, "Rain fading finished at %i drips\n", Rain_dripsPerSecond); + } + } + + // send rain message + if (rainNeedsUpdate) + { + //search for rain_settings entity + CBaseEntity *pFind; + pFind = UTIL_FindEntityByClassname( NULL, "env_rain" ); + if (!FNullEnt( pFind )) + { + // rain allowed on this map + CBaseEntity *pEnt = CBaseEntity::Instance( pFind->edict() ); + + float raindistance = pEnt->pev->armorvalue; + float rainheight = pEnt->pev->origin[2]; + int rainmode = pEnt->pev->button; + + // search for constant rain_modifies + pFind = UTIL_FindEntityByClassname( NULL, "util_rainmodify" ); + while ( !FNullEnt( pFind ) ) + { + if (pFind->pev->spawnflags & 1) + { + // copy settings to player's data and clear fading + CBaseEntity *pEnt = CBaseEntity::Instance( pFind->edict() ); + CUtilRainModify *pRainModify = (CUtilRainModify *)pEnt; + + Rain_dripsPerSecond = pRainModify->pev->button; + Rain_windX = pRainModify->windXY[1]; + Rain_windY = pRainModify->windXY[0]; + Rain_randX = pRainModify->randXY[1]; + Rain_randY = pRainModify->randXY[0]; + + Rain_endFade = 0; + break; + } + pFind = UTIL_FindEntityByClassname( pFind, "util_rainmodify" ); + } + + MESSAGE_BEGIN(MSG_ONE, gmsg.RainData, NULL, pev); + WRITE_SHORT(Rain_dripsPerSecond); + WRITE_COORD(raindistance); + WRITE_COORD(Rain_windX); + WRITE_COORD(Rain_windY); + WRITE_COORD(Rain_randX); + WRITE_COORD(Rain_randY); + WRITE_SHORT(rainmode); + WRITE_COORD(rainheight); + MESSAGE_END(); + + if (Rain_dripsPerSecond) + ALERT(at_aiconsole, "Sending enabling rain message\n"); + else + ALERT(at_aiconsole, "Sending disabling rain message\n"); + } + else + { + // no rain on this map + Rain_dripsPerSecond = 0; + Rain_windX = 0; + Rain_windY = 0; + Rain_randX = 0; + Rain_randY = 0; + Rain_ideal_dripsPerSecond = 0; + Rain_ideal_windX = 0; + Rain_ideal_windY = 0; + Rain_ideal_randX = 0; + Rain_ideal_randY = 0; + Rain_endFade = 0; + Rain_nextFadeUpdate = 0; + } + rainNeedsUpdate = 0; + } + + // Update Flashlight + if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else m_flFlashLightTime = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsg.FlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + } + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsg.Train > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsg.Train, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + int i; + + for (i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerWeapon::ItemInfoArray[i]; + + if( !II.iId ) continue; + + const char *pszName; + if( !II.iszName ) + pszName = "Empty"; + else pszName = STRING( II.iszName ); + + MESSAGE_BEGIN( MSG_ONE, gmsg.WeaponList, NULL, pev ); + WRITE_STRING( pszName ); // string weapon name + WRITE_BYTE( GetAmmoIndex( STRING( II.iszAmmo1 ))); // byte Ammo Type + WRITE_BYTE( II.iMaxAmmo1 ); // byte Max Ammo 1 + WRITE_BYTE( GetAmmoIndex(STRING( II.iszAmmo2 ))); // byte Ammo2 Type + WRITE_BYTE( II.iMaxAmmo2 ); // byte Max Ammo 2 + WRITE_BYTE( II.iSlot ); // byte bucket + WRITE_BYTE( II.iPosition ); // byte bucket pos + WRITE_BYTE( II.iId ); // byte id (bit index into pev->weapons) + WRITE_BYTE( II.iFlags ); // byte Flags + MESSAGE_END(); + } + } + + SendAmmoUpdate(); + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + + // update Status Bar + if( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } +} + +void CBasePlayer :: SendStartMessages( void ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + + if ( !pEdict ) return; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) continue;// Not in use + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) continue; + + pEntity->StartMessage( this ); + } +} + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if( !fControl ) + { + pev->flags |= FL_FROZEN; + pev->velocity = g_vecZero; //LRC - stop view bobbing + } + else pev->flags &= ~FL_FROZEN; +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + m_vecAutoAim = angles * 0.9; + + // Don't send across network if sv_aim is 0 + if( CVAR_GET_FLOAT( "sv_aim" ) != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = m_vecAutoAim.x; + m_lasty = m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->viewangles + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if( CVAR_GET_FLOAT( "sv_aim" ) == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->viewangles + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->viewangles - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ + if( !g_pGameRules->IsMultiplayer() || (CVAR_GET_FLOAT( "mp_weaponstay" ) > 0 )) + { + // no dropping in single player. + //return; + } + + if ( !strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerWeapon *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( !strcmp( pszItemName, STRING( pWeapon->pev->netname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon ) + { + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + + UTIL_MakeVectors ( pev->angles ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "item_weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerWeapon *pCheckItem ) +{ + CBasePlayerWeapon *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->netname) )) + return TRUE; + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerWeapon *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !strcmp( pszItemName, STRING( pItem->pev->netname ))) + return TRUE; + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerWeapon *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) return FALSE; + ResetAutoaim( ); + + if (m_pActiveItem) m_pActiveItem->Holster( ); + QueueItem(pWeapon); + if (m_pActiveItem) m_pActiveItem->Deploy( ); + + return TRUE; +} + +//========================================================= +// Body queue class here.... It's really just CBaseEntity +//========================================================= +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +//========================================================= +// Dead HEV suit prop +// LRC- i.e. the dead blokes you see in Xen. +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if( pev->sequence == -1 ) + { + ALERT ( at_console, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects = EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} \ No newline at end of file diff --git a/server/monsters/player.h b/server/monsters/player.h new file mode 100644 index 00000000..2b02c05e --- /dev/null +++ b/server/monsters/player.h @@ -0,0 +1,376 @@ +/*** +* +* 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 PLAYER_H +#define PLAYER_H + +#define PLAYER_FATAL_FALL_SPEED 1024 // approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float) 350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +#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' + +// +// Player PHYSICS FLAGS bits +// +#define PFLAG_ONLADDER ( 1<<0 ) +#define PFLAG_ONSWING ( 1<<0 ) +#define PFLAG_ONTRAIN ( 1<<1 ) +#define PFLAG_ONBARNACLE ( 1<<2 ) +#define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet +#define PFLAG_USING ( 1<<4 ) // Using a continuous entity +#define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. + +// +// generic player +// +//----------------------------------------------------- +//This is Half-Life player entity +//----------------------------------------------------- +#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time + +#define SUIT_GROUP TRUE +#define SUIT_SENTENCE FALSE + +#define SUIT_REPEAT_OK 0 +#define SUIT_NEXT_IN_30SEC 30 +#define SUIT_NEXT_IN_1MIN 60 +#define SUIT_NEXT_IN_5MIN 300 +#define SUIT_NEXT_IN_10MIN 600 +#define SUIT_NEXT_IN_30MIN 1800 +#define SUIT_NEXT_IN_1HOUR 3600 + +#define CSUITNOREPEAT 32 + +#define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" +#define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + + +//NB: changing this structure will cause problems! --LRC + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 + +enum sbar_data +{ + SBAR_ID_TARGETNAME = 1, + SBAR_ID_TARGETHEALTH, + SBAR_ID_TARGETARMOR, + SBAR_END, +}; + +#define CHAT_INTERVAL 1.0f + +class CBasePlayer : public CBaseMonster +{ +public: + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden + float m_fNextSuicideTime; // the time after which the player can next use the suicide command + + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + float m_flDuckTime; // how long we've been ducking + float m_flWallJumpTime; // how long until next walljump + + float m_flSuitUpdate; // when to play next suit update + int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update + int m_iSuitPlayNext; // next sentence slot for queue storage; + int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list + float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat + int m_lastDamageAmount; // Last damage taken + float m_tbdPrev; // Time-based damage timer + + float m_flgeigerRange; // range to nearest radiation source + float m_flgeigerDelay; // delay per update of range msg to client + int m_igeigerRangePrev; + int m_iStepLeft; // alternate left/right foot stepping sound + char m_szTextureName[CBTEXTURENAMEMAX]; // current texture name we're standing on + char m_chTextureType; // current texture type + + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + EHANDLE m_pMonitor; + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + float m_fAirFinished; // moved here from progdefs.h + float m_fPainFinished; // moved here from progdefs.h + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + float m_tSneaking; + int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages + int m_iClientHealth; // the health currently known by the client. If this changes, send a new + int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iHideHUD; // the players hud weapon info is to be hidden + int m_iClientHideHUD; + + // usable player items + CBasePlayerWeapon *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerWeapon *m_pActiveItem; + CBasePlayerWeapon *m_pClientActiveItem; // client version of the active item + CBasePlayerWeapon *m_pLastItem; + CBasePlayerWeapon *m_pNextItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( void ); + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeArmor( float flArmor, int suit = 0 ); + virtual int TakeItem( int iItem ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } + virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned + + virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void PackDeadPlayerItems( void ); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerWeapon *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerWeapon *pItem ); + BOOL RemovePlayerItem( CBasePlayerWeapon *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerWeapon *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void QueueItem(CBasePlayerWeapon *pItem); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, char *szName, int iMax ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void CheckWaterJump( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + void UpdateStepSound( void ); + void PlayStepSound(int step, float fvol); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + const char *GetAmmoName(int index ); + int AmmoInventory( int iAmmoIndex ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); + + void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + void DeathMessage( entvars_t *pevKiller ); + void SendStartMessages( void ); + + void SetCustomDecalFrames( int nFrames ); + int GetCustomDecalFrames( void ); + + //Player ID + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + // for trigger_viewset + CBaseEntity *pViewEnt; // entity to look at + int viewFlags; // 1-active, 2-draw hud, 4 - inverse x, 8 - monster look + int viewNeedsUpdate; // precache sets to 1, UpdateClientData() sets to 0 + + // for trigger_sound + byte m_iSndRoomtype; // last roomtype set by sound entity + int hearNeedsUpdate; // update DSP contents + + //warhud message + int m_iWarHUD; + int m_iClientWarHUD; + + //fog variables + int m_iFogStartDist; + int m_iFogEndDist; + int m_iFogFinalEndDist; + int m_FogFadeTime; + Vector m_FogColor; + int fogNeedsUpdate; + + //fade fariables + Vector m_FadeColor; + int m_FadeAlpha; + int m_iFadeFlags; + int m_iFadeTime; + int m_iFadeHold; + int fadeNeedsUpdate; + int m_iStartMessage; + + float m_flStartTime; //start time + + float m_flNextChatTime; + int Rain_dripsPerSecond; + float Rain_windX, Rain_windY; + float Rain_randX, Rain_randY; + + int Rain_ideal_dripsPerSecond; + float Rain_ideal_windX, Rain_ideal_windY; + float Rain_ideal_randX, Rain_ideal_randY; + + float Rain_endFade; // 0 means off + float Rain_nextFadeUpdate; + + int rainNeedsUpdate; + Vector v_LastAngles; +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + +extern BOOL gInitHUD; + +#endif // PLAYER_H diff --git a/server/monsters/rat.cpp b/server/monsters/rat.cpp new file mode 100644 index 00000000..9eef8202 --- /dev/null +++ b/server/monsters/rat.cpp @@ -0,0 +1,104 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// rat - environmental monster +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CRat : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_rat, CRat ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRat :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_INSECT; //LRC- maybe someone needs to give them a basic biology lesson... +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRat :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 45; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRat :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/bigrat.mdl"); + UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 6 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRat :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/bigrat.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/server/monsters/roach.cpp b/server/monsters/roach.cpp new file mode 100644 index 00000000..97fca488 --- /dev/null +++ b/server/monsters/roach.cpp @@ -0,0 +1,466 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// cockroach +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "decals.h" + +#define ROACH_IDLE 0 +#define ROACH_BORED 1 +#define ROACH_SCARED_BY_ENT 2 +#define ROACH_SCARED_BY_LIGHT 3 +#define ROACH_SMELL_FOOD 4 +#define ROACH_EAT 5 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +class CRoach : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + void EXPORT MonsterThink ( void ); + void Move ( float flInterval ); + void PickNewDest ( int iCondition ); + void EXPORT Touch ( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + + float m_flLastLightLevel; + float m_flNextSmellTime; + int Classify ( void ); + void Look ( int iDistance ); + int ISoundMask ( void ); + + // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may + BOOL m_fLightHacked; + int m_iMode; + // ----------------------------- +}; +LINK_ENTITY_TO_CLASS( monster_cockroach, CRoach ); + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CRoach :: ISoundMask ( void ) +{ + return bits_SOUND_CARCASS | bits_SOUND_MEAT; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRoach :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_INSECT; +} + +//========================================================= +// Touch +//========================================================= +void CRoach :: Touch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + if ( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) + { + return; + } + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + // This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood()) + UTIL_DecalTrace( &tr, DECAL_YBLOOD1 +RANDOM_LONG(0,5) ); + + TakeDamage( pOther->pev, pOther->pev, pev->health, DMG_CRUSH ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRoach :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRoach :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/roach.mdl"); + UTIL_SetSize( pev, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = 1; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + SetActivity ( ACT_IDLE ); + + pev->view_ofs = Vector ( 0, 0, 1 );// position of the eyes relative to monster's origin. + pev->takedamage = DAMAGE_YES; + m_fLightHacked = FALSE; + m_flLastLightLevel = -1; + m_iMode = ROACH_IDLE; + m_flNextSmellTime = gpGlobals->time; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRoach :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/roach.mdl"); + + PRECACHE_SOUND("roach/rch_die.wav"); + PRECACHE_SOUND("roach/rch_walk.wav"); + PRECACHE_SOUND("roach/rch_smash.wav"); +} + + +//========================================================= +// Killed. +//========================================================= +void CRoach :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->solid = SOLID_NOT; + + //random sound + if ( RANDOM_LONG(0,4) == 1 ) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "roach/rch_die.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_smash.wav", 0.7, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + UTIL_Remove( this ); +} + +//========================================================= +// MonsterThink, overridden for roaches. +//========================================================= +void CRoach :: MonsterThink( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) && !HaveCamerasInPVS( edict() )) + SetNextThink( RANDOM_FLOAT(1,1.5) ); + else + SetNextThink( 0.1 );// keep monster thinking + + float flInterval = StudioFrameAdvance( ); // animate + + if ( !m_fLightHacked ) + { + // if light value hasn't been collection for the first time yet, + // suspend the creature for a second so the world finishes spawning, then we'll collect the light level. + SetNextThink( 1 ); + m_fLightHacked = TRUE; + return; + } + else if ( m_flLastLightLevel < 0 ) + { + // collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated. + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + } + + switch ( m_iMode ) + { + case ROACH_IDLE: + case ROACH_EAT: + { + // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. + if ( RANDOM_LONG(0,3) == 1 ) + { + Look( 150 ); + if (HasConditions(bits_COND_SEE_FEAR)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// roach will ignore food for 30 to 45 seconds + PickNewDest( ROACH_SCARED_BY_ENT ); + SetActivity ( ACT_WALK ); + } + else if ( RANDOM_LONG(0,149) == 1 ) + { + // if roach doesn't see anything, there's still a chance that it will move. (boredom) + //ALERT ( at_aiconsole, "Bored\n" ); + PickNewDest( ROACH_BORED ); + SetActivity ( ACT_WALK ); + + if ( m_iMode == ROACH_EAT ) + { + // roach will ignore food for 30 to 45 seconds if it got bored while eating. + Eat( 30 + ( RANDOM_LONG(0,14) ) ); + } + } + } + + // don't do this stuff if eating! + if ( m_iMode == ROACH_IDLE ) + { + if ( FShouldEat() ) + { + Listen(); + } + + if ( GETENTITYILLUM( ENT(pev) ) > m_flLastLightLevel ) + { + // someone turned on lights! + //ALERT ( at_console, "Lights!\n" ); + PickNewDest( ROACH_SCARED_BY_LIGHT ); + SetActivity ( ACT_WALK ); + } + else if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + // roach smells food and is just standing around. Go to food unless food isn't on same z-plane. + if ( pSound && abs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3 ) + { + PickNewDest( ROACH_SMELL_FOOD ); + SetActivity ( ACT_WALK ); + } + } + } + + break; + } + case ROACH_SCARED_BY_LIGHT: + { + // if roach was scared by light, then stop if we're over a spot at least as dark as where we started! + if ( GETENTITYILLUM( ENT( pev ) ) <= m_flLastLightLevel ) + { + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// make this our new light level. + } + break; + } + } + + if ( m_flGroundSpeed != 0 ) + { + Move( flInterval ); + } +} + +//========================================================= +// Picks a new spot for roach to run to.( +//========================================================= +void CRoach :: PickNewDest ( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + // find the food and go there. + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + if ( pSound ) + { + m_Route[ 0 ].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.z = pSound->m_vecOrigin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + return; + } + } + + do + { + // picks a random spot, requiring that it be at least 128 units away + // else, the roach will pick a spot too close to itself and run in + // circles. this is a hack but buys me time to work on the real monsters. + vecNewDir.x = RANDOM_FLOAT( -1, 1 ); + vecNewDir.y = RANDOM_FLOAT( -1, 1 ); + flDist = 256 + ( RANDOM_LONG(0,255) ); + vecDest = pev->origin + vecNewDir * flDist; + + } while ( ( vecDest - pev->origin ).Length2D() < 128 ); + + m_Route[ 0 ].vecLocation.x = vecDest.x; + m_Route[ 0 ].vecLocation.y = vecDest.y; + m_Route[ 0 ].vecLocation.z = pev->origin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + + if ( RANDOM_LONG(0,9) == 1 ) + { + // every once in a while, a roach will play a skitter sound when they decide to run + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } +} + +//========================================================= +// roach's move function +//========================================================= +void CRoach :: Move ( float flInterval ) +{ + float flWaypointDist; + Vector vecApex; + + // local move to waypoint. + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + + ChangeYaw ( pev->yaw_speed ); + UTIL_MakeVectors( pev->angles ); + + if ( RANDOM_LONG(0,7) == 1 ) + { + // randomly check for blocked path.(more random load balancing) + if ( !WALK_MOVE( ENT(pev), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) ) + { + // stuck, so just pick a new spot to run off to + PickNewDest( m_iMode ); + } + } + + WALK_MOVE( ENT(pev), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + + // if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot) + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// this is roach's new comfortable light level + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + m_iMode = ROACH_EAT; + } + else + { + m_iMode = ROACH_IDLE; + } + } + + if ( RANDOM_LONG(0,149) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD ) + { + // random skitter while moving as long as not on a b-line to get out of light or going to food + PickNewDest( FALSE ); + } +} + +//========================================================= +// Look - overriden for the roach, which can virtually see +// 360 degrees. +//========================================================= +void CRoach :: Look ( int iDistance ) +{ + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + CBaseEntity *pPreviousEnt;// the last entity added to the link list + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions( bits_COND_SEE_HATE |bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); + + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) && !HaveCamerasInPVS( edict() )) + { + return; + } + + m_pLink = NULL; + pPreviousEnt = this; + + // Does sphere also limit itself to PVS? + // Examine all entities within a reasonable radius + // !!!PERFORMANCE - let's trivially reject the ent list before radius searching! + while ((pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance )) != NULL) + { + // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients + if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->pev->flags, FL_MONSTER ) ) + { + if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) + { + // NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite + // this value. If this ent happens to be the last, the list will be properly terminated. + pPreviousEnt->m_pLink = pSightEnt; + pSightEnt->m_pLink = NULL; + pPreviousEnt = pSightEnt; + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_NO: + break; + default: + ALERT ( at_console, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + SetConditions( iSighted ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/server/monsters/schedule.h b/server/monsters/schedule.h new file mode 100644 index 00000000..59e4079d --- /dev/null +++ b/server/monsters/schedule.h @@ -0,0 +1,292 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Scheduling +//========================================================= + +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + SCHED_AISCRIPT, + SCHED_FAIL, + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_SCRIPT, + TASK_RUN_TO_SCRIPT, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_SCRIPT, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_END_SCRIPT, //LRC + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! +#define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate +#define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of +#define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike +#define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) +#define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) + +#endif // SCHEDULE_H diff --git a/server/monsters/scientist.cpp b/server/monsters/scientist.cpp new file mode 100644 index 00000000..15647566 --- /dev/null +++ b/server/monsters/scientist.cpp @@ -0,0 +1,1458 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// human scientist (passive lab worker) +//========================================================= + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "animation.h" +#include "soundent.h" +#include "defaults.h" + + +#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model +enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 }; + +enum +{ + SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_FEAR, + SCHED_PANIC, + SCHED_STARTLE, + SCHED_TARGET_CHASE_SCARED, + SCHED_TARGET_FACE_SCARED, +}; + +enum +{ + TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1, + TASK_HEAL, + TASK_SAY_FEAR, + TASK_RUN_PATH_SCARED, + TASK_SCREAM, + TASK_RANDOM_SCREAM, + TASK_MOVE_TO_TARGET_RANGE_SCARED, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SCIENTIST_AE_HEAL ( 1 ) +#define SCIENTIST_AE_NEEDLEON ( 2 ) +#define SCIENTIST_AE_NEEDLEOFF ( 3 ) + +//======================================================= +// Scientist +//======================================================= + +class CScientist : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual int FriendNumber( int arrayNumber ); + void SetActivity ( Activity newActivity ); + Activity GetStoppedActivity( void ); + int ISoundMask( void ); + void DeclineFollowing( void ); + + float CoverRadius( void ) { return 1200; } // Need more room for cover because scientists want to get far away! + BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; } + + BOOL CanHeal( void ); + void Heal( void ); + void Scream( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + float m_painTime; + float m_healTime; + float m_fearTime; +}; + +LINK_ENTITY_TO_CLASS( monster_scientist, CScientist ); + +TYPEDESCRIPTION CScientist::m_SaveData[] = +{ + DEFINE_FIELD( CScientist, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_healTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CScientist, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slFollow[] = +{ + { + tlFollow, + ARRAYSIZE ( tlFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlFollowScared[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally + { TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED }, +}; + +Schedule_t slFollowScared[] = +{ + { + tlFollowScared, + ARRAYSIZE ( tlFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slFaceTargetScared[] = +{ + { + tlFaceTargetScared, + ARRAYSIZE ( tlFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slStopFollowing[] = +{ + { + tlStopFollowing, + ARRAYSIZE ( tlStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlHeal[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // Move within 60 of target ent (client) + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE }, // If you fail, catch up with that guy! (change this to put syringe away and then chase) + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SAY_HEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle + { TASK_HEAL, (float)0 }, // Put it in the player + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slHeal[] = +{ + { + tlHeal, + ARRAYSIZE ( tlHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "Heal" + }, +}; + + +Task_t tlFaceTarget[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slFaceTarget[] = +{ + { + tlFaceTarget, + ARRAYSIZE ( tlFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlSciPanic[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SCREAM, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSciPanic[] = +{ + { + tlSciPanic, + ARRAYSIZE ( tlSciPanic ), + 0, + 0, + "SciPanic" + }, +}; + + +Task_t tlIdleSciStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleSciStand[] = +{ + { + tlIdleSciStand, + ARRAYSIZE ( tlIdleSciStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleSciStand" + + }, +}; + + +Task_t tlScientistCover[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH_SCARED, (float)0 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_SET_SCHEDULE, (float)SCHED_HIDE }, +}; + +Schedule_t slScientistCover[] = +{ + { + tlScientistCover, + ARRAYSIZE ( tlScientistCover ), + bits_COND_NEW_ENEMY, + 0, + "ScientistCover" + }, +}; + + + +Task_t tlScientistHide[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame + { TASK_WAIT_RANDOM, (float)10.0 }, +}; + +Schedule_t slScientistHide[] = +{ + { + tlScientistHide, + ARRAYSIZE ( tlScientistHide ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + bits_SOUND_DANGER, + "ScientistHide" + }, +}; + + +Task_t tlScientistStartle[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE }, + { TASK_WAIT_RANDOM, (float)1.0 }, +}; + +Schedule_t slScientistStartle[] = +{ + { + tlScientistStartle, + ARRAYSIZE ( tlScientistStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "ScientistStartle" + }, +}; + + + +Task_t tlFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slFear[] = +{ + { + tlFear, + ARRAYSIZE ( tlFear ), + bits_COND_NEW_ENEMY, + 0, + "Fear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CScientist ) +{ + slFollow, + slFaceTarget, + slIdleSciStand, + slFear, + slScientistCover, + slScientistHide, + slScientistStartle, + slHeal, + slStopFollowing, + slSciPanic, + slFollowScared, + slFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CScientist, CTalkMonster ); + + +void CScientist::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + +void CScientist :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CScientist::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CScientist :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_SAY_HEAL: +// if ( FOkToSpeak() ) + Talk( 2 ); + m_hTalkTarget = m_hTargetEnt; + PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); + + TaskComplete(); + break; + + case TASK_SCREAM: + Scream(); + TaskComplete(); + break; + + case TASK_RANDOM_SCREAM: + if ( RANDOM_FLOAT( 0, 1 ) < pTask->flData ) + Scream(); + TaskComplete(); + break; + + case TASK_SAY_FEAR: + if ( FOkToSpeak() ) + { + Talk( 2 ); + m_hTalkTarget = m_hEnemy; + if ( m_hEnemy->IsPlayer() ) + PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + else + PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + } + TaskComplete(); + break; + + case TASK_HEAL: + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + + case TASK_RUN_PATH_SCARED: + m_movementActivity = ACT_RUN_SCARED; + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) + TaskFail(); + } + } + break; + + default: + CTalkMonster::StartTask( pTask ); + break; + } +} + +void CScientist :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RUN_PATH_SCARED: + if ( MovementIsComplete() ) + TaskComplete(); + if ( RANDOM_LONG(0,31) < 8 ) + Scream(); + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( RANDOM_LONG(0,63)< 8 ) + Scream(); + + if ( m_hEnemy == NULL ) + { + TaskFail(); + } + else + { + float distance; + + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK_SCARED ) + m_movementActivity = ACT_WALK_SCARED; + else if ( distance >= 270 && m_movementActivity != ACT_RUN_SCARED ) + m_movementActivity = ACT_RUN_SCARED; + } + } + break; + + case TASK_HEAL: + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + else + { + if ( TargetDistance() > 90 ) + TaskComplete(); + pev->ideal_yaw = UTIL_VecToYaw( m_hTargetEnt->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CScientist :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CScientist :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 120; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 120; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CScientist :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCIENTIST_AE_HEAL: // Heal my target (if within range) + Heal(); + break; + case SCIENTIST_AE_NEEDLEON: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1; + } + break; + case SCIENTIST_AE_NEEDLEOFF: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0; + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CScientist :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/scientist.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = SCIENTIST_HEALTH; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + +// m_flDistTooFar = 256.0; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + + // White hands + pev->skin = 0; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + MonsterInit(); + SetUse(&CScientist :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CScientist :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/scientist.mdl"); + PRECACHE_SOUND("scientist/sci_pain1.wav"); + PRECACHE_SOUND("scientist/sci_pain2.wav"); + PRECACHE_SOUND("scientist/sci_pain3.wav"); + PRECACHE_SOUND("scientist/sci_pain4.wav"); + PRECACHE_SOUND("scientist/sci_pain5.wav"); + + // every new scientist must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CScientist :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientist will try to talk to friends in this order: + + m_szFriends[0] = "monster_scientist"; + m_szFriends[1] = "monster_sitting_scientist"; + m_szFriends[2] = "monster_barney"; + + // scientists speach group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_USE] = "SC_PFOLLOW"; + else + m_szGrp[TLK_USE] = "SC_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "SC_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "SC_POK"; + else + m_szGrp[TLK_DECLINE] = "SC_NOTOK"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "SC_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SC_PHELLO"; + m_szGrp[TLK_PIDLE] = "SC_PIDLE"; + m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } + + // get voice for head + switch (pev->body % 3) + { + default: + case HEAD_GLASSES: m_voicePitch = 105; break; //glasses + case HEAD_EINSTEIN: m_voicePitch = 100; break; //einstein + case HEAD_LUTHER: m_voicePitch = 95; break; //luther + case HEAD_SLICK: m_voicePitch = 100; break;//slick + } +} + +int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + + if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) + { + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + + // make sure friends talk about it if player hurts scientist... + return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CScientist :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CScientist :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime ) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,4)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CScientist :: DeathSound ( void ) +{ + PainSound(); +} + + +void CScientist::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CScientist :: SetActivity ( Activity newActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( newActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + newActivity = ACT_IDLE; + CTalkMonster::SetActivity( newActivity ); +} + + +Schedule_t* CScientist :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that scientist will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slFollow; + + case SCHED_CANT_FOLLOW: + return slStopFollowing; + + case SCHED_PANIC: + return slSciPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleSciStand; + else + return psched; + + case SCHED_HIDE: + return slScientistHide; + + case SCHED_STARTLE: + return slScientistStartle; + + case SCHED_FEAR: + return slFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CScientist :: GetSchedule ( void ) +{ + // so we don't keep calling through the EHANDLE stuff + CBaseEntity *pEnemy = m_hEnemy; + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( pEnemy ) + { + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + m_fearTime = gpGlobals->time; + else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + m_hEnemy = NULL; + pEnemy = NULL; + } + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // Cower when you hear something scary + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound ) + { + if ( pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT) ) + { + if ( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so + { + m_fearTime = gpGlobals->time; // Update last fear + return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second + } + } + } + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + int relationship = R_NO; + + // Nothing scary, just me and the player + if ( pEnemy != NULL ) + relationship = IRelationship( pEnemy ); + + // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear + if ( relationship != R_DL && relationship != R_HT ) + { + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( CanHeal() ) // Heal opportunistically + return slHeal; + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react + return GetScheduleOfType( SCHED_FEAR ); // React to something scary + return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + case MONSTERSTATE_COMBAT: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + return slFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slScientistCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slScientistCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CScientist :: GetIdealState ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if ( IsFollowing() ) + { + int relationship = IRelationship( m_hEnemy ); + if ( relationship != R_FR || relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Don't go to combat if you're following the player + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + StopFollowing( TRUE ); + } + } + else if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Stop following if you take damage + if ( IsFollowing() ) + StopFollowing( TRUE ); + } + break; + + case MONSTERSTATE_COMBAT: + { + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + // Strip enemy when going to alert + m_IdealMonsterState = MONSTERSTATE_ALERT; + m_hEnemy = NULL; + return m_IdealMonsterState; + } + // Follow if only scared a little + if ( m_hTargetEnt != NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + m_fearTime = gpGlobals->time; + m_IdealMonsterState = MONSTERSTATE_COMBAT; + return m_IdealMonsterState; + } + + } + } + break; + } + + return CTalkMonster::GetIdealState(); +} + + +BOOL CScientist::CanHeal( void ) +{ + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CScientist::Heal( void ) +{ + if ( !CanHeal() ) + return; + + Vector target = m_hTargetEnt->pev->origin - pev->origin; + if ( target.Length() > 100 ) + return; + + m_hTargetEnt->TakeHealth( SCIENTIST_HEAL, DMG_GENERIC ); + // Don't heal again for 1 minute + m_healTime = gpGlobals->time + 60; +} + +int CScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Scientist PROP +//========================================================= +class CDeadScientist : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_PASSIVE; } + + void KeyValue( KeyValueData *pkvd ); + int m_iPose;// which sequence to display + static char *m_szPoses[7]; +}; +char *CDeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadScientist::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} +LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist ); + +// +// ********** DeadScientist SPAWN ********** +// +void CDeadScientist :: Spawn( void ) +{ + PRECACHE_MODEL( "models/scientist.mdl" ); + SET_MODEL( ENT( pev ), "models/scientist.mdl" ); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.scientistHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + else + pev->skin = 0; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead scientist with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Scientist PROP +//========================================================= + +class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SittingThink( void ); + int Classify ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + int FriendNumber( int arrayNumber ); + + int FIdleSpeak ( void ); + int m_baseSequence; + int m_headTurn; + float m_flResponseDelay; +}; + +LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CSittingScientist ); +TYPEDESCRIPTION CSittingScientist::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingScientist, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingScientist, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingScientist, CScientist ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +#define SF_SITTINGSCI_POSTDISASTER 1024 + +// +// ********** Scientist SPAWN ********** +// +void CSittingScientist :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/scientist.mdl"); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/scientist.mdl"); + Precache(); + InitBoneControllers(); + + UTIL_SetSize(pev, Vector(-14, -14, 0), Vector(14, 14, 36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + + m_bloodColor = BLOOD_COLOR_RED; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD; + + if (!FBitSet(pev->spawnflags, SF_SITTINGSCI_POSTDISASTER)) //LRC- allow a sitter to be postdisaster. + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink(&CSittingScientist ::SittingThink); + SetNextThink( 0.1 ); + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingScientist :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingScientist :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +int CSittingScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingScientist :: SittingThink( void ) +{ + CBaseEntity *pent; + + StudioFrameAdvance( ); + + // try to greet player + if (FIdleHello()) + { + pent = FindNearestFriend(TRUE); + if (pent) + { + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, 0 ); + } + } + else if (m_fSequenceFinished) + { + int i = RANDOM_LONG(0,99); + m_headTurn = 0; + + if (m_flResponseDelay && gpGlobals->time > m_flResponseDelay) + { + // respond to question + IdleRespond(); + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + m_flResponseDelay = 0; + } + else if (i < 30) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + + // turn towards player or nearest friend and speak + + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + pent = FindNearestFriend(TRUE); + else + pent = FindNearestFriend(FALSE); + + if (!FIdleSpeak() || !pent) + { + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + } + else + { + // only turn head if we spoke + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + //ALERT(at_console, "sitting speak\n"); + } + } + else if (i < 60) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + if (RANDOM_LONG(0,99) < 5) + { + //ALERT(at_console, "sitting speak2\n"); + FIdleSpeak(); + } + } + else if (i < 80) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting2; + } + else if (i < 100) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + } + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, m_headTurn ); + } + SetNextThink( 0.1 ); +} + +// prepare sitting scientist to answer a question +void CSittingScientist :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CSittingScientist :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + + if (!FOkToSpeak()) + return FALSE; + + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + + pitch = GetVoicePitch(); + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + + // try to talk to any standing or sitting scientists nearby + CBaseEntity *pentFriend = FindNearestFriend(FALSE); + + if (pentFriend && RANDOM_LONG(0,1)) + { + CTalkMonster *pTalkMonster = GetClassPtr((CTalkMonster *)pentFriend->pev); + pTalkMonster->SetAnswerQuestion( this ); + + IdleHeadTurn(pentFriend->pev->origin); + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // otherwise, play an idle statement + if (RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // never spoke + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} diff --git a/server/monsters/scripted.cpp b/server/monsters/scripted.cpp new file mode 100644 index 00000000..a2198551 --- /dev/null +++ b/server/monsters/scripted.cpp @@ -0,0 +1,1246 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "player.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" +#include "defaultai.h" + + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszAttack")) + { + m_iszAttack = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszMoveTarget")) + { + m_iszMoveTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszFireOnBegin")) + { + m_iszFireOnBegin = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fTurnType")) + { + m_fTurnType = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fAction")) + { + m_fAction = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } +// LRC else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) +// { +// m_flRepeat = atof( pkvd->szValue ); +// pkvd->fHandled = TRUE; +// } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iRepeats")) + { + m_iRepeats = atoi( pkvd->szValue ); + m_iRepeatsLeft = m_iRepeats; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fRepeatFrame")) + { + m_fRepeatFrame = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPriority")) + { + m_iPriority = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iState, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszAttack, FIELD_STRING ), //LRC + DEFINE_FIELD( CCineMonster, m_iszMoveTarget, FIELD_STRING ), //LRC + DEFINE_FIELD( CCineMonster, m_iszFireOnBegin, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fTurnType, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fAction, FIELD_INTEGER ), +//LRC- this is unused DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), + + //LRC + DEFINE_FIELD( CCineMonster, m_iRepeats, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iRepeatsLeft, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fRepeatFrame, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_iPriority, FIELD_INTEGER ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +LINK_ENTITY_TO_CLASS( scripted_action, CCineMonster ); //LRC + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + m_iState = STATE_OFF; //LRC + + if ( FStringNull(m_iszIdle) && FStringNull(pev->targetname) ) // if no targetname, start now + { + SetThink(&CCineMonster :: CineThink ); + SetNextThink( 1.0 ); + } + else if ( m_iszIdle ) + { + SetThink(&CCineMonster :: InitIdleThink ); + SetNextThink( 1.0 ); + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; + + //LRC - the only difference between AI and normal sequences + if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + { + m_iPriority |= SS_INTERRUPT_ANYSTATE; + } +} + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { +// ALERT(at_console, "Sequence \"%s\" triggered, already has a target.\n", STRING(pev->targetname)); + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; //why the delay? -- LRC + } + else + { +// ALERT(at_console, "Sequence \"%s\" triggered, can't find target; searching\n", STRING(pev->targetname)); + m_hActivator = pActivator; + // if not, try finding them + SetThink(&CCineMonster :: CineThink ); +// SetNextThink( 0 ); + CineThink(); //LRC + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +} + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink( Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +//LRC: now redefined... find a viable entity with the given name, and return it (or NULL if not found). +CBaseMonster* CCineMonster :: FindEntity( const char* sName, CBaseEntity *pActivator ) +{ + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname(NULL, sName, pActivator); + //m_hTargetEnt = NULL; + CBaseMonster *pMonster = NULL; + + while (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( pMonster && pMonster->CanPlaySequence( m_iPriority | SS_INTERRUPT_ALERT ) ) + { + return pMonster; + } + ALERT( at_console, "Found %s, but can't play!\n", sName ); + } + pEntity = UTIL_FindEntityByTargetname( pEntity, sName, pActivator ); + pMonster = NULL; + } + + // couldn't find something with the given targetname; assume it's a classname instead. + if ( !pMonster ) + { + pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, sName)) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( pMonster && pMonster->CanPlaySequence( m_iPriority ) ) + { + return pMonster; + } + } + } + } + } + return NULL; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + +// ALERT( at_console, "Possess: pEntity %s, pTarget %s\n", STRING(pEntity->pev->targetname), STRING(pTarget->pev->targetname)); + + if ( pTarget ) + { + if (pTarget->m_pCine) + { + pTarget->m_pCine->CancelScript(); + } + + pTarget->m_pCine = this; + if (m_iszAttack) + { + // anything with that name? + pTarget->m_hTargetEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszAttack), m_hActivator); + if ( pTarget->m_hTargetEnt == NULL ) + { // nothing. Anything with that classname? + while ((pTarget->m_hTargetEnt = UTIL_FindEntityInSphere( pTarget->m_hTargetEnt, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pTarget->m_hTargetEnt->pev, STRING(m_iszAttack))) break; + } + } + + if( pTarget->m_hTargetEnt == NULL ) + { + // nothing. Oh well. + ALERT( at_console, "%s %s has a missing \"turn target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszAttack)); + pTarget->m_hTargetEnt = this; + } + } + else + { + pTarget->m_hTargetEnt = this; + } + + if( m_iszMoveTarget ) + { + // anything with that name? + pTarget->m_pGoalEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszMoveTarget), m_hActivator); + if( pTarget->m_pGoalEnt == NULL ) + { + // nothing. Oh well. + ALERT( at_console, "%s %s has a missing \"move target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszMoveTarget)); + pTarget->m_pGoalEnt = this; + } + } + else + { + pTarget->m_pGoalEnt = this; + } +// if (IsAction()) +// pTarget->PushEnemy(this,pev->origin); + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + +// ALERT(at_console, "script. IsAction = %d",IsAction()); + + m_iState = STATE_ON; // LRC: assume we'll set it to 'on', unless proven otherwise... + switch (m_fMoveTo) + { + case 1: + case 2: + DelayStart( 1 ); + m_iState = STATE_TURN_ON; + // fall through... + case 0: + case 4: + //G-Cont. this is a not a better way :( + //in my new project this bug will be removed + //in Spirit... as is. Sorry about that. + //If we interesting - decomment UTIL_AssignOrigin + //and run c1a4i with tentacle script - no comments + //UTIL_AssignOrigin( pTarget, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + //m_startTime = gpGlobals->time + 1E6; + break; + case 5: + case 6: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; +// if (m_iszIdle) +// { +// ALERT(at_console, "Possess: Play idle sequence\n"); +// StartSequence( pTarget, m_iszIdle, FALSE ); +// if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) +// { +// pTarget->pev->framerate = 0; +// } +// } +// ALERT(at_console, "Finished PossessEntity, ms %d, ims %d\n", pTarget->m_MonsterState, pTarget->m_IdealMonsterState); + } + +} + + +// at the beginning of the level, set up the idle animation. --LRC +void CCineMonster :: InitIdleThink( void ) +{ + if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity), NULL)) != NULL) + { + PossessEntity( ); + m_startTime = gpGlobals->time + 1E6; + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + SetNextThink( 1.0 ); + } +} + +void CCineMonster :: CineThink( void ) +{ +// ALERT(at_console, "Sequence think, activator %s\n", STRING(m_hActivator->pev->targetname)); + if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity),m_hActivator)) != NULL) + { +// ALERT(at_console, "Sequence found %s \"%s\"\n", STRING(m_hTargetEnt->pev->classname), STRING(m_hTargetEnt->pev->targetname)); + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { +// ALERT(at_console, "Sequence found nothing called %s\n", STRING(m_iszEntity)); + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + SetNextThink( 1.0 ); + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ +// ALERT( at_console, "StartSequence %s \"%s\"\n", STRING(pev->classname), STRING(pev->targetname)); + + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + m_iRepeatsLeft = m_iRepeats; //LRC - reset the repeater count + m_iState = STATE_OFF; // we've finished. +// ALERT( at_console, "Sequence %s finished\n", STRING(pev->targetname));//STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink( Remove ); + SetNextThink( 0.1 ); + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + UTIL_FireTargets( pev->target, NULL, this, USE_TOGGLE ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +// +// or select a specific AMBUSH schedule, regardless of state. //LRC +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; +// pMonster->ClearSchedule(); + + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) ); + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + + // Big fat BUG: This is an IgnoreConditions function - we need to return the conditions + // that _shouldn't_ be able to break the script, instead of the conditions that _should_!! + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, "scripted_sequence" ) || FClassnameIs( pentCine, "scripted_action" )) + { + GetClassPtr((CCineMonster *)VARS(pentCine))->m_iState = STATE_OFF; + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + //LRC - clean up so that if another script is starting immediately, the monster will notice it. + pTarget->ClearSchedule( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ + ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + CBaseEntity *pCineTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname)); + + while (pCineTarget) + { + ScriptEntityCancel( ENT(pCineTarget->pev) ); + pCineTarget = UTIL_FindEntityByTargetname(pCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them whether to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + CBaseEntity *pCine = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname)); + + while ( pCine ) + { + if (FClassnameIs( pCine->pev, "scripted_sequence" ) || FClassnameIs( pCine->pev, "scripted_action" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)(pCine->pev)); + if (state) + { +// ALERT(at_console, "Delaying start\n"); + pTarget->m_iDelay++; + } + else + { +// ALERT(at_console, "Undelaying start\n"); + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + { + pTarget->m_iState = STATE_ON; //LRC + UTIL_FireTargets( m_iszFireOnBegin, this, this, USE_TOGGLE ); //LRC + pTarget->m_startTime = gpGlobals->time + 0.05; // why the delay? -- LRC + } + } + } + pCine = UTIL_FindEntityByTargetname(pCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + CBaseEntity *pEntity; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pEntity = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + } + pEntity = UTIL_FindEntityByTargetname(pEntity, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pEntity = UTIL_FindEntityByClassname(NULL, STRING(m_iszEntity)); + while (!pTarget && pEntity) + { + pTarget = pEntity->MyMonsterPointer( ); + pEntity = UTIL_FindEntityByClassname(pEntity, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } + + CBaseMonster::Activate(); +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + pev->movetype = m_pCine->m_saved_movetype; + pev->solid = m_pCine->m_saved_solid; + pev->effects = m_pCine->m_saved_effects; + + if (m_pCine->pev->spawnflags & SF_SCRIPT_STAYDEAD) + pev->deadflag = DEAD_DYING; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + SetState( MONSTERSTATE_DEAD ); + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( this, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + //LRC- removed, was never implemented. ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseAnimating +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + void EXPORT DurationThink( void ); + int ObjectCaps( void ) { return (CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + STATE GetState() { return m_playing?STATE_ON:STATE_OFF; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( CBaseEntity *pActivator ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // maximum repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; // is the sentence enabled? (for m_flRepeat) + BOOL m_playing; //LRC- is the sentence playing? (for GetState) + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_playing, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseAnimating ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseAnimating::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + m_hActivator = pActivator; + SetThink(&CScriptedSentence :: FindThink ); + SetNextThink( 0 ); +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + m_playing = FALSE; //LRC + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink(&CScriptedSentence :: FindThink ); + SetNextThink( 1.0 ); + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + if (!m_iszEntity) //LRC- no target monster given: speak through HEV + { + CBasePlayer* pPlayer = (CBasePlayer*)UTIL_FindEntityByClassname( NULL, "player" ); + if (pPlayer) + { + m_playing = TRUE; + if ((STRING(m_iszSentence))[0] == '!') + pPlayer->SetSuitUpdate((char*)STRING(m_iszSentence),FALSE,0); + else + pPlayer->SetSuitUpdate((char*)STRING(m_iszSentence),TRUE,0); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink(&CScriptedSentence :: DurationThink ); + SetNextThink( m_flDuration ); + m_active = FALSE; + } + else + ALERT( at_console, "ScriptedSentence: can't find \"player\" to play HEV sentence!?\n" ); + return; + } + + CBaseMonster *pMonster = FindEntity( m_hActivator ); + if ( pMonster ) + { + m_playing = TRUE; + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink(&CScriptedSentence :: DurationThink ); + SetNextThink( m_flDuration ); + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + SetNextThink( m_flRepeat + 0.5 ); + } +} + +//LRC +void CScriptedSentence :: DurationThink( void ) +{ + m_playing = FALSE; + SetNextThink( m_flRepeat ); + SetThink(&CScriptedSentence :: DelayThink ); +} + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + SetNextThink( 0.1 ); + SetThink(&CScriptedSentence :: FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( CBaseEntity *pActivator ) +{ + CBaseEntity *pTarget; + CBaseMonster *pMonster; + + pTarget = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity), pActivator); + pMonster = NULL; + + while ( pTarget ) + { + pMonster = pTarget->MyMonsterPointer( ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(m_iszEntity), pActivator); + } + + pTarget = NULL; + while ((pTarget = UTIL_FindEntityInSphere( pTarget, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pTarget->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pTarget->pev->flags, FL_MONSTER )) + { + pMonster = pTarget->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + //LRC: Er... if the "concurrent" flag is NOT set, we make bConcurrent true!? + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + UTIL_FireTargets( pev->target, NULL, this, USE_TOGGLE ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink( Remove ); + SetNextThink( 0 ); +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->effects = 0; + pev->yaw_speed = 0; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + + diff --git a/server/monsters/scripted.h b/server/monsters/scripted.h new file mode 100644 index 00000000..9f5cb94e --- /dev/null +++ b/server/monsters/scripted.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1999, 2000 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 +#define SF_SCRIPT_STAYDEAD 256 // LRC- signifies that the animation kills the monster + // (needed because the monster animations don't use AnimEvent 1000 properly) + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +//LRC - rearranged into flags +#define SS_INTERRUPT_IDLE 0x0 +#define SS_INTERRUPT_ALERT 0x1 +#define SS_INTERRUPT_ANYSTATE 0x2 +#define SS_INTERRUPT_SCRIPTS 0x4 + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + //LRC: states for script entities + virtual STATE GetState( void ) { return m_iState; }; + STATE m_iState; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void EXPORT InitIdleThink( void ); //LRC + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + CBaseMonster* FindEntity( const char* sName, CBaseEntity *pActivator ); + virtual void PossessEntity( void ); + + inline BOOL IsAction( void ) { return FClassnameIs(pev,"scripted_action"); }; //LRC + + //LRC: Should the monster do a precise attack for this scripted_action? + // (Do a precise attack if we'll be turning to face the target, but we haven't just walked to the target.) + BOOL PreciseAttack( void ) + { + // if (m_fTurnType != 1) { ALERT(at_console,"preciseattack fails check 1\n"); return FALSE; } + // if (m_fMoveTo == 0) { ALERT(at_console,"preciseattack fails check 2\n"); return FALSE; } + // if (m_fMoveTo != 5 && m_iszAttack == 0) { ALERT(at_console,"preciseattack fails check 3\n"); return FALSE; } + // ALERT(at_console,"preciseattack passes!\n"); + // return TRUE; + return m_fTurnType == 1 && ( m_fMoveTo == 5 || (m_fMoveTo != 0 && !FStrEq(STRING(m_iszAttack), STRING(m_iszMoveTarget)) )); + }; + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_iszAttack; // entity to attack + int m_iszMoveTarget; // entity to move to + int m_iszFireOnBegin; // entity to fire when the sequence _starts_. + int m_fMoveTo; + int m_fTurnType; + int m_fAction; + int m_iFinishSchedule; + float m_flRadius; // range to search +//LRC- this does nothing!! float m_flRepeat; // repeat rate + int m_iRepeats; //LRC - number of times to repeat the animation + int m_iRepeatsLeft; //LRC + float m_fRepeatFrame; //LRC + int m_iPriority; //LRC + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +//LRC - removed CCineAI, obsolete + +#endif //SCRIPTED_H diff --git a/server/monsters/scriptevent.h b/server/monsters/scriptevent.h new file mode 100644 index 00000000..9a02bd64 --- /dev/null +++ b/server/monsters/scriptevent.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 SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/server/monsters/squad.h b/server/monsters/squad.h new file mode 100644 index 00000000..9acfccb2 --- /dev/null +++ b/server/monsters/squad.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// squad.h +//========================================================= + +// these are special group roles that are assigned to members when the group is formed. +// the reason these are explicitly assigned and tasks like throwing grenades to flush out +// enemies is that it's bad to have two members trying to flank left at the same time, but +// ok to have two throwing grenades at the same time. When a squad member cannot attack the +// enemy, it will choose to execute its special role. +#define bits_SQUAD_FLANK_LEFT ( 1 << 0 ) +#define bits_SQUAD_FLANK_RIGHT ( 1 << 1 ) +#define bits_SQUAD_ADVANCE ( 1 << 2 ) +#define bits_SQUAD_FLUSH_ATTACK ( 1 << 3 ) \ No newline at end of file diff --git a/server/monsters/squadmonster.cpp b/server/monsters/squadmonster.cpp new file mode 100644 index 00000000..59a9aaf3 --- /dev/null +++ b/server/monsters/squadmonster.cpp @@ -0,0 +1,642 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Squadmonster functions +//========================================================= +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "squadmonster.h" +#include "plane.h" + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CSquadMonster::m_SaveData[] = +{ + DEFINE_FIELD( CSquadMonster, m_hSquadLeader, FIELD_EHANDLE ), + DEFINE_ARRAY( CSquadMonster, m_hSquadMember, FIELD_EHANDLE, MAX_SQUAD_MEMBERS - 1 ), + + DEFINE_FIELD( CSquadMonster, m_fEnemyEluded, FIELD_BOOLEAN ), + DEFINE_FIELD( CSquadMonster, m_flLastEnemySightTime, FIELD_TIME ), + + DEFINE_FIELD( CSquadMonster, m_iMySlot, FIELD_INTEGER ), + + +}; + +IMPLEMENT_SAVERESTORE( CSquadMonster, CBaseMonster ); + + +//========================================================= +// OccupySlot - if any slots of the passed slots are +// available, the monster will be assigned to one. +//========================================================= +BOOL CSquadMonster :: OccupySlot( int iDesiredSlots ) +{ + int i; + int iMask; + int iSquadSlots; + + if ( !InSquad() ) + { + return TRUE; + } + + if ( SquadEnemySplit() ) + { + // if the squad members aren't all fighting the same enemy, slots are disabled + // so that a squad member doesn't get stranded unable to engage his enemy because + // all of the attack slots are taken by squad members fighting other enemies. + m_iMySlot = bits_SLOT_SQUAD_SPLIT; + return TRUE; + } + + CSquadMonster *pSquadLeader = MySquadLeader(); + + if ( !( iDesiredSlots ^ pSquadLeader->m_afSquadSlots ) ) + { + // none of the desired slots are available. + return FALSE; + } + + iSquadSlots = pSquadLeader->m_afSquadSlots; + + for ( i = 0; i < NUM_SLOTS; i++ ) + { + iMask = 1<m_afSquadSlots |= iMask; + m_iMySlot = iMask; +// ALERT ( at_aiconsole, "Took slot %d - %d\n", i, m_hSquadLeader->m_afSquadSlots ); + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +// VacateSlot +//========================================================= +void CSquadMonster :: VacateSlot() +{ + if ( m_iMySlot != bits_NO_SLOT && InSquad() ) + { +// ALERT ( at_aiconsole, "Vacated Slot %d - %d\n", m_iMySlot, m_hSquadLeader->m_afSquadSlots ); + MySquadLeader()->m_afSquadSlots &= ~m_iMySlot; + m_iMySlot = bits_NO_SLOT; + } +} + +//========================================================= +// ScheduleChange +//========================================================= +void CSquadMonster :: ScheduleChange ( void ) +{ + VacateSlot(); +} + +//========================================================= +// Killed +//========================================================= +void CSquadMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + VacateSlot(); + + if ( InSquad() ) + { + MySquadLeader()->SquadRemove( this ); + } + + CBaseMonster :: Killed ( pevAttacker, iGib ); +} + +// These functions are still awaiting conversion to CSquadMonster + + +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CSquadMonster :: SquadRemove( CSquadMonster *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_hSquadLeader == this ); + + // If I'm the leader, get rid of my squad + if (pRemove == MySquadLeader()) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + CSquadMonster *pMember = MySquadMember(i); + if (pMember) + { + pMember->m_hSquadLeader = NULL; + m_hSquadMember[i] = NULL; + } + } + } + else + { + CSquadMonster *pSquadLeader = MySquadLeader(); + if (pSquadLeader) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + if (pSquadLeader->m_hSquadMember[i] == this) + { + pSquadLeader->m_hSquadMember[i] = NULL; + break; + } + } + } + } + + pRemove->m_hSquadLeader = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +BOOL CSquadMonster :: SquadAdd( CSquadMonster *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + for (int i = 0; i < MAX_SQUAD_MEMBERS-1; i++) + { + if (m_hSquadMember[i] == NULL) + { + m_hSquadMember[i] = pAdd; + pAdd->m_hSquadLeader = this; + return TRUE; + } + } + return FALSE; + // should complain here +} + + +//========================================================= +// +// SquadPasteEnemyInfo - called by squad members that have +// current info on the enemy so that it can be stored for +// members who don't have current info. +// +//========================================================= +void CSquadMonster :: SquadPasteEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + pSquadLeader->m_vecEnemyLKP = m_vecEnemyLKP; +} + +//========================================================= +// +// SquadCopyEnemyInfo - called by squad members who don't +// have current info on the enemy. Reads from the same fields +// in the leader's data that other squad members write to, +// so the most recent data is always available here. +// +//========================================================= +void CSquadMonster :: SquadCopyEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + m_vecEnemyLKP = pSquadLeader->m_vecEnemyLKP; +} + +//========================================================= +// +// SquadMakeEnemy - makes everyone in the squad angry at +// the same entity. +// +//========================================================= +void CSquadMonster :: SquadMakeEnemy ( CBaseEntity *pEnemy ) +{ + if (!InSquad()) + return; + + if ( !pEnemy ) + { + ALERT ( at_console, "ERROR: SquadMakeEnemy() - pEnemy is NULL!\n" ); + return; + } + + CSquadMonster *pSquadLeader = MySquadLeader( ); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember) + { + // reset members who aren't activly engaged in fighting + if (pMember->m_hEnemy != pEnemy && !pMember->HasConditions( bits_COND_SEE_ENEMY)) + { + if ( pMember->m_hEnemy != NULL) + { + // remember their current enemy + pMember->PushEnemy( pMember->m_hEnemy, pMember->m_vecEnemyLKP ); + } + // give them a new enemy + pMember->m_hEnemy = pEnemy; + pMember->m_vecEnemyLKP = pEnemy->pev->origin; + pMember->SetConditions ( bits_COND_NEW_ENEMY ); + } + } + } +} + + +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CSquadMonster :: SquadCount( void ) +{ + if (!InSquad()) + return 0; + + CSquadMonster *pSquadLeader = MySquadLeader(); + int squadCount = 0; + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + if (pSquadLeader->MySquadMember(i) != NULL) + squadCount++; + } + + return squadCount; +} + + +//========================================================= +// +// SquadRecruit(), get some monsters of my classification and +// link them as a group. returns the group size +// +//========================================================= +int CSquadMonster :: SquadRecruit( int searchRadius, int maxMembers ) +{ + int squadCount; + int iMyClass = Classify();// cache this monster's class + + + // Don't recruit if I'm already in a group + if ( InSquad() ) + return 0; + + if ( maxMembers < 2 ) + return 0; + + // I am my own leader + m_hSquadLeader = this; + squadCount = 1; + + CBaseEntity *pEntity = NULL; + + if ( !FStringNull( pev->netname ) ) + { + // I have a netname, so unconditionally recruit everyone else with that name. + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + while ( pEntity ) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer(); + + if ( pRecruit ) + { + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && pRecruit != this ) + { + // minimum protection here against user error.in worldcraft. + if (!SquadAdd( pRecruit )) + break; + squadCount++; + } + } + + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + } + } + else + { + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, searchRadius )) != NULL) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && + ( (iMyClass != CLASS_ALIEN_MONSTER) || FStrEq(STRING(pev->classname), STRING(pRecruit->pev->classname))) && + FStringNull( pRecruit->pev->netname ) ) + { + TraceResult tr; + UTIL_TraceLine( pev->origin + pev->view_ofs, pRecruit->pev->origin + pev->view_ofs, ignore_monsters, pRecruit->edict(), &tr );// try to hit recruit with a traceline. + if ( tr.flFraction == 1.0 ) + { + if (!SquadAdd( pRecruit )) + break; + + squadCount++; + } + } + } + } + } + + // no single member squads + if (squadCount == 1) + { + m_hSquadLeader = NULL; + } + + return squadCount; +} + +//========================================================= +// CheckEnemy +//========================================================= +int CSquadMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + int iUpdatedLKP; + + iUpdatedLKP = CBaseMonster :: CheckEnemy ( m_hEnemy ); + + // communicate with squad members about the enemy IF this individual has the same enemy as the squad leader. + if ( InSquad() && (CBaseEntity *)m_hEnemy == MySquadLeader()->m_hEnemy ) + { + if ( iUpdatedLKP ) + { + // have new enemy information, so paste to the squad. + SquadPasteEnemyInfo(); + } + else + { + // enemy unseen, copy from the squad knowledge. + SquadCopyEnemyInfo(); + } + } + + return iUpdatedLKP; +} + +//========================================================= +// StartMonster +//========================================================= +void CSquadMonster :: StartMonster( void ) +{ + CBaseMonster :: StartMonster(); + + if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) + { + if ( !FStringNull( pev->netname ) ) + { + // if I have a groupname, I can only recruit if I'm flagged as leader + if ( !( pev->spawnflags & SF_SQUADMONSTER_LEADER ) ) + { + return; + } + } + + // try to form squads now. + int iSquadSize = SquadRecruit( 1024, 4 ); + + if ( iSquadSize ) + { + ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, STRING( pev->classname ) ); + } + + if ( IsLeader() && FClassnameIs ( pev, "monster_human_grunt" ) ) + { + SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack + pev->skin = 0; + } + + } +} + +BOOL CSquadMonster :: NoFriendlyFire( void ) +{ + return NoFriendlyFire( FALSE ); //default: don't like the player +} + +//========================================================= +// NoFriendlyFire - checks for possibility of friendly fire +// +// Builds a large box in front of the grunt and checks to see +// if any squad members are in that box. +// +// Can now, also, check whether the player is in the box. LRC +//========================================================= +BOOL CSquadMonster :: NoFriendlyFire( BOOL playerAlly ) +{ + if ( !playerAlly && !InSquad() ) + { + return TRUE; + } + + VPlane backPlane; + VPlane leftPlane; + VPlane rightPlane; + + Vector vecLeftSide; + Vector vecRightSide; + Vector v_left; + + //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! + + if ( m_hEnemy != NULL ) + { + UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); + } + else + { + // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. + return FALSE; + } + + //UTIL_MakeVectors ( pev->angles ); + + vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + v_left = gpGlobals->v_right * -1; + + leftPlane.Init ( gpGlobals->v_right, vecLeftSide ); + rightPlane.Init ( v_left, vecRightSide ); + backPlane.Init ( gpGlobals->v_forward, pev->origin ); + +/* + ALERT ( at_console, "LeftPlane: %f %f %f : %f\n", leftPlane.m_vecNormal.x, leftPlane.m_vecNormal.y, leftPlane.m_vecNormal.z, leftPlane.m_flDist ); + ALERT ( at_console, "RightPlane: %f %f %f : %f\n", rightPlane.m_vecNormal.x, rightPlane.m_vecNormal.y, rightPlane.m_vecNormal.z, rightPlane.m_flDist ); + ALERT ( at_console, "BackPlane: %f %f %f : %f\n", backPlane.m_vecNormal.x, backPlane.m_vecNormal.y, backPlane.m_vecNormal.z, backPlane.m_flDist ); +*/ + + CSquadMonster *pSquadLeader = MySquadLeader(); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember && pMember != this) + { + + if ( backPlane.GetPointSide ( pMember->pev->origin ) && + leftPlane.GetPointSide ( pMember->pev->origin ) && + rightPlane.GetPointSide ( pMember->pev->origin) ) + { + // this guy is in the check volume! Don't shoot! + return FALSE; + } + } + } + + if (playerAlly) + { + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() ); + if (!FNullEnt(pentPlayer) && + backPlane.GetPointSide ( pentPlayer->v.origin ) && + leftPlane.GetPointSide ( pentPlayer->v.origin ) && + rightPlane.GetPointSide ( pentPlayer->v.origin ) ) + { + // the player is in the check volume! Don't shoot! + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CSquadMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( HasConditions ( bits_COND_NEW_ENEMY ) && InSquad() ) + { + SquadMakeEnemy ( m_hEnemy ); + } + break; + } + + return CBaseMonster :: GetIdealState(); +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: FValidateCover ( const Vector &vecCoverLocation ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + if (SquadMemberInRange( vecCoverLocation, 128 )) + { + // another squad member is too close to this piece of cover. + return FALSE; + } + + return TRUE; +} + +//========================================================= +// SquadEnemySplit- returns TRUE if not all squad members +// are fighting the same enemy. +//========================================================= +BOOL CSquadMonster :: SquadEnemySplit ( void ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: SquadMemberInRange ( const Vector &vecLocation, float flDist ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); + if (pSquadMember && (vecLocation - pSquadMember->pev->origin ).Length2D() <= flDist) + return TRUE; + } + return FALSE; +} + + +extern Schedule_t slChaseEnemyFailed[]; + +Schedule_t *CSquadMonster::GetScheduleOfType( int iType ) +{ + switch ( iType ) + { + + case SCHED_CHASE_ENEMY_FAILED: + { + return &slChaseEnemyFailed[ 0 ]; + } + + default: + return CBaseMonster::GetScheduleOfType( iType ); + } +} + diff --git a/server/monsters/squadmonster.h b/server/monsters/squadmonster.h new file mode 100644 index 00000000..8519bdd7 --- /dev/null +++ b/server/monsters/squadmonster.h @@ -0,0 +1,121 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// CSquadMonster - all the extra data for monsters that +// form squads. +//========================================================= + +#define SF_SQUADMONSTER_LEADER 32 + + +#define bits_NO_SLOT 0 + +// HUMAN GRUNT SLOTS +#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 0 ) +#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 1 ) +#define bits_SLOTS_HGRUNT_ENGAGE ( bits_SLOT_HGRUNT_ENGAGE1 | bits_SLOT_HGRUNT_ENGAGE2 ) + +#define bits_SLOT_HGRUNT_GRENADE1 ( 1 << 2 ) +#define bits_SLOT_HGRUNT_GRENADE2 ( 1 << 3 ) +#define bits_SLOTS_HGRUNT_GRENADE ( bits_SLOT_HGRUNT_GRENADE1 | bits_SLOT_HGRUNT_GRENADE2 ) + +// ALIEN GRUNT SLOTS +#define bits_SLOT_AGRUNT_HORNET1 ( 1 << 4 ) +#define bits_SLOT_AGRUNT_HORNET2 ( 1 << 5 ) +#define bits_SLOT_AGRUNT_CHASE ( 1 << 6 ) +#define bits_SLOTS_AGRUNT_HORNET ( bits_SLOT_AGRUNT_HORNET1 | bits_SLOT_AGRUNT_HORNET2 ) + +// HOUNDEYE SLOTS +#define bits_SLOT_HOUND_ATTACK1 ( 1 << 7 ) +#define bits_SLOT_HOUND_ATTACK2 ( 1 << 8 ) +#define bits_SLOT_HOUND_ATTACK3 ( 1 << 9 ) +#define bits_SLOTS_HOUND_ATTACK ( bits_SLOT_HOUND_ATTACK1 | bits_SLOT_HOUND_ATTACK2 | bits_SLOT_HOUND_ATTACK3 ) + +// global slots +#define bits_SLOT_SQUAD_SPLIT ( 1 << 10 )// squad members don't all have the same enemy + +#define NUM_SLOTS 11// update this every time you add/remove a slot. + +#define MAX_SQUAD_MEMBERS 5 + +//========================================================= +// CSquadMonster - for any monster that forms squads. +//========================================================= +class CSquadMonster : public CBaseMonster +{ +public: + // squad leader info + EHANDLE m_hSquadLeader; // who is my leader + EHANDLE m_hSquadMember[MAX_SQUAD_MEMBERS-1]; // valid only for leader + int m_afSquadSlots; + float m_flLastEnemySightTime; // last time anyone in the squad saw the enemy + BOOL m_fEnemyEluded; + + // squad member info + int m_iMySlot;// this is the behaviour slot that the monster currently holds in the squad. + + int CheckEnemy ( CBaseEntity *pEnemy ); + void StartMonster ( void ); + void VacateSlot( void ); + void ScheduleChange( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + BOOL OccupySlot( int iDesiredSlot ); + BOOL NoFriendlyFire( void ); + BOOL NoFriendlyFire( BOOL playerAlly ); + + // squad functions still left in base class + CSquadMonster *MySquadLeader( ) + { + CSquadMonster *pSquadLeader = (CSquadMonster *)((CBaseEntity *)m_hSquadLeader); + if (pSquadLeader != NULL) + return pSquadLeader; + return this; + } + CSquadMonster *MySquadMember( int i ) + { + if (i >= MAX_SQUAD_MEMBERS-1) + return this; + else + return (CSquadMonster *)((CBaseEntity *)m_hSquadMember[i]); + } + int InSquad ( void ) { return m_hSquadLeader != NULL; } + int IsLeader ( void ) { return m_hSquadLeader == this; } + int SquadJoin ( int searchRadius ); + int SquadRecruit ( int searchRadius, int maxMembers ); + int SquadCount( void ); + void SquadRemove( CSquadMonster *pRemove ); + void SquadUnlink( void ); + BOOL SquadAdd( CSquadMonster *pAdd ); + void SquadDisband( void ); + void SquadAddConditions ( int iConditions ); + void SquadMakeEnemy ( CBaseEntity *pEnemy ); + void SquadPasteEnemyInfo ( void ); + void SquadCopyEnemyInfo ( void ); + BOOL SquadEnemySplit ( void ); + BOOL SquadMemberInRange( const Vector &vecLocation, float flDist ); + + virtual CSquadMonster *MySquadMonsterPointer( void ) { return this; } + + static TYPEDESCRIPTION m_SaveData[]; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + BOOL FValidateCover ( const Vector &vecCoverLocation ); + + MONSTERSTATE GetIdealState ( void ); + Schedule_t *GetScheduleOfType ( int iType ); +}; + diff --git a/server/monsters/talkmonster.cpp b/server/monsters/talkmonster.cpp new file mode 100644 index 00000000..472684da --- /dev/null +++ b/server/monsters/talkmonster.cpp @@ -0,0 +1,1557 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "talkmonster.h" +#include "defaultai.h" +#include "scripted.h" +#include "soundent.h" +#include "animation.h" + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore + +TYPEDESCRIPTION CTalkMonster::m_SaveData[] = +{ + DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ), + DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ), + + // Recalc'ed in Precache() + // DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ), + // DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ), + DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszDecline, FIELD_STRING ), //LRC + DEFINE_FIELD( CTalkMonster, m_iszSpeakAs, FIELD_STRING ), //LRC + DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ); + +// array of friend names +char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = +{ + "monster_barney", + "monster_scientist", + "monster_sitting_scientist", +}; + + +//========================================================= +// AI Schedules Specific to talking monsters +//========================================================= + +Task_t tlIdleResponse[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and listen + { TASK_WAIT, (float)0.5 },// Wait until sure it's me they are talking to + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done + { TASK_TLK_RESPOND, (float)0 },// Wait and then say my response + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slIdleResponse[] = +{ + { + tlIdleResponse, + ARRAYSIZE ( tlIdleResponse ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Response" + + }, +}; + +Task_t tlIdleSpeak[] = +{ + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slIdleSpeak[] = +{ + { + tlIdleSpeak, + ARRAYSIZE ( tlIdleSpeak ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak" + }, +}; + +Task_t tlIdleSpeakWait[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_EYECONTACT, (float)0 },// + { TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned +}; + +Schedule_t slIdleSpeakWait[] = +{ + { + tlIdleSpeakWait, + ARRAYSIZE ( tlIdleSpeakWait ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak Wait" + }, +}; + +Task_t tlIdleHello[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + +}; + +Schedule_t slIdleHello[] = +{ + { + tlIdleHello, + ARRAYSIZE ( tlIdleHello ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT, + "Idle Hello" + }, +}; + +Task_t tlIdleStopShooting[] = +{ + { TASK_TLK_STOPSHOOTING, (float)0 },// tell player to stop shooting friend + // { TASK_TLK_EYECONTACT, (float)0 },// look at the player +}; + +Schedule_t slIdleStopShooting[] = +{ + { + tlIdleStopShooting, + ARRAYSIZE ( tlIdleStopShooting ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + 0, + "Idle Stop Shooting" + }, +}; + +Task_t tlMoveAway[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAway[] = +{ + { + tlMoveAway, + ARRAYSIZE ( tlMoveAway ), + 0, + 0, + "MoveAway" + }, +}; + + +Task_t tlMoveAwayFail[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAwayFail[] = +{ + { + tlMoveAwayFail, + ARRAYSIZE ( tlMoveAwayFail ), + 0, + 0, + "MoveAwayFail" + }, +}; + + + +Task_t tlMoveAwayFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slMoveAwayFollow[] = +{ + { + tlMoveAwayFollow, + ARRAYSIZE ( tlMoveAwayFollow ), + 0, + 0, + "MoveAwayFollow" + }, +}; + +Task_t tlTlkIdleWatchClient[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_LOOK_AT_CLIENT, (float)6 }, +}; + +Task_t tlTlkIdleWatchClientStare[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_CLIENT_STARE, (float)6 }, + { TASK_TLK_STARE, (float)0 }, + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, +}; + +Schedule_t slTlkIdleWatchClient[] = +{ + { + tlTlkIdleWatchClient, + ARRAYSIZE ( tlTlkIdleWatchClient ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClient" + }, + + { + tlTlkIdleWatchClientStare, + ARRAYSIZE ( tlTlkIdleWatchClientStare ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClientStare" + }, +}; + + +Task_t tlTlkIdleEyecontact[] = +{ + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slTlkIdleEyecontact[] = +{ + { + tlTlkIdleEyecontact, + ARRAYSIZE ( tlTlkIdleEyecontact ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "TlkIdleEyecontact" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) +{ + slIdleResponse, + slIdleSpeak, + slIdleHello, + slIdleSpeakWait, + slIdleStopShooting, + slMoveAway, + slMoveAwayFollow, + slMoveAwayFail, + slTlkIdleWatchClient, + &slTlkIdleWatchClient[ 1 ], + slTlkIdleEyecontact, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster ); + + +void CTalkMonster :: SetActivity ( Activity newActivity ) +{ + if (newActivity == ACT_IDLE && IsTalking() ) + newActivity = ACT_SIGNAL3; + + if ( newActivity == ACT_SIGNAL3 && (LookupActivity ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE)) + newActivity = ACT_IDLE; + + CBaseMonster::SetActivity( newActivity ); +} + + +void CTalkMonster :: StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TLK_SPEAK: + // ask question or make statement + FIdleSpeak(); + TaskComplete(); + break; + + case TASK_TLK_RESPOND: + // respond to question + IdleRespond(); + TaskComplete(); + break; + + case TASK_TLK_HELLO: + // greet player + FIdleHello(); + TaskComplete(); + break; + + + case TASK_TLK_STARE: + // let the player know I know he's staring at me. + FIdleStare(); + TaskComplete(); + break; + + case TASK_FACE_PLAYER: + case TASK_TLK_LOOK_AT_CLIENT: + case TASK_TLK_CLIENT_STARE: + // track head to the client for a while. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + + case TASK_TLK_EYECONTACT: + break; + + case TASK_TLK_IDEALYAW: + if (m_hTalkTarget != NULL) + { + pev->yaw_speed = 60; + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw < 0) + { + pev->ideal_yaw = min( yaw + 45, 0 ) + pev->angles.y; + } + else + { + pev->ideal_yaw = max( yaw - 45, 0 ) + pev->angles.y; + } + } + TaskComplete(); + break; + + case TASK_TLK_HEADRESET: + // reset head position after looking at something + m_hTalkTarget = NULL; + TaskComplete(); + break; + + case TASK_TLK_STOPSHOOTING: + // tell player to stop shooting + PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_CANT_FOLLOW: + StopFollowing( FALSE ); + PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT(2, 2.5), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_WALK_PATH_FOR_UNITS: + m_movementActivity = ACT_WALK; + break; + + case TASK_MOVE_AWAY_PATH: + { + Vector dir = pev->angles; + dir.y = pev->ideal_yaw + 180; + Vector move; + + UTIL_MakeVectorsPrivate( dir, move, NULL, NULL ); + dir = pev->origin + move * pTask->flData; + if ( MoveToLocation( ACT_WALK, 2, dir ) ) + { + TaskComplete(); + } + else if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + 2; + TaskComplete(); + } + else + { + // nowhere to go? + TaskFail(); + } + } + break; + + case TASK_PLAY_SCRIPT: + m_hTalkTarget = NULL; + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + } +} + + +void CTalkMonster :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_TLK_CLIENT_STARE: + case TASK_TLK_LOOK_AT_CLIENT: + + edict_t *pPlayer; + + // track head to the client for a while. + if ( m_MonsterState == MONSTERSTATE_IDLE && + !IsMoving() && + !IsTalking() ) + { + // Get edict for one player + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + IdleHeadTurn( pPlayer->v.origin ); + } + } + else + { + // started moving or talking + TaskFail(); + return; + } + + if ( pTask->iTask == TASK_TLK_CLIENT_STARE ) + { + // fail out if the player looks away or moves away. + if ( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST ) + { + // player moved away. + TaskFail(); + } + + UTIL_MakeVectors( pPlayer->v.angles ); + if ( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView ) + { + // player looked away + TaskFail(); + } + } + + if ( gpGlobals->time > m_flWaitFinished ) + { + TaskComplete(); + } + break; + + case TASK_FACE_PLAYER: + { + // Get edict for one player + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + MakeIdealYaw ( pPlayer->v.origin ); + ChangeYaw ( pev->yaw_speed ); + IdleHeadTurn( pPlayer->v.origin ); + if ( gpGlobals->time > m_flWaitFinished && FlYawDiff() < 10 ) + { + TaskComplete(); + } + } + else + { + TaskFail(); + } + } + break; + + case TASK_TLK_EYECONTACT: + if (!IsMoving() && IsTalking() && m_hTalkTarget != NULL) + { + // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + TaskComplete(); + } + break; + + case TASK_WALK_PATH_FOR_UNITS: + { + float distance; + + distance = (m_vecLastPosition - pev->origin).Length2D(); + + // Walk path until far enough away + if ( distance > pTask->flData || MovementIsComplete() ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + } + break; + case TASK_WAIT_FOR_MOVEMENT: + if (IsTalking() && m_hTalkTarget != NULL) + { + // ALERT(at_console, "walking, talking\n"); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + IdleHeadTurn( pev->origin ); + // override so that during walk, a scientist may talk and greet player + FIdleHello(); + if (RANDOM_LONG(0,m_nSpeak * 20) == 0) + { + FIdleSpeak(); + } + } + + CBaseMonster::RunTask( pTask ); + if (TaskIsComplete()) + IdleHeadTurn( pev->origin ); + break; + + default: + if (IsTalking() && m_hTalkTarget != NULL) + { + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + } +} + + +void CTalkMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + // If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him + if ( (pevAttacker->flags & FL_CLIENT) && m_MonsterState != MONSTERSTATE_PRONE ) + { + AlertFriends(); + LimitFollowers( CBaseEntity::Instance(pevAttacker), 0 ); + } + + m_hTargetEnt = NULL; + // Don't finish that sentence + StopTalking(); + SetUse( NULL ); + CBaseMonster::Killed( pevAttacker, iGib ); +} + + + +CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace ) +{ + CBaseEntity *pFriend = pPrevious; + char *pszFriend; + TraceResult tr; + Vector vecCheck; + + pszFriend = m_szFriends[ FriendNumber(listNumber) ]; + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + if ( bTrace ) + { + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT(pev), &tr); + } + else + tr.flFraction = 1.0; + + if (tr.flFraction == 1.0) + { + return pFriend; + } + } + + return NULL; +} + + +void CTalkMonster::AlertFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster->IsAlive() ) + { + // don't provoke a friend that's playing a death animation. They're a goner + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + } + } + } +} + + + +void CTalkMonster::ShutUpFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + pMonster->SentenceStop(); + } + } + } +} + + +// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU +// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers +void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) +{ + CBaseEntity *pFriend = NULL; + int i, count; + + count = 0; + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, FALSE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + if ( pMonster->m_hTargetEnt == pPlayer ) + { + count++; + if ( count > maxFollowers ) + pMonster->StopFollowing( TRUE ); + } + } + } + } +} + + +float CTalkMonster::TargetDistance( void ) +{ + // If we lose the player, or he dies, return a really large distance + if ( m_hTargetEnt == NULL || !m_hTargetEnt->IsAlive() ) + return 1e6; + + return (m_hTargetEnt->pev->origin - pev->origin).Length(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTalkMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time + if (RANDOM_LONG(0,99) < 75) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + ShutUpFriends(); + PlaySentence( pEvent->options, RANDOM_FLOAT(2.8, 3.4), VOL_NORM, ATTN_IDLE ); + //ALERT(at_console, "script event speak\n"); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +// monsters derived from ctalkmonster should call this in precache() + +void CTalkMonster :: TalkInit( void ) +{ + // every new talking monster must reset this global, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + + CTalkMonster::g_talkWaitTime = 0; + + if (m_iszSpeakAs) //LRC: changing voice groups for monsters + { + char szBuf[64]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_"); + char *szAssign = &(szBuf[strlen(szBuf)]); + + //LRC - this is pretty dodgy; test with save/restore. + strcpy(szAssign,"ANSWER"); + m_szGrp[TLK_ANSWER] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"QUESTION"); + m_szGrp[TLK_QUESTION] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"IDLE"); + m_szGrp[TLK_IDLE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"STARE"); + m_szGrp[TLK_STARE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"PFOLLOW"); + else + strcpy(szAssign,"OK"); + m_szGrp[TLK_USE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"PWAIT"); + else + strcpy(szAssign,"WAIT"); + m_szGrp[TLK_UNUSE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"POK"); + else + strcpy(szAssign,"NOTOK"); + m_szGrp[TLK_DECLINE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"STOP"); + m_szGrp[TLK_STOP] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"NOSHOOT"); + m_szGrp[TLK_NOSHOOT] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"HELLO"); + m_szGrp[TLK_HELLO] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT1"); + m_szGrp[TLK_PLHURT1] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT2"); + m_szGrp[TLK_PLHURT2] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT3"); + m_szGrp[TLK_PLHURT3] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PHELLO"); + m_szGrp[TLK_PHELLO] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PIDLE"); + m_szGrp[TLK_PIDLE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PQUESTION"); + m_szGrp[TLK_PQUESTION] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"SMELL"); + m_szGrp[TLK_SMELL] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"WOUND"); + m_szGrp[TLK_WOUND] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"MORTAL"); + m_szGrp[TLK_MORTAL] = STRING(ALLOC_STRING(szBuf)); + } + + m_voicePitch = 100; +} + +//========================================================= +// FindNearestFriend +// Scan for nearest, visible friend. If fPlayer is true, look for +// nearest player +//========================================================= +CBaseEntity *CTalkMonster :: FindNearestFriend(BOOL fPlayer) +{ + CBaseEntity *pFriend = NULL; + CBaseEntity *pNearest = NULL; + float range = 10000000.0; + TraceResult tr; + Vector vecStart = pev->origin; + Vector vecCheck; + int i; + char *pszFriend; + int cfriends; + + vecStart.z = pev->absmax.z; + + if (fPlayer) + cfriends = 1; + else + cfriends = TLK_CFRIENDS; + + // for each type of friend... + + for (i = cfriends-1; i > -1; i--) + { + if (fPlayer) + pszFriend = "player"; + else + pszFriend = m_szFriends[FriendNumber(i)]; + + if (!pszFriend) + continue; + + // for each friend in this bsp... + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + + // If not a monster for some reason, or in a script, or prone + if ( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE ) + continue; + + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + // if closer than previous friend, and in range, see if he's visible + + if (range > (vecStart - vecCheck).Length()) + { + UTIL_TraceLine(vecStart, vecCheck, ignore_monsters, ENT(pev), &tr); + + if (tr.flFraction == 1.0) + { + // visible and in range, this is the new nearest scientist + if ((vecStart - vecCheck).Length() < TALKRANGE_MIN) + { + pNearest = pFriend; + range = (vecStart - vecCheck).Length(); + } + } + } + } + } + return pNearest; +} + +int CTalkMonster :: GetVoicePitch( void ) +{ + return m_voicePitch + RANDOM_LONG(0,3); +} + + +void CTalkMonster :: Touch( CBaseEntity *pOther ) +{ + // Did the player touch me? + if ( pOther->IsPlayer() ) + { + // Ignore if pissed at player + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return; + + // Stay put during speech + if ( IsTalking() ) + return; + + // Heuristic for determining if the player is pushing me away + float speed = fabs(pOther->pev->velocity.x) + fabs(pOther->pev->velocity.y); + if ( speed > 50 ) + { + SetConditions( bits_COND_CLIENT_PUSH ); + MakeIdealYaw( pOther->pev->origin ); + } + } +} + + + +//========================================================= +// IdleRespond +// Respond to a previous question +//========================================================= +void CTalkMonster :: IdleRespond( void ) +{ + int pitch = GetVoicePitch(); + + // play response + PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); +} + +int CTalkMonster :: FOkToSpeak( void ) +{ + // if in the grip of a barnacle, don't speak + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) + { + return FALSE; + } + + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + // if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + //monster generic can speak always + if ( pev->spawnflags & SF_MONSTER_GAG && !FClassnameIs(pev, "monster_generic") ) + return FALSE; + + if ( m_MonsterState == MONSTERSTATE_PRONE ) + return FALSE; + + // if player is not in pvs, don't speak + if (!IsAlive() || (FNullEnt(FIND_CLIENT_IN_PVS(edict())) && !HaveCamerasInPVS( edict() ))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + return FALSE; + + return TRUE; +} + + +int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) +{ + if ( fDisregardState ) + return CBaseMonster::CanPlaySentence( fDisregardState ); + return FOkToSpeak(); +} + +//========================================================= +// FIdleStare +//========================================================= +int CTalkMonster :: FIdleStare( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE ); + + m_hTalkTarget = FindNearestFriend( TRUE ); + return TRUE; +} + +//========================================================= +// IdleHello +// Try to greet player first time he's seen +//========================================================= +int CTalkMonster :: FIdleHello( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + // if this is first time scientist has seen player, greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + // get a player + CBaseEntity *pPlayer = FindNearestFriend(TRUE); + + if (pPlayer) + { + if (FInViewCone(pPlayer) && FVisible(pPlayer)) + { + m_hTalkTarget = pPlayer; + + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + else + PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + + SetBits(m_bitsSaid, bit_saidHelloPlayer); + + return TRUE; + } + } + } + return FALSE; +} + + +// turn head towards supplied origin +void CTalkMonster :: IdleHeadTurn( Vector &vecFriend ) +{ + // turn head in desired direction only if ent has a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = VecToYaw(vecFriend - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } +} + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CTalkMonster :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + const char *szIdleGroup; + const char *szQuestionGroup; + float duration; + + if (!FOkToSpeak()) + return FALSE; + + // set idle groups based on pre/post disaster + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + { + szIdleGroup = m_szGrp[TLK_PIDLE]; + szQuestionGroup = m_szGrp[TLK_PQUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(4.8, 5.2); + } + else + { + szIdleGroup = m_szGrp[TLK_IDLE]; + szQuestionGroup = m_szGrp[TLK_QUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(2.8, 3.2); + + } + + pitch = GetVoicePitch(); + + // player using this entity is alive and wounded? + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL ) + { + if ( pTarget->IsPlayer() ) + { + if ( pTarget->IsAlive() ) + { + m_hTalkTarget = m_hTargetEnt; + if (!FBitSet(m_bitsSaid, bit_saidDamageHeavy) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageHeavy); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageMedium) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageMedium); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageLight) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageLight); + return TRUE; + } + } + else + { + //!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want. + // "Oh dear, Gordon Freeman is dead!" -Scientist + // "Damn, I can't do this without you." -Barney + } + } + } + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && !(pFriend->IsMoving()) && (RANDOM_LONG(0,99) < 75)) + { + PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE ); + //SENTENCEG_PlayRndSz( ENT(pev), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch ); + + // force friend to answer + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + m_hTalkTarget = pFriend; + pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!! + pTalkMonster->m_flStopTalkTime = m_flStopTalkTime; + + m_nSpeak++; + return TRUE; + } + + // otherwise, play an idle statement, try to face client when making a statement. + if ( RANDOM_LONG(0,1) ) + { + //SENTENCEG_PlayRndSz( ENT(pev), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch ); + CBaseEntity *pFriend = FindNearestFriend(TRUE); + + if ( pFriend ) + { + m_hTalkTarget = pFriend; + PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); + m_nSpeak++; + return TRUE; + } + } + + // didn't speak + Talk( 0 ); + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} + +void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + if ( !bConcurrent ) + ShutUpFriends(); + + ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! + m_useTime = gpGlobals->time + duration; + PlaySentence( pszSentence, duration, volume, attenuation ); + + m_hTalkTarget = pListener; +} + +void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( !pszSentence ) + return; + + Talk ( duration ); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0; + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch()); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() ); + + // If you say anything, don't greet the player - you may have already spoken to them + SetBits(m_bitsSaid, bit_saidHelloPlayer); +} + +//========================================================= +// Talk - set a timer that tells us when the monster is done +// talking. +//========================================================= +void CTalkMonster :: Talk( float flDuration ) +{ + if ( flDuration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->time + 3; + } + else + { + m_flStopTalkTime = gpGlobals->time + flDuration; + } +} + +// Prepare this talking monster to answer question +void CTalkMonster :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + if ( !m_pCine ) + ChangeSchedule( slIdleResponse ); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + +int CTalkMonster :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + if ( IsAlive() ) + { + // if player damaged this entity, have other friends talk about it + if (pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet(pevAttacker->flags, FL_CLIENT)) + { + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && pFriend->IsAlive()) + { + // only if not dead or dying! + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + pTalkMonster->ChangeSchedule( slIdleStopShooting ); + } + } + } + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +Schedule_t* CTalkMonster :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_MOVE_AWAY: + return slMoveAway; + + case SCHED_MOVE_AWAY_FOLLOW: + return slMoveAwayFollow; + + case SCHED_MOVE_AWAY_FAIL: + return slMoveAwayFail; + + case SCHED_TARGET_FACE: + // speak during 'use' + if (RANDOM_LONG(0,99) < 2) + //ALERT ( at_console, "target chase speak\n" ); + return slIdleSpeakWait; + else + return slIdleStand; + + case SCHED_IDLE_STAND: + { + // if never seen player, try to greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + return slIdleHello; + } + + // sustained light wounds? + if (!FBitSet(m_bitsSaid, bit_saidWoundLight) && (pev->health <= (pev->max_health * 0.75))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundLight); + return slIdleStand; + } + // sustained heavy wounds? + else if (!FBitSet(m_bitsSaid, bit_saidWoundHeavy) && (pev->health <= (pev->max_health * 0.5))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundHeavy); + return slIdleStand; + } + + // talk about world + if (FOkToSpeak() && RANDOM_LONG(0,m_nSpeak * 2) == 0) + { + //ALERT ( at_console, "standing idle speak\n" ); + return slIdleSpeak; + } + + if ( !IsTalking() && HasConditions ( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 ) + { + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + // watch the client. + UTIL_MakeVectors ( pPlayer->v.angles ); + if ( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && + UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView ) + { + // go into the special STARE schedule if the player is close, and looking at me too. + return &slTlkIdleWatchClient[ 1 ]; + } + + return slTlkIdleWatchClient; + } + } + else + { + if (IsTalking()) + // look at who we're talking to + return slTlkIdleEyecontact; + else + // regular standing idle + return slIdleStand; + } + + + // NOTE - caller must first CTalkMonster::GetScheduleOfType, + // then check result and decide what to return ie: if sci gets back + // slIdleStand, return slIdleSciStand + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// IsTalking - am I saying a sentence right now? +//========================================================= +BOOL CTalkMonster :: IsTalking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->time ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// If there's a player around, watch him. +//========================================================= +void CTalkMonster :: PrescheduleThink ( void ) +{ + if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) + { + SetConditions ( bits_COND_CLIENT_UNSEEN ); + } +} + +// try to smell something +void CTalkMonster :: TrySmellTalk( void ) +{ + if ( !FOkToSpeak() ) + return; + + // clear smell bits periodically + if ( gpGlobals->time > m_flLastSaidSmelled ) + { +// ALERT ( at_aiconsole, "Clear smell bits\n" ); + ClearBits(m_bitsSaid, bit_saidSmelled); + } + // smelled something? + if (!FBitSet(m_bitsSaid, bit_saidSmelled) && HasConditions ( bits_COND_SMELL )) + { + PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_flLastSaidSmelled = gpGlobals->time + 60;// don't talk about the stinky for a while. + SetBits(m_bitsSaid, bit_saidSmelled); + } +} + + + +int CTalkMonster::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return R_HT; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CTalkMonster::StopFollowing( BOOL clearSchedule ) +{ + if ( IsFollowing() ) + { + if ( !(m_afMemory & bits_MEMORY_PROVOKED) ) + { + PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + } + + if ( m_movementGoal == MOVEGOAL_TARGETENT ) + RouteClear(); // Stop him from walking toward the player + m_hTargetEnt = NULL; + if ( clearSchedule ) + ClearSchedule(); + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } +} + + +void CTalkMonster::StartFollowing( CBaseEntity *pLeader ) +{ + if ( m_pCine ) + m_pCine->CancelScript(); + + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + + m_hTargetEnt = pLeader; + PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + ClearConditions( bits_COND_CLIENT_PUSH ); + ClearSchedule(); +} + +//LRC- redefined, now returns true if following would be physically possible +BOOL CTalkMonster::CanFollow( void ) +{ + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + if ( !m_pCine->CanInterrupt() ) + return FALSE; + } + + if ( !IsAlive() ) + return FALSE; + + return TRUE; +} + + +//LRC- rewritten +void CTalkMonster :: FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( m_useTime > gpGlobals->time ) + return; + + //ALERT(at_console,"Talkmonster was Used: "); + + // CanFollow is now true if the monster could physically follow anyone + if ( pCaller != NULL && pCaller->IsPlayer() && CanFollow() ) + { + if ( !IsFollowing() ) + { + // Pre-disaster followers can't be used unless they've got a master to override their behaviour... + if (IsLockedByMaster() || (pev->spawnflags & SF_MONSTER_PREDISASTER && !m_sMaster)) + { + //ALERT(at_console,"Decline\n"); + DeclineFollowing(); + } + else + { + LimitFollowers( pCaller , 1 ); + if ( m_afMemory & bits_MEMORY_PROVOKED ) + { + //ALERT(at_console,"Fail\n"); + ALERT( at_aiconsole, "I'm not following you, you evil person!\n" ); + } + else + { + //ALERT(at_console,"Start\n"); + StartFollowing( pCaller ); + SetBits(m_bitsSaid, bit_saidHelloPlayer); // Don't say hi after you've started following + } + } + } + else + { + //ALERT(at_console,"Stop\n"); + StopFollowing( TRUE ); + } + } +} + +void CTalkMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "UseSentence")) + { + m_iszUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "UnUseSentence")) + { + m_iszUnUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "RefusalSentence")) //LRC + { + m_iszDecline = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "SpeakAs")) //LRC + { + m_iszSpeakAs = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CTalkMonster::Precache( void ) +{ + if ( m_iszUse ) + m_szGrp[TLK_USE] = STRING( m_iszUse ); + if ( m_iszUnUse ) + m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); + if ( m_iszDecline ) //LRC + m_szGrp[TLK_DECLINE] = STRING( m_iszDecline ); +} + diff --git a/server/monsters/talkmonster.h b/server/monsters/talkmonster.h new file mode 100644 index 00000000..a6736868 --- /dev/null +++ b/server/monsters/talkmonster.h @@ -0,0 +1,186 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +#ifndef MONSTERS_H +#include "monsters.h" +#endif + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= + +#define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this + +#define TLK_STARE_DIST 128 // anyone closer than this and looking at me is probably staring at me. + +#define bit_saidDamageLight (1<<0) // bits so we don't repeat key sentences +#define bit_saidDamageMedium (1<<1) +#define bit_saidDamageHeavy (1<<2) +#define bit_saidHelloPlayer (1<<3) +#define bit_saidWoundLight (1<<4) +#define bit_saidWoundHeavy (1<<5) +#define bit_saidHeard (1<<6) +#define bit_saidSmelled (1<<7) + +#define TLK_CFRIENDS 3 + +typedef enum +{ + TLK_ANSWER = 0, + TLK_QUESTION, + TLK_IDLE, + TLK_STARE, + TLK_USE, + TLK_UNUSE, + TLK_DECLINE, //LRC- refuse to accompany + TLK_STOP, + TLK_NOSHOOT, + TLK_HELLO, + TLK_PHELLO, + TLK_PIDLE, + TLK_PQUESTION, + TLK_PLHURT1, + TLK_PLHURT2, + TLK_PLHURT3, + TLK_SMELL, + TLK_WOUND, + TLK_MORTAL, + + TLK_CGROUPS, // MUST be last entry +} TALKGROUPNAMES; + + +enum +{ + SCHED_CANT_FOLLOW = LAST_COMMON_SCHEDULE + 1, + SCHED_MOVE_AWAY, // Try to get out of the player's way + SCHED_MOVE_AWAY_FOLLOW, // same, but follow afterward + SCHED_MOVE_AWAY_FAIL, // Turn back toward player + + LAST_TALKMONSTER_SCHEDULE, // MUST be last +}; + +enum +{ + TASK_CANT_FOLLOW = LAST_COMMON_TASK + 1, + TASK_MOVE_AWAY_PATH, + TASK_WALK_PATH_FOR_UNITS, + + TASK_TLK_RESPOND, // say my response + TASK_TLK_SPEAK, // question or remark + TASK_TLK_HELLO, // Try to say hello to player + TASK_TLK_HEADRESET, // reset head position + TASK_TLK_STOPSHOOTING, // tell player to stop shooting friend + TASK_TLK_STARE, // let the player know I know he's staring at me. + TASK_TLK_LOOK_AT_CLIENT,// faces player if not moving and not talking and in idle. + TASK_TLK_CLIENT_STARE, // same as look at client, but says something if the player stares. + TASK_TLK_EYECONTACT, // maintain eyecontact with person who I'm talking to + TASK_TLK_IDEALYAW, // set ideal yaw to face who I'm talking to + TASK_FACE_PLAYER, // Face the player + + LAST_TALKMONSTER_TASK, // MUST be last +}; + +class CTalkMonster : public CBaseMonster +{ +public: + void TalkInit( void ); + CBaseEntity *FindNearestFriend(BOOL fPlayer); + float TargetDistance( void ); + void StopTalking( void ) { SentenceStop(); } + + // Base Monster functions + void Precache( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Touch( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int CanPlaySentence( BOOL fDisregardState ); + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + void KeyValue( KeyValueData *pkvd ); + + // AI functions + void SetActivity ( Activity newActivity ); + Schedule_t *GetScheduleOfType ( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void PrescheduleThink( void ); + + + // Conversations / communication + int GetVoicePitch( void ); + void IdleRespond( void ); + int FIdleSpeak( void ); + int FIdleStare( void ); + int FIdleHello( void ); + void IdleHeadTurn( Vector &vecFriend ); + int FOkToSpeak( void ); + void TrySmellTalk( void ); + CBaseEntity *EnumFriends( CBaseEntity *pentPrevious, int listNumber, BOOL bTrace ); + void AlertFriends( void ); + void ShutUpFriends( void ); + BOOL IsTalking( void ); + void Talk( float flDuration ); + // For following + BOOL CanFollow( void ); + BOOL IsFollowing( void ) { return m_hTargetEnt != NULL && m_hTargetEnt->IsPlayer(); } + void StopFollowing( BOOL clearSchedule ); + void StartFollowing( CBaseEntity *pLeader ); + virtual void DeclineFollowing( void ) {} + void LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ); + + void EXPORT FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + + static char *m_szFriends[TLK_CFRIENDS]; // array of friend names + static float g_talkWaitTime; + + int m_bitsSaid; // set bits for sentences we don't want repeated + int m_nSpeak; // number of times initiated talking + int m_voicePitch; // pitch of voice for this head + const char *m_szGrp[TLK_CGROUPS]; // sentence group names + float m_useTime; // Don't allow +USE until this time + int m_iszUse; // Custom +USE sentence group (follow) + int m_iszUnUse; // Custom +USE sentence group (stop following) + int m_iszDecline; // Custom +USE sentence group (refuse to follow) LRC + int m_iszSpeakAs; // Change the prefix for all this monster's speeches LRC + + float m_flLastSaidSmelled;// last time we talked about something that stinks + float m_flStopTalkTime;// when in the future that I'll be done saying this sentence. + + EHANDLE m_hTalkTarget; // who to look at while talking + CUSTOM_SCHEDULES; +}; + + +// Clients can push talkmonsters out of their way +#define bits_COND_CLIENT_PUSH ( bits_COND_SPECIAL1 ) +// Don't see a client right now. +#define bits_COND_CLIENT_UNSEEN ( bits_COND_SPECIAL2 ) + + +#endif //TALKMONSTER_H diff --git a/server/monsters/turret.cpp b/server/monsters/turret.cpp new file mode 100644 index 00000000..7b896906 --- /dev/null +++ b/server/monsters/turret.cpp @@ -0,0 +1,1336 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "client.h" +#include "monsters.h" +#include "baseweapon.h" +#include "basebeams.h" +#include "defaults.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CBaseTurret : public CBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + virtual STATE getState() { if (m_iOn) { return STATE_ON; } else { return STATE_OFF; } } + + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +TYPEDESCRIPTION CBaseTurret::m_SaveData[] = +{ + DEFINE_FIELD( CBaseTurret, m_flMaxSpin, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSpin, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CBaseTurret, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iDeployHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iRetractHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iMinPitch, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fTurnRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iOrientation, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iOn, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fBeserk, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iAutoStart, FIELD_INTEGER ), + + + DEFINE_FIELD( CBaseTurret, m_vecLastSight, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flMaxWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSearchSpeed, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_flStartYaw, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_vecCurAngles, FIELD_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_vecGoalAngles, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseTurret, m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flSpinUpTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBaseTurret, CBaseMonster ); + +class CTurret : public CBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + +private: + int m_iStartSpin; + +}; +TYPEDESCRIPTION CTurret::m_SaveData[] = +{ + DEFINE_FIELD( CTurret, m_iStartSpin, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTurret, CBaseTurret ); + + +class CMiniTurret : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); +}; + + +LINK_ENTITY_TO_CLASS( monster_turret, CTurret ); +LINK_ENTITY_TO_CLASS( monster_miniturret, CMiniTurret ); + +void CBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CBaseTurret::Spawn() +{ + Precache( ); + SetNextThink( 1 ); + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse(&CBaseTurret:: TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + if (m_iOrientation == 1) + { + pev->ideal_pitch = 180; + pev->angles.x = 180; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CTurret::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/turret.mdl"); + if (!pev->health) + pev->health = TURRET_HEALTH; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(&CTurret::Initialize); + + m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetParent( this, 2 ); + m_eyeBrightness = 0; + + SetNextThink( 0.3 ); +} + +void CTurret::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMiniTurret::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + if (!pev->health) + pev->health = MINITURRET_HEALTH; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(&CMiniTurret::Initialize); + SetNextThink( 0.3 ); +} + + +void CMiniTurret::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { +// pev->ideal_pitch = 180; //This is moved to CBaseTurret::Spawn for fix old bug in original HL. G-Cont. +// pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::AutoSearchThink); + SetNextThink( 0.1 ); + } + else + SetThink( NULL ); +} + +void CBaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (useType == USE_TOGGLE) + { + if(m_iOn) useType = USE_OFF; + else useType = USE_ON; + } + if (useType == USE_ON) + { + SetNextThink( 0.1 ); // turn on delay + + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + m_iAutoStart = TRUE; + SetThink( Deploy ); + } + else if(useType == USE_OFF) + { + m_hEnemy = NULL; + SetNextThink( 0.1 ); + m_iAutoStart = FALSE;// switching off a turret disables autostart + SetThink( Retire ); + } +} + + +void CBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + SetNextThink( 0.1 ); + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + + // if it's dead, look for something new + if ( !m_hEnemy->IsAlive() ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, m_hEnemy->pev, vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enmey is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_9MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CBaseTurret::Deploy(void) +{ + SetNextThink( 0.1 ); + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + UTIL_FireTargets( pev->target, this, this, USE_ON ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(&CBaseTurret::SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + SetNextThink( 0.1 ); + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + UTIL_FireTargets( pev->target, this, this, USE_OFF ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(&CBaseTurret::AutoSearchThink); + SetNextThink( 0.1 ); + } + else + SetThink( NULL ); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + SetNextThink( 1.0 ); // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + SetNextThink( 0.1 ); // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(&CTurret::ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(&CTurret::ActiveThink); + } +} + + +void CTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + SetNextThink( 0.3 ); + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(&CBaseTurret::Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && !MoveTurret( ) && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + + + +void CBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CBaseTurret::TurretDeath); + UTIL_FireTargets( pev->target, this, this, USE_ON ); + SetNextThink( 0.1 ); + + return 0; + } + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(&CBaseTurret::SearchThink); + } + } + + return 1; +} + +int CBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CBaseTurret::Classify ( void ) +{ + if (m_iClass) return m_iClass; + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CSentry : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void EXPORT SentryTouch( CBaseEntity *pOther ); + void EXPORT SentryDeath( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_sentry, CSentry ); + +void CSentry::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CSentry::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/sentry.mdl"); + if (!pev->health) //LRC + pev->health = SENTRY_HEALTH; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + //m_flMaxWait = 1E6; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;//G-Cont. now sentry can deployed + m_flMaxSpin = 1E6; + + CBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + DROP_TO_FLOOR ( ENT(pev) ); + + SetTouch(&CSentry::SentryTouch); + SetThink(&CSentry::Initialize); + SetNextThink( 0.3 ); +} + +void CSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MP5, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + +int CSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink(&CSentry:: Deploy ); + SetUse( NULL ); + SetNextThink( 0.1 ); + } + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CSentry::SentryDeath); + UTIL_FireTargets( pev->target, this, this, USE_ON ); + SetNextThink( 0.1 ); + + return 0; + } + + return 1; +} + + +void CSentry::SentryTouch( CBaseEntity *pOther ) +{ + if ( pOther && (pOther->IsPlayer() || (pOther->pev->flags & FL_MONSTER)) ) + { + TakeDamage(pOther->pev, pOther->pev, 0, 0 ); + } +} + + +void CSentry :: SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, gmsg.TempEntity ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + diff --git a/server/monsters/zombie.cpp b/server/monsters/zombie.cpp new file mode 100644 index 00000000..9d2658f3 --- /dev/null +++ b/server/monsters/zombie.cpp @@ -0,0 +1,381 @@ +/*** +* +* 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. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Zombie +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "utils.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaults.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ZOMBIE_AE_ATTACK_RIGHT 0x01 +#define ZOMBIE_AE_ATTACK_LEFT 0x02 +#define ZOMBIE_AE_ATTACK_BOTH 0x03 +#define ZOMBIE_AE_CRAB1 0x04 +#define ZOMBIE_AE_CRAB2 0x05 +#define ZOMBIE_AE_CRAB3 0x06 + +#define ZOMBIE_FLINCH_DELAY 2 // at most one flinch every n secs +#define ZOMBIE_CRAB "monster_headcrab" // headcrab jumps from zombie + + +class CZombie : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + void SpawnCrab( void ); + + float m_flNextFlinch; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_zombie, CZombie ); + +const char *CZombie::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CZombie::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CZombie::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CZombie::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CZombie::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CZombie::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CZombie :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CZombie :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 120; +} + +int CZombie :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 30% damage from bullets + if ( bitsDamageType == DMG_BULLET ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + float flForce = DamageForce( flDamage ); + pev->velocity = pev->velocity + vecDir * flForce; + flDamage *= 0.3; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CZombie :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: AlertSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: IdleSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + +void CZombie :: AttackSound( void ) +{ + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CZombie :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ZOMBIE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, ZOMBIE_ONE_SLASH, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_LEFT: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, ZOMBIE_ONE_SLASH, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = 18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, ZOMBIE_BOTH_SLASH, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * -100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_CRAB1: + { + pev->body = 1; // set the head to a skull + SpawnCrab(); + } + break; + + case ZOMBIE_AE_CRAB2: + { + pev->body = 1; // set the head to a skull + SpawnCrab(); + } + break; + + case ZOMBIE_AE_CRAB3: + { + pev->body = 1; // set the head to a skull + SpawnCrab(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CZombie :: Spawn() +{ + Precache(); + + if (pev->model) UTIL_SetModel(ENT(pev), pev->model); + else + { + if(pev->button == 0) UTIL_SetModel(ENT(pev), "models/monsters/zombie_sci.mdl"); + else if(pev->button == 1) UTIL_SetModel(ENT(pev), "models/monsters/zombie_bar.mdl"); + } + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = ZOMBIE_HEALTH; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_DOORS_GROUP; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CZombie :: Precache() +{ + int i; + + if (pev->model) UTIL_PrecacheModel(pev->model); //LRC + else + { + if(pev->button) UTIL_PrecacheModel("models/monsters/zombie_bar.mdl"); + else UTIL_PrecacheModel("models/monsters/zombie_sci.mdl"); + } + UTIL_PrecacheEntity( ZOMBIE_CRAB ); + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CZombie::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK1)) + { + if (m_flNextFlinch >= gpGlobals->time) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + } + + if ((m_Activity == ACT_SMALL_FLINCH) || (m_Activity == ACT_BIG_FLINCH)) + { + if (m_flNextFlinch < gpGlobals->time) + m_flNextFlinch = gpGlobals->time + ZOMBIE_FLINCH_DELAY; + } + + return iIgnore; + +} + +//========================================================= +// Spawn Headcrab - headcrab jumps from zombie +//========================================================= +void CZombie :: SpawnCrab( void ) +{ + vec3_t vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + CBaseEntity *pCrab = CBaseEntity::Create( ZOMBIE_CRAB, vecSrc, vecAng, edict() ); // create the crab + pCrab->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; // make the crab fall + pCrab->pev->velocity = pev->movedir * 30; + pCrab->pev->velocity.z += 30; +} diff --git a/todo.log b/todo.log index ffd6ba88..dba381b3 100644 --- a/todo.log +++ b/todo.log @@ -113,11 +113,13 @@ Beta 13.12.08 90. get rid of Com_ParseExt OK 91. implement rendermodes OK 92. implement VBO OK -93. implement sky rotate, sky shader, etc +93. implement sky rotate, sky shader, etc OK 94. implement studio format 95. support for custom tables (external) OK 96. implement sprite format OK -97. fix fog color in release build OK +97. fix fog in release build 98. fix crash in release build 99. rewrote RF_* flags OK -100. implement JpegLib ? +100. implement JpegLib OK +101. replace Matrix_ with Matrix3x3_ OK +102. replace Matrix4_ with Matrix4x4_ ? diff --git a/vprogs/pr_comp.c b/vprogs/pr_comp.c new file mode 100644 index 00000000..1e901ca3 --- /dev/null +++ b/vprogs/pr_comp.c @@ -0,0 +1,6199 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// pr_comp.c - progs compiler base +//======================================================================= + +#include "vprogs.h" + +#define TOP_PRIORITY 7 +#define NOT_PRIORITY 5 + +bool autoprototype; // take two passes over the source code. First time round doesn't enter and functions or initialise variables. +bool pr_subscopedlocals; // causes locals to be valid ONLY within thier statement block. (they simply can't be referenced by name outside of it) + +bool opt_laxcasts; // Allow lax casting. This'll produce loadsa warnings of course. But allows compilation of certain dodgy code. +bool opt_ifstring; // makes if (blah) equivelent to if (blah != "") which resolves some issues in multiprogs situations. +bool opt_overlaptemps; // reduce numpr_globals by reuse of temps. When they are not needed they are freed for reuse. The way this is implemented is better than frikqcc's. (This is the single most important optimisation) +bool opt_assignments; // STORE_F isn't used if an operation wrote to a temp. +bool opt_shortenifnots; // if(!var) is made an IF rather than NOT IFNOT +bool opt_noduplicatestrings; // brute force string check. time consuming but more effective than the equivelent in frikqcc. +bool opt_constantarithmatic; // 3*5 appears as 15 instead of the extra statement. +bool opt_nonvec_parms; // store_f instead of store_v on function calls, where possible. +bool opt_constant_names; // take out the defs and name strings of constants. +bool opt_constant_names_strings;//removes the defs of strings too. plays havok with multiprogs. +bool opt_precache_file; // remove the call, the parameters, everything. +bool opt_filenames; // strip filenames. hinders older decompilers. +bool opt_unreferenced; // strip defs that are not referenced. +bool opt_function_names; // strip out the names of builtin functions. +bool opt_locals; // strip out the names of locals and immediates. +bool opt_dupconstdefs; // float X = 5; and float Y = 5; occupy the same global with this. +bool opt_return_only; // RETURN; DONE; at the end of a function strips out the done statement if there is no way to get to it. +bool opt_compound_jumps; // jumps to jump statements jump to the final point. +bool opt_stripfunctions; // if a functions is only ever called directly or by exe, don't emit the def. +bool opt_locals_marshalling; // make the local vars of all functions occupy the same globals. +bool opt_logicops; // don't make conditions enter functions if the return value will be discarded due to a previous value. (C style if statements) +bool opt_vectorcalls; // vectors can be packed into 3 floats, which can yield lower numpr_globals, but cost two more statements per call (only works for q1 calling conventions). +bool opt_simplifiedifs; // if (f != 0) -> if (f). if (f == 0) -> ifnot (f) +bool opt_writelinenums; // write debug info version 7 (line of numbers) +bool opt_writetypes; // write debug info version 7 (types) +bool opt_writesources; // write compressed sources into progs.dat for easy decompile and extended debug +bool opt_compstrings; // compress all strings into produced file +bool opt_compfunctions; // compress all functions and statements +bool opt_compress_other; // compress all parts of progs + +bool simplestore; +bool pr_setevarsname = false; +file_t *asmfile; +pr_info_t pr; +freeoffset_t *freeofs; +int conditional; +int basictypefield[ev_union + 1]; +char *progs_src; +char *saved_progs_src; +char pevname[32]; // support custom "self" pointer names +char opevname[32]; // class self name +char sourcefilename[MAX_SYSPATH]; + +char *basictypenames[] = +{ + "void", + "string", + "float", + "vector", + "entity", + "field", + "function", + "pointer", + "integer", + "struct", + "union", + "bool", +}; + +//======================================== + +def_t *pr_scope; // the function being parsed, or NULL +type_t *pr_classtype; +string_t s_file, s_file2; // filename for function definition +uint locals_start; // for tracking local variables vs temps +uint locals_end; // for tracking local variables vs temps +jmp_buf pr_parse_abort; // longjump with this on parse error +jmp_buf pr_int_error; // longjump with internal error +void PR_ParseDefs (char *classname); +bool qcc_usefulstatement; +int max_breaks; +int max_continues; +int max_cases; +int num_continues; +int num_breaks; +int num_cases; +int *pr_breaks; +int *pr_continues; +int *pr_cases; +def_t **pr_casesdef; +def_t **pr_casesdef2; + +typedef struct +{ + int statementno; + int lineno; + char name[256]; +} gotooperator_t; + +int max_labels; +int max_gotos; +gotooperator_t *pr_labels; +gotooperator_t *pr_gotos; +int num_gotos; +int num_labels; +temp_t *functemps; // floats/strings/funcs/ents... +def_t *extra_parms[MAX_PARMS_EXTRA]; +//======================================== + +// FIXME: modifiy list so most common GROUPS are first +// use look up table for value of first char and sort by first char and most common...? + +// if true, effectivly { b = a; return a; } +opcode_t pr_opcodes[] = +{ +{"", "DONE", -1, ASSOC_LEFT, &type_void, &type_void, &type_void}, +{"*", "MUL_F", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"*", "MUL_V", 3, ASSOC_LEFT, &type_vector, &type_vector, &type_float}, +{"*", "MUL_FV", 3, ASSOC_LEFT, &type_float, &type_vector, &type_vector}, +{"*", "MUL_VF", 3, ASSOC_LEFT, &type_vector, &type_float, &type_vector}, +{"/", "DIV_F", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"+", "ADD_F", 4, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"+", "ADD_V", 4, ASSOC_LEFT, &type_vector, &type_vector, &type_vector}, +{"-", "SUB_F", 4, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"-", "SUB_V", 4, ASSOC_LEFT, &type_vector, &type_vector, &type_vector}, +{"==", "EQ_F", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"==", "EQ_V", 5, ASSOC_LEFT, &type_vector, &type_vector, &type_float}, +{"==", "EQ_S", 5, ASSOC_LEFT, &type_string, &type_string, &type_float}, +{"==", "EQ_E", 5, ASSOC_LEFT, &type_entity, &type_entity, &type_float}, +{"==", "EQ_FNC", 5, ASSOC_LEFT, &type_function, &type_function, &type_float}, +{"!=", "NE_F", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"!=", "NE_V", 5, ASSOC_LEFT, &type_vector, &type_vector, &type_float}, +{"!=", "NE_S", 5, ASSOC_LEFT, &type_string, &type_string, &type_float}, +{"!=", "NE_E", 5, ASSOC_LEFT, &type_entity, &type_entity, &type_float}, +{"!=", "NE_FNC", 5, ASSOC_LEFT, &type_function, &type_function, &type_float}, +{"<=", "LE", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{">=", "GE", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<", "LT", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{">", "GT", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{".", "INDIRECT_F", 1, ASSOC_LEFT, &type_entity, &type_field, &type_float}, +{".", "INDIRECT_V", 1, ASSOC_LEFT, &type_entity, &type_field, &type_vector}, +{".", "INDIRECT_S", 1, ASSOC_LEFT, &type_entity, &type_field, &type_string}, +{".", "INDIRECT_E", 1, ASSOC_LEFT, &type_entity, &type_field, &type_entity}, +{".", "INDIRECT_FI", 1, ASSOC_LEFT, &type_entity, &type_field, &type_field}, +{".", "INDIRECT_FU", 1, ASSOC_LEFT, &type_entity, &type_field, &type_function}, +{".", "ADDRESS", 1, ASSOC_LEFT, &type_entity, &type_field, &type_pointer}, +{"=", "STORE_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"=", "STORE_V", 6, ASSOC_RIGHT,&type_vector, &type_vector, &type_vector}, +{"=", "STORE_S", 6, ASSOC_RIGHT,&type_string, &type_string, &type_string}, +{"=", "STORE_ENT", 6, ASSOC_RIGHT,&type_entity, &type_entity, &type_entity}, +{"=", "STORE_FLD", 6, ASSOC_RIGHT,&type_field, &type_field, &type_field}, +{"=", "STORE_FNC", 6, ASSOC_RIGHT,&type_function, &type_function, &type_function}, +{"=", "STOREP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"=", "STOREP_V", 6, ASSOC_RIGHT,&type_pointer, &type_vector, &type_vector}, +{"=", "STOREP_S", 6, ASSOC_RIGHT,&type_pointer, &type_string, &type_string}, +{"=", "STOREP_ENT", 6, ASSOC_RIGHT,&type_pointer, &type_entity, &type_entity}, +{"=", "STOREP_FLD", 6, ASSOC_RIGHT,&type_pointer, &type_field, &type_field}, +{"=", "STOREP_FNC", 6, ASSOC_RIGHT,&type_pointer, &type_function, &type_function}, +{"", "RETURN", -1, ASSOC_LEFT, &type_float, &type_void, &type_void}, +{"!", "NOT_F", -1, ASSOC_LEFT, &type_float, &type_void, &type_float}, +{"!", "NOT_V", -1, ASSOC_LEFT, &type_vector, &type_void, &type_float}, +{"!", "NOT_S", -1, ASSOC_LEFT, &type_string, &type_void, &type_float}, +{"!", "NOT_ENT", -1, ASSOC_LEFT, &type_entity, &type_void, &type_float}, +{"!", "NOT_FNC", -1, ASSOC_LEFT, &type_function, &type_void, &type_float}, +{"~", "NOT_BITI", -1, ASSOC_LEFT, &type_integer, &type_void, &type_integer}, +{"~", "NOT_BITF", -1, ASSOC_LEFT, &type_float, &type_void, &type_float}, +{"", "IF", -1, ASSOC_RIGHT,&type_float, NULL, &type_void}, +{"", "IFNOT", -1, ASSOC_RIGHT,&type_float, NULL, &type_void}, +{"", "CALL0", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL1", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL2", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL3", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL4", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL5", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL6", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL7", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL8", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "CALL9", -1, ASSOC_LEFT, &type_function, &type_void, &type_void}, +{"", "STATE", -1, ASSOC_LEFT, &type_float, &type_float, &type_void}, +{"", "GOTO", -1, ASSOC_RIGHT,NULL, &type_void, &type_void}, +{"&&", "AND", 7, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"||", "OR", 7, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"&", "BITAND", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"|", "BITOR", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"*=", "MULSTORE_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"*=", "MULSTORE_V", 6, ASSOC_RIGHT,&type_vector, &type_float, &type_vector}, +{"*=", "MULSTOREP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"*=", "MULSTOREP_V", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_vector}, +{"/=", "DIVSTORE_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"/=", "DIVSTOREP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"+=", "ADDSTORE_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"+=", "ADDSTORE_V", 6, ASSOC_RIGHT,&type_vector, &type_vector, &type_vector}, +{"+=", "ADDSTOREP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"+=", "ADDSTOREP_V", 6, ASSOC_RIGHT,&type_pointer, &type_vector, &type_vector}, +{"-=", "SUBSTORE_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"-=", "SUBSTORE_V", 6, ASSOC_RIGHT,&type_vector, &type_vector, &type_vector}, +{"-=", "SUBSTOREP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"-=", "SUBSTOREP_V", 6, ASSOC_RIGHT,&type_pointer, &type_vector, &type_vector}, +{"", "FETCH_GBL_F", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"", "FETCH_GBL_V", -1, ASSOC_LEFT, &type_vector, &type_float, &type_vector}, +{"", "FETCH_GBL_S", -1, ASSOC_LEFT, &type_string, &type_float, &type_string}, +{"", "FETCH_GBL_E", -1, ASSOC_LEFT, &type_entity, &type_float, &type_entity}, +{"", "FETCH_G_FNC", -1, ASSOC_LEFT, &type_function, &type_float, &type_function}, +{"", "CSTATE", -1, ASSOC_LEFT, &type_float, &type_float, &type_void}, +{"", "CWSTATE", -1, ASSOC_LEFT, &type_float, &type_float, &type_void}, +{"", "THINKTIME", -1, ASSOC_LEFT, &type_entity, &type_float, &type_void}, +{"|=", "BITSET_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"|=", "BITSETP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"&=", "BITCLR_F", 6, ASSOC_RIGHT,&type_float, &type_float, &type_float}, +{"&=", "BITCLRP_F", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_float}, +{"", "RAND0", -1, ASSOC_LEFT, &type_void, &type_void, &type_float}, +{"", "RAND1", -1, ASSOC_LEFT, &type_float, &type_void, &type_float}, +{"", "RAND2", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"", "RANDV0", -1, ASSOC_LEFT, &type_void, &type_void, &type_vector}, +{"", "RANDV1", -1, ASSOC_LEFT, &type_vector, &type_void, &type_vector}, +{"", "RANDV2", -1, ASSOC_LEFT, &type_vector, &type_vector, &type_vector}, +{"", "SWITCH_F", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "SWITCH_V", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "SWITCH_S", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "SWITCH_E", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "SWITCH_FNC", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "CASE", -1, ASSOC_LEFT, &type_void, NULL, &type_void}, +{"", "CASERANGE", -1, ASSOC_LEFT, &type_void, &type_void, NULL}, +{"=", "STORE_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"=", "STORE_IF", 6, ASSOC_RIGHT,&type_integer, &type_float, &type_integer}, +{"=", "STORE_FI", 6, ASSOC_RIGHT,&type_float, &type_integer, &type_float}, +{"+", "ADD_I", 4, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"+", "ADD_FI", 4, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"+", "ADD_IF", 4, ASSOC_LEFT, &type_integer, &type_float, &type_float}, +{"-", "SUB_I", 4, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"-", "SUB_FI", 4, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"-", "SUB_IF", 4, ASSOC_LEFT, &type_integer, &type_float, &type_float}, +{"", "C_ITOF", -1, ASSOC_LEFT, &type_integer, &type_void, &type_float}, +{"", "C_FTOI", -1, ASSOC_LEFT, &type_float, &type_void, &type_integer}, +{"", "CP_ITOF", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_float}, +{"", "CP_FTOI", -1, ASSOC_LEFT, &type_pointer, &type_float, &type_integer}, +{".", "INDIRECT", 1, ASSOC_LEFT, &type_entity, &type_field, &type_integer}, +{"=", "STOREP_I", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_integer}, +{"=", "STOREP_IF", 6, ASSOC_RIGHT,&type_pointer, &type_float, &type_integer}, +{"=", "STOREP_FI", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_float}, +{"&", "BITAND_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"|", "BITOR_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"*", "MUL_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"/", "DIV_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"==", "EQ_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"!=", "NE_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"", "IFNOTS", -1, ASSOC_RIGHT,&type_string, NULL, &type_void}, +{"", "IFS", -1, ASSOC_RIGHT,&type_string, NULL, &type_void}, +{"!", "NOT_I", -1, ASSOC_LEFT, &type_integer, &type_void, &type_integer}, +{"/", "DIV_VF", 3, ASSOC_LEFT, &type_vector, &type_float, &type_float}, +{"^", "POWER_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{">>", "RSHIFT_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"<<", "LSHIFT_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{">>", "RSHIFT_F", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<<", "LSHIFT_F", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"%", "MODULO_I", 3, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"%", "MODULO_F", 3, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"", "GET_POINTER", -1, ASSOC_LEFT, &type_float, &type_integer, &type_pointer}, +{"", "ARRAY_OFS", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_pointer}, +{"=", "LOADA_F", 6, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"=", "LOADA_V", 6, ASSOC_LEFT, &type_vector, &type_integer, &type_vector}, +{"=", "LOADA_S", 6, ASSOC_LEFT, &type_string, &type_integer, &type_string}, +{"=", "LOADA_ENT", 6, ASSOC_LEFT, &type_entity, &type_integer, &type_entity}, +{"=", "LOADA_FLD", 6, ASSOC_LEFT, &type_field, &type_integer, &type_field}, +{"=", "LOADA_FNC", 6, ASSOC_LEFT, &type_function, &type_integer, &type_function}, +{"=", "LOADA_I", 6, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"=", "STORE_P", 6, ASSOC_RIGHT,&type_pointer, &type_pointer, &type_void}, +{".", "INDIRECT_P", 1, ASSOC_LEFT, &type_entity, &type_field, &type_pointer}, +{"=", "LOADP_F", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_float}, +{"=", "LOADP_V", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_vector}, +{"=", "LOADP_S", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_string}, +{"=", "LOADP_ENT", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_entity}, +{"=", "LOADP_FLD", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_field}, +{"=", "LOADP_FNC", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_function}, +{"=", "LOADP_I", 6, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer}, +{"<=", "LE_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{">=", "GE_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"<", "LT_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{">", "GT_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"<=", "LE_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{">=", "GE_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"<", "LT_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{">", "GT_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"<=", "LE_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_integer}, +{">=", "GE_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_integer}, +{"<", "LT_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_integer}, +{">", "GT_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_integer}, +{"==", "EQ_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"==", "EQ_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"+", "ADD_SF", 4, ASSOC_LEFT, &type_string, &type_float, &type_string}, +{"-", "SUB_S", 4, ASSOC_LEFT, &type_string, &type_string, &type_float}, +{"", "STOREP_C", 1, ASSOC_RIGHT,&type_string, &type_float, &type_float}, +{"", "LOADP_C", 1, ASSOC_LEFT, &type_string, &type_void, &type_float}, +{"*", "MUL_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"*", "MUL_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"*", "MUL_VI", 5, ASSOC_LEFT, &type_vector, &type_integer, &type_vector}, +{"*", "MUL_IV", 5, ASSOC_LEFT, &type_integer, &type_vector, &type_vector}, +{"/", "DIV_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"/", "DIV_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"&", "BITAND_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"|", "BITOR_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"&", "BITAND_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"|", "BITOR_FI", 5, ASSOC_LEFT, &type_float, &type_integer, &type_float}, +{"&&", "AND_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"||", "OR_I", 5, ASSOC_LEFT, &type_integer, &type_integer, &type_integer}, +{"&&", "AND_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"||", "OR_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"&&", "AND_FI", 5, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"||", "OR_FI", 5, ASSOC_LEFT, &type_float, &type_float, &type_integer}, +{"!=", "NE_IF", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"!=", "NE_FI", 5, ASSOC_LEFT, &type_integer, &type_float, &type_integer}, +{"<>", "GSTOREP_I", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTOREP_F", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTOREP_ENT", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTOREP_FLD", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTOREP_S", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTORE_PFNC", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GSTOREP_V", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GADDRESS", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"<>", "GLOAD_I", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_integer}, +{"<>", "GLOAD_F", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_float}, +{"<>", "GLOAD_FLD", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_field}, +{"<>", "GLOAD_ENT", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_entity}, +{"<>", "GLOAD_S", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_string}, +{"<>", "GLOAD_FNC", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_function}, +{"<>", "GLOAD_V", -1, ASSOC_LEFT, &type_pointer, &type_integer, &type_vector}, +{"<>", "BOUNDCHECK", -1, ASSOC_LEFT, &type_float, &type_float, &type_float}, +{"=", "STOREP_P", 6, ASSOC_RIGHT,&type_pointer, &type_pointer, &type_void}, +{"", "PUSH", -1, ASSOC_RIGHT,&type_float, &type_void, &type_pointer}, +{"", "POP", -1, ASSOC_RIGHT,&type_float, &type_void, &type_void}, +{"", "POP", -1, ASSOC_RIGHT,&type_float, &type_void, &type_void}, +{"|=", "BITSET_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"|=", "BITSETP_I", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_integer}, +{"*=", "MULSTORE_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"*=", "MULSTOREP_I", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_vector}, +{"/=", "DIVSTORE_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"/=", "DIVSTOREP_I", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_vector}, +{"+=", "ADDSTORE_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"+=", "ADDSTOREP_I", 6, ASSOC_RIGHT,&type_pointer, &type_integer, &type_integer}, +{"-=", "SUBSTORE_I", 6, ASSOC_RIGHT,&type_integer, &type_integer, &type_integer}, +{"-=", "SUBSTOREP_I", 6, ASSOC_RIGHT,&type_pointer, &type_vector, &type_vector}, +{NULL, NULL, -1, ASSOC_LEFT, NULL, NULL, NULL} +}; + +keyword_t pr_keywords[] = +{ +{KEYWORD_DO, "do", "", 0 }, +{KEYWORD_IF, "if", "", 0 }, +{KEYWORD_VOID, "void", "", 0 }, +{KEYWORD_ELSE, "else", "", 0 }, +{KEYWORD_LOCAL, "local", "", 0 }, // FIXME: get rid of this +{KEYWORD_WHILE, "while", "", 0 }, +{KEYWORD_ENTITY, "entity", "", 0 }, +{KEYWORD_FLOAT, "float", "", 0 }, +{KEYWORD_STRING, "string", "", 0 }, +{KEYWORD_VECTOR, "vector", "", 0 }, +{KEYWORD_RETURN, "return", "", 0 }, +{KEYWORD_BREAK, "break", "", ev_function}, +{KEYWORD_FOR, "for", "", 0 }, +{KEYWORD_CONTINUE, "continue", "", 0 }, +{KEYWORD_CONST, "const", "", 0 }, +{KEYWORD_ENUM, "enum", "", 0 }, +{KEYWORD_ENUMFLAGS, "enumflags", "", 0 }, +{KEYWORD_CASE, "case", "", 0 }, +{KEYWORD_DEFAULT, "default", "", 0 }, +{KEYWORD_GOTO, "goto", "", 0 }, +{KEYWORD_INT, "int", "integer",0 }, +{KEYWORD_STATE, "state", "", ev_field},// intrinsic +{KEYWORD_CLASS, "class", "", 0 }, +{KEYWORD_STRUCT, "struct", "", 0 }, +{KEYWORD_SWITCH, "switch", "", 0 }, +{KEYWORD_TYPEDEF, "typedef", "", 0 }, +{KEYWORD_EXTERN, "extern", "", 0 }, +{KEYWORD_UNION, "union", "", 0 }, +{KEYWORD_THINKTIME, "thinktime", "", 0 }, // intrinsic +{KEYWORD_BOOL, "bool", "BOOL", 0 }, +{KEYWORD_ASM, "asm", "_asm", 0 }, +{KEYWORD_SHARED, "shared", "_export",0 }, // rename to ? +{KEYWORD_NOSAVE, "nosave", "", 0 }, // rename to ? +{NUM_KEYWORDS, "", "", 0 }, +}; + +// this system cuts out 10/120 +// these evaluate as top first. +opcode_t *opcodeprioritized[TOP_PRIORITY+1][64] = +{ + { // don't use + NULL + }, + { + // 1 + &pr_opcodes[OP_LOAD_F], + &pr_opcodes[OP_LOAD_V], + &pr_opcodes[OP_LOAD_S], + &pr_opcodes[OP_LOAD_ENT], + &pr_opcodes[OP_LOAD_FLD], + &pr_opcodes[OP_LOAD_FNC], + &pr_opcodes[OP_LOAD_I], + &pr_opcodes[OP_LOAD_P], + &pr_opcodes[OP_ADDRESS], + NULL + }, + { + // 2 + NULL + }, + { + // 3 + &pr_opcodes[OP_MUL_F], + &pr_opcodes[OP_MUL_V], + &pr_opcodes[OP_MUL_FV], + &pr_opcodes[OP_MUL_VF], + &pr_opcodes[OP_MUL_I], + &pr_opcodes[OP_DIV_F], + &pr_opcodes[OP_DIV_I], + &pr_opcodes[OP_DIV_VF], + &pr_opcodes[OP_BITAND], + &pr_opcodes[OP_BITAND_I], + &pr_opcodes[OP_BITOR], + &pr_opcodes[OP_BITOR_I], + &pr_opcodes[OP_POWER_I], + &pr_opcodes[OP_RSHIFT_I], + &pr_opcodes[OP_LSHIFT_I], + &pr_opcodes[OP_RSHIFT_F], + &pr_opcodes[OP_LSHIFT_F], + &pr_opcodes[OP_MODULO_I], + &pr_opcodes[OP_MODULO_F], + NULL + }, + { + // 4 + &pr_opcodes[OP_ADD_F], + &pr_opcodes[OP_ADD_V], + &pr_opcodes[OP_ADD_I], + &pr_opcodes[OP_ADD_FI], + &pr_opcodes[OP_ADD_IF], + &pr_opcodes[OP_ADD_SF], + &pr_opcodes[OP_SUB_F], + &pr_opcodes[OP_SUB_V], + &pr_opcodes[OP_SUB_I], + &pr_opcodes[OP_SUB_FI], + &pr_opcodes[OP_SUB_IF], + &pr_opcodes[OP_SUB_S], + NULL + }, + { + // 5 + &pr_opcodes[OP_EQ_F], + &pr_opcodes[OP_EQ_V], + &pr_opcodes[OP_EQ_S], + &pr_opcodes[OP_EQ_E], + &pr_opcodes[OP_EQ_FNC], + &pr_opcodes[OP_EQ_I], + &pr_opcodes[OP_EQ_IF], + &pr_opcodes[OP_EQ_FI], + &pr_opcodes[OP_NE_F], + &pr_opcodes[OP_NE_V], + &pr_opcodes[OP_NE_S], + &pr_opcodes[OP_NE_E], + &pr_opcodes[OP_NE_FNC], + &pr_opcodes[OP_NE_I], + &pr_opcodes[OP_LE], + &pr_opcodes[OP_LE_I], + &pr_opcodes[OP_LE_IF], + &pr_opcodes[OP_LE_FI], + &pr_opcodes[OP_GE], + &pr_opcodes[OP_GE_I], + &pr_opcodes[OP_GE_IF], + &pr_opcodes[OP_GE_FI], + &pr_opcodes[OP_LT], + &pr_opcodes[OP_LT_I], + &pr_opcodes[OP_LT_IF], + &pr_opcodes[OP_LT_FI], + &pr_opcodes[OP_GT], + &pr_opcodes[OP_GT_I], + &pr_opcodes[OP_GT_IF], + &pr_opcodes[OP_GT_FI], + NULL + }, + { + // 6 + &pr_opcodes[OP_STORE_F], + &pr_opcodes[OP_STORE_V], + &pr_opcodes[OP_STORE_S], + &pr_opcodes[OP_STORE_ENT], + &pr_opcodes[OP_STORE_FLD], + &pr_opcodes[OP_STORE_FNC], + &pr_opcodes[OP_STORE_I], + &pr_opcodes[OP_STORE_IF], + &pr_opcodes[OP_STORE_FI], + &pr_opcodes[OP_STORE_P], + &pr_opcodes[OP_STOREP_F], + &pr_opcodes[OP_STOREP_V], + &pr_opcodes[OP_STOREP_S], + &pr_opcodes[OP_STOREP_ENT], + &pr_opcodes[OP_STOREP_FLD], + &pr_opcodes[OP_STOREP_FNC], + &pr_opcodes[OP_STOREP_I], + &pr_opcodes[OP_STOREP_IF], + &pr_opcodes[OP_STOREP_FI], + &pr_opcodes[OP_STOREP_P], + &pr_opcodes[OP_DIVSTORE_F], + &pr_opcodes[OP_DIVSTOREP_F], + &pr_opcodes[OP_MULSTORE_F], + &pr_opcodes[OP_MULSTORE_V], + &pr_opcodes[OP_MULSTOREP_F], + &pr_opcodes[OP_MULSTOREP_V], + &pr_opcodes[OP_ADDSTORE_F], + &pr_opcodes[OP_ADDSTORE_V], + &pr_opcodes[OP_ADDSTOREP_F], + &pr_opcodes[OP_ADDSTOREP_V], + &pr_opcodes[OP_SUBSTORE_F], + &pr_opcodes[OP_SUBSTORE_V], + &pr_opcodes[OP_SUBSTOREP_F], + &pr_opcodes[OP_SUBSTOREP_V], + &pr_opcodes[OP_BITSET], + &pr_opcodes[OP_BITSETP], + &pr_opcodes[OP_BITCLR], + &pr_opcodes[OP_BITCLRP], + NULL + }, + { + // 7 + &pr_opcodes[OP_AND], + &pr_opcodes[OP_OR], + NULL + } +}; + +char *PR_NameFromType( etype_t type ) +{ + static char typeinfo[32]; + + Mem_Set( typeinfo, 0, 32 ); + switch( type ) + { + case ev_float: com.strncpy(typeinfo, "float", 32 ); break; + case ev_string: com.strncpy(typeinfo, "string", 32 ); break; + case ev_vector: com.strncpy(typeinfo, "vector", 32 ); break; + case ev_entity: com.strncpy(typeinfo, "entity", 32 ); break; + case ev_integer: com.strncpy(typeinfo, "int", 32 ); break; + case ev_struct: com.strncpy(typeinfo, "struct", 32 ); break; + case ev_union: com.strncpy(typeinfo, "union", 32 ); break; + case ev_void: com.strncpy(typeinfo, "void", 32 ); break; + } + return typeinfo; +} + +void PR_KeyWordValid( char *name, type_t *type ) +{ + int i; + char *typeinfo; + + switch( type->type ) + { + default: + case ev_float: + case ev_string: + case ev_vector: + case ev_entity: + case ev_integer: + case ev_struct: + case ev_union: + typeinfo = PR_NameFromType( type->type ); + break; + case ev_field: + typeinfo = PR_NameFromType( type->aux_type->type ); + break; + } + for(i = 0; i < NUM_KEYWORDS; i++ ) + { + // resereved keyword valid only for new versions + if(pr_keywords[i].ignoretype == type->type) continue; + if(!STRCMP(name, pr_keywords[i].name)) + { + // MSVC 6.0 style + PR_ParseWarning( ERR_ILLEGALNAME, "'%s' followed by '%s' is illegal", typeinfo, name ); + PR_ParseWarning( WARN_IGNOREDONLEFT, "' ' : ignored on left of '%s ' when no variable is declared", typeinfo ); + break; + } + } +} + +void PR_GetEntvarsName( void ) +{ + def_t *d; + + // for emit class constructor we need before + // get name of global entvars pointer (default "pev") + + if( pr_setevarsname ) return; + + for( d = pr.def_head.next; d; d = d->next ) + { + if( !com.strcmp (d->name, "end_sys_globals" )) break; + if( d->ofs < RESERVED_OFS ) continue; + if( d->type->type == ev_entity ) + { + com.strncpy( pevname, d->name, 32 ); + com.strncpy( opevname, va( "o%s", pevname ), 32 ); + pr_setevarsname = true; + break; + } + } +} + +//=========================================================================== +/* +============ +PR_Statement + +Emits a primitive statement, returning the var it places it's value in +============ +*/ + +_inline int PR_ShouldConvert(def_t *var, etype_t wanted) +{ + if (var->type->type == ev_integer && wanted == ev_function) + return 0; + if (var->type->type == ev_pointer && var->type->aux_type) + { + if (var->type->aux_type->type == ev_float && wanted == ev_integer) + return OP_CP_FTOI; + + if (var->type->aux_type->type == ev_integer && wanted == ev_float) + return OP_CP_ITOF; + } + else + { + if (var->type->type == ev_float && wanted == ev_integer) + return OP_CONV_FTOI; + + if (var->type->type == ev_integer && wanted == ev_float) + return OP_CONV_ITOF; + } + return -1; +} + +def_t *PR_SupplyConversion(def_t *var, etype_t wanted) +{ + int o; + + if (pr_classtype && var->type->type == ev_field && wanted != ev_field) + { + if (pr_classtype) + { + // load pevname.var into a temp + def_t *pev; + pev = PR_GetDef(type_entity, pevname, NULL, true, 1); + switch(wanted) + { + case ev_float: + return PR_Statement(pr_opcodes+OP_LOAD_F, pev, var, NULL); + case ev_string: + return PR_Statement(pr_opcodes+OP_LOAD_S, pev, var, NULL); + case ev_function: + return PR_Statement(pr_opcodes+OP_LOAD_FNC, pev, var, NULL); + case ev_vector: + return PR_Statement(pr_opcodes+OP_LOAD_V, pev, var, NULL); + case ev_entity: + return PR_Statement(pr_opcodes+OP_LOAD_ENT, pev, var, NULL); + default: + PR_ParseError(ERR_INTERNAL, "Inexplicit field load failed, try explicit"); + } + } + } + + o = PR_ShouldConvert(var, wanted); + + if (o <= 0) return var;// no conversion + return PR_Statement(&pr_opcodes[o], var, NULL, NULL); //conversion return value +} + +// assistant functions. This can safly be bipassed with the old method for more complex things. +gofs_t PR_GetFreeOffsetSpace(uint size) +{ + int ofs; + if (opt_locals_marshalling) + { + freeoffset_t *fofs, *prev; + for (fofs = freeofs, prev = NULL; fofs; fofs=fofs->next) + { + if (fofs->size == size) + { + if (prev) prev->next = fofs->next; + else freeofs = fofs->next; + return fofs->ofs; + } + prev = fofs; + } + for (fofs = freeofs, prev = NULL; fofs; fofs=fofs->next) + { + if (fofs->size > size) + { + fofs->size -= size; + fofs->ofs += size; + return fofs->ofs-size; + } + prev = fofs; + } + } + + ofs = numpr_globals; + numpr_globals += size; + + if (numpr_globals >= MAX_REGS) + { + if (!opt_overlaptemps || !opt_locals_marshalling) + PR_ParseError(ERR_INTERNAL, "numpr_globals exceeded MAX_REGS - you'll need to use more optimisations"); + else PR_ParseError(ERR_INTERNAL, "numpr_globals exceeded MAX_REGS"); + } + + return ofs; +} + +void PR_FreeOffset(gofs_t ofs, uint size) +{ + freeoffset_t *fofs; + if (ofs + size == numpr_globals) + { + // FIXME: is this a bug? + numpr_globals -= size; + return; + } + + for (fofs = freeofs; fofs; fofs = fofs->next) + { + // FIXME: if this means the last block becomes free, free them all. + if (fofs->ofs == ofs + size) + { + fofs->ofs -= size; + fofs->size += size; + return; + } + if (fofs->ofs+fofs->size == ofs) + { + fofs->size += size; + return; + } + } + + fofs = Qalloc(sizeof(freeoffset_t)); + fofs->next = freeofs; + fofs->ofs = ofs; + fofs->size = size; + + freeofs = fofs; + return; +} + +static def_t *PR_GetTemp(type_t *type) +{ + def_t *var_c; + temp_t *t; + + var_c = (void *)Qalloc(sizeof(def_t)); + var_c->type = type; + var_c->name = "temp"; + + // don't exceed. This lets us allocate a huge block, and still be able to compile smegging big funcs. + if (opt_overlaptemps) + { + for (t = functemps; t; t = t->next) + { + if (!t->used && t->size == type->size) + break; + } + if (t && t->scope && t->scope != pr_scope) + PR_ParseError(ERR_INTERNAL, "temp has scope not equal to current scope"); + + if (!t) + { + // allocate a new one + t = Qalloc(sizeof(temp_t)); + t->size = type->size; + t->next = functemps; + functemps = t; + + t->ofs = PR_GetFreeOffsetSpace(t->size); + numtemps += t->size; + } + + // use a previous one. + var_c->ofs = t->ofs; + var_c->temp = t; + t->lastfunc = pr_scope; + } + else if (opt_locals_marshalling) + { + // allocate a new one + t = Qalloc(sizeof(temp_t)); + t->size = type->size; + + t->next = functemps; + functemps = t; + + t->ofs = PR_GetFreeOffsetSpace(t->size); + + numtemps += t->size; + + var_c->ofs = t->ofs; + var_c->temp = t; + t->lastfunc = pr_scope; + } + else + { + // we're not going to reallocate any temps so allocate permanently + var_c->ofs = PR_GetFreeOffsetSpace(type->size); + numtemps+=type->size; + } + + var_c->s_file = s_file; + var_c->s_line = pr_source_line; + + if (var_c->temp) var_c->temp->used = true; + + return var_c; +} + +// nothing else references this temp. +static void PR_FreeTemp(def_t *t) +{ + if (t && t->temp) t->temp->used = false; +} + +static void PR_UnFreeTemp(def_t *t) +{ + if (t->temp) t->temp->used = true; +} + +// We've just parsed a statement. +// We can gaurentee that any used temps are now not used. +#ifdef _DEBUG +static void PR_FreeTemps( void ) +{ + temp_t *t; + + t = functemps; + while(t) + { + if (t->used && !pr_error_count) // don't print this after an error jump out. + { + PR_ParseWarning(WARN_DEBUGGING, "Temp was used in %s", pr_scope->name); + t->used = false; + } + t = t->next; + } +} +#else +#define PR_FreeTemps() +#endif + +// temps that are still in use over a function call can be considered dodgy. +// we need to remap these to locally defined temps, on return from the function so we know we got them all. +static void PR_LockActiveTemps(void) +{ + temp_t *t; + + t = functemps; + while(t) + { + if (t->used) t->scope = pr_scope; + t = t->next; + } + +} + +static void PR_RemapLockedTemp(temp_t *t, int firststatement, int laststatement) +{ + char buffer[128]; + + def_t *def; + int newofs = 0; + dstatement_t *st; + int i; + + for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) + { + if (pr_opcodes[st->op].type_a && st->a == t->ofs) + { + if (!newofs) + { + newofs = PR_GetFreeOffsetSpace(t->size); + numtemps+=t->size; + + def = PR_DummyDef(type_float, NULL, pr_scope, t->size, newofs, false); + def->nextlocal = pr.localvars; + def->constant = false; + com.sprintf(buffer, "locked_%i", t->ofs); + def->name = copystring( buffer ); + pr.localvars = def; + } + st->a = newofs; + } + if (pr_opcodes[st->op].type_b && st->b == t->ofs) + { + if (!newofs) + { + newofs = PR_GetFreeOffsetSpace(t->size); + numtemps+=t->size; + + def = PR_DummyDef(type_float, NULL, pr_scope, t->size, newofs, false); + def->nextlocal = pr.localvars; + def->constant = false; + com.sprintf(buffer, "locked_%i", t->ofs); + def->name = copystring( buffer ); + pr.localvars = def; + } + st->b = newofs; + } + if (pr_opcodes[st->op].type_c && st->c == t->ofs) + { + if (!newofs) + { + newofs = PR_GetFreeOffsetSpace(t->size); + numtemps+=t->size; + + def = PR_DummyDef(type_float, NULL, pr_scope, t->size, newofs, false); + def->nextlocal = pr.localvars; + def->constant = false; + com.sprintf(buffer, "locked_%i", t->ofs); + def->name = copystring( buffer ); + pr.localvars = def; + } + st->c = newofs; + } + } +} + +static void PR_RemapLockedTemps(int firststatement, int laststatement) +{ + temp_t *t; + + t = functemps; + while(t) + { + if (t->scope || opt_locals_marshalling) + { + PR_RemapLockedTemp(t, firststatement, laststatement); + t->scope = NULL; + t->lastfunc = NULL; + } + t = t->next; + } +} + +static void PR_fprintfLocals(file_t *f, gofs_t paramstart, gofs_t paramend) +{ + def_t *var; + temp_t *t; + int i; + + for (var = pr.localvars; var; var = var->nextlocal) + { + if (var->ofs >= paramstart && var->ofs < paramend) + continue; + FS_Printf(f, "local %s %s;\n", TypeName(var->type), var->name); + } + + for (t = functemps, i = 0; t; t = t->next, i++) + { + if (t->lastfunc == pr_scope) + { + FS_Printf(f, "local %s temp_%i;\n", (t->size == 1)?"float":"vector", i); + } + } +} + +static const char *PR_VarAtOffset(uint ofs, uint size) +{ + static char message[1024]; + def_t *var; + temp_t *t; + int i; + + for (t = functemps, i = 0; t; t = t->next, i++) + { + if (ofs >= t->ofs && ofs < t->ofs + t->size) + { + if (size < t->size) com.sprintf(message, "temp_%i_%c", i, 'x' + (ofs-t->ofs)%3); + else com.sprintf(message, "temp_%i", i); + return message; + } + } + + for (var = pr.localvars; var; var = var->nextlocal) + { + if (var->scope && var->scope != pr_scope) + continue; // this should be an error + if (ofs >= var->ofs && ofs < var->ofs + var->type->size) + { + if (*var->name) + { + // continue, don't get bogged down by multiple bits of code + if (!STRCMP(var->name, "IMMEDIATE")) continue; + if (size < var->type->size) com.sprintf(message, "%s_%c", var->name, 'x' + (ofs-var->ofs)%3); + else com.sprintf(message, "%s", var->name); + return message; + } + } + } + + for (var = pr.def_head.next; var; var = var->next) + { + if (var->scope && var->scope != pr_scope) + continue; + + if (ofs >= var->ofs && ofs < var->ofs + var->type->size) + { + if (*var->name) + { + if (!STRCMP(var->name, "IMMEDIATE")) + { + switch(var->type->type) + { + case ev_string: + com.sprintf(message, "\"%.1020s\"", &strings[((int *)pr_globals)[var->ofs]]); + return message; + case ev_integer: + com.sprintf(message, "%i", ((int *)pr_globals)[var->ofs]); + return message; + case ev_float: + com.sprintf(message, "%f", pr_globals[var->ofs]); + return message; + case ev_vector: + com.sprintf(message, "'%f %f %f'", pr_globals[var->ofs], pr_globals[var->ofs+1], pr_globals[var->ofs+2]); + return message; + default: + com.sprintf(message, "IMMEDIATE"); + return message; + } + } + if (size < var->type->size) com.sprintf(message, "%s_%c", var->name, 'x' + (ofs-var->ofs)%3); + else com.sprintf(message, "%s", var->name); + return message; + } + } + } + + if (size >= 3) + { + if (ofs >= OFS_RETURN && ofs < OFS_PARM0) com.sprintf(message, "return"); + else if (ofs >= OFS_PARM0 && ofs < RESERVED_OFS) com.sprintf(message, "parm%i", (ofs-OFS_PARM0)/3); + else com.sprintf(message, "offset_%i", ofs); + } + else + { + if (ofs >= OFS_RETURN && ofs < OFS_PARM0) com.sprintf(message, "return_%c", 'x' + ofs-OFS_RETURN); + else if (ofs >= OFS_PARM0 && ofs < RESERVED_OFS) com.sprintf(message, "parm%i_%c", (ofs-OFS_PARM0)/3, 'x' + (ofs-OFS_PARM0)%3); + else com.sprintf(message, "offset_%i", ofs); + } + return message; +} + +def_t *PR_Statement ( opcode_t *op, def_t *var_a, def_t *var_b, dstatement_t **outstatement) +{ + dstatement_t *statement; + def_t *var_c = NULL, *temp = NULL; + + if (outstatement == (dstatement_t **)0xffffffff) outstatement = NULL; + else if (op->priority != -1) + { + if (op->associative != ASSOC_LEFT) + { + if (op->type_a == &type_pointer) var_b = PR_SupplyConversion(var_b, (*op->type_b)->type); + else var_b = PR_SupplyConversion(var_b, (*op->type_a)->type); + } + else + { + if (var_a) var_a = PR_SupplyConversion(var_a, (*op->type_a)->type); + if (var_b) var_b = PR_SupplyConversion(var_b, (*op->type_b)->type); + } + } + + if (var_a) + { + var_a->references++; + PR_FreeTemp(var_a); + } + if (var_b) + { + var_b->references++; + PR_FreeTemp(var_b); + } + + if (PR_KeywordEnabled(KEYWORD_CLASS) && var_a && var_b) + { + if (var_a->type->type == ev_entity && var_b->type->type == ev_entity) + { + if (var_a->type != var_b->type) + { + if (com.strcmp(var_a->type->name, var_b->type->name)) + PR_ParseWarning(0, "Inexplict cast"); + } + } + } + + // maths operators + if (opt_constantarithmatic && (var_a && var_a->constant) && (var_b && var_b->constant)) + { + switch (op - pr_opcodes)// improve some of the maths. + { + case OP_BITOR: + return PR_MakeFloatDef((float)((int)G_FLOAT(var_a->ofs) | (int)G_FLOAT(var_b->ofs))); + case OP_BITAND: + return PR_MakeFloatDef((float)((int)G_FLOAT(var_a->ofs) & (int)G_FLOAT(var_b->ofs))); + case OP_MUL_F: + return PR_MakeFloatDef(G_FLOAT(var_a->ofs) * G_FLOAT(var_b->ofs)); + case OP_DIV_F: + return PR_MakeFloatDef(G_FLOAT(var_a->ofs) / G_FLOAT(var_b->ofs)); + case OP_ADD_F: + return PR_MakeFloatDef(G_FLOAT(var_a->ofs) + G_FLOAT(var_b->ofs)); + case OP_SUB_F: + return PR_MakeFloatDef(G_FLOAT(var_a->ofs) - G_FLOAT(var_b->ofs)); + case OP_BITOR_I: + return PR_MakeIntDef(G_INT(var_a->ofs) | G_INT(var_b->ofs)); + case OP_BITAND_I: + return PR_MakeIntDef(G_INT(var_a->ofs) & G_INT(var_b->ofs)); + case OP_MUL_I: + return PR_MakeIntDef(G_INT(var_a->ofs) * G_INT(var_b->ofs)); + case OP_DIV_I: + return PR_MakeIntDef(G_INT(var_a->ofs) / G_INT(var_b->ofs)); + case OP_ADD_I: + return PR_MakeIntDef(G_INT(var_a->ofs) + G_INT(var_b->ofs)); + case OP_SUB_I: + return PR_MakeIntDef(G_INT(var_a->ofs) - G_INT(var_b->ofs)); + } + } + + switch (op - pr_opcodes) + { + case OP_AND: + if (var_a->ofs == var_b->ofs) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Parameter offsets for && are the same"); + if (var_a->constant || var_b->constant) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); + break; + case OP_OR: + if (var_a->ofs == var_b->ofs) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Parameters for || are the same"); + if (var_a->constant || var_b->constant) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); + break; + case OP_EQ_F: + case OP_EQ_S: + case OP_EQ_E: + case OP_EQ_FNC: + case OP_EQ_V: + + case OP_NE_F: + case OP_NE_V: + case OP_NE_S: + case OP_NE_E: + case OP_NE_FNC: + + case OP_LE: + case OP_GE: + case OP_LT: + case OP_GT: + if ((var_a->constant && var_b->constant && !var_a->temp && !var_b->temp) || var_a->ofs == var_b->ofs) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); + break; + case OP_IFS: + case OP_IFNOTS: + case OP_IF: + case OP_IFNOT: + if (var_a->constant && !var_a->temp) + PR_ParseWarning(WARN_CONSTANTCOMPARISON, "Result of comparison is constant"); + break; + default: + break; + } + + if (numstatements) + { + // optimise based on last statement. + if (op - pr_opcodes == OP_IFNOT) + { + if (opt_shortenifnots && var_a && (statements[numstatements-1].op == OP_NOT_F || statements[numstatements-1].op == OP_NOT_FNC || statements[numstatements-1].op == OP_NOT_ENT)) + { + if (statements[numstatements-1].c == var_a->ofs) + { + static def_t nvara; + op = &pr_opcodes[OP_IF]; + numstatements--; + PR_FreeTemp(var_a); + Mem_Copy(&nvara, var_a, sizeof(nvara)); + nvara.ofs = statements[numstatements].a; + var_a = &nvara; + } + } + } + else if (op - pr_opcodes == OP_IFNOTS) + { + if (opt_shortenifnots && var_a && statements[numstatements-1].op == OP_NOT_S) + { + if (statements[numstatements-1].c == var_a->ofs) + { + static def_t nvara; + op = &pr_opcodes[OP_IFS]; + numstatements--; + PR_FreeTemp(var_a); + Mem_Copy(&nvara, var_a, sizeof(nvara)); + nvara.ofs = statements[numstatements].a; + var_a = &nvara; + } + } + } + else if (((uint) ((op - pr_opcodes) - OP_STORE_F) < 6)) + { + if (opt_assignments && var_a && var_a->ofs == statements[numstatements-1].c) + { + if (var_a->type->type == var_b->type->type) + { + if (var_a->temp) + { + statement = &statements[numstatements-1]; + statement->c = var_b->ofs; + + if (var_a->type->type != var_b->type->type) + PR_ParseWarning(0, "store type mismatch"); + var_b->references++; + var_a->references--; + PR_FreeTemp(var_a); + simplestore = true; + + PR_UnFreeTemp(var_b); + return var_b; + } + } + } + } + } + + simplestore = false; + statement = &statements[numstatements]; + numstatements++; + + if( outstatement ) *outstatement = statement; + statement_linenums[statement-statements] = pr_source_line; + statement->op = op - pr_opcodes; + statement->a = var_a ? var_a->ofs : 0; + statement->b = var_b ? var_b->ofs : 0; + if (var_c != NULL) + { + statement->c = var_c->ofs; + } + else if (op->type_c == &type_void || op->associative == ASSOC_RIGHT || op->type_c == NULL) + { + var_c = NULL; + statement->c = 0; // ifs, gotos, and assignments don't need vars allocated + } + else + { // allocate result space + var_c = PR_GetTemp(*op->type_c); + statement->c = var_c->ofs; + if( op->type_b == &type_field ) + { + var_c->name = var_b->name; + var_c->s_file = var_b->s_file; + var_c->s_line = var_b->s_line; + } + } + + if ((op - pr_opcodes >= OP_LOAD_F && op - pr_opcodes <= OP_LOAD_FNC) || op - pr_opcodes == OP_LOAD_I) + { + if (var_b->constant == 2) var_c->constant = true; + } + + if (!var_c) + { + if (var_a) PR_UnFreeTemp(var_a); + return var_a; + } + return var_c; +} + +/* +============ +PR_SimpleStatement + +Emits a primitive statement, returning the var it places it's value in +============ +*/ +dstatement_t *PR_SimpleStatement( int op, int var_a, int var_b, int var_c, int force ) +{ + dstatement_t *statement; + + statement_linenums[numstatements] = pr_source_line; + statement = &statements[numstatements]; + + numstatements++; + statement->op = op; + statement->a = var_a; + statement->b = var_b; + statement->c = var_c; + return statement; +} + +void PR_Statement3( opcode_t *op, def_t *var_a, def_t *var_b, def_t *var_c, int force) +{ + dstatement_t *statement; + + statement = &statements[numstatements]; + numstatements++; + + statement_linenums[statement-statements] = pr_source_line; + statement->op = op - pr_opcodes; + statement->a = var_a ? var_a->ofs : 0; + statement->b = var_b ? var_b->ofs : 0; + statement->c = var_c ? var_c->ofs : 0; +} + +/* +============ +PR_ParseImmediate + +Looks for a preexisting constant +============ +*/ +def_t *PR_ParseImmediate (void) +{ + def_t *cn; + + if (pr_immediate_type == type_float) + { + cn = PR_MakeFloatDef(pr_immediate._float); + PR_Lex(); + return cn; + } + if (pr_immediate_type == type_integer) + { + cn = PR_MakeIntDef(pr_immediate._int); + PR_Lex(); + return cn; + } + + if (pr_immediate_type == type_string) + { + cn = PR_MakeStringDef(pr_immediate_string); + PR_Lex(); + return cn; + } + + // check for a constant with the same value + for (cn = pr.def_head.next; cn; cn = cn->next)// FIXME - hashtable. + { + if (!cn->initialized) continue; + if (!cn->constant) continue; + if (cn->type != pr_immediate_type) continue; + if (pr_immediate_type == type_string) + { + if (!STRCMP(G_STRING(cn->ofs), pr_immediate_string) ) + { + PR_Lex(); + return cn; + } + } + else if (pr_immediate_type == type_float) + { + if ( G_FLOAT(cn->ofs) == pr_immediate._float ) + { + PR_Lex(); + return cn; + } + } + else if (pr_immediate_type == type_integer) + { + if ( G_INT(cn->ofs) == pr_immediate._int ) + { + PR_Lex (); + return cn; + } + } + else if (pr_immediate_type == type_vector) + { + if (( G_FLOAT(cn->ofs) == pr_immediate.vector[0] ) + && (G_FLOAT(cn->ofs+1) == pr_immediate.vector[1] ) + && (G_FLOAT(cn->ofs+2) == pr_immediate.vector[2])) + { + PR_Lex(); + return cn; + } + } + else PR_ParseError (ERR_BADIMMEDIATETYPE, "weird immediate type"); + } + + // allocate a new one + cn = (void *)Qalloc (sizeof(def_t)); + cn->next = NULL; + pr.def_tail->next = cn; + pr.def_tail = cn; + + cn->type = pr_immediate_type; + cn->name = "IMMEDIATE"; + cn->constant = true; + cn->initialized = 1; + cn->scope = NULL; // always share immediates + + // copy the immediate to the global area + cn->ofs = PR_GetFreeOffsetSpace(type_size[pr_immediate_type->type]); + + if (pr_immediate_type == type_string) + pr_immediate.string = PR_CopyString (pr_immediate_string, opt_noduplicatestrings ); + Mem_Copy(pr_globals + cn->ofs, &pr_immediate, 4*type_size[pr_immediate_type->type]); + PR_Lex (); + + return cn; +} + +/* +============ +PR_ParseFunctionCall + +warning, the func could have no name set if it's a field call. +============ +*/ +def_t *PR_ParseFunctionCall (def_t *func) +{ + def_t *e, *d, *old, *opev; + int arg = 0, i, np; + type_t *t, *p; + dstatement_t *st; + int extraparms = false; + int laststatement = numstatements; + def_t *param[MAX_PARMS+MAX_PARMS_EXTRA]; + + func->timescalled++; + t = func->type; + + if( t && t->type != ev_function ) + { + PR_ParseErrorPrintDef( ERR_NOTAFUNCTION, func, "not a function" ); + } + + if( !t->num_parms ) + { + // intrinsics. These base functions have variable arguments. I would check for (...) args too, + // but that might be used for extended builtin functionality. (this code wouldn't compile otherwise) + if( !com.strcmp( func->name, "spawn" )) + { + type_t *rettype; + if( PR_CheckToken( ")" )) + { + rettype = type_entity; + } + else + { + rettype = TypeForName( PR_ParseName()); + if( !rettype || rettype->type != ev_entity ) + PR_ParseError(ERR_NOTANAME, "spawn operator with undefined class" ); + + PR_Expect(")"); + } + + if( def_ret.temp->used ) + PR_ParseWarning( 0, "return value conflict - output is likly to be invalid" ); + def_ret.temp->used = true; + + if( rettype != type_entity ) + { + char genfunc[2048]; + + com.sprintf( genfunc, "Class*%s", rettype->name ); + func = PR_GetDef( type_function, genfunc, NULL, true, 1 ); + func->references++; + } + PR_SimpleStatement( OP_CALL0, func->ofs, 0, 0, false ); + def_ret.type = rettype; + return &def_ret; + } + else if( !com.strcmp( func->name, "entnum" ) && !PR_CheckToken( ")" )) + { + // t = (a/%1) / (nextent(world)/%1) + // a/%1 does a (int)entity to float conversion type thing + + e = PR_Expression(TOP_PRIORITY, false); + PR_Expect(")"); + e = PR_Statement(&pr_opcodes[OP_DIV_F], e, PR_MakeIntDef( 1 ), (dstatement_t **)0xffffffff); + + d = PR_GetDef( NULL, "nextent", NULL, false, 0 ); + if( !d ) PR_ParseError(0, "the nextent builtin is not defined"); + PR_FreeTemp( PR_Statement(&pr_opcodes[OP_STORE_F], e, &def_parms[0], (dstatement_t **)0xffffffff)); + d = PR_Statement(&pr_opcodes[OP_CALL0], d, NULL, NULL); + d = PR_Statement(&pr_opcodes[OP_DIV_F], d, PR_MakeIntDef(1), (dstatement_t **)0xffffffff); + + e = PR_Statement(&pr_opcodes[OP_DIV_F], e, d, (dstatement_t **)0xffffffff); + + return e; + } + } // so it's not an intrinsic. + + if( opt_precache_file ) + { + // should we strip out all precache_file calls? + if( !com.strncmp( func->name,"precache_file", 13 )) + { + if( pr_token_type == tt_immediate && pr_immediate_type->type == ev_string ) + { + PR_Lex(); + PR_Expect( ")" ); + def_ret.type = type_void; + return &def_ret; + } + } + } + PR_LockActiveTemps(); // any temps before are likely to be used with the return value. + + // any temps referenced to build the parameters don't need to be locked. + // copy the arguments to the global parameter variables + if( t->num_parms < 0 ) + { + extraparms = true; + np = (t->num_parms * -1) - 1; + } + else np = t->num_parms; + + if (opt_vectorcalls && (t->num_parms == 1 && t->param->type == ev_vector)) + { + // if we're using vectorcalls + // if it's a function, takes a vector + + // vectorcalls is an evil hack + // it'll make your mod bigger and less efficient. + // however, it'll cut down on numpr_globals, so your mod can become a much greater size. + vec3_t arg; + + if (pr_token_type == tt_immediate && pr_immediate_type == type_vector) + { + Mem_Copy(arg, pr_immediate.vector, sizeof(arg)); + while(*pr_file_p == ' ' || *pr_file_p == '\t' || *pr_file_p == '\n') pr_file_p++; + if (*pr_file_p == ')') + { + // woot + def_parms[0].ofs = OFS_PARM0 + 0; + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_F], PR_MakeFloatDef(arg[0]), &def_parms[0], (dstatement_t **)0xffffffff)); + def_parms[0].ofs = OFS_PARM0 + 1; + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_F], PR_MakeFloatDef(arg[1]), &def_parms[0], (dstatement_t **)0xffffffff)); + def_parms[0].ofs = OFS_PARM0 + 2; + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_F], PR_MakeFloatDef(arg[2]), &def_parms[0], (dstatement_t **)0xffffffff)); + def_parms[0].ofs = OFS_PARM0; + + PR_Lex(); + PR_Expect(")"); + } + else + { // bum + e = PR_Expression (TOP_PRIORITY, false); + if (e->type->type != ev_vector) + { + if (opt_laxcasts) + { + PR_ParseWarning(WARN_LAXCAST, "type mismatch on parm %i - (%s should be %s)", 1, TypeName(e->type), TypeName(type_vector)); + PR_ParsePrintDef(WARN_LAXCAST, func); + } + else PR_ParseErrorPrintDef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i - (%s should be %s)", 1, TypeName(e->type), TypeName(type_vector)); + } + PR_Expect(")"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_V], e, &def_parms[0], (dstatement_t **)0xffffffff)); + } + } + else + { // bother + e = PR_Expression (TOP_PRIORITY, false); + if (e->type->type != ev_vector) + { + if (opt_laxcasts) + { + PR_ParseWarning(WARN_LAXCAST, "type mismatch on parm %i - (%s should be %s)", 1, TypeName(e->type), TypeName(type_vector)); + PR_ParsePrintDef(WARN_LAXCAST, func); + } + else PR_ParseErrorPrintDef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i - (%s should be %s)", 1, TypeName(e->type), TypeName(type_vector)); + } + PR_Expect(")"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_V], e, &def_parms[0], (dstatement_t **)0xffffffff)); + } + } + else + { + if (!PR_CheckToken(")")) + { + p = t->param; + do + { + if (extraparms && arg >= MAX_PARMS) + PR_ParseErrorPrintDef (ERR_TOOMANYPARAMETERSVARARGS, func, "More than %i parameters on varargs function", MAX_PARMS); + else if (arg >= MAX_PARMS+MAX_PARMS_EXTRA) + PR_ParseErrorPrintDef (ERR_TOOMANYTOTALPARAMETERS, func, "More than %i parameters", MAX_PARMS+MAX_PARMS_EXTRA); + if (!extraparms && arg >= t->num_parms) + { + PR_ParseWarning (WARN_TOOMANYPARAMETERSFORFUNC, "too many parameters"); + PR_ParsePrintDef(WARN_TOOMANYPARAMETERSFORFUNC, func); + } + + e = PR_Expression (TOP_PRIORITY, false); + if (arg >= MAX_PARMS) + { + if (!extra_parms[arg - MAX_PARMS]) + { + d = (def_t *) Qalloc (sizeof(def_t)); + d->name = "extra parm"; + d->ofs = PR_GetFreeOffsetSpace (3); + extra_parms[arg - MAX_PARMS] = d; + } + d = extra_parms[arg - MAX_PARMS]; + } + else d = &def_parms[arg]; + + if (pr_classtype && e->type->type == ev_field && p->type != ev_field) + { + // convert. + opev = PR_GetDef(type_entity, pevname, NULL, true, 1); + switch(e->type->aux_type->type) + { + case ev_string: + e = PR_Statement(pr_opcodes+OP_LOAD_S, opev, e, NULL); + break; + case ev_integer: + e = PR_Statement(pr_opcodes+OP_LOAD_I, opev, e, NULL); + break; + case ev_float: + e = PR_Statement(pr_opcodes+OP_LOAD_F, opev, e, NULL); + break; + case ev_function: + e = PR_Statement(pr_opcodes+OP_LOAD_FNC, opev, e, NULL); + break; + case ev_vector: + e = PR_Statement(pr_opcodes+OP_LOAD_V, opev, e, NULL); + break; + case ev_entity: + e = PR_Statement(pr_opcodes+OP_LOAD_ENT, opev, e, NULL); + break; + default: + PR_ParseError(ERR_INTERNAL, "Bad member type. Try forced expansion"); + } + } + + if (p) + { + if (typecmp(e->type, p)) + { + if (p->type == ev_integer && e->type->type == ev_float) + { + // convert float -> int... is this a constant? + e = PR_Statement(pr_opcodes+OP_CONV_FTOI, e, NULL, NULL); + } + else if (p->type == ev_float && e->type->type == ev_integer) + { + // convert float -> int... is this a constant? + e = PR_Statement(pr_opcodes+OP_CONV_ITOF, e, NULL, NULL); + } + else if (p->type == ev_function && e->type->type == ev_integer && e->constant && !((int*)pr_globals)[e->ofs]) + { + // you're allowed to use int 0 to pass a null function pointer + // this is basically because __NULL__ is defined as ~0 (int 0) + } + else + { + // can cast to variant whatever happens + if (opt_laxcasts || (p->type == ev_function && e->type->type == ev_function)) + { + PR_ParseWarning(WARN_LAXCAST, "type mismatch on parm %i - (%s should be %s)", arg+1, TypeName(e->type), TypeName(p)); + PR_ParsePrintDef(WARN_LAXCAST, func); + } + else PR_ParseErrorPrintDef (ERR_TYPEMISMATCHPARM, func, "type mismatch on parm %i - (%s should be %s)", arg+1, TypeName(e->type), TypeName(p)); + } + } + d->type = p; + p = p->next; + } + else d->type = type_void; // a vector copy will copy everything + + param[arg] = e; + arg++; + } while (PR_CheckToken (",")); + + if (t->num_parms != -1 && arg < np) + PR_ParseWarning (WARN_TOOFEWPARAMS, "too few parameters on call to %s", func->name); + PR_Expect (")"); + } + else if (np) + { + PR_ParseWarning (WARN_TOOFEWPARAMS, "%s: Too few parameters", func->name); + PR_ParsePrintDef (WARN_TOOFEWPARAMS, func); + } + + for (i = 0; i < arg; i++) + { + if (i >= MAX_PARMS) d = extra_parms[i - MAX_PARMS]; + else d = &def_parms[i]; + + if (param[i]->type->size>1 || !opt_nonvec_parms) + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_V], param[i], d, (dstatement_t **)0xffffffff)); + else + { + d->type = param[i]->type; + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STORE_F], param[i], d, (dstatement_t **)0xffffffff)); + } + } + } + + if (def_ret.temp->used) + { + old = PR_GetTemp(def_ret.type); + if (def_ret.type->size == 3) PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_V], &def_ret, old, NULL)); + else PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_F], &def_ret, old, NULL)); + PR_UnFreeTemp(old); + PR_UnFreeTemp(&def_ret); + PR_ParseWarning(WARN_FIXEDRETURNVALUECONFLICT, "Return value conflict - output is inefficient"); + } + else old = NULL; + + if( com.strchr(func->name, ':') && laststatement && statements[laststatement-1].op == OP_LOAD_FNC && statements[laststatement-1].c == func->ofs) + { + // we're entering C++ code with a different pevname. + // FIXME: problems could occur with hexen2 calling conventions when parm0/1 is 'pev' or 'self' + // thiscall. copy the right ent into 'pev' or 'self' (if it's not the same offset) + + d = PR_GetDef(type_entity, pevname, NULL, true, 1); + if (statements[laststatement-1].a != d->ofs) + { + opev = PR_GetTemp(type_entity); + PR_SimpleStatement(OP_STORE_ENT, d->ofs, opev->ofs, 0, false); + PR_SimpleStatement(OP_STORE_ENT, statements[laststatement-1].a, d->ofs, 0, false); + } + else + { + opev = NULL; + d = NULL; + } + } + else + { + opev = NULL; + d = NULL; + } + + if (arg>MAX_PARMS) PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CALL1-1+MAX_PARMS], func, 0, (dstatement_t **)&st)); + else if (arg) PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CALL1-1+arg], func, 0, (dstatement_t **)&st)); + else PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CALL0], func, 0, (dstatement_t **)&st)); + if (opev) PR_SimpleStatement(OP_STORE_ENT, opev->ofs, d->ofs, 0, false); + + for( ; arg; arg-- ) + { + PR_FreeTemp( param[arg-1] ); + } + + if( old ) + { + d = PR_GetTemp(t->aux_type); + + if( t->aux_type->size == 3 ) PR_FreeTemp( PR_Statement( pr_opcodes+OP_STORE_V, &def_ret, d, NULL )); + else PR_FreeTemp( PR_Statement( pr_opcodes+OP_STORE_F, &def_ret, d, NULL )); + + if( def_ret.type->size == 3 ) PR_FreeTemp( PR_Statement(pr_opcodes+OP_STORE_V, old, &def_ret, NULL )); + else PR_FreeTemp(PR_Statement( pr_opcodes+OP_STORE_F, old, &def_ret, NULL )); + + PR_FreeTemp( old ); + PR_UnFreeTemp( &def_ret ); + PR_UnFreeTemp( d ); + + return d; + } + + def_ret.type = t->aux_type; + if( def_ret.temp->used ) + PR_ParseWarning( WARN_FIXEDRETURNVALUECONFLICT, "return value conflict - output is inefficient" ); + def_ret.temp->used = true; + + return &def_ret; +} + +def_t *PR_MakeIntDef(int value) +{ + def_t *cn; + + cn = Hash_GetKey(&intconstdefstable, value ); + if (cn) return cn; + + // allocate a new one + cn = (void *)Qalloc (sizeof(def_t)); + cn->next = NULL; + pr.def_tail->next = cn; + pr.def_tail = cn; + + cn->type = type_integer; + cn->name = "IMMEDIATE"; + cn->constant = true; + cn->initialized = 1; + cn->scope = NULL; // always share immediates + cn->arraysize = 1; + + // copy the immediate to the global area + cn->ofs = PR_GetFreeOffsetSpace (type_size[type_integer->type]); + Hash_AddKey(&intconstdefstable, value, cn, Qalloc(sizeof(bucket_t))); + G_INT(cn->ofs) = value; + + return cn; +} + +def_t *PR_MakeFloatDef(float value) +{ + def_t *cn; + union { float f; int i; } fi; + + fi.f = value; + + cn = Hash_GetKey(&floatconstdefstable, fi.i); + if (cn) return cn; + + // allocate a new one + cn = (void *)Qalloc(sizeof(def_t)); + cn->next = NULL; + pr.def_tail->next = cn; + pr.def_tail = cn; + + cn->type = type_float; + cn->name = "IMMEDIATE"; + cn->constant = true; + cn->initialized = 1; + cn->scope = NULL; // always share immediates + cn->arraysize = 1; + + // copy the immediate to the global area + cn->ofs = PR_GetFreeOffsetSpace (type_size[type_integer->type]); + Hash_AddKey(&floatconstdefstable, fi.i, cn, Qalloc(sizeof(bucket_t))); + G_FLOAT(cn->ofs) = value; + + return cn; +} + +def_t *PR_MakeStringDef(char *value) +{ + def_t *cn; + int string; + + cn = Hash_Get(&stringconstdefstable, value); + if (cn) return cn; + + // allocate a new one + cn = (void *)Qalloc (sizeof(def_t)); + cn->next = NULL; + pr.def_tail->next = cn; + pr.def_tail = cn; + + cn->type = type_string; + cn->name = "IMMEDIATE"; + cn->constant = true; + cn->initialized = 1; + cn->scope = NULL; // always share immediates + cn->arraysize = 1; + + // copy the immediate to the global area + cn->ofs = PR_GetFreeOffsetSpace (type_size[type_integer->type]); + string = PR_CopyString (value, opt_noduplicatestrings ); + Hash_Add(&stringconstdefstable, strings+string, cn, Qalloc(sizeof(bucket_t))); + G_INT(cn->ofs) = string; + + return cn; +} + +type_t *PR_PointerTypeTo(type_t *type) +{ + type_t *newtype; + newtype = PR_NewType("POINTER TYPE", ev_pointer); + newtype->aux_type = type; + return newtype; +} + +def_t *PR_MemberInParentClass(char *name, type_t *class) +{ + // if a member exists, return the member field (rather than mapped-to field) + type_t *mt; + def_t *def; + int p, np; + char membername[2048]; + + if (!class) + { + def = PR_GetDef(NULL, name, NULL, 0, 0); + if (def && def->type->type == ev_field) + return def; // the member existed as a normal entity field. + return NULL; + } + + np = class->num_parms; + for (p = 0, mt = class->param; p < np; p++, mt = mt->next) + { + if (com.strcmp(mt->name, name)) + continue; + + // the parent has it. + com.sprintf(membername, "%s::"MEMBERFIELDNAME, class->name, mt->name); + def = PR_GetDef(NULL, membername, NULL, false, 0); + return def; + } + return PR_MemberInParentClass(name, class->parentclass); +} + +/* +========================= +PR_EmitFieldsForMembers + +create fields for the types, instanciate the members to the fields. +we retouch the parents each time to guarentee polymorphism works. +FIXME: virtual methods will not work properly. Need to trace down to see if a parent already defined it +========================= +*/ +void PR_EmitFieldsForMembers(type_t *class) +{ + char membername[2048]; + int p, np, a; + uint o; + type_t *mt, *ft; + def_t *f, *m; + + // we created fields for each class when we defined the actual classes. + // we need to go through each member and match it to the offset of it's parent class, + // if overloaded, or create a new field if not.. + // basictypefield is cleared before we do this + // we emit the parent's fields first (every time), + // thus ensuring that we don't reuse parent fields on a child class. + + if (class->parentclass != type_entity) + { + // parents MUST have all thier fields set or inheritance would go crazy. + PR_EmitFieldsForMembers(class->parentclass); + } + + np = class->num_parms; + mt = class->param; + + for (p = 0; p < np; p++, mt = mt->next) + { + com.sprintf(membername, "%s::"MEMBERFIELDNAME, class->name, mt->name); + m = PR_GetDef(NULL, membername, NULL, false, 0); + + f = PR_MemberInParentClass(mt->name, class->parentclass); + if(f) + { + if (m->arraysize > 1) PR_ParseError(ERR_INTERNAL, "QCCLIB does not support overloaded arrays of members"); + a = 0; + for (o = 0; o < m->type->size; o++) + { + ((int *)pr_globals)[o+a*mt->size+m->ofs] = ((int *)pr_globals)[o+f->ofs]; + } + continue; + } + for (a = 0; a < m->arraysize; a++) + { + // we need the type in here so saved games can still work + // without saving ints as floats. (would be evil) + + ft = PR_NewType(basictypenames[mt->type], ev_field); + ft->aux_type = PR_NewType(basictypenames[mt->type], mt->type); + ft->aux_type->aux_type = type_void; + ft->size = ft->aux_type->size; + ft = PR_FindType(ft); + com.sprintf(membername, "__f_%s_%i", ft->name, ++basictypefield[mt->type]); + f = PR_GetDef(ft, membername, NULL, true, 1); + + for (o = 0; o < m->type->size; o++) + { + ((int *)pr_globals)[o+a*mt->size+m->ofs] = ((int *)pr_globals)[o+f->ofs]; + } + f->references++; + } + } +} + +/* +========================= +PR_EmitClassFunctionTable + +go through clas, do the virtual thing only if the child class does not override. +========================= +*/ +void PR_EmitClassFunctionTable(type_t *class, type_t *childclass, def_t *ed, def_t **constructor) +{ + char membername[2048]; + type_t *type, *oc; + def_t *point, *member, *virt; + int p; + + if (class->parentclass) + PR_EmitClassFunctionTable(class->parentclass, childclass, ed, constructor); + + type = class->param; + for (p = 0; p < class->num_parms; p++, type = type->next) + { + for (oc = childclass; oc != class; oc = oc->parentclass) + { + com.sprintf(membername, "%s::"MEMBERFIELDNAME, oc->name, type->name); + if (PR_GetDef(NULL, membername, NULL, false, 0)) + break; // a child class overrides. + } + if (oc != class) continue; + if (type->type == ev_function) + { + // FIXME: inheritance will not install all the member functions. + com.sprintf(membername, "%s::"MEMBERFIELDNAME, class->name, type->name); + member = PR_GetDef(NULL, membername, NULL, false, 1); + if (!member) + { + PR_Warning(0, NULL, 0, "Member function %s was not defined", membername); + continue; + } + if (!com.strcmp(type->name, class->name)) + { + *constructor = member; + } + point = PR_Statement(&pr_opcodes[OP_ADDRESS], ed, member, NULL); + com.sprintf(membername, "%s::%s", class->name, type->name); + virt = PR_GetDef(type, membername, NULL, false, 1); + PR_Statement(&pr_opcodes[OP_STOREP_FNC], virt, point, NULL); + } + } +} + +/* +========================= +PR_EmitClassFromFunction + +take all functions in the type, and parent types, and make sure the links all work properly. +========================= +*/ +void PR_EmitClassFromFunction(def_t *scope, char *tname) +{ + type_t *basetype; + dfunction_t *df; + def_t *virt; + def_t *ed, *opev, *pev; + def_t *constructor = NULL; + + basetype = TypeForName(tname); + if (!basetype) PR_ParseError(ERR_INTERNAL, "Type %s was not defined...", tname); + + pr_scope = NULL; + Mem_Set(basictypefield, 0, sizeof(basictypefield)); + PR_EmitFieldsForMembers(basetype); + + pr_scope = scope; + df = &functions[numfunctions]; + numfunctions++; + + df->s_file = 0; + df->s_name = 0; + df->first_statement = numstatements; + df->parm_size[0] = 1; + df->numparms = 0; + df->parm_start = numpr_globals; + + G_FUNCTION(scope->ofs) = df - functions; + + // locals here... + ed = PR_GetDef(type_entity, "ent", pr_scope, true, 1); + + virt = PR_GetDef(type_function, "spawn", NULL, false, 0); + if (!virt) PR_ParseError(ERR_INTERNAL, "spawn function was not defined\n"); + PR_SimpleStatement(OP_CALL0, virt->ofs, 0, 0, false); // calling convention doesn't come into it. + + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_ENT], &def_ret, ed, NULL)); + + ed->references = 1; //there may be no functions. + PR_EmitClassFunctionTable(basetype, basetype, ed, &constructor); + + if (constructor) + { + pev = PR_GetDef(type_entity, pevname, NULL, false, 0); + opev = PR_GetDef(type_entity, opevname, scope, true, 1); + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_ENT], pev, opev, NULL)); + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_ENT], ed, pev, NULL));// return to our old pev. boom boom. + PR_SimpleStatement(OP_CALL0, constructor->ofs, 0, 0, false); + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_STORE_ENT], opev, pev, NULL)); + } + // apparently we do actually have to return something. *sigh*... + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_RETURN], ed, NULL, NULL)); + PR_FreeTemp(PR_Statement(&pr_opcodes[OP_DONE], NULL, NULL, NULL)); + PR_WriteAsmFunction(scope, df->first_statement, df->parm_start); + locals_end = numpr_globals + basetype->size; + df->locals = locals_end - df->parm_start; + pr.localvars = NULL; +} +/* +============ +PR_ParseValue + +Returns the global ofs for the current token +============ +*/ +def_t *PR_ParseValue (type_t *assumeclass) +{ + def_t *ao = NULL;// arrayoffset + def_t *d, *nd, *od; + char *name, membername[2048]; + int i; + + // if the token is an immediate, allocate a constant for it + if (pr_token_type == tt_immediate) return PR_ParseImmediate(); + + if (PR_CheckToken("[")) // vector array acess + { + // looks like a funky vector. :) + vec3_t v; + + pr_immediate_type = type_vector; + v[0] = pr_immediate._float; + PR_Lex(); + v[1] = pr_immediate._float; + PR_Lex(); + v[2] = pr_immediate._float; + pr_immediate.vector[0] = v[0]; + pr_immediate.vector[1] = v[1]; + pr_immediate.vector[2] = v[2]; + pr_immediate_type = type_vector; + d = PR_ParseImmediate(); + PR_Expect("]"); + return d; + } + name = PR_ParseName (); + + if (assumeclass && assumeclass->parentclass) + { + // 'testvar' becomes 'pev::testvar' + type_t *type = assumeclass; + d = NULL; + + // try getting a member. + while(type != type_entity && type) + { + com.sprintf(membername, "%s::"MEMBERFIELDNAME, type->name, name); + od = d = PR_GetDef (NULL, membername, pr_scope, false, 0); + if (d) break; + type = type->parentclass; + } + if (!d) od = d = PR_GetDef (NULL, name, pr_scope, false, 0); + } + else od = d = PR_GetDef (NULL, name, pr_scope, false, 0); // look through the defs + + if (!d) + { + // intrinsics, any old function with no args will do. + if(( !com.strcmp( name, "spawn" )) || (!com.strcmp( name, "entnum" ))) + od = d = PR_GetDef( type_function, name, NULL, true, 1 ); + else if( PR_KeywordEnabled( KEYWORD_CLASS ) && !com.strcmp( name, "this" )) + { + if( !pr_classtype ) + PR_ParseError( ERR_NOTANAME, "cannot use 'this' outside of an OO function\n" ); + od = PR_GetDef( NULL, pevname, NULL, true, 1 ); + od = d = PR_DummyDef( pr_classtype, "this", pr_scope, 1, od->ofs, true ); + } + else if( PR_KeywordEnabled( KEYWORD_CLASS ) && !com.strcmp( name, "super" )) + { + if( !pr_classtype ) + PR_ParseError( ERR_NOTANAME, "cannot use 'super' outside of an OO function\n" ); + od = PR_GetDef( NULL, pevname, NULL, true, 1 ); + od = d = PR_DummyDef( pr_classtype, "super", pr_scope, 1, od->ofs, true ); + } + else PR_ParseError( ERR_UNKNOWNVALUE, "unknown value \"%s\"", name ); + } +reloop: + // FIXME: Make this work with double arrays/2nd level structures. + // Should they just jump back to here? + + if( PR_CheckToken( "[" )) + { + type_t *newtype; + if (ao) + { + numstatements--; // remove the last statement + nd = PR_Expression (TOP_PRIORITY, true); + PR_Expect("]"); + + if (d->type->size != 1) // we need to multiply it to find the offset. + { + if (ao->type->type == ev_integer) nd = PR_Statement(&pr_opcodes[OP_MUL_I], nd, PR_MakeIntDef(d->type->size), NULL); // get add part + else if (ao->type->type == ev_float) nd = PR_Statement(&pr_opcodes[OP_MUL_F], nd, PR_MakeFloatDef((float)d->type->size), NULL); // get add part + else + { + PR_ParseError(ERR_BADARRAYINDEXTYPE, "Array offset is not of integer or float type"); + nd = NULL; + } + } + if (nd->type->type == ao->type->type) + { + if (ao->type->type == ev_integer) ao = PR_Statement(&pr_opcodes[OP_ADD_I], ao, nd, NULL); // get add part + else if (ao->type->type == ev_float) ao = PR_Statement(&pr_opcodes[OP_ADD_F], ao, nd, NULL); // get add part + else + { + PR_ParseError(ERR_BADARRAYINDEXTYPE, "Array offset is not of integer or float type"); + nd = NULL; + } + } + else + { + if (nd->type->type == ev_float) nd = PR_Statement (&pr_opcodes[OP_CONV_FTOI], nd, 0, NULL); + ao = PR_Statement(&pr_opcodes[OP_ADD_I], ao, nd, NULL); // get add part + } + newtype = d->type; + d = od; + } + else + { + ao = PR_Expression (TOP_PRIORITY, true); + PR_Expect("]"); + + if( d->type->size != 1 ) // we need to multiply it to find the offset. + { + if (ao->type->type == ev_integer) ao = PR_Statement(&pr_opcodes[OP_MUL_I], ao, PR_MakeIntDef(d->type->size), NULL); // get add part + else if (ao->type->type == ev_float) ao = PR_Statement(&pr_opcodes[OP_MUL_F], ao, PR_MakeFloatDef((float)d->type->size), NULL); // get add part + else + { + nd = NULL; + PR_ParseError(ERR_BADARRAYINDEXTYPE, "Array offset is not of integer or float type"); + } + } + newtype = d->type; + } + if (ao->type->type == ev_integer) + { + switch(newtype->type) + { + case ev_float: + nd = PR_Statement(&pr_opcodes[OP_LOADA_F], d, ao, NULL); // get pointer to precise def. + break; + case ev_string: + if (d->arraysize <= 1) + { + nd = PR_Statement(&pr_opcodes[OP_LOADP_C], d, PR_Statement (&pr_opcodes[OP_CONV_ITOF], ao, 0, NULL), NULL); //get pointer to precise def. + newtype = nd->type; //don't be fooled + } + else nd = PR_Statement(&pr_opcodes[OP_LOADA_S], d, ao, NULL); // get pointer to precise def. + break; + case ev_vector: + nd = PR_Statement(&pr_opcodes[OP_LOADA_V], d, ao, NULL); // get pointer to precise def. + break; + case ev_entity: + nd = PR_Statement(&pr_opcodes[OP_LOADA_ENT], d, ao, NULL); // get pointer to precise def. + break; + case ev_field: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FLD], d, ao, NULL); // get pointer to precise def. + break; + case ev_function: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FNC], d, ao, NULL); // get pointer to precise def. + nd->type = d->type; + break; + case ev_integer: + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, ao, NULL);// get pointer to precise def. + break; + case ev_struct: + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, ao, NULL);// get pointer to precise def. + nd->type = d->type; + break; + default: + PR_ParseError(ERR_NOVALIDOPCODES, "No op available. Try assembler"); + nd = NULL; + break; + } + d = nd; + } + else if (ao->type->type == ev_float) + { + switch(newtype->type) + { + case ev_pointer: + if (d->arraysize>1) // use the array + { + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL); // get pointer to precise def. + nd->type = d->type->aux_type; + } + else + { + // dereference the pointer. + switch(newtype->aux_type->type) + { + case ev_pointer: + nd = PR_Statement(&pr_opcodes[OP_LOADP_I], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL); // get pointer to precise def. + nd->type = d->type->aux_type; + break; + case ev_float: + nd = PR_Statement(&pr_opcodes[OP_LOADP_F], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL); // get pointer to precise def. + nd->type = d->type->aux_type; + break; + case ev_integer: + nd = PR_Statement(&pr_opcodes[OP_LOADP_I], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL); // get pointer to precise def. + nd->type = d->type->aux_type; + break; + default: + PR_ParseError(ERR_NOVALIDOPCODES, "No op available. Try assembler"); + nd = NULL; + break; + } + } + break; + case ev_float: + nd = PR_Statement(&pr_opcodes[OP_LOADA_F], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_string: + if (d->arraysize <= 1) + { + nd = PR_Statement(&pr_opcodes[OP_LOADP_C], d, ao, NULL); //get pointer to precise def. + newtype = nd->type;//don't be fooled + } + else nd = PR_Statement(&pr_opcodes[OP_LOADA_S], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_vector: + nd = PR_Statement(&pr_opcodes[OP_LOADA_V], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_entity: + nd = PR_Statement(&pr_opcodes[OP_LOADA_ENT], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_field: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FLD], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_function: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FNC], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + nd->type = d->type; + break; + case ev_integer: + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + break; + case ev_struct: + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, PR_Statement (&pr_opcodes[OP_CONV_FTOI], ao, 0, NULL), NULL);// get pointer to precise def. + nd->type = d->type; + break; + default: + PR_ParseError(ERR_NOVALIDOPCODES, "No op available. Try assembler"); + nd = NULL; + break; + } + } + d = nd; + d->type = newtype; + goto reloop; + } + + i = d->type->type; + if (i == ev_pointer) + { + int j; + type_t *type; + if (PR_CheckToken(".") || PR_CheckToken("->")) + { + for (i = d->type->num_parms, type = d->type+1; i; i--, type++) + { + if (PR_CheckName(type->name)) + { + // give result + if (ao) + { + numstatements--; //remove the last statement + d = od; + + nd = PR_MakeIntDef(type->ofs); + ao = PR_Statement(&pr_opcodes[OP_ADD_I], ao, nd, NULL);// get add part + // so that we may offset it and readd it. + } + else ao = PR_MakeIntDef(type->ofs); + + switch (type->type) + { + case ev_float: + nd = PR_Statement(&pr_opcodes[OP_LOADP_F], d, ao, NULL); // get pointer to precise def. + break; + case ev_string: + nd = PR_Statement(&pr_opcodes[OP_LOADP_S], d, ao, NULL); // get pointer to precise def. + break; + case ev_vector: + nd = PR_Statement(&pr_opcodes[OP_LOADP_V], d, ao, NULL); // get pointer to precise def. + break; + case ev_entity: + nd = PR_Statement(&pr_opcodes[OP_LOADP_ENT], d, ao, NULL); // get pointer to precise def. + break; + case ev_field: + nd = PR_Statement(&pr_opcodes[OP_LOADP_FLD], d, ao, NULL); // get pointer to precise def. + break; + case ev_function: + nd = PR_Statement(&pr_opcodes[OP_LOADP_FNC], d, ao, NULL); // get pointer to precise def. + nd->type = type; + break; + case ev_integer: + nd = PR_Statement(&pr_opcodes[OP_LOADP_I], d, ao, NULL); // get pointer to precise def. + break; + default: + PR_ParseError(ERR_NOVALIDOPCODES, "No op available. Try assembler"); + nd = NULL; + break; + } + d = nd; + break; + } + if (type->num_parms) for (j = type->num_parms; j; j--) type++; + } + if (!i) PR_ParseError (ERR_MEMBERNOTVALID, "\"%s\" is not a member of \"%s\"", pr_token, od->type->name); + goto reloop; + } + } + else if (i == ev_struct || i == ev_union) + { + int j; + type_t *type; + + if (PR_CheckToken(".") || PR_CheckToken("->")) + { + for (i = d->type->num_parms, type = d->type+1; i; i--, type++) + { + if (PR_CheckName(type->name)) + { + // give result + if (ao) + { + numstatements--; // remove the last statement + d = od; + + nd = PR_MakeIntDef(type->ofs); + ao = PR_Statement(&pr_opcodes[OP_ADD_I], ao, nd, NULL); // get add part + + // so that we may offset it and readd it. + } + else ao = PR_MakeIntDef(type->ofs); + + switch (type->type) + { + case ev_float: + nd = PR_Statement(&pr_opcodes[OP_LOADA_F], d, ao, NULL); // get pointer to precise def. + break; + case ev_string: + nd = PR_Statement(&pr_opcodes[OP_LOADA_S], d, ao, NULL); // get pointer to precise def. + break; + case ev_vector: + nd = PR_Statement(&pr_opcodes[OP_LOADA_V], d, ao, NULL); // get pointer to precise def. + break; + case ev_entity: + nd = PR_Statement(&pr_opcodes[OP_LOADA_ENT], d, ao, NULL); // get pointer to precise def. + break; + case ev_field: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FLD], d, ao, NULL); // get pointer to precise def. + break; + case ev_function: + nd = PR_Statement(&pr_opcodes[OP_LOADA_FNC], d, ao, NULL); // get pointer to precise def. + nd->type = type; + break; + case ev_integer: + nd = PR_Statement(&pr_opcodes[OP_LOADA_I], d, ao, NULL); // get pointer to precise def. + break; + default: + PR_ParseError(ERR_NOVALIDOPCODES, "No op available. Try assembler"); + nd = NULL; + break; + } + d = nd; + break; + } + if (type->num_parms) for (j = type->num_parms; j;j--) type++; + } + if (!i) PR_ParseError (ERR_MEMBERNOTVALID, "\"%s\" is not a member of \"%s\"", pr_token, od->type->name); + goto reloop; + } + } + + if (!PR_KeywordEnabled(KEYWORD_CLASS)) return d; + + if (d->type->parentclass || d->type->type == ev_entity) // class + { + if (PR_CheckToken(".") || PR_CheckToken("->")) + { + def_t *field; + + if (PR_CheckToken("(")) + { + field = PR_Expression(TOP_PRIORITY, true); + PR_Expect(")"); + } + else field = PR_ParseValue(d->type); + + if (field->type->type == ev_field) + { + if (!field->type->aux_type) + { + PR_ParseWarning(ERR_INTERNAL, "Field with null aux_type"); + return PR_Statement(&pr_opcodes[OP_LOAD_FLD], d, field, NULL); + } + else + { + switch(field->type->aux_type->type) + { + case ev_integer: + return PR_Statement(&pr_opcodes[OP_LOAD_I], d, field, NULL); + case ev_field: + d = PR_Statement(&pr_opcodes[OP_LOAD_FLD], d, field, NULL); + nd = (void *)Qalloc (sizeof(def_t)); + nd->type = field->type->aux_type; + nd->ofs = d->ofs; + nd->temp = d->temp; + nd->constant = false; + nd->name = d->name; + return nd; + case ev_float: + return PR_Statement(&pr_opcodes[OP_LOAD_F], d, field, NULL); + case ev_string: + return PR_Statement(&pr_opcodes[OP_LOAD_S], d, field, NULL); + case ev_vector: + return PR_Statement(&pr_opcodes[OP_LOAD_V], d, field, NULL); + case ev_function: + // complicated for a typecast + d = PR_Statement(&pr_opcodes[OP_LOAD_FNC], d, field, NULL); + nd = (void *)Qalloc (sizeof(def_t)); + nd->type = field->type->aux_type; + nd->ofs = d->ofs; + nd->temp = d->temp; + nd->constant = false; + nd->name = d->name; + return nd; + case ev_entity: + return PR_Statement(&pr_opcodes[OP_LOAD_ENT], d, field, NULL); + default: + PR_ParseError(ERR_INTERNAL, "Bad field type"); + return d; + } + } + } + else PR_IncludeChunk(".", false, NULL); + } + } + return d; +} + + +/* +============ +PR_Term +============ +*/ +def_t *PR_Term( void ) +{ + def_t *e, *e2; + etype_t t; + + if( pr_token_type == tt_punct ) // a little extra speed... + { + if( PR_CheckToken( "++" )) + { + qcc_usefulstatement = true; + e = PR_Term (); + if (e->constant) PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to constant %s", e->name); + if (e->temp) PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Hey! That's a temp! ++ operators cannot work on temps!"); + + switch (e->type->type) + { + case ev_integer: + PR_Statement3(&pr_opcodes[OP_ADD_I], e, PR_MakeIntDef(1), e, false); + break; + case ev_float: + PR_Statement3(&pr_opcodes[OP_ADD_F], e, PR_MakeFloatDef(1), e, false); + break; + default: + PR_ParseError(ERR_BADPLUSPLUSOPERATOR, "++ operator on unsupported type"); + break; + } + return e; + } + else if( PR_CheckToken( "--" )) + { + qcc_usefulstatement = true; + e = PR_Term (); + if (e->constant) PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to constant %s", e->name); + if (e->temp) PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Hey! That's a temp! -- operators cannot work on temps!"); + switch (e->type->type) + { + case ev_integer: + PR_Statement3(&pr_opcodes[OP_SUB_I], e, PR_MakeIntDef(1), e, false); + break; + case ev_float: + PR_Statement3(&pr_opcodes[OP_SUB_F], e, PR_MakeFloatDef(1), e, false); + break; + default: + PR_ParseError(ERR_BADPLUSPLUSOPERATOR, "-- operator on unsupported type"); + break; + } + return e; + } + if( PR_CheckToken( "!" )) + { + e = PR_Expression (NOT_PRIORITY, false); + t = e->type->type; + switch(t) + { + case ev_float: + e2 = PR_Statement (&pr_opcodes[OP_NOT_F], e, 0, NULL); + break; + case ev_string: + e2 = PR_Statement (&pr_opcodes[OP_NOT_S], e, 0, NULL); + break; + case ev_entity: + e2 = PR_Statement (&pr_opcodes[OP_NOT_ENT], e, 0, NULL); + break; + case ev_vector: + e2 = PR_Statement (&pr_opcodes[OP_NOT_V], e, 0, NULL); + break; + case ev_function: + e2 = PR_Statement (&pr_opcodes[OP_NOT_FNC], e, 0, NULL); + break; + case ev_integer: + e2 = PR_Statement (&pr_opcodes[OP_NOT_FNC], e, 0, NULL); + break; // functions are integer values too. + case ev_pointer: + e2 = PR_Statement (&pr_opcodes[OP_NOT_FNC], e, 0, NULL); + break; // Pointers are too. + default: + e2 = NULL; // shut up compiler warning; + PR_ParseError (ERR_BADNOTTYPE, "type mismatch for !"); + break; + } + return e2; + } + else if( PR_CheckToken( "~" )) + { + e = PR_Expression( NOT_PRIORITY, false ); + t = e->type->type; + switch( t ) + { + case ev_float: + e2 = PR_Statement( &pr_opcodes[OP_NOT_BITF], e, 0, NULL ); + break; + case ev_integer: + e2 = PR_Statement( &pr_opcodes[OP_NOT_BITI], e, 0, NULL ); + break; // functions are integer values too. + default: + e2 = NULL; // shut up compiler warning; + PR_ParseError( ERR_BADNOTTYPE, "type mismatch for ~" ); + break; + } + return e2; + } + else if( PR_CheckToken( "&" )) + { + int st = numstatements; + e = PR_Expression (NOT_PRIORITY, false); + t = e->type->type; + + if (st != numstatements) // woo, something like ent.field? + { + if ((unsigned)(statements[numstatements-1].op - OP_LOAD_F) < 6 || statements[numstatements-1].op == OP_LOAD_I || statements[numstatements-1].op == OP_LOAD_P) + { + statements[numstatements-1].op = OP_ADDRESS; + PR_ParseWarning(0, "debug: &ent.field"); + e->type = PR_PointerType(e->type); + return e; + } + else // this is a restriction that could be lifted, I just want to make sure that I got all the bits first. + { + PR_ParseError (ERR_BADNOTTYPE, "type mismatch for '&' Must be singular expression or field reference"); + return e; + } + } + e2 = PR_Statement (&pr_opcodes[OP_GLOBAL_ADD], e, 0, NULL); + e2->type = PR_PointerType(e->type); + return e2; + } + else if (PR_CheckToken ("*")) + { + e = PR_Expression (NOT_PRIORITY, false); + t = e->type->type; + + if (t != ev_pointer) PR_ParseError (ERR_BADNOTTYPE, "type mismatch for *"); + + switch(e->type->aux_type->type) + { + case ev_float: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_F], e, 0, NULL); + break; + case ev_string: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_S], e, 0, NULL); + break; + case ev_vector: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_V], e, 0, NULL); + break; + case ev_entity: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_ENT], e, 0, NULL); + break; + case ev_field: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_FLD], e, 0, NULL); + break; + case ev_function: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_FLD], e, 0, NULL); + break; + case ev_integer: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_I], e, 0, NULL); + break; + case ev_pointer: + e2 = PR_Statement (&pr_opcodes[OP_LOADP_I], e, 0, NULL); + break; + + default: + PR_ParseError (ERR_BADNOTTYPE, "type mismatch for * (unrecognized type)"); + e2 = NULL; + break; + } + e2->type = e->type->aux_type; + return e2; + } + else if (PR_CheckToken ("-")) + { + e = PR_Expression (NOT_PRIORITY, false); + switch(e->type->type) + { + case ev_float: + e2 = PR_Statement (&pr_opcodes[OP_SUB_F], PR_MakeFloatDef(0), e, NULL); + break; + case ev_integer: + e2 = PR_Statement (&pr_opcodes[OP_SUB_I], PR_MakeIntDef(0), e, NULL); + break; + default: + PR_ParseError (ERR_BADNOTTYPE, "type mismatch for -"); + e2 = NULL; + break; + } + return e2; + } + else if (PR_CheckToken ("+")) + { + e = PR_Expression (NOT_PRIORITY, false); + switch(e->type->type) + { + case ev_float: + e2 = PR_Statement (&pr_opcodes[OP_ADD_F], PR_MakeFloatDef(0), e, NULL); + break; + case ev_integer: + e2 = PR_Statement (&pr_opcodes[OP_ADD_I], PR_MakeIntDef(0), e, NULL); + break; + default: + PR_ParseError (ERR_BADNOTTYPE, "type mismatch for +"); + e2 = NULL; + break; + } + return e2; + } + if (PR_CheckToken ("(")) + { + // float is always enabled + if (PR_CheckForKeyword( KEYWORD_FLOAT )) // check for type casts + { + PR_Expect (")"); + e = PR_Term(); + if (e->type->type == ev_float) return e; + else if (e->type->type == ev_integer) + return PR_Statement (&pr_opcodes[OP_CONV_ITOF], e, 0, NULL); + else if (e->type->type == ev_function) return e; + PR_ParseWarning (0, "Not all vars make sence as floats"); + + e2 = (void *)Qalloc (sizeof(def_t)); + e2->type = type_float; + e2->ofs = e->ofs; + e2->constant = true; + e2->temp = e->temp; + return e2; + } + else if (PR_CheckForKeyword( KEYWORD_CLASS )) + { + type_t *classtype = TypeForName(PR_ParseName()); + if (!classtype) PR_ParseError(ERR_NOTANAME, "Class not defined for cast"); + + PR_Expect (")"); + e = PR_Term(); + e2 = (void *)Qalloc (sizeof(def_t)); + e2->type = classtype; + e2->ofs = e->ofs; + e2->constant = true; + e2->temp = e->temp; + return e2; + } + else if (PR_CheckForKeyword( KEYWORD_INT )) // check for type casts + { + PR_Expect (")"); + e = PR_Term(); + if (e->type->type == ev_integer) return e; + else if (e->type->type == ev_float) + return PR_Statement (&pr_opcodes[OP_CONV_FTOI], e, 0, NULL); + else PR_ParseError (ERR_BADTYPECAST, "invalid typecast"); + } + else + { + bool oldcond = conditional; + conditional = conditional ? 2:0; + e = PR_Expression (TOP_PRIORITY, false); + PR_Expect (")"); + conditional = oldcond; + } + return e; + } + } + return PR_ParseValue (pr_classtype); +} + +int PR_canConv(def_t *from, etype_t to) +{ + if (from->type->type == to) return 0; + if (from->type->type == ev_vector && to == ev_float) return 4; + + if (pr_classtype) + { + if (from->type->type == ev_field) + { + if (from->type->aux_type->type == to) + return 1; + } + } + + if (from->type->type == ev_integer && to == ev_function) + return 1; + + return -100; +} + +/* +============== +PR_Expression +============== +*/ +def_t *PR_Expression (int priority, bool allowcomma) +{ + dstatement_t *st; + def_t *e, *e2; + opcode_t *op, *oldop, *bestop; + int opnum, numconversions, c; + etype_t type_a, type_b, type_c; + + if (priority == 0) return PR_Term(); + + e = PR_Expression(priority - 1, allowcomma); + + while (1) + { + if (priority == 1) + { + if (PR_CheckToken ("(") ) + { + qcc_usefulstatement = true; + return PR_ParseFunctionCall(e); + } + if (PR_CheckToken ("?")) + { + dstatement_t *fromj, *elsej; + PR_Statement(&pr_opcodes[OP_IFNOT], e, NULL, &fromj); + e = PR_Expression(TOP_PRIORITY, true); + e2 = PR_GetTemp(e->type); + PR_Statement(&pr_opcodes[(e2->type->size>=3)?OP_STORE_V:OP_STORE_F], e, e2, NULL); + + PR_Expect(":"); + PR_Statement(&pr_opcodes[OP_GOTO], NULL, NULL, &elsej); + fromj->b = &statements[numstatements] - fromj; + e = PR_Expression(TOP_PRIORITY, true); + + if (typecmp(e->type, e2->type) != 0) PR_ParseError(0, "Ternary operator with mismatching types\n"); + PR_Statement(&pr_opcodes[(e2->type->size>=3)?OP_STORE_V:OP_STORE_F], e, e2, NULL); + + elsej->a = &statements[numstatements] - elsej; + return e2; + } + if (allowcomma && PR_CheckToken (",")) + { + PR_FreeTemp(e); + return PR_Expression(TOP_PRIORITY, true); + } + } + opnum = 0; + + if (pr_token_type == tt_immediate) + { + if (pr_immediate_type->type == ev_float) + { + if (pr_immediate._float < 0) + { + // hehehe... was a minus all along... + PR_IncludeChunk(pr_token, true, NULL); + com.strcpy(pr_token, "+"); // two negatives would make a positive. + pr_token_type = tt_punct; + } + } + } + if (pr_token_type != tt_punct) + { + PR_ParseWarning(WARN_UNEXPECTEDPUNCT, "Expected punctuation"); + } + + // go straight for the correct priority. + for (op = opcodeprioritized[priority][opnum]; op; op = opcodeprioritized[priority][++opnum]) + { + if (!PR_CheckToken (op->name)) continue; + st = NULL; + + if ( op->associative != ASSOC_LEFT ) + { + // if last statement is an indirect, change it to an address of + if (!simplestore && ((uint)(statements[numstatements-1].op - OP_LOAD_F) < 6 || statements[numstatements-1].op == OP_LOAD_I || statements[numstatements-1].op == OP_LOAD_P) && statements[numstatements-1].c == e->ofs) + { + qcc_usefulstatement=true; + statements[numstatements-1].op = OP_ADDRESS; + type_pointer->aux_type->type = e->type->type; + e->type = type_pointer; + } + // if last statement retrieved a value, switch it to retrieve a usable pointer. + if ( !simplestore && (unsigned)(statements[numstatements-1].op - OP_LOADA_F) < 7)// || statements[numstatements-1].op == OP_LOADA_C) + { + statements[numstatements-1].op = OP_GLOBAL_ADD; + type_pointer->aux_type->type = e->type->type; + e->type = type_pointer; + } + if ( !simplestore && (unsigned)(statements[numstatements-1].op - OP_LOADP_F) < 7) + { + statements[numstatements-1].op = OP_ADD_I; + } + if ( !simplestore && statements[numstatements-1].op == OP_LOADP_C && e->ofs == statements[numstatements-1].c) + { + statements[numstatements-1].op = OP_ADD_SF; + e->type = type_string; + + // now we want to make sure that string = float can't work without it being a dereferenced pointer. (we don't want to allow storep_c without dereferece) + e2 = PR_Expression (priority, allowcomma); + if (e2->type->type == ev_float) op = &pr_opcodes[OP_STOREP_C]; + } + else e2 = PR_Expression (priority, allowcomma); + } + else + { + if (op->priority == 7 && opt_logicops) + { + st = &statements[numstatements]; + if (*op->name == '&') // statement 3 because we don't want to optimise this into if from not ifnot + PR_Statement3(&pr_opcodes[OP_IFNOT], e, NULL, NULL, false); + else PR_Statement3(&pr_opcodes[OP_IF], e, NULL, NULL, false); + } + e2 = PR_Expression (priority-1, allowcomma); + } + + // type check + type_a = e->type->type; + type_b = e2->type->type; + + if (op->name[0] == '.') + { + // field access gets type from field + if (e2->type->aux_type) type_c = e2->type->aux_type->type; + else type_c = -1; // not a field + } + else type_c = ev_void; + + oldop = op; + bestop = NULL; + numconversions = 32767; + + while (op) + { + if (!(type_c != ev_void && type_c != (*op->type_c)->type)) + { + if(!STRCMP (op->name , oldop->name)) // matches + { + // return values are never converted - what to? + if (op->associative!=ASSOC_LEFT) + { + // assignment + if (op->type_a == &type_pointer) // ent var + { + if (e->type->type != ev_pointer) c = -200; // don't cast to a pointer. + else if ((*op->type_c)->type == ev_void && op->type_b == &type_pointer && e2->type->type == ev_pointer) + c = 0; // generic pointer... FIXME: is this safe? make sure both sides are equivelent + else if (e->type->aux_type->type != (*op->type_b)->type) // if e isn't a pointer to a type_b + c = -200; // don't let the conversion work + else c = PR_canConv(e2, (*op->type_c)->type); + } + else + { + c = PR_canConv(e2, (*op->type_b)->type); + if (type_a != (*op->type_a)->type) // in this case, a is the final assigned value + c = -300; // don't use this op, as we must not change var b's type + } + } + else + { + if (op->type_a == &type_pointer) // ent var + { + // if e isn't a pointer to a type_b + if (e2->type->type != ev_pointer || e2->type->aux_type->type != (*op->type_b)->type) + c = -200; // don't let the conversion work + else c = 0; + } + else + { + c = PR_canConv(e, (*op->type_a)->type); + c += PR_canConv(e2, (*op->type_b)->type); + } + } + + if (c >= 0 && c < numconversions) + { + bestop = op; + numconversions = c; + if (c == 0) break; // can't get less conversions than 0... + } + } + else break; + } + op = opcodeprioritized[priority][++opnum]; + } + if (bestop == NULL) + { + if (oldop->priority == TOP_PRIORITY) op = oldop; + else + { + if (opt_laxcasts) + { + op = oldop; + PR_ParseWarning(WARN_LAXCAST, "type mismatch for %s (%s and %s)", oldop->name, e->type->name, e2->type->name); + } + else PR_ParseError (ERR_TYPEMISMATCH, "type mismatch for %s (%s and %s)", oldop->name, e->type->name, e2->type->name); + } + } + else + { + if (numconversions>3) PR_ParseWarning(WARN_IMPLICITCONVERSION, "Implicit conversion"); + op = bestop; + } + + if (st) st->b = &statements[numstatements] - st; + if (op->associative != ASSOC_LEFT) + { + qcc_usefulstatement = true; + if (e->constant || e->ofs < OFS_PARM0) + { + if (e->type->type == ev_function) + { + PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANTFUNC, "Assignment to function %s", e->name); + PR_ParsePrintDef(WARN_ASSIGNMENTTOCONSTANTFUNC, e); + } + else + { + PR_ParseWarning(WARN_ASSIGNMENTTOCONSTANT, "Assignment to constant %s", e->name); + PR_ParsePrintDef(WARN_ASSIGNMENTTOCONSTANT, e); + } + } + if (conditional & 1) PR_ParseWarning(WARN_ASSIGNMENTINCONDITIONAL, "Assignment in conditional"); + e = PR_Statement (op, e2, e, NULL); + } + else e = PR_Statement (op, e, e2, NULL); + + // field access gets type from field + if (type_c != ev_void) e->type = e2->type->aux_type; + break; + } + if (!op) + { + if (e == NULL) PR_ParseError(ERR_INTERNAL, "e == null"); + if (!STRCMP(pr_token, "++")) + { + // if the last statement was an ent.float (or something) + if (((uint)(statements[numstatements-1].op - OP_LOAD_F) < 6 || statements[numstatements-1].op == OP_LOAD_I) && statements[numstatements-1].c == e->ofs) + { + def_t *e3; // we have our load. + // the only inefficiency here is with an extra temp (we can't reuse the original) + // this is not a problem, as the optimise temps or locals marshalling can clean these up for us + // 1. load + // 2. add to temp + // 3. store temp to offset + // 4. return original loaded (which is not at the same offset as the pointer we store to) + + qcc_usefulstatement = true; + e2 = PR_GetTemp(type_float); + e3 = PR_GetTemp(type_pointer); + PR_SimpleStatement(OP_ADDRESS, statements[numstatements-1].a, statements[numstatements-1].b, e3->ofs, false); + if (e->type->type == ev_float) + { + PR_Statement3(&pr_opcodes[OP_ADD_F], e, PR_MakeFloatDef(1), e2, false); + PR_Statement3(&pr_opcodes[OP_STOREP_F], e2, e3, NULL, false); + } + else if (e->type->type == ev_integer) + { + PR_Statement3(&pr_opcodes[OP_ADD_I], e, PR_MakeIntDef(1), e2, false); + PR_Statement3(&pr_opcodes[OP_STOREP_I], e2, e3, NULL, false); + } + else + { + PR_ParseError(ERR_PARSEERRORS, "-- suffix operator results in nonstandard behaviour. Use -=1 or prefix form instead"); + PR_IncludeChunk("-=1", false, NULL); + } + PR_FreeTemp(e2); + PR_FreeTemp(e3); + } + else if (e->type->type == ev_float) + { + // 1. copy to temp + // 2. add to original + // 3. return temp (which == original) + + PR_ParseWarning(WARN_INEFFICIENTPLUSPLUS, "++ suffix operator results in inefficient behaviour. Use += 1 or prefix form instead"); + qcc_usefulstatement=true; + + e2 = PR_GetTemp(type_float); + PR_Statement3(&pr_opcodes[OP_STORE_F], e, e2, NULL, false); + PR_Statement3(&pr_opcodes[OP_ADD_F], e, PR_MakeFloatDef(1), e, false); + PR_FreeTemp(e); + e = e2; + } + else if (e->type->type == ev_integer) + { + PR_ParseWarning(WARN_INEFFICIENTPLUSPLUS, "++ suffix operator results in inefficient behaviour. Use +=1 or prefix form instead"); + qcc_usefulstatement=true; + + e2 = PR_GetTemp(type_integer); + PR_Statement3(&pr_opcodes[OP_STORE_I], e, e2, NULL, false); + PR_Statement3(&pr_opcodes[OP_ADD_I], e, PR_MakeIntDef(1), e, false); + PR_FreeTemp(e); + e = e2; + } + else + { + PR_ParseWarning(WARN_NOTSTANDARDBEHAVIOUR, "++ suffix operator results in nonstandard behaviour. Use +=1 or prefix form instead"); + PR_IncludeChunk("+=1", false, NULL); + } + PR_Lex(); + } + else if (!STRCMP(pr_token, "--")) + { + if (((uint)(statements[numstatements-1].op - OP_LOAD_F) < 6 || statements[numstatements-1].op == OP_LOAD_I) && statements[numstatements-1].c == e->ofs) + { + def_t *e3; // we have our load. + // 1. load + // 2. add to temp + // 3. store temp to offset + // 4. return original loaded (which is not at the same offset as the pointer we store to) + + e2 = PR_GetTemp(type_float); + e3 = PR_GetTemp(type_pointer); + PR_SimpleStatement(OP_ADDRESS, statements[numstatements-1].a, statements[numstatements-1].b, e3->ofs, false); + if (e->type->type == ev_float) + { + PR_Statement3(&pr_opcodes[OP_SUB_F], e, PR_MakeFloatDef(1), e2, false); + PR_Statement3(&pr_opcodes[OP_STOREP_F], e2, e3, NULL, false); + } + else if (e->type->type == ev_integer) + { + PR_Statement3(&pr_opcodes[OP_SUB_I], e, PR_MakeIntDef(1), e2, false); + PR_Statement3(&pr_opcodes[OP_STOREP_I], e2, e3, NULL, false); + } + else + { + PR_ParseError(ERR_PARSEERRORS, "-- suffix operator results in nonstandard behaviour. Use -=1 or prefix form instead"); + PR_IncludeChunk("-=1", false, NULL); + } + PR_FreeTemp(e2); + PR_FreeTemp(e3); + } + else if (e->type->type == ev_float) + { + PR_ParseWarning(WARN_INEFFICIENTPLUSPLUS, "-- suffix operator results in inefficient behaviour. Use -= 1 or prefix form instead"); + qcc_usefulstatement=true; + + e2 = PR_GetTemp(type_float); + PR_Statement3(&pr_opcodes[OP_STORE_F], e, e2, NULL, false); + PR_Statement3(&pr_opcodes[OP_SUB_F], e, PR_MakeFloatDef(1), e, false); + PR_FreeTemp(e); + e = e2; + } + else if (e->type->type == ev_integer) + { + PR_ParseWarning(WARN_INEFFICIENTPLUSPLUS, "-- suffix operator results in inefficient behaviour. Use -= 1 or prefix form instead"); + qcc_usefulstatement=true; + + e2 = PR_GetTemp(type_integer); + PR_Statement3(&pr_opcodes[OP_STORE_I], e, e2, NULL, false); + PR_Statement3(&pr_opcodes[OP_SUB_I], e, PR_MakeIntDef(1), e, false); + PR_FreeTemp(e); + e = e2; + } + else + { + PR_ParseWarning(WARN_NOTSTANDARDBEHAVIOUR, "-- suffix operator results in nonstandard behaviour. Use -= 1 or prefix form instead"); + PR_IncludeChunk("-=1", false, NULL); + } + PR_Lex(); + } + break; // next token isn't at this priority level + } + } + if (e == NULL) PR_ParseError(ERR_INTERNAL, "e == null"); + return e; +} + +void PR_GotoStatement (dstatement_t *patch2, char *labelname) +{ + if (num_gotos >= max_gotos) + { + max_gotos += 8; + // realloc also used for first allocate + pr_gotos = Qrealloc(pr_gotos, sizeof(*pr_gotos)*max_gotos); + } + + com.strncpy(pr_gotos[num_gotos].name, labelname, sizeof(pr_gotos[num_gotos].name) -1); + pr_gotos[num_gotos].lineno = pr_source_line; + pr_gotos[num_gotos].statementno = patch2 - statements; + num_gotos++; +} + +bool PR_StatementBlocksMatch(dstatement_t *p1, int p1count, dstatement_t *p2, int p2count) +{ + if (p1count != p2count) return false; + + while(p1count > 0) + { + if (p1->op != p2->op) return false; + if (p1->a != p2->a) return false; + if (p1->b != p2->b) return false; + if (p1->c != p2->c) return false; + p1++, p2++, p1count--; + } + return true; +} + +/* +============ +PR_ParseStatement + +============ +*/ +void PR_ParseStatement( void ) +{ + int continues; + int breaks; + int cases, i; + def_t *e, *e2; + dstatement_t *patch1, *patch2, *patch3; + int statementstart = pr_source_line; + + if( PR_CheckToken( "{" )) + { + e = pr.localvars; + while( !PR_CheckToken( "}" )) PR_ParseStatement(); + + if( pr_subscopedlocals ) + { + for( e2 = pr.localvars; e2 != e; e2 = e2->nextlocal ) + { + Hash_RemoveData( &localstable, e2->name, e2 ); + } + } + return; + } + if( PR_CheckForKeyword( KEYWORD_RETURN )) + { + if( PR_CheckToken( ";" )) + { + if( pr_scope->type->aux_type->type != ev_void ) + PR_ParseWarning( WARN_MISSINGRETURNVALUE, "\'%s\' should return %s", pr_scope->name, pr_scope->type->aux_type->name); + if( opt_return_only ) PR_FreeTemp(PR_Statement (&pr_opcodes[OP_DONE], 0, 0, NULL)); + else PR_FreeTemp(PR_Statement (&pr_opcodes[OP_RETURN], 0, 0, NULL)); + return; + } + e = PR_Expression( TOP_PRIORITY, true ); + PR_Expect( ";" ); + if( pr_scope->type->aux_type->type != e->type->type ) + { + if( pr_scope->type->aux_type->type == ev_void ) + PR_ParseWarning( WARN_WRONGRETURNTYPE, "\'%s\' : 'void' function returning a value", pr_scope->name ); + else PR_ParseWarning( WARN_WRONGRETURNTYPE, "\'%s\' returned %s, expected %s", pr_scope->name, e->type->name, pr_scope->type->aux_type->name); + } + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_RETURN], e, 0, NULL)); + return; + } + if (PR_CheckForKeyword( KEYWORD_WHILE )) + { + continues = num_continues; + breaks = num_breaks; + + PR_Expect ("("); + patch2 = &statements[numstatements]; + conditional = 1; + e = PR_Expression (TOP_PRIORITY, true); + conditional = 0; + if (((e->constant && !e->temp) || !STRCMP(e->name, "IMMEDIATE")) && opt_compound_jumps) + { + if (!G_INT(e->ofs)) + { + PR_ParseWarning(0, "while(0)?"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch1)); + } + else patch1 = NULL; + } + else + { + if (e->constant && !e->temp) + { + if (!G_FLOAT(e->ofs)) + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch1)); + else patch1 = NULL; + } + else if (!typecmp( e->type, type_string) && opt_ifstring) + { + // special case, as strings are now pointers, not offsets from string table + PR_ParseWarning(0, "while (string) can result in bizzare behaviour"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IFNOTS], e, 0, &patch1)); + } + else PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IFNOT], e, 0, &patch1)); + } + PR_Expect (")"); // after the line number is noted.. + PR_ParseStatement (); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], NULL, 0, &patch3)); + patch3->a = patch2 - patch3; + if (patch1) + { + if (patch1->op == OP_GOTO) patch1->a = &statements[numstatements] - patch1; + else patch1->b = &statements[numstatements] - patch1; + } + + if (breaks != num_breaks) + { + for(i = breaks; i < num_breaks; i++) + { + patch1 = &statements[pr_breaks[i]]; // jump to after the return-to-top goto + statements[pr_breaks[i]].a = &statements[numstatements] - patch1; + } + num_breaks = breaks; + } + if (continues != num_continues) + { + for(i = continues; i < num_continues; i++) + { + patch1 = &statements[pr_continues[i]]; + statements[pr_continues[i]].a = patch2 - patch1; // jump back to top + } + num_continues = continues; + } + return; + } + if (PR_CheckForKeyword( KEYWORD_FOR )) + { + int old_numstatements; + int numtemp, i; + int linenum[32]; + dstatement_t temp[sizeof(linenum)/sizeof(linenum[0])]; + + continues = num_continues; + breaks = num_breaks; + + PR_Expect("("); + if (!PR_CheckToken(";")) + { + PR_FreeTemp(PR_Expression(TOP_PRIORITY, true)); + PR_Expect(";"); + } + + patch2 = &statements[numstatements]; + if (!PR_CheckToken(";")) + { + conditional = 1; + e = PR_Expression(TOP_PRIORITY, true); + conditional = 0; + PR_Expect(";"); + } + else e = NULL; + + if (!PR_CheckToken(")")) + { + old_numstatements = numstatements; + PR_FreeTemp(PR_Expression(TOP_PRIORITY, true)); + numtemp = numstatements - old_numstatements; + if (numtemp > sizeof(linenum)/sizeof(linenum[0])) + PR_ParseError(ERR_TOOCOMPLEX, "Update expression too large"); + numstatements = old_numstatements; + for (i = 0 ; i < numtemp ; i++) + { + linenum[i] = statement_linenums[numstatements + i]; + temp[i] = statements[numstatements + i]; + } + PR_Expect(")"); + } + else numtemp = 0; + + if (e) PR_FreeTemp(PR_Statement(&pr_opcodes[OP_IFNOT], e, 0, &patch1)); + else patch1 = NULL; + if (!PR_CheckToken(";")) PR_ParseStatement(); // don't give the hanging ';' warning. + patch3 = &statements[numstatements]; + + for (i = 0; i < numtemp; i++) + { + statement_linenums[numstatements] = linenum[i]; + statements[numstatements++] = temp[i]; + } + + PR_SimpleStatement(OP_GOTO, patch2 - &statements[numstatements], 0, 0, false); + if (patch1) patch1->b = &statements[numstatements] - patch1; + + if (breaks != num_breaks) + { + for(i = breaks; i < num_breaks; i++) + { + patch1 = &statements[pr_breaks[i]]; + statements[pr_breaks[i]].a = &statements[numstatements] - patch1; + } + num_breaks = breaks; + } + if (continues != num_continues) + { + for(i = continues; i < num_continues; i++) + { + patch1 = &statements[pr_continues[i]]; + statements[pr_continues[i]].a = patch3 - patch1; + } + num_continues = continues; + } + return; + } + if (PR_CheckForKeyword( KEYWORD_DO )) + { + continues = num_continues; + breaks = num_breaks; + + patch1 = &statements[numstatements]; + PR_ParseStatement (); + PR_Expect ("while"); + PR_Expect ("("); + conditional = 1; + e = PR_Expression (TOP_PRIORITY, true); + conditional = 0; + + if (e->constant && !e->temp) + { + if (G_FLOAT(e->ofs)) + { + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], NULL, 0, &patch2)); + patch2->a = patch1 - patch2; + } + } + else + { + if (!typecmp( e->type, type_string) && opt_ifstring) + { + PR_ParseWarning(WARN_IFSTRING_USED, "do {} while(string) can result in bizzare behaviour"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IFS], e, NULL, &patch2)); + } + else PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IF], e, NULL, &patch2)); + patch2->b = patch1 - patch2; + } + PR_Expect (")"); + PR_Expect (";"); + + if (breaks != num_breaks) + { + for(i = breaks; i < num_breaks; i++) + { + patch2 = &statements[pr_breaks[i]]; + statements[pr_breaks[i]].a = &statements[numstatements] - patch2; + } + num_breaks = breaks; + } + if (continues != num_continues) + { + for(i = continues; i < num_continues; i++) + { + patch2 = &statements[pr_continues[i]]; + statements[pr_continues[i]].a = patch1 - patch2; + } + num_continues = continues; + } + return; + } + + if (PR_CheckForKeyword( KEYWORD_LOCAL )) + { + type_t *functionsclasstype = pr_classtype; + PR_ParseDefs( NULL ); + pr_classtype = functionsclasstype; + locals_end = numpr_globals; + return; + } + + if (pr_token_type == tt_name) + { + bool result = false; + + if( PR_MatchKeyword( KEYWORD_STRING )) result = true; + else if( PR_MatchKeyword( KEYWORD_FLOAT )) result = true; + else if( PR_MatchKeyword( KEYWORD_ENTITY )) result = true; + else if( PR_MatchKeyword( KEYWORD_VECTOR )) result = true; + else if( PR_MatchKeyword( KEYWORD_INT )) result = true; + else if( PR_MatchKeyword( KEYWORD_CLASS )) result = true; + else if( PR_MatchKeyword( KEYWORD_CONST )) result = true; + else result = false; + + if( result ) + { + PR_ParseDefs( NULL ); + locals_end = numpr_globals; + return; + } + } + if( PR_CheckForKeyword( KEYWORD_STATE )) + { + PR_Expect("["); + PR_ParseState(); + PR_Expect(";"); + return; + } + if (PR_CheckToken("#")) + { + char *name; + float frame = pr_immediate._float; + PR_Lex(); + name = PR_ParseName(); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STATE], PR_MakeFloatDef(frame), PR_GetDef(type_function, name, NULL, false, 0), NULL)); + PR_Expect(";"); + return; + } + + if (PR_CheckForKeyword( KEYWORD_IF )) + { + PR_Expect ("("); + conditional = 1; + e = PR_Expression (TOP_PRIORITY, true); + conditional = 0; + + if (!typecmp( e->type, type_string) && opt_ifstring) + { + PR_ParseWarning(WARN_IFSTRING_USED, "if (string) can result in bizzare behaviour"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IFNOTS], e, 0, &patch1)); + } + else PR_FreeTemp(PR_Statement (&pr_opcodes[OP_IFNOT], e, 0, &patch1)); + + PR_Expect (")"); // close bracket is after we save the statement to mem (so debugger does not show the if statement as being on the line after + PR_ParseStatement (); + + if (PR_CheckForKeyword( KEYWORD_ELSE )) + { + int lastwasreturn; + lastwasreturn = statements[numstatements-1].op == OP_RETURN || statements[numstatements-1].op == OP_DONE; + + // the last statement of the if was a return, so we don't need the goto at the end + if (lastwasreturn && opt_compound_jumps && !PR_AStatementJumpsTo(numstatements, patch1-statements, numstatements)) + { + patch1->b = &statements[numstatements] - patch1; + PR_ParseStatement (); + } + else + { + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch2)); + patch1->b = &statements[numstatements] - patch1; + PR_ParseStatement (); + patch2->a = &statements[numstatements] - patch2; + + if (PR_StatementBlocksMatch(patch1+1, patch2-patch1, patch2+1, &statements[numstatements] - patch2)) + PR_ParseWarning(0, "Two identical blocks each side of an else"); + } + } + else patch1->b = &statements[numstatements] - patch1; + return; + } + if (PR_CheckForKeyword( KEYWORD_SWITCH )) + { + int op; + int defaultcase = -1; + temp_t *et; + int oldst; + + breaks = num_breaks; + cases = num_cases; + PR_Expect ("("); + + conditional = 1; + e = PR_Expression (TOP_PRIORITY, true); + conditional = 0; + + if (e == &def_ret) et = NULL; + else + { + et = e->temp; + e->temp = NULL; // so noone frees it until we finish this loop + } + + /* + expands from: + + switch (CONDITION) + { + case 1: + break; + case 2: + default: + break; + } + + to: + + x = CONDITION, goto start + l1: + goto end + l2: + def: + goto end + goto end P1 + start: + if (x == 1) goto l1; + if (x == 2) goto l2; + goto def + end: + */ + // x is emitted in an opcode, stored as a register that we cannot access later. + // it should be possible to nest these. + + switch(e->type->type) + { + case ev_float: + op = OP_SWITCH_F; + break; + case ev_entity: // whu??? + op = OP_SWITCH_E; + break; + case ev_vector: + op = OP_SWITCH_V; + break; + case ev_string: + op = OP_SWITCH_S; + break; + case ev_function: + op = OP_SWITCH_FNC; + break; + default: // err hmm. + op = 0; + break; + } + + PR_FreeTemp(PR_Statement (&pr_opcodes[op], e, 0, &patch1)); + PR_Expect (")"); // close bracket is after we save the statement to mem (so debugger does not show the if statement as being on the line after + + oldst = numstatements; + PR_ParseStatement (); + + // this is so that a missing goto at the end of your switch doesn't end up in the jumptable again + if (oldst == numstatements || !PR_StatementIsAJump(numstatements-1, numstatements-1)) + { + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch2)); // the P1 statement/the theyforgotthebreak statement. + } + else patch2 = NULL; + patch1->b = &statements[numstatements] - patch1; // the goto start part + + for (i = cases; i < num_cases; i++) + { + if (!pr_casesdef[i]) + { + if (defaultcase >= 0) PR_ParseError(ERR_MULTIPLEDEFAULTS, "Duplicated default case"); + defaultcase = i; + } + else + { + if (pr_casesdef[i]->type->type != e->type->type) + { + if (e->type->type == ev_integer && pr_casesdef[i]->type->type == ev_float) + pr_casesdef[i] = PR_MakeIntDef((int)pr_globals[pr_casesdef[i]->ofs]); + else PR_ParseWarning(WARN_SWITCHTYPEMISMATCH, "switch case type mismatch"); + } + if (pr_casesdef2[i]) + { + if (pr_casesdef2[i]->type->type != e->type->type) + { + if (e->type->type == ev_integer && pr_casesdef[i]->type->type == ev_float) + pr_casesdef2[i] = PR_MakeIntDef((int)pr_globals[pr_casesdef2[i]->ofs]); + else PR_ParseWarning(WARN_SWITCHTYPEMISMATCH, "switch caserange type mismatch"); + } + + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CASERANGE], pr_casesdef[i], pr_casesdef2[i], &patch3)); + patch3->c = &statements[pr_cases[i]] - patch3; + } + else + { + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CASE], pr_casesdef[i], 0, &patch3)); + patch3->b = &statements[pr_cases[i]] - patch3; + } + } + } + if( defaultcase >= 0 ) + { + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch3)); + patch3->a = &statements[pr_cases[defaultcase]] - patch3; + } + + num_cases = cases; + patch3 = &statements[numstatements]; + if (patch2) patch2->a = patch3 - patch2; // set P1 jump + + if (breaks != num_breaks) + { + for(i = breaks; i < num_breaks; i++) + { + patch2 = &statements[pr_breaks[i]]; + patch2->a = patch3 - patch2; + } + num_breaks = breaks; + } + + if (et) + { + e->temp = et; + PR_FreeTemp(e); + } + return; + } + if (PR_CheckForKeyword( KEYWORD_ASM )) + { + if (PR_CheckToken("{")) + { + while (!PR_CheckToken("}")) + PR_ParseAsm (); + } + else PR_ParseAsm(); + return; + } + if (PR_CheckToken(":")) + { + if (pr_token_type != tt_name) + { + PR_ParseError(ERR_BADLABELNAME, "invalid label name \"%s\"", pr_token); + return; + } + + for (i = 0; i < num_labels; i++) + { + if (!STRNCMP(pr_labels[i].name, pr_token, sizeof(pr_labels[num_labels].name) -1)) + { + PR_ParseWarning(WARN_DUPLICATELABEL, "Duplicate label %s", pr_token); + PR_Lex(); + return; + } + } + if (num_labels >= max_labels) + { + max_labels += 8; + pr_labels = Qrealloc(pr_labels, sizeof(*pr_labels)*max_labels); + } + + com.strncpy(pr_labels[num_labels].name, pr_token, sizeof(pr_labels[num_labels].name) -1); + pr_labels[num_labels].lineno = pr_source_line; + pr_labels[num_labels].statementno = numstatements; + num_labels++; + + PR_Lex(); + return; + } + if (PR_CheckForKeyword( KEYWORD_GOTO )) + { + if (pr_token_type != tt_name) + { + PR_ParseError(ERR_NOLABEL, "invalid label name \"%s\"", pr_token); + return; + } + + PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, &patch2); + PR_GotoStatement (patch2, pr_token); + PR_Lex(); + PR_Expect(";"); + return; + } + if (PR_CheckForKeyword( KEYWORD_BREAK )) + { + if (!STRCMP ("(", pr_token)) + { + // make sure it wasn't a call to the break function. + PR_IncludeChunk("break(", true, NULL); + PR_Lex(); //so it sees the break. + } + else + { + if (num_breaks >= max_breaks) + { + max_breaks += 8; + pr_breaks = Qrealloc(pr_breaks, sizeof(*pr_breaks) * max_breaks); + } + pr_breaks[num_breaks] = numstatements; + PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, NULL); + num_breaks++; + PR_Expect(";"); + return; + } + } + if (PR_CheckForKeyword( KEYWORD_CONTINUE )) + { + if (num_continues >= max_continues) + { + max_continues += 8; + pr_continues = Qrealloc(pr_continues, sizeof(*pr_continues)*max_continues); + } + pr_continues[num_continues] = numstatements; + PR_Statement (&pr_opcodes[OP_GOTO], 0, 0, NULL); + num_continues++; + PR_Expect(";"); + return; + } + if (PR_CheckForKeyword( KEYWORD_CASE )) + { + if (num_cases >= max_cases) + { + max_cases += 8; + pr_cases = Qrealloc(pr_cases, sizeof(*pr_cases)*max_cases); + pr_casesdef = Qrealloc(pr_casesdef, sizeof(*pr_casesdef)*max_cases); + pr_casesdef2 = Qrealloc(pr_casesdef2, sizeof(*pr_casesdef2)*max_cases); + } + pr_cases[num_cases] = numstatements; + pr_casesdef[num_cases] = PR_Expression (TOP_PRIORITY, false); + if (PR_CheckToken("..")) + { + pr_casesdef2[num_cases] = PR_Expression (TOP_PRIORITY, false); + if (pr_casesdef[num_cases]->constant && pr_casesdef2[num_cases]->constant && !pr_casesdef[num_cases]->temp && !pr_casesdef2[num_cases]->temp) + { + if (G_FLOAT(pr_casesdef[num_cases]->ofs) >= G_FLOAT(pr_casesdef2[num_cases]->ofs)) + PR_ParseError(ERR_CASENOTIMMEDIATE, "Caserange statement uses backwards range\n"); + } + } + else pr_casesdef2[num_cases] = NULL; + + if (numstatements != pr_cases[num_cases]) PR_ParseError(ERR_CASENOTIMMEDIATE, "Case statements may not use formulas\n"); + num_cases++; + PR_Expect(":"); + return; + } + if (PR_CheckForKeyword( KEYWORD_DEFAULT )) + { + if (num_cases >= max_cases) + { + max_cases += 8; + pr_cases = Qrealloc(pr_cases, sizeof(*pr_cases)*max_cases); + pr_casesdef = Qrealloc(pr_casesdef, sizeof(*pr_casesdef)*max_cases); + pr_casesdef2 = Qrealloc(pr_casesdef2, sizeof(*pr_casesdef2)*max_cases); + } + pr_cases[num_cases] = numstatements; + pr_casesdef[num_cases] = NULL; + pr_casesdef2[num_cases] = NULL; + num_cases++; + PR_Expect(":"); + return; + } + if (PR_CheckForKeyword( KEYWORD_THINKTIME )) + { + e = PR_Expression (TOP_PRIORITY, true); + PR_Expect(":"); + e2 = PR_Expression (TOP_PRIORITY, true); + if (e->type->type != ev_entity || e2->type->type != ev_float) + PR_ParseError(ERR_THINKTIMETYPEMISMATCH, "thinktime type mismatch"); + + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_THINKTIME], e, e2, NULL)); + PR_Expect(";"); + return; + } + if (PR_CheckToken(";")) + { + int osl = pr_source_line; + pr_source_line = statementstart; + PR_ParseWarning(WARN_POINTLESSSTATEMENT, "Hanging ';'"); + pr_source_line = osl; + return; + } + + qcc_usefulstatement = false; + e = PR_Expression (TOP_PRIORITY, true); + PR_Expect (";"); + + if (e->type->type != ev_void && !qcc_usefulstatement) + { + int osl = pr_source_line; + pr_source_line = statementstart; + PR_ParseWarning(WARN_POINTLESSSTATEMENT, "Effectless statement"); + pr_source_line = osl; + } + PR_FreeTemp(e); +} + + +/* +============== +PR_ParseState + +States are special functions made for convenience. They automatically +set frame, nextthink (implicitly), and think (allowing forward definitions). + +// void() name = [framenum, nextthink] { code } +// expands to: +// function void name () +// { +// pev.frame = framenum; +// pev.nextthink = time + 0.1; +// pev.think = nextthink +// +// }; +============== +*/ +void PR_ParseState( void ) +{ + char *name; + def_t *s1, *def, *sc = pr_scope; + char f; + + f = *pr_token; + + if (PR_CheckToken("++") || PR_CheckToken("--")) + { + s1 = PR_ParseImmediate (); + PR_Expect(".."); + def = PR_ParseImmediate (); + PR_Expect ("]"); + + if (s1->type->type != ev_float || def->type->type != ev_float) + PR_ParseError(ERR_STATETYPEMISMATCH, "state type mismatch"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_CSTATE], s1, def, NULL)); + return; + } + + if (pr_token_type != tt_immediate || pr_immediate_type != type_float) + PR_ParseError (ERR_STATETYPEMISMATCH, "state frame must be a number"); + s1 = PR_ParseImmediate (); + PR_CheckToken (","); + + name = PR_ParseName (); + pr_scope = NULL; + def = PR_GetDef (type_function, name, NULL, true, 1); + pr_scope = sc; + + PR_Expect ("]"); + PR_FreeTemp(PR_Statement (&pr_opcodes[OP_STATE], s1, def, NULL)); +} + +void PR_ParseAsm(void) +{ + dstatement_t *patch1; + int op, p; + def_t *a, *b, *c; + + if (PR_CheckForKeyword( KEYWORD_LOCAL )) + { + PR_ParseDefs (NULL); + locals_end = numpr_globals; + return; + } + + for (op = 0; op < OP_NUMOPS; op++) + { + if (!STRCMP(pr_token, pr_opcodes[op].opname)) + { + PR_Lex(); + if (pr_opcodes[op].priority == -1 && pr_opcodes[op].associative != ASSOC_LEFT) + { + if (pr_opcodes[op].type_a==NULL) + { + patch1 = &statements[numstatements]; + PR_Statement3(&pr_opcodes[op], NULL, NULL, NULL, true); + + if (pr_token_type == tt_name) + { + PR_GotoStatement(patch1, PR_ParseName()); + } + else + { + p = (int)pr_immediate._float; + patch1->a = (int)p; + } + PR_Lex(); + } + else if (pr_opcodes[op].type_b==NULL) + { + patch1 = &statements[numstatements]; + a = PR_ParseValue(pr_classtype); + PR_Statement3(&pr_opcodes[op], a, NULL, NULL, true); + + if (pr_token_type == tt_name) + { + PR_GotoStatement(patch1, PR_ParseName()); + } + else + { + p = (int)pr_immediate._float; + patch1->b = (int)p; + } + PR_Lex(); + } + else + { + patch1 = &statements[numstatements]; + a = PR_ParseValue(pr_classtype); + b = PR_ParseValue(pr_classtype); + PR_Statement3(&pr_opcodes[op], a, b, NULL, true); + + if (pr_token_type == tt_name) + { + PR_GotoStatement(patch1, PR_ParseName()); + } + else + { + p = (int)pr_immediate._float; + patch1->c = (int)p; + } + PR_Lex(); + } + } + else + { + if (pr_opcodes[op].type_a != &type_void) + a = PR_ParseValue(pr_classtype); + else a = NULL; + if (pr_opcodes[op].type_b != &type_void) + b = PR_ParseValue(pr_classtype); + else b = NULL; + if (pr_opcodes[op].associative==ASSOC_LEFT && pr_opcodes[op].type_c != &type_void) + c = PR_ParseValue(pr_classtype); + else c = NULL; + PR_Statement3(&pr_opcodes[op], a, b, c, true); + } + PR_Expect(";"); + return; + } + } + PR_ParseError(ERR_BADOPCODE, "Bad op code name %s", pr_token); +} + +bool PR_FuncJumpsTo(int first, int last, int statement) +{ + int st; + + for (st = first; st < last; st++) + { + if (pr_opcodes[statements[st].op].type_a == NULL) + { + if (st + (signed)statements[st].a == statement) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + if (pr_opcodes[statements[st].op].type_b == NULL) + { + if (st + (signed)statements[st].b == statement) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + if (pr_opcodes[statements[st].op].type_c == NULL) + { + if (st + (signed)statements[st].c == statement) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + } + return false; +} + +bool PR_FuncJumpsToRange(int first, int last, int firstr, int lastr) +{ + int st; + + for (st = first; st < last; st++) + { + if (pr_opcodes[statements[st].op].type_a == NULL) + { + if (st + (signed)statements[st].a >= firstr && st + (signed)statements[st].a <= lastr) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + if (pr_opcodes[statements[st].op].type_b == NULL) + { + if (st + (signed)statements[st].b >= firstr && st + (signed)statements[st].b <= lastr) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + if (pr_opcodes[statements[st].op].type_c == NULL) + { + if (st + (signed)statements[st].c >= firstr && st + (signed)statements[st].c <= lastr) + { + if (st != first) + { + if (statements[st-1].op == OP_RETURN) continue; + if (statements[st-1].op == OP_DONE) continue; + return true; + } + } + } + } + return false; +} + +/* +========================== +PR_CompoundJumps + +jumps to jumps are reordered so they become jumps to the final target. +========================== +*/ +void PR_CompoundJumps(int first, int last) +{ + int statement; + int st; + int infloop; + + for (st = first; st < last; st++) + { + if (pr_opcodes[statements[st].op].type_a == NULL) + { + statement = st + (signed)statements[st].a; + if (statements[statement].op == OP_RETURN || statements[statement].op == OP_DONE) + { + // goto leads to return. Copy the command out to remove the goto. + statements[st].op = statements[statement].op; + statements[st].a = statements[statement].a; + statements[st].b = statements[statement].b; + statements[st].c = statements[statement].c; + } + infloop = 1000; + while (statements[statement].op == OP_GOTO) + { + if (!infloop--) + { + PR_ParseWarning(0, "Infinate loop detected"); + break; + } + statements[st].a = (statement+statements[statement].a - st); + statement = st + (signed)statements[st].a; + } + } + if (pr_opcodes[statements[st].op].type_b == NULL) + { + statement = st + (signed)statements[st].b; + infloop = 1000; + while (statements[statement].op == OP_GOTO) + { + if (!infloop--) + { + PR_ParseWarning(0, "Infinate loop detected"); + break; + } + statements[st].b = (statement+statements[statement].a - st); + statement = st + (signed)statements[st].b; + } + } + if (pr_opcodes[statements[st].op].type_c == NULL) + { + statement = st + (signed)statements[st].c; + infloop = 1000; + while (statements[statement].op == OP_GOTO) + { + if (!infloop--) + { + PR_ParseWarning(0, "Infinate loop detected"); + break; + } + statements[st].c = (statement+statements[statement].a - st); + statement = st + (signed)statements[st].c; + } + } + } +} + +void PR_CheckForDeadAndMissingReturns(int first, int last, int rettype) +{ + int st, st2; + + if (statements[last-1].op == OP_DONE) last--; // don't want the done + + if (rettype != ev_void) + { + if (statements[last-1].op != OP_RETURN) + { + if (statements[last-1].op != OP_GOTO || (signed)statements[last-1].a > 0) + { + PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); + return; + } + } + } + + for (st = first; st < last; st++) + { + if (statements[st].op == OP_RETURN || statements[st].op == OP_GOTO) + { + st++; + if (st == last) continue; // erm... end of function doesn't count as unreachable. + + if (!opt_compound_jumps) + { + // we can ignore single statements like these without compound jumps (compound jumps correctly removes all). + if (statements[st].op == OP_GOTO) continue; // inefficient compiler, we can ignore this. + if (statements[st].op == OP_DONE) continue; // inefficient compiler, we can ignore this. + if (statements[st].op == OP_RETURN) continue;// inefficient compiler, we can ignore this. + } + + //make sure something goes to just after this return. + for (st2 = first; st2 < last; st2++) + { + if (pr_opcodes[statements[st2].op].type_a == NULL) + { + if (st2 + (signed)statements[st2].a == st) + break; + } + if (pr_opcodes[statements[st2].op].type_b == NULL) + { + if (st2 + (signed)statements[st2].b == st) + break; + } + if (pr_opcodes[statements[st2].op].type_c == NULL) + { + if (st2 + (signed)statements[st2].c == st) + break; + } + } + if (st2 == last) PR_ParseWarning(WARN_UNREACHABLECODE, "%s: contains unreachable code", pr_scope->name ); + continue; + } + if (rettype != ev_void) + { + if (pr_opcodes[statements[st].op].type_a == NULL) + { + if (st + (signed)statements[st].a == last) + { + PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); + return; + } + } + if (pr_opcodes[statements[st].op].type_b == NULL) + { + if (st + (signed)statements[st].b == last) + { + PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); + return; + } + } + if (pr_opcodes[statements[st].op].type_c == NULL) + { + if (st + (signed)statements[st].c == last) + { + PR_ParseWarning(WARN_MISSINGRETURN, "%s: not all control paths return a value", pr_scope->name ); + return; + } + } + } + } +} + +bool PR_StatementIsAJump(int stnum, int notifdest) +{ + if (statements[stnum].op == OP_RETURN) return true; + if (statements[stnum].op == OP_DONE) return true; + if (statements[stnum].op == OP_GOTO) + { + if ((int)statements[stnum].a != notifdest) + return true; + } + return false; +} + +int PR_AStatementJumpsTo(int targ, int first, int last) +{ + int st; + + for (st = first; st < last; st++) + { + if (pr_opcodes[statements[st].op].type_a == NULL) + { + if (st + (signed)statements[st].a == targ && statements[st].a) + return true; + } + if (pr_opcodes[statements[st].op].type_b == NULL) + { + if (st + (signed)statements[st].b == targ) + return true; + } + if (pr_opcodes[statements[st].op].type_c == NULL) + { + if (st + (signed)statements[st].c == targ) + return true; + } + } + + for (st = 0; st < num_labels; st++) // assume it's used. + { + if (pr_labels[st].statementno == targ) + return true; + } + return false; +} + +void PR_RemapOffsets(uint firststatement, uint laststatement, uint min, uint max, uint newmin) +{ + dstatement_t *st; + uint i; + + for (i = firststatement, st = &statements[i]; i < laststatement; i++, st++) + { + if (pr_opcodes[st->op].type_a && st->a >= min && st->a < max) + st->a = st->a - min + newmin; + if (pr_opcodes[st->op].type_b && st->b >= min && st->b < max) + st->b = st->b - min + newmin; + if (pr_opcodes[st->op].type_c && st->c >= min && st->c < max) + st->c = st->c - min + newmin; + } +} + +void PR_Marshal_Locals(int first, int laststatement) +{ + def_t *local; + uint newofs; + + if (!pr.localvars) //nothing to marshal + { + locals_start = numpr_globals; + locals_end = numpr_globals; + return; + } + + if (!opt_locals_marshalling) + { + pr.localvars = NULL; + return; + } + + // initial backwards bounds. + locals_start = MAX_REGS; + locals_end = 0; + newofs = MAX_REGS; // this is a handy place to put it. :) + + // the params need to be in the order that they were allocated + // so we allocate in a backwards order. + for (local = pr.localvars; local; local = local->nextlocal) + { + if (local->constant) continue; + + newofs += local->type->size*local->arraysize; + if (local->arraysize > 1) newofs++; + } + + locals_start = MAX_REGS; + locals_end = newofs; + + for (local = pr.localvars; local; local = local->nextlocal) + { + if (local->constant) continue; + + if (((int*)pr_globals)[local->ofs]) PR_ParseError(ERR_INTERNAL, "Marshall of a set value"); + newofs -= local->type->size*local->arraysize; + if (local->arraysize > 1) newofs--; + + PR_RemapOffsets(first, laststatement, local->ofs, local->ofs+local->type->size*local->arraysize, newofs); + PR_FreeOffset(local->ofs, local->type->size*local->arraysize); + local->ofs = newofs; + } + pr.localvars = NULL; +} + +void PR_WriteAsmFunction(def_t *sc, uint firststatement, gofs_t firstparm) +{ + uint i; + uint p; + gofs_t o; + type_t *type; + def_t *param; + + if (!asmfile) return; + + type = sc->type; + FS_Printf(asmfile, "%s(", TypeName(type->aux_type)); + p = type->num_parms; + for (o = firstparm, i = 0, type = type->param; i < p; i++, type = type->next) + { + if (i) FS_Printf(asmfile, ", "); + for (param = pr.localvars; param; param = param->nextlocal) + { + if (param->ofs == o) break; + } + if (param) FS_Printf(asmfile, "%s %s", TypeName(type), param->name); + else FS_Printf(asmfile, "%s", TypeName(type)); + o += type->size; + } + + FS_Printf(asmfile, ") %s = asm\n{\n", sc->name); + PR_fprintfLocals(asmfile, firstparm, o); + + for (i = firststatement; i < (uint)numstatements; i++) + { + FS_Printf(asmfile, "\t%s", pr_opcodes[statements[i].op].opname); + if (pr_opcodes[statements[i].op].type_a != &type_void) + { + if (com.strlen(pr_opcodes[statements[i].op].opname) < 6) + FS_Printf(asmfile, "\t"); + if (pr_opcodes[statements[i].op].type_a) + FS_Printf(asmfile, "\t%s", PR_VarAtOffset(statements[i].a, (*pr_opcodes[statements[i].op].type_a)->size)); + else FS_Printf(asmfile, "\t%i", statements[i].a); + + if (pr_opcodes[statements[i].op].type_b != &type_void) + { + if (pr_opcodes[statements[i].op].type_b) + FS_Printf(asmfile, ",\t%s", PR_VarAtOffset(statements[i].b, (*pr_opcodes[statements[i].op].type_b)->size)); + else FS_Printf(asmfile, ",\t%i", statements[i].b); + if (pr_opcodes[statements[i].op].type_c != &type_void && pr_opcodes[statements[i].op].associative == ASSOC_LEFT) + { + if (pr_opcodes[statements[i].op].type_c) + FS_Printf(asmfile, ",\t%s", PR_VarAtOffset(statements[i].c, (*pr_opcodes[statements[i].op].type_c)->size)); + else FS_Printf(asmfile, ",\t%i", statements[i].c); + } + } + else + { + if (pr_opcodes[statements[i].op].type_c != &type_void) + { + if (pr_opcodes[statements[i].op].type_c) + FS_Printf(asmfile, ",\t%s", PR_VarAtOffset(statements[i].c, (*pr_opcodes[statements[i].op].type_c)->size)); + else FS_Printf(asmfile, ",\t%i", statements[i].c); + } + } + } + FS_Printf(asmfile, ";\n"); + } + FS_Printf(asmfile, "}\n\n"); +} + +/* +============ +PR_ParseImmediateStatements + +Parse a function body +============ +*/ +function_t *PR_ParseImmediateStatements (type_t *type) +{ + int i; + function_t *f; + def_t *defs[MAX_PARMS+MAX_PARMS_EXTRA], *e2; + type_t *parm; + bool needsdone = false; + freeoffset_t *oldfofs; + + conditional = 0; + f = (void *)Qalloc (sizeof(function_t)); + + // check for builtin function definition #1, #2, etc + if (PR_CheckToken ("#")) + { + if (pr_token_type != tt_immediate || pr_immediate_type != type_float || pr_immediate._float != (int)pr_immediate._float) + PR_ParseError (ERR_BADBUILTINIMMEDIATE, "Bad builtin immediate"); + f->builtin = (int)pr_immediate._float; + PR_Lex (); + locals_start = locals_end = OFS_PARM0; // hmm... + return f; + } + + if (type->num_parms < 0) PR_ParseError (ERR_FUNCTIONWITHVARGS, "QC function with variable arguments and function body"); + f->builtin = 0; + + // define the parms + locals_start = locals_end = numpr_globals; + oldfofs = freeofs; + freeofs = NULL; + parm = type->param; + + for (i = 0; i < type->num_parms; i++) + { + if (!*pr_parm_names[i]) PR_ParseError(ERR_PARAMWITHNONAME, "Parameter is not named"); + defs[i] = PR_GetDef (parm, pr_parm_names[i], pr_scope, true, 1); + + defs[i]->references++; + if (i < MAX_PARMS) + { + f->parm_ofs[i] = defs[i]->ofs; + if (i > 0 && f->parm_ofs[i] < f->parm_ofs[i-1]) PR_ParseError(ERR_INTERNAL, "bad parm order"); + if (i > 0 && f->parm_ofs[i] != f->parm_ofs[i-1]+defs[i-1]->type->size) + PR_ParseError(ERR_INTERNAL, "parms not packed"); + } + parm = parm->next; + } + + if (type->num_parms) locals_start = locals_end = defs[0]->ofs; + freeofs = oldfofs; + f->code = numstatements; + + if (type->num_parms > MAX_PARMS) + { + for (i = MAX_PARMS; i < type->num_parms; i++) + { + if (!extra_parms[i - MAX_PARMS]) + { + e2 = (def_t *) Qalloc (sizeof(def_t)); + e2->name = "extra parm"; + e2->ofs = PR_GetFreeOffsetSpace(3); + extra_parms[i - MAX_PARMS] = e2; + } + extra_parms[i - MAX_PARMS]->type = defs[i]->type; + if (defs[i]->type->type != ev_vector) + PR_Statement (&pr_opcodes[OP_STORE_F], extra_parms[i - MAX_PARMS], defs[i], NULL); + else PR_Statement (&pr_opcodes[OP_STORE_V], extra_parms[i - MAX_PARMS], defs[i], NULL); + } + } + PR_RemapLockedTemps(-1, -1); + + // check for a state opcode + if (PR_CheckToken ("[")) PR_ParseState (); + + if (PR_CheckForKeyword( KEYWORD_ASM )) + { + PR_Expect ("{"); + while (!PR_CheckToken("}")) PR_ParseAsm (); + } + else + { + PR_Expect ("{"); + // parse regular statements + while (!PR_CheckToken("}")) + { + PR_ParseStatement (); + PR_FreeTemps(); + } + } + PR_FreeTemps(); + + if (f->code == numstatements) needsdone = true; + else if (statements[numstatements - 1].op != OP_RETURN && statements[numstatements - 1].op != OP_DONE) + needsdone = true; + + if (num_gotos) + { + int j; + + for (i = 0; i < num_gotos; i++) + { + for (j = 0; j < num_labels; j++) + { + if (!com.strcmp(pr_gotos[i].name, pr_labels[j].name)) + { + if (!pr_opcodes[statements[pr_gotos[i].statementno].op].type_a) + statements[pr_gotos[i].statementno].a += pr_labels[j].statementno - pr_gotos[i].statementno; + else if (!pr_opcodes[statements[pr_gotos[i].statementno].op].type_b) + statements[pr_gotos[i].statementno].b += pr_labels[j].statementno - pr_gotos[i].statementno; + else statements[pr_gotos[i].statementno].c += pr_labels[j].statementno - pr_gotos[i].statementno; + break; + } + } + if (j == num_labels) + { + num_gotos = 0; + PR_ParseError(ERR_NOLABEL, "Goto statement with no matching label \"%s\"", pr_gotos[i].name); + } + } + num_gotos = 0; + } + + if (opt_return_only && !needsdone) needsdone = PR_FuncJumpsTo(f->code, numstatements, numstatements); + + // emit an end of statements opcode + if (!opt_return_only || needsdone) PR_Statement (pr_opcodes, 0,0, NULL); + + PR_CheckForDeadAndMissingReturns(f->code, numstatements, type->aux_type->type); + if (opt_compound_jumps) PR_CompoundJumps(f->code, numstatements); + PR_RemapLockedTemps(f->code, numstatements); + locals_end = numpr_globals; + PR_WriteAsmFunction(pr_scope, f->code, locals_start); + PR_Marshal_Locals(f->code, numstatements); + if (num_labels) num_labels = 0; + + if (num_continues) + { + num_continues = 0; + PR_ParseError(ERR_ILLEGALCONTINUES, "%s: function contains illegal continues\n", pr_scope->name); + } + if (num_breaks) + { + num_breaks = 0; + PR_ParseError(ERR_ILLEGALBREAKS, "%s: function contains illegal breaks\n", pr_scope->name); + } + if (num_cases) + { + num_cases = 0; + PR_ParseError(ERR_ILLEGALCASES, "%s: function contains illegal cases\n", pr_scope->name); + } + return f; +} + +void PR_ArrayRecurseDivideRegular(def_t *array, def_t *index, int min, int max, bool usingvectors) +{ + dstatement_t *st; + def_t *eq; + + if (min == max || min+1 == max) + { + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef(min+0.5f), NULL); + PR_UnFreeTemp(index); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 2; + PR_Statement(pr_opcodes+OP_RETURN, 0, 0, &st); + if(usingvectors) st->a = array->ofs + min * 3; + else st->a = array->ofs + min * array->type->size; + } + else + { + int mid = min + (max-min)/2; + + if (max-min > 4) + { + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef(mid+0.5f), NULL); + PR_UnFreeTemp(index); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + } + else st = NULL; + + PR_ArrayRecurseDivideRegular(array, index, min, mid, usingvectors ); + if (st) st->b = numstatements - (st-statements); + PR_ArrayRecurseDivideRegular(array, index, mid, max, usingvectors ); + } +} + +/* +======================= +PR_EmitArrayGetVector + +returns a vector overlapping the result needed. +======================= +*/ +def_t *PR_EmitArrayGetVector(def_t *array) +{ + dfunction_t *df; + def_t *temp, *index, *func; + + func = PR_GetDef(type_function, va("ArrayGetVec*%s", array->name), NULL, true, 1); + pr_scope = func; + df = &functions[numfunctions]; + numfunctions++; + + df->s_file = 0; + df->s_name = PR_CopyString(func->name, opt_noduplicatestrings ); + df->first_statement = numstatements; + df->parm_size[0] = 1; + df->numparms = 1; + df->parm_start = numpr_globals; + index = PR_GetDef(type_float, "index___", func, true, 1); + index->references++; + temp = PR_GetDef(type_float, "div3___", func, true, 1); + locals_end = numpr_globals; + df->locals = locals_end - df->parm_start; + PR_Statement3(pr_opcodes+OP_DIV_F, index, PR_MakeFloatDef(3), temp, false); + PR_Statement3(pr_opcodes+OP_BITAND, temp, temp, temp, false); // round down to int + PR_ArrayRecurseDivideRegular(array, temp, 0, (array->arraysize + 2)/3, true); // round up + + PR_Statement(pr_opcodes+OP_RETURN, PR_MakeFloatDef(0), 0, NULL); // err... we didn't find it, give up. + PR_Statement(pr_opcodes+OP_DONE, 0, 0, NULL); // err... we didn't find it, give up. + + G_FUNCTION(func->ofs) = df - functions; + func->initialized = 1; + return func; +} + +void PR_EmitArrayGetFunction(def_t *scope, char *arrayname) +{ + def_t *vectortrick; + dfunction_t *df; + dstatement_t *st; + def_t *eq, *def, *index; + + def = PR_GetDef(NULL, arrayname, NULL, false, 0); + + if (def->arraysize >= 15 && def->type->size == 1) vectortrick = PR_EmitArrayGetVector(def); + else vectortrick = NULL; + + pr_scope = scope; + + df = &functions[numfunctions]; + numfunctions++; + + df->s_file = 0; + df->s_name = PR_CopyString(scope->name, opt_noduplicatestrings ); + df->first_statement = numstatements; + df->parm_size[0] = 1; + df->numparms = 1; + df->parm_start = numpr_globals; + index = PR_GetDef(type_float, "indexg___", def, true, 1); + G_FUNCTION(scope->ofs) = df - functions; + + if (vectortrick) + { + def_t *div3, *intdiv3, *ret; + + // okay, we've got a function to retrieve the var as part of a vector. + // we need to work out which part, x/y/z that it's stored in. + // 0, 1, 2 = i - ((int)i/3 *) 3; + + div3 = PR_GetDef(type_float, "div3___", def, true, 1); + intdiv3 = PR_GetDef(type_float, "intdiv3___", def, true, 1); + + // escape clause - should call some sort of error function instead.. that'd rule! + eq = PR_Statement(pr_opcodes+OP_GE, index, PR_MakeFloatDef((float)def->arraysize), NULL); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 2; + PR_Statement(pr_opcodes+OP_RETURN, PR_MakeFloatDef(0), 0, &st); + + div3->references++; + PR_Statement3(pr_opcodes+OP_BITAND, index, index, index, false); + PR_Statement3(pr_opcodes+OP_DIV_F, index, PR_MakeFloatDef(3), div3, false); + PR_Statement3(pr_opcodes+OP_BITAND, div3, div3, intdiv3, false); + + PR_Statement3(pr_opcodes+OP_STORE_F, index, &def_parms[0], NULL, false); + PR_Statement3(pr_opcodes+OP_CALL1, vectortrick, NULL, NULL, false); + vectortrick->references++; + ret = PR_GetDef(type_vector, "vec__", pr_scope, true, 1); + ret->references += 4; + PR_Statement3(pr_opcodes+OP_STORE_V, &def_ret, ret, NULL, false); + + div3 = PR_Statement(pr_opcodes+OP_MUL_F, intdiv3, PR_MakeFloatDef(3), NULL); + PR_Statement3(pr_opcodes+OP_SUB_F, index, div3, index, false); + PR_FreeTemp(div3); + + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef(0+0.5f), NULL); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 2; + PR_Statement(pr_opcodes+OP_RETURN, 0, 0, &st); + st->a = ret->ofs + 0; + + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef(1+0.5f), NULL); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 2; + PR_Statement(pr_opcodes+OP_RETURN, 0, 0, &st); + st->a = ret->ofs + 1; + + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef(2+0.5), NULL); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 2; + PR_Statement(pr_opcodes+OP_RETURN, 0, 0, &st); + st->a = ret->ofs + 2; + PR_FreeTemp(ret); + PR_FreeTemp(index); + } + else + { + PR_Statement3(pr_opcodes+OP_BITAND, index, index, index, false); + PR_ArrayRecurseDivideRegular(def, index, 0, def->arraysize, false ); + } + + PR_Statement(pr_opcodes+OP_RETURN, PR_MakeFloatDef(0), 0, NULL); + PR_Statement(pr_opcodes+OP_DONE, 0, 0, NULL); + + locals_end = numpr_globals; + df->locals = locals_end - df->parm_start; + + PR_WriteAsmFunction(pr_scope, df->first_statement, df->parm_start); + PR_FreeTemps(); +} + +void PR_ArraySetRecurseDivide(def_t *array, def_t *index, def_t *value, int min, int max) +{ + dstatement_t *st; + def_t *eq; + + if (min == max || min+1 == max) + { + eq = PR_Statement(pr_opcodes+OP_EQ_F, index, PR_MakeFloatDef((float)min), NULL); + PR_UnFreeTemp(index); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + st->b = 3; + if (array->type->size == 3) PR_Statement(pr_opcodes+OP_STORE_V, value, array, &st); + else PR_Statement(pr_opcodes+OP_STORE_F, value, array, &st); + st->b = array->ofs + min*array->type->size; + PR_Statement(pr_opcodes+OP_RETURN, 0, 0, &st); + } + else + { + int mid = min + (max-min)/2; + + if (max-min>4) + { + eq = PR_Statement(pr_opcodes+OP_LT, index, PR_MakeFloatDef((float)mid), NULL); + PR_UnFreeTemp(index); + PR_FreeTemp(PR_Statement(pr_opcodes+OP_IFNOT, eq, 0, &st)); + } + else st = NULL; + PR_ArraySetRecurseDivide(array, index, value, min, mid); + if (st) st->b = numstatements - (st-statements); + PR_ArraySetRecurseDivide(array, index, value, mid, max); + } +} + +void PR_EmitArraySetFunction(def_t *scope, char *arrayname) +{ + dfunction_t *df; + def_t *def, *index, *value; + + def = PR_GetDef(NULL, arrayname, NULL, false, 0); + pr_scope = scope; + + df = &functions[numfunctions]; + numfunctions++; + + df->s_file = 0; + df->s_name = PR_CopyString(scope->name, opt_noduplicatestrings ); + df->first_statement = numstatements; + df->parm_size[0] = 1; + df->parm_size[1] = def->type->size; + df->numparms = 2; + df->parm_start = numpr_globals; + index = PR_GetDef(type_float, "indexs___", def, true, 1); + value = PR_GetDef(def->type, "value___", def, true, 1); + locals_end = numpr_globals; + df->locals = locals_end - df->parm_start; + G_FUNCTION(scope->ofs) = df - functions; + + PR_Statement3(pr_opcodes+OP_BITAND, index, index, index, false); + PR_ArraySetRecurseDivide(def, index, value, 0, def->arraysize); + PR_Statement(pr_opcodes+OP_DONE, 0, 0, NULL); + PR_WriteAsmFunction(pr_scope, df->first_statement, df->parm_start); + PR_FreeTemps(); +} + +/* +==================== +PR_DummyDef + +register a def, and all of it's sub parts. +only the main def is of use to the compiler. +the subparts are emitted to the compiler and allow correct saving/loading +be careful with fields, this doesn't allocated space, so will it allocate fields. +It only creates defs at specified offsets. +==================== +*/ +def_t *PR_DummyDef(type_t *type, char *name, def_t *scope, int arraysize, uint ofs, int referable) +{ + char array[64]; + char newname[256]; + int a; + def_t *def, *first=NULL; + + // check for macthing with keywords + if (name) PR_KeyWordValid( name, type ); + + for (a = 0; a < arraysize; a++) + { + if (a == 0) *array = '\0'; + else com.sprintf(array, "[%i]", a); + + if (name) com.sprintf(newname, "%s%s", name, array); + else *newname = *""; + + // allocate a new def + def = (void *)Qalloc (sizeof(def_t)); + def->next = NULL; + def->arraysize = arraysize; + if (name) + { + pr.def_tail->next = def; + pr.def_tail = def; + } + + if (a > 0) def->references++; + + def->s_line = pr_source_line; + def->s_file = s_file; + def->name = copystring( newname ); + def->type = type; + def->scope = scope; + def->constant = true; + + if (ofs + type->size*a >= MAX_REGS) PR_ParseError(ERR_INTERNAL, "MAX_REGS is too small"); + def->ofs = ofs + type->size*a; + if (!first) first = def; + + if (type->type == ev_struct) + { + int partnum; + type_t *parttype = type->param; + + for (partnum = 0; partnum < type->num_parms; partnum++) + { + switch (parttype->type) + { + case ev_vector: + com.sprintf(newname, "%s%s.%s", name, array, parttype->name); + PR_DummyDef(parttype, newname, scope, 1, ofs + type->size*a + parttype->ofs, false); + com.sprintf(newname, "%s%s.%s_x", name, array, parttype->name); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a + parttype->ofs, false); + com.sprintf(newname, "%s%s.%s_y", name, array, parttype->name); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a + parttype->ofs+1, false); + com.sprintf(newname, "%s%s.%s_z", name, array, parttype->name); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a + parttype->ofs+2, false); + break; + case ev_float: + case ev_string: + case ev_entity: + case ev_field: + case ev_pointer: + case ev_integer: + case ev_struct: + case ev_union: + com.sprintf( newname, "%s%s.%s", name, array, parttype->name ); + PR_DummyDef( parttype, newname, scope, 1, ofs + type->size*a + parttype->ofs, false ); + break; + case ev_function: + com.sprintf( newname, "%s%s.%s", name, array, parttype->name ); + PR_DummyDef( parttype, newname, scope, 1, ofs + type->size*a +parttype->ofs, false)->initialized = true; + break; + case ev_void: + break; + } + parttype = parttype->next; + } + } + else if( type->type == ev_vector ) + { + // do the vector thing. + com.sprintf(newname, "%s%s_x", name, array); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a+0, referable); + com.sprintf(newname, "%s%s_y", name, array); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a+1, referable); + com.sprintf(newname, "%s%s_z", name, array); + PR_DummyDef(type_float, newname, scope, 1, ofs + type->size*a+2, referable); + } + else if (type->type == ev_field) + { + if (type->aux_type->type == ev_vector) + { + // do the vector thing. + com.sprintf(newname, "%s%s_x", name, array); + PR_DummyDef(type_floatfield, newname, scope, 1, ofs + type->size*a+0, referable); + com.sprintf(newname, "%s%s_y", name, array); + PR_DummyDef(type_floatfield, newname, scope, 1, ofs + type->size*a+1, referable); + com.sprintf(newname, "%s%s_z", name, array); + PR_DummyDef(type_floatfield, newname, scope, 1, ofs + type->size*a+2, referable); + } + } + } + if (referable) + { + // anything above needs to be left in, and so warning about not using it is just going to pee people off. + if (!Hash_Get(&globalstable, "end_sys_fields")) first->references++; + if (arraysize <= 1) first->constant = false; + if (scope) Hash_Add(&localstable, first->name, first, Qalloc(sizeof(bucket_t))); + else Hash_Add(&globalstable, first->name, first, Qalloc(sizeof(bucket_t))); + if (!scope && asmfile) FS_Printf(asmfile, "%s %s;\n", TypeName(first->type), first->name); + } + return first; +} + +/* +============ +PR_GetDef + +If type is NULL, it will match any type +If allocate is true, a new def will be allocated if it can't be found +============ +*/ +def_t *PR_GetDef (type_t *type, char *name, def_t *scope, bool allocate, int arraysize) +{ + int ofs; + def_t *def; + uint i; + + if (scope) + { + def = Hash_Get(&localstable, name); + while(def) + { + if ( def->scope && def->scope != scope) + { + def = Hash_GetNext(&localstable, name, def); + continue; // in a different function + } + + if (type && typecmp(def->type, type)) PR_ParseError (ERR_TYPEMISMATCHREDEC, "Type mismatch on redeclaration of %s. %s, should be %s",name, TypeName(type), TypeName(def->type)); + if (def->arraysize != arraysize && arraysize) + PR_ParseError (ERR_TYPEMISMATCHARRAYSIZE, "Array sizes for redecleration of %s do not match",name); + if (allocate && scope) + { + PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s duplicate, second definition ignored", name); + PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); + } + return def; + } + } + + def = Hash_Get(&globalstable, name); + while(def) + { + if ( def->scope && def->scope != scope) + { + def = Hash_GetNext(&globalstable, name, def); + continue; // in a different function + } + + if (type && typecmp(def->type, type)) + { + if (!pr_scope) PR_ParseError (ERR_TYPEMISMATCHREDEC, "Type mismatch on redeclaration of %s. %s, should be %s",name, TypeName(type), TypeName(def->type)); + } + if (def->arraysize != arraysize && arraysize) + PR_ParseError (ERR_TYPEMISMATCHARRAYSIZE, "Array sizes for redecleration of %s do not match",name); + if (allocate && scope) + { + if (pr_scope) + { + // warn? or would that be pointless? + def = Hash_GetNext(&globalstable, name, def); + continue; // in a different function + } + + PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s duplicate, second definition ignored", name); + PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); + } + return def; + } + + if (Hash_Get != &Hash_Get && !allocate) // do we want to try case insensative too? + { + if (scope) + { + def = Hash_Get(&localstable, name); + while(def) + { + if ( def->scope && def->scope != scope) + { + def = Hash_GetNext(&localstable, name, def); + continue; // in a different function + } + + if (type && typecmp(def->type, type)) + PR_ParseError (ERR_TYPEMISMATCHREDEC, "Type mismatch on redeclaration of %s. %s, should be %s",name, TypeName(type), TypeName(def->type)); + if (def->arraysize != arraysize && arraysize) + PR_ParseError (ERR_TYPEMISMATCHARRAYSIZE, "Array sizes for redecleration of %s do not match",name); + if (allocate && scope) + { + PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s duplicate, second definition ignored", name); + PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); + } + return def; + } + } + + def = Hash_Get(&globalstable, name); + while(def) + { + if ( def->scope && def->scope != scope) + { + def = Hash_GetNext(&globalstable, name, def); + continue; // in a different function + } + + if (type && typecmp(def->type, type)) + { + if (!pr_scope) PR_ParseError (ERR_TYPEMISMATCHREDEC, "Type mismatch on redeclaration of %s. %s, should be %s",name, TypeName(type), TypeName(def->type)); + } + if (def->arraysize != arraysize && arraysize) + PR_ParseError (ERR_TYPEMISMATCHARRAYSIZE, "Array sizes for redecleration of %s do not match",name); + if (allocate && scope) + { + if (pr_scope) + { + // warn? or would that be pointless? + def = Hash_GetNext(&globalstable, name, def); + continue; // in a different function + } + + PR_ParseWarning (WARN_DUPLICATEDEFINITION, "%s duplicate, second definition ignored", name); + PR_ParsePrintDef(WARN_DUPLICATEDEFINITION, def); + } + return def; + } + } + + // quake1 compatiable opcode don't create new type and cause to exception + if (!allocate || !type) return NULL; + if (arraysize < 1) PR_ParseError (ERR_ARRAYNEEDSSIZE, "First declaration of array %s with no size",name); + + /*if (scope && PR_GetDef(type, name, NULL, false, arraysize)) + { + PR_ParseWarning(WARN_SAMENAMEASGLOBAL, "Local \"%s\" defined with name of a global", name); + }*/ + + ofs = numpr_globals; + if (arraysize > 1) + { + // write the array size + ofs = PR_GetFreeOffsetSpace(1 + (type->size * arraysize)); + // An array needs the size written first. This is a hexen2 opcode thing. + ((int *)pr_globals)[ofs] = arraysize - 1; + ofs++; + } + else ofs = PR_GetFreeOffsetSpace(type->size * arraysize); + + def = PR_DummyDef(type, name, scope, arraysize, ofs, true); + + // fix up fields. + if (type->type == ev_field && allocate != 2) + { + for (i = 0; i < type->size*arraysize; i++) // make arrays of fields work. + *(int *)&pr_globals[def->ofs+i] = pr.size_fields+i; + pr.size_fields += i; + } + + if (scope) + { + def->nextlocal = pr.localvars; + pr.localvars = def; + def->local = true; + } + return def; +} + +def_t *PR_DummyFieldDef(type_t *type, char *name, def_t *scope, int arraysize, uint *fieldofs) +{ + char array[64]; + char newname[256]; + int a, parms; + def_t *def, *first=NULL; + uint startfield = *fieldofs; + uint maxfield = startfield; + type_t *ftype; + bool isunion; + + for (a = 0; a < arraysize; a++) + { + if (a == 0) *array = '\0'; + else com.sprintf(array, "[%i]", a); + + if (*name) + { + com.sprintf(newname, "%s%s", name, array); + + // allocate a new def + def = (void *)Qalloc (sizeof(def_t)); + def->next = NULL; + def->arraysize = arraysize; + + pr.def_tail->next = def; + pr.def_tail = def; + + def->s_line = pr_source_line; + def->s_file = s_file; + + def->name = copystring( newname ); + def->type = type; + def->scope = scope; + def->ofs = PR_GetFreeOffsetSpace(1); + ((int *)pr_globals)[def->ofs] = *fieldofs; + *fieldofs++; + if (!first) first = def; + } + else def = NULL; + + if ((type)->type == ev_struct || (type)->type == ev_union) + { + int partnum; + type_t *parttype; + + if (def) def->references++; + parttype = (type)->param; + isunion = ((type)->type == ev_union); + + for (partnum = 0, parms = (type)->num_parms; partnum < parms; partnum++) + { + switch (parttype->type) + { + case ev_union: + case ev_struct: + if (*name) com.sprintf(newname, "%s%s.%s", name, array, parttype->name); + else com.sprintf(newname, "%s%s", parttype->name, array); + def = PR_DummyFieldDef(parttype, newname, scope, 1, fieldofs); + break; + case ev_float: + case ev_string: + case ev_vector: + case ev_entity: + case ev_field: + case ev_pointer: + case ev_integer: + if( *name ) com.sprintf( newname, "%s%s.%s", name, array, parttype->name ); + else com.sprintf( newname, "%s%s", parttype->name, array ); + ftype = PR_NewType( "FIELD TYPE", ev_field ); + ftype->aux_type = parttype; + // vector fields create a _y and _z too, so we need this still. + if( parttype->type == ev_vector ) ftype->size = parttype->size; + def = PR_GetDef( NULL, newname, scope, false, 1 ); + if( !def ) def = PR_GetDef( ftype, newname, scope, true, 1 ); + else + { + PR_ParseWarning( WARN_CONFLICTINGUNIONMEMBER, "conflicting offsets for union/struct expansion of %s. Ignoring new def.", newname ); + PR_ParsePrintDef( WARN_CONFLICTINGUNIONMEMBER, def ); + } + break; + case ev_function: + if (*name) com.sprintf(newname, "%s%s.%s", name, array, parttype->name); + else com.sprintf(newname, "%s%s", parttype->name, array); + ftype = PR_NewType("FIELD TYPE", ev_field); + ftype->aux_type = parttype; + def = PR_GetDef(ftype, newname, scope, true, 1); + def->initialized = true; + ((int *)pr_globals)[def->ofs] = *fieldofs; + *fieldofs += parttype->size; + break; + case ev_void: + break; + } + if (*fieldofs > maxfield) maxfield = *fieldofs; + if (isunion) *fieldofs = startfield; + + type = parttype; + parttype = parttype->next; + } + } + } + + *fieldofs = maxfield; // final size of the union. + return first; +} + + + +void PR_ExpandUnionToFields(type_t *type, int *fields) +{ + type_t *pass = type->aux_type; + PR_DummyFieldDef(pass, "", pr_scope, 1, fields); +} + +/* +================ +PR_ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void PR_ParseDefs (char *classname) +{ + char *name; + type_t *type, *parm; + def_t *def, *d; + function_t *f; + dfunction_t *df; + int i; + bool shared = false; + bool externfnc = false; + bool isconstant = false; + bool nosave = false; + bool allocatenew = true; + int ispointer; + gofs_t oldglobals; + int arraysize; + + if(PR_CheckForKeyword( KEYWORD_ENUM )) + { + float v = 0; + PR_Expect("{"); + i = 0; + d = NULL; + while(1) + { + name = PR_ParseName(); + if (PR_CheckToken("=")) + { + if (pr_token_type != tt_immediate && pr_immediate_type->type != ev_float) + { + def = PR_GetDef(NULL, PR_ParseName(), NULL, false, 0); + if (def) + { + if (!def->constant) PR_ParseError(ERR_NOTANUMBER, "enum - %s is not a constant", def->name); + else v = G_FLOAT(def->ofs); + } + else PR_ParseError(ERR_NOTANUMBER, "enum - not a number"); + } + else + { + v = pr_immediate._float; + PR_Lex(); + } + } + def = PR_MakeFloatDef(v); + Hash_Add(&globalstable, name, def, Qalloc(sizeof(bucket_t))); + v++; + + if (PR_CheckToken("}")) break; + PR_Expect(","); + } + PR_Expect(";"); + return; + } + + if (PR_CheckForKeyword( KEYWORD_ENUMFLAGS )) + { + float v = 1; + int bits; + PR_Expect("{"); + i = 0; + d = NULL; + while(1) + { + name = PR_ParseName(); + if (PR_CheckToken("=")) + { + if (pr_token_type != tt_immediate && pr_immediate_type->type != ev_float) + { + def = PR_GetDef(NULL, PR_ParseName(), NULL, false, 0); + if (def) + { + if (!def->constant) + PR_ParseError(ERR_NOTANUMBER, "enumflags - %s is not a constant", def->name); + else v = G_FLOAT(def->ofs); + } + else PR_ParseError(ERR_NOTANUMBER, "enumflags - not a number"); + } + else + { + v = pr_immediate._float; + PR_Lex(); + } + } + + bits = 0; + i = (int)v; + if (i != v) PR_ParseWarning(WARN_ENUMFLAGS_NOTINTEGER, "enumflags - %f not an integer", v); + else + { + while(i) + { + if (((i>>1)<<1) != i) bits++; + i>>=1; + } + if (bits != 1) PR_ParseWarning(WARN_ENUMFLAGS_NOTBINARY, "enumflags - value %i not a single bit", (int)v); + } + + def = PR_MakeFloatDef(v); + Hash_Add(&globalstable, name, def, Qalloc(sizeof(bucket_t))); + + v *= 2; + if (PR_CheckToken("}")) break; + PR_Expect(","); + } + PR_Expect(";"); + return; + } + if (PR_CheckForKeyword( KEYWORD_TYPEDEF )) + { + type = PR_ParseType( true ); + if (!type) PR_ParseError(ERR_NOTANAME, "typedef found unexpected tokens"); + type->name = PR_CopyString(pr_token, opt_noduplicatestrings ) + strings; + PR_Lex(); + PR_Expect(";"); + return; + } + while(1) + { + if( PR_CheckForKeyword( KEYWORD_EXTERN )) externfnc = true; + else if( PR_CheckForKeyword( KEYWORD_SHARED )) + { + shared = true; + if( pr_scope ) PR_ParseError( ERR_NOSHAREDLOCALS, "cannot have shared locals" ); + } + else if( PR_CheckForKeyword( KEYWORD_CONST )) isconstant = true; + else if( PR_CheckForKeyword( KEYWORD_NOSAVE )) nosave = true; + else break; + } + + type = PR_ParseType (false); + if (type == NULL) return; + + if (externfnc && type->type != ev_function) + { + PR_Message("Only functions may be defined as external (yet)\n"); + externfnc = false; + } + do + { + if( PR_CheckToken( "*" )) + { + ispointer = 1; + while(PR_CheckToken ("*")) ispointer++; + name = PR_ParseName (); + } + else if (PR_CheckToken (";")) + { + if (type->type == ev_field && (type->aux_type->type == ev_union || type->aux_type->type == ev_struct)) + { + PR_ExpandUnionToFields(type, &pr.size_fields); + return; + } + PR_ParseError (ERR_TYPEWITHNONAME, "type with no name"); + name = NULL; + ispointer = false; + } + else + { + name = PR_ParseName (); + ispointer = false; + } + if (PR_CheckToken("::") && !classname) + { + classname = name; + name = PR_ParseName(); + } + + // check for an array + if ( PR_CheckToken ("[") ) + { + char *oldprfile = pr_file_p; + arraysize = 0; + if (PR_CheckToken("]")) + { + PR_Expect("="); + PR_Expect("{"); + PR_Lex(); + arraysize++; + while(1) + { + if(pr_token_type == tt_eof) break; + if (PR_CheckToken(",")) arraysize++; + if (PR_CheckToken("}")) break; + PR_Lex(); + } + pr_file_p = oldprfile; + PR_Lex(); + } + else + { + def = PR_Expression(TOP_PRIORITY, true); + if (!def->constant) PR_ParseError(ERR_BADARRAYSIZE, "Array size is not a constant value"); + else if (def->type->type == ev_integer) arraysize = G_INT(def->ofs); + else if (def->type->type == ev_float) + { + arraysize = (int)G_FLOAT(def->ofs); + if ((float)arraysize != G_FLOAT(def->ofs)) + PR_ParseError(ERR_BADARRAYSIZE, "Array size is not a constant value"); + } + else PR_ParseError(ERR_BADARRAYSIZE, "Array size must be of int value"); + } + if (arraysize < 1) + { + PR_ParseError (ERR_BADARRAYSIZE, "Definition of array (%s) size is not of a numerical value", name); + arraysize = 0; // grrr... + } + PR_Expect("]"); + } + else arraysize = 1; + + if (PR_CheckToken("(")) type = PR_ParseFunctionType(false, type); + + if( classname ) + { + char *membername = name; + name = Qalloc(com.strlen(classname) + com.strlen(name) + 3); + com.sprintf(name, "%s::"MEMBERFIELDNAME, classname, membername); + if (!PR_GetDef(NULL, name, NULL, false, 0)) + PR_ParseError(ERR_NOTANAME, "%s %s is not a member of class %s\n", TypeName(type), membername, classname); + com.sprintf(name, "%s::%s", classname, membername); + + pr_classtype = TypeForName(classname); + if (!pr_classtype || !pr_classtype->parentclass) + PR_ParseError(ERR_NOTANAME, "%s is not a class\n", classname); + } + else pr_classtype = NULL; + + oldglobals = numpr_globals; + if (ispointer) + { + parm = type; + while(ispointer) + { + ispointer--; + parm = PR_PointerTypeTo(parm); + } + def = PR_GetDef (parm, name, pr_scope, allocatenew, arraysize); + } + else def = PR_GetDef (type, name, pr_scope, allocatenew, arraysize); + + if (!def) PR_ParseError(ERR_NOTANAME, "%s is not part of class %s", name, classname); + + if (nosave) def->saved = false; + else def->saved = true; + + // shared count as initiialized + if (!def->initialized && shared) + { + def->shared = shared; + def->initialized = true; + } + if (externfnc) def->initialized = 2; + + // check for an initialization + if (type->type == ev_function && (pr_scope)) + { + if ( PR_CheckToken ("=") ) + PR_ParseError (ERR_INITIALISEDLOCALFUNCTION, "local functions may not be initialised"); + + arraysize = def->arraysize; + d = def; //apply to ALL elements + while(arraysize--) + { + d->initialized = 1; //fake function + G_FUNCTION(d->ofs) = 0; + d = d->next; + } + continue; + } + // this is an initialisation (or a function) + if ( PR_CheckToken ("=") || ((type->type == ev_function) && (pr_token[0] == '{' || pr_token[0] == '[' || pr_token[0] == ':'))) + { + if (def->shared) + PR_ParseError (ERR_SHAREDINITIALISED, "shared values may not be assigned an initial value", name); + if (def->initialized == 1) + { + if (def->type->type == ev_function) + { + i = G_FUNCTION(def->ofs); + df = &functions[i]; + PR_ParseErrorPrintDef (ERR_REDECLARATION, def, "%s redeclared, prev instance is in %s", name, strings+df->s_file); + } + else PR_ParseErrorPrintDef(ERR_REDECLARATION, def, "%s redeclared", name); + } + + if (autoprototype) + { + // ignore the code and stuff + if (PR_CheckToken("[")) + { + while (!PR_CheckToken("]")) + { + if (pr_token_type == tt_eof) break; + PR_Lex(); + } + } + if (PR_CheckToken("{")) + { + int blev = 1; + // balance out the { and } + while(blev) + { + if (pr_token_type == tt_eof) break; + if (PR_CheckToken("{")) blev++; + else if (PR_CheckToken("}")) blev--; + else PR_Lex(); // ignore it. + } + } + else + { + PR_CheckToken("#"); + PR_Lex(); + } + continue; + } + + if (pr_token_type == tt_name) + { + uint i; + + if (def->arraysize>1) + PR_ParseError(ERR_ARRAYNEEDSBRACES, "Array initialisation requires curly braces"); + + d = PR_GetDef(NULL, pr_token, pr_scope, false, 0); + if (!d) PR_ParseError(ERR_NOTDEFINED, "%s was not defined\n", name); + if (typecmp(def->type, d->type)) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + for (i = 0; i < d->type->size; i++) G_INT(def->ofs) = G_INT(d->ofs); + PR_Lex(); + + if( type->type == ev_function ) + { + def->initialized = 1; + def->constant = true; + } + continue; + } + else if( type->type == ev_function ) + { + def->constant = true; + + if (PR_CheckToken("0")) + { + // fake function + def->constant = 0; + def->initialized = 1; + G_FUNCTION(def->ofs) = 0; + continue; + } + if (!def->constant && arraysize == 1) + { + // fake function + def->constant = 0; + def->initialized = 1; + + name = PR_ParseName (); + d = PR_GetDef (NULL, name, pr_scope, false, 0); + if (!d) PR_ParseError(ERR_NOTDEFINED, "%s was not previously defined", name); + G_FUNCTION(def->ofs+i) = G_FUNCTION(d->ofs); + continue; + } + + if (arraysize > 1) + { + int i; + + // fake function + def->initialized = 1; + PR_Expect ("{"); + i = 0; + + do + { + if (PR_CheckToken("0")) G_FUNCTION(def->ofs+i) = 0; + else + { + name = PR_ParseName (); + d = PR_GetDef (NULL, name, pr_scope, false, 0); + if (!d) PR_ParseError(ERR_NOTDEFINED, "%s was not defined", name); + else + { + if (!d->initialized) + PR_ParseWarning(WARN_NOTDEFINED, "initialisation of function arrays must be placed after the body of all functions used (%s)", name); + G_FUNCTION(def->ofs+i) = G_FUNCTION(d->ofs); + } + } + i++; + } while(PR_CheckToken(",")); + + arraysize = def->arraysize; + d = def; // apply to ALL elements + while(arraysize--) + { + d->initialized = 1; //fake function + d = d->next; + } + + PR_Expect("}"); + if (i > def->arraysize) PR_ParseError(ERR_TOOMANYINITIALISERS, "Too many initializers"); + continue; + } + if (!def->constant) PR_ParseError(0, "Initialised functions must be constant"); + + def->references++; + pr_scope = def; + f = PR_ParseImmediateStatements (type); + pr_scope = NULL; + def->initialized = 1; + G_FUNCTION(def->ofs) = numfunctions; + f->def = def; + + // fill in the dfunction + df = &functions[numfunctions]; + numfunctions++; + if (f->builtin) df->first_statement = -f->builtin; + else df->first_statement = f->code; + + if (f->builtin && opt_function_names); + else df->s_name = PR_CopyString (f->def->name, opt_noduplicatestrings ); + df->s_file = s_file2; + df->numparms = f->def->type->num_parms; + df->locals = locals_end - locals_start; + df->parm_start = locals_start; + for (i=0,parm = type->param ; inumparms ; i++, parm = parm->next) + { + df->parm_size[i] = parm->size; + } + continue; + } + + else if (type->type == ev_struct) + { + int arraypart, partnum; + type_t *parttype; + def->initialized = 1; + def->constant = false; + + // FIXME: should do this recursivly + PR_Expect("{"); + for (arraypart = 0; arraypart < arraysize; arraypart++) + { + parttype = type->param; + PR_Expect("{"); + for (partnum = 0; partnum < type->num_parms; partnum++) + { + switch (parttype->type) + { + case ev_float: + case ev_integer: + case ev_vector: + if (pr_token_type == tt_punct) + { + if (PR_CheckToken("{")) PR_Expect("}"); + else PR_ParseError(ERR_UNEXPECTEDPUNCTUATION, "Unexpected punctuation"); + + } + else if (pr_token_type == tt_immediate) + { + if (pr_immediate_type->type == ev_float && parttype->type == ev_integer) + G_INT(def->ofs + arraypart*type->size + parttype->ofs) = (int)pr_immediate._float; + else if (pr_immediate_type->type != parttype->type) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate subtype for %s.%s", def->name, parttype->name); + else Mem_Copy (pr_globals + def->ofs + arraypart*type->size + parttype->ofs, &pr_immediate, 4*type_size[pr_immediate_type->type]); + } + else if (pr_token_type == tt_name) + { + d = PR_GetDef(NULL, pr_token, pr_scope, false, 0); + if (!d) PR_ParseError(ERR_NOTDEFINED, "%s was not defined\n", pr_token); + else if (d->type->type != parttype->type) + PR_ParseError (ERR_WRONGSUBTYPE, "wrong subtype for %s.%s", def->name, parttype->name); + else if (!d->constant) PR_ParseError(ERR_NOTACONSTANT, "%s isn't a constant\n", pr_token); + Mem_Copy (pr_globals + def->ofs + arraypart*type->size + parttype->ofs, pr_globals + d->ofs, 4*d->type->size); + } + else PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate subtype for %s.%s", def->name, parttype->name); + PR_Lex (); + break; + case ev_string: + if (pr_token_type == tt_punct) + { + if (PR_CheckToken("{")) + { + uint i; + for (i = 0; i < parttype->size; i++) + { + G_INT(def->ofs+arraypart*type->size+parttype->ofs+i) = PR_CopyString(pr_immediate_string, opt_noduplicatestrings ); + PR_Lex (); + + if (!PR_CheckToken(",")) + { + i++; + break; + } + } + for (; i < parttype->size; i++) + { + G_INT(def->ofs+arraypart*type->size+parttype->ofs+i) = 0; + } + PR_Expect("}"); + } + else + PR_ParseError(ERR_UNEXPECTEDPUNCTUATION, "Unexpected punctuation"); + } + else + { + G_INT(def->ofs+arraypart*type->size+parttype->ofs) = PR_CopyString(pr_immediate_string, opt_noduplicatestrings ); + PR_Lex (); + } + break; + case ev_function: + if (pr_token_type == tt_immediate) + { + if (pr_immediate._int != 0) + PR_ParseError(ERR_NOTFUNCTIONTYPE, "Expected function name or NULL"); + G_FUNCTION(def->ofs+arraypart*type->size+parttype->ofs) = 0; + PR_Lex(); + } + else + { + name = PR_ParseName (); + + d = PR_GetDef (NULL, name, pr_scope, false, 0); + if (!d) PR_ParseError(ERR_NOTDEFINED, "%s was not defined\n", name); + else G_FUNCTION(def->ofs+arraypart*type->size+parttype->ofs) = G_FUNCTION(d->ofs); + } + break; + default: + PR_ParseError(ERR_TYPEINVALIDINSTRUCT, "type %i not valid in a struct", parttype->type); + PR_Lex(); + break; + } + if (!PR_CheckToken(",")) break; + parttype = parttype->next; + } + PR_Expect("}"); + if (!PR_CheckToken(",")) break; + } + PR_Expect("}"); + continue; + } + else if( type->type == ev_integer ) + { + def->constant = true; + + def->initialized = 1; + Mem_Copy (pr_globals + def->ofs, &pr_immediate, 4*type_size[pr_immediate_type->type]); + PR_Lex(); + + if (pr_immediate_type->type == ev_float) G_INT(def->ofs) = (int)pr_immediate._float; + else if (pr_immediate_type->type != ev_integer) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + continue; + } + else if (type->type == ev_string) + { + if (arraysize>=1 && PR_CheckToken("{")) + { + int i; + + for (i = 0; i < arraysize; i++) + { + // the executor defines strings as true c strings, but reads in index from string table. + // structures can hide these strings. + if (i != 0) // not for the first entry - already a string def for that + { + d = (void *)Qalloc (sizeof(def_t)); + d->next = NULL; + pr.def_tail->next = d; + pr.def_tail = d; + + d->type = type_string; + d->name = "IMMEDIATE"; + d->constant = true; + d->initialized = 1; + d->scope = NULL; + + d->ofs = def->ofs+i; + if (d->ofs >= MAX_REGS) PR_ParseError(ERR_INTERNAL, "MAX_REGS is too small"); + } + (((int *)pr_globals)[def->ofs+i]) = PR_CopyString(pr_immediate_string, opt_noduplicatestrings ); + PR_Lex (); + if (!PR_CheckToken(",")) break; + } + PR_Expect("}"); + continue; + } + else if (arraysize<=1) + { + def->constant = true; + def->initialized = 1; + (((int *)pr_globals)[def->ofs]) = PR_CopyString(pr_immediate_string, opt_noduplicatestrings ); + PR_Lex(); + + if (pr_immediate_type->type == ev_float) G_INT(def->ofs) = (int)pr_immediate._float; + else if (pr_immediate_type->type != ev_string) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + continue; + } + else PR_ParseError(ERR_ARRAYNEEDSBRACES, "Array initialization needs curly braces"); + } + else if (type->type == ev_float) + { + if (arraysize >=1 && PR_CheckToken("{")) + { + int i; + for (i = 0; i < arraysize; i++) + { + if (pr_immediate_type->type != ev_float) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + (((float *)pr_globals)[def->ofs+i]) = pr_immediate._float; + PR_Lex (); + if (!PR_CheckToken(",")) break; + } + PR_Expect("}"); + continue; + } + else if (arraysize<=1) + { + def->constant = true; + def->initialized = 1; + + if (pr_immediate_type->type != ev_float) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + + if (def->constant && opt_dupconstdefs) + { + if (def->ofs == oldglobals) + { + Hash_GetKey(&floatconstdefstable, *(int*)&pr_immediate._float); + PR_FreeOffset(def->ofs, def->type->size); + d = PR_MakeFloatDef(pr_immediate._float); + d->references++; + def->ofs = d->ofs; + PR_Lex(); + continue; + } + } + (((float *)pr_globals)[def->ofs]) = pr_immediate._float; + PR_Lex (); + continue; + } + else PR_ParseError(ERR_ARRAYNEEDSBRACES, "Array initialisation requires curly brasces"); + } + else if (type->type == ev_vector) + { + if (arraysize >= 1 && PR_CheckToken("{")) + { + int i; + for (i = 0; i < arraysize; i++) + { + if (pr_immediate_type->type != ev_vector) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + (((float *)pr_globals)[def->ofs+i*3+0]) = pr_immediate.vector[0]; + (((float *)pr_globals)[def->ofs+i*3+1]) = pr_immediate.vector[1]; + (((float *)pr_globals)[def->ofs+i*3+2]) = pr_immediate.vector[2]; + PR_Lex (); + + if (!PR_CheckToken(",")) break; + } + PR_Expect("}"); + continue; + } + else if (arraysize<=1) + { + def->constant = true; + def->initialized = 1; + (((float *)pr_globals)[def->ofs+0]) = pr_immediate.vector[0]; + (((float *)pr_globals)[def->ofs+1]) = pr_immediate.vector[1]; + (((float *)pr_globals)[def->ofs+2]) = pr_immediate.vector[2]; + PR_Lex (); + + if (pr_immediate_type->type != ev_vector) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s", name); + continue; + } + else PR_ParseError(ERR_ARRAYNEEDSBRACES, "Array initialization needs curly braces"); + } + else if (pr_token_type == tt_name) + { + d = PR_GetDef (NULL, pr_token, pr_scope, false, 0); + if (!d) PR_ParseError (ERR_NOTDEFINED, "initialisation name not defined : %s", pr_token); + if (!d->constant) + { + PR_ParseWarning (WARN_NOTCONSTANT, "initialisation name not a constant : %s", pr_token); + PR_ParsePrintDef(WARN_NOTCONSTANT, d); + } + Mem_Copy(def, d, sizeof(*d)); + def->name = name; + def->initialized = true; + } + else if (pr_token_type != tt_immediate) + PR_ParseError (ERR_BADIMMEDIATETYPE, "not an immediate for %s - %s", name, pr_token); + else if (pr_immediate_type->type != type->type) + PR_ParseError (ERR_BADIMMEDIATETYPE, "wrong immediate type for %s - %s", name, pr_token); + else Mem_Copy (pr_globals + def->ofs, &pr_immediate, 4*type_size[pr_immediate_type->type]); + + def->constant = true; + def->initialized = true; + PR_Lex (); + } + else + { + // special flag on fields, 2, makes the pointer obtained from them also constant. + if (isconstant && type->type == ev_field) + def->constant = 2; + else def->constant = isconstant; + } + + } while (PR_CheckToken (",")); + + if (type->type == ev_function) PR_CheckToken (";"); + else + { + if (!PR_CheckToken (";")) + PR_ParseWarning(WARN_UNDESIRABLECONVENTION, "Missing semicolon at end of definition"); + } +} + +/* +============ +PR_CompileFile + +compiles the 0 terminated text, adding defintions to the pr structure +============ +*/ +void PR_CompileFile( char *string, char *filename ) +{ + jmp_buf abort_parse; + + pr_error_count = 0; // reset counter for new file + PR_ClearGrabMacros();// clear the frame macros + compilingfile = filename; + + if( opt_filenames ) + { + pr_file_p = copystring( filename ); + s_file = pr_file_p - strings; + s_file2 = 0; + } + else s_file = s_file2 = PR_CopyString( filename, opt_noduplicatestrings ); + + pr_file_p = string; + pr_source_line = 0; + PR_NewLine( false ); + PR_Lex(); // read first token + + // save state + Mem_Copy(&abort_parse, &pr_parse_abort, sizeof(abort_parse)); + + while( pr_token_type != tt_eof ) + { + if (setjmp(pr_parse_abort)) + { + pr_total_error_count++; // increase total err counter + pr_error_count++; //increase local err counter + if (pr_error_count > MAX_ERRORS) + { + PR_ParseWarning(ERR_EXCEEDERRCOUNT, "error count exceeds %i; stopping compilation\n", MAX_ERRORS); + Mem_Copy(&pr_parse_abort, &abort_parse, sizeof(abort_parse)); + return; + } + PR_SkipToSemicolon(); + if (pr_token_type == tt_eof) + { + Mem_Copy(&pr_parse_abort, &abort_parse, sizeof(abort_parse)); + return; + } + } + // outside all functions + pr_scope = NULL; + PR_ParseDefs( NULL ); + } + + PR_GetEntvarsName(); // set entavrs default name e.g. "self" + // restore state + Mem_Copy(&pr_parse_abort, &abort_parse, sizeof(abort_parse)); +} + +/* +============== +PR_FinishCompilation + +called after all files are compiled to check for errors +Returns false if errors were detected. +============== +*/ +void PR_FinishCompilation( void ) +{ + def_t *d; + int errors = pr_total_error_count; + + currentchunk = NULL; + + // check to make sure all functions prototyped have code + for( d = pr.def_head.next; d; d = d->next ) + { + if (d->type->type == ev_function && !d->scope)// function parms are ok + { + if (d->initialized == 0) + { + if (!com.strncmp(d->name, "ArrayGet*", 9)) + { + PR_EmitArrayGetFunction(d, d->name + 9); + pr_scope = NULL; + } + else if (!com.strncmp(d->name, "ArraySet*", 9)) + { + PR_EmitArraySetFunction(d, d->name + 9); + pr_scope = NULL; + } + else if (!com.strncmp(d->name, "Class*", 6)) + { + PR_EmitClassFromFunction(d, d->name + 6); + pr_scope = NULL; + } + else + { + PR_Warning(WARN_NOTDEFINED, strings + d->s_file, d->s_line, "function %s was not defined",d->name); + bodylessfuncs = true; + } + } + else if (d->initialized == 2) bodylessfuncs = true; + } + } + + pr_scope = NULL; + // compilation failed ? + if( errors ) + { + string errormsg; + com.sprintf( errormsg, "%s - %i error(s), %i warning(s)\n", progsoutname, pr_total_error_count, pr_warning_count ); + if( host_instance == HOST_NORMAL || host_instance == HOST_DEDICATED ) + { + PR_Message( errormsg ); + prvm_state = comp_error; // abort compilation + longjmp( pr_int_error, 1 ); + } + else Sys_Break( errormsg ); + return; + } + PR_WriteDAT(); + + PR_Message( "‘ª®¯¨à®¢ ­® ä ©«®¢: 1.\n\n");// enigma from M$ :) + PR_Message( "%s - %i error(s), %i warning(s)\n", progsoutname, pr_total_error_count, pr_warning_count ); +} + +/* +============== +PR_ContinueCompilation + +it will return false, when compilation is finished +called between exe frames - won't loose net connection (is the theory)... +============== +*/ +bool PR_ContinueCompile( void ) +{ + char *qc_file; + + if( !progs_src ) return false; + currentchunk = NULL; + PR_ParseToken( &progs_src, true ); + + if(!progs_src) + { + if (autoprototype) + { + progs_src = saved_progs_src; + PR_SetDefaultProperties(); + autoprototype = false; + PR_Message("\nCompiling...\n"); + return true; + } + return false; // end of compile + } + + PR_Message( "%s\n", pr_token ); + qc_file = QCC_LoadFile( pr_token, true ); + if( prvm_state == comp_error ) + return false; + PR_CompileFile( qc_file, pr_token ); + + return true; +} + + +/* +============== +PR_BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void PR_BeginCompilation ( void ) +{ + string compilename; + + pr.def_tail = &pr.def_head; + pr_scope = NULL; + + type_void = PR_NewType("void", ev_void); + type_string = PR_NewType("string", ev_string); + type_float = PR_NewType("float", ev_float); + type_vector = PR_NewType("vector", ev_vector); + type_entity = PR_NewType("entity", ev_entity); + type_field = PR_NewType("field", ev_field); + type_function = PR_NewType("function", ev_function); + type_pointer = PR_NewType("pointer", ev_pointer); + type_integer = PR_NewType("__integer", ev_integer); + type_floatfield = PR_NewType("fieldfloat", ev_field); + type_pointer->aux_type = PR_NewType("pointeraux", ev_float); + if (PR_KeywordEnabled(KEYWORD_INT)) PR_NewType("int", ev_integer); + + type_floatfield->aux_type = type_float; + type_function->aux_type = type_void; + + pr.types = NULL; + pr_error_count = 0; + pr_total_error_count = 0; + pr_warning_count = 0; + recursivefunctiontype = 0; + numpr_globals = RESERVED_OFS; // default + + freeofs = NULL; + progs_src = QCC_LoadFile( "progs.src", false );// loading progs.src + if(!progs_src) progs_src = PR_CreateProgsSRC(); // virtual list + + while(*progs_src && *progs_src < ' ') progs_src++; + + pr_file_p = PR_ParseToken( &progs_src, true ); + com.strcpy( progsoutname, pr_token ); + + // this progs.src was written by qcclib without sorting + if(!com.stricmp(progsoutname, "unknown.dat" )) + { + // ready to compile any version + autoprototype = true; + } + FS_FileBase( pr_token, compilename ); + + if (FS_CheckParm("-asm")) asmfile = FS_Open(va("%s.asm", pr_token), "wb" ); + + // msvc6.0 style message + PR_Message("------------Configuration: %s - Vm32 %s------------\n", compilename, opt_writelinenums ? "Debug" : "Release" ); + + currentchunk = NULL; + saved_progs_src = progs_src; // save it for prototyping + + if (autoprototype) PR_Message ("Prototyping...\n"); + else PR_Message("Compiling...\n"); +} diff --git a/vprogs/pr_edict.c b/vprogs/pr_edict.c new file mode 100644 index 00000000..cd21c143 --- /dev/null +++ b/vprogs/pr_edict.c @@ -0,0 +1,2058 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// pr_edict.c - vm runtime base +//======================================================================= + +#include "vprogs.h" + +static prvm_prog_t prog_list[PRVM_MAXPROGS]; +sizebuf_t vm_tempstringsbuf; + +int prvm_type_size[8] = {1, sizeof(string_t)/4,1,3,1,1, sizeof(func_t)/4, sizeof(void *)/4}; + +ddef_t *PRVM_ED_FieldAtOfs(int ofs); +bool PRVM_ED_ParseEpair(pr_edict_t *ent, ddef_t *key, const char *s); + +//============================================================================ +// mempool handling + +/* +=============== +PRVM_MEM_Alloc +=============== +*/ +void PRVM_MEM_Alloc(void) +{ + int i; + + // reserve space for the null entity aka world + // check bound of max_edicts + vm.prog->max_edicts = bound(1 + vm.prog->reserved_edicts, vm.prog->max_edicts, vm.prog->limit_edicts); + vm.prog->num_edicts = bound(1 + vm.prog->reserved_edicts, vm.prog->num_edicts, vm.prog->max_edicts); + + // edictprivate_size has to be min as big prvm_edict_private_t + vm.prog->edictprivate_size = max(vm.prog->edictprivate_size, (int)sizeof(vm_edict_t)); + + // alloc edicts + vm.prog->edicts = (pr_edict_t *)Mem_Alloc(vm.prog->progs_mempool, vm.prog->limit_edicts * sizeof(pr_edict_t)); + + // alloc edict private space + vm.prog->edictprivate = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edictprivate_size); + + // alloc edict fields + vm.prog->edictsfields = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edict_size); + + // set edict pointers + for(i = 0; i < vm.prog->max_edicts; i++) + { + vm.prog->edicts[i].priv.ed = (vm_edict_t *)((byte *)vm.prog->edictprivate + i * vm.prog->edictprivate_size); + vm.prog->edicts[i].progs.vp = (void*)((byte *)vm.prog->edictsfields + i * vm.prog->edict_size); + } +} + +/* +=============== +PRVM_MEM_IncreaseEdicts +=============== +*/ +void PRVM_MEM_IncreaseEdicts( void ) +{ + int i; + int oldmaxedicts = vm.prog->max_edicts; + void *oldedictsfields = vm.prog->edictsfields; + void *oldedictprivate = vm.prog->edictprivate; + + if(vm.prog->max_edicts >= vm.prog->limit_edicts) + return; + + PRVM_GCALL(begin_increase_edicts)(); + + // increase edicts + vm.prog->max_edicts = min(vm.prog->max_edicts + 256, vm.prog->limit_edicts); + + vm.prog->edictsfields = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edict_size); + vm.prog->edictprivate = Mem_Alloc(vm.prog->progs_mempool, vm.prog->max_edicts * vm.prog->edictprivate_size); + + Mem_Copy(vm.prog->edictsfields, oldedictsfields, oldmaxedicts * vm.prog->edict_size); + Mem_Copy(vm.prog->edictprivate, oldedictprivate, oldmaxedicts * vm.prog->edictprivate_size); + + // set e and v pointers + for(i = 0; i < vm.prog->max_edicts; i++) + { + vm.prog->edicts[i].priv.ed = (vm_edict_t *)((byte *)vm.prog->edictprivate + i * vm.prog->edictprivate_size); + vm.prog->edicts[i].progs.vp = (void*)((byte *)vm.prog->edictsfields + i * vm.prog->edict_size); + } + + PRVM_GCALL(end_increase_edicts)(); + + Mem_Free(oldedictsfields); + Mem_Free(oldedictprivate); +} + +//============================================================================ +// normal prvm + +int PRVM_ED_FindFieldOffset(const char *field) +{ + ddef_t *d; + d = PRVM_ED_FindField(field); + if (!d) return 0; + return d->ofs * 4; +} + +int PRVM_ED_FindGlobalOffset(const char *global) +{ + ddef_t *d; + d = PRVM_ED_FindGlobal(global); + if (!d) return 0; + return d->ofs * 4; +} + +func_t PRVM_ED_FindFunctionOffset(const char *function) +{ + mfunction_t *f; + f = PRVM_ED_FindFunction(function); + if (!f) + return 0; + return (func_t)(f - vm.prog->functions); +} + +bool PRVM_ProgLoaded( int prognr ) +{ + if(prognr < 0 || prognr >= PRVM_MAXPROGS) + return false; + + return (prog_list[prognr].loaded ? true : false); +} + +/* +================= +PRVM_SetProgFromString +================= +*/ +// perhaps add a return value when the str doesnt exist +bool PRVM_SetProgFromString(const char *str) +{ + int i = 0; + for(; i < PRVM_MAXPROGS ; i++) + if(prog_list[i].name && !com.strcmp(prog_list[i].name,str)) + { + if(prog_list[i].loaded) + { + vm.prog = &prog_list[i]; + return true; + } + else + { + Msg("%s not loaded !\n", PRVM_NAME); + return false; + } + } + + Msg("Invalid program name %s !\n", str); + return false; +} + +/* +================= +PRVM_SetProg +================= +*/ +void PRVM_SetProg(int prognr) +{ + if(0 <= prognr && prognr < PRVM_MAXPROGS) + { + if(prog_list[prognr].loaded) + vm.prog = &prog_list[prognr]; + else PRVM_ERROR("%i not loaded!\n", prognr); + return; + } + PRVM_ERROR("Invalid program number %i", prognr); +} + +/* +================= +PRVM_ED_ClearEdict + +Sets everything to NULL +================= +*/ +void PRVM_ED_ClearEdict (pr_edict_t *e) +{ + Mem_Set (e->progs.vp, 0, vm.prog->progs->entityfields * 4); + e->priv.ed->free = false; + + // AK: Let the init_edict function determine if something needs to be initialized + PRVM_GCALL(init_edict)(e); +} + +/* +================= +PRVM_ED_Alloc + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +pr_edict_t *PRVM_ED_Alloc (void) +{ + int i; + pr_edict_t *e; + + // the client qc dont need maxclients + // thus it doesnt need to use svs.maxclients + // AK: changed i=svs.maxclients+1 + // AK: changed so the edict 0 wont spawn -> used as reserved/world entity + // although the menu/client has no world + for (i = vm.prog->reserved_edicts + 1; i < vm.prog->num_edicts; i++) + { + e = PRVM_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->priv.ed->free && ( e->priv.ed->freetime < 2 || (*vm.prog->time - e->priv.ed->freetime) > 0.5 ) ) + { + PRVM_ED_ClearEdict (e); + return e; + } + } + + if (i == vm.prog->limit_edicts) + PRVM_ERROR ("%s: PRVM_ED_Alloc: no free edicts",PRVM_NAME); + + vm.prog->num_edicts++; + if (vm.prog->num_edicts >= vm.prog->max_edicts) + PRVM_MEM_IncreaseEdicts(); + + e = PRVM_EDICT_NUM(i); + PRVM_ED_ClearEdict (e); + + return e; +} + +/* +================= +PRVM_ED_Free + +Marks the edict as free +FIXME: walk all entities and NULL out references to this entity +================= +*/ +void PRVM_ED_Free (pr_edict_t *ed) +{ + // dont delete the null entity (world) or reserved edicts + if(PRVM_NUM_FOR_EDICT(ed) <= vm.prog->reserved_edicts ) + return; + + PRVM_GCALL(free_edict)(ed); + + ed->priv.ed->free = true; + ed->priv.ed->freetime = *vm.prog->time; +} + +//=========================================================================== + +/* +============ +PRVM_ED_GlobalAtOfs +============ +*/ +ddef_t *PRVM_ED_GlobalAtOfs( int ofs ) +{ + ddef_t *def; + int i; + + for( i = 0; i < vm.prog->progs->numglobaldefs; i++ ) + { + def = &vm.prog->globaldefs[i]; + if( def->ofs == ofs ) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FieldAtOfs +============ +*/ +ddef_t *PRVM_ED_FieldAtOfs (int ofs) +{ + ddef_t *def; + int i; + + for (i=0 ; iprogs->numfielddefs ; i++) + { + def = &vm.prog->fielddefs[i]; + if (def->ofs == ofs) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindField +============ +*/ +ddef_t *PRVM_ED_FindField( const char *name ) +{ + ddef_t *def; + int i; + + for (i=0 ; iprogs->numfielddefs ; i++) + { + def = &vm.prog->fielddefs[i]; + if (!com.strcmp(PRVM_GetString(def->s_name), name)) + return def; + } + return NULL; +} + +/* +============ +PRVM_ED_FindGlobal +============ +*/ +ddef_t *PRVM_ED_FindGlobal (const char *name) +{ + ddef_t *def; + int i; + + for (i=0 ; iprogs->numglobaldefs ; i++) + { + def = &vm.prog->globaldefs[i]; + if (!com.strcmp(PRVM_GetString(def->s_name), name)) + return def; + } + return NULL; +} + + +/* +============ +PRVM_ED_FindFunction +============ +*/ +mfunction_t *PRVM_ED_FindFunction (const char *name) +{ + mfunction_t *func; + int i; + + for (i=0 ; iprogs->numfunctions ; i++) + { + func = &vm.prog->functions[i]; + if (!com.strcmp(PRVM_GetString(func->s_name), name)) + return func; + } + return NULL; +} + + +/* +============ +PRVM_ValueString + +Returns a string describing *data in a type specific manner +============= +*/ +char *PRVM_ValueString( etype_t type, prvm_eval_t *val ) +{ + static char line[MAX_MSGLEN]; + ddef_t *def; + mfunction_t *f; + int n; + + type = (etype_t)type & ~DEF_SAVEGLOBAL; + + switch( type ) + { + case ev_struct: + com.strncpy ( line, "struct", sizeof (line)); + break; + case ev_union: + com.strncpy ( line, "union", sizeof (line)); + break; + case ev_string: + com.strncpy (line, PRVM_GetString (val->string), sizeof (line)); + break; + case ev_entity: + n = val->edict; + if (n < 0 || n >= vm.prog->limit_edicts) + com.sprintf (line, "entity %i (invalid!)", n); + else com.sprintf (line, "entity %i", n); + break; + case ev_function: + f = vm.prog->functions + val->function; + com.sprintf (line, "%s()", PRVM_GetString(f->s_name)); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( val->_int ); + if(!def) com.sprintf (line, ".???", val->_int ); + else com.sprintf (line, ".%s", PRVM_GetString(def->s_name)); + break; + case ev_void: + com.sprintf (line, "void"); + break; + case ev_float: + com.sprintf (line, "%10.4f", val->_float); + break; + case ev_integer: + com.sprintf (line, "%i", val->_int); + break; + case ev_vector: + com.sprintf (line, "'%10.4f %10.4f %10.4f'", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_pointer: + com.sprintf (line, "pointer"); + break; + default: + com.sprintf (line, "bad type %i", (int) type); + break; + } + + return line; +} + +/* +============ +PRVM_UglyValueString + +Returns a string describing *data in a type specific manner +Easier to parse than PR_ValueString +============= +*/ +char *PRVM_UglyValueString (etype_t type, prvm_eval_t *val) +{ + static char line[MAX_MSGLEN]; + int i; + const char *s; + ddef_t *def; + mfunction_t *f; + + type = (etype_t)type & ~DEF_SAVEGLOBAL; + + switch( type ) + { + case ev_string: + // Parse the string a bit to turn special characters + // (like newline, specifically) into escape codes, + // this fixes saving games from various mods + s = PRVM_GetString ( val->string ); + for( i = 0;i < (int)sizeof(line) - 2 && *s; ) + { + if( *s == '\n' ) + { + line[i++] = '\\'; + line[i++] = 'n'; + } + else if( *s == '\r' ) + { + line[i++] = '\\'; + line[i++] = 'r'; + } + else line[i++] = *s; + s++; + } + line[i] = '\0'; + break; + case ev_entity: + com.sprintf( line, "%i", PRVM_NUM_FOR_EDICT(PRVM_PROG_TO_EDICT(val->edict))); + break; + case ev_function: + f = vm.prog->functions + val->function; + com.strncpy (line, PRVM_GetString (f->s_name), sizeof (line)); + break; + case ev_field: + def = PRVM_ED_FieldAtOfs ( val->_int ); + com.sprintf (line, ".%s", PRVM_GetString(def->s_name)); + break; + case ev_void: + com.sprintf (line, "void"); + break; + case ev_float: + com.sprintf (line, "%f", val->_float); + break; + case ev_vector: + com.sprintf (line, "%f %f %f", val->vector[0], val->vector[1], val->vector[2]); + break; + case ev_integer: + com.sprintf (line, "%i", val->_int); + break; + case ev_pointer: + case ev_struct: + case ev_union: + com.sprintf (line, "skip new type %i", type); + break; + default: + com.sprintf (line, "bad type %i", type); + break; + } + + return line; +} + +/* +============ +PRVM_GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +char *PRVM_GlobalString (int ofs) +{ + char *s; + ddef_t *def; + void *val; + static char line[128]; + + val = (void *)&vm.prog->globals.gp[ofs]; + def = PRVM_ED_GlobalAtOfs(ofs); + if( !def ) com.sprintf (line,"GLOBAL%i", ofs); + else + { + s = PRVM_ValueString((etype_t)def->type, (prvm_eval_t *)val); + com.sprintf (line,"%s (=%s)", PRVM_GetString(def->s_name), s); + } + + return line; +} + +char *PRVM_GlobalStringNoContents (int ofs) +{ + //size_t i; + ddef_t *def; + static char line[128]; + + def = PRVM_ED_GlobalAtOfs(ofs); + if (!def) + com.sprintf (line,"GLOBAL%i", ofs); + else + com.sprintf (line,"%s", PRVM_GetString(def->s_name)); + + return line; +} + + +/* +============= +PRVM_ED_Print + +For debugging +============= +*/ +// LordHavoc: optimized this to print out much more quickly (tempstring) +// LordHavoc: changed to print out every 4096 characters (incase there are a lot of fields to print) +void PRVM_ED_Print(pr_edict_t *ed) +{ + size_t l; + ddef_t *d; + int *v; + int i, j; + const char *name; + int type; + char tempstring[MAX_MSGLEN], tempstring2[260]; // temporary string buffers + + if( ed->priv.ed->free ) + { + Msg( "%s: FREE\n", PRVM_NAME ); + return; + } + + tempstring[0] = 0; + com.sprintf(tempstring, "\n%s EDICT %i:\n", PRVM_NAME, PRVM_NUM_FOR_EDICT(ed)); + for( i = 1; i < vm.prog->progs->numfielddefs; i++ ) + { + d = &vm.prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if(name[com.strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)ed->progs.vp + d->ofs*4); + + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + + for (j=0 ; j sizeof(tempstring2)-4) + { + Mem_Copy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + com.strncat(tempstring, name, sizeof(tempstring)); + for (l = com.strlen(name);l < 14;l++) + com.strncat(tempstring, " ", sizeof(tempstring)); + com.strncat(tempstring, " ", sizeof(tempstring)); + + name = PRVM_ValueString((etype_t)d->type, (prvm_eval_t *)v); + if (com.strlen(name) > sizeof(tempstring2)-4) + { + Mem_Copy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + com.strncat(tempstring, name, sizeof(tempstring)); + com.strncat(tempstring, "\n", sizeof(tempstring)); + if (com.strlen(tempstring) >= sizeof(tempstring)/2) + { + Msg(tempstring); + tempstring[0] = 0; + } + } + if (tempstring[0]) Msg(tempstring); +} + +/* +============= +PRVM_ED_Write + +For savegames +============= +*/ +void PRVM_ED_Write( pr_edict_t *ed, void *buffer, void *numpairs, setpair_t callback ) +{ + ddef_t *d; + int *v; + int i, j; + const char *name, *value; + int type; + + if( !callback ) return; + if( ed->priv.ed->free ) + { + // freed entity too has serialnumber! + callback( NULL, NULL, buffer, numpairs ); + return; + } + + for( i = 1; i < vm.prog->progs->numfielddefs; i++ ) + { + d = &vm.prog->fielddefs[i]; + name = PRVM_GetString( d->s_name ); + if(name[com.strlen(name) - 2] == '_') + continue; // skip _x, _y, _z vars + + v = (int *)((char *)ed->progs.vp + d->ofs * 4); + // if the value is still all 0, skip the field + type = d->type & ~DEF_SAVEGLOBAL; + for( j = 0; j < prvm_type_size[type]; j++ ) + if( v[j] ) break; + if( j == prvm_type_size[type] ) continue; + + value = PRVM_UglyValueString((etype_t)d->type, (prvm_eval_t *)v); + callback( name, value, buffer, numpairs ); + } +} + +/* +============= +PRVM_ED_Read + +For savegames +============= +*/ +void PRVM_ED_Read( int s_table, int entnum, dkeyvalue_t *fields, int numpairs ) +{ + const char *keyname, *value; + ddef_t *key; + pr_edict_t *ent; + int i; + + if( entnum >= vm.prog->limit_edicts ) Host_Error( "PRVM_ED_Read: too many edicts in save file\n" ); + while( entnum >= vm.prog->max_edicts) PRVM_MEM_IncreaseEdicts(); // increase edict numbers + ent = PRVM_EDICT_NUM( entnum ); + PRVM_ED_ClearEdict( ent ); + + if( !numpairs ) + { + // freed edict + ent->priv.ed->free = true; + return; + } + + // go through all the dictionary pairs + for( i = 0; i < numpairs; i++ ) + { + keyname = StringTable_GetString( s_table, fields[i].epair[DENT_KEY] ); + value = StringTable_GetString( s_table, fields[i].epair[DENT_VAL] ); + + key = PRVM_ED_FindField( keyname ); + if( !key ) + { + PRVM_GCALL(keyvalue_edict)( ent, keyname, value ); + else MsgDev( D_WARN, "%s: unknown field '%s'\n", PRVM_NAME, keyname); + continue; + } + + // simple huh ? + if(!PRVM_ED_ParseEpair( ent, key, value )) PRVM_ERROR( "PRVM_ED_ParseEdict: parse error" ); + } + + // all done, restore physics interaction links or somelike + PRVM_GCALL(restore_edict)(ent); +} + +void PRVM_ED_PrintNum (int ent) +{ + PRVM_ED_Print(PRVM_EDICT_NUM(ent)); +} + +/* +============= +PRVM_ED_PrintEdicts_f + +For debugging, prints all the entities in the current server +============= +*/ +void PRVM_ED_PrintEdicts_f (void) +{ + int i; + + if(Cmd_Argc() != 2) + { + Msg("prvm_edicts \n"); + return; + } + + + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + Msg("%s: %i entities\n", PRVM_NAME, vm.prog->num_edicts); + for (i=0 ; inum_edicts ; i++) + PRVM_ED_PrintNum (i); + + vm.prog = NULL; +} + +/* +============= +PRVM_ED_PrintEdict_f + +For debugging, prints a single edict +============= +*/ +void PRVM_ED_PrintEdict_f( void ) +{ + int i; + + if( Cmd_Argc() != 3 ) + { + Msg("prvm_edict \n"); + return; + } + + if(!PRVM_SetProgFromString(Cmd_Argv( 1 ))) return; + + i = com.atoi (Cmd_Argv(2)); + if( i >= vm.prog->num_edicts ) + { + Msg( "bad edict number\n" ); + vm.prog = NULL; + return; + } + + PRVM_ED_PrintNum( i ); + vm.prog = NULL; +} + +/* +============= +PRVM_ED_Count + +For debugging +============= +*/ +void PRVM_ED_Count_f( void ) +{ + int i; + pr_edict_t *ent; + int active; + + if(Cmd_Argc() != 2) + { + Msg( "prvm_count \n" ); + return; + } + + + if(!PRVM_SetProgFromString(Cmd_Argv( 1 ))) + return; + + if( vm.prog->count_edicts ) vm.prog->count_edicts(); + else + { + active = 0; + for( i = 0; i < vm.prog->num_edicts; i++ ) + { + ent = PRVM_EDICT_NUM( i ); + if( ent->priv.ed->free ) + continue; + active++; + } + + Msg( "num_edicts:%3i\n", vm.prog->num_edicts ); + Msg( "active :%3i\n", active ); + } + vm.prog = NULL; +} + +/* +============================================================================== + ARCHIVING GLOBALS +============================================================================== +*/ +/* +============= +PRVM_ED_WriteGlobals +============= +*/ +void PRVM_ED_WriteGlobals( void *buffer, void *numpairs, setpair_t callback ) +{ + ddef_t *def; + const char *name; + const char *value; + int i, type; + + // nothing to process ? + if( !callback ) return; + + for( i = 0; i < vm.prog->progs->numglobaldefs; i++ ) + { + def = &vm.prog->globaldefs[i]; + type = def->type; + if(!(def->type & DEF_SAVEGLOBAL)) + continue; + type &= ~DEF_SAVEGLOBAL; + + if( type != ev_string && type != ev_float && type != ev_entity ) + continue; + + name = PRVM_GetString(def->s_name); + value = PRVM_UglyValueString((etype_t)type, (prvm_eval_t *)&vm.prog->globals.gp[def->ofs]); + callback( name, value, buffer, numpairs ); + } +} + +/* +============= +PRVM_ED_ReadGlobals +============= +*/ +void PRVM_ED_ReadGlobals( int s_table, dkeyvalue_t *globals, int numpairs ) +{ + const char *keyname; + const char *value; + ddef_t *key; + int i; + + for( i = 0; i < numpairs; i++ ) + { + keyname = StringTable_GetString( s_table, globals[i].epair[DENT_KEY] ); + value = StringTable_GetString( s_table, globals[i].epair[DENT_VAL]); + + key = PRVM_ED_FindGlobal( keyname ); + if( !key ) + { + MsgDev( D_INFO, "'%s' is not a global on %s\n", keyname, PRVM_NAME ); + continue; + } + if( !PRVM_ED_ParseEpair( NULL, key, value )) PRVM_ERROR( "PRVM_ED_ReadGlobals: parse error\n" ); + } +} + +//============================================================================ + + +/* +============= +PRVM_ED_ParseEval + +Can parse either fields or globals +returns false if error +============= +*/ +bool PRVM_ED_ParseEpair( pr_edict_t *ent, ddef_t *key, const char *s ) +{ + int i, l; + char *new_p; + ddef_t *def; + prvm_eval_t *val; + mfunction_t *func; + + if( ent ) val = (prvm_eval_t *)((int *)ent->progs.vp + key->ofs); + else val = (prvm_eval_t *)((int *)vm.prog->globals.gp + key->ofs); + + switch( key->type & ~DEF_SAVEGLOBAL ) + { + case ev_string: + l = (int)com.strlen(s) + 1; + val->string = PRVM_AllocString(l, &new_p); + for (i = 0;i < l;i++) + { + if (s[i] == '\\' && i < l-1) + { + i++; + if (s[i] == 'n') + *new_p++ = '\n'; + else if (s[i] == 'r') + *new_p++ = '\r'; + else + *new_p++ = s[i]; + } + else + *new_p++ = s[i]; + } + break; + case ev_float: + while(*s && *s <= ' ') s++; + val->_float = com.atof( s ); + break; + case ev_vector: + for( i = 0; i < 3; i++ ) + { + while(*s && *s <= ' ') s++; + if( !*s ) break; + val->vector[i] = com.atof( s ); + while( *s > ' ' ) s++; + if (!*s) break; + } + break; + case ev_entity: + while( *s && *s <= ' ' ) s++; + i = com.atoi( s ); + if (i >= vm.prog->limit_edicts) + MsgDev( D_WARN, "PRVM_ED_ParseEpair: ev_entity reference too large (edict %u >= limit_edicts %u) on %s\n", (uint)i, (uint)vm.prog->limit_edicts, PRVM_NAME); + while( i >= vm.prog->max_edicts ) PRVM_MEM_IncreaseEdicts(); + // if IncreaseEdicts was called the base pointer needs to be updated + if( ent ) val = (prvm_eval_t *)((int *)ent->progs.vp + key->ofs); + val->edict = PRVM_EDICT_TO_PROG(PRVM_EDICT_NUM((int)i)); + break; + + case ev_field: + def = PRVM_ED_FindField(s); + if( !def ) + { + MsgDev( D_WARN, "PRVM_ED_ParseEpair: Can't find field %s in %s\n", s, PRVM_NAME ); + return false; + } + val->_int = def->ofs; + break; + + case ev_function: + func = PRVM_ED_FindFunction(s); + if( !func ) + { + MsgDev( D_WARN, "PRVM_ED_ParseEpair: Can't find function %s in %s\n", s, PRVM_NAME ); + return false; + } + val->function = func - vm.prog->functions; + break; + + default: + MsgDev( D_WARN, "PRVM_ED_ParseEpair: Unknown key->type %i for key \"%s\" on %s\n", key->type, PRVM_GetString(key->s_name), PRVM_NAME ); + return false; + } + return true; +} + +/* +============= +PRVM_ED_EdictSet_f + +Console command to set a field of a specified edict +============= +*/ +void PRVM_ED_EdictSet_f(void) +{ + pr_edict_t *ed; + ddef_t *key; + + if(Cmd_Argc() != 5) + { + Msg("prvm_edictset \n"); + return; + } + + + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + { + Msg("Wrong program name %s !\n", Cmd_Argv(1)); + return; + } + + ed = PRVM_EDICT_NUM(com.atoi(Cmd_Argv(2))); + + if((key = PRVM_ED_FindField(Cmd_Argv(3))) == 0) + MsgDev( D_WARN, "Key %s not found !\n", Cmd_Argv(3)); + else PRVM_ED_ParseEpair(ed, key, Cmd_Argv(4)); + + vm.prog = NULL; +} + +/* +==================== +PRVM_ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +Used for initial level load and for savegames. +==================== +*/ +const char *PRVM_ED_ParseEdict( const char *data, pr_edict_t *ent ) +{ + ddef_t *key; + bool init, newline, anglehack; + char keyname[256]; + size_t n; + + init = false; + + // go through all the dictionary pairs + MsgDev( D_NOTE, "{\n" ); + while( 1 ) + { + // parse key + PR_ParseToken( &data, true ); + + if( pr_token[0] == '\0') + PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace"); + + // just format console messages + newline = (pr_token[0] == '}') ? true : false; + if(!newline) MsgDev(D_NOTE, "\"%s\"", pr_token); + else + { + MsgDev( D_NOTE, "}\n"); + break; + } + + // anglehack is to allow QuakeEd to write single scalar angles + // and allow them to be turned into vectors. (FIXME...) + if( !com.strcmp( pr_token, "angle" )) + { + com.strncpy( keyname, "angles", sizeof(keyname)); + anglehack = true; + } + else + { + anglehack = false; + com.strncpy( keyname, pr_token, sizeof(keyname)); + } + // another hack to fix keynames with trailing spaces + n = com.strlen( keyname ); + while(n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + if (!PR_ParseToken( &data, true )) + PRVM_ERROR ("PRVM_ED_ParseEdict: EOF without closing brace"); + MsgDev(D_NOTE, " \"%s\"\n", pr_token); + if( pr_token[0] == '}' ) PRVM_ERROR( "PRVM_ED_ParseEdict: closing brace without data" ); + init = true; + + // ignore attempts to set key "" (this problem occurs in nehahra neh1m8.bsp) + if (!keyname[0]) continue; + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by quake + if(!stricmp( keyname, "light" )) continue; // ignore lightvalue + if( keyname[0] == '_' ) continue; + + key = PRVM_ED_FindField( keyname ); + if( !key ) + { + PRVM_GCALL(keyvalue_edict)( ent, keyname, pr_token ); + else MsgDev( D_WARN, "%s: unknown field '%s'\n", PRVM_NAME, keyname); + continue; + } + + if( anglehack ) + { + char temp[32]; + + com.strncpy( temp, pr_token, sizeof(temp)); + com.sprintf( pr_token, "0 %s 0", temp ); + } + if(!PRVM_ED_ParseEpair( ent, key, pr_token )) PRVM_ERROR ("PRVM_ED_ParseEdict: parse error"); + } + if( !init ) ent->priv.ed->free = true; + + return data; +} + + +/* +================ +PRVM_ED_LoadFromFile + +The entities are directly placed in the array, rather than allocated with +PRVM_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. + +Used for both fresh maps and savegame loads. A fresh map would also need +to call PRVM_ED_CallSpawnFunctions () to let the objects initialize themselves. +================ +*/ +void PRVM_ED_LoadFromFile( const char *data ) +{ + pr_edict_t *ent; + int parsed, inhibited, spawned, died; + mfunction_t *func; + + parsed = 0; + inhibited = 0; + spawned = 0; + died = 0; + + // parse ents + while( 1 ) + { + // parse the opening brace + PR_ParseToken( &data, true ); + + if( pr_token[0] == '\0') break; + if( pr_token[0] != '{' ) + PRVM_ERROR ("PRVM_ED_LoadFromFile: %s: found %s when expecting {", PRVM_NAME, pr_token ); + + // CHANGED: this is not conform to PR_LoadFromFile + if(vm.prog->loadintoworld) + { + vm.prog->loadintoworld = false; + ent = PRVM_EDICT_NUM(0); + } + else ent = PRVM_ED_Alloc(); + + // HACKHACK: clear it + if( ent != vm.prog->edicts ) Mem_Set( ent->progs.vp, 0, vm.prog->progs->entityfields * 4 ); + + data = PRVM_ED_ParseEdict( data, ent ); + parsed++; + + // remove the entity ? + if(vm.prog->load_edict && !vm.prog->load_edict(ent)) + { + PRVM_ED_Free(ent); + inhibited++; + continue; + } + + // immediately call spawn function, but only if there is a pev global and a classname + if(vm.prog->pev && vm.prog->flag & PRVM_FE_CLASSNAME) + { + string_t handle = *(string_t*)&((byte*)ent->progs.vp)[PRVM_ED_FindFieldOffset("classname")]; + if( !handle ) + { + if(prvm_developer >= D_NOTE) + { + MsgDev( D_ERROR, "No classname for:\n"); + PRVM_ED_Print(ent); + } + PRVM_ED_Free (ent); + continue; + } + + // look for the spawn function + func = PRVM_ED_FindFunction (PRVM_GetString(handle)); + + if( !func ) + { + if( prvm_developer >= D_ERROR ) + { + MsgDev( D_ERROR, "No spawn function for:\n" ); + PRVM_ED_Print( ent ); + } + PRVM_ED_Free( ent ); + continue; + } + + // pev = ent + PRVM_G_INT(vm.prog->pev->ofs) = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram( func - vm.prog->functions, "", __FILE__, __LINE__ ); + } + + spawned++; + if (ent->priv.ed->free) died++; + } + MsgDev(D_NOTE, "%s: %i new entities parsed, %i new inhibited, %i (%i new) spawned (whereas %i removed self, %i stayed)\n", PRVM_NAME, parsed, inhibited, vm.prog->num_edicts, spawned, died, spawned - died); +} + +/* +=============== +PRVM_ResetProg +=============== +*/ + +void PRVM_ResetProg( void ) +{ + PRVM_GCALL(reset_cmd)(); + Mem_FreePool(&vm.prog->progs_mempool); + Mem_Set(vm.prog, 0, sizeof(prvm_prog_t)); +} + +/* +=============== +PRVM_LoadProgs +=============== +*/ +void PRVM_LoadProgs( const char *filename ) +{ + dstatement_t *st; + ddef_t *infielddefs; + dfunction_t *dfunctions; + fs_offset_t filesize; + int i, len, complen; + byte *s; + + if( vm.prog->loaded ) + { + PRVM_ERROR ("PRVM_LoadProgs: there is already a %s program loaded!", PRVM_NAME ); + } + + if( vm.prog->progs ) Mem_Free( vm.prog->progs ); // release progs file + vm.prog->progs = (dprograms_t *)FS_LoadFile(va("%s", filename ), &filesize); + + if( vm.prog->progs == NULL || filesize < (fs_offset_t)sizeof(dprograms_t)) + PRVM_ERROR("PRVM_LoadProgs: couldn't load %s for %s\n", filename, PRVM_NAME); + + MsgDev(D_NOTE, "%s programs occupy %iK.\n", PRVM_NAME, filesize/1024); + SwapBlock((int *)vm.prog->progs, sizeof(*vm.prog->progs)); // byte swap the header + + if( vm.prog->progs->version != VPROGS_VERSION) + PRVM_ERROR( "%s: %s has wrong version number (%i should be %i)", PRVM_NAME, filename, vm.prog->progs->version, VPROGS_VERSION ); + + // try to recognize progs.dat by crc + if( PRVM_GetProgNr() == PRVM_DECOMPILED ) + { + MsgDev(D_INFO, "Prepare %s [CRC %d]\n", filename, vm.prog->progs->crc ); + } + else if( vm.prog->progs->crc != vm.prog->filecrc ) + { + PRVM_ERROR("%s: %s system vars have been modified, progdefs.h is out of date %d\n", PRVM_NAME, filename, vm.prog->filecrc ); + } + else MsgDev(D_LOAD, "%s [^2CRC %d^7]\n", filename, vm.prog->progs->crc ); + + // set initial pointers + vm.prog->statements = (dstatement_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_statements); + vm.prog->globaldefs = (ddef_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_globaldefs); + infielddefs = (ddef_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_fielddefs); + dfunctions = (dfunction_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_functions); + vm.prog->strings = (char *)vm.prog->progs + vm.prog->progs->ofs_strings; + vm.prog->globals.gp = (float *)((byte *)vm.prog->progs + vm.prog->progs->ofs_globals); + + // debug info + if( vm.prog->progs->ofssources ) vm.prog->sources = (dsource_t*)((byte *)vm.prog->progs + vm.prog->progs->ofssources); + if( vm.prog->progs->ofslinenums ) vm.prog->linenums = (int *)((byte *)vm.prog->progs + vm.prog->progs->ofslinenums); + if( vm.prog->progs->ofs_types ) vm.prog->types = (type_t *)((byte *)vm.prog->progs + vm.prog->progs->ofs_types); + + // decompress progs if needed + if( vm.prog->progs->flags & COMP_STATEMENTS ) + { + len = sizeof(dstatement_t) * vm.prog->progs->numstatements; + complen = LittleLong(*(int*)vm.prog->statements); + + MsgDev(D_NOTE, "Unpacked statements: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->statements)+1), complen, &s, len ); + vm.prog->statements = (dstatement_t *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->progs->flags & COMP_DEFS ) + { + len = sizeof(ddef_t) * vm.prog->progs->numglobaldefs; + complen = LittleLong(*(int*)vm.prog->globaldefs); + + MsgDev(D_NOTE, "Unpacked defs: len %d, comp len %d\n", len, complen); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->globaldefs)+1), complen, &s, len ); + vm.prog->globaldefs = (ddef_t *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->progs->flags & COMP_FIELDS ) + { + len = sizeof(ddef_t) * vm.prog->progs->numfielddefs; + complen = LittleLong(*(int*)infielddefs); + + MsgDev(D_NOTE, "Unpacked fields: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)infielddefs)+1), complen, &s, len ); + infielddefs = (ddef_t *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->progs->flags & COMP_FUNCTIONS ) + { + len = sizeof(dfunction_t) * vm.prog->progs->numfunctions; + complen = LittleLong(*(int*)dfunctions); + + MsgDev(D_NOTE, "Unpacked functions: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)dfunctions)+1), complen, &s, len ); + dfunctions = (dfunction_t *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->progs->flags & COMP_STRINGS ) + { + len = sizeof(char) * vm.prog->progs->numstrings; + complen = LittleLong(*(int*)vm.prog->strings); + + MsgDev(D_NOTE, "Unpacked strings: count %d, len %d, comp len %d\n", vm.prog->progs->numstrings, len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->strings)+1), complen, &s, len ); + vm.prog->strings = (char *)s; + + vm.prog->progs->ofs_strings += 4; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->progs->flags & COMP_GLOBALS ) + { + len = sizeof(float) * vm.prog->progs->numglobals; + complen = LittleLong(*(int*)vm.prog->globals.gp); + + MsgDev(D_NOTE, "Unpacked globals: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->globals.gp)+1), complen, &s, len ); + vm.prog->globals.gp = (float *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->linenums && vm.prog->progs->flags & COMP_LINENUMS ) + { + len = sizeof(int) * vm.prog->progs->numstatements; + complen = LittleLong(*(int*)vm.prog->linenums); + + MsgDev(D_NOTE, "Unpacked linenums: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->linenums)+1), complen, &s, len ); + vm.prog->linenums = (int *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + if( vm.prog->types && vm.prog->progs->flags & COMP_TYPES ) + { + len = sizeof(type_t) * vm.prog->progs->numtypes; + complen = LittleLong(*(int*)vm.prog->types); + + MsgDev(D_NOTE, "Unpacked types: len %d, comp len %d\n", len, complen ); + s = Mem_Alloc(vm.prog->progs_mempool, len ); // alloc memory for inflate block + VFS_Unpack((char *)(((int *)vm.prog->types)+1), complen, &s, len ); + vm.prog->types = (type_t *)s; + filesize += len - complen - sizeof(int); //merge filesize + } + + vm.prog->stringssize = 0; + + for( i = 0; vm.prog->stringssize < vm.prog->progs->numstrings; i++ ) + { + if( vm.prog->progs->ofs_strings + vm.prog->stringssize >= (int)filesize ) + PRVM_ERROR ("%s: %s strings go past end of file", PRVM_NAME, filename); + vm.prog->stringssize += (int)com.strlen(vm.prog->strings + vm.prog->stringssize) + 1; + } + + vm.prog->numknownstrings = 0; + vm.prog->maxknownstrings = 0; + vm.prog->knownstrings = NULL; + vm.prog->knownstrings_freeable = NULL; + + // we need to expand the fielddefs list to include all the engine fields, + // so allocate a new place for it ( + DPFIELDS ) + vm.prog->fielddefs = (ddef_t *)Mem_Alloc(vm.prog->progs_mempool, (vm.prog->progs->numfielddefs) * sizeof(ddef_t)); + vm.prog->statement_profile = (double *)Mem_Alloc(vm.prog->progs_mempool, vm.prog->progs->numstatements * sizeof(*vm.prog->statement_profile)); + + // byte swap the lumps + for (i = 0; i < vm.prog->progs->numstatements; i++) + { + vm.prog->statements[i].op = LittleLong(vm.prog->statements[i].op); + vm.prog->statements[i].a = LittleLong(vm.prog->statements[i].a); + vm.prog->statements[i].b = LittleLong(vm.prog->statements[i].b); + vm.prog->statements[i].c = LittleLong(vm.prog->statements[i].c); + } + vm.prog->functions = (mfunction_t *)Mem_Alloc(vm.prog->progs_mempool, sizeof(mfunction_t) * vm.prog->progs->numfunctions); + for (i = 0; i < vm.prog->progs->numfunctions; i++) + { + vm.prog->functions[i].first_statement = LittleLong (dfunctions[i].first_statement); + vm.prog->functions[i].parm_start = LittleLong (dfunctions[i].parm_start); + vm.prog->functions[i].s_name = LittleLong (dfunctions[i].s_name); + vm.prog->functions[i].s_file = LittleLong (dfunctions[i].s_file); + vm.prog->functions[i].numparms = LittleLong (dfunctions[i].numparms); + vm.prog->functions[i].locals = LittleLong (dfunctions[i].locals); + Mem_Copy(vm.prog->functions[i].parm_size, dfunctions[i].parm_size, sizeof(dfunctions[i].parm_size)); + } + + for (i = 0; i < vm.prog->progs->numglobaldefs; i++) + { + vm.prog->globaldefs[i].type = LittleLong (vm.prog->globaldefs[i].type); + vm.prog->globaldefs[i].ofs = LittleLong (vm.prog->globaldefs[i].ofs); + vm.prog->globaldefs[i].s_name = LittleLong (vm.prog->globaldefs[i].s_name); + } + + // copy the progs fields to the new fields list + for (i = 0; i < vm.prog->progs->numfielddefs; i++) + { + vm.prog->fielddefs[i].type = LittleLong (infielddefs[i].type); + if (vm.prog->fielddefs[i].type & DEF_SAVEGLOBAL) + PRVM_ERROR ("PRVM_LoadProgs: vm.prog->fielddefs[i].type & DEF_SAVEGLOBAL in %s", PRVM_NAME); + vm.prog->fielddefs[i].ofs = LittleLong (infielddefs[i].ofs); + vm.prog->fielddefs[i].s_name = LittleLong (infielddefs[i].s_name); + } + + for( i = 0; i < vm.prog->progs->numglobals; i++ ) + ((int *)vm.prog->globals.gp)[i] = LittleLong (((int *)vm.prog->globals.gp)[i]); + + // moved edict_size calculation down here, below field adding code + // LordHavoc: this no longer includes the pr_edict_t header + vm.prog->edict_size = vm.prog->progs->entityfields * 4; + vm.prog->edictareasize = vm.prog->edict_size * vm.prog->limit_edicts; + + // LordHavoc: bounds check anything static + for (i = 0, st = vm.prog->statements; i < vm.prog->progs->numstatements; i++, st++) + { + switch (st->op) + { + case OP_IF: + case OP_IFNOT: + if((dword)st->a >= vm.prog->progs->numglobals || st->b + i < 0 || st->b + i >= vm.prog->progs->numstatements) + PRVM_ERROR("PRVM_LoadProgs: out of bounds IF/IFNOT (statement %d) in %s", i, PRVM_NAME); + break; + case OP_IFNOTS: + // FIXME: make work + break; + case OP_GOTO: + if(st->a + i < 0 || st->a + i >= vm.prog->progs->numstatements) + PRVM_ERROR("PRVM_LoadProgs: out of bounds GOTO (statement %d) in %s", i, PRVM_NAME); + break; + // global global global + case OP_ADD_F: + case OP_ADD_V: + case OP_SUB_F: + case OP_SUB_V: + case OP_MUL_F: + case OP_MUL_V: + case OP_MUL_FV: + case OP_MUL_VF: + case OP_DIV_F: + case OP_BITAND: + case OP_BITOR: + case OP_BITSET: + case OP_BITSETP: + case OP_BITCLR: + case OP_BITCLRP: + case OP_GE: + case OP_LE: + case OP_GT: + case OP_LT: + case OP_AND: + case OP_OR: + case OP_EQ_F: + case OP_EQ_V: + case OP_EQ_S: + case OP_EQ_E: + case OP_EQ_FNC: + case OP_NE_F: + case OP_NE_V: + case OP_NE_S: + case OP_NE_E: + case OP_NE_FNC: + case OP_ADDRESS: + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + case OP_LOAD_V: + case OP_LOADA_F: + case OP_LOADA_V: + case OP_LOADA_S: + case OP_LOADA_ENT: + case OP_LOADA_FLD: + case OP_LOADA_FNC: + case OP_LOADA_I: + case OP_LE_I: + case OP_GE_I: + case OP_LT_I: + case OP_GT_I: + case OP_LE_IF: + case OP_GE_IF: + case OP_LT_IF: + case OP_GT_IF: + case OP_LE_FI: + case OP_GE_FI: + case OP_LT_FI: + case OP_GT_FI: + case OP_EQ_IF: + case OP_EQ_FI: + case OP_CONV_ITOF: + case OP_CONV_FTOI: + case OP_CP_ITOF: + case OP_CP_FTOI: + case OP_GLOBAL_ADD: + case OP_POINTER_ADD: + if((dword) st->a >= vm.prog->progs->numglobals || (dword) st->b >= vm.prog->progs->numglobals || (dword)st->c >= vm.prog->progs->numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d)", i); + break; + // global none global + case OP_NOT_F: + case OP_NOT_V: + case OP_NOT_S: + case OP_NOT_FNC: + case OP_NOT_ENT: + if((dword) st->a >= vm.prog->progs->numglobals || (dword) st->c >= vm.prog->progs->numglobals) + PRVM_ERROR("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + break; + // 2 globals + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: + case OP_STOREP_S: + case OP_STOREP_FNC: + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FLD: + case OP_STORE_S: + case OP_STORE_FNC: + case OP_STATE: + case OP_STOREP_V: + case OP_STORE_V: + case OP_MULSTORE_F: + case OP_MULSTORE_V: + case OP_MULSTOREP_F: + case OP_MULSTOREP_V: + case OP_DIVSTORE_F: + case OP_DIVSTOREP_F: + case OP_ADDSTORE_F: + case OP_ADDSTORE_V: + case OP_ADDSTOREP_F: + case OP_ADDSTOREP_V: + case OP_SUBSTORE_F: + case OP_SUBSTORE_V: + case OP_SUBSTOREP_F: + case OP_SUBSTOREP_V: + if ((dword) st->a >= vm.prog->progs->numglobals || (dword) st->b >= vm.prog->progs->numglobals) + Host_Error("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + break; + // 1 global + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: + case OP_CALL9: + case OP_DONE: + case OP_RETURN: + if ((dword) st->a >= vm.prog->progs->numglobals) + Host_Error("PRVM_LoadProgs: out of bounds global index (statement %d) in %s", i, PRVM_NAME); + break; + default: + MsgDev( D_NOTE, "PRVM_LoadProgs: unknown opcode OP_%s at statement %d in %s\n", pr_opcodes[st->op].opname, i, PRVM_NAME); + break; + } + } + + PRVM_Init_Exec(); + vm.prog->loaded = true; + + // set flags & ddef_ts in prog + vm.prog->flag = 0; + vm.prog->pev = PRVM_ED_FindGlobal( "pev" ); // critical stuff + + if( PRVM_ED_FindGlobal("time") && PRVM_ED_FindGlobal("time")->type & ev_float ) + vm.prog->time = &PRVM_G_FLOAT(PRVM_ED_FindGlobal("time")->ofs); + + if(PRVM_ED_FindField("chain")) vm.prog->flag |= PRVM_FE_CHAIN; + if(PRVM_ED_FindField("classname")) vm.prog->flag |= PRVM_FE_CLASSNAME; + if(PRVM_ED_FindField("nextthink") && PRVM_ED_FindField ("frame") && PRVM_ED_FindField ("think") && vm.prog->flag && vm.prog->pev ) + vm.prog->flag |= PRVM_OP_STATE; + if(PRVM_ED_FindField ("nextthink") && vm.prog->flag && vm.prog->pev ) + vm.prog->flag |= PRVM_OP_THINKTIME; + PRVM_GCALL(init_cmd)(); + + // init mempools + PRVM_MEM_Alloc(); +} + + +void PRVM_Fields_f (void) +{ + int i, j, ednum, used, usedamount; + int *counts; + char tempstring[MAX_MSGLEN], tempstring2[260]; + const char *name; + pr_edict_t *ed; + ddef_t *d; + int *v; + + // TODO + /* + if (!sv.active) + { + Msg("no progs loaded\n"); + return; + } + */ + + if(Cmd_Argc() != 2) + { + Msg("prvm_fields \n"); + return; + } + + + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + counts = (int *)Qalloc(vm.prog->progs->numfielddefs * sizeof(int)); + for (ednum = 0; ednum < vm.prog->max_edicts; ednum++) + { + ed = PRVM_EDICT_NUM(ednum); + if (ed->priv.ed->free) + continue; + for (i = 1;i < vm.prog->progs->numfielddefs;i++) + { + d = &vm.prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if (name[com.strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + v = (int *)((char *)ed->progs.vp + d->ofs*4); + // if the value is still all 0, skip the field + for (j = 0;j < prvm_type_size[d->type & ~DEF_SAVEGLOBAL];j++) + { + if (v[j]) + { + counts[i]++; + break; + } + } + } + } + used = 0; + usedamount = 0; + tempstring[0] = 0; + for (i = 0;i < vm.prog->progs->numfielddefs;i++) + { + d = &vm.prog->fielddefs[i]; + name = PRVM_GetString(d->s_name); + if (name[com.strlen(name)-2] == '_') + continue; // skip _x, _y, _z vars + switch(d->type & ~DEF_SAVEGLOBAL) + { + case ev_string: + com.strncat(tempstring, "string ", sizeof(tempstring)); + break; + case ev_entity: + com.strncat(tempstring, "entity ", sizeof(tempstring)); + break; + case ev_function: + com.strncat(tempstring, "function ", sizeof(tempstring)); + break; + case ev_field: + com.strncat(tempstring, "field ", sizeof(tempstring)); + break; + case ev_void: + com.strncat(tempstring, "void ", sizeof(tempstring)); + break; + case ev_float: + com.strncat(tempstring, "float ", sizeof(tempstring)); + break; + case ev_vector: + com.strncat(tempstring, "vector ", sizeof(tempstring)); + break; + case ev_pointer: + com.strncat(tempstring, "pointer ", sizeof(tempstring)); + break; + default: + com.sprintf (tempstring2, "bad type %i ", d->type & ~DEF_SAVEGLOBAL); + com.strncat(tempstring, tempstring2, sizeof(tempstring)); + break; + } + if (com.strlen(name) > sizeof(tempstring2)-4) + { + Mem_Copy (tempstring2, name, sizeof(tempstring2)-4); + tempstring2[sizeof(tempstring2)-4] = tempstring2[sizeof(tempstring2)-3] = tempstring2[sizeof(tempstring2)-2] = '.'; + tempstring2[sizeof(tempstring2)-1] = 0; + name = tempstring2; + } + com.strncat(tempstring, name, sizeof(tempstring)); + for (j = (int)com.strlen(name);j < 25;j++) + com.strncat(tempstring, " ", sizeof(tempstring)); + com.sprintf(tempstring2, "%5d", counts[i]); + com.strncat(tempstring, tempstring2, sizeof(tempstring)); + com.strncat(tempstring, "\n", sizeof(tempstring)); + if (com.strlen(tempstring) >= sizeof(tempstring)/2) + { + Msg(tempstring); + tempstring[0] = 0; + } + if (counts[i]) + { + used++; + usedamount += prvm_type_size[d->type & ~DEF_SAVEGLOBAL]; + } + } + Mem_Free(counts); + Msg("%s: %i entity fields (%i in use), totalling %i bytes per edict (%i in use), %i edicts allocated, %i bytes total spent on edict fields (%i needed)\n", PRVM_NAME, vm.prog->progs->entityfields, used, vm.prog->progs->entityfields * 4, usedamount * 4, vm.prog->max_edicts, vm.prog->progs->entityfields * 4 * vm.prog->max_edicts, usedamount * 4 * vm.prog->max_edicts); + + vm.prog = NULL; +} + +void PRVM_Globals_f (void) +{ + int i; + // TODO + /*if (!sv.active) + { + Msg("no progs loaded\n"); + return; + }*/ + if(Cmd_Argc () != 2) + { + Msg("prvm_globals \n"); + return; + } + + + if(!PRVM_SetProgFromString (Cmd_Argv (1))) + return; + + Msg("%s :", PRVM_NAME); + + for (i = 0;i < vm.prog->progs->numglobaldefs;i++) + Msg("%s\n", PRVM_GetString(vm.prog->globaldefs[i].s_name)); + Msg("%i global variables, totalling %i bytes\n", vm.prog->progs->numglobals, vm.prog->progs->numglobals * 4); + + vm.prog = NULL; +} + +/* +=============== +PRVM_Global +=============== +*/ +void PRVM_Global_f(void) +{ + ddef_t *global; + if( Cmd_Argc() != 3 ) + { + Msg( "prvm_global \n" ); + return; + } + + + if( !PRVM_SetProgFromString( Cmd_Argv(1) ) ) + return; + + global = PRVM_ED_FindGlobal( Cmd_Argv(2) ); + if( !global ) + Msg( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + Msg( "%s: %s\n", Cmd_Argv(2), PRVM_ValueString( (etype_t)global->type, (prvm_eval_t *) &vm.prog->globals.gp[ global->ofs ] ) ); + vm.prog = NULL; +} + +/* +=============== +PRVM_GlobalSet +=============== +*/ +void PRVM_GlobalSet_f(void) +{ + ddef_t *global; + if( Cmd_Argc() != 4 ) { + Msg( "prvm_globalset \n" ); + return; + } + + + if( !PRVM_SetProgFromString( Cmd_Argv(1) ) ) + return; + + global = PRVM_ED_FindGlobal( Cmd_Argv(2) ); + if( !global ) + Msg( "No global '%s' in %s!\n", Cmd_Argv(2), Cmd_Argv(1) ); + else + PRVM_ED_ParseEpair( NULL, global, Cmd_Argv(3) ); + vm.prog = NULL; +} + +// LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value +void VM_Warning( const char *fmt, ... ) +{ + va_list argptr; + static char msg[MAX_MSGLEN]; + + va_start( argptr, fmt ); + com.vsnprintf( msg, sizeof(msg), fmt, argptr ); + va_end( argptr ); + + Msg( msg ); + // TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Msgf [9/13/2006 Black] + //PRVM_PrintState(); +} + +/* +=============== +VM_error + +Abort the server with a game error +=============== +*/ +void VM_Error( const char *fmt, ... ) +{ + char msg[1024]; + va_list argptr; + + va_start (argptr, fmt); + com.vsprintf (msg, fmt, argptr); + va_end (argptr); + + Host_Error("Prvm error: %s", msg); +} + +/* +=============== +PRVM_InitProg +=============== +*/ +void PRVM_InitProg( int prognr ) +{ + if(prognr < 0 || prognr >= PRVM_MAXPROGS) + Host_Error("PRVM_InitProg: Invalid program number %i",prognr); + + vm.prog = &prog_list[prognr]; + + if(vm.prog->loaded) PRVM_ResetProg(); + + Mem_Set(vm.prog, 0, sizeof(prvm_prog_t)); + + vm.prog->time = &vm.prog->_time; + vm.prog->error_cmd = VM_Error; +} + +int PRVM_GetProgNr() +{ + return vm.prog - prog_list; +} + +void *_PRVM_Alloc(size_t buffersize, const char *filename, int fileline) +{ + return com.malloc(vm.prog->progs_mempool, buffersize, filename, fileline); +} + +void _PRVM_Free(void *buffer, const char *filename, int fileline) +{ + com.free(buffer, filename, fileline); +} + +void _PRVM_FreeAll(const char *filename, int fileline) +{ + vm.prog->progs = NULL; + vm.prog->fielddefs = NULL; + vm.prog->functions = NULL; + com.clearpool( vm.prog->progs_mempool, filename, fileline); +} + +// LordHavoc: turned PRVM_EDICT_NUM into a #define for speed reasons +pr_edict_t *PRVM_EDICT_NUM_ERROR(int n, char *filename, int fileline) +{ + PRVM_ERROR ("PRVM_EDICT_NUM: %s: bad number %i (called at %s:%i)", PRVM_NAME, n, filename, fileline); + return NULL; +} + +const char *PRVM_GetString(int num) +{ + if( num >= 0 ) + { + if( num < vm.prog->stringssize ) return vm.prog->strings + num; + else if( num <= vm.prog->stringssize + vm_tempstringsbuf.maxsize) + { + num -= vm.prog->stringssize; + if( num < vm_tempstringsbuf.cursize ) + return (char *)vm_tempstringsbuf.data + num; + else + { + VM_Warning("PRVM_GetString: Invalid temp-string offset (%i >= %i vm_tempstringsbuf.cursize)\n", num, vm_tempstringsbuf.cursize); + return ""; + } + } + else + { + VM_Warning("PRVM_GetString: Invalid constant-string offset (%i >= %i prog->stringssize)\n", num, vm.prog->stringssize); + return ""; + } + } + else + { + num = -1 - num; + if (num < vm.prog->numknownstrings) + { + if (!vm.prog->knownstrings[num]) + VM_Warning("PRVM_GetString: Invalid zone-string offset (%i has been freed)\n", num); + return vm.prog->knownstrings[num]; + } + else + { + VM_Warning("PRVM_GetString: Invalid zone-string offset (%i >= %i)\n", num, vm.prog->numknownstrings); + return ""; + } + } +} + +int PRVM_SetEngineString( const char *s ) +{ + int i; + if (!s) + return 0; + if (s >= vm.prog->strings && s <= vm.prog->strings + vm.prog->stringssize) + PRVM_ERROR("PRVM_SetEngineString: s in vm.prog->strings area"); + // if it's in the tempstrings area, use a reserved range + // (otherwise we'd get millions of useless string offsets cluttering the database) + if (s >= (char *)vm_tempstringsbuf.data && s < (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.maxsize) + return vm.prog->stringssize + (s - (char *)vm_tempstringsbuf.data); + // see if it's a known string address + for (i = 0;i < vm.prog->numknownstrings;i++) + if (vm.prog->knownstrings[i] == s) + return -1 - i; + // new unknown engine string + MsgDev(D_STRING, "new engine string %p\n", s ); + for (i = vm.prog->firstfreeknownstring;i < vm.prog->numknownstrings;i++) + if (!vm.prog->knownstrings[i]) + break; + if (i >= vm.prog->numknownstrings) + { + if (i >= vm.prog->maxknownstrings) + { + const char **oldstrings = vm.prog->knownstrings; + const byte *oldstrings_freeable = vm.prog->knownstrings_freeable; + vm.prog->maxknownstrings += 128; + vm.prog->knownstrings = (const char **)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(char *)); + vm.prog->knownstrings_freeable = (byte *)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(byte)); + if (vm.prog->numknownstrings) + { + Mem_Copy((char **)vm.prog->knownstrings, oldstrings, vm.prog->numknownstrings * sizeof(char *)); + Mem_Copy((char **)vm.prog->knownstrings_freeable, oldstrings_freeable, vm.prog->numknownstrings * sizeof(byte)); + } + } + vm.prog->numknownstrings++; + } + vm.prog->firstfreeknownstring = i + 1; + vm.prog->knownstrings[i] = s; + return -1 - i; +} + +// temp string handling + +// all tempstrings go into this buffer consecutively, and it is reset +// whenever PRVM_ExecuteProgram returns to the engine +// (technically each PRVM_ExecuteProgram call saves the cursize value and +// restores it on return, so multiple recursive calls can share the same +// buffer) +// the buffer size is automatically grown as needed + +int PRVM_SetTempString( const char *s ) +{ + int size; + char *t; + + if (!s) return 0; + + size = (int)com.strlen(s) + 1; + MsgDev( D_STRING, "PRVM_SetTempString: cursize %i, size %i\n", vm_tempstringsbuf.cursize, size); + if (vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size) + { + size_t old_maxsize = vm_tempstringsbuf.maxsize; + if( vm_tempstringsbuf.cursize + size >= 1<<28 ) + PRVM_ERROR("PRVM_SetTempString: ran out of tempstring memory! (refusing to grow tempstring buffer over 256MB, cursize %i, size %i)\n", vm_tempstringsbuf.cursize, size); + vm_tempstringsbuf.maxsize = max( vm_tempstringsbuf.maxsize, 65536 ); + while( vm_tempstringsbuf.maxsize < vm_tempstringsbuf.cursize + size ) + vm_tempstringsbuf.maxsize *= 2; + if (vm_tempstringsbuf.maxsize != old_maxsize || vm_tempstringsbuf.data == NULL) + { + MsgDev( D_NOTE, "PRVM_SetTempString: enlarging tempstrings buffer (%iKB -> %iKB)\n", old_maxsize/1024, vm_tempstringsbuf.maxsize/1024); + vm_tempstringsbuf.data = Mem_Realloc( vm.prog->progs_mempool, vm_tempstringsbuf.data, vm_tempstringsbuf.maxsize ); + } + } + t = (char *)vm_tempstringsbuf.data + vm_tempstringsbuf.cursize; + Mem_Copy( t, s, size ); + vm_tempstringsbuf.cursize += size; + return PRVM_SetEngineString( t ); +} + +int PRVM_AllocString(size_t bufferlength, char **pointer) +{ + int i; + if (!bufferlength) + return 0; + for (i = vm.prog->firstfreeknownstring;i < vm.prog->numknownstrings;i++) + if (!vm.prog->knownstrings[i]) + break; + if (i >= vm.prog->numknownstrings) + { + if (i >= vm.prog->maxknownstrings) + { + const char **oldstrings = vm.prog->knownstrings; + const byte *oldstrings_freeable = vm.prog->knownstrings_freeable; + vm.prog->maxknownstrings += 128; + vm.prog->knownstrings = (const char **)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(char *)); + vm.prog->knownstrings_freeable = (byte *)PRVM_Alloc(vm.prog->maxknownstrings * sizeof(byte)); + if (vm.prog->numknownstrings) + { + Mem_Copy((char **)vm.prog->knownstrings, oldstrings, vm.prog->numknownstrings * sizeof(char *)); + Mem_Copy((char **)vm.prog->knownstrings_freeable, oldstrings_freeable, vm.prog->numknownstrings * sizeof(byte)); + } + } + vm.prog->numknownstrings++; + } + vm.prog->firstfreeknownstring = i + 1; + vm.prog->knownstrings[i] = (char *)PRVM_Alloc(bufferlength); + vm.prog->knownstrings_freeable[i] = true; + if (pointer) + *pointer = (char *)(vm.prog->knownstrings[i]); + return -1 - i; +} + +void PRVM_FreeString(int num) +{ + if (num == 0) + PRVM_ERROR("PRVM_FreeString: attempt to free a NULL string"); + else if (num >= 0 && num < vm.prog->stringssize) + PRVM_ERROR("PRVM_FreeString: attempt to free a constant string"); + else if (num < 0 && num >= -vm.prog->numknownstrings) + { + num = -1 - num; + if (!vm.prog->knownstrings[num]) + PRVM_ERROR("PRVM_FreeString: attempt to free a non-existent or already freed string"); + if (!vm.prog->knownstrings[num]) + PRVM_ERROR("PRVM_FreeString: attempt to free a string owned by the engine"); + PRVM_Free((char *)vm.prog->knownstrings[num]); + vm.prog->knownstrings[num] = NULL; + vm.prog->knownstrings_freeable[num] = false; + vm.prog->firstfreeknownstring = min(vm.prog->firstfreeknownstring, num); + } + else + PRVM_ERROR("PRVM_FreeString: invalid string offset %i", num); +} + diff --git a/vprogs/pr_exec.c b/vprogs/pr_exec.c new file mode 100644 index 00000000..4e4b580e --- /dev/null +++ b/vprogs/pr_exec.c @@ -0,0 +1,1497 @@ +/* +Copyright (C) 1996-1997 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. + +*/ + +#include "vprogs.h" +#include "mathlib.h" + +char *prvm_opnames[] = +{ +"^5DONE", + +"MUL_F", +"MUL_V", +"MUL_FV", +"MUL_VF", + +"DIV", + +"ADD_F", +"ADD_V", + +"SUB_F", +"SUB_V", + +"^2EQ_F", +"^2EQ_V", +"^2EQ_S", +"^2EQ_E", +"^2EQ_FNC", + +"^2NE_F", +"^2NE_V", +"^2NE_S", +"^2NE_E", +"^2NE_FNC", + +"^2LE", +"^2GE", +"^2LT", +"^2GT", + +"^6FIELD_F", +"^6FIELD_V", +"^6FIELD_S", +"^6FIELD_ENT", +"^6FIELD_FLD", +"^6FIELD_FNC", + +"^1ADDRESS", + +"STORE_F", +"STORE_V", +"STORE_S", +"STORE_ENT", +"STORE_FLD", +"STORE_FNC", + +"^1STOREP_F", +"^1STOREP_V", +"^1STOREP_S", +"^1STOREP_ENT", +"^1STOREP_FLD", +"^1STOREP_FNC", + +"^5RETURN", + +"^2NOT_F", +"^2NOT_V", +"^2NOT_S", +"^2NOT_ENT", +"^2NOT_FNC", +"^2NOT_BITI", +"^2NOT_BITF", + +"^5IF", +"^5IFNOT", + +"^3CALL0", +"^3CALL1", +"^3CALL2", +"^3CALL3", +"^3CALL4", +"^3CALL5", +"^3CALL6", +"^3CALL7", +"^3CALL8", +"^3CALL9", + +"^1STATE", + +"^5GOTO", + +"^2AND", +"^2OR", + +"BITAND", +"BITOR", +"MULSTORE_F", +"MULSTORE_V", +"MULSTOREP_F", +"MULSTOREP_V", + +"DIVSTORE_F", +"DIVSTOREP_F", + +"ADDSTORE_F", +"ADDSTORE_V", +"ADDSTOREP_F", +"ADDSTOREP_V", + +"SUBSTORE_F", +"SUBSTORE_V", +"SUBSTOREP_F", +"SUBSTOREP_V", + +"FETCH_GBL_F", +"FETCH_GBL_V", +"FETCH_GBL_S", +"FETCH_GBL_E", +"FETCH_G_FNC", + +"^9CSTATE", +"^9CWSTATE", + +"^6THINKTIME", + +"^4BITSET", +"^4BITSETP", +"^4BITCLR", +"^4BITCLRP", + +"^9RAND0", +"^9RAND1", +"^9RAND2", +"^9RANDV0", +"^9RANDV1", +"^9RANDV2", + +"^6SWITCH_F", +"^6SWITCH_V", +"^6SWITCH_S", +"^6SWITCH_E", +"^6SWITCH_FNC", + +"^6CASE", +"^6CASERANGE", + +"STORE_I", +"STORE_IF", +"STORE_FI", + +"ADD_I", +"ADD_FI", +"ADD_IF", + +"SUB_I", +"SUB_FI", +"SUB_IF", + +"CONV_ITOF", +"CONV_FTOI", +"CP_ITOF", +"CP_FTOI", +"LOAD_I", +"STOREP_I", +"STOREP_IF", +"STOREP_FI", + +"BITAND_I", +"BITOR_I", + +"MUL_I", +"DIV_I", +"EQ_I", +"NE_I", + +"IFNOTS", +"IFS", + +"NOT_I", + +"DIV_VF", + +"POWER_I", +"RSHIFT_I", +"LSHIFT_I", +"RSHIFT_F", +"LSHIFT_F", +"MODULO_I", +"MODULO_F", +}; + +char *PRVM_GlobalString (int ofs); +char *PRVM_GlobalStringNoContents (int ofs); + + +//============================================================================= + +/* +================= +PRVM_PrintStatement +================= +*/ +extern cvar_t *prvm_statementprofiling; +void PRVM_PrintStatement (dstatement_t *s) +{ + size_t i; + int opnum = (int)(s - vm.prog->statements); + + Msg( "s%i: ", opnum ); + if( vm.prog->statement_linenums ) + Msg( "%s:%i: ", PRVM_GetString( vm.prog->xfunction->s_file ), vm.prog->statement_linenums[ opnum ] ); + + if( prvm_statementprofiling->integer ) + Msg( "%7.0f ", vm.prog->statement_profile[s - vm.prog->statements] ); + + if((uint)s->op < sizeof( prvm_opnames ) / sizeof( prvm_opnames[0] )) + { + Msg( "%s ", prvm_opnames[s->op] ); + i = com.cstrlen( prvm_opnames[s->op] ); + for( ; i < 10; i++ ) Msg(" "); + } + if (s->op == OP_IF || s->op == OP_IFNOT) + Msg("%s, s%i",PRVM_GlobalString((unsigned short) s->a),(signed short)s->b + opnum); + else if (s->op == OP_GOTO) + Msg("s%i",(signed short)s->a + opnum); + else if ( (unsigned)(s->op - OP_STORE_F) < 6) + { + Msg(PRVM_GlobalString((unsigned short) s->a)); + Msg(", "); + Msg(PRVM_GlobalStringNoContents((unsigned short) s->b)); + } + else if (s->op == OP_ADDRESS || (unsigned)(s->op - OP_LOAD_F) < 6) + { + if (s->a) + Msg(PRVM_GlobalString((unsigned short) s->a)); + if (s->b) + { + Msg(", "); + Msg(PRVM_GlobalStringNoContents((unsigned short) s->b)); + } + if (s->c) + { + Msg(", "); + Msg(PRVM_GlobalStringNoContents((unsigned short) s->c)); + } + } + else + { + if (s->a) + Msg(PRVM_GlobalString((unsigned short) s->a)); + if (s->b) + { + Msg(", "); + Msg(PRVM_GlobalString((unsigned short) s->b)); + } + if (s->c) + { + Msg(", "); + Msg(PRVM_GlobalStringNoContents((unsigned short) s->c)); + } + } + Msg("\n"); +} + +void PRVM_PrintFunctionStatements( const char *name ) +{ + int i, firststatement, endstatement; + mfunction_t *func; + func = PRVM_ED_FindFunction (name); + if (!func) + { + Msg("%s progs: no function named %s\n", PRVM_NAME, name); + return; + } + firststatement = func->first_statement; + if (firststatement < 0) + { + Msg("%s progs: function %s is builtin #%i\n", PRVM_NAME, name, -firststatement); + return; + } + + // find the end statement + endstatement = vm.prog->progs->numstatements; + for (i = 0;i < vm.prog->progs->numfunctions;i++) + if (endstatement > vm.prog->functions[i].first_statement && firststatement < vm.prog->functions[i].first_statement) + endstatement = vm.prog->functions[i].first_statement; + + // now print the range of statements + Msg("%s progs: disassembly of function %s (statements %i-%i):\n", PRVM_NAME, name, firststatement, endstatement); + for (i = firststatement;i < endstatement;i++) + { + PRVM_PrintStatement(vm.prog->statements + i); + vm.prog->statement_profile[i] = 0; + } +} + +/* +============ +PRVM_PrintFunction_f + +============ +*/ +void PRVM_PrintFunction_f (void) +{ + if (Cmd_Argc() != 3) + { + Msg("usage: prvm_printfunction \n"); + return; + } + + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_PrintFunctionStatements(Cmd_Argv(2)); + + vm.prog = NULL; +} + +/* +============ +PRVM_StackTrace +============ +*/ +void PRVM_StackTrace (void) +{ + mfunction_t *f; + int i; + + vm.prog->stack[vm.prog->depth].s = vm.prog->xstatement; + vm.prog->stack[vm.prog->depth].f = vm.prog->xfunction; + for (i = vm.prog->depth;i > 0;i--) + { + f = vm.prog->stack[i].f; + + if (!f) + Msg("\n"); + else + Msg("%12s : %s : statement %i\n", PRVM_GetString(f->s_file), PRVM_GetString(f->s_name), vm.prog->stack[i].s - f->first_statement); + } +} + + +void PRVM_Profile (int maxfunctions, int mininstructions) +{ + mfunction_t *f, *best; + int i, num; + double max; + + Msg( "%s Profile:\n[CallCount] [Statements] [BuiltinCost]\n", PRVM_NAME ); + + num = 0; + do + { + max = 0; + best = NULL; + for (i=0 ; iprogs->numfunctions ; i++) + { + f = &vm.prog->functions[i]; + if (max < f->profile + f->builtinsprofile + f->callcount) + { + max = f->profile + f->builtinsprofile + f->callcount; + best = f; + } + } + if (best) + { + if (num < maxfunctions && max >= mininstructions) + { + if (best->first_statement < 0) + Msg("%9.0f ----- builtin ----- %s\n", best->callcount, PRVM_GetString(best->s_name)); + else + Msg("%9.0f %9.0f %9.0f %s\n", best->callcount, best->profile, best->builtinsprofile, PRVM_GetString(best->s_name)); + } + num++; + best->profile = 0; + best->builtinsprofile = 0; + best->callcount = 0; + } + } while (best); +} + +/* +============ +PRVM_Profile_f + +============ +*/ +void PRVM_Profile_f (void) +{ + int howmany; + + howmany = 1<<30; + if (Cmd_Argc() == 3) + howmany = com.atoi(Cmd_Argv(2)); + else if (Cmd_Argc() != 2) + { + Msg("prvm_profile \n"); + return; + } + + if(!PRVM_SetProgFromString(Cmd_Argv(1))) + return; + + PRVM_Profile(howmany, 1); + + vm.prog = NULL; +} + +void PRVM_CrashAll() +{ + int i; + prvm_prog_t *oldprog = vm.prog; + + for(i = 0; i < PRVM_MAXPROGS; i++) + { + if(!PRVM_ProgLoaded(i)) + continue; + PRVM_SetProg(i); + PRVM_Crash(); + } + vm.prog = oldprog; +} + +void PRVM_PrintState(void) +{ + int i; + if( vm.prog->xfunction ) + { + for(i = -7; i <= 0;i++) + if (vm.prog->xstatement + i >= vm.prog->xfunction->first_statement) + PRVM_PrintStatement (vm.prog->statements + vm.prog->xstatement + i); + } + else + Msg("null function executing??\n"); + PRVM_StackTrace (); +} + +void PRVM_Crash() +{ + if( vm.prog == NULL ) + return; + + if( vm.prog->depth > 0 ) + { + Msg("QuakeC crash report for %s:\n", PRVM_NAME); + PRVM_PrintState(); + } + + // dump the stack so host_error can shutdown functions + vm.prog->depth = 0; + vm.prog->localstack_used = 0; + + // reset the prog pointer + vm.prog = NULL; +} + +/* +============================================================================ +PRVM_ExecuteProgram + +The interpretation main loop +============================================================================ +*/ + +/* +==================== +PRVM_EnterFunction + +Returns the new program statement counter +==================== +*/ +int PRVM_EnterFunction( mfunction_t *f ) +{ + int i, j, c, o; + + if( !f ) PRVM_ERROR ("PRVM_EnterFunction: NULL function in %s", PRVM_NAME); + + vm.prog->stack[vm.prog->depth].s = vm.prog->xstatement; + vm.prog->stack[vm.prog->depth].f = vm.prog->xfunction; + vm.prog->depth++; + if (vm.prog->depth >=PRVM_MAX_STACK_DEPTH) + PRVM_ERROR( "stack overflow\n" ); + + // save off any locals that the new function steps on + c = f->locals; + if (vm.prog->localstack_used + c > PRVM_LOCALSTACK_SIZE) + PRVM_ERROR( "PRVM_ExecuteProgram: locals stack overflow in %s", PRVM_NAME ); + + for( i = 0; i < c; i++ ) + vm.prog->localstack[vm.prog->localstack_used+i] = ((int *)vm.prog->globals.gp)[f->parm_start + i]; + vm.prog->localstack_used += c; + + // copy parameters + o = f->parm_start; + for (i=0 ; inumparms ; i++) + { + for (j=0 ; jparm_size[i] ; j++) + { + ((int *)vm.prog->globals.gp)[o] = ((int *)vm.prog->globals.gp)[OFS_PARM0+i*3+j]; + o++; + } + } + + vm.prog->xfunction = f; + return f->first_statement - 1; // offset the s++ +} + +/* +==================== +PRVM_LeaveFunction +==================== +*/ +int PRVM_LeaveFunction( void ) +{ + int i, c; + + if (vm.prog->depth <= 0) + PRVM_ERROR ("prog stack underflow in %s", PRVM_NAME); + + if (!vm.prog->xfunction) + PRVM_ERROR ("PR_LeaveFunction: NULL function in %s", PRVM_NAME); + // restore locals from the stack + c = vm.prog->xfunction->locals; + vm.prog->localstack_used -= c; + if (vm.prog->localstack_used < 0) + PRVM_ERROR ("PRVM_ExecuteProgram: locals stack underflow in %s", PRVM_NAME); + + for (i=0 ; i < c ; i++) + ((int *)vm.prog->globals.gp)[vm.prog->xfunction->parm_start + i] = vm.prog->localstack[vm.prog->localstack_used+i]; + + // up stack + vm.prog->depth--; + vm.prog->xfunction = vm.prog->stack[vm.prog->depth].f; + return vm.prog->stack[vm.prog->depth].s; +} + +void PRVM_Init_Exec(void) +{ + // dump the stack + vm.prog->depth = 0; + vm.prog->localstack_used = 0; + // reset the string table + // nothing here yet +} + +/* +==================== +PRVM_ExecuteProgram +==================== +*/ +// LordHavoc: optimized +#define OPA ((prvm_eval_t *)&vm.prog->globals.gp[(word)st->a]) +#define OPB ((prvm_eval_t *)&vm.prog->globals.gp[(word)st->b]) +#define OPC ((prvm_eval_t *)&vm.prog->globals.gp[(word)st->c]) +extern cvar_t *prvm_boundscheck; +extern cvar_t *prvm_traceqc; +extern cvar_t *prvm_statementprofiling; +extern int PRVM_ED_FindFieldOffset (const char *field); +extern ddef_t* PRVM_ED_FindGlobal(const char *name); + +void PRVM_ExecuteProgram( func_t fnum, const char *name, const char *file, const int line ) +{ + dstatement_t *st, *startst; + mfunction_t *f, *newf; + pr_edict_t *ed; + prvm_eval_t *ptr, *_switch; + int switchtype, exitdepth; + int i, jumpcount, cachedpr_trace; + + if( !fnum || fnum >= (uint)vm.prog->progs->numfunctions ) + { + if( vm.prog->pev && PRVM_G_INT(vm.prog->pev->ofs) ) + PRVM_ED_Print(PRVM_PROG_TO_EDICT(PRVM_G_INT(vm.prog->pev->ofs))); + PRVM_ERROR( "PRVM_ExecuteProgram: QC function %s is missing( called at %s:%i)\n", name, file, line ); + return; + } + + f = &vm.prog->functions[fnum]; + + vm.prog->trace = prvm_traceqc->value; + + // we know we're done when pr_depth drops to this + exitdepth = vm.prog->depth; + + // make a stack frame + st = &vm.prog->statements[PRVM_EnterFunction (f)]; + // save the starting statement pointer for profiling + // (when the function exits or jumps, the (st - startst) integer value is + // added to the function's profile counter) + startst = st; + // instead of counting instructions, we count jumps + jumpcount = 0; + // add one to the callcount of this function because otherwise engine-called functions aren't counted + vm.prog->xfunction->callcount++; + +chooseexecprogram: + cachedpr_trace = vm.prog->trace; + + while( 1 ) + { + st++; + + if( vm.prog->trace ) PRVM_PrintStatement(st); + if( prvm_statementprofiling->value ) vm.prog->statement_profile[st - vm.prog->statements]++; + + switch( st->op ) + { + case OP_ADD_F: + OPC->_float = OPA->_float + OPB->_float; + break; + case OP_ADD_V: + OPC->vector[0] = OPA->vector[0] + OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] + OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] + OPB->vector[2]; + break; + case OP_SUB_F: + OPC->_float = OPA->_float - OPB->_float; + break; + case OP_SUB_V: + OPC->vector[0] = OPA->vector[0] - OPB->vector[0]; + OPC->vector[1] = OPA->vector[1] - OPB->vector[1]; + OPC->vector[2] = OPA->vector[2] - OPB->vector[2]; + break; + case OP_MUL_F: + OPC->_float = OPA->_float * OPB->_float; + break; + case OP_MUL_V: + OPC->_float = OPA->vector[0]*OPB->vector[0] + OPA->vector[1]*OPB->vector[1] + OPA->vector[2]*OPB->vector[2]; + break; + case OP_MUL_FV: + OPC->vector[0] = OPA->_float * OPB->vector[0]; + OPC->vector[1] = OPA->_float * OPB->vector[1]; + OPC->vector[2] = OPA->_float * OPB->vector[2]; + break; + case OP_MUL_VF: + OPC->vector[0] = OPB->_float * OPA->vector[0]; + OPC->vector[1] = OPB->_float * OPA->vector[1]; + OPC->vector[2] = OPB->_float * OPA->vector[2]; + break; + case OP_DIV_F: + // don't divide by zero + if( OPB->_float == 0.0f ) OPC->_float = 0.0f; + else OPC->_float = OPA->_float / OPB->_float; + break; + case OP_DIV_VF: + OPC->vector[0] = OPB->_float / OPA->vector[0]; + OPC->vector[1] = OPB->_float / OPA->vector[1]; + OPC->vector[2] = OPB->_float / OPA->vector[2]; + break; + case OP_BITAND: + OPC->_float = (float)((int)OPA->_float & (int)OPB->_float); + break; + case OP_BITOR: + OPC->_float = (float)((int)OPA->_float | (int)OPB->_float); + break; + case OP_GE: + OPC->_float = (float)OPA->_float >= OPB->_float; + break; + case OP_GE_I: + OPC->_int = (int)(OPA->_int >= OPB->_int); + break; + case OP_GE_IF: + OPC->_float = (float)(OPA->_int >= OPB->_float); + break; + case OP_GE_FI: + OPC->_float = (float)(OPA->_float >= OPB->_int); + break; + case OP_LE: + OPC->_float = OPA->_float <= OPB->_float; + break; + case OP_LE_I: + OPC->_int = (int)(OPA->_int <= OPB->_int); + break; + case OP_LE_IF: + OPC->_float = (float)(OPA->_int <= OPB->_float); + break; + case OP_LE_FI: + OPC->_float = (float)(OPA->_float <= OPB->_int); + break; + case OP_GT: + OPC->_float = OPA->_float > OPB->_float; + break; + case OP_GT_I: + OPC->_int = (int)(OPA->_int > OPB->_int); + break; + case OP_GT_IF: + OPC->_float = (float)(OPA->_int > OPB->_float); + break; + case OP_GT_FI: + OPC->_float = (float)(OPA->_float > OPB->_int); + break; + case OP_LT: + OPC->_float = OPA->_float < OPB->_float; + break; + case OP_LT_I: + OPC->_int = (int)(OPA->_int < OPB->_int); + break; + case OP_LT_IF: + OPC->_float = (float)(OPA->_int < OPB->_float); + break; + case OP_LT_FI: + OPC->_float = (float)(OPA->_float < OPB->_int); + break; + case OP_AND: + OPC->_float = OPA->_float && OPB->_float; + break; + case OP_OR: + OPC->_float = OPA->_float || OPB->_float; + break; + case OP_NOT_F: + OPC->_float = !OPA->_float; + break; + case OP_NOT_V: + OPC->_float = !OPA->vector[0] && !OPA->vector[1] && !OPA->vector[2]; + break; + case OP_NOT_S: + OPC->_float = !OPA->string || !*PRVM_GetString(OPA->string); + break; + case OP_NOT_FNC: + OPC->_float = !OPA->function; + break; + case OP_NOT_ENT: + OPC->_float = (OPA->edict == 0); + break; + case OP_NOT_BITI: + OPC->_int = ~OPA->_int; + break; + case OP_NOT_BITF: + OPC->_float = (float)(~(int)OPA->_float); + break; + case OP_EQ_F: + OPC->_float = (float)(OPA->_float == OPB->_float); + break; + case OP_EQ_IF: + OPC->_float = (float)(OPA->_int == OPB->_float); + break; + case OP_EQ_FI: + OPC->_float = (float)(OPA->_float == OPB->_int); + break; + case OP_EQ_V: + OPC->_float = (OPA->vector[0] == OPB->vector[0]) && (OPA->vector[1] == OPB->vector[1]) && (OPA->vector[2] == OPB->vector[2]); + break; + case OP_EQ_S: + if( OPA->string == OPB->string ) OPC->_float = true; // try fast method first + else if( !OPA->string ) + { + if( !OPB->string || !*PRVM_GetString( OPB->string )) + OPC->_float = true; + else OPC->_float = false; + } + else if( !OPB->string ) + { + if( !OPA->string || !*PRVM_GetString( OPA->string )) + OPC->_float = true; + else OPC->_float = false; + } + else OPC->_float = !com.strcmp( PRVM_GetString( OPA->string ), PRVM_GetString( OPB->string )); + break; + case OP_EQ_E: + OPC->_float = (float)(OPA->_int == OPB->_int); + break; + case OP_EQ_FNC: + OPC->_float = OPA->function == OPB->function; + break; + case OP_NE_F: + OPC->_float = (float)(OPA->_float != OPB->_float); + break; + case OP_NE_V: + OPC->_float = (OPA->vector[0] != OPB->vector[0]) || (OPA->vector[1] != OPB->vector[1]) || (OPA->vector[2] != OPB->vector[2]); + break; + case OP_NE_S: + if (OPA->string == OPB->string) OPC->_float = false; // try fast method first + else if (!OPA->string) + { + if (!OPB->string || !*PRVM_GetString(OPB->string)) + OPC->_float = false; + else OPC->_float = true; + } + else if (!OPB->string) + { + if (!OPA->string || !*PRVM_GetString(OPA->string)) + OPC->_float = false; + else OPC->_float = true; + } + else OPC->_float = com.strcmp(PRVM_GetString(OPA->string), PRVM_GetString(OPB->string)); + break; + case OP_NE_E: + OPC->_float = (float)(OPA->_int != OPB->_int); + break; + case OP_NE_FNC: + OPC->_float = (float)(OPA->function != OPB->function); + break; + case OP_STORE_IF: + OPB->_float = (float)OPA->_int; + break; + case OP_STORE_FI: + OPB->_int = (int)OPA->_float; + break; + case OP_STORE_F: + case OP_STORE_I: + case OP_STORE_ENT: + case OP_STORE_FLD: // integers + case OP_STORE_S: + case OP_STORE_FNC: // pointers + OPB->_int = OPA->_int; + break; + case OP_STORE_V: + OPB->vector[0] = OPA->vector[0]; + OPB->vector[1] = OPA->vector[1]; + OPB->vector[2] = OPA->vector[2]; + break; + case OP_STOREP_IF: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + ptr->_float = (float)OPA->_int; + break; + case OP_STOREP_FI: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + ptr->_int = (int)OPA->_float; + break; + case OP_STOREP_I: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + ptr->_int = OPA->_int; + break; + case OP_STOREP_F: + case OP_STOREP_ENT: + case OP_STOREP_FLD: // integers + case OP_STOREP_S: + case OP_STOREP_FNC: // pointers + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + ptr->_int = OPA->_int; + break; + case OP_STOREP_V: + PRVM_CHECK_PTR(OPB, 12); + ptr = PRVM_ED_POINTER(OPB); + ptr->vector[0] = OPA->vector[0]; + ptr->vector[1] = OPA->vector[1]; + ptr->vector[2] = OPA->vector[2]; + break; + case OP_STOREP_C: //store character in a string + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + *(byte *)ptr = (char)OPA->_float; + break; + case OP_MULSTORE_F: + OPB->_float *= OPA->_float; + break; + case OP_MULSTORE_V: + OPB->vector[0] *= OPA->_float; + OPB->vector[1] *= OPA->_float; + OPB->vector[2] *= OPA->_float; + break; + case OP_MULSTOREP_F: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + OPC->_float = (ptr->_float *= OPA->_float); + break; + case OP_MULSTOREP_V: + PRVM_CHECK_PTR(OPB, 12); + ptr = PRVM_ED_POINTER(OPB); + OPC->vector[0] = (ptr->vector[0] *= OPA->_float); + OPC->vector[0] = (ptr->vector[1] *= OPA->_float); + OPC->vector[0] = (ptr->vector[2] *= OPA->_float); + break; + case OP_DIVSTORE_F: + OPB->_float /= OPA->_float; + break; + case OP_DIVSTOREP_F: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + OPC->_float = (ptr->_float /= OPA->_float); + break; + case OP_ADDSTORE_F: + OPB->_float += OPA->_float; + break; + case OP_ADDSTORE_V: + OPB->vector[0] += OPA->vector[0]; + OPB->vector[1] += OPA->vector[1]; + OPB->vector[2] += OPA->vector[2]; + break; + case OP_ADDSTOREP_F: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + OPC->_float = (ptr->_float += OPA->_float); + break; + case OP_ADDSTOREP_V: + PRVM_CHECK_PTR(OPB, 12); + ptr = PRVM_ED_POINTER(OPB); + OPC->vector[0] = (ptr->vector[0] += OPA->vector[0]); + OPC->vector[1] = (ptr->vector[1] += OPA->vector[1]); + OPC->vector[2] = (ptr->vector[2] += OPA->vector[2]); + break; + case OP_SUBSTORE_F: + OPB->_float -= OPA->_float; + break; + case OP_SUBSTORE_V: + OPB->vector[0] -= OPA->vector[0]; + OPB->vector[1] -= OPA->vector[1]; + OPB->vector[2] -= OPA->vector[2]; + break; + case OP_SUBSTOREP_F: + PRVM_CHECK_PTR(OPB, 4); + ptr = PRVM_ED_POINTER(OPB); + OPC->_float = (ptr->_float -= OPA->_float); + break; + case OP_SUBSTOREP_V: + PRVM_CHECK_PTR(OPB, 12); + ptr = PRVM_ED_POINTER(OPB); + OPC->vector[0] = (ptr->vector[0] -= OPA->vector[0]); + OPC->vector[1] = (ptr->vector[1] -= OPA->vector[1]); + OPC->vector[2] = (ptr->vector[2] -= OPA->vector[2]); + break; + case OP_ADDRESS: + if (prvm_boundscheck->value && ((uint)(OPB->_int) >= (uint)(vm.prog->progs->entityfields))) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("%s attempted to address an invalid field (%i) in an edict", PRVM_NAME, OPB->_int); + return; + } + if (OPA->edict == 0 && vm.prog->protect_world) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("forbidden assignment to null/world entity in %s", PRVM_NAME); + return; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = (byte *)((int *)ed->progs.vp + OPB->_int) - (byte *)vm.prog->edictsfields; + break; + case OP_LOAD_I: + case OP_LOAD_F: + case OP_LOAD_FLD: + case OP_LOAD_ENT: + case OP_LOAD_S: + case OP_LOAD_FNC: + if (prvm_boundscheck->value && ((uint)(OPB->_int) >= (uint)(vm.prog->progs->entityfields))) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("%s attempted to read an invalid field in an edict (%i)", PRVM_NAME, OPB->_int); + return; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = ((prvm_eval_t *)((int *)ed->progs.vp + OPB->_int))->_int; + break; + case OP_LOAD_V: + if (prvm_boundscheck->value && (OPB->_int < 0 || OPB->_int + 2 >= vm.prog->progs->entityfields)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("%s attempted to read an invalid field in an edict (%i)", PRVM_NAME, OPB->_int); + return; + } + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->vector[0] = ((prvm_eval_t *)((int *)ed->progs.vp + OPB->_int))->vector[0]; + OPC->vector[1] = ((prvm_eval_t *)((int *)ed->progs.vp + OPB->_int))->vector[1]; + OPC->vector[2] = ((prvm_eval_t *)((int *)ed->progs.vp + OPB->_int))->vector[2]; + break; + case OP_IFNOTS: + if (!OPA->string || !*PRVM_GetString(OPA->string)) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_IFNOT: + if (!OPA->_int) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_IFS: + if (OPA->string && *PRVM_GetString(OPA->string)) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_IF: + if (OPA->_int) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_GOTO: + vm.prog->xfunction->profile += (st - startst); + st += st->a - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + break; + case OP_CALL0: + case OP_CALL1: + case OP_CALL2: + case OP_CALL3: + case OP_CALL4: + case OP_CALL5: + case OP_CALL6: + case OP_CALL7: + case OP_CALL8: + case OP_CALL9: + vm.prog->xfunction->profile += (st - startst); + startst = st; + vm.prog->xstatement = st - vm.prog->statements; + vm.prog->argc = st->op - OP_CALL0; + if( !OPA->function ) + PRVM_ERROR( "NULL function in %s (%s)(called at %s:%i)\n", name, PRVM_NAME, file, line ); + + newf = &vm.prog->functions[OPA->function]; + newf->callcount++; + + if( newf->first_statement < 0 ) + { + // negative statements are built in functions + int builtinnumber = -newf->first_statement; + vm.prog->xfunction->builtinsprofile++; + if (builtinnumber < vm.prog->numbuiltins && vm.prog->builtins[builtinnumber]) + vm.prog->builtins[builtinnumber](); + else PRVM_ERROR( "No such builtin #%i in %s\n", builtinnumber, PRVM_NAME ); + } + else st = vm.prog->statements + PRVM_EnterFunction(newf); + startst = st; + break; + case OP_DONE: + case OP_RETURN: + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + + vm.prog->globals.gp[OFS_RETURN+0] = vm.prog->globals.gp[(word) st->a+0]; + vm.prog->globals.gp[OFS_RETURN+1] = vm.prog->globals.gp[(word) st->a+1]; + vm.prog->globals.gp[OFS_RETURN+2] = vm.prog->globals.gp[(word) st->a+2]; + + st = vm.prog->statements + PRVM_LeaveFunction(); + startst = st; + if (vm.prog->depth <= exitdepth) return; // all done + if (vm.prog->trace != cachedpr_trace) goto chooseexecprogram; + break; + case OP_STATE: + if(vm.prog->flag & PRVM_OP_STATE) + { + ed = PRVM_PROG_TO_EDICT(PRVM_G_INT(vm.prog->pev->ofs)); + PRVM_E_FLOAT(ed, PRVM_ED_FindField ("nextthink")->ofs) = *vm.prog->time + 0.1; + PRVM_E_FLOAT(ed, PRVM_ED_FindField ("frame")->ofs) = OPA->_float; + *(func_t *)((float*)ed->progs.vp + PRVM_ED_FindField ("think")->ofs) = OPB->function; + } + else + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("OP_STATE not supported by %s", PRVM_NAME); + } + break; + case OP_ADD_I: + OPC->_int = OPA->_int + OPB->_int; + break; + case OP_ADD_FI: + OPC->_float = OPA->_float + (float)OPB->_int; + break; + case OP_ADD_IF: + OPC->_float = (float)OPA->_int + OPB->_float; + break; + case OP_SUB_I: + OPC->_int = OPA->_int - OPB->_int; + break; + case OP_SUB_FI: + OPC->_float = OPA->_float - (float)OPB->_int; + break; + case OP_SUB_IF: + OPC->_float = (float)OPA->_int - OPB->_float; + break; + case OP_CONV_ITOF: + OPC->_float = (float)OPA->_int; + break; + case OP_CONV_FTOI: + OPC->_int = (int)OPA->_float; + break; + case OP_CP_ITOF: + ptr = PRVM_EV_POINTER(OPA); + OPC->_float = (float)ptr->_int; + break; + case OP_CP_FTOI: + ptr = PRVM_EV_POINTER(OPA); + OPC->_int = (int)ptr->_float; + break; + case OP_BITAND_I: + OPC->_int = (OPA->_int & OPB->_int); + break; + case OP_BITOR_I: + OPC->_int = (OPA->_int | OPB->_int); + break; + case OP_MUL_I: + OPC->_int = OPA->_int * OPB->_int; + break; + case OP_DIV_I: + // don't divide by zero + if (OPB->_int == 0) OPC->_int = 0; + else OPC->_int = OPA->_int / OPB->_int; + break; + case OP_EQ_I: + OPC->_int = (OPA->_int == OPB->_int); + break; + case OP_NE_I: + OPC->_int = (OPA->_int != OPB->_int); + break; + case OP_GLOBAL_ADD: + ed = PRVM_PROG_TO_EDICT(OPA->edict); + OPC->_int = (byte *)((int *)OPB->_int) - (byte *)vm.prog->edictsfields; + break; + case OP_POINTER_ADD: + OPC->_int = OPA->_int + OPB->_int * 4; + break; + case OP_LOADA_I: + case OP_LOADA_F: + case OP_LOADA_FLD: + case OP_LOADA_ENT: + case OP_LOADA_S: + case OP_LOADA_FNC: + ptr = (prvm_eval_t *)(&OPA->_int + OPB->_int); + OPC->_int = ptr->_int; + break; + case OP_LOADA_V: + ptr = (prvm_eval_t *)(&OPA->_int + OPB->_int); + OPC->vector[0] = ptr->vector[0]; + OPC->vector[1] = ptr->vector[1]; + OPC->vector[2] = ptr->vector[2]; + break; + case OP_ADD_SF: + OPC->_int = OPA->_int + (int)OPB->_float; + break; + case OP_SUB_S: + OPC->_int = OPA->_int - OPB->_int; + break; + case OP_LOADP_C: + ptr = PRVM_EM_POINTER(OPA->_int + (int)OPB->_float); + OPC->_float = *(byte *)ptr; + break; + case OP_LOADP_I: + case OP_LOADP_F: + case OP_LOADP_FLD: + case OP_LOADP_ENT: + case OP_LOADP_S: + case OP_LOADP_FNC: + ptr = PRVM_EM_POINTER(OPA->_int + OPB->_int); + OPC->_int = ptr->_int; + break; + case OP_LOADP_V: + ptr = PRVM_EM_POINTER(OPA->_int + OPB->_int); + OPC->vector[0] = ptr->vector[0]; + OPC->vector[1] = ptr->vector[1]; + OPC->vector[2] = ptr->vector[2]; + break; + case OP_POWER_I: + OPC->_int = OPA->_int ^ OPB->_int; + break; + case OP_RSHIFT_I: + OPC->_int = OPA->_int >> OPB->_int; + break; + case OP_LSHIFT_I: + OPC->_int = OPA->_int << OPB->_int; + break; + case OP_RSHIFT_F: + OPC->_float = (float)((int)OPA->_float >> (int)OPB->_float); + break; + case OP_LSHIFT_F: + OPC->_float = (float)((int)OPA->_float << (int)OPB->_float); + break; + case OP_FETCH_GBL_F: + case OP_FETCH_GBL_S: + case OP_FETCH_GBL_E: + case OP_FETCH_G_FNC: + i = (int)OPB->_float; + if(prvm_boundscheck->value && (i < 0 || i > ((prvm_eval_t *)&vm.prog->globals.gp[st->a-1])->_int)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs array index out of bounds", PRVM_NAME); + return; + } + ptr = (prvm_eval_t *)&vm.prog->globals.gp[(word)st->a + i]; + OPC->_int = ptr->_int; + break; + case OP_FETCH_GBL_V: + i = (int)OPB->_float; + if(prvm_boundscheck->value && (i < 0 || i > ((prvm_eval_t *)&vm.prog->globals.gp[st->a-1])->_int)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs array index out of bounds", PRVM_NAME); + return; + } + ptr = (prvm_eval_t *)&vm.prog->globals.gp[(word)st->a + ((int)OPB->_float)*3]; + OPC->vector[0] = ptr->vector[0]; + OPC->vector[1] = ptr->vector[1]; + OPC->vector[2] = ptr->vector[2]; + break; + case OP_BITSET: + OPB->_float = (float)((int)OPB->_float | (int)OPA->_float); + break; + case OP_BITSETP: + ptr = PRVM_ED_POINTER(OPB); + ptr->_float = (float)((int)ptr->_float | (int)OPA->_float); + break; + case OP_BITCLR: + OPB->_float = (float)((int)OPB->_float & ((int)OPA->_float)); + break; + case OP_BITCLRP: + ptr = PRVM_ED_POINTER(OPB); + ptr->_float = (float)((int)ptr->_float & ((int)OPA->_float)); + break; + case OP_SWITCH_F: + case OP_SWITCH_V: + case OP_SWITCH_S: + case OP_SWITCH_E: + case OP_SWITCH_FNC: + _switch = OPA; + switchtype = st->op; + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + break; + case OP_CASE: + switch(switchtype) + { + case OP_SWITCH_F: + if (_switch->_float == OPA->_float) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_SWITCH_E: + case OP_SWITCH_FNC: + if (_switch->_int == OPA->_int) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_SWITCH_S: + if (_switch->_int == OPA->_int) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + if((!_switch->_int && PRVM_GetString(OPA->string)) || (!OPA->_int && PRVM_GetString(_switch->string))) + break; + if(!com.strcmp(PRVM_GetString(_switch->string), PRVM_GetString(OPA->string))) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + case OP_SWITCH_V: + if (_switch->vector[0] == OPA->vector[0] && _switch->vector[1] == OPA->vector[1] && _switch->vector[2] == OPA->vector[2]) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + default: + PRVM_ERROR ("%s Progs OP_CASE with bad/missing OP_SWITCH", PRVM_NAME); + break; + } + break; + case OP_CASERANGE: + switch(switchtype) + { + case OP_SWITCH_F: + if (_switch->_float >= OPA->_float && _switch->_float <= OPB->_float) + { + vm.prog->xfunction->profile += (st - startst); + st += st->b - 1; // offset the s++ + startst = st; + PRVM_CHECK_INFINITE(); + } + break; + default: + PRVM_ERROR ("%s Progs OP_CASERANGE with bad/missing OP_SWITCH", PRVM_NAME); + break; + } + break; + case OP_BITAND_IF: + OPC->_int = (OPA->_int & (int)OPB->_float); + break; + case OP_BITOR_IF: + OPC->_int = (OPA->_int | (int)OPB->_float); + break; + case OP_BITAND_FI: + OPC->_int = ((int)OPA->_float & OPB->_int); + break; + case OP_BITOR_FI: + OPC->_int = ((int)OPA->_float | OPB->_int); + break; + case OP_MUL_IF: + OPC->_float = (OPA->_int * OPB->_float); + break; + case OP_MUL_FI: + OPC->_float = (OPA->_float * OPB->_int); + break; + case OP_MUL_VI: + OPC->vector[0] = OPA->vector[0] * OPB->_int; + OPC->vector[1] = OPA->vector[0] * OPB->_int; + OPC->vector[2] = OPA->vector[0] * OPB->_int; + break; + case OP_MUL_IV: + OPC->vector[0] = OPB->_int * OPA->vector[0]; + OPC->vector[1] = OPB->_int * OPA->vector[1]; + OPC->vector[2] = OPB->_int * OPA->vector[2]; + break; + case OP_DIV_IF: + OPC->_float = (OPA->_int / OPB->_float); + break; + case OP_DIV_FI: + OPC->_float = (OPA->_float / OPB->_int); + break; + case OP_AND_I: + OPC->_int = (OPA->_int && OPB->_int); + break; + case OP_OR_I: + OPC->_int = (OPA->_int || OPB->_int); + break; + case OP_AND_IF: + OPC->_int = (OPA->_int && OPB->_float); + break; + case OP_OR_IF: + OPC->_int = (OPA->_int || OPB->_float); + break; + case OP_AND_FI: + OPC->_int = (OPA->_float && OPB->_int); + break; + case OP_OR_FI: + OPC->_int = (OPA->_float || OPB->_int); + break; + case OP_NOT_I: + OPC->_int = !OPA->_int; + break; + case OP_NE_IF: + OPC->_int = (OPA->_int != OPB->_float); + break; + case OP_NE_FI: + OPC->_int = (OPA->_float != OPB->_int); + break; + case OP_GSTOREP_I: + case OP_GSTOREP_F: + case OP_GSTOREP_ENT: + case OP_GSTOREP_FLD: // integers + case OP_GSTOREP_S: + case OP_GSTOREP_FNC: // pointers + if (prvm_boundscheck->value && (OPB->_int < 0 || OPB->_int >= (uint)vm.prog->progs->numglobaldefs)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs attempted to write to an invalid indexed global", PRVM_NAME); + return; + } + vm.prog->globals.gp[OPB->_int] = OPA->_float; + break; + case OP_GSTOREP_V: + if (prvm_boundscheck->value && (OPB->_int < 0 || OPB->_int + 2 >= (uint)vm.prog->progs->numglobaldefs)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs attempted to write to an invalid indexed global", PRVM_NAME); + return; + } + vm.prog->globals.gp[OPB->_int+0] = OPA->vector[0]; + vm.prog->globals.gp[OPB->_int+1] = OPA->vector[1]; + vm.prog->globals.gp[OPB->_int+2] = OPA->vector[2]; + break; + case OP_GADDRESS: + i = OPA->_int + (int)OPB->_float; + if (prvm_boundscheck->value && (i < 0 || i >= (uint)vm.prog->progs->numglobaldefs)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs attempted to address an out of bounds global", PRVM_NAME); + return; + } + OPC->_float = vm.prog->globals.gp[i]; + break; + case OP_GLOAD_I: + case OP_GLOAD_F: + case OP_GLOAD_FLD: + case OP_GLOAD_ENT: + case OP_GLOAD_S: + case OP_GLOAD_FNC: + if (prvm_boundscheck->value && (OPA->_int < 0 || OPA->_int >= (uint)vm.prog->progs->numglobaldefs)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs attempted to read an invalid indexed global", PRVM_NAME); + return; + } + OPC->_float = vm.prog->globals.gp[OPA->_int]; + break; + case OP_GLOAD_V: + if (prvm_boundscheck->value && (OPA->_int < 0 || OPA->_int + 2 >= (uint)vm.prog->progs->numglobaldefs)) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs attempted to read an invalid indexed global", PRVM_NAME); + return; + } + OPC->vector[0] = vm.prog->globals.gp[OPA->_int+0]; + OPC->vector[1] = vm.prog->globals.gp[OPA->_int+1]; + OPC->vector[2] = vm.prog->globals.gp[OPA->_int+2]; + break; + case OP_BOUNDCHECK: + if (OPA->_int < 0 || OPA->_int >= st->b) + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR ("%s Progs boundcheck failed at line number %d, value is < 0 or >= %d", PRVM_NAME, st->b, st->c); + return; + } + break; + case OP_CSTATE: + if( vm.prog->flag & PRVM_OP_STATE ) + { + ed = PRVM_PROG_TO_EDICT(PRVM_G_INT(vm.prog->pev->ofs)); + PRVM_E_FLOAT(ed, PRVM_ED_FindField ("nextthink")->ofs) = *vm.prog->time + 0.1f; + PRVM_E_FLOAT(ed, PRVM_ED_FindField ("frame")->ofs) = OPA->_float; + *(func_t *)((float*)ed->progs.vp + PRVM_ED_FindField ("think")->ofs) = OPB->function; + } + else + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("OP_STATE not supported by %s", PRVM_NAME); + } + break; + case OP_THINKTIME: + if( vm.prog->flag & PRVM_OP_THINKTIME ) + { + ed = PRVM_PROG_TO_EDICT(PRVM_G_INT(vm.prog->pev->ofs)); + PRVM_E_FLOAT(ed, PRVM_ED_FindField ("nextthink")->ofs) = *vm.prog->time + OPB->_float; + } + else + { + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR("OP_THINKTIME not supported by %s", PRVM_NAME); + } + break; + case OP_MODULO_I: + OPC->_int = (int)(OPA->_int % OPB->_int); + case OP_MODULO_F: + OPC->_float = fmod( OPA->_float, OPB->_float ); + break; + default: + vm.prog->xfunction->profile += (st - startst); + vm.prog->xstatement = st - vm.prog->statements; + PRVM_ERROR( "Bad opcode %i in %s (called at %s:%i)\n", st->op, PRVM_NAME, file, line ); + } + } +} diff --git a/vprogs/pr_lex.c b/vprogs/pr_lex.c new file mode 100644 index 00000000..1d509593 --- /dev/null +++ b/vprogs/pr_lex.c @@ -0,0 +1,2806 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// pr_lex.c - qcclib lexemes set +//======================================================================= + +#include +//#include +#include "vprogs.h" +#include "mathlib.h" + +char *compilingfile; +int pr_source_line; +char *pr_file_p; +char *pr_line_start; // start of current source line +int pr_bracelevel; +char pr_token[8192]; +token_type_t pr_token_type; +type_t *pr_immediate_type; +eval_t pr_immediate; +char pr_immediate_string[8192]; +int pr_error_count; +int pr_warning_count; +int pr_total_error_count; +const_t *CompilerConstant; +int numCompilerConstants; +int ForcedCRC; +bool recursivefunctiontype; + +// read on until the end of the line +#define GoToEndLine() while(*pr_file_p != '\n' && *pr_file_p != '\0'){ pr_file_p++; } + +// longer symbols must be before a shorter partial match +char *pr_punctuation1[] = {"&&", "||", "<=", ">=","==", "!=", "/=", "*=", "+=", "-=", "(+)", "|=", "(-)", "&=", "++", "--", "->", "::", ";", ",", "!", "*", "/", "(", ")", "-", "+", "=", "[", "]", "{", "}", "...", "..", ".", "<<", "<", ">>", ">" , "#" , "%", "@", "&" , "|", "^", "~", ":", NULL}; +char *pr_punctuation2[] = {"&&", "||", "<=", ">=","==", "!=", "/=", "*=", "+=", "-=", "|=", "|=", "&=", "&=", "++", "--", ".", "::", ";", ",", "!", "*", "/", "(", ")", "-", "+", "=", "[", "]", "{", "}", "...", "..", ".", "<<", "<", ">>", ">" , "#" , "%", "@", "&" , "|", "^", "~", ":", NULL}; + +optimisations_t pr_optimisations[] = +{ + // level debug = include debug info + {&opt_writesources, "ws", "write source", FL_DBG, FLAG_V7_ONLY }, + {&opt_writelinenums, "wl", "write linenums", FL_DBG, 0 }, + {&opt_writetypes, "wt", "write types", FL_DBG, FLAG_V7_ONLY }, + {&opt_laxcasts, "lx", "allow laxcasting", FL_DBG, 0 }, + + // level 0 = fixed some qcc errors + {&opt_nonvec_parms, "nv", "fix nonvec parms", FL_OP0, FLAG_DEFAULT }, + + // level 1 = size optimizations + {&opt_shortenifnots, "f", "shorten if(!a)", FL_OP1, FLAG_DEFAULT }, + {&opt_assignments, "e", "assigments", FL_OP1, FLAG_DEFAULT }, + {&opt_dupconstdefs, "j", "no dup constants", FL_OP1, FLAG_DEFAULT }, + {&opt_noduplicatestrings, "k", "no dup strings", FL_OP1, 0, }, + {&opt_locals, "l", "strip local names", FL_OP1, 0 }, + {&opt_function_names, "m", "strip func names", FL_OP1, 0 }, + {&opt_filenames, "n", "strip file names", FL_OP1, 0 }, + {&opt_unreferenced, "o", "strip unreferenced", FL_OP1, FLAG_DEFAULT }, + {&opt_overlaptemps, "p", "optimize overlaptemps", FL_OP1, FLAG_DEFAULT }, + {&opt_constantarithmatic, "q", "precompute constnts", FL_OP1, FLAG_DEFAULT }, + {&opt_compstrings, "x", "deflate prog strings", FL_OP1, FLAG_V7_ONLY }, + + // level 2 = speed optimizations + {&opt_constant_names, "h", "strip const names", FL_OP2, 0 }, + {&opt_precache_file, "r", "strip precache files", FL_OP2, 0 }, + {&opt_compfunctions, "y", "deflate prog funcs", FL_OP2, FLAG_V7_ONLY }, + + // level 3 = dodgy optimizations + {&opt_return_only, "s", "optimize return calls", FL_OP3, 0 }, + {&opt_compound_jumps, "t", "optimize num of jumps", FL_OP3, 0 }, + {&opt_stripfunctions, "u", "strip functions", FL_OP3, 0 }, + {&opt_constant_names_strings, "i", "strip const strings", FL_OP3, 0 }, + {&opt_compress_other, "z", "deflate all prog", FL_OP3, FLAG_V7_ONLY }, + {&opt_logicops, "a", "optimize logic ops", FL_OP3, 0 }, + {&opt_ifstring, "sf","if(string) fix", FL_OP3, FLAG_V7_ONLY }, + + // level 4 = use with caution, may be bugly + {&opt_locals_marshalling, "y", "reduce locals, buggly", FL_OP4, FLAG_V7_ONLY }, + {&opt_vectorcalls, "w", "optimize vector calls", FL_OP4, FLAG_V7_ONLY }, + {NULL, "", "", 0, 0 }, +}; + +// simple types. function types are dynamically allocated +type_t *type_void; +type_t *type_string; +type_t *type_float; +type_t *type_vector; +type_t *type_entity; +type_t *type_field; +type_t *type_function; +type_t *type_pointer; +type_t *type_integer; +type_t *type_variant; +type_t *type_floatfield; + +const int type_size[12] = +{ + 1, // void + sizeof(string_t)/4, // string + 1, // float + 3, // vector + 1, // entity + 1, // field + sizeof(func_t)/4, // function + sizeof(void *)/4, // pointer + 1, // integer + 1, // FIXME: how big should a variant be? + 0, // ev_struct. variable sized. + 0 // ev_union. variable sized. +}; + +char pr_parm_names[MAX_PARMS + MAX_PARMS_EXTRA][MAX_NAME]; +def_t def_ret, def_parms[MAX_PARMS]; +includechunk_t *currentchunk; + +/* +================= +PR_SkipWhiteSpace +================= +*/ +const char *PR_SkipWhiteSpace( const char *data_p, bool *newline ) +{ + int c; + + while((c = *data_p) <= ' ') + { + if( !c ) return NULL; + if( c == '\n' ) + *newline = true; + data_p++; + } + return data_p; +} + +/* +============== +PR_ParseToken + +Parse a token out of a string +============== +*/ +char *PR_ParseToken( const char **data_p, bool allow_newline ) +{ + int c; + int len = 0; + const char *data; + bool newline = false; + + pr_token[0] = 0; + data = *data_p; + + if( !data ) + { + *data_p = NULL; + return pr_token; + } + + while( 1 ) + { + data = PR_SkipWhiteSpace( data, &newline ); + if( !data ) + { + *data_p = NULL; + return pr_token; + } + if( newline && !allow_newline ) + { + *data_p = data; + return pr_token; + } + + c = *data; + + if( c=='/' && data[1] == '/' ) + { + // skip // comments + while( *data && *data != '\n' ) + data++; + } + else if( c=='/' && data[1] == '*' ) + { + // skip /* comments + while( data[1] && (data[0] != '*' || data[1] != '/')) + data++; + if( *data ) data += 2; + } + else break; // an actual token + } + + // handle quoted strings specially + if (*data == '\"' || *data == '\'') + { + data++; + while( 1 ) + { + c = *data++; + if( c=='\"' || c=='\0' ) + { + pr_token[len] = 0; + *data_p = data; + return pr_token; + } + pr_token[len++] = c; + } + } + + // parse single characters + if( c == '{' || c == '}'|| c == ')' || c == '(' || c == '\'' || c == ':' || c == ',' ) + { + pr_token[len] = c; + data++; + len++; + pr_token[len] = 0; + *data_p = data; + return pr_token; + } + + // parse a regular word + do + { + pr_token[len] = c; + data++; + len++; + c = *data; + if( c == '{' || c == '}'|| c == ')'|| c == '(' || c == '\'' || c == ':' || c == ',' ) + break; + } while( c > 32 ); + + pr_token[len] = 0; + *data_p = data; + return pr_token; +} + +/* +============== +PR_ParseWord + +Parse a word out of a string +============== +*/ +char *PR_ParseWord( const char **data_p, bool allow_newline ) +{ + int c; + const char *data; + int len = 0; + bool newline = false; + + pr_token[0] = 0; + data = *data_p; + + if( !data ) + { + *data_p = NULL; + return NULL; + } + + while( 1 ) + { + data = PR_SkipWhiteSpace( data, &newline ); + if( !data ) + { + *data_p = NULL; + return NULL; + } + if( newline && !allow_newline ) + { + *data_p = data; + return pr_token; + } + + c = *data; + + if( c=='/' && data[1] == '/' ) + { + // skip // comments + while( *data && *data != '\n' ) + data++; + } + else if( c=='/' && data[1] == '*' ) + { + // skip /* comments + while( data[1] && (data[0] != '*' || data[1] != '/')) + data++; + if( *data ) data += 2; + } + else break; // an actual token + } + + // handle quoted strings specially + if( c == '\"' ) + { + data++; + do + { + c = *data++; + if( c=='\"' || c=='\0' ) + { + pr_token[len] = 0; + *data_p = data; + return pr_token; + } + pr_token[len] = c; + len++; + } while( 1 ); + } + + // parse numbers + if( c >= '0' && c <= '9' ) + { + if( c == '0' && data[1] == 'x' ) + { + // parse hex + pr_token[0] = '0'; + c = 'x'; + len = 1; + data++; + while( 1 ) + { + // parse regular number + pr_token[len] = c; + data++; + len++; + c = *data; + if ((c < '0'|| c > '9') && (c < 'a'||c > 'f') && (c < 'A'|| c > 'F') && c != '.') + break; + } + + } + else + { + while( 1 ) + { + // parse regular number + pr_token[len] = c; + data++; + len++; + c = *data; + if ((c < '0'|| c > '9') && c != '.') + break; + } + } + + pr_token[len] = 0; + *data_p = data; + return pr_token; + } + + // parse words + else if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_') + { + do + { + pr_token[len] = c; + data++; + len++; + c = *data; + } while ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'); + + pr_token[len] = 0; + *data_p = data; + return pr_token; + } + else + { + pr_token[len] = c; + len++; + pr_token[len] = 0; + *data_p = data; + return pr_token; + } +} + +void PR_IncludeChunkEx( char *data, bool duplicate, char *filename, const_t *cnst ) +{ + includechunk_t *chunk = Qalloc( sizeof(includechunk_t)); + chunk->prev = currentchunk; + currentchunk = chunk; + + chunk->currentdatapoint = pr_file_p; + chunk->currentlinenumber = pr_source_line; + chunk->cnst = cnst; + if( cnst ) cnst->inside++; + + if (duplicate) + { + pr_file_p = Qalloc(com.strlen( data ) + 1); + com.strcpy( pr_file_p, data ); + } + else pr_file_p = data; +} + +void PR_IncludeChunk( char *data, bool duplicate, char *filename ) +{ + PR_IncludeChunkEx( data, duplicate, filename, NULL ); +} + +bool PR_UnInclude(void) +{ + if (!currentchunk) return false; + + if( currentchunk->cnst ) currentchunk->cnst->inside--; + pr_file_p = currentchunk->currentdatapoint; + pr_source_line = currentchunk->currentlinenumber; + + currentchunk = currentchunk->prev; + return true; +} + +type_t *PR_NewType( char *name, int basictype ) +{ + if( numtypeinfos >= maxtypeinfos ) PR_ParseError(ERR_INTERNAL, "Too many types"); + Mem_Set(&qcc_typeinfo[numtypeinfos], 0, sizeof(type_t)); + qcc_typeinfo[numtypeinfos].type = basictype; + qcc_typeinfo[numtypeinfos].name = name; + qcc_typeinfo[numtypeinfos].num_parms = 0; + qcc_typeinfo[numtypeinfos].param = NULL; + qcc_typeinfo[numtypeinfos].size = type_size[basictype]; + + numtypeinfos++; + return &qcc_typeinfo[numtypeinfos-1]; +} + +void PR_FindBestInclude( char *newfile, char *currentfile, char *rootpath ) +{ + char fullname[MAX_SYSPATH]; + char *stripfrom; + char *end = fullname; + + if( !*newfile ) return; + + // allow to include ../pathes/ + currentfile += com.strlen( rootpath ); // could this be bad? + + for( stripfrom = currentfile + com.strlen(currentfile) - 1; stripfrom > currentfile; stripfrom-- ) + { + if( *stripfrom == '/' || *stripfrom == '\\' ) + { + stripfrom++; + break; + } + } + + com.strcpy( end, rootpath ); + end = end + com.strlen(end); + + if( *fullname && end[-1] != '/' ) + { + com.strcpy( end, "/" ); + end = end + com.strlen(end); + } + + com.strncpy( end, currentfile, stripfrom - currentfile ); + end += stripfrom - currentfile; *end = '\0'; + + // FIXME: clean code + com.strcpy( fullname, newfile ); + + PR_Include( fullname ); +} + +/* +============== +PR_Precompiler + +Runs precompiler stage +============== +*/ +bool PR_Precompiler(void) +{ + char buf[1024]; + char *msg = buf; + int ifmode; + int a; + static int ifs = 0; + int level; //#if level + bool eval = false; + + if (*pr_file_p == '#') + { + char *directive; + for (directive = pr_file_p + 1; *directive; directive++) // so # define works + { + if (*directive == '\r' || *directive == '\n') + PR_ParseError(ERR_UNKNOWNPUCTUATION, "Hanging # with no directive\n"); + if (*directive > ' ') break; + } + if (!com.strncmp(directive, "define", 6)) + { + pr_file_p = directive; + PR_ConditionCompilation(); + GoToEndLine(); + } + else if (!com.strncmp(directive, "undef", 5)) + { + pr_file_p = directive+5; + while(*pr_file_p <= ' ') pr_file_p++; + + PR_SimpleGetToken (); + PR_UndefineName(pr_token); + GoToEndLine(); + } + else if (!com.strncmp(directive, "if", 2)) + { + int originalline = pr_source_line; + pr_file_p = directive + 2; + if (!com.strncmp(pr_file_p, "def ", 4)) + { + ifmode = 0; + pr_file_p+=4; + } + else if (!com.strncmp(pr_file_p, "ndef ", 5)) + { + ifmode = 1; + pr_file_p+=5; + } + else + { + ifmode = 2; + pr_file_p+=0; + } + + PR_SimpleGetToken (); + level = 1; + + GoToEndLine(); + + if (ifmode == 2) + { + if (com.atof(pr_token)) eval = true; + } + else + { + if (PR_CheckCompConstDefined(pr_token)) + eval = true; + + if (ifmode == 1) eval = eval ? false : true; //same as eval = !eval + } + + if (eval) ifs++; + else + { + while (1) + { + while(*pr_file_p && (*pr_file_p==' ' || *pr_file_p == '\t')) + pr_file_p++; + + if (!*pr_file_p) + { + pr_source_line = originalline; + PR_ParseError (ERR_NOENDIF, "#if with no endif"); + } + + if (*pr_file_p == '#') + { + pr_file_p++; + while(*pr_file_p==' ' || *pr_file_p == '\t') + pr_file_p++; + if (!com.strncmp(pr_file_p, "endif", 5)) + level--; + if (!com.strncmp(pr_file_p, "if", 2)) + level++; + if (!com.strncmp(pr_file_p, "else", 4) && level == 1) + { + ifs++; + GoToEndLine(); + } + } + + GoToEndLine(); + if (level <= 0) break; + pr_file_p++; // next line + pr_source_line++; + } + } + } + else if (!com.strncmp(directive, "else", 4)) + { + int originalline = pr_source_line; + + ifs -= 1; + level = 1; + + GoToEndLine(); + while (1) + { + while(*pr_file_p && (*pr_file_p==' ' || *pr_file_p == '\t')) + pr_file_p++; + + if (!*pr_file_p) + { + pr_source_line = originalline; + PR_ParseError(ERR_NOENDIF, "#if with no endif"); + } + + if (*pr_file_p == '#') + { + pr_file_p++; + while(*pr_file_p==' ' || *pr_file_p == '\t') + pr_file_p++; + + if (!com.strncmp(pr_file_p, "endif", 5)) + level--; + if (!com.strncmp(pr_file_p, "if", 2)) + level++; + if (!com.strncmp(pr_file_p, "else", 4) && level == 1) + { + ifs+=1; + break; + } + } + + GoToEndLine(); + if (level <= 0) break; + pr_file_p++; // go off the end + pr_source_line++; + } + } + else if (!com.strncmp(directive, "endif", 5)) + { + GoToEndLine(); + if (ifs <= 0) PR_ParseError(ERR_NOPRECOMPILERIF, "unmatched #endif"); + else ifs -= 1; + } + else if (!com.strncmp(directive, "eof", 3)) + { + pr_file_p = NULL; + return true; + } + else if (!com.strncmp(directive, "error", 5)) + { + pr_file_p = directive+5; + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + + GoToEndLine(); + PR_ParseError(ERR_PRECOMPILERMESSAGE, "#error: %s", msg); + } + else if (!com.strncmp(directive, "warning", 7)) + { + pr_file_p = directive+7; + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + + GoToEndLine(); + PR_ParseWarning(WARN_PRECOMPILERMESSAGE, "#warning: %s", msg); + } + else if (!com.strncmp(directive, "message", 7)) + { + pr_file_p = directive+7; + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + + GoToEndLine(); + PR_Message("#message: %s\n", msg); + } + else if (!com.strncmp(directive, "copyright", 9)) + { + pr_file_p = directive+9; + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + + GoToEndLine(); + if (com.strlen(msg) >= sizeof(v_copyright)) + PR_ParseWarning(WARN_STRINGTOOLONG, "Copyright message is too long\n"); + com.strncpy(v_copyright, msg, sizeof(v_copyright)-1); + } + else if (!com.strncmp(directive, "forcecrc", 8)) + { + pr_file_p=directive+8; + + ForcedCRC = PR_LexInteger(); + + pr_file_p++; + + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + GoToEndLine(); + } + else if (!com.strncmp(directive, "includelist", 11)) + { + pr_file_p=directive+11; + + while(*pr_file_p <= ' ') pr_file_p++; + + while(1) + { + PR_LexWhitespace(); + if (!PR_SimpleGetToken()) + { + if (!*pr_file_p) + PR_ParseError(ERR_INTERNAL, "eof in includelist"); + else + { + pr_file_p++; + pr_source_line++; + } + continue; + } + if (!com.strcmp(pr_token, "#endlist")) + break; + + PR_FindBestInclude(pr_token, compilingfile, sourcedir); + + if (*pr_file_p == '\r') + pr_file_p++; + + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + + GoToEndLine(); + } + GoToEndLine(); + } + else if (!com.strncmp(directive, "include", 7)) + { + char sm; + + pr_file_p=directive+7; + + while(*pr_file_p <= ' ') pr_file_p++; + + msg[0] = '\0'; + if (*pr_file_p == '\"') sm = '\"'; + else if (*pr_file_p == '<') sm = '>'; + else + { + PR_ParseError(0, "Not a string literal (on a #include)"); + sm = 0; + } + + pr_file_p++; + a = 0; + + while(*pr_file_p != sm) + { + if (*pr_file_p == '\n') + { + PR_ParseError(0, "#include continued over line boundy\n"); + break; + } + msg[a++] = *pr_file_p; + pr_file_p++; + } + msg[a] = 0; + + PR_FindBestInclude(msg, compilingfile, sourcedir); + pr_file_p++; + + while(*pr_file_p != '\n' && *pr_file_p != '\0' && *pr_file_p <= ' ') + pr_file_p++; + GoToEndLine(); + } + else if (!com.strncmp(directive, "library", 8)) + { + pr_file_p=directive+8; + + while(*pr_file_p <= ' ') pr_file_p++; + + PR_LexString(); + PR_Message("Including library: %s\n", pr_token); + QCC_LoadData(pr_token); + + pr_file_p++; + + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + GoToEndLine(); + } + else if (!com.strncmp(directive, "output", 6)) + { + pr_file_p = directive + 6; + + while(*pr_file_p <= ' ') pr_file_p++; + + PR_LexString(); + com.strcpy(progsoutname, pr_token); + PR_Message("Outputfile: %s\n", progsoutname); + + pr_file_p++; + + for (a = 0; a < 1023 && pr_file_p[a] != '\n' && pr_file_p[a] != '\0'; a++) + msg[a] = pr_file_p[a]; + + msg[a-1] = '\0'; + GoToEndLine(); + } + else if (!com.strncmp(directive, "pragma", 6)) + { + pr_file_p = directive+6; + while(*pr_file_p <= ' ') pr_file_p++; + + pr_token[0] = '\0'; + for(a = 0; *pr_file_p != '\n' && *pr_file_p != '\0'; pr_file_p++) // read on until the end of the line + { + if ((*pr_file_p == ' ' || *pr_file_p == '\t'|| *pr_file_p == '(') && !*pr_token) + { + msg[a] = '\0'; + com.strcpy(pr_token, msg); + a=0; + continue; + } + msg[a++] = *pr_file_p; + } + + msg[a] = '\0'; + { + char *end; + for (end = msg + a-1; end>=msg && *end <= ' '; end--) + *end = '\0'; + } + + if (!*pr_token) + { + com.strcpy(pr_token, msg); + msg[0] = '\0'; + } + + { + char *end; + for (end = msg + a-1; end>=msg && *end <= ' '; end--) + *end = '\0'; + } + + if (!com.stricmp( pr_token, "DONT_COMPILE_THIS_FILE" )) + { + while (*pr_file_p) + { + GoToEndLine(); + if (*pr_file_p == '\n') + { + PR_NewLine(false); + pr_file_p++; + } + } + } + else if (!com.stricmp( pr_token, "COPYRIGHT" )) + { + if (com.strlen(msg) >= sizeof(v_copyright)) + PR_ParseWarning(WARN_STRINGTOOLONG, "Copyright message is too long\n"); + com.strncpy(v_copyright, msg, sizeof(v_copyright)-1); + } + else if (!com.strncmp(directive, "forcecrc", 8)) + { + ForcedCRC = com.atoi(msg); + } + else if (!com.stricmp( pr_token, "warning" )) + { + int st; + + PR_ParseToken( &msg, true ); + if (!com.stricmp(pr_token, "enable") || !com.stricmp(pr_token, "on")) st = 0; + else if (!com.stricmp(pr_token, "disable") || !com.stricmp(pr_token, "off")) st = 1; + else if (!com.stricmp(pr_token, "toggle")) st = 2; + else + { + PR_ParseWarning(WARN_BADPRAGMA, "warning state not recognized"); + st = -1; + } + if (st >= 0) + { + int wn; + PR_ParseToken( &msg, true ); // just a number of warning + wn = com.atoi( pr_token ); + if( wn < 0 || wn > WARN_CONSTANTCOMPARISON ) + { + PR_ParseWarning(WARN_BADPRAGMA, "warning id not recognized"); + } + else + { + if (st == 2) pr_warning[wn] = true - pr_warning[wn]; + else pr_warning[wn] = st; + } + } + } + else PR_ParseWarning(WARN_BADPRAGMA, "Unknown pragma \'%s\'", pr_token); + } + return true; + } + return false; +} + +/* +============== +PR_NewLine + +Call at start of file and when *pr_file_p == '\n' +============== +*/ +void PR_NewLine (bool incomment) +{ + bool m; + + if (*pr_file_p == '\n') + { + pr_file_p++; + m = true; + } + else m = false; + + pr_source_line++; + pr_line_start = pr_file_p; + while(*pr_file_p==' ' || *pr_file_p == '\t') pr_file_p++; + + if (incomment); // no constants if in a comment. + else if (PR_Precompiler()); + if (m) pr_file_p--; +} + +/* +============== +PR_LexString + +Parses a quoted string +============== +*/ +void PR_LexString (void) +{ + int c; + int len = 0; + char *end, *cnst; + int texttype = 0; + + pr_file_p++; + + do + { + c = *pr_file_p++; + if (!c) PR_ParseError (ERR_EOF, "EOF inside quote"); + if (c=='\n') PR_ParseError (ERR_INVALIDSTRINGIMMEDIATE, "newline inside quote"); + if (c=='\\') + { + // escape char + c = *pr_file_p++; + if (!c) PR_ParseError (ERR_EOF, "EOF inside quote"); + if (c == 'n') c = '\n'; + else if (c == 'r') c = '\r'; + else if (c == '"') c = '"'; + else if (c == 't') c = '\t'; + else if (c == 'a') c = '\a'; + else if (c == 'v') c = '\v'; + else if (c == 'f') c = '\f'; + else if (c == 's' || c == 'b') + { + texttype ^= 128; + continue; + } + else if (c == '[') c = 16; + else if (c == ']') c = 17; + else if (c == '{') + { + int d; + c = 0; + while ((d = *pr_file_p++) != '}') + { + c = c * 10 + d - '0'; + if (d < '0' || d > '9' || c > 255) + PR_ParseError(ERR_BADCHARACTURECODE, "Bad character code"); + } + } + else if (c == '<') c = 29; + else if (c == '-') c = 30; + else if (c == '>') c = 31; + else if (c == '\\') c = '\\'; + else if (c == '\'') c = '\''; + else if (c >= '0' && c <= '9') c = 18 + c - '0'; + else if (c == '\r') + { + c = *pr_file_p++;// sigh + if (c != '\n') PR_ParseWarning(WARN_HANGINGSLASHR, "Hanging \\\\\r"); + pr_source_line++; + } + else if (c == '\n') + { + pr_source_line++;// sigh + } + else PR_ParseError (ERR_INVALIDSTRINGIMMEDIATE, "Unknown escape char %c", c); + } + else if (c=='\"') + { + if (len >= sizeof(pr_immediate_string) - 1) + PR_ParseError(ERR_INTERNAL, "String length exceeds %i", sizeof(pr_immediate_string)-1); + + while(*pr_file_p && *pr_file_p <= ' ') + { + if (*pr_file_p == '\n') + PR_NewLine(false); + pr_file_p++; + } + if (*pr_file_p == '\"') + { + // have annother go + pr_file_p++; + continue; + } + pr_token[len] = 0; + pr_token_type = tt_immediate; + pr_immediate_type = type_string; + com.strcpy (pr_immediate_string, pr_token); + return; + } + else if (c == '#') + { + for (end = pr_file_p;; end++) + { + if (*end <= ' ') break; + if (*end == ')') break; + if (*end == '(') break; + if (*end == '+') break; + if (*end == '-') break; + if (*end == '*') break; + if (*end == '/') break; + if (*end == '%') break; + if (*end =='\\') break; + if (*end == '|') break; + if (*end == '&') break; + if (*end == '=') break; + if (*end == '^') break; + if (*end == '[') break; + if (*end == ']') break; + if (*end =='\"') break; + if (*end == '{') break; + if (*end == '}') break; + if (*end == ';') break; + if (*end == ':') break; + if (*end == ',') break; + if (*end == '.') break; + if (*end == '#') break; + } + + c = *end; + *end = '\0'; + cnst = PR_CheakCompConstString(pr_file_p); + if (cnst == pr_file_p) cnst = NULL; + *end = c; + c = '#'; // undo + if (cnst) + { + PR_ParseWarning(WARN_MACROINSTRING, "Macro expansion in string"); + if (len+com.strlen(cnst) >= sizeof(pr_token)-1) + PR_ParseError(ERR_INTERNAL, "String length exceeds %i", sizeof(pr_token)-1); + com.strcpy(pr_token+len, cnst); + len+=com.strlen(cnst); + pr_file_p = end; + continue; + } + } + else c |= texttype; + + pr_token[len] = c; + len++; + if (len >= sizeof(pr_token)-1) + PR_ParseError(ERR_INTERNAL, "String length exceeds %i", sizeof(pr_token)-1); + } while (1); +} + +/* +============== +PR_LexNumber +============== +*/ +int PR_LexInteger (void) +{ + int c = *pr_file_p; + int len = 0; + + if (pr_file_p[0] == '0' && pr_file_p[1] == 'x') + { + pr_token[0] = '0'; + pr_token[1] = 'x'; + len = 2; + c = *(pr_file_p+=2); + } + do + { + pr_token[len] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } while ((c >= '0' && c<= '9') || c == '.' || (c>='a' && c <= 'f')); + + pr_token[len] = 0; + return com.atoi (pr_token); +} + +void PR_LexNumber (void) +{ + int num = 0; + int base = 10; + int c; + int sign = 1; + + if (*pr_file_p == '-') + { + sign = -1; + pr_file_p++; + } + if (pr_file_p[1] == 'x') + { + pr_file_p += 2; + base = 16; + } + + while((c = *pr_file_p)) + { + if (c >= '0' && c <= '9') + { + num*=base; + num += c-'0'; + } + else if (c >= 'a' && c <= 'f') + { + num*=base; + num += c -'a'+10; + } + else if (c >= 'A' && c <= 'F') + { + num*=base; + num += c -'A'+10; + } + else if (c == '.') + { + pr_file_p++; + pr_immediate_type = type_float; + pr_immediate._float = (float)num; + num = 1; + while(1) + { + c = *pr_file_p; + if (c >= '0' && c <= '9') + { + num*=base; + pr_immediate._float += (c-'0')/(float)(num); + } + else + { + break; + } + pr_file_p++; + } + pr_immediate._float *= sign; + return; + } + else if (c == 'i') + { + pr_file_p++; + pr_immediate_type = type_integer; + pr_immediate._int = num*sign; + return; + } + else break; + pr_file_p++; + } + + pr_immediate_type = type_float; + pr_immediate._float = (float)(num*sign); +} + +float PR_LexFloat( void ) +{ + int c; + int len; + + len = 0; + c = *pr_file_p; + do + { + pr_token[len] = c; + len++; + pr_file_p++; + c = *pr_file_p; + // only allow a . if the next isn't too... + } while((c >= '0' && c <= '9') || (c == '.'&&pr_file_p[1]!='.') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + + pr_token[len] = 0; + return (float)com.atof (pr_token); +} + +/* +============== +PR_LexVector + +Parses a single quoted vector +============== +*/ +void PR_LexVector (void) +{ + int i; + + pr_file_p++; + + if (*pr_file_p == '\\') + { + // extended characture constant + pr_token_type = tt_immediate; + pr_immediate_type = type_float; + pr_file_p++; + switch(*pr_file_p) + { + case 'n': + pr_immediate._float = '\n'; + break; + case 'r': + pr_immediate._float = '\r'; + break; + case 't': + pr_immediate._float = '\t'; + break; + case '\'': + pr_immediate._float = '\''; + break; + case '\"': + pr_immediate._float = '\"'; + break; + case '\\': + pr_immediate._float = '\\'; + break; + default: + PR_ParseError (ERR_INVALIDVECTORIMMEDIATE, "Bad characture constant"); + break; + } + if (*pr_file_p != '\'') + PR_ParseError (ERR_INVALIDVECTORIMMEDIATE, "Bad characture constant"); + pr_file_p++; + return; + } + if (pr_file_p[1] == '\'') + { + // character constant + pr_token_type = tt_immediate; + pr_immediate_type = type_float; + pr_immediate._float = pr_file_p[0]; + pr_file_p+=2; + return; + } + + pr_token_type = tt_immediate; + pr_immediate_type = type_vector; + PR_LexWhitespace (); + + for (i = 0; i < 3; i++) + { + pr_immediate.vector[i] = PR_LexFloat (); + PR_LexWhitespace (); + } + + if (*pr_file_p != '\'') + PR_ParseError (ERR_INVALIDVECTORIMMEDIATE, "Bad vector"); + pr_file_p++; +} + +/* +============== +PR_LexName + +Parses an identifier +============== +*/ +void PR_LexName (void) +{ + int c; + int len; + + len = 0; + c = *pr_file_p; + do + { + pr_token[len] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } while ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || (c >= '0' && c <= '9')); + + pr_token[len] = 0; + pr_token_type = tt_name; +} + +/* +============== +PR_LexPunctuation +============== +*/ +void PR_LexPunctuation( void ) +{ + int i, len; + char *p; + + pr_token_type = tt_punct; + + for( i = 0; (p = pr_punctuation1[i]) != NULL; i++ ) + { + len = com.strlen( p ); + if(!com.strncmp( p, pr_file_p, len )) + { + com.strcpy( pr_token, pr_punctuation2[i] ); + if( p[0] == '{' ) pr_bracelevel++; + else if( p[0] == '}' ) pr_bracelevel--; + pr_file_p += len; + return; + } + } + PR_ParseError( ERR_UNKNOWNPUCTUATION, "Unknown punctuation" ); +} + +/* +============== +PR_LexWhitespace +============== +*/ +void PR_LexWhitespace (void) +{ + int c; + + while (1) + { + // skip whitespace + while ( (c = *pr_file_p) <= ' ') + { + if (c=='\n') + { + PR_NewLine (false); + if (!pr_file_p) return; + } + if (c == 0) return; // end of file + pr_file_p++; + } + + // skip // comments + if (c=='/' && pr_file_p[1] == '/') + { + while (*pr_file_p && *pr_file_p != '\n') + pr_file_p++; + PR_NewLine(false); + pr_file_p++; + continue; + } + + // skip /* */ comments + if (c=='/' && pr_file_p[1] == '*') + { + do + { + pr_file_p++; + if (pr_file_p[0]=='\n') + PR_NewLine(true); + if (pr_file_p[1] == 0) + { + pr_file_p++; + return; + } + } while (pr_file_p[-1] != '*' || pr_file_p[0] != '/'); + pr_file_p++; + continue; + } + break; // a real character has been found + } +} + +//============================================================================ + +#define MAX_FRAMES 8192 +char pr_framemodelname[64]; +char pr_framemacros[MAX_FRAMES][16]; +int pr_framemacrovalue[MAX_FRAMES]; +int pr_nummacros, pr_oldmacros; +int pr_macrovalue; +int pr_savedmacro; + +void PR_ClearGrabMacros (void) +{ + pr_oldmacros = pr_nummacros; + pr_macrovalue = 0; + pr_savedmacro = -1; +} + +int PR_FindMacro (char *name) +{ + int i; + + for (i = pr_nummacros - 1; i >= 0; i--) + { + if (!STRCMP (name, pr_framemacros[i])) + { + return pr_framemacrovalue[i]; + } + } + for (i = pr_nummacros - 1; i >= 0; i--) + { + if (!com.stricmp (name, pr_framemacros[i])) + { + PR_ParseWarning(WARN_CASEINSENSATIVEFRAMEMACRO, "Case insensative frame macro"); + return pr_framemacrovalue[i]; + } + } + return -1; +} + +void PR_ExpandMacro(void) +{ + int i = PR_FindMacro(pr_token); + + if (i < 0) PR_ParseError (ERR_BADFRAMEMACRO, "Unknown frame macro $%s", pr_token); + + com.sprintf (pr_token,"%d", i); + pr_token_type = tt_immediate; + pr_immediate_type = type_float; + pr_immediate._float = (float)i; +} + +bool PR_SimpleGetToken (void) +{ + int c; + int i = 0; + + // skip whitespace + while ( (c = *pr_file_p) <= ' ') + { + if (c=='\n' || c == 0) + return false; + pr_file_p++; + } + if (pr_file_p[0] == '/') + { + if (pr_file_p[1] == '/') + { //comment alert + while(*pr_file_p && *pr_file_p != '\n') + pr_file_p++; + return false; + } + if (pr_file_p[1] == '*') + return false; + } + + while ( (c = *pr_file_p) > ' ' && c != ',' && c != ';' && c != ')' && c != '(' && c != ']') + { + pr_token[i] = c; + i++; + pr_file_p++; + } + pr_token[i] = 0; + return i!=0; +} + +void PR_MacroFrame(char *name, int value) +{ + int i; + for (i = pr_nummacros - 1; i >= 0; i--) + { + if (!STRCMP (name, pr_framemacros[i])) + { + pr_framemacrovalue[i] = value; + if (i >= pr_oldmacros) + PR_ParseWarning(WARN_DUPLICATEMACRO, "Duplicate macro defined (%s)", pr_token); + return; + } + } + + if( com.strlen(name)+1 > sizeof(pr_framemacros[0])) + PR_ParseWarning(ERR_TOOMANYFRAMEMACROS, "Name for frame macro %s is too long", name); + else + { + com.strcpy( pr_framemacros[pr_nummacros], name ); + pr_framemacrovalue[pr_nummacros] = value; + pr_nummacros++; + if(pr_nummacros >= MAX_FRAMES) PR_ParseError(ERR_TOOMANYFRAMEMACROS, "Too many frame macros defined"); + } +} + +void PR_ParseFrame (void) +{ + while (PR_SimpleGetToken ()) + { + PR_MacroFrame(pr_token, pr_macrovalue++); + } +} + +/* +============== +PR_LexGrab + +Deals with counting sequence numbers and replacing frame macros +============== +*/ +void PR_LexGrab (void) +{ + pr_file_p++; // skip the $ + + if (*pr_file_p <= ' ') PR_ParseError (ERR_BADFRAMEMACRO, "hanging $"); + PR_SimpleGetToken(); + if (!*pr_token) PR_ParseError (ERR_BADFRAMEMACRO, "hanging $"); + + // check for $frame + if (!STRCMP (pr_token, "frame") || !STRCMP (pr_token, "framesave")) + { + PR_ParseFrame(); + PR_Lex(); + } + + // ignore other known $commands - just for model/spritegen + else if (!STRCMP (pr_token, "cd")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "load")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "type")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "load")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "spritename")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "framegroupstart")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "framegroupend")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "origin")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "base")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "flags")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "scale")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "skin")) + { + // skip to end of line + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "flush")) + { + PR_ClearGrabMacros(); + while (PR_SimpleGetToken ()); + PR_Lex (); + } + else if (!STRCMP (pr_token, "framevalue")) + { + PR_SimpleGetToken (); + pr_macrovalue = com.atoi(pr_token); + PR_Lex (); + } + else if (!STRCMP (pr_token, "framerestore")) + { + PR_SimpleGetToken (); + PR_ExpandMacro(); + pr_macrovalue = (int)pr_immediate._float; + PR_Lex (); + } + else if (!STRCMP (pr_token, "modelname")) + { + int i; + PR_SimpleGetToken (); + + if (*pr_framemodelname) + PR_MacroFrame(pr_framemodelname, pr_macrovalue); + + com.strncpy(pr_framemodelname, pr_token, sizeof(pr_framemodelname)-1); + pr_framemodelname[sizeof(pr_framemodelname)-1] = '\0'; + + i = PR_FindMacro(pr_framemodelname); + if (i) pr_macrovalue = i; + else i = 0; + PR_Lex (); + } + else PR_ExpandMacro (); // look for a frame name macro +} + +bool PR_UndefineName(char *name) +{ + const_t *c = Hash_Get(&compconstantstable, name); + + if(!c) + { + PR_ParseWarning(WARN_UNDEFNOTDEFINED, "Precompiler constant %s was not defined", name); + return false; + } + + Hash_Remove(&compconstantstable, name); + return true; +} + +const_t *PR_DefineName(char *name) +{ + int i; + const_t *cnst; + + if (com.strlen(name) >= MAX_NAME || !*name) + PR_ParseError(ERR_CONSTANTTOOLONG, "Compiler constant name length is too long or short"); + + cnst = Hash_Get(&compconstantstable, name); + if (cnst ) + { + PR_ParseWarning(WARN_DUPLICATEDEFINITION, "Duplicate definition for Precompiler constant %s", name); + Hash_Remove(&compconstantstable, name); + } + + cnst = Qalloc(sizeof(const_t)); + cnst->used = false; + cnst->numparams = 0; + com.strcpy(cnst->name, name); + cnst->namelen = com.strlen(name); + *cnst->value = '\0'; + for (i = 0; i < MAX_PARMS; i++) cnst->params[i][0] = '\0'; + + Hash_Add(&compconstantstable, cnst->name, cnst, Qalloc(sizeof(bucket_t))); + + if (!STRCMP(name, "OP_NODUP")) opt_noduplicatestrings = true; + + // group - optimize for a fast compiler + if (!STRCMP(name, "OP_TIME")) + { + PR_UndefineName("OP_SIZE"); + PR_UndefineName("OP_SPEED"); + + PR_UndefineName("OP_NODUP"); + PR_UndefineName("OP_COMP_ALL"); + } + + // group - optimize run speed + if (!STRCMP(name, "OP_SPEED")) + { + PR_UndefineName("OP_SIZE"); + PR_UndefineName("OP_TIME"); + PR_UndefineName("OP_COMP_ALL"); + } + + // group - produce small output. + if (!STRCMP(name, "OP_SIZE")) + { + PR_UndefineName("OP_SPEED"); + PR_UndefineName("OP_TIME"); + PR_DefineName("OP_NODUP"); + PR_DefineName("OP_COMP_ALL"); + } + + // group - compress the output + if (!STRCMP(name, "OP_COMP_ALL")) + { + PR_DefineName("OP_COMP_STATEMENTS"); + PR_DefineName("OP_COMP_DEFS"); + PR_DefineName("OP_COMP_FIELDS"); + PR_DefineName("OP_COMP_FUNCTIONS"); + PR_DefineName("OP_COMP_STRINGS"); + PR_DefineName("OP_COMP_GLOBALS"); + PR_DefineName("OP_COMP_LINES"); + PR_DefineName("OP_COMP_TYPES"); + } + return cnst; +} + +void PR_Undefine(void) +{ + PR_SimpleGetToken (); + PR_UndefineName(pr_token); +} + +void PR_ConditionCompilation( void ) +{ + char *oldval; + char *d; + char *s; + int quote=false; + const_t *cnst; + + PR_SimpleGetToken (); + + if (!PR_SimpleGetToken()) PR_ParseError(ERR_NONAME, "No name defined for compiler constant"); + + cnst = Hash_Get(&compconstantstable, pr_token); + if (cnst) + { + oldval = cnst->value; + Hash_Remove(&compconstantstable, pr_token); + } + else oldval = NULL; + + cnst = PR_DefineName(pr_token); + + if (*pr_file_p == '(') + { + s = pr_file_p + 1; + while(*pr_file_p++) + { + if (*pr_file_p == ',') + { + com.strncpy(cnst->params[cnst->numparams], s, pr_file_p-s); + cnst->params[cnst->numparams][pr_file_p-s] = '\0'; + cnst->numparams++; + if (cnst->numparams > MAX_PARMS) + PR_ParseError(ERR_MACROTOOMANYPARMS, "May not have more than %i parameters to a macro", MAX_PARMS); + pr_file_p++; + s = pr_file_p; + } + if (*pr_file_p == ')') + { + com.strncpy(cnst->params[cnst->numparams], s, pr_file_p-s); + cnst->params[cnst->numparams][pr_file_p-s] = '\0'; + cnst->numparams++; + if (cnst->numparams > MAX_PARMS) + PR_ParseError(ERR_MACROTOOMANYPARMS, "May not have more than %i parameters to a macro", MAX_PARMS); + pr_file_p++; + break; + } + } + } + else cnst->numparams = -1; + + s = pr_file_p; + d = cnst->value; + while(*s == ' ' || *s == '\t') s++; + while(1) + { + if( *s == '\\' ) + { + // read over a newline if necessary + if( s[1] == '\n' || s[1] == '\r' ) + { + s++; + *d++ = *s++; + if(s[-1] == '\r' && s[0] == '\n') *d++ = *s++; + } + } + else if(*s == '\r' || *s == '\n' || *s == '\0') + { + break; + } + + if (!quote && s[0]=='/'&&(s[1]=='/'||s[1]=='*')) break; + if (*s == '\"') quote=!quote; + *d = *s; + d++, s++; + } + + *d = '\0'; + d--; + while(*d<= ' ' && d >= cnst->value) *d-- = '\0'; + + if (com.strlen(cnst->value) >= sizeof(cnst->value)) //this is too late. + PR_ParseError(ERR_CONSTANTTOOLONG, "Macro %s too long (%i not %i)", cnst->name, com.strlen(cnst->value), sizeof(cnst->value)); + + if (oldval) + { + // we always warn if it was already defined + // we use different warning codes so that -Wno-mundane can be used to ignore identical redefinitions. + if (com.strcmp(oldval, cnst->value)) + PR_ParseWarning(WARN_DUPLICATEPRECOMPILER, "Alternate precompiler definition of %s", pr_token); + else PR_ParseWarning(WARN_IDENTICALPRECOMPILER, "Identical precompiler definition of %s", pr_token); + } + pr_file_p = s; +} + +int PR_CheakCompConst( void ) +{ + char *oldpr_file_p = pr_file_p; + int whitestart; + const_t *c; + char *end; + + for (end = pr_file_p; ; end++) + { + if (*end <= ' ') break; + if (*end == ')') break; + if (*end == '(') break; + if (*end == '+') break; + if (*end == '-') break; + if (*end == '*') break; + if (*end == '/') break; + if (*end == '%') break; + if (*end == '|') break; + if (*end == '&') break; + if (*end == '=') break; + if (*end == '^') break; + if (*end == '[') break; + if (*end == ']') break; + if (*end =='\"') break; + if (*end == '{') break; + if (*end == '}') break; + if (*end == ';') break; + if (*end == ':') break; + if (*end == ',') break; + if (*end == '.') break; + if (*end == '#') break; + } + + Mem_Copy(pr_token, pr_file_p, end - pr_file_p); + pr_token[end - pr_file_p] = '\0'; + + c = Hash_Get(&compconstantstable, pr_token); + + if (c && !c->inside) + { + pr_file_p = oldpr_file_p + com.strlen(c->name); + while(*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; + if (c->numparams>=0) + { + if (*pr_file_p == '(') + { + int p; + char *start; + char buffer[1024]; + char *paramoffset[MAX_PARMS+1]; + int param=0; + int plevel=0; + + pr_file_p++; + while(*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; + start = pr_file_p; + + while(1) + { + // handle strings correctly by ignoring them + if( *pr_file_p == '\"' ) + { + do + { + pr_file_p++; + } while((pr_file_p[-1] == '\\' || pr_file_p[0] != '\"') && *pr_file_p && *pr_file_p != '\n'); + } + if(*pr_file_p == '(') plevel++; + else if(!plevel && (*pr_file_p == ',' || *pr_file_p == ')')) + { + paramoffset[param++] = start; + start = pr_file_p+1; + if (*pr_file_p == ')') + { + *pr_file_p = '\0'; + pr_file_p++; + break; + } + *pr_file_p = '\0'; + pr_file_p++; + while(*pr_file_p == ' ' || *pr_file_p == '\t') pr_file_p++; + + // move back by one char because we move forward by one at the end of the loop + pr_file_p--; + if (param == MAX_PARMS) + PR_ParseError(ERR_TOOMANYPARAMS, "Too many parameters in macro call"); + } + else if(*pr_file_p == ')') plevel--; + + if(!*pr_file_p) PR_ParseError( ERR_EOF, "EOF on macro call" ); + pr_file_p++; + } + if (param < c->numparams) + PR_ParseError(ERR_TOOFEWPARAMS, "Not enough macro parameters"); + paramoffset[param] = start; + + *buffer = '\0'; + oldpr_file_p = pr_file_p; + pr_file_p = c->value; + + while( 1 ) + { + whitestart = p = com.strlen(buffer); + while(*pr_file_p <= ' ') // copy across whitespace + { + if (!*pr_file_p) break; + buffer[p++] = *pr_file_p++; + } + buffer[p] = 0; + + // if you ask for #a##b you will be shot. use #a #b instead, or chain macros. + if (*pr_file_p == '#') + { + if (pr_file_p[1] == '#') + { + // concatinate (skip out whitespace) + buffer[whitestart] = '\0'; + pr_file_p+=2; + } + else + { // stringify + pr_file_p++; + PR_ParseWord( &pr_file_p, true ); + if (!pr_file_p) break; + + for (p = 0; p < param; p++) + { + if (!STRCMP(pr_token, c->params[p])) + { + com.strcat(buffer, "\""); + com.strcat(buffer, paramoffset[p]); + com.strcat(buffer, "\""); + break; + } + } + if (p == param) + { + com.strcat(buffer, "#"); + com.strcat(buffer, pr_token); + PR_ParseWarning(0, "Stringification ignored"); + } + continue;// already did this one + } + } + PR_ParseWord( &pr_file_p, true ); + if (!pr_file_p) break; + + for (p = 0; p < param; p++) + { + if (!STRCMP(pr_token, c->params[p])) + { + com.strcat(buffer, paramoffset[p]); + break; + } + } + if (p == param) com.strcat(buffer, pr_token); + } + + for (p = 0; p < param-1; p++) + paramoffset[p][com.strlen(paramoffset[p])] = ','; + paramoffset[p][com.strlen(paramoffset[p])] = ')'; + pr_file_p = oldpr_file_p; + PR_IncludeChunkEx(buffer, true, NULL, c ); + } + else PR_ParseError(ERR_TOOFEWPARAMS, "Macro without opening brace"); + } + else PR_IncludeChunkEx(c->value, false, NULL, c ); + + PR_Lex(); + return true; + } + + // start of macros variables + if (!com.strncmp(pr_file_p, "__TIME__", 8)) + { + pr_file_p = (char *)timestamp( TIME_TIME_ONLY ); + PR_Lex();// translate the macro's value + pr_file_p = oldpr_file_p + 8; + + return true; + } + if (!com.strncmp(pr_file_p, "__DATE__", 8)) + { + pr_file_p = (char *)timestamp( TIME_DATE_ONLY ); + PR_Lex(); // translate the macro's value + pr_file_p = oldpr_file_p + 8; + + return true; + } + if (!com.strncmp(pr_file_p, "__FILE__", 8)) + { + static char retbuf[256]; + com.sprintf(retbuf, "\"%s\"", strings + s_file); + pr_file_p = retbuf; + PR_Lex(); // translate the macro's value + pr_file_p = oldpr_file_p + 8; + + return true; + } + if (!com.strncmp(pr_file_p, "__LINE__", 8)) + { + static char retbuf[256]; + com.sprintf(retbuf, "\"%i\"", pr_source_line); + pr_file_p = retbuf; + PR_Lex(); //translate the macro's value + pr_file_p = oldpr_file_p + 8; + return true; + } + if (!com.strncmp(pr_file_p, "__FUNC__", 8)) + { + static char retbuf[256]; + com.sprintf(retbuf, "\"%s\"", pr_scope->name); + pr_file_p = retbuf; + PR_Lex(); //translate the macro's value + pr_file_p = oldpr_file_p+8; + return true; + } + if (!com.strncmp(pr_file_p, "__NULL__", 8)) + { + static char retbuf[256]; + com.sprintf(retbuf, "~0"); + pr_file_p = retbuf; + PR_Lex(); //translate the macro's value + pr_file_p = oldpr_file_p + 8; + return true; + } + return false; +} + +char *PR_CheakCompConstString(char *def) +{ + char *s; + const_t *c; + + c = Hash_Get(&compconstantstable, def); + if (c) + { + s = PR_CheakCompConstString(c->value); + return s; + } + return def; +} + +const_t *PR_CheckCompConstDefined(char *def) +{ + return Hash_Get(&compconstantstable, def); +} + +//============================================================================ + +/* +============== +PR_Lex + +Advanced version of Com_ParseToken() +Sets pr_token, pr_token_type, and possibly pr_immediate and pr_immediate_type +============== +*/ +void PR_Lex( void ) +{ + int c; + + pr_token[0] = 0; + + if( !pr_file_p ) goto end_of_file; + + PR_LexWhitespace(); + + if( !pr_file_p ) goto end_of_file; + c = *pr_file_p; + if( !c ) goto end_of_file; + + // handle quoted strings as a unit + if( c == '\"' ) + { + PR_LexString (); + return; + } + + // handle quoted vectors as a unit + if (c == '\'') + { + PR_LexVector (); + return; + } + + if( c == '0' && pr_file_p[1] == 'x' ) + { + pr_token_type = tt_immediate; + PR_LexNumber(); + return; + } + if(( c == '.'&&pr_file_p[1] >='0' && pr_file_p[1] <= '9') || (c >= '0' && c <= '9') || ( c=='-' && pr_file_p[1]>='0' && pr_file_p[1] <='9') ) + { + pr_token_type = tt_immediate; + pr_immediate_type = type_float; + pr_immediate._float = PR_LexFloat (); + return; + } + + if (c == '#' && !(pr_file_p[1]=='-' || (pr_file_p[1]>='0' && pr_file_p[1] <='9'))) + { + // hash and not number + pr_file_p++; + if (!PR_CheakCompConst()) + { + if (!PR_SimpleGetToken()) com.strcpy(pr_token, "unknown"); + PR_ParseError(ERR_CONSTANTNOTDEFINED, "Explicit precompiler usage when not defined %s", pr_token); + } + else if (pr_token_type == tt_eof) PR_Lex(); + return; + } + + if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' ) + { + if (!PR_CheakCompConst()) PR_LexName (); // look for a macro. + else if (pr_token_type == tt_eof) goto end_of_file; + return; + } + if (c == '$') + { + PR_LexGrab (); + return; + } + + // parse symbol strings until a non-symbol is found + PR_LexPunctuation (); + return; + +end_of_file: + if (PR_UnInclude()) + { + PR_Lex(); + return; + } + pr_token_type = tt_eof; +} + +void PR_ParsePrintDef (int type, def_t *def) +{ + if(pr_warning[type]) return; + if(def->s_file) PR_Message ("%s:%i: '%s' is defined here\n", strings + def->s_file, def->s_line, def->name); +} + +/* +============ +PR_ParseError +============ +*/ +void PR_ParseError( int errortype, char *error, ... ) +{ + va_list argptr; + char string[1024]; + + va_start( argptr, error ); + com.vsnprintf(string, sizeof(string) - 1, error, argptr); + va_end( argptr ); + + if( errortype == ERR_INTERNAL ) + { + // because sys_error hide message in non-developer mode + // but in-game engine replaced Sys_Error with Host_Error and + // we can use it here + if( host_instance == HOST_NORMAL || host_instance == HOST_DEDICATED ) + { + PR_Message( "^3Error:^7 %s\n", string ); + prvm_state = comp_error; // abort compilation + longjmp( pr_int_error, 1 ); + } + else Sys_Break( "internal error: %s\n", string ); + } + else + { + PR_Message("%s:%i: error: %s\n", strings + s_file, pr_source_line, string); + longjmp( pr_parse_abort, 1 ); + } +} + +void PR_ParseErrorPrintDef( int errortype, def_t *def, char *error, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr,error); + com.vsnprintf (string, sizeof(string)-1, error, argptr); + va_end (argptr); + + PR_Message ("%s:%i: error: %s\n", strings + s_file, pr_source_line, string); + + PR_ParsePrintDef(WARN_ERROR, def); + longjmp (pr_parse_abort, 1); +} + +/* +============ +PR_ParseWarning +============ +*/ +void PR_ParseWarning (int type, char *error, ...) +{ + va_list argptr; + char string[1024]; + + if (type < ERR_PARSEERRORS && pr_warning[type]) return; + + va_start (argptr,error); + com.vsnprintf (string,sizeof(string) - 1, error,argptr); + va_end (argptr); + + if(type == ERR_INTERNAL) + { + // instead of sys error + pr_total_error_count++; + com.error( "internal error C%i: %s\n", type, string ); + } + else if (type > ERR_INTERNAL) + { + PR_Message("%s:%i: error C%i: %s\n", strings + s_file, pr_source_line, type, string); + pr_total_error_count++; + pr_error_count++; + } + else + { + PR_Message("%s:%i: warning C%i: %s\n", strings + s_file, pr_source_line, type, string); + pr_warning_count++; + } +} + +void PR_Warning (int type, char *file, int line, char *error, ...) +{ + va_list argptr; + char string[1024]; + + if (pr_warning[type]) return; + + va_start (argptr,error); + com.vsnprintf (string,sizeof(string) - 1, error,argptr); + va_end (argptr); + + if (file) PR_Message ("%s(%i) : warning C%i: %s\n", file, line, type, string); + else PR_Message ("warning C%i: %s\n", type, string); + pr_warning_count++; +} + +void PR_Message( char *message, ... ) +{ + va_list argptr; + char string[1024]; + + va_start (argptr, message); + com.vsnprintf (string, sizeof(string) - 1, message, argptr); + va_end (argptr); + + com.print( string ); +} + +/* +============= +PR_Expect + +Issues an error if the current token isn't equal to string +Gets the next token +============= +*/ +void PR_Expect (char *string) +{ + if(STRCMP(string, pr_token)) + PR_ParseError (ERR_EXPECTED, "expected %s, found %s",string, pr_token); + PR_Lex (); +} + +/* +============= +PR_Check + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool PR_CheckToken (char *string) +{ + if (STRCMP (string, pr_token)) + return false; + + PR_Lex (); + return true; +} + +bool PR_CheckName(char *string) +{ + if (STRCMP(string, pr_token)) + return false; + + PR_Lex (); + return true; +} + +bool PR_CheckForKeyword( int keyword ) +{ + if(!PR_MatchKeyword( keyword )) + return false; + + PR_Lex (); + return true; +} + +bool PR_MatchKeyword( int keyword ) +{ + int i; + + for(i = 0; i < NUM_KEYWORDS; i++ ) + { + if(pr_keywords[i].number == keyword) + { + // not keyword for current version + if(!STRCMP(pr_token, pr_keywords[i].name)) return true; + if(com.strlen(pr_keywords[i].alias) && !STRCMP(pr_token, pr_keywords[i].alias)) + return true; // use alias + break; // match found + } + } + + // this keyword is present, but not defined + // just in case + return false; +} + +bool PR_KeywordEnabled( int keyword ) +{ + int i; + + for(i = 0; i < NUM_KEYWORDS; i++ ) + { + // found it + if(pr_keywords[i].number == keyword) + return true; + } + + // not exist ? + return false; +} + +/* +============ +PR_ParseName + +Checks to see if the current token is a valid name +============ +*/ +char *PR_ParseName( void ) +{ + static char ident[MAX_NAME]; + char *ret; + + if( pr_token_type != tt_name ) PR_ParseError( ERR_NOTANAME, "\"%s\" - not a name", pr_token ); + if( com.strlen(pr_token ) >= MAX_NAME-1 ) PR_ParseError (ERR_NAMETOOLONG, "name too long" ); + + com.strcpy( ident, pr_token ); + PR_Lex(); + + ret = Qalloc( com.strlen( ident ) + 1 ); + com.strcpy( ret, ident ); + return ret; +} + +/* +============ +PR_FindType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +int typecmp(type_t *a, type_t *b) +{ + if (a == b) return 0; + if (!a || !b) return 1; // different (^ and not both null) + + if (a->type != b->type) return 1; + if (a->num_parms != b->num_parms) return 1; + if (a->size != b->size) return 1; + if (typecmp(a->aux_type, b->aux_type)) return 1; + + if (a->param || b->param) + { + a = a->param; + b = b->param; + + while(a || b) + { + if(typecmp(a, b)) return 1; + + a = a->next; + b = b->next; + } + } + return 0; +} + +type_t *PR_DuplicateType(type_t *in) +{ + type_t *out, *op, *ip; + + if (!in) return NULL; + out = PR_NewType(in->name, in->type); + out->aux_type = PR_DuplicateType(in->aux_type); + out->param = PR_DuplicateType(in->param); + ip = in->param; + op = NULL; + + while(ip) + { + if (!op) out->param = op = PR_DuplicateType(ip); + else op = (op->next = PR_DuplicateType(ip)); + ip = ip->next; + } + + out->size = in->size; + out->num_parms = in->num_parms; + out->ofs = in->ofs; + out->name = in->name; + out->parentclass = in->parentclass; + + return out; +} + +char *TypeName(type_t *type) +{ + static char buffer[2][512]; + static int op; + char *ret; + + op++; + ret = buffer[op&1]; + + if (type->type == ev_field) + { + type = type->aux_type; + *ret++ = '.'; + } + *ret = 0; + + if (type->type == ev_function) + { + com.strcat(ret, type->aux_type->name); + com.strcat(ret, " ("); + type = type->param; + + while(type) + { + com.strcat(ret, type->name); + type = type->next; + if (type) com.strcat(ret, ", "); + } + com.strcat(ret, ")"); + } + else if (type->type == ev_entity && type->parentclass) + { + ret = buffer[op&1]; + *ret = 0; + com.strcat(ret, "class "); + com.strcat(ret, type->name); + } + else com.strcpy(ret, type->name); + + return buffer[op&1]; +} + +type_t *PR_FindType (type_t *type) +{ + int t; + for (t = 0; t < numtypeinfos; t++) + { + if (typecmp(&qcc_typeinfo[t], type)) + continue; + return &qcc_typeinfo[t]; + } + + PR_ParseError(ERR_INTERNAL, "Error with type"); + return type; +} + +type_t *TypeForName(char *name) +{ + int i; + + for (i = 0; i < numtypeinfos; i++) + { + if (!STRCMP(qcc_typeinfo[i].name, name)) + return &qcc_typeinfo[i]; + } + + return NULL; +} + +/* +============ +PR_SkipToSemicolon + +For error recovery, also pops out of nested braces +============ +*/ +void PR_SkipToSemicolon (void) +{ + do + { + if (!pr_bracelevel && PR_CheckToken (";")) + return; + PR_Lex (); + } while (pr_token_type != tt_eof); +} + + +/* +============ +PR_ParseType + +Parses a variable type, including field and functions types +============ +*/ +type_t *PR_ParseFunctionType (int newtype, type_t *returntype) +{ + type_t *ftype, *ptype, *nptype; + char *name; + int definenames = !recursivefunctiontype; + + recursivefunctiontype++; + ftype = PR_NewType(type_function->name, ev_function); + + ftype->aux_type = returntype; // return type + ftype->num_parms = 0; + ptype = NULL; + + if (!PR_CheckToken (")")) + { + if (PR_CheckToken ("...")) ftype->num_parms = -1; // variable args + else + { + do + { + if (ftype->num_parms>=MAX_PARMS+MAX_PARMS_EXTRA) + PR_ParseError(ERR_TOOMANYTOTALPARAMETERS, "Too many parameters. Sorry. (limit is %i)\n", MAX_PARMS+MAX_PARMS_EXTRA); + + if (PR_CheckToken ("...")) + { + ftype->num_parms = (ftype->num_parms * -1) - 1; + break; + } + nptype = PR_ParseType(true); + + if (nptype->type == ev_void) break; + if (!ptype) + { + ptype = nptype; + ftype->param = ptype; + } + else + { + ptype->next = nptype; + ptype = ptype->next; + } + if (STRCMP(pr_token, ",") && STRCMP(pr_token, ")")) + { + name = PR_ParseName (); + if (definenames) com.strcpy (pr_parm_names[ftype->num_parms], name); + } + else if (definenames) com.strcpy (pr_parm_names[ftype->num_parms], ""); + ftype->num_parms++; + } while (PR_CheckToken (",")); + } + PR_Expect (")"); + } + + recursivefunctiontype--; + if (newtype) return ftype; + return PR_FindType (ftype); +} + +type_t *PR_PointerType (type_t *pointsto) +{ + type_t *ptype; + char name[128]; + + com.sprintf(name, "*%s", pointsto->name); + ptype = PR_NewType(name, ev_pointer); + ptype->aux_type = pointsto; + + return PR_FindType (ptype); +} + +type_t *PR_FieldType (type_t *pointsto) +{ + type_t *ptype; + char name[128]; + + com.sprintf(name, "FIELD TYPE(%s)", pointsto->name); + ptype = PR_NewType(name, ev_field); + ptype->aux_type = pointsto; + ptype->size = ptype->aux_type->size; + + return PR_FindType (ptype); +} + +type_t *PR_ParseType (int newtype) +{ + type_t *newparm; + type_t *newt; + type_t *type; + char *name; + int i; + + if (PR_CheckToken ("..")) //so we don't end up with the user specifying '. .vector blah' + { + newt = PR_NewType("FIELD TYPE", ev_field); + newt->aux_type = PR_ParseType (false); + newt->size = newt->aux_type->size; + newt = PR_FindType (newt); + + type = PR_NewType("FIELD TYPE", ev_field); + type->aux_type = newt; + type->size = type->aux_type->size; + + if (newtype) return type; + return PR_FindType (type); + } + if (PR_CheckToken (".")) + { + newt = PR_NewType("FIELD TYPE", ev_field); + newt->aux_type = PR_ParseType (false); + newt->size = newt->aux_type->size; + + if (newtype) return newt; + return PR_FindType (newt); + } + + name = PR_CheakCompConstString(pr_token); + + if (PR_CheckForKeyword( KEYWORD_CLASS )) + { + type_t *fieldtype; + char membername[2048]; + char *classname = PR_ParseName(); + + newt = PR_NewType(classname, ev_entity); + newt->size = type_entity->size; + type = NULL; + + if (PR_CheckToken(":")) + { + char *parentname = PR_ParseName(); + newt->parentclass = TypeForName(parentname); + if (!newt->parentclass) + PR_ParseError(ERR_NOTANAME, "Parent class %s was not defined", parentname); + } + else newt->parentclass = type_entity; + + PR_Expect("{"); + if (PR_CheckToken(",")) PR_ParseError(ERR_NOTANAME, "member missing name"); + + while (!PR_CheckToken("}")) + { + newparm = PR_ParseType(true); + + // we wouldn't be able to handle it. + if (newparm->type == ev_struct || newparm->type == ev_union) + PR_ParseError(ERR_INTERNAL, "Struct or union in class %s", classname); + + if (!PR_CheckToken(";")) + { + newparm->name = PR_CopyString(pr_token, opt_noduplicatestrings ) + strings; + PR_Lex(); + if (PR_CheckToken("[")) + { + type->next->size*=com.atoi(pr_token); + PR_Lex(); + PR_Expect("]"); + } + PR_CheckToken(";"); + } + else newparm->name = PR_CopyString("", opt_noduplicatestrings )+strings; + + com.sprintf(membername, "%s::"MEMBERFIELDNAME, classname, newparm->name); + fieldtype = PR_NewType(newparm->name, ev_field); + fieldtype->aux_type = newparm; + fieldtype->size = newparm->size; + PR_GetDef(fieldtype, membername, pr_scope, 2, 1); + + newparm->ofs = 0; // newt->size; + newt->num_parms++; + + if (type) type->next = newparm; + else newt->param = newparm; + type = newparm; + } + PR_Expect(";"); + return NULL; + } + if (PR_CheckForKeyword( KEYWORD_STRUCT )) + { + newt = PR_NewType("struct", ev_struct); + newt->size=0; + PR_Expect("{"); + + type = NULL; + if (PR_CheckToken(",")) PR_ParseError(ERR_NOTANAME, "element missing name"); + + newparm = NULL; + while (!PR_CheckToken("}")) + { + if (PR_CheckToken(",")) + { + if (!newparm) PR_ParseError(ERR_NOTANAME, "element missing type"); + newparm = PR_NewType(newparm->name, newparm->type); + } + else newparm = PR_ParseType(true); + + if (!PR_CheckToken(";")) + { + newparm->name = PR_CopyString(pr_token, opt_noduplicatestrings )+strings; + PR_Lex(); + if (PR_CheckToken("[")) + { + newparm->size*=com.atoi(pr_token); + PR_Lex(); + PR_Expect("]"); + } + PR_CheckToken(";"); + } + else newparm->name = PR_CopyString("", opt_noduplicatestrings )+strings; + newparm->ofs = newt->size; + newt->size += newparm->size; + newt->num_parms++; + + if (type) type->next = newparm; + else newt->param = newparm; + type = newparm; + } + return newt; + } + if(PR_CheckForKeyword( KEYWORD_UNION )) + { + newt = PR_NewType("union", ev_union); + newt->size = 0; + PR_Expect("{"); + + type = NULL; + if (PR_CheckToken(",")) PR_ParseError(ERR_NOTANAME, "element missing name"); + newparm = NULL; + + while (!PR_CheckToken("}")) + { + if (PR_CheckToken(",")) + { + if (!newparm) PR_ParseError(ERR_NOTANAME, "element missing type"); + newparm = PR_NewType(newparm->name, newparm->type); + } + else newparm = PR_ParseType(true); + + if (PR_CheckToken(";")) newparm->name = PR_CopyString("", opt_noduplicatestrings )+strings; + else + { + newparm->name = PR_CopyString(pr_token, opt_noduplicatestrings )+strings; + PR_Lex(); + PR_Expect(";"); + } + + newparm->ofs = 0; + if (newparm->size > newt->size) newt->size = newparm->size; + newt->num_parms++; + + if (type) type->next = newparm; + else newt->param = newparm; + type = newparm; + } + return newt; + } + + type = NULL; + for (i = 0; i < numtypeinfos; i++) + { + if (!STRCMP(qcc_typeinfo[i].name, name)) + { + type = &qcc_typeinfo[i]; + break; + } + } + + if (i == numtypeinfos) + { + if (!*name) return NULL; + PR_ParseError (ERR_NOTATYPE, "\"%s\" is not a type", name); + type = type_float; // shut up compiler warning + } + PR_Lex (); + + // this is followed by parameters. Must be a function. + if (PR_CheckToken ("(")) return PR_ParseFunctionType(newtype, type); + else + { + if (newtype) type = PR_DuplicateType(type); + return type; + } +} \ No newline at end of file diff --git a/vprogs/pr_local.h b/vprogs/pr_local.h new file mode 100644 index 00000000..2853dbd9 --- /dev/null +++ b/vprogs/pr_local.h @@ -0,0 +1,712 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// pr_local.h - virtual machine definitions +//======================================================================= +#ifndef PR_LOCAL_H +#define PR_LOCAL_H + +#include +#include "byteorder.h" + +/* + +TODO: + other pointer types for models and clients? + compact string heap? + always initialize all variables to something safe + the def->type->type arrangement is really silly. + return type checking + parm count type checking + immediate overflow checking + pass the first two parms in call->b and call->c +*/ + +#define MEMBERFIELDNAME "__m%s" // mark +#define MAX_NAME 64 // chars long +#define MAX_PARMS_EXTRA 128 +#define PROGDEFS_MAX_SIZE MAX_MSGLEN // 32 kbytes + +// optimization level flags +#define FL_DBG 1 +#define FL_OP0 2 +#define FL_OP1 4 +#define FL_OP2 8 +#define FL_OP3 16 +#define FL_OP4 32 + +#define MASK_DEBUG (FL_DBG) +#define MASK_LEVEL_O (FL_OP0) +#define MASK_LEVEL_1 (FL_OP0|FL_OP1) +#define MASK_LEVEL_2 (FL_OP0|FL_OP1|FL_OP2) +#define MASK_LEVEL_3 (FL_OP0|FL_OP1|FL_OP2|FL_OP3) +#define MASK_LEVEL_4 (FL_OP0|FL_OP1|FL_OP2|FL_OP3|FL_OP4) + +//compiler flags +#define FLAG_V7_ONLY 1 // only 7 version can use this optimization +#define FLAG_DEFAULT 2 // enabled as default + +#define G_FLOAT(o) (pr_globals[o]) +#define G_INT(o) (*(int *)&pr_globals[o]) +#define G_VECTOR(o) (&pr_globals[o]) +#define G_STRING(o) (strings + *(string_t *)&pr_globals[o]) +#define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) + +//qcc private pool +#define Qalloc( size ) Mem_Alloc(qccpool, size ) +#define Qrealloc( ptr, size ) Mem_Realloc(qccpool, ptr, size ) + +extern int com_argc; +extern char **com_argv; + +//loading files +#define QCC_LoadFile(f, c) PR_LoadFile(f, c, FT_CODE) +#define QCC_LoadData(f) PR_LoadFile(f, true, FT_DATA) + +#define BytesForBuckets(b) (sizeof(bucket_t)*b) +#define STRCMP(s1, s2) (((*s1)!=(*s2)) || com.strcmp(s1+1,s2+1)) +#define STRNCMP(s1, s2, l) (((*s1)!=(*s2)) || com.strncmp(s1+1,s2+1,l)) + +typedef uint gofs_t; // offset in global data block +typedef struct function_s function_t; + +typedef enum +{ + comp_inactive = 0, + comp_begin, + comp_frame, + comp_done, + comp_error, +} comp_state_t; + +typedef enum +{ + tt_eof, // end of file reached + tt_name, // an alphanumeric name token + tt_punct, // code punctuation + tt_immediate, // string, float, vector +} token_type_t; + +enum +{ + // progs 6 keywords + KEYWORD_DO, + KEYWORD_IF, + KEYWORD_VOID, + KEYWORD_ELSE, + KEYWORD_LOCAL, // legacy ( not used ) + KEYWORD_WHILE, + KEYWORD_ENTITY, + KEYWORD_FLOAT, + KEYWORD_STRING, + KEYWORD_VECTOR, + KEYWORD_RETURN, + + // 6 extended (no new opcode used) + KEYWORD_BREAK, + KEYWORD_FOR, + KEYWORD_CONTINUE, + KEYWORD_CONST, + + // progs 7 keywords + KEYWORD_ENUM, + KEYWORD_ENUMFLAGS, + KEYWORD_CASE, + KEYWORD_DEFAULT, + KEYWORD_GOTO, + KEYWORD_INT, + KEYWORD_STATE, + KEYWORD_CLASS, + KEYWORD_STRUCT, + KEYWORD_SWITCH, + KEYWORD_TYPEDEF, + KEYWORD_EXTERN, + KEYWORD_UNION, + KEYWORD_THINKTIME, + + // progs 8 keywords + KEYWORD_BOOL, + KEYWORD_ASM, + + KEYWORD_SHARED, + KEYWORD_NOSAVE, + NUM_KEYWORDS +}; + +typedef enum +{ + ev_void, + ev_string, + ev_float, + ev_vector, + ev_entity, + ev_field, + ev_function, + ev_pointer, + ev_integer, + ev_struct, + ev_union, + ev_bool, +} etype_t; + +typedef struct bucket_s +{ + void *data; + char *keystring; + struct bucket_s *next; +} bucket_t; + +typedef struct hashtable_s +{ + uint numbuckets; + bucket_t **bucket; +} hashtable_t; + +typedef struct cachedsourcefile_s +{ + char filename[128]; + int size; + byte *file; + enum + { + FT_CODE, // quakec source + FT_DATA, // quakec lib + } type; + struct cachedsourcefile_s *next; // chain +} cachedsourcefile_t; + +struct function_s +{ + int builtin; // if non 0, call an internal function + int code; // first statement + char *file; // source file with definition + int file_line; + struct def_s *def; + uint parm_ofs[MAX_PARMS];// always contiguous, right? +}; + +typedef struct type_s +{ + etype_t type; + + struct type_s *parentclass; // type_entity... + struct type_s *next; + struct type_s *aux_type; // return type or field type + struct type_s *param; + + int num_parms; // -1 = variable args + uint ofs; // inside a structure. + uint size; + char *name; +} type_t; + +typedef struct temp_s +{ + gofs_t ofs; + struct def_s *scope; + struct def_s *lastfunc; + struct temp_s *next; + bool used; + uint size; +} temp_t; + +typedef union eval_s +{ + float _float; + int _int; + float vector[3]; + func_t function; + string_t string; + union eval_s *ptr; +} eval_t; + +// virtual typedef +typedef union prvm_eval_s +{ + int edict; + int _int; + float _float; + float vector[3]; + func_t function; + string_t string; +} prvm_eval_t; + +typedef struct +{ + bool *enabled; + char *shortname; // for option "/Ox", where x is shortname + char *fullname; // display name + int levelmask; // optimization level mask + int flags; // sepcial flags for kill debug extensions, e.t.c +} optimisations_t; + +typedef struct keyword_s +{ + int number; + const char *name; // first name + const char *alias; // alias for keyword + uint ignoretype; // ignore matching for this type +} keyword_t; + +typedef struct +{ + char name[MAX_NAME]; + char value[MAX_NAME * 4]; + char params[MAX_PARMS][32]; + int numparams; + bool used; + bool inside; + int namelen; +} const_t; + +typedef struct includechunk_s +{ + struct includechunk_s *prev; + char *filename; + char *currentdatapoint; + int currentlinenumber; + const_t *cnst; +} includechunk_t; + +typedef struct freeoffset_s +{ + struct freeoffset_s *next; + gofs_t ofs; + uint size; + +} freeoffset_t; + +typedef struct +{ + char *name; + char *opname; + int priority; + + enum + { + ASSOC_LEFT, + ASSOC_RIGHT, + } associative; + + type_t **type_a; + type_t **type_b; + type_t **type_c; +} opcode_t; + +typedef struct def_s +{ + type_t *type; + char *name; + struct def_s *next; + struct def_s *nextlocal; // provides a chain of local variables + gofs_t ofs; // for the opt_locals_marshalling optimisation. + struct def_s *scope; // function the var was defined in, or NULL + int initialized; // 1 when a declaration included "= immediate" + int constant; // 1 says we can use the value over and over again + bool local; // 1 indices local variable + + int references; + int timescalled; // part of the opt_stripfunctions optimisation. + + int s_file; + int s_line; + + int arraysize; + bool shared; + bool saved; + + temp_t *temp; +} def_t; + +typedef struct +{ + type_t *types; + + def_t def_head; // unused head of linked list + def_t *def_tail; // add new defs after this and move it + def_t *localvars; // chain of variables which need to be pushed and stuff. + + int size_fields; +} pr_info_t; + +typedef enum { + + WARN_DEBUGGING, + WARN_ERROR, + WARN_NOTREFERENCED, + WARN_NOTREFERENCEDCONST, + WARN_CONFLICTINGRETURNS, + WARN_TOOFEWPARAMS, + WARN_TOOMANYPARAMS, + WARN_UNEXPECTEDPUNCT, + WARN_ASSIGNMENTTOCONSTANT, + WARN_ASSIGNMENTTOCONSTANTFUNC, + WARN_MISSINGRETURNVALUE, + WARN_WRONGRETURNTYPE, + WARN_POINTLESSSTATEMENT, + WARN_MISSINGRETURN, + WARN_DUPLICATEDEFINITION, + WARN_UNDEFNOTDEFINED, + WARN_PRECOMPILERMESSAGE, + WARN_TOOMANYPARAMETERSFORFUNC, + WARN_STRINGTOOLONG, + WARN_BADTARGET, + WARN_BADPRAGMA, + WARN_HANGINGSLASHR, + WARN_NOTDEFINED, + WARN_NOTCONSTANT, + WARN_SWITCHTYPEMISMATCH, + WARN_CONFLICTINGUNIONMEMBER, + WARN_ENUMFLAGS_NOTINTEGER, + WARN_ENUMFLAGS_NOTBINARY, + WARN_CASEINSENSATIVEFRAMEMACRO, + WARN_DUPLICATELABEL, + WARN_DUPLICATEMACRO, + WARN_ASSIGNMENTINCONDITIONAL, + WARN_MACROINSTRING, + WARN_BADPARAMS, + WARN_IMPLICITCONVERSION, + WARN_FIXEDRETURNVALUECONFLICT, + WARN_EXTRAPRECACHE, + WARN_NOTPRECACHED, + WARN_DEADCODE, + WARN_UNREACHABLECODE, + WARN_NOTSTANDARDBEHAVIOUR, + WARN_INEFFICIENTPLUSPLUS, + WARN_DUPLICATEPRECOMPILER, + WARN_IDENTICALPRECOMPILER, + WARN_EXTENSION_USED, //extension that frikqcc also understands + WARN_IFSTRING_USED, + WARN_LAXCAST, //some errors become this with a compiler flag + WARN_UNDESIRABLECONVENTION, + WARN_SAMENAMEASGLOBAL, + WARN_CONSTANTCOMPARISON, + WARN_IMAGETOOBIG, + WARN_IGNOREDONLEFT, + + // decompiling warns + WARN_UNKNOWNTEMPTYPE, // + + ERR_PARSEERRORS, //caused by pr_parseerror being called. + + // these are definatly my fault... + ERR_INTERNAL, + ERR_TOOCOMPLEX, + ERR_BADOPCODE, + ERR_TOOMANYSTATEMENTS, + ERR_TOOMANYSTRINGS, + ERR_BADTARGETSWITCH, + ERR_TOOMANYTYPES, + ERR_TOOMANYPAKFILES, + ERR_PRECOMPILERCONSTANTTOOLONG, + ERR_MACROTOOMANYPARMS, + ERR_CONSTANTTOOLONG, + ERR_TOOMANYFRAMEMACROS, + + // limitations, some are imposed by compiler, some arn't. + ERR_TOOMANYGLOBALS, + ERR_TOOMANYGOTOS, + ERR_TOOMANYBREAKS, + ERR_TOOMANYCONTINUES, + ERR_TOOMANYCASES, + ERR_TOOMANYLABELS, + ERR_TOOMANYOPENFILES, + ERR_TOOMANYPARAMETERSVARARGS, + ERR_TOOMANYTOTALPARAMETERS, + + // these are probably yours, or qcc being fussy. + ERR_BADEXTENSION, + ERR_BADIMMEDIATETYPE, + ERR_NOOUTPUT, + ERR_NOTAFUNCTION, + ERR_FUNCTIONWITHVARGS, + ERR_BADHEX, + ERR_UNKNOWNPUCTUATION, + ERR_EXPECTED, + ERR_NOTANAME, + ERR_NAMETOOLONG, + ERR_NOFUNC, + ERR_COULDNTOPENFILE, + ERR_NOTFUNCTIONTYPE, + ERR_TOOFEWPARAMS, + ERR_TOOMANYPARAMS, + ERR_CONSTANTNOTDEFINED, + ERR_BADFRAMEMACRO, + ERR_TYPEMISMATCH, + ERR_TYPEMISMATCHREDEC, + ERR_TYPEMISMATCHPARM, + ERR_TYPEMISMATCHARRAYSIZE, + ERR_UNEXPECTEDPUNCTUATION, + ERR_NOTACONSTANT, + ERR_REDECLARATION, + ERR_INITIALISEDLOCALFUNCTION, + ERR_NOTDEFINED, + ERR_ARRAYNEEDSSIZE, + ERR_ARRAYNEEDSBRACES, + ERR_TOOMANYINITIALISERS, + ERR_TYPEINVALIDINSTRUCT, + ERR_NOSHAREDLOCALS, + ERR_TYPEWITHNONAME, + ERR_BADARRAYSIZE, + ERR_NONAME, + ERR_SHAREDINITIALISED, + ERR_UNKNOWNVALUE, + ERR_BADARRAYINDEXTYPE, + ERR_NOVALIDOPCODES, + ERR_MEMBERNOTVALID, + ERR_BADPLUSPLUSOPERATOR, + ERR_BADNOTTYPE, + ERR_BADTYPECAST, + ERR_MULTIPLEDEFAULTS, + ERR_CASENOTIMMEDIATE, + ERR_BADSWITCHTYPE, + ERR_BADLABELNAME, + ERR_NOLABEL, + ERR_THINKTIMETYPEMISMATCH, + ERR_STATETYPEMISMATCH, + ERR_BADBUILTINIMMEDIATE, + ERR_PARAMWITHNONAME, + ERR_ILLEGALNAME, + ERR_BADPARAMORDER, + ERR_ILLEGALCONTINUES, + ERR_ILLEGALBREAKS, + ERR_ILLEGALCASES, + ERR_NOTANUMBER, + ERR_WRONGSUBTYPE, + ERR_EOF, + ERR_NOPRECOMPILERIF, + ERR_NOENDIF, + ERR_HASHERROR, + ERR_NOTATYPE, + ERR_TOOMANYPACKFILES, + ERR_INVALIDVECTORIMMEDIATE, + ERR_INVALIDSTRINGIMMEDIATE, + ERR_BADCHARACTURECODE, + ERR_BADPARMS, + ERR_EXCEEDERRCOUNT, + ERR_PRECOMPILERMESSAGE, + + WARN_MAX, +}; + + +extern pr_info_t pr; +extern byte *qccpool; +extern file_t *asmfile; +extern int MAX_ERRORS; +extern char *compilingfile; +extern char progsoutname[MAX_SYSPATH]; +extern char sourcedir[MAX_SYSPATH]; +extern int recursivefunctiontype; +extern freeoffset_t *freeofs; +extern type_t *type_void; +extern type_t *type_string; +extern type_t *type_float; +extern type_t *type_vector; +extern type_t *type_entity; +extern type_t *type_field; +extern type_t *type_function; +extern type_t *type_pointer; +extern type_t *type_integer; +extern type_t *type_floatfield; +extern includechunk_t *currentchunk; +extern cachedsourcefile_t *sourcefile; +extern optimisations_t pr_optimisations[]; +extern char v_copyright[1024]; +extern hashtable_t compconstantstable; +extern hashtable_t globalstable, localstable; +extern hashtable_t intconstdefstable; +extern hashtable_t floatconstdefstable; +extern hashtable_t stringconstdefstable; +extern opcode_t pr_opcodes[]; // sized by initialization +extern keyword_t pr_keywords[]; +extern temp_t *functemps; +const extern int type_size[]; +extern const_t *CompilerConstant; +extern bool autoprototype; +extern bool bodylessfuncs; +extern char pr_token[8192]; +extern token_type_t pr_token_type; +extern type_t *pr_immediate_type; +extern eval_t pr_immediate; +extern char *basictypenames[]; +extern int host_instance; +extern int prvm_state; + +extern bool opt_laxcasts; +extern bool opt_ifstring; +extern bool opt_overlaptemps; +extern bool opt_shortenifnots; +extern bool opt_noduplicatestrings; +extern bool opt_constantarithmatic; +extern bool opt_nonvec_parms; +extern bool opt_constant_names; +extern bool opt_precache_file; +extern bool opt_filenames; +extern bool opt_assignments; +extern bool opt_unreferenced; +extern bool opt_function_names; +extern bool opt_locals; +extern bool opt_dupconstdefs; +extern bool opt_constant_names_strings; +extern bool opt_return_only; +extern bool opt_compound_jumps; +extern bool opt_stripfunctions; +extern bool opt_locals_marshalling; +extern bool opt_logicops; +extern bool opt_vectorcalls; +extern bool opt_writelinenums; +extern bool opt_writetypes; +extern bool opt_writesources; +extern bool opt_compstrings; // compress all strings into produced file +extern bool opt_compfunctions; // compress all functions and statements +extern bool opt_compress_other; +extern bool pr_subscopedlocals; +extern bool pr_warning[WARN_MAX]; +extern char pr_parm_names[MAX_PARMS + MAX_PARMS_EXTRA][MAX_NAME]; +extern def_t *extra_parms[MAX_PARMS_EXTRA]; +extern jmp_buf pr_parse_abort; // longjump with this on parse error +extern jmp_buf pr_int_error; // casued in-game instead of Host_Error +extern int pr_source_line; +extern char *pr_file_p; +extern def_t *pr_scope; +extern int pr_bracelevel; +extern int pr_error_count; +extern int pr_total_error_count; +extern int pr_warning_count; +extern int ForcedCRC; +extern string_t s_file, s_file2; // filename for function definition +extern def_t def_ret; +extern def_t def_parms[MAX_PARMS]; +extern char pr_immediate_string[8192]; +extern char sourcefilename[MAX_SYSPATH]; +extern float *pr_globals; +extern uint numpr_globals; +extern char *strings; +extern int strofs; +extern dstatement_t *statements; +extern int numstatements; +extern int *statement_linenums; +extern dfunction_t *functions; +extern int numfunctions; +extern ddef_t *qcc_globals; +extern int numglobaldefs; +extern def_t *activetemps; +extern ddef_t *fields; +extern int numfielddefs; +extern type_t *qcc_typeinfo; +extern int numtypeinfos; +extern int maxtypeinfos; +extern int numtemps; + +// +// pr_main.c +// +void PR_InitCompile( const char *name ); +void PR_InitDecompile( const char *name ); +bool PRVM_DecompileProgs( const char *name ); + +// +// pr_utils.c +// +extern int typecmp(type_t *a, type_t *b); +void Hash_InitTable(hashtable_t *table, int numbucks); +uint Hash_Key(char *name, int modulus); +void *Hash_Get(hashtable_t *table, char *name); +void *Hash_GetKey(hashtable_t *table, int key); +void Hash_Remove(hashtable_t *table, char *name); +void Hash_RemoveKey(hashtable_t *table, int key); +void *Hash_GetNext(hashtable_t *table, char *name, void *old); +void Hash_RemoveData(hashtable_t *table, char *name, void *data); +void *Hash_Add(hashtable_t *table, char *name, void *data, bucket_t *buck); +void *Hash_AddKey(hashtable_t *table, int key, void *data, bucket_t *buck); +void PR_WriteBlock(file_t *f, fs_offset_t pos, const void *data, size_t blocksize, bool compress); +bool PR_LoadingProject( const char *filename ); +void PR_SetDefaultProperties( void ); +word PR_WriteProgdefs( void ); +void PR_WriteLNOfile(char *filename); +byte *PR_CreateProgsSRC( void ); +void PR_WriteDAT( void ); + +// +// pr_lex.c +// +int PR_LexInteger (void); +void PR_LexString (void); +void PR_LexWhitespace (void); +bool PR_SimpleGetToken (void); +bool PR_Include(char *filename); +bool PR_UndefineName(char *name); +void PR_ConditionCompilation(void); +char *PR_CheakCompConstString(char *def); +const_t *PR_CheckCompConstDefined(char *def); +type_t *PR_NewType (char *name, int basictype); +char *PR_ParseToken( const char **data_p, bool allow_newline ); + +// +// pr_comp.c +// +void PR_Lex( void ); +void PR_ParseAsm(void); +bool PR_UnInclude( void ); +void PR_PrintDefs( void ); +void PR_ParseState (void); +char *PR_ParseName( void ); +void PR_Expect (char *string); +char *TypeName( type_t *type ); +void PR_SkipToSemicolon( void ); +type_t *TypeForName(char *name); +void PR_ClearGrabMacros( void ); +def_t *PR_MakeIntDef(int value); +void PR_UnmarshalLocals( void ); +void PR_NewLine (bool incomment); +bool PR_CheckToken (char *string); +bool PR_CheckName (char *string); +const_t *PR_DefineName(char *name); +type_t *PR_ParseType (int newtype); +type_t *PR_FindType (type_t *type); +bool PR_MatchKeyword( int keyword ); +def_t *PR_MakeFloatDef(float value); +def_t *PR_MakeStringDef(char *value); +bool PR_KeywordEnabled( int keyword ); +void PR_Message( char *message, ... ); +bool PR_CheckForKeyword( int keyword ); +type_t *PR_FieldType (type_t *pointsto); +void PR_PrintStatement (dstatement_t *s); +type_t *PR_PointerType (type_t *pointsto); +int PR_WriteBodylessFuncs (file_t *handle); +char *PR_ValueString (etype_t type, void *val); +int PR_CopyString (char *str, bool noduplicate ); +void PR_CompileFile (char *string, char *filename); +bool PR_StatementIsAJump(int stnum, int notifdest); +void PR_ParsePrintDef (int warningtype, def_t *def); +def_t *PR_Expression (int priority, bool allowcomma); +void PR_ParseError (int errortype, char *error, ...); +int PR_AStatementJumpsTo(int targ, int first, int last); +void PR_ParseWarning (int warningtype, char *error, ...); +void PR_EmitClassFromFunction(def_t *scope, char *tname); +byte *PR_LoadFile(char *filename, bool crash, int type ); +int PR_encode( int len, int method, char *in, file_t *h ); +void PR_EmitArrayGetFunction(def_t *scope, char *arrayname); +void PR_EmitArraySetFunction(def_t *scope, char *arrayname); +type_t *PR_ParseFunctionType (int newtype, type_t *returntype); +void PR_IncludeChunk (char *data, bool duplicate, char *filename); +void PR_Warning (int type, char *file, int line, char *error, ...); +void PR_ParseErrorPrintDef (int errortype, def_t *def, char *error, ...); +int PR_WriteSourceFiles(file_t *h, dprograms_t *progs, bool sourceaswell); +int PR_decode(int complen, int len, int method, char *info, char **buffer); +void PR_WriteAsmFunction(def_t *sc, uint firststatement, gofs_t firstparm); +def_t *PR_GetDef (type_t *type, char *name, def_t *scope, bool allocate, int arraysize); +def_t *PR_Statement ( opcode_t *op, def_t *var_a, def_t *var_b, dstatement_t **outstatement); +void PR_RemapOffsets(uint firststatement, uint laststatement, uint min, uint max, uint newmin); +def_t *PR_DummyDef(type_t *type, char *name, def_t *scope, int arraysize, uint ofs, int referable); +void PR_BeginCompilation ( void ); +bool PR_ContinueCompile ( void ); +void PR_FinishCompilation ( void ); + +// +// pr_decomp.c +// +void PR_InitTypes( void ); +bool PR_Decompile( const char *name ); + +#endif//PR_LOCAL_H \ No newline at end of file diff --git a/vprogs/pr_main.c b/vprogs/pr_main.c new file mode 100644 index 00000000..9e6bb190 --- /dev/null +++ b/vprogs/pr_main.c @@ -0,0 +1,392 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// pr_main.c - PRVM compiler-executor +//======================================================================= + +#include "vprogs.h" + +stdlib_api_t com; +byte *qccpool; +int com_argc = 0; +char **com_argv; +char v_copyright[1024]; +int MAX_ERRORS; +int numtemps; +bool compileactive = false; +char progsoutname[MAX_SYSPATH]; +float *pr_globals; +uint numpr_globals; +char *strings; +int strofs; +dstatement_t *statements; +int numstatements; +int *statement_linenums; +char sourcedir[MAX_SYSPATH]; +cachedsourcefile_t *sourcefile; +dfunction_t *functions; +int numfunctions; +ddef_t *qcc_globals; +int numglobaldefs; +ddef_t *fields; +int numfielddefs; +bool pr_warning[WARN_MAX]; +bool bodylessfuncs; +type_t *qcc_typeinfo; +int numtypeinfos; +int maxtypeinfos; +int prvm_developer; +int host_instance; +int prvm_state; +vprogs_exp_t vm; + +hashtable_t compconstantstable; +hashtable_t globalstable; +hashtable_t localstable; +hashtable_t intconstdefstable; +hashtable_t floatconstdefstable; +hashtable_t stringconstdefstable; + +cvar_t *prvm_maxedicts; +cvar_t *prvm_traceqc; +cvar_t *prvm_boundscheck; +cvar_t *prvm_statementprofiling; + +void PR_SetDefaultProperties( void ) +{ + const_t *cnst; + int i, j; + char *name, *val; + + Hash_InitTable( &compconstantstable, MAX_CONSTANTS ); + + ForcedCRC = 0; + // enable all warnings + Mem_Set(pr_warning, 0, sizeof(pr_warning)); + + // reset all optimizarions + for( i = 0; pr_optimisations[i].enabled; i++ ) + *pr_optimisations[i].enabled = false; + + PR_DefineName("_QCLIB"); // compiler type + + // play with default warnings. + pr_warning[WARN_NOTREFERENCEDCONST] = true; + pr_warning[WARN_MACROINSTRING] = true; + pr_warning[WARN_FIXEDRETURNVALUECONFLICT] = true; + pr_warning[WARN_EXTRAPRECACHE] = true; + pr_warning[WARN_DEADCODE] = true; + pr_warning[WARN_INEFFICIENTPLUSPLUS] = true; + pr_warning[WARN_EXTENSION_USED] = true; + pr_warning[WARN_IFSTRING_USED] = true; + + for (i = 1; i < com_argc; i++) + { + if( !com.strnicmp(com_argv[i], "/D", 2)) + { + // #define + name = com_argv[i+1]; + val = com.strchr(name, '='); + if (val) { *val = '\0', val++; } + cnst = PR_DefineName(name); + if (val) + { + if(com.strlen(val) + 1 >= sizeof(cnst->value)) + PR_ParseError(ERR_INTERNAL, "compiler constant value is too long\n"); + com.strncpy(cnst->value, val, sizeof(cnst->value)-1); + PR_Message("value %s\n", val ); + cnst->value[sizeof(cnst->value)-1] = '\0'; + } + } + else if( !com.strnicmp(com_argv[i], "/O", 2)) + { + int currentlevel = 0; // optimization level + if(com_argv[i][2] == 'd') currentlevel = MASK_DEBUG; // disable optimizations + else if (com_argv[i][2] == '0') currentlevel = MASK_LEVEL_O; + else if (com_argv[i][2] == '1') currentlevel = MASK_LEVEL_1; + else if (com_argv[i][2] == '2') currentlevel = MASK_LEVEL_2; + else if (com_argv[i][2] == '3') currentlevel = MASK_LEVEL_3; + else if (com_argv[i][2] == '4') currentlevel = MASK_LEVEL_4; + + if(currentlevel) + { + for (j = 0; pr_optimisations[j].enabled; j++) + { + if(pr_optimisations[j].levelmask & currentlevel) + *pr_optimisations[j].enabled = true; + } + } + else + { + char *abbrev = com_argv[i]+2; // custom optimisation + for (j = 0; pr_optimisations[j].enabled; j++) + { + if(!com.strcmp(pr_optimisations[j].shortname, abbrev)) + { + *pr_optimisations[j].enabled = true; + break; + } + } + } + } + } + +} + +/* +=================== +PR_Init + +initialize compiler and hash tables +=================== +*/ +void PR_InitCompile( const char *name ) +{ + static char parmname[12][MAX_PARMS]; + static temp_t ret_temp; + int i; + + com.strncat(v_copyright,"This file was created with Xash3D QuakeC compiler,\n", sizeof(v_copyright)); + com.strncat(v_copyright,"who based on original code of ForeThought's QuakeC compiler.\n",sizeof(v_copyright)); + com.strncat(v_copyright,"Thanks to ID Software at all.", sizeof(v_copyright)); + MAX_ERRORS = 10; // per one file + maxtypeinfos = 16384; + + PR_SetDefaultProperties(); + + numtemps = 0; + functemps = NULL; + sourcefile = NULL; + + strings = (void *)Qalloc(sizeof(char) * MAX_STRINGS); + strofs = 1; + + statements = (void *)Qalloc(sizeof(dstatement_t) * MAX_STATEMENTS); + numstatements = 1; + + statement_linenums = (void *)Qalloc(sizeof(int) * MAX_STATEMENTS); + + functions = (void *)Qalloc(sizeof(dfunction_t) * MAX_FUNCTIONS); + numfunctions = 1; + + pr_bracelevel = 0; + + pr_globals = (void *)Qalloc(sizeof(float) * MAX_REGS); + numpr_globals = 0; + + Hash_InitTable(&globalstable, MAX_REGS); + Hash_InitTable(&localstable, MAX_REGS); + Hash_InitTable(&floatconstdefstable, MAX_REGS+1); + Hash_InitTable(&intconstdefstable, MAX_REGS+1); + Hash_InitTable(&stringconstdefstable, MAX_REGS); + + qcc_globals = (void *)Qalloc(sizeof(ddef_t) * MAX_GLOBALS); + numglobaldefs = 1; + + fields = (void *)Qalloc(sizeof(ddef_t) * MAX_FIELDS); + numfielddefs = 1; + + qcc_typeinfo = (void *)Qalloc(sizeof(type_t) * maxtypeinfos); + numtypeinfos = 0; + bodylessfuncs = 0; + + Mem_Set(&pr, 0, sizeof(pr)); + Mem_Set(&ret_temp, 0, sizeof(ret_temp)); + Mem_Set(pr_immediate_string, 0, sizeof(pr_immediate_string)); + + if (opt_locals_marshalling) MsgDev( D_INFO, "Locals marshalling might be buggy. Use with caution\n"); + com.strncpy( sourcefilename, name, sizeof(sourcefilename)); + + // default parms + def_ret.ofs = OFS_RETURN; + def_ret.name = "return"; + def_ret.temp = &ret_temp; + def_ret.constant = false; + def_ret.type = NULL; + ret_temp.ofs = def_ret.ofs; + ret_temp.scope = NULL; + ret_temp.size = 3; + ret_temp.next = NULL; + + for (i = 0; i < MAX_PARMS; i++) + { + def_parms[i].temp = NULL; + def_parms[i].type = NULL; + def_parms[i].ofs = OFS_PARM0 + 3*i; + def_parms[i].name = parmname[i]; + com.sprintf(parmname[i], "parm%i", i); + } +} + +void PRVM_Init( int argc, char **argv ) +{ + char dev_level[4]; + + com_argc = argc; + com_argv = argv; + + qccpool = Mem_AllocPool( "VM progs" ); + host_instance = g_Instance; + + if(FS_GetParmFromCmdLine("-dev", dev_level )) + prvm_developer = com.atoi(dev_level); + + Cmd_AddCommand("prvm_edict", PRVM_ED_PrintEdict_f, "print all data about an entity number in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_edicts", PRVM_ED_PrintEdicts_f, "set a property on an entity number in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_edictcount", PRVM_ED_Count_f, "prints number of active entities in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_profile", PRVM_Profile_f, "prints execution statistics about the most used QuakeC functions in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_fields", PRVM_Fields_f, "prints usage statistics on properties (how many entities have non-zero values) in the selected VM (server, client, menu)"); + Cmd_AddCommand("prvm_globals", PRVM_Globals_f, "prints all global variables in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_global", PRVM_Global_f, "prints value of a specified global variable in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_globalset", PRVM_GlobalSet_f, "sets value of a specified global variable in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_edictset", PRVM_ED_EdictSet_f, "changes value of a specified property of a specified entity in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("prvm_printfunction", PRVM_PrintFunction_f, "prints a disassembly (QuakeC instructions) of the specified function in the selected VM (server, client, uimenu)"); + Cmd_AddCommand("compile", PRVM_Compile_f, "compile specified VM (server, client, menu), changes will take affect after map restart"); + + // LordHavoc: optional runtime bounds checking (speed drain, but worth it for security, on by default - breaks most QCCX features (used by CRMod and others)) + prvm_boundscheck = Cvar_Get( "prvm_boundscheck", "0", 0, "enable vm internal boundschecker" ); + prvm_traceqc = Cvar_Get( "prvm_traceqc", "0", 0, "enable tracing (only for debug)" ); + prvm_statementprofiling = Cvar_Get ("prvm_statementprofiling", "0", 0, "counts how many times each QC statement has been executed" ); + prvm_maxedicts = Cvar_Get( "host_maxedicts", "2048", CVAR_SYSTEMINFO, "user limit edicts number fof server, client and renderer, absolute limit 65535" ); + + if( host_instance == HOST_NORMAL || host_instance == HOST_DEDICATED ) + { + size_t size; + byte *image; + + // FIXME: get rid of this + // dump internal copies of progs into hdd if missing + if(!FS_FileExists(va("%s/uimenu.dat", GI->vprogs_dir))) + { + image = FS_LoadInternal( "uimenu.dat", &size ); + if( size ) FS_WriteFile(va("%s/uimenu.dat", GI->vprogs_dir), image, size ); + } + } +} + +void PRVM_Shutdown( void ) +{ + Mem_FreePool( &qccpool ); +} + +void PRVM_PrepareProgs( const char *dir, const char *name ) +{ + int i; + + switch( host_instance ) + { + case HOST_QCCLIB: + FS_InitRootDir((char *)dir); + PR_InitCompile( name ); + break; + case HOST_NORMAL: + case HOST_DEDICATED: + com_argc = Cmd_Argc(); + for( i = 0; i < com_argc; i++ ) + com_argv[i] = copystring(Cmd_Argv(i)); + com.strncpy( sourcedir, name, MAX_SYSPATH ); + PR_InitCompile( name ); + prvm_state = comp_begin; + break; + default: + Sys_Break("PRVM_PrepareProgs: can't prepare progs for instance %d\n", host_instance ); + break; + } +} + +void PRVM_CompileProgs( void ) +{ + PR_BeginCompilation(); + while(PR_ContinueCompile()); + PR_FinishCompilation(); +} + +void PRVM_Frame( double time ) +{ + if( setjmp( pr_int_error )) + return; + + switch( prvm_state ) + { + case comp_begin: + PR_BeginCompilation(); + prvm_state = comp_frame; + break; + case comp_frame: + if(PR_ContinueCompile()); + else prvm_state = comp_done; + break; + case comp_done: + prvm_state = comp_inactive; + PR_FinishCompilation(); + break; + case comp_error: + prvm_state = comp_inactive; + break; + case comp_inactive: + default: return; + } +} + +void PRVM_Compile_f( void ) +{ + if( Cmd_Argc() < 2) + { + Msg( "Usage: compile > >\n"); + return; + } + if( prvm_state != comp_inactive ) + { + Msg( "Compile already in progress, please wait\n" ); + return; + } + // engine already known about vprogs and vsource directory + PRVM_PrepareProgs( NULL, Cmd_Argv( 1 )); +} + +vprogs_exp_t DLLEXPORT *CreateAPI( stdlib_api_t *input, void *unused ) +{ + com = *input; + + // generic functions + vm.api_size = sizeof(vprogs_exp_t); + + vm.Init = PRVM_Init; + vm.Free = PRVM_Shutdown; + vm.PrepareDAT = PRVM_PrepareProgs; + vm.CompileDAT = PRVM_CompileProgs; + vm.Update = PRVM_Frame; + + vm.WriteGlobals = PRVM_ED_WriteGlobals; + vm.ReadGlobals = PRVM_ED_ReadGlobals; + vm.PrintEdict = PRVM_ED_Print; + vm.WriteEdict = PRVM_ED_Write; + vm.ReadEdict = PRVM_ED_Read; + vm.AllocEdict = PRVM_ED_Alloc; + vm.FreeEdict = PRVM_ED_Free; + + vm.LoadFromFile = PRVM_ED_LoadFromFile; + vm.GetString = PRVM_GetString; + vm.SetEngineString = PRVM_SetEngineString; + vm.SetTempString = PRVM_SetTempString; + + vm.InitProg = PRVM_InitProg; + vm.SetProg = PRVM_SetProg; + vm.ProgLoaded = PRVM_ProgLoaded; + vm.LoadProgs = PRVM_LoadProgs; + vm.ResetProg = PRVM_ResetProg; + + vm.FindFunctionOffset = PRVM_ED_FindFunctionOffset; + vm.FindGlobalOffset = PRVM_ED_FindGlobalOffset; + vm.FindFieldOffset = PRVM_ED_FindFieldOffset; + vm.FindFunction = PRVM_ED_FindFunction; + vm.FindGlobal = PRVM_ED_FindGlobal; + vm.FindField = PRVM_ED_FindField; + + vm.StackTrace = PRVM_StackTrace; + vm.Warning = VM_Warning; + vm.Error = VM_Error; + vm.ExecuteProgram = PRVM_ExecuteProgram; + vm.Crash = PRVM_Crash; + + return &vm; +} \ No newline at end of file diff --git a/vprogs/pr_utils.c b/vprogs/pr_utils.c new file mode 100644 index 00000000..9b2ef58d --- /dev/null +++ b/vprogs/pr_utils.c @@ -0,0 +1,1055 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// qcc_utils.c - qcc common tools +//======================================================================= + +#include "vprogs.h" +#include "const.h" + +void Hash_InitTable(hashtable_t *table, int numbucks) +{ + table->numbuckets = numbucks; + table->bucket = (bucket_t **)Qalloc(BytesForBuckets(numbucks)); +} + +uint Hash_Key(char *name, int modulus) +{ + //FIXME: optimize. + uint key; + for (key = 0; *name; name++) + key += ((key<<3) + (key>>28) + *name); + + return (key%modulus); +} + +void *Hash_Get(hashtable_t *table, char *name) +{ + uint bucknum = Hash_Key(name, table->numbuckets); + bucket_t *buck; + + buck = table->bucket[bucknum]; + while(buck) + { + if (!STRCMP(name, buck->keystring)) + return buck->data; + + buck = buck->next; + } + + return NULL; +} + +void *Hash_GetKey(hashtable_t *table, int key) +{ + uint bucknum = key%table->numbuckets; + bucket_t *buck; + + buck = table->bucket[bucknum]; + while(buck) + { + if ((int)buck->keystring == key) + return buck->data; + + buck = buck->next; + } + + return NULL; +} + +void *Hash_GetNext(hashtable_t *table, char *name, void *old) +{ + uint bucknum = Hash_Key(name, table->numbuckets); + bucket_t *buck; + + buck = table->bucket[bucknum]; + while(buck) + { + if (!STRCMP(name, buck->keystring)) + { + if (buck->data == old)//found the old one + break; + } + buck = buck->next; + } + if (!buck) return NULL; + + buck = buck->next;//don't return old + while(buck) + { + if (!STRCMP(name, buck->keystring)) + return buck->data; + + buck = buck->next; + } + return NULL; +} + +void *Hash_Add(hashtable_t *table, char *name, void *data, bucket_t *buck) +{ + uint bucknum = Hash_Key(name, table->numbuckets); + + buck->data = data; + buck->keystring = name; + buck->next = table->bucket[bucknum]; + table->bucket[bucknum] = buck; + + return buck; +} + +void *Hash_AddKey(hashtable_t *table, int key, void *data, bucket_t *buck) +{ + uint bucknum = key%table->numbuckets; + + buck->data = data; + buck->keystring = (char*)key; + buck->next = table->bucket[bucknum]; + table->bucket[bucknum] = buck; + + return buck; +} + +void Hash_Remove(hashtable_t *table, char *name) +{ + uint bucknum = Hash_Key(name, table->numbuckets); + bucket_t *buck; + + buck = table->bucket[bucknum]; + if (!STRCMP(name, buck->keystring)) + { + table->bucket[bucknum] = buck->next; + return; + } + + while(buck->next) + { + if (!STRCMP(name, buck->next->keystring)) + { + buck->next = buck->next->next; + return; + } + + buck = buck->next; + } +} + +void Hash_RemoveData(hashtable_t *table, char *name, void *data) +{ + uint bucknum = Hash_Key(name, table->numbuckets); + bucket_t *buck; + + buck = table->bucket[bucknum]; + if (buck->data == data) + { + if (!STRCMP(name, buck->keystring)) + { + table->bucket[bucknum] = buck->next; + return; + } + } + while(buck->next) + { + if (buck->next->data == data) + { + if (!STRCMP(name, buck->next->keystring)) + { + buck->next = buck->next->next; + return; + } + } + buck = buck->next; + } +} + + +void Hash_RemoveKey(hashtable_t *table, int key) +{ + uint bucknum = key%table->numbuckets; + bucket_t *buck; + + buck = table->bucket[bucknum]; + if ((int)buck->keystring == key) + { + table->bucket[bucknum] = buck->next; + return; + } + while(buck->next) + { + if ((int)buck->next->keystring == key) + { + buck->next = buck->next->next; + return; + } + buck = buck->next; + } +} + +/* +================ +PR_decode +================ +*/ +int PR_decode(int complen, int len, int method, char *src, char **dst) +{ + int i; + char *buffer = *dst; + + switch(method) + { + case CMP_NONE: + if (complen != len) + { + MsgDev(D_WARN, "PR_decode: complen[%d] != len[%d]\n", complen, len); + return false; + } + Mem_Copy(buffer, src, len); + break; + case CMP_LZSS: + if (complen != len) + { + MsgDev(D_WARN, "PR_decode: complen[%d] != len[%d]\n", complen, len); + return false; + } + for (i = 0; i < len; i++) buffer[i] = src[i] ^ 0xA5; + break; + case CMP_ZLIB: + VFS_Unpack( src, complen, dst, len ); + break; + default: + MsgDev(D_WARN, "PR_decode: invalid method\n"); + return false; + } + return true; +} + +/* +================ +PR_encode +================ +*/ +int PR_encode(int len, int method, char *src, file_t *h) +{ + int i, pos; + vfile_t *vf; + + switch(method) + { + case CMP_NONE: + FS_Write(h, src, len); + return len; + case CMP_LZSS: + for (i = 0; i < len; i++) src[i] = src[i] ^ 0xA5; + FS_Write(h, src, len); + return len; + case CMP_ZLIB: + vf = VFS_Open( h, "wz" ); + pos = FS_Tell(h); // member ofs + VFS_Write( vf, src, len ); + VFS_Close( vf ); + return FS_Tell(h) - pos; + default: + MsgDev(D_WARN, "PR_encode: invalid method\n"); + return false; + } +} + +// CopyString returns an offset from the string heap +int PR_CopyString (char *str, bool noduplicate ) +{ + int old; + char *s; + + if (noduplicate) + { + if (!str || !*str) return 0; + for (s = strings; s < strings + strofs; s++) + if (!com.strcmp(s, str)) return s - strings; + } + + old = strofs; + com.strcpy (strings + strofs, str); + strofs += com.strlen(str)+1; + return old; +} + +/* +================ +PR_WriteSourceFiles + +include sources into progs.dat for fake decompile +if it needs +================ +*/ +bool PR_WriteSourceFiles( file_t *h, dprograms_t *progs, bool sourceaswell ) +{ + dsource_t *idf; + cachedsourcefile_t *f; + int num = 0; + int ofs; + + for (f = sourcefile, num = 0; f; f = f->next) + { + if (f->type == FT_CODE && !sourceaswell) + continue; + num++; + } + if( !num ) + { + progs->ofssources = progs->numsources = 0; + return false; + } + idf = Qalloc(sizeof(dsource_t) * num); + for( f = sourcefile, num = 0; f; f = f->next ) + { + if( f->type == FT_CODE && !sourceaswell ) + continue; + com.strncpy( idf[num].name, f->filename, 64 ); + idf[num].size = f->size; + idf[num].compression = CMP_ZLIB; + idf[num].filepos = FS_Tell( h ); + idf[num].disksize = PR_encode(f->size, idf[num].compression, f->file, h ); + num++; + } + ofs = FS_Tell( h ); + FS_Write( h, idf, sizeof(dsource_t) * num ); + sourcefile = NULL; + + // update header + progs->ofssources = ofs; + progs->numsources = num; + return true; +} + +int PR_WriteBodylessFuncs (file_t *handle) +{ + def_t *d; + int ret = 0; + + for (d = pr.def_head.next; d; d = d->next) + { + // function parms are ok + if (d->type->type == ev_function && !d->scope) + { + if (d->initialized != 1 && d->references > 0) + { + FS_Write(handle, d->name, com.strlen(d->name)+1); + ret++; + } + } + } + return ret; +} + +/* +==================== +AddBuffer + +copy string into buffer and process crc +==================== +*/ +#define NEED_CRC 1 // calcaulate crc +#define NEED_CPY 2 // copy string +#define NEED_ALL NEED_CRC|NEED_CPY + +#define ADD1(p) AddBuffer(p, &crc, file, NEED_ALL) +#define ADD2(p) AddBuffer(p, &crc, file, NEED_CPY) +#define ADD3(p) AddBuffer(p, &crc, file, NEED_CRC) + +_inline void AddBuffer(char *p, word *crc, char *file, byte flags ) +{ + char *s; + int i = com.strlen(file); + + if (i + com.strlen(p) + 1 >= PROGDEFS_MAX_SIZE) + return; + + for(s = p; *s; s++, i++) + { + if(flags & NEED_CRC) CRC_ProcessByte(crc, *s); + if(flags & NEED_CPY) file[i] = *s; + } + if(flags & NEED_CPY) file[i] = '\0'; +} + +/* +==================== +PR_WriteProgdefs + +write advanced progdefs.h into disk +==================== +*/ +word PR_WriteProgdefs( void ) +{ + char file[PROGDEFS_MAX_SIZE]; + string header_name, lwr_header; + char lwr_prefix[8], upr_prefix[8]; + char array_size[8]; // g-cont: mega name! + int f = 0, k = 1; + string path; + def_t *d; + word crc; + + CRC_Init( &crc ); + Mem_Set( file, 0, PROGDEFS_MAX_SIZE ); + FS_FileBase( progsoutname, header_name ); + com.strupr( header_name, header_name ); // as part of define name + com.strlwr( header_name, lwr_header ); // as part of head comment + + // convert progsoutname to prefix + // using special case for "server", because "se" prefix looks ugly + if(!com.stricmp( header_name, "server" )) com.strncpy( lwr_prefix, "sv", 8 ); + else com.strnlwr( header_name, lwr_prefix, 3 ); + com.strnupr( lwr_prefix, upr_prefix, 3 ); + + ADD1("//=======================================================================\n"); + ADD1("// Copyright XashXT Group 2008 ©\n"); + ADD2(va("// %s_edict.h - %s prvm edict\n", lwr_prefix, lwr_header )); + ADD1("//=======================================================================\n"); + ADD2(va("#ifndef %s_EDICT_H\n#define %s_EDICT_H\n", upr_prefix, upr_prefix )); + ADD1("\nstruct"); + ADD2(va(" %s_globalvars_s", lwr_prefix )); + ADD1(va("\n{")); + ADD2(va("\n")); + ADD1(va("\tint\tpad[%i];\n", RESERVED_OFS)); + + // write globalvars_t + for( d = pr.def_head.next; d; d = d->next ) + { + if( !com.strcmp (d->name, "end_sys_globals")) break; + if( d->ofs < RESERVED_OFS ) continue; + if( com.strchr( d->name, '[' )) continue; // skip arrays + if( d->arraysize > 1 ) com.snprintf( array_size, 8, "[%i]", d->arraysize ); + else array_size[0] = '\0'; // clear array + + switch( d->type->type ) + { + case ev_float: + ADD1(va("\tfloat\t%s%s;\n",d->name, array_size)); + break; + case ev_vector: + ADD1(va("\tvec3_t\t%s%s;\n",d->name, array_size)); + d = d->next->next->next; // skip the elements + break; + case ev_string: + ADD1(va("\tstring_t\t%s%s;\n",d->name, array_size)); + break; + case ev_function: + ADD1(va("\tfunc_t\t%s%s;\n",d->name, array_size)); + break; + case ev_entity: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + case ev_integer: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + default: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + } + } + ADD1("};\n\n"); + + // write entvars_t + ADD1("struct"); + ADD2(va(" %s_entvars_s", lwr_prefix )); + ADD1("\n{\n"); + + // write entvars_t + for( d = pr.def_head.next; d; d = d->next ) + { + if( !com.strcmp (d->name, "end_sys_fields")) break; + if( d->type->type != ev_field ) continue; + if( com.strchr( d->name, '[' )) continue; // skip arrays + if( d->arraysize > 1 ) com.snprintf( array_size, 8, "[%i]", d->arraysize ); + else array_size[0] = '\0'; // clear array + + switch( d->type->aux_type->type ) + { + case ev_float: + ADD1(va("\tfloat\t%s%s;\n",d->name, array_size )); + break; + case ev_vector: + ADD1(va("\tvec3_t\t%s%s;\n",d->name, array_size)); + d = d->next->next->next; // skip the elements + break; + case ev_string: + ADD1(va("\tstring_t\t%s%s;\n",d->name, array_size)); + break; + case ev_function: + ADD1(va("\tfunc_t\t%s%s;\n",d->name, array_size)); + break; + case ev_entity: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + case ev_integer: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + default: + ADD1(va("\tint\t%s%s;\n",d->name, array_size)); + break; + } + } + ADD1("};\n\n"); + ADD2(va("#define PROG_CRC_%s\t\t%i\n", header_name, crc )); + ADD2(va("\n#endif//%s_EDICT_H", upr_prefix )); + + if( ForcedCRC ) crc = ForcedCRC; // potentially backdoor + + if( host_instance == HOST_NORMAL || host_instance == HOST_DEDICATED ) + { + // make sure what progs file will be placed into right directory + com.snprintf( progsoutname, MAX_SYSPATH, "%s/%s.dat", GI->vprogs_dir, header_name ); + } + + switch( crc ) + { + case 8505: + PR_Message("Xash3D unmodified server.dat\n"); + if(!com.strcmp(progsoutname, "unknown.dat")) com.strcpy(progsoutname, "server.dat"); + break; + case 3720: + PR_Message("Xash3D unmodified client.dat\n"); + if(!com.strcmp(progsoutname, "unknown.dat")) com.strcpy(progsoutname, "client.dat"); + break; + case 2158: + PR_Message("Xash3D unmodified uimenu.dat\n"); + if(!com.strcmp(progsoutname, "unknown.dat")) com.strcpy(progsoutname, "uimenu.dat"); + break; + default: + PR_Message("Custom progs crc %d\n", crc ); + if(!com.strcmp(progsoutname, "unknown.dat")) com.strcpy(progsoutname, "progs.dat"); + if( host_instance != HOST_QCCLIB ) break; + com.snprintf( path, MAX_STRING, "../%s_edict.h", lwr_prefix ); + PR_Message( "writing %s\n", path ); // auto-determine + FS_WriteFile( path, file, com.strlen(file)); + break; + } + return crc; +} + +void PR_WriteDAT( void ) +{ + ddef_t *dd; + file_t *f; + dprograms_t progs; // header + char element[MAX_NAME]; + int i, crc, progsize, num_ref; + def_t *def, *comp_x, *comp_y, *comp_z; + + crc = PR_WriteProgdefs(); // write progdefs.h + + progs.flags = 0; + + if(numstatements > MAX_STATEMENTS) PR_ParseError(ERR_INTERNAL, "Too many statements - %i\n", numstatements ); + if(strofs > MAX_STRINGS) PR_ParseError(ERR_INTERNAL, "Too many strings - %i\n", strofs ); + + PR_UnmarshalLocals(); + + if(opt_compstrings) progs.flags |= COMP_STRINGS; + if(opt_compfunctions) + { + progs.flags |= COMP_FUNCTIONS; + progs.flags |= COMP_STATEMENTS; + } + if(opt_compress_other) + { + progs.flags |= COMP_DEFS; + progs.flags |= COMP_FIELDS; + progs.flags |= COMP_GLOBALS; + } + + // compression of blocks? + if( opt_writelinenums ) progs.flags |= COMP_LINENUMS; + if( opt_writetypes ) progs.flags |= COMP_TYPES; + + // part of how compilation works. This def is always present, and never used. + def = PR_GetDef(NULL, "end_sys_globals", NULL, false, 0); + if(def) def->references++; + + def = PR_GetDef(NULL, "end_sys_fields", NULL, false, 0); + if(def) def->references++; + + for (def = pr.def_head.next; def; def = def->next) + { + if (def->type->type == ev_vector || (def->type->type == ev_field && def->type->aux_type->type == ev_vector)) + { + // do the references, so we don't get loadsa not referenced VEC_HULL_MINS_x + com.sprintf(element, "%s_x", def->name); + comp_x = PR_GetDef(NULL, element, def->scope, false, 0); + com.sprintf(element, "%s_y", def->name); + comp_y = PR_GetDef(NULL, element, def->scope, false, 0); + com.sprintf(element, "%s_z", def->name); + comp_z = PR_GetDef(NULL, element, def->scope, false, 0); + + num_ref = def->references; + if (comp_x && comp_y && comp_z) + { + num_ref += comp_x->references; + num_ref += comp_y->references; + num_ref += comp_z->references; + + if (!def->references) + { + if (!comp_x->references || !comp_y->references || !comp_z->references) + num_ref = 0; // one of these vars is useless... + } + def->references = num_ref; + + if (!num_ref) num_ref = 1; + if (comp_x) comp_x->references = num_ref; + if (comp_y) comp_y->references = num_ref; + if (comp_z) comp_z->references = num_ref; + } + } + if (def->references <= 0) + { + if(def->local) PR_Warning(WARN_NOTREFERENCED, strings + def->s_file, def->s_line, "'%s' : unreferenced local variable", def->name); + if (opt_unreferenced && def->type->type != ev_field) continue; + } + if (def->type->type == ev_function) + { + if( opt_function_names && functions[G_FUNCTION(def->ofs)].first_statement < 0 ) + { + def->name = ""; + } + if( !def->timescalled ) + { + if( def->references <= 1 ) + PR_Warning(WARN_DEADCODE, strings + def->s_file, def->s_line, "%s is never directly called or referenced (spawn function or dead code)", def->name); + } + if( opt_stripfunctions && def->timescalled >= def->references - 1 ) + { + // make sure it's not copied into a different var. + // if it ever does self.think then it could be needed for saves. + // if it's only ever called explicitly, the engine doesn't need to know. + continue; + } + } + else if( def->type->type == ev_field ) + { + dd = &fields[numfielddefs]; + dd->type = def->type->aux_type->type; + dd->s_name = PR_CopyString( def->name, opt_noduplicatestrings ); + dd->ofs = G_INT(def->ofs); + numfielddefs++; + } + else if ((def->scope||def->constant) && (def->type->type != ev_string || opt_constant_names_strings)) + { + if (opt_constant_names) continue; + } + + dd = &qcc_globals[numglobaldefs]; + numglobaldefs++; + + if (opt_writetypes) dd->type = def->type-qcc_typeinfo; + else dd->type = def->type->type; + + if ( def->saved && ((!def->initialized || def->type->type == ev_function) && def->type->type != ev_field && def->scope == NULL)) + dd->type |= DEF_SAVEGLOBAL; + + if (def->shared) dd->type |= DEF_SHARED; + + if (opt_locals && (def->scope || !com.strcmp(def->name, "IMMEDIATE"))) + { + dd->s_name = 0; + } + else dd->s_name = PR_CopyString (def->name, opt_noduplicatestrings ); + dd->ofs = def->ofs; + } + if(numglobaldefs > MAX_GLOBALS) PR_ParseError(ERR_INTERNAL, "Too many globals - %i\n", numglobaldefs ); + + strofs = (strofs + 3) & ~3; + + PR_Message("Linking...\n"); + if( host_instance == HOST_QCCLIB ) + { + // don't flood into engine console + MsgDev(D_INFO, "%6i strofs (of %i)\n", strofs, MAX_STRINGS); + MsgDev(D_INFO, "%6i numstatements (of %i)\n", numstatements, MAX_STATEMENTS); + MsgDev(D_INFO, "%6i numfunctions (of %i)\n", numfunctions, MAX_FUNCTIONS); + MsgDev(D_INFO, "%6i numglobaldefs (of %i)\n", numglobaldefs, MAX_GLOBALS); + MsgDev(D_INFO, "%6i numfielddefs (%i unique) (of %i)\n", numfielddefs, pr.size_fields, MAX_FIELDS); + MsgDev(D_INFO, "%6i numpr_globals (of %i)\n", numpr_globals, MAX_REGS); + } + + f = FS_Open( progsoutname, "wb" ); + if( !f ) PR_ParseError( ERR_INTERNAL, "Couldn't open file %s", progsoutname ); + FS_Write(f, &progs, sizeof(progs)); + FS_Write(f, "\r\n\r\n", 4); + FS_Write(f, v_copyright, com.strlen(v_copyright) + 1); + FS_Write(f, "\r\n\r\n", 4); + + progs.ofs_strings = FS_Tell(f); + progs.numstrings = strofs; + + PR_WriteBlock(f, progs.ofs_strings, strings, strofs, progs.flags & COMP_STRINGS); + + progs.ofs_statements = FS_Tell(f); + progs.numstatements = numstatements; + + for (i = 0; i < numstatements; i++) + { + statements[i].op = LittleLong(statements[i].op); + statements[i].a = LittleLong(statements[i].a); + statements[i].b = LittleLong(statements[i].b); + statements[i].c = LittleLong(statements[i].c); + } + PR_WriteBlock(f, progs.ofs_statements, statements, progs.numstatements*sizeof(dstatement_t), progs.flags & COMP_STATEMENTS); + + progs.ofs_functions = FS_Tell(f); + progs.numfunctions = numfunctions; + + for (i = 0; i < numfunctions; i++) + { + functions[i].first_statement = LittleLong (functions[i].first_statement); + functions[i].parm_start = LittleLong (functions[i].parm_start); + functions[i].s_name = LittleLong (functions[i].s_name); + functions[i].s_file = LittleLong (functions[i].s_file); + functions[i].numparms = LittleLong ((functions[i].numparms > MAX_PARMS) ? MAX_PARMS : functions[i].numparms); + functions[i].locals = LittleLong (functions[i].locals); + } + + PR_WriteBlock(f, progs.ofs_functions, functions, progs.numfunctions*sizeof(dfunction_t), progs.flags & COMP_FUNCTIONS); + + progs.ofs_globaldefs = FS_Tell(f); + progs.numglobaldefs = numglobaldefs; + for (i = 0; i < numglobaldefs; i++) + { + qcc_globals[i].type = LittleLong(qcc_globals[i].type); + qcc_globals[i].ofs = LittleLong(qcc_globals[i].ofs); + qcc_globals[i].s_name = LittleLong(qcc_globals[i].s_name); + } + + PR_WriteBlock(f, progs.ofs_globaldefs, qcc_globals, progs.numglobaldefs*sizeof(ddef_t), progs.flags & COMP_DEFS); + + progs.ofs_fielddefs = FS_Tell(f); + progs.numfielddefs = numfielddefs; + + for (i = 0; i < numfielddefs; i++) + { + fields[i].type = LittleLong(fields[i].type); + fields[i].ofs = LittleLong(fields[i].ofs); + fields[i].s_name = LittleLong(fields[i].s_name); + } + + PR_WriteBlock(f, progs.ofs_fielddefs, fields, progs.numfielddefs*sizeof(ddef_t), progs.flags & COMP_FIELDS); + + progs.ofs_globals = FS_Tell(f); + progs.numglobals = numpr_globals; + + for (i = 0; (uint) i < numpr_globals; i++) ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); + PR_WriteBlock(f, progs.ofs_globals, pr_globals, numpr_globals*4, progs.flags & COMP_GLOBALS); + + if(opt_writetypes) + { + for (i = 0; i < numtypeinfos; i++) + { + if (qcc_typeinfo[i].aux_type) qcc_typeinfo[i].aux_type = (type_t*)(qcc_typeinfo[i].aux_type - qcc_typeinfo); + if (qcc_typeinfo[i].next) qcc_typeinfo[i].next = (type_t*)(qcc_typeinfo[i].next - qcc_typeinfo); + qcc_typeinfo[i].name = (char *)PR_CopyString(qcc_typeinfo[i].name, true ); + } + } + + progs.ident = VPROGSHEADER32; + progs.version = VPROGS_VERSION; + progs.ofssources = 0; + progs.ofslinenums = 0; + progs.ident = 0; + progs.ofsbodylessfuncs = 0; + progs.numbodylessfuncs = 0; + progs.ofs_types = 0; + progs.numtypes = 0; + progs.ofsbodylessfuncs = FS_Tell(f); + progs.numbodylessfuncs = PR_WriteBodylessFuncs(f); + + if( opt_writelinenums ) + { + progs.ofslinenums = FS_Tell(f); + PR_WriteBlock(f, progs.ofslinenums, statement_linenums, numstatements*sizeof(int), progs.flags & COMP_LINENUMS); + } + + if( opt_writetypes ) + { + progs.ofs_types = FS_Tell(f); + progs.numtypes = numtypeinfos; + PR_WriteBlock(f, progs.ofs_types, qcc_typeinfo, progs.numtypes*sizeof(type_t), progs.flags & COMP_TYPES); + } + + PR_WriteSourceFiles( f, &progs, opt_writesources ); + + progsize = ((FS_Tell(f)+3) & ~3); + progs.entityfields = pr.size_fields; + progs.crc = crc; + + // byte swap the header and write it out + for (i = 0; i < sizeof(progs)/4; i++)((int *)&progs)[i] = LittleLong (((int *)&progs)[i]); + FS_Seek(f, 0, SEEK_SET); + FS_Write(f, &progs, sizeof(progs)); + if( asmfile ) FS_Close(asmfile); + + if( host_instance == HOST_QCCLIB ) + MsgDev(D_INFO, "Writing %s, total size %i bytes\n", progsoutname, progsize ); + FS_Close( f ); +} + +/* +======================= +PR_UnmarshalLocals + +marshalled locals remaps all the functions to use the range MAX_REGS +onwards for the offset to thier locals. +this function remaps all the locals back into the function. +======================= +*/ +void PR_UnmarshalLocals( void ) +{ + def_t *def; + uint ofs; + uint maxo; + int i; + + ofs = numpr_globals; + maxo = ofs; + + for (def = pr.def_head.next ; def ; def = def->next) + { + if (def->ofs >= MAX_REGS) //unmap defs. + { + def->ofs = def->ofs + ofs - MAX_REGS; + if (maxo < def->ofs) + maxo = def->ofs; + } + } + + for (i = 0; i < numfunctions; i++) + { + if (functions[i].parm_start == MAX_REGS) + functions[i].parm_start = ofs; + } + + PR_RemapOffsets(0, numstatements, MAX_REGS, MAX_REGS + maxo-numpr_globals + 3, ofs); + + numpr_globals = maxo+3; + if (numpr_globals > MAX_REGS) + PR_ParseError(ERR_INTERNAL, "Too many globals are in use to unmarshal all locals"); + + if (maxo-ofs) PR_Message("Total of %i marshalled globals\n", maxo-ofs); +} + +byte *PR_LoadFile( char *filename, bool crash, int type ) +{ + int length; + cachedsourcefile_t *newfile; + string fullname; + byte *file; + char *path; + + // NOTE: in-game we can't use ../pathes, but root directory always + // ahead over ../pathes, so translate path from + // ../common/svc_user.h to source/common/svc_user.h + if( host_instance == HOST_NORMAL || host_instance == HOST_DEDICATED ) + { + if((path = com.strstr( filename, ".." ))) + { + path += 2; // skip .. + com.snprintf( fullname, MAX_STRING, "%s%s", GI->source_dir, path ); + } + else com.snprintf( fullname, MAX_STRING, "%s/%s/%s", GI->source_dir, sourcedir, filename ); + } + else com.strncpy( fullname, filename, MAX_STRING ); + + file = FS_LoadFile( fullname, &length ); + + if( !file || !length ) + { + if( crash ) PR_ParseError( ERR_INTERNAL, "Couldn't open file %s", fullname ); + else return NULL; + } + newfile = (cachedsourcefile_t*)Qalloc(sizeof(cachedsourcefile_t)); + newfile->next = sourcefile; + sourcefile = newfile; // make chain + + com.strcpy(sourcefile->filename, fullname ); + sourcefile->file = file; + sourcefile->type = type; + sourcefile->size = length; + + return sourcefile->file; +} + +bool PR_Include( char *filename ) +{ + char *newfile; + char fname[512]; + char *opr_file_p; + string_t os_file, os_file2; + int opr_source_line; + char *ocompilingfile; + includechunk_t *oldcurrentchunk; + + ocompilingfile = compilingfile; + os_file = s_file; + os_file2 = s_file2; + opr_source_line = pr_source_line; + opr_file_p = pr_file_p; + oldcurrentchunk = currentchunk; + + com.strcpy(fname, filename); + newfile = QCC_LoadFile( fname, true ); + currentchunk = NULL; + pr_file_p = newfile; + PR_CompileFile( newfile, fname ); + currentchunk = oldcurrentchunk; + + compilingfile = ocompilingfile; + s_file = os_file; + s_file2 = os_file2; + pr_source_line = opr_source_line; + pr_file_p = opr_file_p; + + return true; +} + +void PR_WriteBlock(file_t *f, fs_offset_t pos, const void *data, size_t blocksize, bool compress) +{ + vfile_t *h; + int len = 0; + + if (compress) + { + h = VFS_Open( f, "wz" ); + VFS_Write( h, data, blocksize ); // write into buffer + FS_Write(f, &len, sizeof(int)); // first four bytes it's a compressed filesize + f = VFS_Close(h); // deflate, then write into disk + len = LittleLong(FS_Tell(f) - pos); // calculate complength + FS_Seek(f, pos, SEEK_SET); // seek back to start block... + FS_Write(f, &len, sizeof(int)); // ... and write real complength value. + FS_Seek(f, 0, SEEK_END); // return + } + else FS_Write(f, data, blocksize); // just write data +} + +byte *PR_CreateProgsSRC( void ) +{ + search_t *qc = FS_Search( "*", true ); + const char *datname = "unknown.dat\n"; // outname will be set by PR_WriteProgdefs + byte *newprogs_src = NULL; + char searchmask[8][16]; + string headers[2]; // contains filename with struct description + bool have_entvars = 0; + bool have_globals = 0; + int i, k, j = 0; + + // hardcoded table! don't change! + com.strcpy( searchmask[0], "qh" ); // quakec header + com.strcpy( searchmask[1], "h" ); // c-style header + com.strcpy( searchmask[2], "qc" ); // quakec sources + com.strcpy( searchmask[3], "c" ); // c-style sources + + if(!qc) + { + PR_ParseError(ERR_INTERNAL, "Couldn't open file progs.src" ); + return NULL; + } + Mem_Set( headers, '/0', MAX_STRING * 2 ); + + for( i = 0; i < qc->numfilenames; i++ ) + { + // search by mask + for( k = 0; k < 8; k++) + { + // skip blank mask + if(!com.strlen( searchmask[k] )) continue; + if(!com.stricmp( searchmask[k], FS_FileExtension( qc->filenames[i] ))) // validate ext + { + script_t *source = Com_OpenScript( qc->filenames[i], NULL, 0 ); + token_t token; + + if( source ) + { + while( Com_ReadToken( source, SC_ALLOW_NEWLINES, &token )) + { + // parse all sources for "end_sys_globals" + if( !com.strcmp( token.string, "end_sys_globals" )) + { + com.strncpy( headers[0], qc->filenames[i], MAX_STRING ); + have_globals = true; + } + else if( !com.strcmp( token.string, "end_sys_fields" )) + { + com.strncpy( headers[1], qc->filenames[i], MAX_STRING ); + have_entvars = true; + } + if( have_globals && have_entvars ) + { + Com_CloseScript( source ); + goto buildnewlist; // end of parsing + } + } + Com_CloseScript( source ); + } + } + } + } + + // globals and locals not declared + PR_ParseError( ERR_INTERNAL, "Couldn't open file progs.src" ); + return NULL; +buildnewlist: + + newprogs_src = Qrealloc( newprogs_src, j + com.strlen( datname ) + 1 ); // outfile name + Mem_Copy( newprogs_src + j, (char *)datname, com.strlen( datname )); + j += com.strlen(datname) + 1; // null term + + // file contains "sys_globals" and possible "sys_fields" + newprogs_src = Qrealloc(newprogs_src, j + com.strlen(headers[0]) + 2); // first file + com.strncat(newprogs_src, va("%s\n", headers[0]), com.strlen(headers[0]) + 1); + j += com.strlen(headers[0]) + 2; //null term + + if(STRCMP( headers[0], headers[1] )) + { + // file contains sys_fields description + newprogs_src = Qrealloc( newprogs_src, j + com.strlen( headers[1] ) + 2 ); // second file (option) + com.strncat( newprogs_src, va("%s\n", headers[1]), com.strlen(headers[1]) + 1); + j += com.strlen(headers[1]) + 2; //null term + } + + // add headers + for( i = 0; i < qc->numfilenames; i++ ) + { + for( k = 0; k < 2; k++) + { + // skip blank mask + if(!com.strlen(searchmask[k])) continue; + if(!com.stricmp(searchmask[k], FS_FileExtension(qc->filenames[i]))) // validate ext + { + if(!com.strcmp(qc->filenames[i], headers[0]) || !com.strcmp(qc->filenames[i], headers[1])) + break; //we already have it, just skip + newprogs_src = Qrealloc( newprogs_src, j + com.strlen(qc->filenames[i]) + 2); + com.strncat(newprogs_src, va("%s\n", qc->filenames[i]), com.strlen(qc->filenames[i]) + 1); + j += com.strlen(qc->filenames[i]) + 2; + } + } + } + + // add other sources + for(i = 0; i < qc->numfilenames; i++) + { + for( k = 2; k < 8; k++) + { + // skip blank mask + if(!com.strlen(searchmask[k])) continue; + if(!com.stricmp(searchmask[k], FS_FileExtension(qc->filenames[i]))) // validate ext + { + if(!com.strcmp(qc->filenames[i], headers[0]) || !com.strcmp(qc->filenames[i], headers[1])) + break; //we already have it, just skip + newprogs_src = Qrealloc( newprogs_src, j + com.strlen(qc->filenames[i]) + 2); + com.strncat(newprogs_src, va("%s\n", qc->filenames[i]), com.strlen(qc->filenames[i]) + 1); + j += com.strlen(qc->filenames[i]) + 2; + } + } + } + Mem_Free( qc ); // free search + FS_WriteFile( "progs.src", newprogs_src, j ); + + return newprogs_src; +} \ No newline at end of file diff --git a/vprogs/vprogs.dsp b/vprogs/vprogs.dsp new file mode 100644 index 00000000..ec4cd035 --- /dev/null +++ b/vprogs/vprogs.dsp @@ -0,0 +1,153 @@ +# Microsoft Developer Studio Project File - Name="vprogs" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=vprogs - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "vprogs.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "vprogs.mak" CFG="vprogs - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "vprogs - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "vprogs - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "vprogs - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\vprogs\!release" +# PROP Intermediate_Dir "..\temp\vprogs\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "./" /I "../public" /I "../common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib /nologo /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /opt:nowin98 +# SUBTRACT LINK32 /profile +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\vprogs\!release +InputPath=\Xash3D\src_main\temp\vprogs\!release\vprogs.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\vprogs.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\vprogs.dll "D:\Xash3D\bin\vprogs.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "vprogs - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\temp\vprogs\!debug" +# PROP Intermediate_Dir "..\temp\vprogs\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 msvcrtd.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# SUBTRACT LINK32 /incremental:no /nodefaultlib +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\vprogs\!debug +InputPath=\Xash3D\src_main\temp\vprogs\!debug\vprogs.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\vprogs.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\vprogs.dll "D:\Xash3D\bin\vprogs.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "vprogs - Win32 Release" +# Name "vprogs - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\pr_comp.c +# End Source File +# Begin Source File + +SOURCE=.\pr_edict.c +# End Source File +# Begin Source File + +SOURCE=.\pr_exec.c +# End Source File +# Begin Source File + +SOURCE=.\pr_lex.c +# End Source File +# Begin Source File + +SOURCE=.\pr_main.c +# End Source File +# Begin Source File + +SOURCE=.\pr_utils.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\pr_local.h +# End Source File +# Begin Source File + +SOURCE=.\vprogs.h +# End Source File +# End Group +# End Target +# End Project diff --git a/vprogs/vprogs.h b/vprogs/vprogs.h new file mode 100644 index 00000000..ba762ac8 --- /dev/null +++ b/vprogs/vprogs.h @@ -0,0 +1,467 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// vprogs.h - virtual machine export +//======================================================================= +#ifndef VPROGS_H +#define VPROGS_H + +#include +#include "launch_api.h" +#include "qfiles_ref.h" +#include "vprogs_api.h" +#include "pr_local.h" + +extern stdlib_api_t com; +extern vprogs_exp_t vm; + +extern cvar_t *prvm_traceqc; +extern cvar_t *prvm_boundscheck; +extern cvar_t *prvm_statementprofiling; +extern int prvm_developer; + +#define Host_Error com.error +#define PRVM_MAX_STACK_DEPTH 1024 +#define PRVM_LOCALSTACK_SIZE 16384 +#define PRVM_MAX_OPENFILES 256 +#define PRVM_MAX_OPENSEARCHES 128 + +enum op_state +{ + OP_DONE, // 0 + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, // (vec3_t) * (float) + OP_MUL_VF, // (float) * (vec3_t) + OP_DIV_F, + OP_ADD_F, + OP_ADD_V, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, // 10 + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_FNC, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_FNC, + + OP_LE, // = (float) <= (float); + OP_GE, // = (float) >= (float); + OP_LT, // = (float) < (float); + OP_GT, // = (float) > (float); + + OP_LOAD_F, + OP_LOAD_V, + OP_LOAD_S, + OP_LOAD_ENT, + OP_LOAD_FLD, + OP_LOAD_FNC, + + OP_ADDRESS, // 30 + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_FLD, + OP_STORE_FNC, + + OP_STOREP_F, + OP_STOREP_V, + OP_STOREP_S, + OP_STOREP_ENT, // 40 + OP_STOREP_FLD, + OP_STOREP_FNC, + + OP_RETURN, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + OP_NOT_FNC, + OP_NOT_BITI, + OP_NOT_BITF, + OP_IF, + OP_IFNOT, // 50 + OP_CALL0, + OP_CALL1, + OP_CALL2, + OP_CALL3, + OP_CALL4, + OP_CALL5, + OP_CALL6, + OP_CALL7, + OP_CALL8, + OP_CALL9, + OP_STATE, // 60 + OP_GOTO, + OP_AND, + OP_OR, + + OP_BITAND, // = (float) & (float); // of cource converting into integer in real code + OP_BITOR, + + OP_MULSTORE_F, // f *= f + OP_MULSTORE_V, // v *= f + OP_MULSTOREP_F, // e.f *= f + OP_MULSTOREP_V, // e.v *= f + + OP_DIVSTORE_F, // f /= f + OP_DIVSTOREP_F, // e.f /= f + + OP_ADDSTORE_F, // f += f + OP_ADDSTORE_V, // v += v + OP_ADDSTOREP_F, // e.f += f + OP_ADDSTOREP_V, // e.v += v + + OP_SUBSTORE_F, // f -= f + OP_SUBSTORE_V, // v -= v + OP_SUBSTOREP_F, // e.f -= f + OP_SUBSTOREP_V, // e.v -= v + + OP_FETCH_GBL_F, // 80 + OP_FETCH_GBL_V, + OP_FETCH_GBL_S, + OP_FETCH_GBL_E, + OP_FETCH_G_FNC, + + OP_CSTATE, + OP_CWSTATE, + + OP_THINKTIME, + + OP_BITSET, // b (+) a + OP_BITSETP, // .b (+) a + OP_BITCLR, // b (-) a + OP_BITCLRP, // .b (-) a + + OP_RAND0, + OP_RAND1, + OP_RAND2, + OP_RANDV0, + OP_RANDV1, + OP_RANDV2, + + OP_SWITCH_F, // switches + OP_SWITCH_V, + OP_SWITCH_S, // 100 + OP_SWITCH_E, + OP_SWITCH_FNC, + + OP_CASE, + OP_CASERANGE, + + OP_STORE_I, + OP_STORE_IF, + OP_STORE_FI, + + OP_ADD_I, + OP_ADD_FI, + OP_ADD_IF, // 110 + + OP_SUB_I, + OP_SUB_FI, + OP_SUB_IF, + + OP_CONV_ITOF, + OP_CONV_FTOI, + OP_CP_ITOF, + OP_CP_FTOI, + OP_LOAD_I, + OP_STOREP_I, + OP_STOREP_IF, // 120 + OP_STOREP_FI, + + OP_BITAND_I, + OP_BITOR_I, + + OP_MUL_I, + OP_DIV_I, + OP_EQ_I, + OP_NE_I, + + OP_IFNOTS, + OP_IFS, + + OP_NOT_I, // 130 + + OP_DIV_VF, + + OP_POWER_I, + OP_RSHIFT_I, + OP_LSHIFT_I, + OP_RSHIFT_F, + OP_LSHIFT_F, + OP_MODULO_I, // (int)c = (int)a % (int)b + OP_MODULO_F, // (float)c = fmod( (float)a, (float)b ) + + OP_GLOBAL_ADD, + OP_POINTER_ADD, // pointer to 32 bit (remember to *3 for vectors) + + OP_LOADA_F, + OP_LOADA_V, + OP_LOADA_S, + OP_LOADA_ENT, // 140 + OP_LOADA_FLD, + OP_LOADA_FNC, + OP_LOADA_I, + + OP_STORE_P, + OP_LOAD_P, + + OP_LOADP_F, + OP_LOADP_V, + OP_LOADP_S, + OP_LOADP_ENT, + OP_LOADP_FLD, // 150 + OP_LOADP_FNC, + OP_LOADP_I, + + OP_LE_I, // (int)c = (int)a <= (int)b; + OP_GE_I, // (int)c = (int)a >= (int)b; + OP_LT_I, // (int)c = (int)a < (int)b; + OP_GT_I, // (int)c = (int)a > (int)b; + + OP_LE_IF, // (float)c = (int)a <= (float)b; + OP_GE_IF, // (float)c = (int)a >= (float)b; + OP_LT_IF, // (float)c = (int)a < (float)b; + OP_GT_IF, // (float)c = (int)a > (float)b; + + OP_LE_FI, // (float)c = (float)a <= (int)b; + OP_GE_FI, // (float)c = (float)a >= (int)b; + OP_LT_FI, // (float)c = (float)a < (int)b; + OP_GT_FI, // (float)c = (float)a > (int)b; + + OP_EQ_IF, + OP_EQ_FI, + + OP_ADD_SF, // (char*)c = (char*)a + (float)b + OP_SUB_S, // (float)c = (char*)a - (char*)b + OP_STOREP_C, // (float)c = *(char*)b = (float)a + OP_LOADP_C, // (float)c = *(char*) // 170 + + OP_MUL_IF, + OP_MUL_FI, + OP_MUL_VI, + OP_MUL_IV, + OP_DIV_IF, + OP_DIV_FI, + OP_BITAND_IF, + OP_BITOR_IF, + OP_BITAND_FI, + OP_BITOR_FI, // 180 + OP_AND_I, + OP_OR_I, + OP_AND_IF, + OP_OR_IF, + OP_AND_FI, + OP_OR_FI, + OP_NE_IF, + OP_NE_FI, + + OP_GSTOREP_I, + OP_GSTOREP_F, // 190 + OP_GSTOREP_ENT, + OP_GSTOREP_FLD, // integers + OP_GSTOREP_S, + OP_GSTOREP_FNC, // pointers + OP_GSTOREP_V, + OP_GADDRESS, + OP_GLOAD_I, + OP_GLOAD_F, + OP_GLOAD_FLD, + OP_GLOAD_ENT, // 200 + OP_GLOAD_S, + OP_GLOAD_FNC, + OP_GLOAD_V, + OP_BOUNDCHECK, + + OP_STOREP_P, // back to ones that we do use. + OP_PUSH, + OP_POP, + + OP_NUMOPS, +}; + +#define PRVM_EDICTFIELDVALUE(ed, fieldoffset) (fieldoffset ? (prvm_eval_t *)((byte *)ed->progs.vp + fieldoffset) : NULL) +#define PRVM_GLOBALFIELDVALUE(fieldoffset) (fieldoffset ? (prvm_eval_t *)((byte *)vm.prog->globals.gp + fieldoffset) : NULL) + +extern prvm_prog_t prvm_prog_list[PRVM_MAXPROGS]; + +//============================================================================ +// prvm_cmds part + +extern prvm_builtin_t vm_sv_builtins[]; +extern prvm_builtin_t vm_cl_builtins[]; +extern prvm_builtin_t vm_m_builtins[]; + +extern const int vm_sv_numbuiltins; +extern const int vm_cl_numbuiltins; +extern const int vm_m_numbuiltins; + +extern char *vm_sv_extensions; +extern char *vm_cl_extensions; +extern char *vm_m_extensions; + +void VM_SV_Cmd_Init(void); +void VM_SV_Cmd_Reset(void); + +void VM_CL_Cmd_Init(void); +void VM_CL_Cmd_Reset(void); + +void VM_M_Cmd_Init(void); +void VM_M_Cmd_Reset(void); + +void VM_Cmd_Init(void); +void VM_Cmd_Reset(void); +//============================================================================ + +// console commands +void PRVM_ED_PrintEdicts_f( void ); +void PRVM_ED_PrintEdict_f( void ); +void PRVM_ED_EdictSet_f( void ); +void PRVM_GlobalSet_f( void ); +void PRVM_Decompile_f( void ); +void PRVM_ED_Count_f( void ); +void PRVM_Globals_f( void ); +void PRVM_Compile_f( void ); +void PRVM_Global_f( void ); +void PRVM_Fields_f( void ); + +void PRVM_ExecuteProgram( func_t fnum, const char *name, const char *file, const int line ); + +#define PRVM_Alloc(buffersize) _PRVM_Alloc(buffersize, __FILE__, __LINE__) +#define PRVM_Free(buffer) _PRVM_Free(buffer, __FILE__, __LINE__) +#define PRVM_FreeAll() _PRVM_FreeAll(__FILE__, __LINE__) +void *_PRVM_Alloc (size_t buffersize, const char *filename, int fileline); +void _PRVM_Free (void *buffer, const char *filename, int fileline); +void _PRVM_FreeAll (const char *filename, int fileline); + +void PRVM_Profile (int maxfunctions, int mininstructions); +void PRVM_Profile_f (void); +void PRVM_PrintFunction_f (void); + +void PRVM_PrintState(void); +void PRVM_CrashAll (void); +void PRVM_Crash (void); + +int PRVM_ED_FindFieldOffset(const char *field); +int PRVM_ED_FindGlobalOffset(const char *global); +ddef_t *PRVM_ED_FindField (const char *name); +ddef_t *PRVM_ED_FindGlobal (const char *name); +mfunction_t *PRVM_ED_FindFunction (const char *name); +func_t PRVM_ED_FindFunctionOffset(const char *function); + +void PRVM_MEM_IncreaseEdicts(void); + +pr_edict_t *PRVM_ED_Alloc (void); +void PRVM_ED_Free (pr_edict_t *ed); +void PRVM_ED_ClearEdict (pr_edict_t *e); +ddef_t *PRVM_ED_GlobalAtOfs( int ofs ); +void PRVM_PrintFunctionStatements (const char *name); +void PRVM_ED_Print(pr_edict_t *ed); +void PRVM_ED_Write( pr_edict_t *ed, void *buffer, void *ptr, setpair_t callback ); +void PRVM_ED_Read( int s_table, int ednum, dkeyvalue_t *fields, int numpairs ); +const char *PRVM_ED_ParseEdict (const char *data, pr_edict_t *ent); +char *PRVM_ValueString( etype_t type, prvm_eval_t *val ); +void PRVM_ED_WriteGlobals( void *buffer, void *ptr, setpair_t callback ); +void PRVM_ED_ReadGlobals( int s_table, dkeyvalue_t *globals, int numpairs ); + +void PRVM_ED_LoadFromFile( const char *data ); + +pr_edict_t *PRVM_EDICT_NUM_ERROR(int n, char *filename, int fileline); +#define PRVM_EDICT_NUM(n) (((n) >= 0 && (n) < vm.prog->max_edicts) ? vm.prog->edicts + (n) : PRVM_EDICT_NUM_ERROR(n, __FILE__, __LINE__)) +#define PRVM_EDICT_NUM_UNSIGNED(n) (((n) < vm.prog->max_edicts) ? vm.prog->edicts + (n) : PRVM_EDICT_NUM_ERROR(n, __FILE__, __LINE__)) +#define PRVM_NUM_FOR_EDICT(e) ((int)((pr_edict_t *)(e) - vm.prog->edicts)) +#define PRVM_NEXT_EDICT(e) ((e) + 1) +#define PRVM_EDICT_TO_PROG(e) (PRVM_NUM_FOR_EDICT(e)) +#define PRVM_PROG_TO_EDICT(n) (PRVM_EDICT_NUM(n)) +#define PRVM_ED_POINTER(p) (prvm_eval_t *)((byte *)vm.prog->edictsfields + p->_int) +#define PRVM_EM_POINTER(p) (prvm_eval_t *)((byte *)vm.prog->edictsfields + (p)) +#define PRVM_EV_POINTER(p) (prvm_eval_t *)(((byte *)vm.prog->edicts) + p->_int) // this is correct ??? +#define PRVM_CHECK_PTR(p, size) if(prvm_boundscheck->value && (p->_int < 0 || p->_int + size > vm.prog->edictareasize))\ +{\ +vm.prog->xfunction->profile += (st - startst);\ +vm.prog->xstatement = st - vm.prog->statements;\ +PRVM_ERROR("%s attempted to write to an out of bounds edict (%i)", PRVM_NAME, p->_int);\ +return;\ +} +#define PRVM_CHECK_INFINITE() if (++jumpcount == 10000000)\ +{\ +vm.prog->xstatement = st - vm.prog->statements;\ +PRVM_Profile(1<<30, 1000000);\ +PRVM_ERROR("runaway loop counter hit limit of %d jumps\n", jumpcount, PRVM_NAME);\ +} +//============================================================================ + +// get arguments from progs +#define PRVM_G_FLOAT(o) (vm.prog->globals.gp[o]) +#define PRVM_G_INT(o) (*(int *)&vm.prog->globals.gp[o]) +#define PRVM_G_EDICT(o) (PRVM_PROG_TO_EDICT(*(uint *)&vm.prog->globals.gp[o])) +#define PRVM_G_EDICTNUM(o) PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(o)) +#define PRVM_G_VECTOR(o) (&vm.prog->globals.gp[o]) +#define PRVM_G_STRING(o) (PRVM_GetString(*(string_t *)&vm.prog->globals.gp[o])) +#define PRVM_G_FUNCTION(o) (*(func_t *)&vm.prog->globals.gp[o]) + +// FIXME: make these go away? +#define PRVM_E_FLOAT(e,o) (((float*)e->progs.vp)[o]) +#define PRVM_E_INT(e,o) (((int*)e->progs.vp)[o]) +//#define PRVM_E_VECTOR(e,o) (&((float*)e->progs.vp)[o]) +#define PRVM_E_STRING(e,o) (PRVM_GetString(*(string_t *)&((float*)e->progs.vp)[o])) + +extern int prvm_type_size[8]; // for consistency : I think a goal of this sub-project is to +// make the new vm mostly independent from the old one, thus if it's necessary, I copy everything + +void PRVM_Init_Exec(void); +void PRVM_StackTrace (void); +void PRVM_ED_PrintEdicts_f (void); +void PRVM_ED_PrintNum (int ent); + +const char *PRVM_GetString(int num); +int PRVM_SetEngineString(const char *s); +int PRVM_SetTempString( const char *s ); +int PRVM_AllocString(size_t bufferlength, char **pointer); +void PRVM_FreeString(int num); + +//============================================================================ +#define PRVM_NAME (vm.prog->name ? vm.prog->name : "unnamed.dat") + +// helper macro to make function pointer calls easier +#define PRVM_GCALL(func) if(vm.prog->func) vm.prog->func +#define PRVM_ERROR if(vm.prog) vm.prog->error_cmd + +// other prog handling functions +bool PRVM_SetProgFromString(const char *str); +void PRVM_SetProg(int prognr); + +/* +Initializing a vm: +Call InitProg with the num +Set up the fields marked with [INIT] in the prog struct +Load a program with LoadProgs +*/ +void PRVM_InitProg(int prognr); + +// LoadProgs expects to be called right after InitProg +void PRVM_LoadProgs( const char *filename ); +void PRVM_ResetProg( void ); + +bool PRVM_ProgLoaded(int prognr); + +int PRVM_GetProgNr(void); + +void VM_Warning(const char *fmt, ...); +void VM_Error(const char *fmt, ...); + +// TODO: fill in the params +//void PRVM_Create(); + +//============================================================================ +// nice helper macros + +#endif//VPROGS_H \ No newline at end of file diff --git a/vsound/libogg.lib b/vsound/libogg.lib new file mode 100644 index 0000000000000000000000000000000000000000..6802905dd695aea242d5c80d3e86e56d60aa21b5 GIT binary patch literal 27254 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 ) + { + samples += sfx->samples; + Msg("%8i ", sfx->samples); + Msg("%5i ", sfx->rate); + + switch( sfx->format ) + { + case AL_FORMAT_STEREO16: Msg("STEREO16 "); break; + case AL_FORMAT_STEREO8: Msg("STEREO8 "); break; + case AL_FORMAT_MONO16: Msg("MONO16 "); break; + case AL_FORMAT_MONO8: Msg("MONO8 "); break; + default: Msg("???????? "); break; + } + + if( sfx->name[0] == '#' ) Msg("%s", &sfx->name[1]); + else Msg("sound/%s", sfx->name); + Msg( "\n" ); + } + else + { + if( sfx->name[0] == '*' ) Msg(" placeholder %s\n", sfx->name); + else Msg(" not loaded %s\n", sfx->name); + } + } + + Msg("-------------------------------------------\n"); + Msg("%i total samples\n", samples ); + Msg("%i total sounds\n", s_numSfx ); + Msg("\n"); +} + +/* + ======================================================================= + + WAV LOADING + + ======================================================================= +*/ +static byte *iff_data; +static byte *iff_dataPtr; +static byte *iff_end; +static byte *iff_lastChunk; +static int iff_chunkLen; + +/* +================= +S_GetLittleShort +================= +*/ +static short S_GetLittleShort( void ) +{ + short val = 0; + + val += (*(iff_dataPtr+0) << 0); + val += (*(iff_dataPtr+1) << 8); + iff_dataPtr += 2; + + return val; +} + +/* +================= +S_GetLittleLong +================= +*/ +static int S_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; +} + +/* +================= +S_FindNextChunk +================= +*/ +static void S_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 = S_GetLittleLong(); + if (iff_chunkLen < 0) + { + iff_dataPtr = NULL; + return; + } + + iff_dataPtr -= 8; + iff_lastChunk = iff_dataPtr + 8 + ((iff_chunkLen + 1) & ~1); + if(!com.strncmp( iff_dataPtr, name, 4 )) + return; + } +} + +/* +================= +S_FindChunk +================= +*/ +static void S_FindChunk( const char *name ) +{ + iff_lastChunk = iff_data; + S_FindNextChunk( name ); +} + +/* +================= +S_LoadWAV +================= +*/ +static bool S_LoadWAV( const char *name, byte **wav, wavinfo_t *info ) +{ + byte *buffer, *out; + int length, samples; + + buffer = FS_LoadFile( name, &length ); + if( !buffer ) return false; + + iff_data = buffer; + iff_end = buffer + length; + + // dind "RIFF" chunk + S_FindChunk( "RIFF" ); + if(!(iff_dataPtr && !com.strncmp(iff_dataPtr+8, "WAVE", 4))) + { + MsgDev( D_WARN, "S_LoadWAV: missing 'RIFF/WAVE' chunks (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + // get "fmt " chunk + iff_data = iff_dataPtr + 12; + S_FindChunk("fmt "); + if( !iff_dataPtr ) + { + MsgDev( D_WARN, "S_LoadWAV: missing 'fmt ' chunk (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + iff_dataPtr += 8; + if( S_GetLittleShort() != 1 ) + { + MsgDev( D_WARN, "S_LoadWAV: microsoft PCM format only (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + info->channels = S_GetLittleShort(); + if( info->channels != 1 ) + { + MsgDev( D_WARN, "S_LoadWAV: only mono WAV files supported (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + info->rate = S_GetLittleLong(); + iff_dataPtr += 4+2; + + info->width = S_GetLittleShort() / 8; + if( info->width != 1 && info->width != 2 ) + { + MsgDev( D_WARN, "S_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + // get cue chunk + S_FindChunk( "cue " ); + if( iff_dataPtr ) + { + iff_dataPtr += 32; + info->loopstart = S_GetLittleLong(); + S_FindNextChunk( "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker + if( iff_dataPtr ) + { + if( !com.strncmp((const char *)iff_dataPtr + 28, "mark", 4 )) + { + // this is not a proper parse, but it works with CoolEdit... + iff_dataPtr += 24; + info->samples = info->loopstart + S_GetLittleLong(); // samples in loop + } + } + } + else + { + info->loopstart = -1; + info->samples = 0; + } + + // find data chunk + S_FindChunk( "data" ); + if( !iff_dataPtr ) + { + MsgDev( D_WARN, "S_LoadWAV: missing 'data' chunk (%s)\n", name ); + Mem_Free( buffer ); + return false; + } + + iff_dataPtr += 4; + samples = S_GetLittleLong() / info->width; + + if( info->samples ) + { + if( samples < info->samples ) + { + MsgDev( D_ERROR, "S_LoadWAV: %s has a bad loop length\n", name ); + Mem_Free( buffer ); + return false; + } + } + else info->samples = samples; + + if( info->samples <= 0 ) + { + MsgDev( D_WARN, "S_LoadWAV: file with %i samples (%s)\n", info->samples, name ); + Mem_Free( buffer ); + return false; + } + + // Load the data + *wav = out = Z_Malloc( info->samples * info->width ); + Mem_Copy( out, buffer + (iff_dataPtr - buffer), info->samples * info->width ); + Mem_Free( buffer ); + + return true; +} + +/* + ======================================================================= + + OGG LOADING + + ======================================================================= +*/ +typedef struct +{ + byte *buffer; + ogg_int64_t ind; + ogg_int64_t buffsize; +} ov_decode_t; + +static size_t ovc_read( void *ptr, size_t size, size_t nb, void *datasource ) +{ + ov_decode_t *sound = (ov_decode_t *)datasource; + size_t remain, length; + + remain = sound->buffsize - sound->ind; + length = size * nb; + if( remain < length ) length = remain - remain % size; + + Mem_Copy( ptr, sound->buffer + sound->ind, length ); + sound->ind += length; + + return length / size; +} + +static int ovc_seek ( void *datasource, ogg_int64_t offset, int whence ) +{ + ov_decode_t *sound = (ov_decode_t*)datasource; + + switch( whence ) + { + case SEEK_SET: + break; + case SEEK_CUR: + offset += sound->ind; + break; + case SEEK_END: + offset += sound->buffsize; + break; + default: + return -1; + } + if( offset < 0 || offset > sound->buffsize ) + return -1; + + sound->ind = offset; + return 0; +} + +static int ovc_close( void *datasource ) +{ + return 0; +} + +static long ovc_tell (void *datasource) +{ + return ((ov_decode_t*)datasource)->ind; +} + +/* +================= +S_LoadOGG +================= +*/ +static bool S_LoadOGG( const char *name, byte **wav, wavinfo_t *info ) +{ + vorbisfile_t vf; + vorbis_info_t *vi; + vorbis_comment_t *vc; + fs_offset_t filesize; + ov_decode_t ov_decode; + ogg_int64_t length, done = 0; + ov_callbacks_t ov_callbacks = { ovc_read, ovc_seek, ovc_close, ovc_tell }; + byte *data, *buffer; + const char *comm; + int dummy; + long ret; + + // load the file + data = FS_LoadFile( name, &filesize ); + if( !data ) return false; + + // Open it with the VorbisFile API + ov_decode.buffer = data; + ov_decode.ind = 0; + ov_decode.buffsize = filesize; + if( ov_open_callbacks( &ov_decode, &vf, NULL, 0, ov_callbacks ) < 0 ) + { + MsgDev( D_ERROR, "S_LoadOGG: couldn't open ogg stream %s\n", name ); + Mem_Free( data ); + return false; + } + + // get the stream information + vi = ov_info( &vf, -1 ); + if( vi->channels != 1 ) + { + MsgDev( D_ERROR, "S_LoadOGG: only mono OGG files supported (%s)\n", name ); + ov_clear( &vf ); + Mem_Free( data ); + return false; + } + + info->channels = vi->channels; + info->rate = vi->rate; + info->width = 2; // always 16-bit PCM + info->loopstart = -1; + length = ov_pcm_total( &vf, -1 ) * vi->channels * 2; // 16 bits => "* 2" + if( !length ) + { + // bad ogg file + MsgDev( D_ERROR, "S_LoadOGG: (%s) is probably corrupted\n", name ); + ov_clear( &vf ); + Mem_Free( data ); + return false; + } + buffer = (byte *)Z_Malloc( length ); + + // decompress ogg into pcm wav format + while((ret = ov_read( &vf, &buffer[done], (int)(length - done), big_endian, 2, 1, &dummy )) > 0) + done += ret; + info->samples = done / ( vi->channels * 2 ); + vc = ov_comment( &vf, -1 ); + + if( vc ) + { + comm = vorbis_comment_query( vc, "LOOP_START", 0 ); + if( comm ) + { + //FXIME: implement + Msg("ogg 'cue' %d\n", com.atoi(comm) ); + //info->loopstart = bound( 0, com.atoi(comm), info->samples ); + } + } + + // close file + ov_clear( &vf ); + Mem_Free( data ); + *wav = buffer; // load the data + + return true; +} + +/* +================= +S_UploadSound +================= +*/ +static void S_UploadSound( byte *data, int width, int channels, sfx_t *sfx ) +{ + int size; + + // calculate buffer size + size = sfx->samples * width * channels; + + // Set buffer format + if( width == 2 ) + { + if( channels == 2 ) sfx->format = AL_FORMAT_STEREO16; + else sfx->format = AL_FORMAT_MONO16; + } + else + { + if( channels == 2 ) sfx->format = AL_FORMAT_STEREO8; + else sfx->format = AL_FORMAT_MONO8; + } + + // upload the sound + palGenBuffers( 1, &sfx->bufferNum ); + palBufferData( sfx->bufferNum, sfx->format, data, size, sfx->rate ); + S_CheckForErrors(); +} + +/* +================= +S_CreateDefaultSound +================= +*/ +static void S_CreateDefaultSound( byte **wav, wavinfo_t *info ) +{ + byte *out; + int i; + + info->rate = 22050; + info->width = 2; + info->channels = 1; + info->samples = 11025; + + *wav = out = Z_Malloc( info->samples * info->width ); + + if( s_check_errors->integer ) + { + // create 1 kHz tone as default sound + for( i = 0; i < info->samples; i++ ) + ((short *)out)[i] = sin(i * 0.1f) * 20000; + } + else + { + // create silent sound + for( i = 0; i < info->samples; i++ ) + ((short *)out)[i] = i; + } +} + +/* +================= +S_LoadSound +================= +*/ +loadformat_t load_formats[] = +{ + {"sound/%s.%s", "ogg", S_LoadOGG}, + {"sound/%s.%s", "wav", S_LoadWAV}, + {"%s.%s", "ogg", S_LoadOGG}, + {"%s.%s", "wav", S_LoadWAV}, + {NULL, NULL} +}; + +bool S_LoadSound( sfx_t *sfx ) +{ + byte *data; + wavinfo_t info; + const char *ext; + string loadname, path; + loadformat_t *format; + bool anyformat; + + if( !sfx ) return false; + if( sfx->name[0] == '*' ) return false; + if( sfx->loaded ) return true; // see if still in memory + + // load it from disk + ext = FS_FileExtension( sfx->name ); + anyformat = !com.stricmp( ext, "" ) ? true : false; + + com.strncpy( loadname, sfx->name, sizeof( loadname )); + FS_StripExtension( loadname ); // remove extension if needed + Mem_Set( &info, 0, sizeof( info )); + + // developer warning + if( !anyformat ) MsgDev(D_NOTE, "Note: %s will be loading only with ext .%s\n", loadname, ext ); + + // now try all the formats in the selected list + for( format = load_formats; format->formatstring; format++ ) + { + if( anyformat || !com.stricmp( ext, format->ext )) + { + com.sprintf( path, format->formatstring, loadname, format->ext ); + if( format->loadfunc( path, &data, &info )) + goto snd_loaded; + } + } + + sfx->default_sound = true; + MsgDev(D_WARN, "FS_LoadSound: couldn't load %s\n", sfx->name ); + S_CreateDefaultSound( &data, &info ); + info.loopstart = -1; + +snd_loaded: + // load it in + sfx->loopstart = info.loopstart; + sfx->samples = info.samples; + sfx->rate = info.rate; + S_UploadSound( data, info.width, info.channels, sfx ); + sfx->loaded = true; + Mem_Free( data ); + + return true; +} + +// ======================================================================= +// Load a sound +// ======================================================================= +/* +================= +S_FindSound +================= +*/ +sfx_t *S_FindSound( const char *name ) +{ + sfx_t *sfx; + int i; + + if( !name || !name[0] ) return NULL; + if( com.strlen(name) >= MAX_STRING ) + { + MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", name ); + return NULL; + } + + for( i = 0; i < s_numSfx; i++ ) + { + sfx = &s_knownSfx[i]; + if( !sfx->name[0] ) continue; + if( !com.strcmp( name, sfx->name )) + { + // prolonge registration + sfx->registration_sequence = 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]; + Mem_Set( sfx, 0, sizeof(*sfx)); + com.strncpy( sfx->name, name, MAX_STRING ); + sfx->registration_sequence = s_registration_sequence; + + return sfx; +} + +/* +===================== +S_BeginRegistration +===================== +*/ +void S_BeginRegistration( void ) +{ + s_registration_sequence++; + s_registering = true; +} + +/* +===================== +S_EndRegistration +===================== +*/ +void S_EndRegistration( void ) +{ + sfx_t *sfx; + int i; + + // 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->registration_sequence != s_registration_sequence ) + { + // don't need this sound + palDeleteBuffers( 1, &sfx->bufferNum ); + Mem_Set( sfx, 0, sizeof( sfx_t )); // free spot + } + } + + // 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( !al_state.initialized ) + return -1; + + sfx = S_FindSound( name ); + if( !sfx ) return -1; + + sfx->registration_sequence = s_registration_sequence; + if( !s_registering ) S_LoadSound( sfx ); + + return sfx - s_knownSfx; +} + +sfx_t *S_GetSfxByHandle( sound_t handle ) +{ + 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; + + // stop all sounds + S_StopAllSounds(); + + // free all sounds + for (i = 0; i < s_numSfx; i++) + { + sfx = &s_knownSfx[i]; + if( !sfx->loaded ) continue; + palDeleteBuffers(1, &sfx->bufferNum); + } + + Mem_Set( s_knownSfx, 0, sizeof(s_knownSfx)); + s_numSfx = 0; +} \ No newline at end of file diff --git a/vsound/s_main.c b/vsound/s_main.c new file mode 100644 index 00000000..dc33907a --- /dev/null +++ b/vsound/s_main.c @@ -0,0 +1,819 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// s_main.c - sound engine +//======================================================================= + +#include "sound.h" +#include "const.h" +#include "trace_def.h" + +#define MAX_PLAYSOUNDS 256 +#define MAX_CHANNELS 64 + +static playSound_t s_playSounds[MAX_PLAYSOUNDS]; +static playSound_t s_freePlaySounds; +static playSound_t s_pendingPlaySounds; +static channel_t s_channels[MAX_CHANNELS]; +static listener_t s_listener; + +const guid_t DSPROPSETID_EAX20_ListenerProperties = {0x306a6a8, 0xb224, 0x11d2, {0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22}}; +const guid_t DSPROPSETID_EAX20_BufferProperties = {0x306a6a7, 0xb224, 0x11d2, {0x99, 0xe5, 0x0, 0x0, 0xe8, 0xd8, 0xc7, 0x22}}; + +cvar_t *host_sound; +cvar_t *s_alDevice; +cvar_t *s_soundfx; +cvar_t *s_check_errors; +cvar_t *s_volume; // master volume +cvar_t *s_musicvolume; // background track volume +cvar_t *s_pause; +cvar_t *s_minDistance; +cvar_t *s_maxDistance; +cvar_t *s_rolloffFactor; +cvar_t *s_dopplerFactor; +cvar_t *s_dopplerVelocity; + +/* +================= +S_CheckForErrors +================= +*/ +bool S_CheckForErrors( void ) +{ + int err; + char *str; + + if( !s_check_errors->integer ) + return false; + if((err = palGetError()) == AL_NO_ERROR) + return false; + + switch( err ) + { + case AL_INVALID_NAME: + str = "AL_INVALID_NAME"; + break; + case AL_INVALID_ENUM: + str = "AL_INVALID_ENUM"; + break; + case AL_INVALID_VALUE: + str = "AL_INVALID_VALUE"; + break; + case AL_INVALID_OPERATION: + str = "AL_INVALID_OPERATION"; + break; + case AL_OUT_OF_MEMORY: + str = "AL_OUT_OF_MEMORY"; + break; + default: + str = "UNKNOWN ERROR"; + break; + } + + if( al_state.active ) + Host_Error( "S_CheckForErrors: %s", str ); + else MsgDev( D_ERROR, "S_CheckForErrors: %s", str ); + + return true; +} + +/* +================= +S_AllocChannels +================= +*/ +static void S_AllocChannels( void ) +{ + channel_t *ch; + int i; + + for( i = 0, ch = s_channels; i < MAX_CHANNELS; i++, ch++) + { + palGenSources( 1, &ch->sourceNum ); + if( palGetError() != AL_NO_ERROR ) + break; + al_state.num_channels++; + } +} + +/* +================= +S_FreeChannels +================= +*/ +static void S_FreeChannels( void ) +{ + channel_t *ch; + int i; + + for( i = 0, ch = s_channels; i < al_state.num_channels; i++, ch++ ) + { + palDeleteSources(1, &ch->sourceNum); + Mem_Set(ch, 0, sizeof(*ch)); + } + al_state.num_channels = 0; +} + +/* +================= +S_ChannelState +================= +*/ +static int S_ChannelState( channel_t *ch ) +{ + int state; + + palGetSourcei( ch->sourceNum, AL_SOURCE_STATE, &state ); + return state; +} + +/* +================= +S_StopChannel +================= +*/ +static void S_StopChannel( channel_t *ch ) +{ + ch->sfx = NULL; + + palSourceStop( ch->sourceNum ); + palSourcei( ch->sourceNum, AL_BUFFER, 0 ); +} + +/* +================= +S_PlayChannel +================= +*/ +static void S_PlayChannel( channel_t *ch, sfx_t *sfx ) +{ + ch->sfx = sfx; + + palSourcei( ch->sourceNum, AL_BUFFER, sfx->bufferNum ); + palSourcei( ch->sourceNum, AL_LOOPING, ch->loopsound ); + palSourcei( ch->sourceNum, AL_SOURCE_RELATIVE, false ); + palSourcei( ch->sourceNum, AL_SAMPLE_OFFSET, 0 ); + + if( ch->loopstart >= 0 ) + { + // kill any looping sounds + if( s_pause->integer ) + { + palSourceStop( ch->sourceNum ); + return; + } + if( ch->state == CHAN_FIRSTPLAY ) ch->state = CHAN_LOOPED; + else if( ch->state == CHAN_LOOPED ) palSourcei( ch->sourceNum, AL_SAMPLE_OFFSET, sfx->loopstart ); + } + palSourcePlay( ch->sourceNum ); +} + +/* +================= +S_SpatializeChannel +================= +*/ +static void S_SpatializeChannel( channel_t *ch ) +{ + vec3_t position, velocity; + + // update position and velocity + if( ch->entnum == al_state.clientnum || !ch->distanceMult ) + { + palSourcefv(ch->sourceNum, AL_POSITION, s_listener.position); + palSourcefv(ch->sourceNum, AL_VELOCITY, s_listener.velocity); + } + else + { + if( ch->fixedPosition ) + { + palSource3f(ch->sourceNum, AL_POSITION, ch->position[1], ch->position[2], -ch->position[0]); + palSource3f(ch->sourceNum, AL_VELOCITY, 0, 0, 0); + } + else + { + if( ch->loopsound ) si.GetSoundSpatialization( ch->loopnum, position, velocity ); + else si.GetSoundSpatialization( ch->entnum, position, velocity ); + + palSource3f( ch->sourceNum, AL_POSITION, position[1], position[2], -position[0] ); + palSource3f( ch->sourceNum, AL_VELOCITY, velocity[1], velocity[2], -velocity[0] ); + } + } + + // update min/max distance + if( ch->distanceMult ) + palSourcef( ch->sourceNum, AL_REFERENCE_DISTANCE, s_minDistance->value * ch->distanceMult ); + else palSourcef( ch->sourceNum, AL_REFERENCE_DISTANCE, s_maxDistance->value ); + + palSourcef( ch->sourceNum, AL_MAX_DISTANCE, s_maxDistance->value ); + + // update volume and rolloff factor + palSourcef( ch->sourceNum, AL_GAIN, ch->volume ); + palSourcef( ch->sourceNum, AL_PITCH, ch->pitch ); + palSourcef( ch->sourceNum, AL_ROLLOFF_FACTOR, s_rolloffFactor->value ); +} + +/* +================= +S_PickChannel + +Tries to find a free channel, or tries to replace an active channel +================= +*/ +channel_t *S_PickChannel( int entnum, int channel ) +{ + channel_t *ch; + int i; + int firstToDie = -1; + float oldestTime = Sys_DoubleTime(); + + if( entnum < 0 || channel < 0 ) return NULL; // invalid channel or entnum + + for( i = 0, ch = s_channels; i < al_state.num_channels; i++, ch++ ) + { + // don't let game sounds override streaming sounds + if( ch->streaming ) continue; + + // check if this channel is active + if( channel == CHAN_AUTO && !ch->sfx ) + { + // free channel + firstToDie = i; + break; + } + + // channel 0 never overrides + if( channel != CHAN_AUTO && (ch->entnum == entnum && ch->entchannel == channel)) + { + // always override sound from same entity + firstToDie = i; + break; + } + + // don't let monster sounds override player sounds + if( entnum != al_state.clientnum && ch->entnum == al_state.clientnum ) + continue; + + // replace the oldest sound + if( ch->startTime < oldestTime ) + { + oldestTime = ch->startTime; + firstToDie = i; + } + } + + if( firstToDie == -1 ) return NULL; + ch = &s_channels[firstToDie]; + + ch->entnum = entnum; + ch->entchannel = channel; + ch->startTime = Sys_DoubleTime(); + ch->state = CHAN_NORMAL; // remove any loop sound + ch->loopsound = false; // clear loopstate + ch->loopstart = -1; + + // make sure this channel is stopped + S_StopChannel( ch ); + + return ch; +} + +/* +================= +S_AddLoopingSounds + +Entities with a sound field will generate looping sounds that are +automatically started and stopped as the entities are sent to the +client +================= +*/ +bool S_AddLoopingSound( int entnum, sound_t handle, float volume, float attn ) +{ + channel_t *ch; + sfx_t *sfx = NULL; + int i; + + if(!al_state.initialized ) + return false; + sfx = S_GetSfxByHandle( handle ); + + // default looped sound it's terrible :) + if( !sfx || !sfx->loaded || sfx->default_sound ) + return false; + + // if this entity is already playing the same sound effect on an + // active channel, then simply update it + for( i = 0, ch = s_channels; i < al_state.num_channels; i++, ch++ ) + { + if( ch->sfx != sfx ) continue; + if( !ch->loopsound ) continue; + if( ch->loopnum != entnum ) continue; + if( ch->loopframe + 1 != al_state.framecount ) + continue; + + ch->loopframe = al_state.framecount; + break; + } + if( i != al_state.num_channels ) + return false; + + // otherwise pick a channel and start the sound effect + ch = S_PickChannel( 0, 0 ); + if( !ch ) + { + MsgDev( D_ERROR, "dropped sound \"sound/%s\"\n", sfx->name ); + return false; + } + + ch->loopsound = true; + ch->loopnum = entnum; + ch->loopframe = al_state.framecount; + ch->fixedPosition = false; + ch->volume = 1.0f; + ch->pitch = 1.0f; + ch->distanceMult = 1.0f / ATTN_STATIC; + + S_SpatializeChannel( ch ); + S_PlayChannel( ch, sfx ); + return true; +} + +/* +================= +S_AllocPlaySound +================= +*/ +static playSound_t *S_AllocPlaySound( void ) +{ + playSound_t *ps; + + ps = s_freePlaySounds.next; + if( ps == &s_freePlaySounds ) + return NULL; // No free playSounds + + ps->prev->next = ps->next; + ps->next->prev = ps->prev; + + return ps; +} + +/* +================= +S_FreePlaySound +================= +*/ +static void S_FreePlaySound( playSound_t *ps ) +{ + ps->prev->next = ps->next; + ps->next->prev = ps->prev; + + // add to free list + ps->next = s_freePlaySounds.next; + s_freePlaySounds.next->prev = ps; + ps->prev = &s_freePlaySounds; + s_freePlaySounds.next = ps; +} + +/* + ================= +S_IssuePlaySounds + +Take all the pending playSounds and begin playing them. +This is never called directly by S_Start*, but only by the update loop. +================= +*/ +static void S_IssuePlaySounds( void ) +{ + playSound_t *ps; + channel_t *ch; + + while( 1 ) + { + ps = s_pendingPlaySounds.next; + if(ps == &s_pendingPlaySounds) + break; // no more pending playSounds + if( ps->beginTime > Sys_DoubleTime()) + break; // No more pending playSounds this frame + // pick a channel and start the sound effect + ch = S_PickChannel( ps->entnum, ps->entchannel ); + if(!ch) + { + if( ps->sfx->name[0] == '#' ) MsgDev(D_ERROR, "dropped sound %s\n", &ps->sfx->name[1] ); + else MsgDev( D_ERROR, "dropped sound \"sound/%s\"\n", ps->sfx->name ); + S_FreePlaySound( ps ); + continue; + } + + // check for looping sounds with "cue " marker + if( ps->use_loop && ps->sfx->loopstart >= 0 ) + { + // jump to loopstart at next playing + ch->state = CHAN_FIRSTPLAY; + ch->loopstart = ps->sfx->loopstart; + } + + ch->fixedPosition = ps->fixedPosition; + VectorCopy( ps->position, ch->position ); + ch->volume = ps->volume; + ch->pitch = ps->pitch; + + if( ps->attenuation != ATTN_NONE ) ch->distanceMult = 1.0 / ps->attenuation; + else ch->distanceMult = 0.0; + + S_SpatializeChannel( ch ); + S_PlayChannel( ch, ps->sfx ); + + // free the playSound + S_FreePlaySound( ps ); + } +} + +/* +================= +S_StartSound + +Validates the parms and queues the sound up. +if origin is NULL, the sound will be dynamically sourced from the entity. +entchannel 0 will never override a playing sound. +================= +*/ +void S_StartSound( const vec3_t pos, int entnum, int channel, sound_t handle, float vol, float attn, float pitch, bool use_loop ) +{ + playSound_t *ps, *sort; + sfx_t *sfx = NULL; + + if(!al_state.initialized ) + return; + sfx = S_GetSfxByHandle( handle ); + if( !sfx ) return; + + // Make sure the sound is loaded + if( !S_LoadSound( sfx )) + return; + + // Allocate a playSound + ps = S_AllocPlaySound(); + if( !ps ) + { + if( sfx->name[0] == '#' ) MsgDev( D_ERROR, "dropped sound %s\n", &sfx->name[1] ); + else MsgDev( D_ERROR, "dropped sound \"sound/%s\"\n", sfx->name ); + return; + } + + ps->sfx = sfx; + ps->entnum = entnum; + ps->entchannel = channel; + ps->use_loop = use_loop; + + if( pos ) + { + ps->fixedPosition = true; + VectorCopy( pos, ps->position ); + } + else ps->fixedPosition = false; + + ps->volume = vol; + ps->pitch = pitch / PITCH_NORM; + ps->attenuation = attn; + ps->beginTime = Sys_DoubleTime(); + + // sort into the pending playSounds list + for( sort = s_pendingPlaySounds.next; sort != &s_pendingPlaySounds && sort->beginTime < ps->beginTime; sort = sort->next ); + + ps->next = sort; + ps->prev = sort->prev; + ps->next->prev = ps; + ps->prev->next = ps; +} + +/* +================= +S_StartLocalSound + +menu sound +================= +*/ +bool S_StartLocalSound( const char *name, float volume, float pitch, const float *origin ) +{ + sound_t sfxHandle; + + if( !al_state.initialized ) + return false; + + sfxHandle = S_RegisterSound( name ); + S_StartSound( origin, al_state.clientnum, CHAN_AUTO, sfxHandle, volume, ATTN_NONE, pitch, false ); + return true; +} + +/* +================= +S_StopAllSounds +================= +*/ +void S_StopAllSounds( void ) +{ + channel_t *ch; + int i; + + if(!al_state.initialized) + return; + + // Clear all the playSounds + Mem_Set( s_playSounds, 0, sizeof(s_playSounds)); + + s_freePlaySounds.next = s_freePlaySounds.prev = &s_freePlaySounds; + s_pendingPlaySounds.next = s_pendingPlaySounds.prev = &s_pendingPlaySounds; + + for( i = 0; i < MAX_PLAYSOUNDS; i++ ) + { + s_playSounds[i].prev = &s_freePlaySounds; + s_playSounds[i].next = s_freePlaySounds.next; + s_playSounds[i].prev->next = &s_playSounds[i]; + s_playSounds[i].next->prev = &s_playSounds[i]; + } + + // Stop all the channels + for( i = 0, ch = s_channels; i < al_state.num_channels; i++, ch++ ) + { + if( !ch->sfx ) continue; + S_StopChannel( ch ); + } + + S_StopStreaming(); // stop streaming channel + S_StopBackgroundTrack(); // stop background track + al_state.framecount = 0; // reset frame count +} + +/* +================= +S_AddEnvironmentEffects + +process all effects here +================= +*/ +void S_AddEnvironmentEffects( const vec3_t position ) +{ + uint eaxEnv; + + if( !al_config.allow_3DMode ) return; + + // if eax is enabled, apply listener environmental effects + if( si.PointContents((float *)position ) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) + eaxEnv = EAX_ENVIRONMENT_UNDERWATER; + else eaxEnv = EAX_ENVIRONMENT_GENERIC; + al_config.Set3DMode(&DSPROPSETID_EAX20_ListenerProperties, DSPROPERTY_EAXLISTENER_ENVIRONMENT|DSPROPERTY_EAXLISTENER_DEFERRED, 0, &eaxEnv, sizeof(eaxEnv)); +} + +/* +================= +S_Update + +Called once each time through the main loop +================= +*/ +void S_Update( int clientnum, const vec3_t position, const vec3_t velocity, const vec3_t at, const vec3_t up ) +{ + channel_t *ch; + int i; + + if(!al_state.initialized ) return; + //if( s_pause->integer ) return; + + // bump frame count + al_state.framecount++; + al_state.clientnum = clientnum; + + // set up listener + VectorSet( s_listener.position, position[1], position[2], -position[0]); + VectorSet( s_listener.velocity, velocity[1], velocity[2], -velocity[0]); + + // set listener orientation matrix + s_listener.orientation[0] = at[1]; + s_listener.orientation[1] = -at[2]; + s_listener.orientation[2] = -at[0]; + s_listener.orientation[3] = up[1]; + s_listener.orientation[4] = -up[2]; + s_listener.orientation[5] = -up[0]; + + palListenerfv(AL_POSITION, s_listener.position); + palListenerfv(AL_VELOCITY, s_listener.velocity); + palListenerfv(AL_ORIENTATION, s_listener.orientation); + palListenerf(AL_GAIN, (al_state.active) ? s_volume->value : 0.0f ); + + // Set state + palDistanceModel( AL_INVERSE_DISTANCE_CLAMPED ); + + palDopplerFactor(s_dopplerFactor->value); + palDopplerVelocity(s_dopplerVelocity->value); + + S_AddEnvironmentEffects( position ); + + // Stream background track + S_StreamBackgroundTrack(); + + // Add looping sounds + si.AddLoopingSounds(); + + // Issue playSounds + S_IssuePlaySounds(); + + // update spatialization for all sounds + for( i = 0, ch = s_channels; i < al_state.num_channels; i++, ch++ ) + { + if( !ch->sfx ) continue; // not active + + // check for stop + if( ch->loopsound ) + { + if( ch->loopframe != al_state.framecount ) + { + S_StopChannel( ch ); + continue; + } + } + else if( ch->loopstart >= 0 ) + { + if( S_ChannelState( ch ) == AL_STOPPED ) + { + S_PlayChannel( ch, ch->sfx ); + } + } + else if( S_ChannelState( ch ) == AL_STOPPED ) + { + S_StopChannel( ch ); + continue; + } + + // respatialize channel + S_SpatializeChannel( ch ); + } + + // check for errors + S_CheckForErrors(); +} + +/* +================= +S_Activate + +Called when the main window gains or loses focus. +The window may have been destroyed and recreated between a deactivate +and an activate. +================= +*/ +void S_Activate( bool active ) +{ + if(!al_state.initialized ) + return; + + al_state.active = active; + if( active ) palListenerf( AL_GAIN, s_volume->value ); + else palListenerf( AL_GAIN, 0.0 ); +} + +/* +================= +S_Play_f +================= +*/ +void S_PlaySound_f( void ) +{ + if( Cmd_Argc() == 1 ) + { + Msg( "Usage: playsound \n" ); + return; + } + S_StartLocalSound( Cmd_Argv( 1 ), 1.0f, PITCH_NORM, NULL ); +} + +/* +================= +S_Music_f +================= +*/ +void S_Music_f( void ) +{ + int c = Cmd_Argc(); + + if( c == 2 ) S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(1) ); + else if( c == 3 ) S_StartBackgroundTrack( Cmd_Argv(1), Cmd_Argv(2) ); + else Msg( "Usage: music [loopfile]\n" ); +} + +/* +================= +S_StopSound_f +================= +*/ +void S_StopSound_f( void ) +{ + S_StopAllSounds(); +} + +/* +================= +S_SoundInfo_f +================= +*/ +void S_SoundInfo_f( void ) +{ + if(!al_state.initialized ) + { + Msg("Sound system not started\n"); + return; + } + + Msg("\n"); + Msg("AL_VENDOR: %s\n", al_config.vendor_string); + Msg("AL_RENDERER: %s\n", al_config.renderer_string); + Msg("AL_VERSION: %s\n", al_config.version_string); + Msg("AL_EXTENSIONS: %s\n", al_config.extensions_string); + Msg("\n"); + Msg("DEVICE: %s\n", s_alDevice->string ); + Msg("CHANNELS: %i\n", al_state.num_channels); + Msg("3D sound: %s\n", (al_config.allow_3DMode) ? "enabled" : "disabled" ); + Msg("\n"); +} + +/* +================= + S_Init +================= +*/ +bool S_Init( void *hInst ) +{ + int num_mono_src, num_stereo_src; + + host_sound = Cvar_Get("host_sound", "1", CVAR_SYSTEMINFO, "enable sound system" ); + s_alDevice = Cvar_Get("s_device", "Generic Software", CVAR_LATCH|CVAR_ARCHIVE, "OpenAL current device name" ); + s_soundfx = Cvar_Get("s_soundfx", "1", CVAR_LATCH|CVAR_ARCHIVE, "allow OpenAl extensions" ); + s_check_errors = Cvar_Get("s_check_errors", "1", CVAR_ARCHIVE, "ignore audio engine errors" ); + s_volume = Cvar_Get("s_volume", "1.0", CVAR_ARCHIVE, "sound volume" ); + s_musicvolume = Cvar_Get("s_musicvolume", "1.0", CVAR_ARCHIVE, "background music volume" ); + s_minDistance = Cvar_Get("s_mindistance", "240.0", CVAR_ARCHIVE, "3d sound min distance" ); + s_maxDistance = Cvar_Get("s_maxdistance", "8192.0", CVAR_ARCHIVE, "3d sound max distance" ); + s_rolloffFactor = Cvar_Get("s_rollofffactor", "1.0", CVAR_ARCHIVE, "3d sound rolloff factor" ); + s_dopplerFactor = Cvar_Get("s_dopplerfactor", "1.0", CVAR_ARCHIVE, "cutoff doppler effect value" ); + s_dopplerVelocity = Cvar_Get("s_dopplervelocity", "10976.0", CVAR_ARCHIVE, "doppler effect maxvelocity" ); + s_pause = Cvar_Get( "paused", "0", 0, "sound engine pause" ); + + Cmd_AddCommand( "playsound", S_PlaySound_f, "playing a specified sound file" ); + Cmd_AddCommand( "music", S_Music_f, "starting a background track" ); + Cmd_AddCommand( "s_stop", S_StopSound_f, "stop all sounds" ); + Cmd_AddCommand( "s_info", S_SoundInfo_f, "print sound system information" ); + Cmd_AddCommand( "soundlist", S_SoundList_f, "display loaded sounds" ); + + if( !host_sound->integer ) + { + MsgDev( D_INFO, "Audio: disabled\n" ); + return false; + } + + if(!S_Init_OpenAL()) + { + MsgDev( D_INFO, "S_Init: sound system can't initialized\n" ); + return false; + } + + palcGetIntegerv( al_state.hDevice, ALC_MONO_SOURCES, sizeof(int), &num_mono_src ); + palcGetIntegerv( al_state.hDevice, ALC_STEREO_SOURCES, sizeof(int), &num_stereo_src ); + MsgDev( D_INFO, "mono sources %d, stereo %d\n", num_mono_src, num_stereo_src ); + + sndpool = Mem_AllocPool( "Sound Zone" ); + al_state.initialized = true; + + S_AllocChannels(); + S_StopAllSounds(); + + // initialize error catched + if(S_CheckForErrors()) + return false; + + al_state.active = true; // enabled + + return true; +} + +/* +================= +S_Shutdown +================= +*/ +void S_Shutdown( void ) +{ + + Cmd_RemoveCommand( "playsound" ); + Cmd_RemoveCommand( "music" ); + Cmd_RemoveCommand( "s_stop" ); + Cmd_RemoveCommand( "s_info" ); + Cmd_RemoveCommand( "soundlist" ); + + if( !al_state.initialized ) + return; + + S_FreeSounds(); + S_FreeChannels(); + + Mem_FreePool( &sndpool ); + S_Free_OpenAL(); + al_state.initialized = false; +} \ No newline at end of file diff --git a/vsound/s_openal.c b/vsound/s_openal.c new file mode 100644 index 00000000..8532428c --- /dev/null +++ b/vsound/s_openal.c @@ -0,0 +1,399 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// s_openal.c - openal32.dll handler +//======================================================================= + +#include "sound.h" + +static dllfunc_t openal_funcs[] = +{ + {"alcOpenDevice", (void **) &palcOpenDevice }, + {"alcCloseDevice", (void **) &palcCloseDevice }, + {"alcCreateContext", (void **) &palcCreateContext }, + {"alcDestroyContext", (void **) &palcDestroyContext }, + {"alcMakeContextCurrent", (void **) &palcMakeContextCurrent }, + {"alcProcessContext", (void **) &palcProcessContext }, + {"alcSuspendContext", (void **) &palcSuspendContext }, + {"alcGetCurrentContext", (void **) &palcGetCurrentContext }, + {"alcGetContextsDevice", (void **) &palcGetContextsDevice }, + {"alcGetString", (void **) &palcGetString }, + {"alcGetIntegerv", (void **) &palcGetIntegerv }, + {"alcGetError", (void **) &palcGetError }, + {"alcIsExtensionPresent", (void **) &palcIsExtensionPresent }, + {"alcGetProcAddress", (void **) &palcGetProcAddress }, + {"alcGetEnumValue", (void **) &palcGetEnumValue }, + + {"alBufferData", (void **) &palBufferData }, + {"alDeleteBuffers", (void **) &palDeleteBuffers }, + {"alDeleteSources", (void **) &palDeleteSources }, + {"alDisable", (void **) &palDisable }, + {"alDistanceModel", (void **) &palDistanceModel }, + {"alDopplerFactor", (void **) &palDopplerFactor }, + {"alDopplerVelocity", (void **) &palDopplerVelocity }, + {"alEnable", (void **) &palEnable }, + {"alGenBuffers", (void **) &palGenBuffers }, + {"alGenSources", (void **) &palGenSources }, + {"alGetBoolean", (void **) &palGetBoolean }, + {"alGetBooleanv", (void **) &palGetBooleanv }, + {"alGetBufferf", (void **) &palGetBufferf }, + {"alGetBufferi", (void **) &palGetBufferi }, + {"alGetDouble", (void **) &palGetDouble }, + {"alGetDoublev", (void **) &palGetDoublev }, + {"alGetEnumValue", (void **) &palGetEnumValue }, + {"alGetError", (void **) &palGetError }, + {"alGetFloat", (void **) &palGetFloat }, + {"alGetFloatv", (void **) &palGetFloatv }, + {"alGetInteger", (void **) &palGetInteger }, + {"alGetIntegerv", (void **) &palGetIntegerv }, + {"alGetListener3f", (void **) &palGetListener3f }, + {"alGetListenerf", (void **) &palGetListenerf }, + {"alGetListenerfv", (void **) &palGetListenerfv }, + {"alGetListeneri", (void **) &palGetListeneri }, + {"alGetProcAddress", (void **) &palGetProcAddress }, + {"alGetSource3f", (void **) &palGetSource3f }, + {"alGetSourcef", (void **) &palGetSourcef }, + {"alGetSourcefv", (void **) &palGetSourcefv }, + {"alGetSourcei", (void **) &palGetSourcei }, + {"alGetString", (void **) &palGetString }, + {"alIsBuffer", (void **) &palIsBuffer }, + {"alIsEnabled", (void **) &palIsEnabled }, + {"alIsExtensionPresent", (void **) &palIsExtensionPresent }, + {"alIsSource", (void **) &palIsSource }, + {"alListener3f", (void **) &palListener3f }, + {"alListenerf", (void **) &palListenerf }, + {"alListenerfv", (void **) &palListenerfv }, + {"alListeneri", (void **) &palListeneri }, + {"alSource3f", (void **) &palSource3f }, + {"alSourcef", (void **) &palSourcef }, + {"alSourcefv", (void **) &palSourcefv }, + {"alSourcei", (void **) &palSourcei }, + {"alSourcePause", (void **) &palSourcePause }, + {"alSourcePausev", (void **) &palSourcePausev }, + {"alSourcePlay", (void **) &palSourcePlay }, + {"alSourcePlayv", (void **) &palSourcePlayv }, + {"alSourceQueueBuffers", (void **) &palSourceQueueBuffers }, + {"alSourceRewind", (void **) &palSourceRewind }, + {"alSourceRewindv", (void **) &palSourceRewindv }, + {"alSourceStop", (void **) &palSourceStop }, + {"alSourceStopv", (void **) &palSourceStopv }, + {"alSourceUnqueueBuffers", (void **) &palSourceUnqueueBuffers }, + { NULL, NULL } +}; + +static dllfunc_t openal_effects[] = +{ + {"alGenEffects", (void **) &alGenEffects }, + {"alDeleteEffects", (void **) &alDeleteEffects }, + {"alIsEffect", (void **) &alIsEffect }, + {"alEffecti", (void **) &alEffecti }, + {"alEffectiv", (void **) &alEffectiv }, + {"alEffectf", (void **) &alEffectf }, + {"alEffectfv", (void **) &alEffectfv }, + {"alGetEffecti", (void **) &alGetEffecti }, + {"alGetEffectiv", (void **) &alGetEffectiv }, + {"alGetEffectf", (void **) &alGetEffectf }, + {"alGetEffectfv", (void **) &alGetEffectfv }, + {"alGenFilters", (void **) &alGenFilters }, + {"alDeleteFilters", (void **) &alDeleteFilters }, + {"alIsFilter", (void **) &alIsFilter }, + {"alFilteri", (void **) &alFilteri }, + {"alFilteriv", (void **) &alFilteriv }, + {"alFilterf", (void **) &alFilterf }, + {"alFilterfv", (void **) &alFilterfv }, + {"alGetFilteri", (void **) &alGetFilteri }, + {"alGetFilteriv", (void **) &alGetFilteriv }, + {"alGetFilterf", (void **) &alGetFilterf }, + {"alGetFilterfv", (void **) &alGetFilterfv }, + {"alGenAuxiliaryEffectSlots", (void **) &alGenAuxiliaryEffectSlots }, + {"alDeleteAuxiliaryEffectSlots", (void **) &alDeleteAuxiliaryEffectSlots }, + {"alIsAuxiliaryEffectSlot", (void **) &alIsAuxiliaryEffectSlot }, + {"alAuxiliaryEffectSloti", (void **) &alAuxiliaryEffectSloti }, + {"alAuxiliaryEffectSlotiv", (void **) &alAuxiliaryEffectSlotiv }, + {"alAuxiliaryEffectSlotf", (void **) &alAuxiliaryEffectSlotf }, + {"alAuxiliaryEffectSlotfv", (void **) &alAuxiliaryEffectSlotfv }, + {"alGetAuxiliaryEffectSloti", (void **) &alGetAuxiliaryEffectSloti }, + {"alGetAuxiliaryEffectSlotiv", (void **) &alGetAuxiliaryEffectSlotiv }, + {"alGetAuxiliaryEffectSlotf", (void **) &alGetAuxiliaryEffectSlotf }, + {"alGetAuxiliaryEffectSlotfv", (void **) &alGetAuxiliaryEffectSlotfv }, + { NULL, NULL } +}; + +dll_info_t openal_dll = { "openal32.dll", openal_funcs, NULL, NULL, NULL, false, 0 }; + +alconfig_t al_config; +alstate_t al_state; + +cvar_t *s_eax; + +/* +================= +S_InitDriver +================= +*/ +static bool S_InitDriver( void ) +{ + int attrlist[3] = { ALC_FREQUENCY, 44100, 0 }; + int *al_contxt = attrlist; + + if((al_state.hDevice = palcOpenDevice( s_alDevice->string )) == NULL) + { + Msg("alOpenDevice - failed\n" ); + return false; + } + if((al_state.hALC = palcCreateContext( al_state.hDevice, al_contxt )) == NULL) + goto failed; + if(!palcMakeContextCurrent(al_state.hALC)) + goto failed; + return true; + +failed: + if(al_state.hALC) + { + palcDestroyContext( al_state.hALC ); + al_state.hALC = NULL; + } + if(al_state.hDevice) + { + palcCloseDevice( al_state.hDevice ); + al_state.hDevice = NULL; + } + + // release openal at all + Sys_FreeLibrary( &openal_dll ); + Mem_Set(&al_config, 0, sizeof(alconfig_t)); + Mem_Set(&al_state, 0, sizeof(alstate_t)); + + return false; +} + +static bool S_SetupEFX( void ) +{ + const dllfunc_t *func; + + // get the function pointers + for(func = openal_effects; func && func->name != NULL; func++) + { + if (!(*func->func = palGetProcAddress( func->name ))) + { + MsgDev(D_ERROR, "S_InitEffects: %s missing or invalid function\n", func->name ); + return false; + } + } + return true; +} + +static void S_InitEffects( void ) +{ + uint uiEffects[1] = { 0 }; + + alGenEffects(1, &uiEffects[0]); + if( palGetError() == AL_NO_ERROR ) + { + MsgDev( D_NOTE, "S_InitEffects:" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_REVERB); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_revrb" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_eaxrevrb" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_CHORUS); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_chorus" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_DISTORTION); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_distortion" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_ECHO); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_echo" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_FLANGER); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_flanger" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_FREQUENCY_SHIFTER); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_frequency_shifter" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_VOCAL_MORPHER); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_vocal_morpher" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_PITCH_SHIFTER); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_pitch_shifter" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_RING_MODULATOR); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_ring_modulator" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_AUTOWAH); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_autowah" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_COMPRESSOR); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_compressor" ); + alEffecti(uiEffects[0], AL_EFFECT_TYPE, AL_EFFECT_EQUALIZER); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_equalizer" ); + MsgDev( D_NOTE, "\n" ); + } + alDeleteEffects(1, &uiEffects[0]); +} + +static void S_InitFilters( void ) +{ + uint uiFilters[1] = { 0 }; + + alGenFilters(1, &uiFilters[0]); + if( palGetError() == AL_NO_ERROR ) + { + MsgDev( D_NOTE, "S_InitFilters:" ); + alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_lowpass" ); + alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_HIGHPASS); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_highpass" ); + alFilteri(uiFilters[0], AL_FILTER_TYPE, AL_FILTER_BANDPASS); + if(!palGetError()) MsgDev( D_NOTE, " ^3al_ext_bandpass" ); + MsgDev( D_NOTE, "\n" ); + } + alDeleteFilters(1, &uiFilters[0]); +} + +/* +================= +S_InitExtensions + +grab extensions +================= +*/ +static void S_InitExtensions( void ) +{ + // set initial state + al_config.Get3DMode = NULL; + al_config.Set3DMode = NULL; + al_config.allow_3DMode = false; + + if( !s_soundfx->integer ) return; + + // check I3DL2 extension for NVidia's Sound Storm chips. I3DL2 is hidden and can be missed in extension list. + if( !com.strcmp( NVIDIA_DEVICE_NAME, al_config.deviceList[0] )) + { + I3DL2Get = (void *)palGetProcAddress("I3DL2Get"); + I3DL2Set = (void *)palGetProcAddress("I3DL2Set"); + if( I3DL2Get && I3DL2Set ) + { + al_config.allow_3DMode = true; + al_config.Get3DMode = I3DL2Get; + al_config.Set3DMode = I3DL2Set; + MsgDev( D_NOTE, "S_InitExtensions: enable I3DL2\n" ); + } + } + if( palIsExtensionPresent("EAX3.0") && !al_config.allow_3DMode ) + { + alEAXSet = (void *)palGetProcAddress( "EAXSet" ); + alEAXGet = (void *)palGetProcAddress( "EAXGet" ); + if( alEAXSet && alEAXGet ) + { + al_config.allow_3DMode = true; + al_config.Get3DMode = alEAXGet; + al_config.Set3DMode = alEAXSet; + MsgDev( D_NOTE, "S_InitExtensions: enable EAX 3.0\n" ); + } + } + if( palIsExtensionPresent("EAX2.0") && !al_config.allow_3DMode ) + { + alEAXSet = (void *)palGetProcAddress( "EAXSet" ); + alEAXGet = (void *)palGetProcAddress( "EAXGet" ); + if( alEAXSet && alEAXGet ) + { + al_config.allow_3DMode = true; + al_config.Get3DMode = alEAXGet; + al_config.Set3DMode = alEAXSet; + MsgDev( D_NOTE, "S_InitExtensions: enable EAX 2.0\n" ); + } + } + if( palIsExtensionPresent("EAX") && !al_config.allow_3DMode ) + { + alEAXSet = (void *)palGetProcAddress( "EAXSet" ); + alEAXGet = (void *)palGetProcAddress( "EAXGet" ); + if( alEAXSet && alEAXGet ) + { + al_config.allow_3DMode = true; + al_config.Get3DMode = alEAXGet; + al_config.Set3DMode = alEAXSet; + MsgDev( D_NOTE, "S_InitExtensions: enable EAX 1.0\n" ); + } + } + + if(palcIsExtensionPresent( al_state.hDevice, "ALC_EXT_EFX")) + { + + uint uiEffectSlots[MAX_EFFECTS] = { 0 }; + + if(!S_SetupEFX()) return; + + // testing effect slots + for( al_config.num_slots = 0; al_config.num_slots < MAX_EFFECTS; al_config.num_slots++ ) + { + alGenAuxiliaryEffectSlots( 1, &uiEffectSlots[al_config.num_slots] ); + if( palGetError() != AL_NO_ERROR) break; + + } + palcGetIntegerv( al_state.hDevice, ALC_MAX_AUXILIARY_SENDS, 1, &al_config.num_sends ); + + S_InitEffects(); + S_InitFilters(); + + alDeleteAuxiliaryEffectSlots( al_config.num_slots, uiEffectSlots ); + } +} + +bool S_Init_OpenAL( void ) +{ + Sys_LoadLibrary( &openal_dll ); + + if( !openal_dll.link ) + { + MsgDev( D_ERROR, "OpenAL driver not installed\n"); + return false; + } + + // Get device list + if( palcIsExtensionPresent( NULL, "ALC_ENUMERATION_EXT")) + { + // get list of devices + const char *device_enum = palcGetString( NULL, ALC_DEVICE_SPECIFIER ); + al_config.defDevice = palcGetString( NULL, ALC_DEFAULT_DEVICE_SPECIFIER ); + + while( *device_enum ) + { + // enumerate devices + com.strncpy( al_config.deviceList[al_config.device_count++], device_enum, MAX_STRING ); + while(*device_enum) device_enum++; + device_enum++; + } + } + else + { + MsgDev( D_ERROR, "can't enumerate OpenAL devices\n" ); + return false; + } + + // initialize the device, context, etc... + if(!S_InitDriver()) return false; + + // get some openal strings + al_config.vendor_string = palGetString(AL_VENDOR); + al_config.renderer_string = palGetString(AL_RENDERER); // stupid name :) + al_config.version_string = palGetString(AL_VERSION); + al_config.extensions_string = palGetString(AL_EXTENSIONS); + MsgDev( D_INFO, "Audio: %s\n", al_config.renderer_string ); + + // Initialize extensions + S_InitExtensions(); + + return true; +} + +void S_Free_OpenAL( void ) +{ + if( al_state.hALC ) + { + if( palcMakeContextCurrent ) + palcMakeContextCurrent( NULL ); + if( palcDestroyContext ) + palcDestroyContext( al_state.hALC ); + al_state.hALC = NULL; + } + if( al_state.hDevice ) + { + if( palcCloseDevice ) + palcCloseDevice( al_state.hDevice ); + al_state.hDevice = NULL; + } + + Sys_FreeLibrary( &openal_dll ); + Mem_Set(&al_config, 0, sizeof(alconfig_t)); + Mem_Set(&al_state, 0, sizeof(alstate_t)); +} \ No newline at end of file diff --git a/vsound/s_openal.h b/vsound/s_openal.h new file mode 100644 index 00000000..0942c378 --- /dev/null +++ b/vsound/s_openal.h @@ -0,0 +1,294 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// s_openal.h - openal definition +//======================================================================= + +#ifndef S_OPENAL_H +#define S_OPENAL_H + +typedef struct aldevice_s aldevice; +typedef struct alcontext_s alcontext; + +#define NVIDIA_DEVICE_NAME "NVIDIA(R) nForce(TM) Audio" +#define MAX_EFFECTS 128 + +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_EXTENSIONS 0x1006 +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 +#define ALC_FREQUENCY 0x1007 +#define ALC_MAX_AUXILIARY_SENDS 0x20003 +#define ALC_MONO_SOURCES 0x1010 +#define ALC_STEREO_SOURCES 0x1011 + +#define AL_SOURCE_RELATIVE 0x202 +#define AL_CONE_INNER_ANGLE 0x1001 +#define AL_CONE_OUTER_ANGLE 0x1002 +#define AL_PITCH 0x1003 +#define AL_POSITION 0x1004 +#define AL_DIRECTION 0x1005 +#define AL_VELOCITY 0x1006 +#define AL_LOOPING 0x1007 +#define AL_BUFFER 0x1009 +#define AL_GAIN 0x100A +#define AL_MIN_GAIN 0x100D +#define AL_MAX_GAIN 0x100E +#define AL_ORIENTATION 0x100F + +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +#define AL_REFERENCE_DISTANCE 0x1020 +#define AL_ROLLOFF_FACTOR 0x1021 +#define AL_CONE_OUTER_GAIN 0x1022 +#define AL_MAX_DISTANCE 0x1023 + +#define AL_DISTANCE_MODEL 0xD000 +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 +#define AL_LINEAR_DISTANCE 0xD003 +#define AL_LINEAR_DISTANCE_CLAMPED 0xD004 +#define AL_EXPONENT_DISTANCE 0xD005 +#define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 + +// sound format +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +// buffer queues +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +// source buffer position information +#define AL_SEC_OFFSET 0x1024 +#define AL_SAMPLE_OFFSET 0x1025 +#define AL_BYTE_OFFSET 0x1026 + +// openal errors +#define AL_NO_ERROR 0 +#define AL_INVALID_NAME 0xA001 +#define AL_INVALID_ENUM 0xA002 +#define AL_INVALID_VALUE 0xA003 +#define AL_INVALID_OPERATION 0xA004 +#define AL_OUT_OF_MEMORY 0xA005 + +// openal effects +#define AL_EFFECT_TYPE 0x8001 +#define AL_EFFECT_NULL 0x0000 +#define AL_EFFECT_REVERB 0x0001 +#define AL_EFFECT_CHORUS 0x0002 +#define AL_EFFECT_DISTORTION 0x0003 +#define AL_EFFECT_ECHO 0x0004 +#define AL_EFFECT_FLANGER 0x0005 +#define AL_EFFECT_FREQUENCY_SHIFTER 0x0006 +#define AL_EFFECT_VOCAL_MORPHER 0x0007 +#define AL_EFFECT_PITCH_SHIFTER 0x0008 +#define AL_EFFECT_RING_MODULATOR 0x0009 +#define AL_EFFECT_AUTOWAH 0x000A +#define AL_EFFECT_COMPRESSOR 0x000B +#define AL_EFFECT_EQUALIZER 0x000C +#define AL_EFFECT_EAXREVERB 0x8000 + +// openal filters +#define AL_FILTER_TYPE 0x8001 +#define AL_FILTER_NULL 0x0000 +#define AL_FILTER_LOWPASS 0x0001 +#define AL_FILTER_HIGHPASS 0x0002 +#define AL_FILTER_BANDPASS 0x0003 + +enum +{ + EAX_ENVIRONMENT_GENERIC, + EAX_ENVIRONMENT_PADDEDCELL, + EAX_ENVIRONMENT_ROOM, + EAX_ENVIRONMENT_BATHROOM, + EAX_ENVIRONMENT_LIVINGROOM, + EAX_ENVIRONMENT_STONEROOM, + EAX_ENVIRONMENT_AUDITORIUM, + EAX_ENVIRONMENT_CONCERTHALL, + EAX_ENVIRONMENT_CAVE, + EAX_ENVIRONMENT_ARENA, + EAX_ENVIRONMENT_HANGAR, + EAX_ENVIRONMENT_CARPETEDHALLWAY, + EAX_ENVIRONMENT_HALLWAY, + EAX_ENVIRONMENT_STONECORRIDOR, + EAX_ENVIRONMENT_ALLEY, + EAX_ENVIRONMENT_FOREST, + EAX_ENVIRONMENT_CITY, + EAX_ENVIRONMENT_MOUNTAINS, + EAX_ENVIRONMENT_QUARRY, + EAX_ENVIRONMENT_PLAIN, + EAX_ENVIRONMENT_PARKINGLOT, + EAX_ENVIRONMENT_SEWERPIPE, + EAX_ENVIRONMENT_UNDERWATER, + EAX_ENVIRONMENT_DRUGGED, + EAX_ENVIRONMENT_DIZZY, + EAX_ENVIRONMENT_PSYCHOTIC, + + EAX_ENVIRONMENT_COUNT +}; + +typedef enum +{ + DSPROPERTY_EAXLISTENER_NONE, + DSPROPERTY_EAXLISTENER_ALLPARAMETERS, + DSPROPERTY_EAXLISTENER_ROOM, + DSPROPERTY_EAXLISTENER_ROOMHF, + DSPROPERTY_EAXLISTENER_ROOMROLLOFFFACTOR, + DSPROPERTY_EAXLISTENER_DECAYTIME, + DSPROPERTY_EAXLISTENER_DECAYHFRATIO, + DSPROPERTY_EAXLISTENER_REFLECTIONS, + DSPROPERTY_EAXLISTENER_REFLECTIONSDELAY, + DSPROPERTY_EAXLISTENER_REVERB, + DSPROPERTY_EAXLISTENER_REVERBDELAY, + DSPROPERTY_EAXLISTENER_ENVIRONMENT, + DSPROPERTY_EAXLISTENER_ENVIRONMENTSIZE, + DSPROPERTY_EAXLISTENER_ENVIRONMENTDIFFUSION, + DSPROPERTY_EAXLISTENER_AIRABSORPTIONHF, + + DSPROPERTY_EAXLISTENER_FLAGS +} DSPROPERTY_EAX_LISTENERPROPERTY; + +// or these flags with property id +#define DSPROPERTY_EAXLISTENER_IMMEDIATE 0x00000000 // changes take effect immediately +#define DSPROPERTY_EAXLISTENER_DEFERRED 0x80000000 // changes take effect later + +// openal32.dll exports +aldevice *(_cdecl *palcOpenDevice)( const char *devicename ); +char (_cdecl *palcCloseDevice)( aldevice *device ); +alcontext *(_cdecl *palcCreateContext)( aldevice *device, const int *attrlist ); +void (_cdecl *palcDestroyContext)( alcontext *context ); +char (_cdecl *palcMakeContextCurrent)( alcontext *context ); +void (_cdecl *palcProcessContext)( alcontext *context ); +void (_cdecl *palcSuspendContext)( alcontext *context ); +alcontext *(_cdecl *palcGetCurrentContext)( void ); +aldevice *(_cdecl *palcGetContextsDevice)( alcontext *context ); +const char *(_cdecl *palcGetString )( aldevice *device, int param ); +void (_cdecl *palcGetIntegerv)( aldevice *device, int param, int size, int *dest ); +int (_cdecl *palcGetError)( aldevice *device ); +char (_cdecl *palcIsExtensionPresent)( aldevice *device, const char *extname ); +void *(_cdecl *palcGetProcAddress)( aldevice *device, const char *function ); +int (_cdecl *palcGetEnumValue)( aldevice *device, const char *enumname ); + +void (_cdecl *palBufferData)( uint bid, int format, const void* data, int size, int freq ); +void (_cdecl *palDeleteBuffers)( int n, const uint* buffers ); +void (_cdecl *palDeleteSources)( int n, const uint* sources ); +void (_cdecl *palDisable)( int capability ); +void (_cdecl *palDistanceModel)( int distanceModel ); +void (_cdecl *palDopplerFactor)( float value ); +void (_cdecl *palDopplerVelocity)( float value ); +void (_cdecl *palEnable)( int capability ); +void (_cdecl *palGenBuffers)( int n, uint* buffers ); +void (_cdecl *palGenSources)( int n, uint* sources ); +char (_cdecl *palGetBoolean)( int param ); +void (_cdecl *palGetBooleanv)( int param, char* data ); +void (_cdecl *palGetBufferf)( uint bid, int param, float* value ); +void (_cdecl *palGetBufferi)( uint bid, int param, int* value ); +double (_cdecl *palGetDouble)( int param ); +void (_cdecl *palGetDoublev)( int param, double* data ); +int (_cdecl *palGetEnumValue)( const char* ename ); +int (_cdecl *palGetError)( void ); +float (_cdecl *palGetFloat)( int param ); +void (_cdecl *palGetFloatv)( int param, float* data ); +int (_cdecl *palGetInteger)( int param ); +void (_cdecl *palGetIntegerv)( int param, int* data ); +void (_cdecl *palGetListener3f)( int param, float *value1, float *value2, float *value3 ); +void (_cdecl *palGetListenerf)( int param, float* value ); +void (_cdecl *palGetListenerfv)( int param, float* values ); +void (_cdecl *palGetListeneri)( int param, int* value ); +void *(_cdecl *palGetProcAddress)( const char* fname ); +void (_cdecl *palGetSource3f)( uint sid, int param, float* value1, float* value2, float* value3 ); +void (_cdecl *palGetSourcef)( uint sid, int param, float* value ); +void (_cdecl *palGetSourcefv)( uint sid, int param, float* values ); +void (_cdecl *palGetSourcei)( uint sid, int param, int* value ); +const char *(_cdecl *palGetString)( int param ); +char (_cdecl *palIsBuffer)( uint bid ); +char (_cdecl *palIsEnabled)( int capability ); +char (_cdecl *palIsExtensionPresent)(const char* extname ); +char (_cdecl *palIsSource)( uint sid ); +void (_cdecl *palListener3f)( int param, float value1, float value2, float value3 ); +void (_cdecl *palListenerf)( int param, float value ); +void (_cdecl *palListenerfv)( int param, const float* values ); +void (_cdecl *palListeneri)( int param, int value ); +void (_cdecl *palSource3f)( uint sid, int param, float value1, float value2, float value3 ); +void (_cdecl *palSourcef)( uint sid, int param, float value ); +void (_cdecl *palSourcefv)( uint sid, int param, const float* values ); +void (_cdecl *palSourcei)( uint sid, int param, int value); +void (_cdecl *palSourcePause)( uint sid ); +void (_cdecl *palSourcePausev)( int ns, const uint *sids ); +void (_cdecl *palSourcePlay)( uint sid ); +void (_cdecl *palSourcePlayv)( int ns, const uint *sids ); +void (_cdecl *palSourceQueueBuffers)(uint sid, int numEntries, const uint *bids ); +void (_cdecl *palSourceRewind)( uint sid ); +void (_cdecl *palSourceRewindv)( int ns, const uint *sids ); +void (_cdecl *palSourceStop)( uint sid ); +void (_cdecl *palSourceStopv)( int ns, const uint *sids ); +void (_cdecl *palSourceUnqueueBuffers)(uint sid, int numEntries, uint *bids ); + +// openal32.dll internal exports which can be get with by algetProcAddress +void (*alGenEffects)( int n, uint* effects ); +void (*alDeleteEffects)( int n, uint* effects ); +char (*alIsEffect)( uint eid ); +void (*alEffecti)( uint eid, int param, int value); +void (*alEffectiv)( uint eid, int param, int* values ); +void (*alEffectf)( uint eid, int param, float value); +void (*alEffectfv)( uint eid, int param, float* values ); +void (*alGetEffecti)( uint eid, int pname, int* value ); +void (*alGetEffectiv)( uint eid, int pname, int* values ); +void (*alGetEffectf)( uint eid, int pname, float* value ); +void (*alGetEffectfv)( uint eid, int pname, float* values ); +void (*alGenFilters)( int n, uint* filters ); +void (*alDeleteFilters)( int n, uint* filters ); +char (*alIsFilter)( uint fid ); +void (*alFilteri)( uint fid, int param, int value ); +void (*alFilteriv)( uint fid, int param, int* values ); +void (*alFilterf)( uint fid, int param, float value); +void (*alFilterfv)( uint fid, int param, float* values ); +void (*alGetFilteri)( uint fid, int pname, int* value ); +void (*alGetFilteriv)( uint fid, int pname, int* values ); +void (*alGetFilterf)( uint fid, int pname, float* value ); +void (*alGetFilterfv)( uint fid, int pname, float* values ); +void (*alGenAuxiliaryEffectSlots)( int n, uint* slots ); +void (*alDeleteAuxiliaryEffectSlots)( int n, uint* slots ); +char (*alIsAuxiliaryEffectSlot)( uint slot ); +void (*alAuxiliaryEffectSloti)( uint asid, int param, int value ); +void (*alAuxiliaryEffectSlotiv)( uint asid, int param, int* values ); +void (*alAuxiliaryEffectSlotf)( uint asid, int param, float value ); +void (*alAuxiliaryEffectSlotfv)( uint asid, int param, float* values ); +void (*alGetAuxiliaryEffectSloti)( uint asid, int pname, int* value ); +void (*alGetAuxiliaryEffectSlotiv)( uint asid, int pname, int* values ); +void (*alGetAuxiliaryEffectSlotf)( uint asid, int pname, float* value ); +void (*alGetAuxiliaryEffectSlotfv)( uint asid, int pname, float* values ); + +typedef struct guid_s +{ + dword Data1; + word Data2; + word Data3; + byte Data4[8]; +} guid_t; + +// EAX 2.0 extension +int (*alEAXSet)( const guid_t*, uint, uint, void*, uint ); +int (*alEAXGet)( const guid_t*, uint, uint, void*, uint ); + +// I3DL2 OpenAL Extension +int (*I3DL2Get)( const guid_t*, uint, uint, void*, uint ); +int (*I3DL2Set)( const guid_t*, uint, uint, void*, uint ); + +bool S_Init_OpenAL( void ); +void S_Free_OpenAL( void ); + +#endif//S_OPENAL_H \ No newline at end of file diff --git a/vsound/s_stream.c b/vsound/s_stream.c new file mode 100644 index 00000000..1d890c0a --- /dev/null +++ b/vsound/s_stream.c @@ -0,0 +1,364 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// s_stream.c - sound streaming +//======================================================================= + +#include "sound.h" +#include "s_stream.h" + +#define BUFFER_SIZE 16384 + +static bg_track_t s_bgTrack; +static channel_t *s_streamingChannel; + +/* +======================================================================= + + OGG VORBIS STREAMING + +======================================================================= +*/ +static size_t ovc_read( void *ptr, size_t size, size_t nmemb, void *datasource ) +{ + bg_track_t *track = (bg_track_t *)datasource; + + if (!size || !nmemb) + return 0; + + return FS_Read( track->file, ptr, size * nmemb ) / size; +} + +static int ovc_seek ( void *datasource, ogg_int64_t offset, int whence ) +{ + bg_track_t *track = (bg_track_t *)datasource; + + switch( whence ) + { + case SEEK_SET: + FS_Seek(track->file, (int)offset, SEEK_SET); + break; + case SEEK_CUR: + FS_Seek(track->file, (int)offset, SEEK_CUR); + break; + case SEEK_END: + FS_Seek(track->file, (int)offset, SEEK_END); + break; + default: + return -1; + } + return 0; +} + +static int ovc_close( void *datasource ) +{ + return 0; +} + +static long ovc_tell (void *datasource) +{ + bg_track_t *track = (bg_track_t *)datasource; + + return FS_Tell( track->file ); +} + +/* +================= +S_OpenBackgroundTrack +================= +*/ +static bool S_OpenBackgroundTrack (const char *name, bg_track_t *track) +{ + vorbisfile_t *vorbisFile; + vorbis_info_t *vorbisInfo; + ov_callbacks_t vorbisCallbacks = { ovc_read, ovc_seek, ovc_close, ovc_tell }; + + track->file = FS_Open( name, "rb" ); + if( !track->file ) + { + MsgDev( D_ERROR, "S_OpenBackgroundTrack: couldn't find %s\n", name ); + return false; + } + track->vorbisFile = vorbisFile = Z_Malloc(sizeof(vorbisfile_t)); + + if( ov_open_callbacks(track, vorbisFile, NULL, 0, vorbisCallbacks) < 0 ) + { + MsgDev( D_ERROR, "S_OpenBackgroundTrack: couldn't open ogg stream %s\n", name ); + return false; + } + + vorbisInfo = ov_info( vorbisFile, -1 ); + if( vorbisInfo->channels != 1 && vorbisInfo->channels != 2) + { + MsgDev( D_ERROR, "S_OpenBackgroundTrack: only mono and stereo ogg files supported %s\n", name ); + return false; + } + + track->start = ov_raw_tell( vorbisFile ); + track->rate = vorbisInfo->rate; + track->format = (vorbisInfo->channels == 2) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + + return true; +} + +/* +================= +S_CloseBackgroundTrack +================= +*/ +static void S_CloseBackgroundTrack( bg_track_t *track ) +{ + if( track->vorbisFile ) + { + ov_clear( track->vorbisFile ); + Mem_Free( track->vorbisFile ); + track->vorbisFile = NULL; + } + if( track->file ) + { + FS_Close( track->file ); + track->file = 0; + } +} + +/* +================= +S_StreamBackgroundTrack +================= +*/ +void S_StreamBackgroundTrack( void ) +{ + byte data[BUFFER_SIZE]; + int processed, queued, state; + int size, read, dummy; + uint buffer; + + if( !s_bgTrack.file || !s_musicvolume->value ) + return; + if(!s_streamingChannel) return; + + // unqueue and delete any processed buffers + palGetSourcei( s_streamingChannel->sourceNum, AL_BUFFERS_PROCESSED, &processed ); + if( processed > 0 ) + { + while( processed-- ) + { + palSourceUnqueueBuffers(s_streamingChannel->sourceNum, 1, &buffer); + palDeleteBuffers(1, &buffer); + } + } + + // make sure we always have at least 4 buffers in the queue + palGetSourcei( s_streamingChannel->sourceNum, AL_BUFFERS_QUEUED, &queued ); + while( queued < 4 ) + { + size = 0; + // stream from disk + while( size < BUFFER_SIZE ) + { + read = ov_read( s_bgTrack.vorbisFile, data + size, BUFFER_SIZE - size, big_endian, 2, 1, &dummy ); + if( read == 0 ) + { + // end of file + if(!s_bgTrack.looping) + { + // close the intro track + S_CloseBackgroundTrack( &s_bgTrack ); + + // open the loop track + if(!S_OpenBackgroundTrack(s_bgTrack.loopName, &s_bgTrack)) + { + S_StopBackgroundTrack(); + return; + } + s_bgTrack.looping = true; + } + + // restart the track, skipping over the header + ov_raw_seek(s_bgTrack.vorbisFile, (ogg_int64_t)s_bgTrack.start ); + + // try streaming again + read = ov_read( s_bgTrack.vorbisFile, data + size, BUFFER_SIZE - size, 0, 2, 1, &dummy ); + } + if( read <= 0 ) + { + // an error occurred + S_StopBackgroundTrack(); + return; + } + size += read; + } + + // upload and queue the new buffer + palGenBuffers( 1, &buffer ); + palBufferData( buffer, s_bgTrack.format, data, size, s_bgTrack.rate ); + palSourceQueueBuffers( s_streamingChannel->sourceNum, 1, &buffer ); + queued++; + } + + // update volume + palSourcef( s_streamingChannel->sourceNum, AL_GAIN, s_musicvolume->value ); + + // if not playing, then do so + palGetSourcei( s_streamingChannel->sourceNum, AL_SOURCE_STATE, &state ); + if( state != AL_PLAYING ) palSourcePlay(s_streamingChannel->sourceNum); +} + +/* +================= +S_StartBackgroundTrack +================= +*/ +void S_StartBackgroundTrack( const char *introTrack, const char *loopTrack ) +{ + if( !al_state.initialized ) return; + + // stop any playing tracks + S_StopBackgroundTrack(); + + // Start it up + com.snprintf( s_bgTrack.introName, sizeof(s_bgTrack.introName), "media/%s.ogg", introTrack); + com.snprintf( s_bgTrack.loopName, sizeof(s_bgTrack.loopName), "media/%s.ogg", loopTrack ); + + S_StartStreaming(); + + // open the intro track + if(!S_OpenBackgroundTrack( s_bgTrack.introName, &s_bgTrack)) + { + S_StopBackgroundTrack(); + return; + } + S_StreamBackgroundTrack(); +} + +/* +================= +S_StopBackgroundTrack +================= +*/ +void S_StopBackgroundTrack( void ) +{ + if( !al_state.initialized ) return; + + S_StopStreaming(); + S_CloseBackgroundTrack(&s_bgTrack); + Mem_Set(&s_bgTrack, 0, sizeof(bg_track_t)); +} + +/* +================= +S_StartStreaming +================= +*/ +void S_StartStreaming( void ) +{ + if( !al_state.initialized ) return; + if( s_streamingChannel ) return; // already started + + s_streamingChannel = S_PickChannel( 0, 0 ); + if( !s_streamingChannel ) return; + + s_streamingChannel->streaming = true; + +// FIXME: OpenAL bug? +//palDeleteSources(1, &s_streamingChannel->sourceNum); +//palGenSources(1, &s_streamingChannel->sourceNum); + + // set up the source + palSourcei(s_streamingChannel->sourceNum, AL_BUFFER, 0 ); + palSourcei(s_streamingChannel->sourceNum, AL_LOOPING, 0 ); + palSourcei(s_streamingChannel->sourceNum, AL_SOURCE_RELATIVE, 1 ); + palSourcefv(s_streamingChannel->sourceNum, AL_POSITION, vec3_origin); + palSourcefv(s_streamingChannel->sourceNum, AL_VELOCITY, vec3_origin); + palSourcef(s_streamingChannel->sourceNum, AL_REFERENCE_DISTANCE, 1.0); + palSourcef(s_streamingChannel->sourceNum, AL_MAX_DISTANCE, 1.0); + palSourcef(s_streamingChannel->sourceNum, AL_ROLLOFF_FACTOR, 0.0); +} + +/* +================= +S_StopStreaming +================= +*/ +void S_StopStreaming( void ) +{ + int processed; + uint buffer; + + if( !al_state.initialized ) return; + if( !s_streamingChannel ) return; // already stopped + + s_streamingChannel->streaming = false; + // clean up the source + palSourceStop(s_streamingChannel->sourceNum); + + palGetSourcei(s_streamingChannel->sourceNum, AL_BUFFERS_PROCESSED, &processed); + if( processed > 0 ) + { + while( processed-- ) + { + palSourceUnqueueBuffers(s_streamingChannel->sourceNum, 1, &buffer); + palDeleteBuffers(1, &buffer); + } + } + + palSourcei(s_streamingChannel->sourceNum, AL_BUFFER, 0); + +// FIXME: OpenAL bug? +//palDeleteSources(1, &s_streamingChannel->sourceNum); +//palGenSources(1, &s_streamingChannel->sourceNum); + s_streamingChannel = NULL; +} + +/* +================= +S_StreamRawSamples + +Cinematic streaming +================= +*/ +void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data ) +{ + int processed, state, size; + uint format, buffer; + + if( !al_state.initialized ) return; + if( !s_streamingChannel ) return; + + // unqueue and delete any processed buffers + palGetSourcei( s_streamingChannel->sourceNum, AL_BUFFERS_PROCESSED, &processed ); + if( processed > 0 ) + { + while( processed-- ) + { + palSourceUnqueueBuffers(s_streamingChannel->sourceNum, 1, &buffer); + palDeleteBuffers(1, &buffer); + } + } + + // calculate buffer size + size = samples * width * channels; + + // set buffer format + if( width == 2 ) + { + if( channels == 2 ) format = AL_FORMAT_STEREO16; + else format = AL_FORMAT_MONO16; + } + else + { + if( channels == 2 ) format = AL_FORMAT_STEREO8; + else format = AL_FORMAT_MONO8; + } + + // upload and queue the new buffer + palGenBuffers( 1, &buffer ); + palBufferData( buffer, format, (byte *)data, size, rate ); + palSourceQueueBuffers( s_streamingChannel->sourceNum, 1, &buffer ); + + // update volume + palSourcef( s_streamingChannel->sourceNum, AL_GAIN, 1.0f ); + + // if not playing, then do so + palGetSourcei(s_streamingChannel->sourceNum, AL_SOURCE_STATE, &state); + if( state != AL_PLAYING ) palSourcePlay( s_streamingChannel->sourceNum ); +} diff --git a/vsound/s_stream.h b/vsound/s_stream.h new file mode 100644 index 00000000..2d8e59b7 --- /dev/null +++ b/vsound/s_stream.h @@ -0,0 +1,190 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// s_openal.h - openal definition +//======================================================================= + +#ifndef S_STREAM_H +#define S_STREAM_H + +#include "byteorder.h" + +typedef __int64 ogg_int64_t; +typedef __int32 ogg_int32_t; +typedef unsigned __int32 ogg_uint32_t; +typedef __int16 ogg_int16_t; +typedef unsigned __int16 ogg_uint16_t; + +/* +======================================================================= + OGG PACK DEFINITION +======================================================================= +*/ +struct ogg_chain_s +{ + void *ptr; + struct alloc_chain *next; +}; + +typedef struct oggpack_buffer_s +{ + long endbyte; + int endbit; + byte *buffer; + byte *ptr; + long storage; +} oggpack_buffer_t; + +typedef struct ogg_sync_state_s +{ + byte *data; + int storage; + int fill; + int returned; + int unsynced; + int headerbytes; + int bodybytes; +} ogg_sync_state_t; + +typedef struct ogg_stream_state_s +{ + byte *body_data; + long body_storage; + long body_fill; + long body_returned; + int *lacing_vals; + ogg_int64_t *granule_vals; + long lacing_storage; + long lacing_fill; + long lacing_packet; + long lacing_returned; + byte header[282]; + int header_fill; + int e_o_s; + int b_o_s; + long serialno; + long pageno; + ogg_int64_t packetno; + ogg_int64_t granulepos; +} ogg_stream_state_t; + +/* +======================================================================= + VORBIS FILE DEFINITION +======================================================================= +*/ +typedef struct vorbis_info_s +{ + int version; + int channels; + long rate; + long bitrate_upper; + long bitrate_nominal; + long bitrate_lower; + long bitrate_window; + void *codec_setup; +} vorbis_info_t; + +typedef struct vorbis_comment_s +{ + char **user_comments; + int *comment_lengths; + int comments; + char *vendor; +} vorbis_comment_t; + +typedef struct vorbis_dsp_state_s +{ + int analysisp; + vorbis_info_t *vi; + float **pcm; + float **pcmret; + int pcm_storage; + int pcm_current; + int pcm_returned; + int preextrapolate; + int eofflag; + long lW; + long W; + long nW; + long centerW; + ogg_int64_t granulepos; + ogg_int64_t sequence; + ogg_int64_t glue_bits; + ogg_int64_t time_bits; + ogg_int64_t floor_bits; + ogg_int64_t res_bits; + void *backend_state; +} vorbis_dsp_state_t; + +typedef struct vorbis_block_s +{ + float **pcm; + oggpack_buffer_t opb; + long lW; + long W; + long nW; + int pcmend; + int mode; + int eofflag; + ogg_int64_t granulepos; + ogg_int64_t sequence; + vorbis_dsp_state_t *vd; + void *localstore; + long localtop; + long localalloc; + long totaluse; + struct ogg_chain_s *reap; + long glue_bits; + long time_bits; + long floor_bits; + long res_bits; + void *internal; +} vorbis_block_t; + +typedef struct ov_callbacks_s +{ + size_t (*read_func)( void *ptr, size_t size, size_t nmemb, void *datasource ); + int (*seek_func)( void *datasource, ogg_int64_t offset, int whence ); + int (*close_func)( void *datasource ); + long (*tell_func)( void *datasource ); +} ov_callbacks_t; + +typedef struct vorbisfile_s +{ + void *datasource; + int seekable; + ogg_int64_t offset; + ogg_int64_t end; + ogg_sync_state_t oy; + int links; + ogg_int64_t *offsets; + ogg_int64_t *dataoffsets; + long *serialnos; + ogg_int64_t *pcmlengths; + vorbis_info_t *vi; + vorbis_comment_t *vc; + ogg_int64_t pcm_offset; + int ready_state; + long current_serialno; + int current_link; + double bittrack; + double samptrack; + ogg_stream_state_t os; + vorbis_dsp_state_t vd; + vorbis_block_t vb; + ov_callbacks_t callbacks; + +} vorbisfile_t; + +// libvorbis exports +int ov_open_callbacks( void *datasource, vorbisfile_t *vf, char *initial, long ibytes, ov_callbacks_t callbacks ); +long ov_read( vorbisfile_t *vf, char *buffer, int length, int bigendianp, int word, int sgned, int *bitstream ); +char *vorbis_comment_query( vorbis_comment_t *vc, char *tag, int count ); +vorbis_comment_t *ov_comment( vorbisfile_t *vf, int link ); +ogg_int64_t ov_pcm_total( vorbisfile_t *vf, int i ); +vorbis_info_t *ov_info( vorbisfile_t *vf, int link ); +int ov_raw_seek( vorbisfile_t *vf, ogg_int64_t pos ); +ogg_int64_t ov_raw_tell( vorbisfile_t *vf ); +int ov_clear( vorbisfile_t *vf); + +#endif//S_STREAM_H \ No newline at end of file diff --git a/vsound/sound.h b/vsound/sound.h new file mode 100644 index 00000000..b1231f6a --- /dev/null +++ b/vsound/sound.h @@ -0,0 +1,194 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// sound.h - sndlib main header +//======================================================================= + +#ifndef SOUND_H +#define SOUND_H + +#include +#include "launch_api.h" +#include "qfiles_ref.h" +#include "vsound_api.h" +#include "s_openal.h" + +extern stdlib_api_t com; +extern vsound_imp_t si; +extern byte *sndpool; + +#include "mathlib.h" + +typedef enum +{ + S_OPENAL_110 = 0, // base + S_EXT_EFX, + S_EXT_I3DL, + S_EXT_EAX, + S_EXT_EAX20, + S_EXT_EAX30, + S_EXTCOUNT +} s_openal_extensions; + +enum +{ + CHAN_FIRSTPLAY, + CHAN_LOOPED, + CHAN_NORMAL, +}; + +typedef struct +{ + int rate; + int width; + int loopstart; + int channels; + int samples; +} wavinfo_t; + +typedef struct sfx_s +{ + string name; + bool loaded; + int loopstart; // looping point (in samples) + int samples; + int rate; + uint format; + uint bufferNum; + + bool default_sound; + int registration_sequence; +} sfx_t; + +typedef struct +{ + string introName; + string loopName; + bool looping; + file_t *file; + int start; + int rate; + uint format; + void *vorbisFile; +} bg_track_t; + +// a playSound will be generated by each call to S_StartSound. +// when the mixer reaches playSound->beginTime, the playSound will be +// assigned to a channel. +typedef struct playsound_s +{ + struct playsound_s *prev, *next; + sfx_t *sfx; + int entnum; + int entchannel; + bool fixedPosition; // Use position instead of fetching entity's origin + bool use_loop; // ignore looping sounds for local sound + vec3_t position; // Only use if fixedPosition is set + float volume; + float attenuation; + float beginTime; // Begin at this time + float pitch; +} playSound_t; + +typedef struct +{ + bool streaming; + sfx_t *sfx; // NULL if unused + int state; // channel state + int entnum; // to allow overriding a specific sound + int entchannel; + float startTime; // for overriding oldest sounds + + bool loopsound; // is looping sound ? + int loopnum; // looping entity number + int loopframe; // for stopping looping sounds + int loopstart; // check it for set properly offset in samples + + bool fixedPosition; // use position instead of fetching entity's origin + vec3_t position; // only use if fixedPosition is set + float volume; + float pitch; + float distanceMult; + uint sourceNum; // openAL source +} channel_t; + +typedef struct +{ + vec3_t position; + vec3_t velocity; + float orientation[6]; +} listener_t; + +typedef struct +{ + const char *vendor_string; + const char *renderer_string; + const char *version_string; + const char *extensions_string; + + byte extension[S_EXTCOUNT]; + string deviceList[4]; + const char *defDevice; + uint device_count; + uint num_slots; + uint num_sends; + + bool allow_3DMode; + + // 3d mode extension (eax or i3d) + int (*Set3DMode)( const guid_t*, uint, uint, void*, uint ); + int (*Get3DMode)( const guid_t*, uint, uint, void*, uint ); +} alconfig_t; + +typedef struct +{ + aldevice *hDevice; + alcontext *hALC; + + bool initialized; + bool active; + uint framecount; + int num_channels; + int clientnum; +} alstate_t; + +extern alconfig_t al_config; +extern alstate_t al_state; + +#define Host_Error com.error +#define Z_Malloc( size ) Mem_Alloc( sndpool, size ) + +// cvars +extern cvar_t *s_alDevice; +extern cvar_t *s_soundfx; +extern cvar_t *s_musicvolume; +extern cvar_t *s_check_errors; + +bool S_Init( void *hInst ); +void S_Shutdown( void ); +void S_Activate( bool active ); +void S_SoundList_f( void ); +bool S_CheckForErrors( void ); +void S_StartSound(const vec3_t pos, int ent, int chan, sound_t sfx, float vol, float attn, float pitch, bool use_loop); +void S_Update( int clientnum, const vec3_t pos, const vec3_t vel, const vec3_t at, const vec3_t up ); +void S_StreamRawSamples( int samples, int rate, int width, int channels, const byte *data ); +bool S_AddLoopingSound( int entnum, sound_t handle, float volume, float attn ); +void S_StartBackgroundTrack( const char *intro, const char *loop ); +channel_t *S_PickChannel( int entNum, int entChannel ); +int S_StartLocalSound( const char *name, float volume, float pitch, const float *org ); +sfx_t *S_GetSfxByHandle( sound_t handle ); +void S_StreamBackgroundTrack( void ); +void S_StopBackgroundTrack( void ); +void S_ClearSoundBuffer( void ); +bool S_LoadSound( sfx_t *sfx ); +void S_StartStreaming( void ); +void S_StopStreaming( void ); +void S_StopAllSounds( void ); +void S_FreeSounds( void ); + +// registration manager +void S_BeginRegistration( void ); +sound_t S_RegisterSound( const char *sample ); +void S_EndRegistration( void ); + + +#endif//SOUND_H \ No newline at end of file diff --git a/vsound/vorbis.lib b/vsound/vorbis.lib new file mode 100644 index 0000000000000000000000000000000000000000..9eea37a0714237c3f17d1da4ec38612ee71524c8 GIT binary patch literal 266902 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 XLRb+vl4GhJsRj}yNTg%U(GdJURj;C@ literal 0 HcmV?d00001 diff --git a/vsound/vsound.dsp b/vsound/vsound.dsp new file mode 100644 index 00000000..8d4e0ac8 --- /dev/null +++ b/vsound/vsound.dsp @@ -0,0 +1,149 @@ +# Microsoft Developer Studio Project File - Name="vsound" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=vsound - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "vsound.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "vsound.mak" CFG="vsound - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "vsound - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "vsound - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "vsound - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\vsound\!release" +# PROP Intermediate_Dir "..\temp\vsound\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "./" /I "../public" /I "../common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /opt:nowin98 +# ADD LINK32 libogg.lib vorbis.lib /nologo /dll /pdb:none /machine:I386 /nodefaultlib:"libcmt.lib" /opt:nowin98 +# SUBTRACT LINK32 /profile +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\vsound\!release +InputPath=\Xash3D\src_main\temp\vsound\!release\vsound.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\vsound.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\vsound.dll "D:\Xash3D\bin\vsound.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "vsound - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\temp\vsound\!debug" +# PROP Intermediate_Dir "..\temp\vsound\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "../public" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 libogg.lib vorbis.lib /nologo /dll /debug /machine:I386 /nodefaultlib:"libcmt.lib" /pdbtype:sept +# SUBTRACT LINK32 /incremental:no /nodefaultlib +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\vsound\!debug +InputPath=\Xash3D\src_main\temp\vsound\!debug\vsound.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\vsound.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\vsound.dll "D:\Xash3D\bin\vsound.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "vsound - Win32 Release" +# Name "vsound - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\s_export.c +# End Source File +# Begin Source File + +SOURCE=.\s_load.c +# End Source File +# Begin Source File + +SOURCE=.\s_main.c +# End Source File +# Begin Source File + +SOURCE=.\s_openal.c +# End Source File +# Begin Source File + +SOURCE=.\s_stream.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\s_openal.h +# End Source File +# Begin Source File + +SOURCE=.\sound.h +# End Source File +# End Group +# End Target +# End Project diff --git a/xtools/bsplib/ambient.c b/xtools/bsplib/ambient.c new file mode 100644 index 00000000..cdbd82da --- /dev/null +++ b/xtools/bsplib/ambient.c @@ -0,0 +1,134 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// ambient.c - calcualte ambient sounds level +//======================================================================= + +#include "bsplib.h" + +/* +Some textures (sky, water, slime, lava) are considered ambient sound emiters. +Find an aproximate distance to the nearest emiter of each class for each leaf. +*/ + +#define MIN_AMBIENT_DIST 128 +#define MAX_AMBIENT_DIST 1024 + +/* +==================== +SurfaceBBox +==================== +*/ +void SurfaceBBox( dsurface_t *s, vec3_t mins, vec3_t maxs ) +{ + int i, e, vi; + vec3_t v; + + ClearBounds( mins, maxs ); + + for( i = 0; i < s->numedges; i++ ) + { + e = dsurfedges[s->firstedge+i]; + if( e >= 0 ) vi = dedges[e].v[0]; + else vi = dedges[-e].v[1]; + + VectorCopy( dvertexes[vi].point, v ); + AddPointToBounds( v, mins, maxs ); + } +} + +void AmbientForLeaf( dleaf_t *leaf, float *dists ) +{ + int j; + float vol; + + for( j = 0; j < NUM_AMBIENTS; j++ ) + { + if( dists[j] < MIN_AMBIENT_DIST ) vol = 1.0; + else if( dists[j] < MAX_AMBIENT_DIST ) + { + vol = 1.0f - (dists[j] / MAX_AMBIENT_DIST); + vol = bound( 0.0f, vol, 1.0f ); + } + else vol = 0.0f; + leaf->sounds[j] = (byte)(vol * 255); + } +} + +/* +==================== +CalcAmbientSounds + +FIXME: make work +==================== +*/ +void CalcAmbientSounds( void ) +{ + int i, j, k, l; + dleaf_t *leaf, *hit; + byte *vis; + dsurface_t *surf; + vec3_t mins, maxs; + float d, maxd; + int ambient_type; + dtexinfo_t *info; + dshader_t *shader; + float dists[NUM_AMBIENTS]; + + Msg( "---- CalcAmbientSounds ----\n" ); + + if( !visdatasize ) + Sys_Break( "can't create ambient sources - map not vised\n" ); + + for( i = 0; i < dvis->numclusters; i++ ) + { + leaf = &dleafs[i+1]; + + // clear ambients + for( j = 0; j < NUM_AMBIENTS; j++ ) + dists[j] = MAX_AMBIENT_DIST; + vis = PhsForCluster( i ); + + for( j = 0; j < dvis->numclusters; j++ ) + { + if(!(vis[j>>3] & (1<<(j & 7)))) + continue; + + // check this leaf for sound textures + hit = &dleafs[j+1]; + + for( k = 0; k < hit->numleafsurfaces; k++ ) + { + surf = &dsurfaces[dleafsurfaces[hit->firstleafsurface + k]]; + info = &texinfo[surf->texinfo]; + shader = &dshaders[info->shadernum]; + + if( shader->contentFlags & CONTENTS_SKY ) + ambient_type = AMBIENT_SKY; + else if( shader->contentFlags & CONTENTS_SOLID ) + continue; + else if( shader->contentFlags & CONTENTS_WATER ) + ambient_type = AMBIENT_WATER; + else if( shader->contentFlags & CONTENTS_SLIME ) + ambient_type = AMBIENT_SLIME; + else if( shader->contentFlags & CONTENTS_LAVA ) + ambient_type = AMBIENT_LAVA; + else continue; + + // find distance from source leaf to polygon + SurfaceBBox( surf, mins, maxs ); + for( l = maxd = 0; l < 3; l++ ) + { + if (maxs[l] > leaf->maxs[l] ) + d = maxs[l] - leaf->maxs[l]; + else if( mins[l] < leaf->mins[l] ) + d = leaf->mins[l] - mins[l]; + else d = 0; + if( d > maxd ) maxd = d; + } + if( maxd < dists[ambient_type] ) + dists[ambient_type] = maxd; + } + } + AmbientForLeaf( leaf, dists ); + } +} \ No newline at end of file diff --git a/xtools/bsplib/brushbsp.c b/xtools/bsplib/brushbsp.c new file mode 100644 index 00000000..62d88ab5 --- /dev/null +++ b/xtools/bsplib/brushbsp.c @@ -0,0 +1,1169 @@ + +#include "bsplib.h" +#include "const.h" + +int c_nodes; +int c_nonvis; +int c_active_brushes; +vec_t microvolume = 0.5f; + +#define PSIDE_FRONT 1 +#define PSIDE_BACK 2 +#define PSIDE_BOTH (PSIDE_FRONT|PSIDE_BACK) +#define PSIDE_FACING 4 + + +void FindBrushInTree( node_t *node, int brushnum ) +{ + bspbrush_t *b; + + if( node->planenum == PLANENUM_LEAF ) + { + for( b = node->brushlist; b; b = b->next ) + if( b->original->brushnum == brushnum ) + Msg ("here\n"); + return; + } + FindBrushInTree (node->children[0], brushnum); + FindBrushInTree (node->children[1], brushnum); +} + +//================================================== + +static void pw(winding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.1f, %5.1f, %5.1f)\n",w->p[i][0], w->p[i][1],w->p[i][2]); +} + +void PrintBrush (bspbrush_t *brush) +{ + int i; + + Msg ("brush: %p\n", brush); + for (i=0;inumsides ; i++) + { + pw(brush->sides[i].winding); + Msg ("\n"); + } +} + +/* +================== +BoundBrush + +Sets the mins/maxs based on the windings +================== +*/ +void BoundBrush (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + + ClearBounds (brush->mins, brush->maxs); + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + AddPointToBounds (w->p[j], brush->mins, brush->maxs); + } +} + +/* +================== +CreateBrushWindings + +================== +*/ +void CreateBrushWindings (bspbrush_t *brush) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + for (i=0 ; inumsides ; i++) + { + side = &brush->sides[i]; + plane = &mapplanes[side->planenum]; + w = BaseWindingForPlane (plane->normal, plane->dist); + for (j=0 ; jnumsides && w; j++) + { + if (i == j) + continue; + if (brush->sides[j].bevel) + continue; + plane = &mapplanes[brush->sides[j].planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); //CLIP_EPSILON); + } + + side->winding = w; + } + + BoundBrush (brush); +} + +/* +================== +BrushFromBounds + +Creates a new axial brush +================== +*/ +bspbrush_t *BrushFromBounds( vec3_t mins, vec3_t maxs ) +{ + bspbrush_t *b; + vec3_t normal; + float dist; + int i; + + b = AllocBrush( 6 ); + b->numsides = 6; + for( i = 0; i < 3; i++ ) + { + VectorClear( normal ); + normal[i] = 1; + dist = maxs[i]; + b->sides[i].planenum = FindFloatPlane( normal, dist ); + + normal[i] = -1; + dist = -mins[i]; + b->sides[3+i].planenum = FindFloatPlane( normal, dist ); + } + CreateBrushWindings( b ); + + return b; +} + +/* +================== +BrushVolume + +================== +*/ +vec_t BrushVolume (bspbrush_t *brush) +{ + int i; + winding_t *w; + vec3_t corner; + vec_t d, area, volume; + plane_t *plane; + + if (!brush) + return 0; + + // grab the first valid point as the corner + + w = NULL; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (w) + break; + } + if (!w) + return 0; + VectorCopy (w->p[0], corner); + + // make tetrahedrons to all other faces + + volume = 0; + for ( ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + plane = &mapplanes[brush->sides[i].planenum]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + area = WindingArea (w); + volume += d*area; + } + + volume /= 3; + return volume; +} + +/* +================ +CountBrushList +================ +*/ +int CountBrushList (bspbrush_t *brushes) +{ + int c; + + c = 0; + for ( ; brushes ; brushes = brushes->next) + c++; + return c; +} + +/* +================ +AllocTree +================ +*/ +tree_t *AllocTree (void) +{ + tree_t *tree; + + tree = Malloc(sizeof(*tree)); + ClearBounds (tree->mins, tree->maxs); + + return tree; +} + +/* +================ +AllocNode +================ +*/ +node_t *AllocNode (void) +{ + node_t *node; + + node = Malloc(sizeof(*node)); + + return node; +} + + +/* +================ +AllocBrush +================ +*/ +bspbrush_t *AllocBrush (int numsides) +{ + bspbrush_t *bb; + int c; + + c = (int)&(((bspbrush_t *)0)->sides[numsides]); + bb = malloc(c); + memset (bb, 0, c); + if (GetNumThreads() == 1) c_active_brushes++; + return bb; +} + +/* +================ +FreeBrush +================ +*/ +void FreeBrush (bspbrush_t *brushes) +{ + int i; + + for (i=0 ; inumsides ; i++) + if (brushes->sides[i].winding) + FreeWinding(brushes->sides[i].winding); + free(brushes); + if (GetNumThreads() == 1) + c_active_brushes--; +} + + +/* +================ +FreeBrushList +================ +*/ +void FreeBrushList (bspbrush_t *brushes) +{ + bspbrush_t *next; + + for ( ; brushes ; brushes = next) + { + next = brushes->next; + + FreeBrush (brushes); + } +} + +/* +================== +CopyBrush + +Duplicates the brush, the sides, and the windings +================== +*/ +bspbrush_t *CopyBrush (bspbrush_t *brush) +{ + bspbrush_t *newbrush; + int size; + int i; + + size = (int)&(((bspbrush_t *)0)->sides[brush->numsides]); + + newbrush = AllocBrush (brush->numsides); + memcpy (newbrush, brush, size); + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].winding) + newbrush->sides[i].winding = CopyWinding (brush->sides[i].winding); + } + + return newbrush; +} + +//======================================================== + +/* +============== +Q_BoxOnPlaneSide + +Returns PSIDE_FRONT, PSIDE_BACK, or PSIDE_BOTH +============== +*/ +int Q_BoxOnPlaneSide (vec3_t mins, vec3_t maxs, plane_t *plane) +{ + int side; + int i; + vec3_t corners[2]; + vec_t dist1, dist2; + + // axial planes are easy + if (plane->type < 3) + { + side = 0; + if (maxs[plane->type] > plane->dist+EQUAL_EPSILON) + side |= PSIDE_FRONT; + if (mins[plane->type] < plane->dist-EQUAL_EPSILON) + side |= PSIDE_BACK; + return side; + } + + // create the proper leading and trailing verts for the box + + for (i=0 ; i<3 ; i++) + { + if (plane->normal[i] < 0) + { + corners[0][i] = mins[i]; + corners[1][i] = maxs[i]; + } + else + { + corners[1][i] = mins[i]; + corners[0][i] = maxs[i]; + } + } + + dist1 = DotProduct (plane->normal, corners[0]) - plane->dist; + dist2 = DotProduct (plane->normal, corners[1]) - plane->dist; + side = 0; + if (dist1 >= EQUAL_EPSILON) + side = PSIDE_FRONT; + if (dist2 < EQUAL_EPSILON) + side |= PSIDE_BACK; + + return side; +} + +/* +============ +QuickTestBrushToPlanenum + +============ +*/ +int QuickTestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits) +{ + int i, num; + plane_t *plane; + int s; + + *numsplits = 0; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i = 0; i < brush->numsides; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) Sys_Error("bad planenum"); + if (num == planenum) return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &mapplanes[planenum]; + s = Q_BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + // if both sides, count the visible faces split + if (s == PSIDE_BOTH) + { + *numsplits += 3; + } + + return s; +} + +/* +============ +TestBrushToPlanenum + +============ +*/ +int TestBrushToPlanenum (bspbrush_t *brush, int planenum, int *numsplits, bool *hintsplit, int *epsilonbrush) +{ + int i, j, num; + plane_t *plane; + int s; + winding_t *w; + vec_t d, d_front, d_back; + int front, back; + + *numsplits = 0; + *hintsplit = false; + + // if the brush actually uses the planenum, + // we can tell the side for sure + for (i=0 ; inumsides ; i++) + { + num = brush->sides[i].planenum; + if (num >= 0x10000) Sys_Error("bad planenum"); + if (num == planenum) return PSIDE_BACK|PSIDE_FACING; + if (num == (planenum ^ 1) ) return PSIDE_FRONT|PSIDE_FACING; + } + + // box on plane side + plane = &mapplanes[planenum]; + s = Q_BoxOnPlaneSide (brush->mins, brush->maxs, plane); + + if (s != PSIDE_BOTH) + return s; + +// if both sides, count the visible faces split + d_front = d_back = 0; + + for (i=0 ; inumsides ; i++) + { + if (brush->sides[i].texinfo == TEXINFO_NODE) + continue; // on node, don't worry about splits + if (!brush->sides[i].visible) + continue; // we don't care about non-visible + w = brush->sides[i].winding; + if (!w) + continue; + front = back = 0; + for (j=0 ; jnumpoints; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > d_front) + d_front = d; + if (d < d_back) + d_back = d; + + if (d > 0.1) // EQUAL_EPSILON) + front = 1; + if (d < -0.1) // EQUAL_EPSILON) + back = 1; + } + if (front && back) + { + if ( !(brush->sides[i].surf & SURF_SKIP) ) + { + (*numsplits)++; + if (brush->sides[i].surf & SURF_HINT) + *hintsplit = true; + } + } + } + + if ( (d_front > 0.0 && d_front < 1.0) + || (d_back < 0.0 && d_back > -1.0) ) + (*epsilonbrush)++; + +#if 0 + if (*numsplits == 0) + { // didn't really need to be split + if (front) + s = PSIDE_FRONT; + else if (back) + s = PSIDE_BACK; + else + s = 0; + } +#endif + + return s; +} + +//======================================================== + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +#define EDGE_LENGTH 0.2 +bool WindingIsTiny (winding_t *w) +{ +#if 0 + if (WindingArea (w) < 1) + return true; + return false; +#else + int i, j; + vec_t len; + vec3_t delta; + int edges; + + edges = 0; + for (i=0 ; inumpoints ; i++) + { + j = i == w->numpoints - 1 ? 0 : i+1; + VectorSubtract (w->p[j], w->p[i], delta); + len = VectorLength (delta); + if (len > EDGE_LENGTH) + { + if (++edges == 3) + return false; + } + } + return true; +#endif +} + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +bool WindingIsHuge (winding_t *w) +{ + int i, j; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + if (w->p[i][j] < -BOGUS_RANGE || w->p[i][j] > BOGUS_RANGE) + return true; + } + return false; +} + +//============================================================ + +/* +================ +Leafnode +================ +*/ +void LeafNode( node_t *node, bspbrush_t *brushes ) +{ + bspbrush_t *b; + int i; + + node->planenum = PLANENUM_LEAF; + node->contents = 0; + + for (b=brushes ; b ; b=b->next) + { + // if the brush is solid and all of its sides are on nodes, + // it eats everything + if (b->original->contents & CONTENTS_SOLID) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].texinfo != TEXINFO_NODE) + break; + if (i == b->numsides ) + { + node->contents = CONTENTS_SOLID; + break; + } + } + node->contents |= b->original->contents; + } + + + node->brushlist = brushes; +} + + +//============================================================ + +void CheckPlaneAgainstParents (int pnum, node_t *node) +{ + node_t *p; + + for (p = node->parent; p; p = p->parent) + { + if (p->planenum == pnum) + Sys_Error("Tried parent"); + } +} + +bool CheckPlaneAgainstVolume (int pnum, node_t *node) +{ + bspbrush_t *front, *back; + bool good; + + SplitBrush (node->volume, pnum, &front, &back); + + good = (front && back); + + if (front) FreeBrush (front); + if (back) FreeBrush (back); + + return good; +} + +/* +================ +SelectSplitSide + +Using a hueristic, choses one of the sides out of the brushlist +to partition the brushes with. +Returns NULL if there are no valid planes to split with.. +================ +*/ +side_t *SelectSplitSide (bspbrush_t *brushes, node_t *node) +{ + int value, bestvalue; + bspbrush_t *brush, *test; + side_t *side, *bestside; + int i, j, pass, numpasses; + int pnum; + int s; + int front, back, both, facing, splits; + int bsplits; + int bestsplits; + int epsilonbrush; + bool hintsplit; + + bestside = NULL; + bestvalue = -99999; + bestsplits = 0; + + // the search order goes: visible-structural, visible-detail, + // nonvisible-structural, nonvisible-detail. + // If any valid plane is available in a pass, no further + // passes will be tried. + numpasses = 4; + for (pass = 0 ; pass < numpasses ; pass++) + { + for (brush = brushes ; brush ; brush=brush->next) + { + if ( (pass & 1) && !(brush->original->contents & CONTENTS_DETAIL) ) + continue; + if ( !(pass & 1) && (brush->original->contents & CONTENTS_DETAIL) ) + continue; + for (i=0 ; inumsides ; i++) + { + side = brush->sides + i; + if (side->bevel) + continue; // never use a bevel as a spliter + if (!side->winding) + continue; // nothing visible, so it can't split + if (side->texinfo == TEXINFO_NODE) + continue; // allready a node splitter + if (side->tested) + continue; // we allready have metrics for this plane + if (side->surf & SURF_SKIP) + continue; // skip surfaces are never chosen + if ( side->visible ^ (pass<2) ) + continue; // only check visible faces on first pass + + pnum = side->planenum; + pnum &= ~1; // allways use positive facing plane + + CheckPlaneAgainstParents (pnum, node); + + if (!CheckPlaneAgainstVolume (pnum, node)) + continue; // would produce a tiny volume + + front = 0; + back = 0; + both = 0; + facing = 0; + splits = 0; + epsilonbrush = 0; + + for (test = brushes ; test ; test=test->next) + { + s = TestBrushToPlanenum (test, pnum, &bsplits, &hintsplit, &epsilonbrush); + + splits += bsplits; + if (bsplits && (s&PSIDE_FACING) ) + Sys_Error("PSIDE_FACING with splits"); + + test->testside = s; + // if the brush shares this face, don't bother + // testing that facenum as a splitter again + if (s & PSIDE_FACING) + { + facing++; + for (j=0 ; jnumsides ; j++) + { + if ( (test->sides[j].planenum&~1) == pnum) + test->sides[j].tested = true; + } + } + if (s & PSIDE_FRONT) + front++; + if (s & PSIDE_BACK) + back++; + if (s == PSIDE_BOTH) + both++; + } + + // give a value estimate for using this plane + + value = 5*facing - 5*splits - abs(front-back); +// value = -5*splits; +// value = 5*facing - 5*splits; + if (mapplanes[pnum].type < 3) + value+=5; // axial is better + value -= epsilonbrush*1000; // avoid! + + // never split a hint side except with another hint + if (hintsplit && !(side->surf & SURF_HINT) ) + value = -9999999; + + // save off the side test so we don't need + // to recalculate it when we actually seperate + // the brushes + if (value > bestvalue) + { + bestvalue = value; + bestside = side; + bestsplits = splits; + for (test = brushes ; test ; test=test->next) + test->side = test->testside; + } + } + } + + // if we found a good plane, don't bother trying any + // other passes + if (bestside) + { + if (pass > 1) + { + if (GetNumThreads() == 1) + c_nonvis++; + } + if (pass > 0) + node->detail_seperator = true; // not needed for vis + break; + } + } + + // + // clear all the tested flags we set + // + for (brush = brushes ; brush ; brush=brush->next) + { + for (i=0 ; inumsides ; i++) + brush->sides[i].tested = false; + } + + return bestside; +} + + +/* +================== +BrushMostlyOnSide + +================== +*/ +int BrushMostlyOnSide (bspbrush_t *brush, plane_t *plane) +{ + int i, j; + winding_t *w; + vec_t d, max; + int side; + + max = 0; + side = PSIDE_FRONT; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > max) + { + max = d; + side = PSIDE_FRONT; + } + if (-d > max) + { + max = -d; + side = PSIDE_BACK; + } + } + } + return side; +} + +/* +================ +SplitBrush + +Generates two new brushes, leaving the original +unchanged +================ +*/ +void SplitBrush( bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back ) +{ + bspbrush_t *b[2]; + int i, j; + winding_t *w, *cw[2], *midwinding; + plane_t *plane, *plane2; + side_t *s, *cs; + float d, d_front, d_back; + + *front = *back = NULL; + plane = &mapplanes[planenum]; + + // check all points + d_front = d_back = 0; + for (i=0 ; inumsides ; i++) + { + w = brush->sides[i].winding; + if (!w) + continue; + for (j=0 ; jnumpoints ; j++) + { + d = DotProduct (w->p[j], plane->normal) - plane->dist; + if (d > 0 && d > d_front) + d_front = d; + if (d < 0 && d < d_back) + d_back = d; + } + } + if (d_front < 0.1) // EQUAL_EPSILON) + { // only on back + *back = CopyBrush (brush); + return; + } + if (d_back > -0.1) // EQUAL_EPSILON) + { // only on front + *front = CopyBrush (brush); + return; + } + + // create a new winding from the split plane + + w = BaseWindingForPlane (plane->normal, plane->dist); + for (i=0 ; inumsides && w ; i++) + { + plane2 = &mapplanes[brush->sides[i].planenum ^ 1]; + ChopWindingInPlace (&w, plane2->normal, plane2->dist, 0); // EQUAL_EPSILON); + } + + if (!w || WindingIsTiny (w) ) + { // the brush isn't really split + int side; + + side = BrushMostlyOnSide (brush, plane); + if (side == PSIDE_FRONT) + *front = CopyBrush (brush); + if (side == PSIDE_BACK) + *back = CopyBrush (brush); + return; + } + + if (WindingIsHuge (w)) Msg("WARNING: huge winding\n"); + + midwinding = w; + + // split it for real + + for (i=0 ; i<2 ; i++) + { + b[i] = AllocBrush (brush->numsides+1); + b[i]->original = brush->original; + } + + // split all the current windings + + for (i=0 ; inumsides ; i++) + { + s = &brush->sides[i]; + w = s->winding; + if (!w) + continue; + ClipWindingEpsilon (w, plane->normal, plane->dist, + 0 /*EQUAL_EPSILON*/, &cw[0], &cw[1]); + for (j=0 ; j<2 ; j++) + { + if (!cw[j]) + continue; +#if 0 + if (WindingIsTiny (cw[j])) + { + FreeWinding (cw[j]); + continue; + } +#endif + cs = &b[j]->sides[b[j]->numsides]; + b[j]->numsides++; + *cs = *s; +// cs->planenum = s->planenum; +// cs->texinfo = s->texinfo; +// cs->visible = s->visible; +// cs->original = s->original; + cs->winding = cw[j]; + cs->tested = false; + } + } + + + // see if we have valid polygons on both sides + + for( i = 0; i < 2; i++) + { + BoundBrush (b[i]); + for (j=0 ; j<3 ; j++) + { + if (b[i]->mins[j] < -131072 || b[i]->maxs[j] > 131072) + break; + } + + if (b[i]->numsides < 3 || j < 3) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } + + if ( !(b[0] && b[1]) ) + { + if (b[0]) + { + FreeBrush (b[0]); + *front = CopyBrush (brush); + } + if (b[1]) + { + FreeBrush (b[1]); + *back = CopyBrush (brush); + } + return; + } + + // add the midwinding to both sides + for (i=0 ; i<2 ; i++) + { + cs = &b[i]->sides[b[i]->numsides]; + b[i]->numsides++; + + cs->planenum = planenum^i^1; + cs->texinfo = TEXINFO_NODE; + cs->visible = false; + cs->tested = false; + if (i==0) + cs->winding = CopyWinding (midwinding); + else + cs->winding = midwinding; + } + +{ + vec_t v1; + int i; + + for (i=0 ; i<2 ; i++) + { + v1 = BrushVolume (b[i]); + if (v1 < 1.0) + { + FreeBrush (b[i]); + b[i] = NULL; + } + } +} + + *front = b[0]; + *back = b[1]; +} + +/* +================ +SplitBrushList +================ +*/ +void SplitBrushList( bspbrush_t *brushes, node_t *node, bspbrush_t **front, bspbrush_t **back ) +{ + bspbrush_t *brush, *newbrush, *newbrush2; + int i, sides; + side_t *side; + + *front = *back = NULL; + + for( brush = brushes; brush; brush = brush->next ) + { + sides = brush->side; + + if( sides == PSIDE_BOTH ) + { + // split into two brushes + SplitBrush( brush, node->planenum, &newbrush, &newbrush2 ); + if( newbrush ) + { + newbrush->next = *front; + *front = newbrush; + } + if( newbrush2 ) + { + newbrush2->next = *back; + *back = newbrush2; + } + continue; + } + + newbrush = CopyBrush( brush ); + + // if the planenum is actualy a part of the brush + // find the plane and flag it as used so it won't be tried + // as a splitter again + if( sides & PSIDE_FACING ) + { + for( i = 0; i < newbrush->numsides; i++ ) + { + side = newbrush->sides + i; + if((side->planenum& ~1) == node->planenum ) + side->texinfo = TEXINFO_NODE; + } + } + + + if( sides & PSIDE_FRONT ) + { + newbrush->next = *front; + *front = newbrush; + continue; + } + if( sides & PSIDE_BACK ) + { + newbrush->next = *back; + *back = newbrush; + continue; + } + } +} + + +/* +================ +BuildTree_r +================ +*/ +node_t *BuildTree_r (node_t *node, bspbrush_t *brushes) +{ + node_t *newnode; + side_t *bestside; + bspbrush_t *children[2]; + int i; + + if( GetNumThreads() == 1 ) + c_nodes++; + + // find the best plane to use as a splitter + bestside = SelectSplitSide( brushes, node ); + if( !bestside ) + { + // leaf node + node->side = NULL; + node->planenum = PLANENUM_LEAF; + LeafNode( node, brushes ); + return node; + } + + // this is a splitplane node + node->side = bestside; + node->planenum = bestside->planenum & ~1; // always use front facing + + SplitBrushList( brushes, node, &children[0], &children[1] ); + FreeBrushList( brushes ); + + // allocate children before recursing + for( i = 0; i < 2; i++ ) + { + newnode = AllocNode(); + newnode->parent = node; + node->children[i] = newnode; + } + + SplitBrush( node->volume, node->planenum, &node->children[0]->volume, &node->children[1]->volume ); + + // recursively process children + for( i = 0; i < 2; i++ ) + { + node->children[i] = BuildTree_r( node->children[i], children[i] ); + } + + return node; +} + +//=========================================================== + +/* +================= +BrushBSP + +The incoming list will be freed before exiting +================= +*/ +tree_t *BrushBSP (bspbrush_t *brushlist, vec3_t mins, vec3_t maxs) +{ + bspbrush_t *b; + node_t *node; + tree_t *tree; + float volume; + int i, c_faces, c_nonvisfaces, c_brushes; + + tree = AllocTree(); + + c_faces = c_nonvisfaces = c_brushes = 0; + + for( b = brushlist; b; b = b->next ) + { + c_brushes++; + + volume = BrushVolume( b ); + if( volume < microvolume ) + { + MsgDev( D_WARN, "entity %i, brush %i: microbrush\n", b->original->entitynum, b->original->brushnum ); + } + + for( i = 0; i < b->numsides; i++ ) + { + if( b->sides[i].bevel ) + continue; + if( !b->sides[i].winding ) + continue; + if( b->sides[i].texinfo == TEXINFO_NODE ) + continue; + if( b->sides[i].visible ) + c_faces++; + else c_nonvisfaces++; + } + AddPointToBounds( b->mins, tree->mins, tree->maxs ); + AddPointToBounds( b->maxs, tree->mins, tree->maxs ); + } + + c_nodes = 0; + c_nonvis = 0; + node = AllocNode(); + node->volume = BrushFromBounds( mins, maxs ); + + tree->headnode = node; + + node = BuildTree_r( node, brushlist ); + return tree; +} \ No newline at end of file diff --git a/xtools/bsplib/bspfile.c b/xtools/bsplib/bspfile.c new file mode 100644 index 00000000..24d0dcd1 --- /dev/null +++ b/xtools/bsplib/bspfile.c @@ -0,0 +1,666 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// bspfile.c - read\save bsp file +//======================================================================= + +#include // sscanf support +#include "bsplib.h" +#include "entity_def.h" +#include "physic_api.h" +#include "byteorder.h" +#include "const.h" + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) +extern physic_exp_t *pe; + +//============================================================================= +wfile_t *wadfile; +dheader_t *header; + +int num_entities; +bsp_entity_t entities[MAX_MAP_ENTITIES]; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; +int lightdatasize; +byte dlightdata[MAX_MAP_LIGHTING]; +int entdatasize; +char dentdata[MAX_MAP_ENTSTRING]; +int numleafs; +dleaf_t dleafs[MAX_MAP_LEAFS]; +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; +int numtexinfo; +dtexinfo_t texinfo[MAX_MAP_TEXINFO]; +int numsurfaces; +dsurface_t dsurfaces[MAX_MAP_SURFACES]; +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; +int numleafsurfaces; +dleafface_t dleafsurfaces[MAX_MAP_LEAFFACES]; +int numleafbrushes; +dleafbrush_t dleafbrushes[MAX_MAP_LEAFBRUSHES]; +int numsurfedges; +dsurfedge_t dsurfedges[MAX_MAP_SURFEDGES]; +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; +int numshaders; +dshader_t dshaders[MAX_MAP_SHADERS]; +int numareas; +darea_t dareas[MAX_MAP_AREAS]; +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; +byte dcollision[MAX_MAP_COLLISION]; +int dcollisiondatasize; + +//============================================================================= +size_t ArrayUsage( char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + MsgDev( D_INFO, "%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + + if( percentage > 80.0 ) MsgDev( D_INFO, "VERY FULL!\n" ); + else if( percentage > 95.0 ) MsgDev( D_INFO, "SIZE DANGER!\n" ); + else if( percentage > 99.9 ) MsgDev( D_INFO, "SIZE OVERFLOW!!!\n" ); + else MsgDev( D_INFO, "\n" ); + + return items * itemsize; +} + +size_t GlobUsage( char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + + MsgDev( D_INFO, "%-12s [variable] %7i/%-7i (%4.1f%%)", szItem, itemstorage, maxstorage, percentage ); + + if( percentage > 80.0 ) MsgDev( D_INFO, "VERY FULL!\n" ); + else if( percentage > 95.0 ) MsgDev( D_INFO, "SIZE DANGER!\n" ); + else if( percentage > 99.9 ) MsgDev( D_INFO, "SIZE OVERFLOW!!!\n" ); + else MsgDev( D_INFO, "\n" ); + + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes( void ) +{ + int totalmemory = 0; + + MsgDev( D_INFO, "\n" ); + MsgDev( D_INFO, "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + MsgDev( D_INFO, "------------ --------------- --------------- --------\n" ); + + // struct arrays + totalmemory += ArrayUsage( "shaders", numshaders, ENTRIES( dshaders ), ENTRYSIZE( dshaders )); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES( dplanes ), ENTRYSIZE( dplanes )); + totalmemory += ArrayUsage( "leafs", numleafs, ENTRIES( dleafs ), ENTRYSIZE( dleafs )); + totalmemory += ArrayUsage( "leaffaces", numleafsurfaces, ENTRIES( dleafsurfaces ), ENTRYSIZE( dleafsurfaces )); + totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES( dleafbrushes ), ENTRYSIZE( dleafbrushes )); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES( dnodes ), ENTRYSIZE( dnodes )); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES( dvertexes ), ENTRYSIZE( dvertexes )); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES( dedges ), ENTRYSIZE( dedges )); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES( dsurfedges ), ENTRYSIZE( dsurfedges )); + totalmemory += ArrayUsage( "texinfos", numtexinfo, ENTRIES( texinfo ), ENTRYSIZE( texinfo )); + totalmemory += ArrayUsage( "surfaces", numsurfaces, ENTRIES( dsurfaces ), ENTRYSIZE( dsurfaces )); + totalmemory += ArrayUsage( "models", nummodels, ENTRIES( dmodels ), ENTRYSIZE( dmodels )); + totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES( dbrushes ), ENTRYSIZE( dbrushes )); + totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES( dbrushsides ), ENTRYSIZE( dbrushsides )); + totalmemory += ArrayUsage( "areas", numareas, ENTRIES( dareas ), ENTRYSIZE( dareas )); + totalmemory += ArrayUsage( "areaportals", numareaportals, ENTRIES( dareaportals ), ENTRYSIZE( dareaportals )); + + // byte arrays + totalmemory += GlobUsage( "entities", entdatasize, sizeof( dentdata )); + totalmemory += GlobUsage( "lightdata", lightdatasize, sizeof( dlightdata )); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof( dvisdata )); + totalmemory += GlobUsage( "collision", dcollisiondatasize, sizeof( dcollision )); + + MsgDev( D_INFO, "=== Total BSP file data space used: %d bytes ===\n", totalmemory ); +} + +/* +============= +SwapBSPFile + +Byte swaps all data in a bsp file. +============= +*/ +void SwapBSPFile( bool todisk ) +{ + int i, j; + + // models + SwapBlock((int *)dmodels, nummodels * sizeof( dmodels[0] )); + + // vertexes + SwapBlock( (int *)dvertexes, numvertexes * sizeof( dvertexes[0] )); + + // planes + SwapBlock( (int *)dplanes, numplanes * sizeof( dplanes[0] )); + + // texinfos + SwapBlock( (int *)texinfo, numtexinfo * sizeof( texinfo[0] )); + + // nodes + SwapBlock( (int *)dnodes, numnodes * sizeof( dnodes[0] )); + + // leafs + SwapBlock( (int *)dleafs, numleafs * sizeof( dleafs[0] )); + + // leaffaces + SwapBlock( (int *)dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] )); + + // leafbrushes + SwapBlock( (int *)dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] )); + + // surfedges + SwapBlock( (int *)dsurfedges, numsurfedges * sizeof( dsurfedges[0] )); + + // edges + SwapBlock( (int *)dedges, numedges * sizeof( dedges[0] )); + + // brushes + SwapBlock( (int *)dbrushes, numbrushes * sizeof( dbrushes[0] )); + + // areas + SwapBlock( (int *)dareas, numareas * sizeof( dareas[0] )); + + // areasportals + SwapBlock( (int *)dareaportals, numareaportals * sizeof( dareaportals[0] )); + + // brushsides + SwapBlock( (int *)dbrushsides, numbrushsides * sizeof( dbrushsides[0] )); + + // shaders + for( i = 0; i < numshaders; i++ ) + { + dshaders[i].size[0] = LittleLong( dshaders[i].size[0] ); + dshaders[i].size[1] = LittleLong( dshaders[i].size[1] ); + dshaders[i].surfaceFlags = LittleLong( dshaders[i].surfaceFlags ); + dshaders[i].contentFlags = LittleLong( dshaders[i].contentFlags ); + } + + // faces + for( i = 0; i < numsurfaces; i++ ) + { + dsurfaces[i].planenum = LittleLong( dsurfaces[i].planenum ); + dsurfaces[i].side = LittleLong (dsurfaces[i].side); + dsurfaces[i].firstedge = LittleLong( dsurfaces[i].firstedge ); + dsurfaces[i].numedges = LittleLong( dsurfaces[i].numedges ); + dsurfaces[i].texinfo = LittleLong( dsurfaces[i].texinfo ); + dsurfaces[i].lightofs = LittleLong( dsurfaces[i].lightofs ); + } + + // visibility + if( todisk ) j = dvis->numclusters; + else j = LittleLong( dvis->numclusters ); + dvis->numclusters = LittleLong( dvis->numclusters ); + for( i = 0; i < j; i++ ) + { + dvis->bitofs[i][0] = LittleLong( dvis->bitofs[i][0] ); + dvis->bitofs[i][1] = LittleLong( dvis->bitofs[i][1] ); + } +} + +bool CompressLump( const char *lumpname, size_t length ) +{ + if( !bsplib_compress_bsp->integer ) + return false; + if( !com.strcmp( lumpname, LUMP_MAPINFO )) + return false; + if( !com.strcmp( lumpname, LUMP_ENTITIES )) + return false; // never compress entities + if( !com.strcmp( lumpname, LUMP_COLLISION )) + return true; + if( !com.strcmp( lumpname, LUMP_VISIBILITY )) + return true; + if( !com.strcmp( lumpname, LUMP_LIGHTING )) + return true; + + // other lumps can be compressed if their size more than 32 kBytes + if( length > 0x8000 ) + return true; + return false; +} + +char TypeForLump( const char *lumpname ) +{ + if( !com.strcmp( lumpname, LUMP_ENTITIES )) + return TYPE_SCRIPT; + return TYPE_BINDATA; +} + +size_t CopyLump( const char *lumpname, void *dest, size_t block_size ) +{ + size_t length; + byte *in; + + if( !wadfile ) return 0; + + in = WAD_Read( wadfile, lumpname, &length, TypeForLump( lumpname )); + if( !in ) return 0; // empty lump + if( length % block_size ) Sys_Break( "LoadBSPFile: %s funny lump size\n", lumpname ); + Mem_Copy( dest, in, length ); + return length / block_size; +} + +void AddLump( const char *lumpname, const void *data, size_t length ) +{ + int compress = CMP_NONE; + + if( !wadfile || !length ) return; + compress = CompressLump( lumpname, length ) ? CMP_ZLIB : CMP_NONE; + WAD_Write( wadfile, lumpname, data, (length + 3) & ~3, TypeForLump( lumpname ), compress ); +} + +/* +============= +LoadBSPFile +============= +*/ +bool LoadBSPFile( void ) +{ + static dheader_t inheader; + + header = &inheader; + wadfile = WAD_Open( va("maps/%s.bsp", gs_filename ), "rb" ); + if( !wadfile ) return false; + + CopyLump( LUMP_MAPINFO, header, 1 ); + if( pe ) pe->LoadBSP( wadfile ); + + MsgDev( D_NOTE, "reading %s.bsp\n", gs_filename ); + + // swap the header + header->ident = LittleLong( header->ident ); + header->version = LittleLong( header->version ); + + if( header->ident != IDBSPMODHEADER ) + Sys_Break( "%s.bsp is not a IBSP file\n", gs_filename ); + if( header->version != BSPMOD_VERSION ) + Sys_Break( "%s.bsp is version %i, not %i\n", gs_filename, header->version, BSPMOD_VERSION ); + + entdatasize = CopyLump( LUMP_ENTITIES, dentdata, 1 ); + numplanes = CopyLump( LUMP_PLANES, dplanes, sizeof( dplanes[0] )); + numvertexes = CopyLump( LUMP_VERTEXES, dvertexes, sizeof( dvertexes[0] )); + visdatasize = CopyLump( LUMP_VISIBILITY, dvisdata, 1 ); + numnodes = CopyLump( LUMP_NODES, dnodes, sizeof( dnodes[0] )); + numtexinfo = CopyLump( LUMP_TEXINFO, texinfo, sizeof( texinfo[0] )); + numsurfaces = CopyLump( LUMP_SURFACES, dsurfaces, sizeof( dsurfaces[0] )); + lightdatasize = CopyLump( LUMP_LIGHTING, dlightdata, 1 ); + numleafs = CopyLump( LUMP_LEAFS, dleafs, sizeof( dleafs[0] )); + numleafsurfaces = CopyLump( LUMP_LEAFFACES, dleafsurfaces, sizeof( dleafsurfaces[0] )); + numleafbrushes = CopyLump( LUMP_LEAFBRUSHES, dleafbrushes, sizeof( dleafbrushes[0] )); + numedges = CopyLump( LUMP_EDGES, dedges, sizeof( dedges[0] )); + numsurfedges = CopyLump( LUMP_SURFEDGES, dsurfedges, sizeof( dsurfedges[0] )); + nummodels = CopyLump( LUMP_MODELS, dmodels, sizeof( dmodels[0] )); + numbrushes = CopyLump( LUMP_BRUSHES, dbrushes, sizeof( dbrushes[0] )); + numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides, sizeof( dbrushsides[0] )); + dcollisiondatasize = CopyLump( LUMP_COLLISION, dcollision, 1 ); + numshaders = CopyLump( LUMP_SHADERS, dshaders, sizeof( dshaders[0] )); + numareas = CopyLump ( LUMP_AREAS, dareas, sizeof( dareas[0] )); + numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals, sizeof( dareaportals[0] )); + WAD_Close( wadfile ); // release memory + wadfile = NULL; + + // swap everything + SwapBSPFile( false ); + + return true; +} + +//============================================================================ + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( void ) +{ + static dheader_t outheader; + + header = &outheader; + Mem_Set( header, 0, sizeof( dheader_t )); + + SwapBSPFile( true ); + + header->ident = LittleLong( IDBSPMODHEADER ); + header->version = LittleLong( BSPMOD_VERSION ); + FindMapMessage( header->message ); + + MsgDev( D_NOTE, "\n\nwriting %s.bsp\n", gs_filename ); + if( pe ) pe->FreeBSP(); + + wadfile = WAD_Open( va( "maps/%s.bsp", gs_filename ), "wb" ); + + AddLump( LUMP_MAPINFO, header, sizeof( dheader_t )); + AddLump( LUMP_ENTITIES, dentdata, entdatasize ); + AddLump( LUMP_PLANES, dplanes, numplanes * sizeof( dplanes[0] )); + AddLump( LUMP_VERTEXES, dvertexes, numvertexes * sizeof( dvertexes[0] )); + AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); + AddLump( LUMP_NODES, dnodes, numnodes * sizeof( dnodes[0] )); + AddLump( LUMP_TEXINFO, texinfo, numtexinfo * sizeof( texinfo[0] )); + AddLump( LUMP_SURFACES, dsurfaces, numsurfaces * sizeof( dsurfaces[0] )); + AddLump( LUMP_LIGHTING, dlightdata, lightdatasize ); + AddLump( LUMP_LEAFS, dleafs, numleafs * sizeof( dleafs[0] )); + AddLump( LUMP_LEAFFACES, dleafsurfaces, numleafsurfaces * sizeof( dleafsurfaces[0] )); + AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes * sizeof( dleafbrushes[0] )); + AddLump( LUMP_EDGES, dedges, numedges * sizeof( dedges[0] )); + AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges * sizeof( dsurfedges[0] )); + AddLump( LUMP_MODELS, dmodels, nummodels * sizeof( dmodels[0] )); + AddLump( LUMP_BRUSHES, dbrushes, numbrushes * sizeof( dbrushes[0] )); + AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides * sizeof( dbrushsides[0] )); + AddLump( LUMP_COLLISION, dcollision, dcollisiondatasize ); + AddLump( LUMP_SHADERS, dshaders, numshaders * sizeof( dshaders[0] )); + AddLump( LUMP_AREAS, dareas, numareas * sizeof( dareas[0] )); + AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals * sizeof( dareaportals[0] )); + + WAD_Close( wadfile ); + wadfile = NULL; +} + +//============================================ +// misc parse functions +//============================================ + +void StripTrailing( char *e ) +{ + char *s; + + s = e + com.strlen( e ) - 1; + while( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair( script_t *script, token_t *token ) +{ + epair_t *e; + + e = Malloc( sizeof( epair_t )); + + if( com.strlen( token->string ) >= MAX_KEY - 1 ) + Sys_Break( "ParseEpair: token too long\n" ); + e->key = copystring( token->string ); + Com_ReadToken( script, SC_PARSE_GENERIC, token ); + if( com.strlen( token->string ) >= MAX_VALUE - 1 ) + Sys_Break( "ParseEpair: token too long\n" ); + e->value = copystring( token->string ); + + // strip trailing spaces + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} + +/* +================ +ParseEntity +================ +*/ +bool ParseEntity( void ) +{ + epair_t *e; + token_t token; + bsp_entity_t *mapent; + + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES, &token )) + return false; + + if( com.stricmp( token.string, "{" )) Sys_Break( "ParseEntity: '{' not found\n" ); + if( num_entities == MAX_MAP_ENTITIES ) Sys_Break( "MAX_MAP_ENTITIES limit excceded\n" ); + + mapent = &entities[num_entities]; + num_entities++; + + while( 1 ) + { + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + Sys_Break( "ParseEntity: EOF without closing brace\n" ); + if( !com.stricmp( token.string, "}" )) break; + e = ParseEpair( mapfile, &token ); + e->next = mapent->epairs; + mapent->epairs = e; + } + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities( void ) +{ + num_entities = 0; + mapfile = Com_OpenScript( "entities", dentdata, entdatasize ); + if( mapfile ) + { + while( ParseEntity( )); + Com_CloseScript( mapfile ); + } +} + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities( void ) +{ + epair_t *ep; + char *buf, *end; + char line[2048]; + char key[MAX_KEY], value[MAX_VALUE]; + int i; + + buf = dentdata; + end = buf; + *end = 0; + + for( i = 0; i < num_entities; i++ ) + { + ep = entities[i].epairs; + if( !ep ) continue; // ent got removed + + com.strcat( end, "{\n" ); + end += 2; + + for( ep = entities[i].epairs; ep; ep = ep->next ) + { + com.strncpy( key, ep->key, MAX_KEY ); + StripTrailing( key ); + com.strncpy( value, ep->value, MAX_VALUE ); + StripTrailing( value ); + + com.snprintf( line, 2048, "\"%s\" \"%s\"\n", key, value ); + com.strcat( end, line ); + end += com.strlen( line ); + } + com.strcat( end, "}\n" ); + end += 2; + + if( end > buf + MAX_MAP_ENTSTRING ) + Sys_Break( "Entity text too long\n" ); + } + entdatasize = end - buf + 1; +} + +void SetKeyValue( bsp_entity_t *ent, const char *key, const char *value ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if(!com.strcmp( ep->key, key )) + { + Mem_Free( ep->value ); + ep->value = copystring( value ); + return; + } + } + ep = Malloc( sizeof( *ep )); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring( key ); + ep->value = copystring( value ); +} + +char *ValueForKey( const bsp_entity_t *ent, const char *key ) +{ + epair_t *ep; + + if( !ent ) return ""; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if(!com.strcmp( ep->key, key )) + return ep->value; + } + return ""; +} + +long IntForKey( const bsp_entity_t *ent, const char *key ) +{ + char *k; + + k = ValueForKey( ent, key ); + return com.atoi( k ); +} + +vec_t FloatForKey( const bsp_entity_t *ent, const char *key ) +{ + char *k; + + k = ValueForKey( ent, key ); + return com.atof( k ); +} + +void GetVectorForKey( const bsp_entity_t *ent, const char *key, vec3_t vec ) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey( ent, key ); + + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + VectorSet( vec, v1, v2, v3 ); +} + +/* +================ +FindTargetEntity + +finds an entity target +================ +*/ +bsp_entity_t *FindTargetEntity( const char *target ) +{ + int i; + const char *n; + + + // walk entity list + for( i = 0; i < num_entities; i++ ) + { + n = ValueForKey( &entities[i], "targetname" ); + if( !com.strcmp( n, target )) + return &entities[i]; + } + return NULL; +} + +/* +================ +FindTargetEntity + +finds an entity target +================ +*/ +void FindMapMessage( char *message ) +{ + int i; + const char *value; + bsp_entity_t *e; + + if( !message ) return; + + // walk entity list + for( i = 0; i < num_entities; i++ ) + { + e = &entities[i]; + value = ValueForKey( e, "classname" ); + if( !com.stricmp( value, "worldspawn" )) + { + value = ValueForKey( e, "message" ); + com.strncpy( message, value, MAX_SHADERPATH ); + return; + } + } + com.strncpy( message, "", MAX_SHADERPATH ); +} + +void Com_CheckToken( script_t *script, const char *match ) +{ + token_t token; + + Com_ReadToken( script, SC_ALLOW_NEWLINES, &token ); + + if( com.stricmp( token.string, match )) + { + Sys_Break( "Com_CheckToken: \"%s\" not found\n", match ); + } +} + +void Com_Parse1DMatrix( script_t *script, int x, vec_t *m ) +{ + int i; + + Com_CheckToken( script, "(" ); + for( i = 0; i < x; i++ ) + Com_ReadFloat( script, false, &m[i] ); + Com_CheckToken( script, ")" ); +} + +void Com_Parse2DMatrix( script_t *script, int y, int x, vec_t *m ) +{ + int i; + + Com_CheckToken( script, "(" ); + for( i = 0; i < y; i++ ) + Com_Parse1DMatrix( script, x, m+i*x ); + Com_CheckToken( script, ")" ); +} \ No newline at end of file diff --git a/xtools/bsplib/bsplib.c b/xtools/bsplib/bsplib.c new file mode 100644 index 00000000..a14e287b --- /dev/null +++ b/xtools/bsplib/bsplib.c @@ -0,0 +1,165 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// bsplib.c - bsp level creator +//======================================================================= + +#include "bsplib.h" +#include "entity_def.h" +#include "physic_api.h" + +byte *checkermate_dds; +size_t checkermate_dds_size; +char path[MAX_SYSPATH]; +uint bsp_parms; +file_t *bsplog; +cvar_t *bsplib_compress_bsp; + +dll_info_t physic_dll = { "physic.dll", NULL, "CreateAPI", NULL, NULL, false, sizeof(physic_exp_t) }; +physic_exp_t *pe; + +void BSP_PrintLog( const char *pMsg ) +{ + if( !enable_log ) return; + if( !bsplog ) bsplog = FS_Open( va("maps/%s.log", gs_filename ), "wb" ); + FS_Print( bsplog, pMsg ); +} + +static void AddCollision( void* handle, const void* buffer, size_t size ) +{ + if((dcollisiondatasize + size) > MAX_MAP_COLLISION ) + Sys_Error( "MAX_MAP_COLLISION limit exceeded\n" ); + Mem_Copy( dcollision + dcollisiondatasize, (void *)buffer, size ); + dcollisiondatasize += size; +} + +void ProcessCollisionTree( void ) +{ + if( !physic_dll.link ) return; + + dcollisiondatasize = 0; + pe->WriteCollisionLump( NULL, AddCollision ); +} + +void Init_PhysicsLibrary( void ) +{ + static physic_imp_t pi; + launch_t CreatePhysic; + + pi.api_size = sizeof(physic_imp_t); + Sys_LoadLibrary( &physic_dll ); + + if( physic_dll.link ) + { + CreatePhysic = (void *)physic_dll.main; + pe = CreatePhysic( &com, &pi ); // sys_error not overrided + pe->Init(); // initialize phys callback + } + else Mem_Set( &pe, 0, sizeof( pe )); +} + +void Free_PhysicLibrary( void ) +{ + if( physic_dll.link ) + { + pe->Shutdown(); + Mem_Set( &pe, 0, sizeof( pe )); + } + Sys_FreeLibrary( &physic_dll ); +} + +bool PrepareBSPModel( const char *dir, const char *name ) +{ + int numshaders; + + bsp_parms = 0; + maxdist = 0.0; + bsplog = NULL; + + // get global parms + if( FS_CheckParm( "-vis" )) bsp_parms |= BSPLIB_MAKEVIS; + if( FS_CheckParm( "-qrad" )) bsp_parms |= BSPLIB_MAKEQ2RAD; + if( FS_CheckParm( "-hlrad" )) bsp_parms |= BSPLIB_MAKEHLRAD; + if( FS_CheckParm( "-full" )) bsp_parms |= BSPLIB_FULLCOMPILE; + if( FS_CheckParm( "-onlyents" )) bsp_parms |= BSPLIB_ONLYENTS; + if( FS_CheckParm( "-info" )) bsp_parms |= BSPLIB_SHOWINFO; + if( FS_CheckParm( "-cullerror" )) bsp_parms |= BSPLIB_CULLERROR; + if( FS_CheckParm( "-sound" )) bsp_parms |= BSPLIB_MAKESOUND; + if( FS_CheckParm( "-deltemp" )) bsp_parms |= BSPLIB_DELETE_TEMP; + + // famous q1 "notexture" image: purple-black checkerboard + checkermate_dds = FS_LoadInternal( "checkerboard.dds", &checkermate_dds_size ); + Image_Init( NULL, IL_ALLOW_OVERWRITE|IL_IGNORE_MIPS ); + bsplib_compress_bsp = Cvar_Get( "bsp_compress", "0", CVAR_SYSTEMINFO, "compress bsp lumps" ); + + // merge parms + if( bsp_parms & BSPLIB_ONLYENTS ) + { + bsp_parms = (BSPLIB_ONLYENTS|BSPLIB_MAKEBSP); + } + else + { + if( bsp_parms & BSPLIB_MAKEQ2RAD && bsp_parms & BSPLIB_MAKEHLRAD ) + { + MsgDev( D_WARN, "both type 'hlrad' and 'qrad' specified\ndefaulting to 'qrad'\n" ); + bsp_parms &= ~BSPLIB_MAKEHLRAD; + } + if( bsp_parms & BSPLIB_FULLCOMPILE ) + { + if((bsp_parms & BSPLIB_MAKEVIS) && (bsp_parms & (BSPLIB_MAKEQ2RAD|BSPLIB_MAKEHLRAD))) + { + bsp_parms |= BSPLIB_MAKEBSP; // rebuild bsp file for final compile + bsp_parms |= BSPLIB_MAKESOUND; + bsp_parms |= BSPLIB_DELETE_TEMP; + } + } + if(!(bsp_parms & (BSPLIB_MAKEVIS|BSPLIB_MAKEQ2RAD|BSPLIB_MAKEHLRAD))) + { + // -vis -light or -rad not specified, just create a .bsp + bsp_parms |= BSPLIB_MAKEBSP; + } + } + + FS_LoadGameInfo( "gameinfo.txt" ); // same as normal gamemode + Init_PhysicsLibrary(); + + enable_log = true; + numshaders = LoadShaderInfo(); + Msg( "%5i shaderInfo\n", numshaders ); + + return true; +} + +bool CompileBSPModel ( void ) +{ + // now run specified utils + if( bsp_parms & BSPLIB_MAKEBSP ) + WbspMain(); + + if( bsp_parms & (BSPLIB_MAKEVIS|BSPLIB_MAKESOUND)) + WvisMain(); + + if( bsp_parms & (BSPLIB_MAKEQ2RAD|BSPLIB_MAKEHLRAD)) + WradMain(); + + if( bsp_parms & BSPLIB_SHOWINFO ) + PrintBSPFileSizes(); + + Free_PhysicLibrary(); + + // close log before deleting temporaries + enable_log = false; + if( bsplog ) FS_Close( bsplog ); + bsplog = NULL; + + if( bsp_parms & BSPLIB_DELETE_TEMP ) + { + // delete all temporary files after final compile + com.sprintf( path, "%s/maps/%s.prt", com.GameInfo->gamedir, gs_filename ); + FS_Delete( path ); + com.sprintf( path, "%s/maps/%s.lin", com.GameInfo->gamedir, gs_filename ); + FS_Delete( path ); + com.sprintf( path, "%s/maps/%s.log", com.GameInfo->gamedir, gs_filename ); + FS_Delete( path ); + } + return true; +} \ No newline at end of file diff --git a/xtools/bsplib/bsplib.h b/xtools/bsplib/bsplib.h new file mode 100644 index 00000000..3f75f80e --- /dev/null +++ b/xtools/bsplib/bsplib.h @@ -0,0 +1,694 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// bsplib.h - bsplib header +//======================================================================= +#ifndef BSPLIB_H +#define BSPLIB_H + +#include "xtools.h" +#include "engine_api.h" +#include "utils.h" +#include "mathlib.h" +#include "trace_def.h" + +// supported map formats +enum +{ + BRUSH_UNKNOWN = 0, + BRUSH_WORLDCRAFT_21, // quake worldcraft <= 2.1 + BRUSH_WORLDCRAFT_22, // half-life worldcraft >= 2.2 + BRUSH_RADIANT, + BRUSH_QUARK, + BRUSH_COUNT +}; + +#define VALVE_FORMAT 220 +#define MAX_BRUSH_SIDES 128 +#define BOGUS_RANGE MAX_WORLD_COORD +#define TEXINFO_NODE -1 // side is allready on a node +#define MAX_PORTALS 32768 +#define PORTALFILE "PRT1" +#define MAX_POINTS_ON_WINDING 64 +#define PLANENUM_LEAF -1 +#define MAXEDGES 20 +#define MAX_NODE_BRUSHES 8 +#define MAX_PORTALS_ON_LEAF 128 +#define MAX_MAP_SIDES (MAX_MAP_BRUSHES*6) +#define MAX_TEXTURE_FRAMES 256 +#define MAX_PATCHES 65000 // larger will cause 32 bit overflows + +// compile parms +typedef enum +{ + BSPLIB_MAKEBSP = BIT(0), // create a bsp file + BSPLIB_MAKEVIS = BIT(1), // do visibility + BSPLIB_MAKEHLRAD = BIT(2), // do half-life radiosity + BSPLIB_MAKEQ2RAD = BIT(3), // do quake2 radiosity + BSPLIB_FULLCOMPILE = BIT(4), // equals -full for vis, -extra for rad or light + BSPLIB_ONLYENTS = BIT(5), // update only ents lump + BSPLIB_RAD_NOPVS = BIT(6), // ignore pvs while processing radiocity (kill smooth light) + BSPLIB_RAD_NOBLOCK = BIT(7), + BSPLIB_RAD_NOCOLOR = BIT(8), + BSPLIB_DELETE_TEMP = BIT(9), // delete itermediate files + BSPLIB_SHOWINFO = BIT(10), + BSPLIB_CULLERROR = BIT(11), + BSPLIB_MAKESOUND = BIT(12), // calculate ambient sounds +} bsplibFlags_t; + +extern uint bsp_parms; +extern char path[MAX_SYSPATH]; +extern cvar_t *bsplib_compress_bsp; +extern float maxdist; + +// bsplib export functions +void WradMain( void ); +void WvisMain( void ); +void WbspMain( void ); + +typedef struct plane_s +{ + vec3_t normal; + vec_t dist; + int type; + struct plane_s *hash_chain; +} plane_t; + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +typedef struct epair_s +{ + struct epair_s *next; + char *key; + char *value; +} epair_t; + +typedef struct +{ + vec3_t origin; + int firstbrush; + int numbrushes; + epair_t *epairs; + + // only valid for func_areaportals + int areaportalnum; + int portalareas[2]; +} bsp_entity_t; + +typedef struct +{ + vec3_t UAxis; + vec3_t VAxis; + float shift[2]; + float rotate; + float scale[2]; +} wrl_vecs; + +typedef struct +{ + float vecs[2][4]; +} qrk_vecs; + +typedef struct +{ + float matrix[2][3]; +} q3a_vecs; + +typedef struct +{ + string name; + int surfaceFlags; + int contents; + vec3_t color; + int intensity; + + bool hasPasses; +} bsp_shader_t; + +typedef union +{ + qrk_vecs quark; + wrl_vecs hammer; + q3a_vecs radiant; +} vects_u; + +typedef struct +{ + vects_u vects; + string name; + int size[2]; + int brush_type; + int contents; + int flags; + int value; +} brush_texture_t; + +typedef struct side_s +{ + int planenum; + int texinfo; + winding_t *winding; + struct side_s *original; // bspbrush_t sides will reference the mapbrush_t sides + int contents; // from miptex + int surf; // from miptex + bool visible; // choose visble planes first + bool tested; // this plane allready checked as a split + bool bevel; // don't ever use for bsp splitting +} side_t; + +typedef struct mapbrush_s +{ + int entitynum; + int brushnum; + int contents; + + int shadernum; + vec3_t mins, maxs; + int numsides; + side_t *original_sides; +} mapbrush_t; + +typedef struct face_s +{ + struct face_s *next; // on node + + // the chain of faces off of a node can be merged or split, + // but each face_t along the way will remain in the chain + // until the entire tree is freed + struct face_s *merged; // if set, this face isn't valid anymore + struct face_s *split[2]; // if set, this face isn't valid anymore + + struct portal_s *portal; + int texinfo; + int planenum; + int contents; // faces in different contents can't merge + int outputnumber; + winding_t *w; + int numpoints; + bool badstartvert; // tjunctions cannot be fixed without a midpoint vertex + int vertexnums[MAXEDGES]; +} face_t; + +typedef struct bspbrush_s +{ + struct bspbrush_s *next; + vec3_t mins, maxs; + int side, testside; // side of node during construction + mapbrush_t *original; + int numsides; + side_t sides[6]; // variably sized +} bspbrush_t; + +typedef struct node_s +{ + // both leafs and nodes + int planenum; // -1 = leaf node + struct node_s *parent; + vec3_t mins, maxs; // valid after portalization + bspbrush_t *volume; // one for each leaf/node + + // nodes only + bool detail_seperator; // a detail brush caused the split + side_t *side; // the side that created the node + struct node_s *children[2]; + face_t *faces; + + // leafs only + bspbrush_t *brushlist; // fragments of all brushes in this leaf + int contents; // OR of all brush contents + int occupied; // 1 or greater can reach entity + bsp_entity_t *occupant; // for leak file testing + int cluster; // for portalfile writing + int area; // for areaportals + struct portal_s *portals; // also on nodes during construction +} node_t; + +typedef struct portal_s +{ + plane_t plane; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + winding_t *winding; + + bool sidefound; // false if ->side hasn't been checked + side_t *side; // NULL = non-visible + face_t *face[2]; // output face in bsp file +} portal_t; + +typedef struct +{ + node_t *headnode; + node_t outside_node; + vec3_t mins, maxs; +} tree_t; + +typedef struct +{ + vec3_t normal; + float dist; +} visplane_t; + +typedef struct +{ + bool original; // don't free, it's part of the portal + int numpoints; + vec3_t points[12]; // variable sized +} viswinding_t; + +typedef enum {stat_none, stat_working, stat_done} vstatus_t; +typedef struct +{ + visplane_t plane; // normal pointing into neighbor + int leaf; // neighbor + int owner_leaf; // neighbor + + vec3_t origin; // for fast clip testing + float radius; + + viswinding_t *winding; + vstatus_t status; + byte *portalfront; // [portals], preliminary + byte *portalflood; // [portals], intermediate + byte *portalvis; // [portals], final + + int nummightsee; // bit count on portalflood for sort +} visportal_t; + +typedef struct seperating_plane_s +{ + struct seperating_plane_s *next; + visplane_t plane; // from portal is on positive side +} sep_t; + + +typedef struct passage_s +{ + struct passage_s *next; + int from, to; // leaf numbers + sep_t *planes; +} passage_t; + +typedef struct leaf_s +{ + int numportals; + passage_t *passages; + visportal_t *portals[MAX_PORTALS_ON_LEAF]; +} leaf_t; + + +typedef struct pstack_s +{ + byte mightsee[MAX_PORTALS/8]; // bit string + struct pstack_s *next; + leaf_t *leaf; + visportal_t *portal; // portal exiting + viswinding_t *source; + viswinding_t *pass; + + viswinding_t windings[3]; // source, pass, temp in any order + int freewindings[3]; + + visplane_t portalplane; +} pstack_t; + +typedef struct +{ + visportal_t *base; + int c_chains; + pstack_t pstack_head; +} threaddata_t; + +extern int num_entities; +extern bsp_entity_t entities[MAX_MAP_ENTITIES]; +extern file_t *bsplog; +extern script_t *mapfile; + +void ParseEntities( void ); +void UnparseEntities( void ); +void Com_CheckToken( script_t *script, const char *match ); +void Com_Parse1DMatrix( script_t *script, int x, vec_t *m ); +void Com_Parse2DMatrix( script_t *script, int y, int x, vec_t *m ); +void SetKeyValue( bsp_entity_t *ent, const char *key, const char *value ); +char *ValueForKey( const bsp_entity_t *ent, const char *key ); // will return "" if not present +vec_t FloatForKey( const bsp_entity_t *ent, const char *key ); +long IntForKey( const bsp_entity_t *ent, const char *key ); +void GetVectorForKey( const bsp_entity_t *ent, const char *key, vec3_t vec ); +bsp_entity_t *FindTargetEntity( const char *target ); +void BSP_PrintLog( const char *pMsg ); +void FindMapMessage( char *message ); +epair_t *ParseEpair( script_t *script, token_t *token ); +void PrintBSPFileSizes( void ); + +extern int entity_num; +extern int g_mapversion; +extern plane_t mapplanes[MAX_MAP_PLANES]; +extern int nummapplanes; +extern int nummapbrushes; +extern mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; +extern vec3_t map_mins, map_maxs; +extern int nummapbrushsides; +extern side_t brushsides[MAX_MAP_SIDES]; +extern int nummodels; +extern dmodel_t dmodels[MAX_MAP_MODELS]; +extern int visdatasize; +extern byte dvisdata[MAX_MAP_VISIBILITY]; +extern dvis_t *dvis; +extern int lightdatasize; +extern byte dlightdata[MAX_MAP_LIGHTING]; +extern int entdatasize; +extern char dentdata[MAX_MAP_ENTSTRING]; +extern int numleafs; +extern dleaf_t dleafs[MAX_MAP_LEAFS]; +extern int numplanes; +extern dplane_t dplanes[MAX_MAP_PLANES]; +extern int numvertexes; +extern dvertex_t dvertexes[MAX_MAP_VERTS]; +extern int numnodes; +extern dnode_t dnodes[MAX_MAP_NODES]; +extern int numtexinfo; +extern dtexinfo_t texinfo[MAX_MAP_TEXINFO]; +extern int numsurfaces; +extern dsurface_t dsurfaces[MAX_MAP_SURFACES]; +extern int numedges; +extern dedge_t dedges[MAX_MAP_EDGES]; +extern int numleafsurfaces; +extern dleafface_t dleafsurfaces[MAX_MAP_LEAFFACES]; +extern int numleafbrushes; +extern dleafbrush_t dleafbrushes[MAX_MAP_LEAFBRUSHES]; +extern int numsurfedges; +extern dsurfedge_t dsurfedges[MAX_MAP_SURFEDGES]; +extern int numareas; +extern darea_t dareas[MAX_MAP_AREAS]; +extern int numareaportals; +extern dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; +extern int numbrushes; +extern dbrush_t dbrushes[MAX_MAP_BRUSHES]; +extern int numbrushsides; +extern dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; +extern int dcollisiondatasize; +extern byte dcollision[MAX_MAP_COLLISION]; +extern dshader_t dshaders[MAX_MAP_SHADERS]; +extern int numshaders; + +void LoadMapFile ( void ); +int FindFloatPlane (vec3_t normal, vec_t dist); +bool LoadBSPFile ( void ); +void WriteBSPFile ( void ); +void DecompressVis (byte *in, byte *decompressed); +int CompressVis (byte *vis, byte *dest); + +//============================================================================= +// bsplib.c + +void ProcessCollisionTree( void ); + +//============================================================================= +// textures.c + +int FindMiptex( const char *name ); +int TexinfoForBrushTexture( plane_t *plane, brush_texture_t *bt, vec3_t origin ); + +//============================================================================= + +mapbrush_t *Brush_LoadEntity (bsp_entity_t *ent); +int PlaneTypeForNormal (vec3_t normal); +bool MakeBrushPlanes (mapbrush_t *b); +int FindIntPlane (int *inormal, int *iorigin); +void CreateBrush (int brushnum); + +//============================================================================= + +// csg + +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs); +bspbrush_t *ChopBrushes (bspbrush_t *head); +bspbrush_t *InitialBrushList (bspbrush_t *list); +bspbrush_t *OptimizedBrushList (bspbrush_t *list); + +//============================================================================= + +// brushbsp + +void WriteBrushList (char *name, bspbrush_t *brush, bool onlyvis); +bspbrush_t *CopyBrush (bspbrush_t *brush); +void SplitBrush (bspbrush_t *brush, int planenum, bspbrush_t **front, bspbrush_t **back); + +tree_t *AllocTree (void); +node_t *AllocNode (void); +bspbrush_t *AllocBrush (int numsides); +int CountBrushList (bspbrush_t *brushes); +void FreeBrush (bspbrush_t *brushes); +vec_t BrushVolume (bspbrush_t *brush); +void BoundBrush (bspbrush_t *brush); +void FreeBrushList (bspbrush_t *brushes); +tree_t *BrushBSP (bspbrush_t *brushlist, vec3_t mins, vec3_t maxs); + +//============================================================================= + +// winding.c + +winding_t *AllocWinding (int points); +vec_t WindingArea (winding_t *w); +void WindingCenter (winding_t *w, vec3_t center); +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back); +winding_t *CopyWinding (winding_t *w); +winding_t *ReverseWinding (winding_t *w); +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist); +void CheckWinding (winding_t *w); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist); +void RemoveColinearPoints (winding_t *w); +void FreeWinding (winding_t *w); +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs); +void ChopWindingInPlace (winding_t **w, vec3_t normal, vec_t dist, vec_t epsilon); +//============================================================================= + +// portals.c + +int VisibleContents (int contents); + +void MakeHeadnodePortals (tree_t *tree); +void MakeNodePortal (node_t *node); +void SplitNodePortals (node_t *node); + +bool Portal_VisFlood (portal_t *p); + +bool FloodEntities (tree_t *tree); +void FillOutside (node_t *headnode); +void FloodAreas (tree_t *tree); +void MarkVisibleSides (tree_t *tree, int start, int end); +void FreePortal (portal_t *p); +void EmitAreaPortals (node_t *headnode); + +void MakeTreePortals (tree_t *tree); + +//============================================================================= +// shaders.c + +int LoadShaderInfo( void ); +bsp_shader_t *FindShader( const char *texture ); + +//============================================================================= +// leakfile.c + +void LeakFile (tree_t *tree); + +//============================================================================= +// prtfile.c + +void NumberClusters( tree_t *tree ); +void WritePortalFile( tree_t *tree ); + +//============================================================================= + +// writebsp.c + +void SetModelNumbers (void); +void SetLightStyles (void); + +void BeginBSPFile (void); +void WriteBSP (node_t *headnode); +void EndBSPFile (void); +void BeginModel (void); +void EndModel (void); + +//============================================================================= + +// faces.c + +void MakeFaces (node_t *headnode); +void FixTjuncs (node_t *headnode); +int GetEdge2 (int v1, int v2, face_t *f); + +face_t *AllocFace (void); +void FreeFace (face_t *f); + +void MergeNodeFaces (node_t *node); + +//============================================================================= + +// tree.c + +void FreeTree (tree_t *tree); +void FreeTree_r (node_t *node); +void PrintTree_r (node_t *node, int depth); +void FreeTreePortals_r (node_t *node); +void PruneNodes_r (node_t *node); +void PruneNodes (node_t *node); + +//============================================================================= + +// vis.c + +viswinding_t *NewVisWinding (int points); +void FreeVisWinding (viswinding_t *w); +viswinding_t *CopyVisWinding (viswinding_t *w); + +extern int numportals; +extern int portalclusters; + +extern visportal_t *portals; +extern leaf_t *leafs; + +extern int c_portaltest, c_portalpass, c_portalcheck; +extern int c_portalskip, c_leafskip; +extern int c_vistest, c_mighttest; +extern int c_chains; + +extern byte *vismap, *vismap_p, *vismap_end; // past visfile + +extern int testlevel; + +extern byte *uncompressedvis; + +extern int leafbytes, leaflongs; +extern int portalbytes, portallongs; + + +void LeafFlow (int leafnum); +void BasePortalVis (int portalnum); +void BetterPortalVis (int portalnum); +void PortalFlow (int portalnum); + +extern visportal_t *sorted_portals[MAX_MAP_PORTALS*2]; + +int CountBits( byte *bits, int numbits ); +int PointInLeafnum( vec3_t point ); +dleaf_t *PointInLeaf( vec3_t point ); +bool PvsForOrigin( vec3_t org, byte *pvs ); +byte *PhsForCluster( int cluster ); +void CalcAmbientSounds( void ); + +//============================================================================= + +// rad.c +#define LIGHTDISTBIAS 6800.0 + +typedef enum +{ + emit_surface, + emit_point, + emit_spotlight, + emit_skylight +} emittype_t; + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + int pad; +} tnode_t; + +// the sum of all tranfer->transfer values for a given patch +// should equal exactly 0x10000, showing that all radiance +// reaches other patches +typedef struct +{ + word patch; + word transfer; +} transfer_t; + +typedef struct patch_s +{ + winding_t *winding; + struct patch_s *next; // next in face + int numtransfers; + transfer_t *transfers; + + int cluster; // for pvs checking + vec3_t origin; + dplane_t *plane; + + bool sky; + + vec3_t totallight; // accumulated by radiosity + // does NOT include light + // accounted for by direct lighting + float area; + + // illuminance * reflectivity = radiosity + vec3_t reflectivity; + vec3_t baselight; // emissivity only + + // each style 0 lightmap sample in the patch will be + // added up to get the average illuminance of the entire patch + vec3_t samplelight; + int samples; // for averaging direct light +} patch_t; + +extern patch_t *face_patches[MAX_MAP_SURFACES]; +extern bsp_entity_t *face_entity[MAX_MAP_SURFACES]; +extern vec3_t face_offset[MAX_MAP_SURFACES]; // for rotating bmodels +extern patch_t patches[MAX_PATCHES]; +extern tnode_t *tnodes; +extern uint num_patches; + +extern int leafparents[MAX_MAP_LEAFS]; +extern int nodeparents[MAX_MAP_NODES]; + +extern float lightscale; +extern float ambient; + +void MakeShadowSplits (void); + +//============================================== + +extern float ambient, maxlight; + +void LinkPlaneFaces (void); + +extern int numbounce; + +extern byte nodehit[MAX_MAP_NODES]; + +void BuildLightmaps (void); + +void BuildFacelights (int facenum); + +void FinalLightFace (int facenum); +int TestLine_r (int node, vec3_t start, vec3_t stop); + +void CreateDirectLights (void); + +extern dplane_t backplanes[MAX_MAP_PLANES]; +extern int fakeplanes;// created planes for origin offset + +extern float subdiv; + +extern float direct_scale; +extern float entity_scale; + +void MakeTnodes (dmodel_t *bm); +void MakePatches (void); +void SubdividePatches (void); +void PairEdges (void); +void CalcTextureReflectivity (void); + +#endif//BSPLIB_H \ No newline at end of file diff --git a/xtools/bsplib/csg.c b/xtools/bsplib/csg.c new file mode 100644 index 00000000..894f8948 --- /dev/null +++ b/xtools/bsplib/csg.c @@ -0,0 +1,533 @@ + +#include "bsplib.h" +#include "const.h" + +/* + +tag all brushes with original contents +brushes may contain multiple contents +there will be no brush overlap after csg phase + + + + +each side has a count of the other sides it splits + +the best split will be the one that minimizes the total split counts +of all remaining sides + +precalc side on plane table + +evaluate split side +{ +cost = 0 +for all sides + for all sides + get + if side splits side and splitside is on same child + cost++; +} + + + */ + +void SplitBrush2 (bspbrush_t *brush, int planenum, + bspbrush_t **front, bspbrush_t **back) +{ + SplitBrush (brush, planenum, front, back); +#if 0 + if (*front && (*front)->sides[(*front)->numsides-1].texinfo == -1) + (*front)->sides[(*front)->numsides-1].texinfo = (*front)->sides[0].texinfo; // not -1 + if (*back && (*back)->sides[(*back)->numsides-1].texinfo == -1) + (*back)->sides[(*back)->numsides-1].texinfo = (*back)->sides[0].texinfo; // not -1 +#endif +} + +/* +=============== +SubtractBrush + +Returns a list of brushes that remain after B is subtracted from A. +May by empty if A is contained inside B. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *SubtractBrush (bspbrush_t *a, bspbrush_t *b) +{ // a - b = out (list) + int i; + bspbrush_t *front, *back; + bspbrush_t *out, *in; + + in = a; + out = NULL; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + { // add to list + front->next = out; + out = front; + } + in = back; + } + if (in) + FreeBrush (in); + else + { // didn't really intersect + FreeBrushList (out); + return a; + } + return out; +} + +/* +=============== +IntersectBrush + +Returns a single brush made up by the intersection of the +two provided brushes, or NULL if they are disjoint. + +The originals are undisturbed. +=============== +*/ +bspbrush_t *IntersectBrush (bspbrush_t *a, bspbrush_t *b) +{ + int i; + bspbrush_t *front, *back; + bspbrush_t *in; + + in = a; + for (i=0 ; inumsides && in ; i++) + { + SplitBrush2 (in, b->sides[i].planenum, &front, &back); + if (in != a) + FreeBrush (in); + if (front) + FreeBrush (front); + in = back; + } + + if (in == a) + return NULL; + + in->next = NULL; + return in; +} + + +/* +=============== +BrushesDisjoint + +Returns true if the two brushes definately do not intersect. +There will be false negatives for some non-axial combinations. +=============== +*/ +bool BrushesDisjoint (bspbrush_t *a, bspbrush_t *b) +{ + int i, j; + + // check bounding boxes + for (i=0 ; i<3 ; i++) + if (a->mins[i] >= b->maxs[i] + || a->maxs[i] <= b->mins[i]) + return true; // bounding boxes don't overlap + + // check for opposing planes + for (i=0 ; inumsides ; i++) + { + for (j=0 ; jnumsides ; j++) + { + if (a->sides[i].planenum == + (b->sides[j].planenum^1) ) + return true; // opposite planes, so not touching + } + } + + return false; // might intersect +} + +int minplanenums[3]; +int maxplanenums[3]; + +/* +=============== +ClipBrushToBox + +Any planes shared with the box edge will be set to no texinfo +=============== +*/ +bspbrush_t *ClipBrushToBox (bspbrush_t *brush, vec3_t clipmins, vec3_t clipmaxs) +{ + int i, j; + bspbrush_t *front, *back; + int p; + + for (j=0 ; j<2 ; j++) + { + if (brush->maxs[j] > clipmaxs[j]) + { + SplitBrush (brush, maxplanenums[j], &front, &back); + if (front) + FreeBrush (front); + brush = back; + if (!brush) + return NULL; + } + if (brush->mins[j] < clipmins[j]) + { + SplitBrush (brush, minplanenums[j], &front, &back); + if (back) + FreeBrush (back); + brush = front; + if (!brush) + return NULL; + } + } + + // remove any colinear faces + + for (i=0 ; inumsides ; i++) + { + p = brush->sides[i].planenum & ~1; + if (p == maxplanenums[0] || p == maxplanenums[1] + || p == minplanenums[0] || p == minplanenums[1]) + { + brush->sides[i].texinfo = TEXINFO_NODE; + brush->sides[i].visible = false; + } + } + return brush; +} + +/* +=============== +MakeBspBrushList +=============== +*/ +bspbrush_t *MakeBspBrushList (int startbrush, int endbrush, + vec3_t clipmins, vec3_t clipmaxs) +{ + mapbrush_t *mb; + bspbrush_t *brushlist, *newbrush; + int i, j; + int c_faces; + int c_brushes; + int numsides; + int vis; + vec3_t normal; + float dist; + + for (i=0 ; i<2 ; i++) + { + VectorClear (normal); + normal[i] = 1; + dist = clipmaxs[i]; + maxplanenums[i] = FindFloatPlane (normal, dist); + dist = clipmins[i]; + minplanenums[i] = FindFloatPlane (normal, dist); + } + + brushlist = NULL; + c_faces = 0; + c_brushes = 0; + + for (i=startbrush ; inumsides; + if (!numsides) + continue; + // make sure the brush has at least one face showing + vis = 0; + for (j=0 ; joriginal_sides[j].visible && mb->original_sides[j].winding) + vis++; +#if 0 + if (!vis) + continue; // no faces at all +#endif + // if the brush is outside the clip area, skip it + for (j=0 ; j<3 ; j++) + if (mb->mins[j] >= clipmaxs[j] + || mb->maxs[j] <= clipmins[j]) + break; + if (j != 3) + continue; + + // + // make a copy of the brush + // + newbrush = AllocBrush (mb->numsides); + newbrush->original = mb; + newbrush->numsides = mb->numsides; + memcpy (newbrush->sides, mb->original_sides, numsides*sizeof(side_t)); + for (j=0 ; jsides[j].winding) + newbrush->sides[j].winding = CopyWinding (newbrush->sides[j].winding); + if (newbrush->sides[j].surf & SURF_HINT) + newbrush->sides[j].visible = true; // hints are always visible + } + VectorCopy (mb->mins, newbrush->mins); + VectorCopy (mb->maxs, newbrush->maxs); + + // + // carve off anything outside the clip box + // + newbrush = ClipBrushToBox (newbrush, clipmins, clipmaxs); + if (!newbrush) + continue; + + c_faces += vis; + c_brushes++; + + newbrush->next = brushlist; + brushlist = newbrush; + } + + return brushlist; +} + +/* +=============== +AddBspBrushListToTail +=============== +*/ +bspbrush_t *AddBrushListToTail (bspbrush_t *list, bspbrush_t *tail) +{ + bspbrush_t *walk, *next; + + for (walk=list ; walk ; walk=next) + { // add to end of list + next = walk->next; + walk->next = NULL; + tail->next = walk; + tail = walk; + } + + return tail; +} + +/* +=========== +CullList + +Builds a new list that doesn't hold the given brush +=========== +*/ +bspbrush_t *CullList (bspbrush_t *list, bspbrush_t *skip1) +{ + bspbrush_t *newlist; + bspbrush_t *next; + + newlist = NULL; + + for ( ; list ; list = next) + { + next = list->next; + if (list == skip1) + { + FreeBrush (list); + continue; + } + list->next = newlist; + newlist = list; + } + return newlist; +} + +/* +================== +BrushGE + +Returns true if b1 is allowed to bite b2 +================== +*/ +bool BrushGE (bspbrush_t *b1, bspbrush_t *b2) +{ + // detail brushes never bite structural brushes + if ( (b1->original->contents & CONTENTS_DETAIL) + && !(b2->original->contents & CONTENTS_DETAIL) ) + return false; + if (b1->original->contents & CONTENTS_SOLID) + return true; + return false; +} + +/* +================= +ChopBrushes + +Carves any intersecting solid brushes into the minimum number +of non-intersecting brushes. +================= +*/ +bspbrush_t *ChopBrushes (bspbrush_t *head) +{ + bspbrush_t *b1, *b2, *next; + bspbrush_t *tail; + bspbrush_t *keep; + bspbrush_t *sub, *sub2; + int c1, c2; + + keep = NULL; + +newlist: + // find tail + if (!head) + return NULL; + for (tail=head ; tail->next ; tail=tail->next) + ; + + for (b1=head ; b1 ; b1=next) + { + next = b1->next; + for (b2=b1->next ; b2 ; b2 = b2->next) + { + if (BrushesDisjoint (b1, b2)) + continue; + + sub = NULL; + sub2 = NULL; + c1 = 999999; + c2 = 999999; + + if ( BrushGE (b2, b1) ) + { + sub = SubtractBrush (b1, b2); + if (sub == b1) + continue; // didn't really intersect + if (!sub) + { // b1 is swallowed by b2 + head = CullList (b1, b1); + goto newlist; + } + c1 = CountBrushList (sub); + } + + if ( BrushGE (b1, b2) ) + { + sub2 = SubtractBrush (b2, b1); + if (sub2 == b2) + continue; // didn't really intersect + if (!sub2) + { // b2 is swallowed by b1 + FreeBrushList (sub); + head = CullList (b1, b2); + goto newlist; + } + c2 = CountBrushList (sub2); + } + + if (!sub && !sub2) + continue; // neither one can bite + + // only accept if it didn't fragment + // (commening this out allows full fragmentation) + if (c1 > 1 && c2 > 1) + { + if (sub2) + FreeBrushList (sub2); + if (sub) + FreeBrushList (sub); + continue; + } + + if (c1 < c2) + { + if (sub2) + FreeBrushList (sub2); + tail = AddBrushListToTail (sub, tail); + head = CullList (b1, b1); + goto newlist; + } + else + { + if (sub) + FreeBrushList (sub); + tail = AddBrushListToTail (sub2, tail); + head = CullList (b1, b2); + goto newlist; + } + } + + if (!b2) + { // b1 is no longer intersecting anything, so keep it + b1->next = keep; + keep = b1; + } + } + return keep; +} + + +/* +================= +InitialBrushList +================= +*/ +bspbrush_t *InitialBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { +#if 0 + for (i=0 ; inumsides ; i++) + if (b->sides[i].visible) + break; + if (i == b->numsides) + continue; +#endif + newb = CopyBrush (b); + newb->next = out; + out = newb; + + // clear visible, so it must be set by MarkVisibleFaces_r + // to be used in the optimized list + for (i=0 ; inumsides ; i++) + { + newb->sides[i].original = &b->sides[i]; +// newb->sides[i].visible = true; + b->sides[i].visible = false; + } + } + + return out; +} + +/* +================= +OptimizedBrushList +================= +*/ +bspbrush_t *OptimizedBrushList (bspbrush_t *list) +{ + bspbrush_t *b; + bspbrush_t *out, *newb; + int i; + + // only return brushes that have visible faces + out = NULL; + for (b=list ; b ; b=b->next) + { + for (i=0 ; inumsides ; i++) + if (b->sides[i].visible) + break; + if (i == b->numsides) + continue; + newb = CopyBrush (b); + newb->next = out; + out = newb; + } + return out; +} diff --git a/xtools/bsplib/faces.c b/xtools/bsplib/faces.c new file mode 100644 index 00000000..9506a1a6 --- /dev/null +++ b/xtools/bsplib/faces.c @@ -0,0 +1,1011 @@ +// faces.c + +#include "bsplib.h" +#include "const.h" + +/* + + some faces will be removed before saving, but still form nodes: + + the insides of sky volumes + meeting planes of different water current volumes + +*/ + +// undefine for dumb linear searches +#define USE_HASHING + +#define INTEGRAL_EPSILON 0.01 +#define POINT_EPSILON 0.5 +#define OFF_EPSILON 0.5 + +int c_merge; +int c_subdivide; + +int c_totalverts; +int c_uniqueverts; +int c_degenerate; +int c_tjunctions; +int c_faceoverflows; +int c_facecollapse; +int c_badstartverts; + +#define MAX_SUPERVERTS 512 +int superverts[MAX_SUPERVERTS]; +int numsuperverts; + +face_t *edgefaces[MAX_MAP_EDGES][2]; +int firstmodeledge = 1; +int firstmodelface; +int c_tryedges; + +vec3_t edge_dir; +vec3_t edge_start; +vec_t edge_len; + +int num_edge_verts; +int edge_verts[MAX_MAP_VERTS]; + +float subdivide_size = 512; + +face_t *NewFaceFromFace (face_t *f); + +//=========================================================================== + +typedef struct hashvert_s +{ + struct hashvert_s *next; + int num; +} hashvert_t; + + +#define HASH_SIZE 512 + + +int vertexchain[MAX_MAP_VERTS]; // the next vertex in a hash chain +int hashverts[HASH_SIZE*HASH_SIZE]; // a vertex number, or 0 for no verts + +face_t *edgefaces[MAX_MAP_EDGES][2]; + +//============================================================================ + + +unsigned HashVec (vec3_t vec) +{ + int x, y; + + x = (MAX_WORLD_COORD + (int)(vec[0]+0.5)) >> 16; + y = (MAX_WORLD_COORD + (int)(vec[1]+0.5)) >> 16; + + if ( x < 0 || x >= HASH_SIZE || y < 0 || y >= HASH_SIZE ) + Sys_Error( "HashVec: point outside valid range\n" ); + + return y*HASH_SIZE + x; +} + +#ifdef USE_HASHING +/* +============= +GetVertex + +Uses hashing +============= +*/ +int GetVertexnum (vec3_t in) +{ + int h; + int i; + float *p; + vec3_t vert; + int vnum; + + c_totalverts++; + + for (i=0 ; i<3 ; i++) + { + if ( fabs(in[i] - floor(in[i] + 0.5)) < INTEGRAL_EPSILON) + vert[i] = floor(in[i] + 0.5); + else + vert[i] = in[i]; + } + + h = HashVec (vert); + + for (vnum=hashverts[h] ; vnum ; vnum=vertexchain[vnum]) + { + p = dvertexes[vnum].point; + if ( fabs(p[0]-vert[0]) 131072) + Sys_Error ("GetVertexnum: outside +/- 131072"); + } + + // search for an existing vertex match + for (i=0, dv=dvertexes ; ipoint[j]; + if ( d > POINT_EPSILON || d < -POINT_EPSILON) + break; + } + if (j == 3) + return i; // a match + } + + // new point + if (numvertexes == MAX_MAP_VERTS) + Sys_Error ("MAX_MAP_VERTS"); + VectorCopy (v, dv->point); + numvertexes++; + c_uniqueverts++; + + return numvertexes-1; +} +#endif + + +/* +================== +FaceFromSuperverts + +The faces vertexes have beeb added to the superverts[] array, +and there may be more there than can be held in a face (MAXEDGES). + +If less, the faces vertexnums[] will be filled in, otherwise +face will reference a tree of split[] faces until all of the +vertexnums can be added. + +superverts[base] will become face->vertexnums[0], and the others +will be circularly filled in. +================== +*/ +void FaceFromSuperverts (node_t *node, face_t *f, int base) +{ + face_t *newf; + int remaining; + int i; + + remaining = numsuperverts; + while (remaining > MAXEDGES) + { // must split into two faces, because of vertex overload + c_faceoverflows++; + + newf = f->split[0] = NewFaceFromFace (f); + newf = f->split[0]; + newf->next = node->faces; + node->faces = newf; + + newf->numpoints = MAXEDGES; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; + + f->split[1] = NewFaceFromFace (f); + f = f->split[1]; + f->next = node->faces; + node->faces = f; + + remaining -= (MAXEDGES-2); + base = (base+MAXEDGES-1)%numsuperverts; + } + + // copy the vertexes back to the face + f->numpoints = remaining; + for (i=0 ; ivertexnums[i] = superverts[(i+base)%numsuperverts]; +} + + +/* +================== +EmitFaceVertexes +================== +*/ +void EmitFaceVertexes (node_t *node, face_t *f) +{ + winding_t *w; + int i; + + if (f->merged || f->split[0] || f->split[1]) + return; + + w = f->w; + for (i=0 ; inumpoints ; i++) + { + superverts[i] = GetVertexnum (w->p[i]); + } + numsuperverts = w->numpoints; + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, 0); +} + +/* +================== +EmitVertexes_r +================== +*/ +void EmitVertexes_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + { + EmitFaceVertexes (node, f); + } + + for (i=0 ; i<2 ; i++) + EmitVertexes_r (node->children[i]); +} + + +#ifdef USE_HASHING +/* +========== +FindEdgeVerts + +Uses the hash tables to cut down to a small number +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int x1, x2, y1, y2, t; + int x, y; + int vnum; + +#if 0 +{ + int i; + num_edge_verts = numvertexes-1; + for (i=0 ; i> 7; + y1 = (131072 + (int)(v1[1]+0.5)) >> 7; + x2 = (131072 + (int)(v2[0]+0.5)) >> 7; + y2 = (131072 + (int)(v2[1]+0.5)) >> 7; + + if (x1 > x2) + { + t = x1; + x1 = x2; + x2 = t; + } + if (y1 > y2) + { + t = y1; + y1 = y2; + y2 = t; + } +#if 0 + x1--; + x2++; + y1--; + y2++; + if (x1 < 0) + x1 = 0; + if (x2 >= HASH_SIZE) + x2 = HASH_SIZE; + if (y1 < 0) + y1 = 0; + if (y2 >= HASH_SIZE) + y2 = HASH_SIZE; +#endif + num_edge_verts = 0; + for (x=x1 ; x <= x2 ; x++) + { + for (y=y1 ; y <= y2 ; y++) + { + for (vnum=hashverts[y*HASH_SIZE+x] ; vnum ; vnum=vertexchain[vnum]) + { + edge_verts[num_edge_verts++] = vnum; + } + } + } +} + +#else +/* +========== +FindEdgeVerts + +Forced a dumb check of everything +========== +*/ +void FindEdgeVerts (vec3_t v1, vec3_t v2) +{ + int i; + + num_edge_verts = numvertexes-1; + for (i=0 ; i= end) + continue; // off an end + VectorMA (edge_start, dist, edge_dir, exact); + VectorSubtract (p, exact, off); + error = VectorLength (off); + + if (fabs(error) > OFF_EPSILON) + continue; // not on the edge + + // break the edge + c_tjunctions++; + TestEdge (start, dist, p1, j, k+1); + TestEdge (dist, end, j, p2, k+1); + return; + } + + // the edge p1 to p2 is now free of tjunctions + if (numsuperverts >= MAX_SUPERVERTS) + Sys_Error ("MAX_SUPERVERTS"); + superverts[numsuperverts] = p1; + numsuperverts++; +} + +/* +================== +FixFaceEdges + +================== +*/ +void FixFaceEdges (node_t *node, face_t *f) +{ + int p1, p2; + int i; + vec3_t e2; + vec_t len; + int count[MAX_SUPERVERTS], start[MAX_SUPERVERTS]; + int base; + + if (f->merged || f->split[0] || f->split[1]) + return; + + numsuperverts = 0; + + for (i=0 ; inumpoints ; i++) + { + p1 = f->vertexnums[i]; + p2 = f->vertexnums[(i+1)%f->numpoints]; + + VectorCopy (dvertexes[p1].point, edge_start); + VectorCopy (dvertexes[p2].point, e2); + + FindEdgeVerts (edge_start, e2); + + VectorSubtract (e2, edge_start, edge_dir); + len = VectorNormalizeLength( edge_dir ); + + start[i] = numsuperverts; + TestEdge (0, len, p1, p2, 0); + + count[i] = numsuperverts - start[i]; + } + + if (numsuperverts < 3) + { // entire face collapsed + f->numpoints = 0; + c_facecollapse++; + return; + } + + // we want to pick a vertex that doesn't have tjunctions + // on either side, which can cause artifacts on trifans, + // especially underwater + for (i=0 ; inumpoints ; i++) + { + if (count[i] == 1 && count[(i+f->numpoints-1)%f->numpoints] == 1) + break; + } + if (i == f->numpoints) + { + f->badstartvert = true; + c_badstartverts++; + base = 0; + } + else + { // rotate the vertex order + base = start[i]; + } + + // this may fragment the face if > MAXEDGES + FaceFromSuperverts (node, f, base); +} + +/* +================== +FixEdges_r +================== +*/ +void FixEdges_r (node_t *node) +{ + int i; + face_t *f; + + if (node->planenum == PLANENUM_LEAF) + return; + + for (f=node->faces ; f ; f=f->next) + FixFaceEdges (node, f); + + for (i=0 ; i<2 ; i++) + FixEdges_r (node->children[i]); +} + +/* +=========== +FixTjuncs + +=========== +*/ +void FixTjuncs (node_t *headnode) +{ + // snap and merge all vertexes + memset (hashverts, 0, sizeof(hashverts)); + c_totalverts = 0; + c_uniqueverts = 0; + c_faceoverflows = 0; + EmitVertexes_r (headnode); + + // break edges on tjunctions + c_tryedges = 0; + c_degenerate = 0; + c_facecollapse = 0; + c_tjunctions = 0; + FixEdges_r (headnode); +} + + +//======================================================== + +int c_faces; + +face_t *AllocFace (void) +{ + face_t *f; + + f = Malloc(sizeof(*f)); + c_faces++; + + return f; +} + +face_t *NewFaceFromFace (face_t *f) +{ + face_t *newf; + + newf = AllocFace (); + *newf = *f; + newf->merged = NULL; + newf->split[0] = newf->split[1] = NULL; + newf->w = NULL; + return newf; +} + +void FreeFace (face_t *f) +{ + if (f->w) + FreeWinding (f->w); + Mem_Free (f); + c_faces--; +} + +//======================================================== + +/* +================== +GetEdge + +Called by writebsp. +Don't allow four way edges +================== +*/ +int GetEdge2 (int v1, int v2, face_t *f) +{ + dedge_t *edge; + int i; + + c_tryedges++; + + for (i=firstmodeledge ; i < numedges ; i++) + { + edge = &dedges[i]; + if (v1 == edge->v[1] && v2 == edge->v[0] && edgefaces[i][0]->contents == f->contents) + { + if (edgefaces[i][1]) + continue; + edgefaces[i][1] = f; + return -i; + } + } + + // emit an edge + if (numedges >= MAX_MAP_EDGES) + Sys_Error ("numedges == MAX_MAP_EDGES"); + edge = &dedges[numedges]; + numedges++; + edge->v[0] = v1; + edge->v[1] = v2; + edgefaces[numedges-1][0] = f; + + return numedges-1; +} + +/* +=========================================================================== + +FACE MERGING + +=========================================================================== +*/ +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWinding (winding_t *f1, winding_t *f2, vec3_t planenormal) +{ + vec_t *p1, *p2, *p3, *p4, *back; + winding_t *newf; + int i, j, k, l; + vec3_t normal, delta; + vec_t dot; + bool keep1, keep2; + + + // + // find a common edge + // + p1 = p2 = NULL; // stop compiler warning + j = 0; // + + for (i=0 ; inumpoints ; i++) + { + p1 = f1->p[i]; + p2 = f1->p[(i+1)%f1->numpoints]; + for (j=0 ; jnumpoints ; j++) + { + p3 = f2->p[j]; + p4 = f2->p[(j+1)%f2->numpoints]; + for (k=0 ; k<3 ; k++) + { + if (fabs(p1[k] - p4[k]) > EQUAL_EPSILON) + break; + if (fabs(p2[k] - p3[k]) > EQUAL_EPSILON) + break; + } + if (k==3) + break; + } + if (j < f2->numpoints) + break; + } + + if (i == f1->numpoints) + return NULL; // no matching edges + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + back = f1->p[(i+f1->numpoints-1)%f1->numpoints]; + VectorSubtract (p1, back, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = f2->p[(j+2)%f2->numpoints]; + VectorSubtract (back, p1, delta); + dot = DotProduct (delta, normal); + if (dot > EQUAL_EPSILON) + return NULL; // not a convex polygon + keep1 = (bool)(dot < -EQUAL_EPSILON); + + back = f1->p[(i+2)%f1->numpoints]; + VectorSubtract (back, p2, delta); + CrossProduct (planenormal, delta, normal); + VectorNormalize (normal); + + back = f2->p[(j+f2->numpoints-1)%f2->numpoints]; + VectorSubtract (back, p2, delta); + dot = DotProduct (delta, normal); + if (dot > EQUAL_EPSILON) + return NULL; // not a convex polygon + keep2 = (bool)(dot < -EQUAL_EPSILON); + + // + // build the new polygon + // + newf = AllocWinding (f1->numpoints + f2->numpoints); + + // copy first polygon + for (k=(i+1)%f1->numpoints ; k != i ; k=(k+1)%f1->numpoints) + { + if (k==(i+1)%f1->numpoints && !keep2) + continue; + + VectorCopy (f1->p[k], newf->p[newf->numpoints]); + newf->numpoints++; + } + + // copy second polygon + for (l= (j+1)%f2->numpoints ; l != j ; l=(l+1)%f2->numpoints) + { + if (l==(j+1)%f2->numpoints && !keep1) + continue; + VectorCopy (f2->p[l], newf->p[newf->numpoints]); + newf->numpoints++; + } + + return newf; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge (face_t *f1, face_t *f2, vec3_t planenormal) +{ + face_t *newf; + winding_t *nw; + + if (!f1->w || !f2->w) + return NULL; + if (f1->texinfo != f2->texinfo) + return NULL; + if (f1->planenum != f2->planenum) // on front and back sides + return NULL; + if (f1->contents != f2->contents) + return NULL; + + + nw = TryMergeWinding (f1->w, f2->w, planenormal); + if (!nw) + return NULL; + + c_merge++; + newf = NewFaceFromFace (f1); + newf->w = nw; + + f1->merged = newf; + f2->merged = newf; + + return newf; +} + +/* +=============== +MergeNodeFaces +=============== +*/ +void MergeNodeFaces (node_t *node) +{ + face_t *f1, *f2, *end; + face_t *merged; + plane_t *plane; + + plane = &mapplanes[node->planenum]; + merged = NULL; + + for (f1 = node->faces ; f1 ; f1 = f1->next) + { + if (f1->merged || f1->split[0] || f1->split[1]) + continue; + for (f2 = node->faces ; f2 != f1 ; f2=f2->next) + { + if (f2->merged || f2->split[0] || f2->split[1]) + continue; + merged = TryMerge (f1, f2, plane->normal); + if (!merged) + continue; + + // add merged to the end of the node face list + // so it will be checked against all the faces again + for (end = node->faces ; end->next ; end = end->next) + ; + merged->next = NULL; + end->next = merged; + break; + } + } +} + +//===================================================================== + +/* +=============== +SubdivideFace + +Chop up faces that are larger than we want in the surface cache +=============== +*/ +void SubdivideFace( node_t *node, face_t *f ) +{ + float mins, maxs; + vec_t v; + int axis, i; + dtexinfo_t *tex; + dshader_t *shader; + vec3_t temp; + vec_t dist; + winding_t *w, *frontw, *backw; + + if( f->merged ) return; + + // special (non-surface cached) faces don't need subdivision + tex = &texinfo[f->texinfo]; + shader = &dshaders[tex->shadernum]; + + if( shader->surfaceFlags & (SURF_WARP|SURF_SKY|SURF_3DSKY)) + { + return; + } + + for( axis = 0; axis < 2; axis++ ) + { + while (1) + { + mins = 999999; + maxs = -999999; + + VectorCopy (tex->vecs[axis], temp); + w = f->w; + for (i=0 ; inumpoints ; i++) + { + v = DotProduct (w->p[i], temp); + if (v < mins) + mins = v; + if (v > maxs) + maxs = v; + } +#if 0 + if (maxs - mins <= 0) + Sys_Error ("zero extents"); +#endif + if (axis == 2) + { // allow double high walls + if (maxs - mins <= subdivide_size/* *2 */) + break; + } + else if (maxs - mins <= subdivide_size) + break; + + // split it + c_subdivide++; + + v = VectorNormalizeLength( temp ); + + dist = (mins + subdivide_size - LM_SAMPLE_SIZE)/v; + + ClipWindingEpsilon (w, temp, dist, ON_EPSILON, &frontw, &backw); + if (!frontw || !backw) + Sys_Error ("SubdivideFace: didn't split the polygon"); + + f->split[0] = NewFaceFromFace (f); + f->split[0]->w = frontw; + f->split[0]->next = node->faces; + node->faces = f->split[0]; + + f->split[1] = NewFaceFromFace (f); + f->split[1]->w = backw; + f->split[1]->next = node->faces; + node->faces = f->split[1]; + + SubdivideFace (node, f->split[0]); + SubdivideFace (node, f->split[1]); + return; + } + } +} + +void SubdivideNodeFaces (node_t *node) +{ + face_t *f; + + for (f = node->faces ; f ; f=f->next) + { + SubdivideFace (node, f); + } +} + +//=========================================================================== + +int c_nodefaces; + + +/* +============ +FaceFromPortal + +============ +*/ +face_t *FaceFromPortal (portal_t *p, int pside) +{ + face_t *f; + side_t *side; + + side = p->side; + if (!side) + return NULL; // portal does not bridge different visible contents + + f = AllocFace (); + + f->texinfo = side->texinfo; + f->planenum = (side->planenum & ~1) | pside; + f->portal = p; + + if ( (p->nodes[pside]->contents & CONTENTS_WINDOW) + && VisibleContents(p->nodes[!pside]->contents^p->nodes[pside]->contents) == CONTENTS_WINDOW ) + return NULL; // don't show insides of windows + + if (pside) + { + f->w = ReverseWinding(p->winding); + f->contents = p->nodes[1]->contents; + } + else + { + f->w = CopyWinding(p->winding); + f->contents = p->nodes[0]->contents; + } + return f; +} + + +/* +=============== +MakeFaces_r + +If a portal will make a visible face, +mark the side that originally created it + + solid / empty : solid + solid / water : solid + water / empty : water + water / water : none +=============== +*/ +void MakeFaces_r (node_t *node) +{ + portal_t *p; + int s; + + // recurse down to leafs + if (node->planenum != PLANENUM_LEAF) + { + MakeFaces_r (node->children[0]); + MakeFaces_r (node->children[1]); + + // merge together all visible faces on the node + MergeNodeFaces (node); + SubdivideNodeFaces (node); + return; + } + + // solid leafs never have visible faces + if (node->contents & CONTENTS_SOLID) + return; + + // see which portals are valid + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + p->face[s] = FaceFromPortal (p, s); + if (p->face[s]) + { + c_nodefaces++; + p->face[s]->next = p->onnode->faces; + p->onnode->faces = p->face[s]; + } + } +} + +/* +============ +MakeFaces +============ +*/ +void MakeFaces (node_t *node) +{ + c_merge = 0; + c_nodefaces = 0; + c_subdivide = 0; + + MakeFaces_r (node); +} diff --git a/xtools/bsplib/flow.c b/xtools/bsplib/flow.c new file mode 100644 index 00000000..bfbf7ea9 --- /dev/null +++ b/xtools/bsplib/flow.c @@ -0,0 +1,803 @@ +#include "bsplib.h" + +/* + + each portal will have a list of all possible to see from first portal + + if (!thread->portalmightsee[portalnum]) + + portal mightsee + + for p2 = all other portals in leaf + get sperating planes + for all portals that might be seen by p2 + mark as unseen if not present in seperating plane + flood fill a new mightsee + save as passagemightsee + + + void CalcMightSee (leaf_t *leaf, +*/ + +int CountBits (byte *bits, int numbits) +{ + int i; + int c; + + c = 0; + for (i=0 ; i>3] & (1<<(i&7)) ) + c++; + + return c; +} + +int c_fullskip; +int c_portalskip, c_leafskip; +int c_vistest, c_mighttest; + +int c_chop, c_nochop; + +int active; + +void CheckStack (leaf_t *leaf, threaddata_t *thread) +{ + pstack_t *p, *p2; + + for (p=thread->pstack_head.next ; p ; p=p->next) + { + if (p->leaf == leaf) + Sys_Error ("CheckStack: leaf recursion"); + for (p2=thread->pstack_head.next ; p2 != p ; p2=p2->next) + if (p2->leaf == p->leaf) + Sys_Error ("CheckStack: late leaf recursion"); + } +} + + +viswinding_t *AllocStackWinding (pstack_t *stack) +{ + int i; + + for (i=0 ; i<3 ; i++) + { + if (stack->freewindings[i]) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + Sys_Error ("AllocStackWinding: failed"); + + return NULL; +} + +void FreeStackWinding (viswinding_t *w, pstack_t *stack) +{ + int i; + + i = w - stack->windings; + + if (i<0 || i>2) + return; // not from local + + if (stack->freewindings[i]) + Sys_Error ("FreeStackWinding: allready free"); + stack->freewindings[i] = 1; +} + +/* +============== +ChopWinding + +============== +*/ +viswinding_t *ChopWinding (viswinding_t *in, pstack_t *stack, visplane_t *split) +{ + vec_t dists[128]; + int sides[128]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + viswinding_t *neww; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->points[i], split->normal); + dot -= split->dist; + dists[i] = dot; + if (dot > ON_EPSILON) + sides[i] = SIDE_FRONT; + else if (dot < -ON_EPSILON) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + + if (!counts[1]) + return in; // completely on front side + + if (!counts[0]) + { + FreeStackWinding (in, stack); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding (stack); + + neww->numpoints = 0; + + for (i=0 ; inumpoints ; i++) + { + p1 = in->points[i]; + + if (neww->numpoints == 12) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, neww->points[neww->numpoints]); + neww->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + if (neww->numpoints == 12) + { + FreeStackWinding (neww, stack); + return in; // can't chop -- fall back to original + } + + // generate a split point + p2 = in->points[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (split->normal[j] == 1) + mid[j] = split->dist; + else if (split->normal[j] == -1) + mid[j] = -split->dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, neww->points[neww->numpoints]); + neww->numpoints++; + } + +// free the original winding + FreeStackWinding (in, stack); + + return neww; +} + + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +viswinding_t *ClipToSeperators (viswinding_t *source, viswinding_t *pass, viswinding_t *target, bool flipclip, pstack_t *stack) +{ + int i, j, k, l; + visplane_t plane; + vec3_t v1, v2; + float d; + vec_t length; + int counts[3]; + bool fliptest; + +// check all combinations + for (i=0 ; inumpoints ; i++) + { + l = (i+1)%source->numpoints; + VectorSubtract (source->points[l] , source->points[i], v1); + + // fing a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for (j=0 ; jnumpoints ; j++) + { + VectorSubtract (pass->points[j], source->points[i], v2); + + plane.normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + plane.normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + plane.normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + // if points don't make a valid plane, skip it + + length = plane.normal[0] * plane.normal[0] + + plane.normal[1] * plane.normal[1] + + plane.normal[2] * plane.normal[2]; + + if (length < ON_EPSILON) + continue; + + length = 1/sqrt(length); + + plane.normal[0] *= length; + plane.normal[1] *= length; + plane.normal[2] *= length; + + plane.dist = DotProduct (pass->points[j], plane.normal); + + // + // find out which side of the generated seperating plane has the + // source portal + // +#if 1 + fliptest = false; + for (k=0 ; knumpoints ; k++) + { + if (k == i || k == l) + continue; + d = DotProduct (source->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + { // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if (d > ON_EPSILON) + { // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + if (k == source->numpoints) + continue; // planar with source portal +#else + fliptest = flipclip; +#endif + // + // flip the normal if the source portal is backwards + // + if (fliptest) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } +#if 1 + // + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + // + counts[0] = counts[1] = counts[2] = 0; + for (k=0 ; knumpoints ; k++) + { + if (k==j) + continue; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + break; + else if (d > ON_EPSILON) + counts[0]++; + else + counts[2]++; + } + if (k != pass->numpoints) + continue; // points on negative side, not a seperating plane + + if (!counts[0]) + continue; // planar with seperating plane +#else + k = (j+1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + continue; + k = (j+pass->numpoints-1)%pass->numpoints; + d = DotProduct (pass->points[k], plane.normal) - plane.dist; + if (d < -ON_EPSILON) + continue; +#endif + // + // flip the normal if we want the back side + // + if (flipclip) + { + VectorSubtract (vec3_origin, plane.normal, plane.normal); + plane.dist = -plane.dist; + } + + // + // clip target by the seperating plane + // + target = ChopWinding (target, stack, &plane); + if (!target) + return NULL; // target is not visible + } + } + + return target; +} + + + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +void RecursiveLeafFlow (int leafnum, threaddata_t *thread, pstack_t *prevstack) +{ + pstack_t stack; + visportal_t *p; + visplane_t backplane; + leaf_t *leaf; + int i, j; + long *test, *might, *vis, more; + int pnum; + + thread->c_chains++; + + leaf = &leafs[leafnum]; +// CheckStack (leaf, thread); + + prevstack->next = &stack; + + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + + might = (long *)stack.mightsee; + vis = (long *)thread->base->portalvis; + +// check all portals for flowing into other leafs + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + if ( ! (prevstack->mightsee[pnum >> 3] & (1<<(pnum&7)) ) ) + { + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't allready seen, skip it + if (p->status == stat_done) + { + test = (long *)p->portalvis; + } + else + { + test = (long *)p->portalflood; + } + + more = 0; + for (j=0 ; jmightsee)[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if (!more && + (thread->base->portalvis[pnum>>3] & (1<<(pnum&7))) ) + { // can't see anything new + continue; + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = p->plane; + VectorSubtract (vec3_origin, p->plane.normal, backplane.normal); + backplane.dist = -p->plane.dist; + +// c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + +#if 1 +{ +float d; + + d = DotProduct (p->origin, thread->pstack_head.portalplane.normal); + d -= thread->pstack_head.portalplane.dist; + if (d < -p->radius) + { + continue; + } + else if (d > p->radius) + { + stack.pass = p->winding; + } + else + { + stack.pass = ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; + } +} +#else + stack.pass = ChopWinding (p->winding, &stack, &thread->pstack_head.portalplane); + if (!stack.pass) + continue; +#endif + + +#if 1 +{ +float d; + + d = DotProduct (thread->base->origin, p->plane.normal); + d -= p->plane.dist; + if (d > thread->base->radius) +// if (d > p->radius) + { + continue; + } +// else if (d < -p->radius) + else if (d < -thread->base->radius) + { + stack.source = prevstack->source; + } + else + { + stack.source = ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; + } +} +#else + stack.source = ChopWinding (prevstack->source, &stack, &backplane); + if (!stack.source) + continue; +#endif + + if (!prevstack->pass) + { // the second leaf can only be blocked if coplanar + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafFlow (p->leaf, thread, &stack); + continue; + } + + stack.pass = ClipToSeperators (stack.source, prevstack->pass, stack.pass, false, &stack); + if (!stack.pass) + continue; + + stack.pass = ClipToSeperators (prevstack->pass, stack.source, stack.pass, true, &stack); + if (!stack.pass) + continue; + + // mark the portal as visible + thread->base->portalvis[pnum>>3] |= (1<<(pnum&7)); + + // flow through it for real + RecursiveLeafFlow (p->leaf, thread, &stack); + } +} + + +/* +=============== +PortalFlow + +generates the portalvis bit vector +=============== +*/ +void PortalFlow (int portalnum) +{ + threaddata_t data; + int i; + visportal_t *p; + int c_might, c_can; + + p = sorted_portals[portalnum]; + p->status = stat_working; + + c_might = CountBits (p->portalflood, numportals*2); + + memset (&data, 0, sizeof(data)); + data.base = p; + + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + for (i=0 ; iportalflood)[i]; + RecursiveLeafFlow (p->leaf, &data, &data.pstack_head); + + p->status = stat_done; + + c_can = CountBits (p->portalvis, numportals*2); +} + + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + + +Calculates portalfront and portalflood bit vectors + +thinking about: + +typedef struct passage_s +{ + struct passage_s *next; + struct portal_s *to; + stryct sep_s *seperators; + byte *mightsee; +} passage_t; + +typedef struct portal_s +{ + struct passage_s *passages; + int leaf; // leaf portal faces into +} portal_s; + +leaf = portal->leaf +clear +for all portals + + +calc portal visibility + clear bit vector + for all passages + passage visibility + + +for a portal to be visible to a passage, it must be on the front of +all seperating planes, and both portals must be behind the mew portal + +=============================================================================== +*/ + +int c_flood, c_vis; + +char test_leaf[MAX_MAP_LEAFS]; + +/* +================== +SimpleFlood + +================== +*/ +void SimpleFlood (visportal_t *srcportal, int leafnum) +{ + int i; + leaf_t *leaf; + visportal_t *p; + int pnum; + + if( bsp_parms & BSPLIB_CULLERROR && !test_leaf[leafnum]) + return; + + test_leaf[leafnum] = 0; + + leaf = &leafs[leafnum]; + + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + if ( ! (srcportal->portalfront[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + if (srcportal->portalflood[pnum>>3] & (1<<(pnum&7)) ) + continue; + + srcportal->portalflood[pnum>>3] |= (1<<(pnum&7)); + + SimpleFlood (srcportal, p->leaf); + } +} + +/* +============== +BasePortalVis +============== +*/ +void BasePortalVis (int portalnum) +{ + int j, k; + visportal_t *tp, *p; + float d; + viswinding_t *w; + vec3_t prenormal, normal; + float p_dot, tp_dot, p_rad, tp_rad; + + p = portals+portalnum; + + p->portalfront = Malloc (portalbytes); + p->portalflood = Malloc (portalbytes); + p->portalvis = Malloc (portalbytes); + + Mem_Set( test_leaf, 0, MAX_MAP_LEAFS ); + + for (j=0, tp = portals ; jleaf == p->owner_leaf) + continue; + + test_leaf[tp->leaf] = 1; + w = tp->winding; + + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], p->plane.normal) + - p->plane.dist; + if (d > ON_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + w = p->winding; + for (k=0 ; knumpoints ; k++) + { + d = DotProduct (w->points[k], tp->plane.normal) + - tp->plane.dist; + if (d < -ON_EPSILON) + break; + } + if (k == w->numpoints) + continue; // no points on front + + if( maxdist > 0.0 ) + { + // This approximation will consider 2 circles in 3d space with the centeer + // and radius of the polygons on the planes of the polygons. + + prenormal[0] = p->origin[0] - tp->origin[0]; + prenormal[1] = p->origin[1] - tp->origin[1]; + prenormal[2] = p->origin[2] - tp->origin[2]; + + VectorCopy( prenormal, normal ); + d = VectorNormalizeLength( normal ); + + p_dot = DotProduct(p->plane.normal, normal); + + if(p_dot < 0.0) + p_rad = -p_dot * p->radius; + else + p_rad = p_dot * p->radius; + + tp_dot = DotProduct(tp->plane.normal, normal); + + if(tp_dot < 0.0) + tp_rad = -tp_dot * tp->radius; + else + tp_rad = tp_dot * tp->radius; + + if(d > (maxdist + tp_rad + p_rad)) + continue; + } + + p->portalfront[j>>3] |= (1<<(j&7)); + } + + SimpleFlood (p, p->leaf); + + p->nummightsee = CountBits (p->portalflood, numportals*2); + c_flood += p->nummightsee; +} + + + + + +/* +=============================================================================== + +This is a second order aproximation + +Calculates portalvis bit vector + +WAAAAAAY too slow. + +=============================================================================== +*/ + +/* +================== +RecursiveLeafBitFlow + +================== +*/ +void RecursiveLeafBitFlow (int leafnum, byte *mightsee, byte *cansee) +{ + visportal_t *p; + leaf_t *leaf; + int i, j; + long more; + int pnum; + byte newmight[MAX_PORTALS/8]; + + leaf = &leafs[leafnum]; + +// check all portals for flowing into other leafs + for (i=0 ; inumportals ; i++) + { + p = leaf->portals[i]; + pnum = p - portals; + + // if some previous portal can't see it, skip + if (! (mightsee[pnum>>3] & (1<<(pnum&7)) ) ) + continue; + + // if this portal can see some portals we mightsee, recurse + more = 0; + for (j=0 ; jportalflood)[j]; + more |= ((long *)newmight)[j] & ~((long *)cansee)[j]; + } + + if (!more) + continue; // can't see anything new + + cansee[pnum>>3] |= (1<<(pnum&7)); + + RecursiveLeafBitFlow (p->leaf, newmight, cansee); + } +} + +/* +============== +BetterPortalVis +============== +*/ +void BetterPortalVis (int portalnum) +{ + visportal_t *p; + + p = portals+portalnum; + + RecursiveLeafBitFlow (p->leaf, p->portalflood, p->portalvis); + + // build leaf vis information + p->nummightsee = CountBits (p->portalvis, numportals*2); + c_vis += p->nummightsee; +} + + diff --git a/xtools/bsplib/leakfile.c b/xtools/bsplib/leakfile.c new file mode 100644 index 00000000..bfc243ec --- /dev/null +++ b/xtools/bsplib/leakfile.c @@ -0,0 +1,74 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// leakfile.c - leaf file generation +//======================================================================= + +#include +#include "bsplib.h" + +/* +============================================================================== + +Leaf file generation + +Save out name.line for qe3 to read +============================================================================== +*/ +/* +============= +LeakFile + +Finds the shortest possible chain of portals +that leads from the outside leaf to a specifically +occupied leaf +============= +*/ +void LeakFile (tree_t *tree) +{ + vec3_t mid; + file_t *linefile; + char path[MAX_SYSPATH]; + node_t *node; + int count; + + if (!tree->outside_node.occupied) + return; + + // write the points to the file + com.sprintf( path, "maps/%s.lin", gs_filename ); + linefile = FS_Open (path, "w" ); + if (!linefile) Sys_Error ("Couldn't open %s\n", path); + + count = 0; + node = &tree->outside_node; + while (node->occupied > 1) + { + int next; + portal_t *p, *nextportal; + node_t *nextnode; + int s; + + // find the best portal exit + next = node->occupied; + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (p->nodes[s]->occupied && p->nodes[s]->occupied < next) + { + nextportal = p; + nextnode = p->nodes[s]; + next = nextnode->occupied; + } + } + node = nextnode; + WindingCenter (nextportal->winding, mid); + FS_Printf(linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + count++; + } + // add the occupant center + GetVectorForKey (node->occupant, "origin", mid); + + FS_Printf (linefile, "%f %f %f\n", mid[0], mid[1], mid[2]); + FS_Close (linefile); +} + diff --git a/xtools/bsplib/lightmap.c b/xtools/bsplib/lightmap.c new file mode 100644 index 00000000..508c21fc --- /dev/null +++ b/xtools/bsplib/lightmap.c @@ -0,0 +1,1540 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// lightmaps.c - lightmap manager +//======================================================================= + +#include +#include "bsplib.h" +#include "const.h" + +float r_avertexnormals[NUMVERTEXNORMALS][3] = +{ +#include "anorms.h" +}; + +// stupid legacy +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +typedef struct directlight_s +{ + struct directlight_s *next; + emittype_t type; + int style; + + union + { + vec3_t intensity; // scaled by intensity + vec3_t color; // normalized light scale + }; + + vec3_t origin; + vec3_t normal; // for surfaces and spotlights + float lightscale; // same as intensity + float stopdot; // for spotlights + float stopdot2; // for spotlights + + dplane_t *plane; + dleaf_t *leaf; +} directlight_t; + +typedef struct +{ + dsurface_t *faces[2]; + bool coplanar; +} edgeshare_t; + +static byte pvs[(MAX_MAP_LEAFS+7)/8]; +edgeshare_t edgeshare[MAX_MAP_EDGES]; + +int facelinks[MAX_MAP_SURFACES]; +int planelinks[2][MAX_MAP_PLANES]; + +/* +============ +LinkPlaneFaces +============ +*/ +void LinkPlaneFaces (void) +{ + int i; + dsurface_t *f; + + f = dsurfaces; + for (i=0 ; iside][f->planenum]; + planelinks[f->side][f->planenum] = i; + } +} + +/* +============ +PairEdges +============ +*/ +void PairEdges (void) +{ + int i, j, k; + dsurface_t *f; + edgeshare_t *e; + + f = dsurfaces; + for (i=0 ; inumedges ; j++) + { + k = dsurfedges[f->firstedge + j]; + if (k < 0) + { + e = &edgeshare[-k]; + e->faces[1] = f; + } + else + { + e = &edgeshare[k]; + e->faces[0] = f; + } + + if (e->faces[0] && e->faces[1]) + { + // determine if coplanar + if (e->faces[0]->planenum == e->faces[1]->planenum) + e->coplanar = true; + } + } + } +} + +/* +================================================================= + + POINT TRIANGULATION + +================================================================= +*/ + +typedef struct triedge_s +{ + int p0, p1; + vec3_t normal; + vec_t dist; + struct triangle_s *tri; +} triedge_t; + +typedef struct triangle_s +{ + triedge_t *edges[3]; +} triangle_t; + +#define MAX_TRI_POINTS 1024 +#define MAX_TRI_EDGES (MAX_TRI_POINTS*6) +#define MAX_TRI_TRIS (MAX_TRI_POINTS*2) + +typedef struct +{ + int numpoints; + int numedges; + int numtris; + dplane_t *plane; + triedge_t *edgematrix[MAX_TRI_POINTS][MAX_TRI_POINTS]; + patch_t *points[MAX_TRI_POINTS]; + triedge_t edges[MAX_TRI_EDGES]; + triangle_t tris[MAX_TRI_TRIS]; +} triangulation_t; + +/* +=============== +AllocTriangulation +=============== +*/ +triangulation_t *AllocTriangulation (dplane_t *plane) +{ + triangulation_t *t; + + t = Malloc(sizeof(triangulation_t)); + t->numpoints = 0; + t->numedges = 0; + t->numtris = 0; + + t->plane = plane; + + return t; +} + +/* +=============== +FreeTriangulation +=============== +*/ +void FreeTriangulation (triangulation_t *tr) +{ + Mem_Free (tr); +} + + +triedge_t *FindEdge (triangulation_t *trian, int p0, int p1) +{ + triedge_t *e, *be; + vec3_t v1; + vec3_t normal; + vec_t dist; + + if (trian->edgematrix[p0][p1]) + return trian->edgematrix[p0][p1]; + + if (trian->numedges > MAX_TRI_EDGES-2) + Sys_Error ("trian->numedges > MAX_TRI_EDGES-2"); + + VectorSubtract (trian->points[p1]->origin, trian->points[p0]->origin, v1); + VectorNormalize (v1); + CrossProduct (v1, trian->plane->normal, normal); + dist = DotProduct (trian->points[p0]->origin, normal); + + e = &trian->edges[trian->numedges]; + e->p0 = p0; + e->p1 = p1; + e->tri = NULL; + VectorCopy (normal, e->normal); + e->dist = dist; + trian->numedges++; + trian->edgematrix[p0][p1] = e; + + be = &trian->edges[trian->numedges]; + be->p0 = p1; + be->p1 = p0; + be->tri = NULL; + VectorSubtract (vec3_origin, normal, be->normal); + be->dist = -dist; + trian->numedges++; + trian->edgematrix[p1][p0] = be; + + return e; +} + +triangle_t *AllocTriangle (triangulation_t *trian) +{ + triangle_t *t; + + if (trian->numtris >= MAX_TRI_TRIS) + Sys_Error ("trian->numtris >= MAX_TRI_TRIS"); + + t = &trian->tris[trian->numtris]; + trian->numtris++; + + return t; +} + +/* +============ +TriEdge_r +============ +*/ +void TriEdge_r (triangulation_t *trian, triedge_t *e) +{ + int i, bestp; + vec3_t v1, v2; + vec_t *p0, *p1, *p; + vec_t best, ang; + triangle_t *nt; + + if (e->tri) + return; // allready connected by someone + + // find the point with the best angle + p0 = trian->points[e->p0]->origin; + p1 = trian->points[e->p1]->origin; + best = 1.1f; + for (i=0 ; i< trian->numpoints ; i++) + { + p = trian->points[i]->origin; + // a 0 dist will form a degenerate triangle + if (DotProduct(p, e->normal) - e->dist < 0) + continue; // behind edge + VectorSubtract (p0, p, v1); + VectorSubtract (p1, p, v2); + if( !VectorNormalizeLength( v1 )) continue; + if( !VectorNormalizeLength( v2 )) continue; + + ang = DotProduct( v1, v2 ); + if( ang < best ) + { + best = ang; + bestp = i; + } + } + if( best >= 1 ) return; // edge doesn't match anything + + // make a new triangle + nt = AllocTriangle (trian); + nt->edges[0] = e; + nt->edges[1] = FindEdge (trian, e->p1, bestp); + nt->edges[2] = FindEdge (trian, bestp, e->p0); + for (i=0 ; i<3 ; i++) + nt->edges[i]->tri = nt; + TriEdge_r (trian, FindEdge (trian, bestp, e->p1)); + TriEdge_r (trian, FindEdge (trian, e->p0, bestp)); +} + +/* +============ +TriangulatePoints +============ +*/ +void TriangulatePoints (triangulation_t *trian) +{ + vec_t d, bestd; + vec3_t v1; + int bp1, bp2, i, j; + vec_t *p1, *p2; + triedge_t *e, *e2; + + if (trian->numpoints < 2) + return; + + // find the two closest points + bestd = 9999; + for (i=0 ; inumpoints ; i++) + { + p1 = trian->points[i]->origin; + for (j=i+1 ; jnumpoints ; j++) + { + p2 = trian->points[j]->origin; + VectorSubtract (p2, p1, v1); + d = VectorLength (v1); + if (d < bestd) + { + bestd = d; + bp1 = i; + bp2 = j; + } + } + } + + e = FindEdge (trian, bp1, bp2); + e2 = FindEdge (trian, bp2, bp1); + TriEdge_r (trian, e); + TriEdge_r (trian, e2); +} + +/* +=============== +AddPointToTriangulation +=============== +*/ +void AddPointToTriangulation (patch_t *patch, triangulation_t *trian) +{ + int pnum; + + pnum = trian->numpoints; + if (pnum == MAX_TRI_POINTS) + Sys_Error ("trian->numpoints == MAX_TRI_POINTS"); + trian->points[pnum] = patch; + trian->numpoints++; +} + +/* +=============== +LerpTriangle +=============== +*/ +void LerpTriangle (triangulation_t *trian, triangle_t *t, vec3_t point, vec3_t color) +{ + patch_t *p1, *p2, *p3; + vec3_t base, d1, d2; + float x, y, x1, y1, x2, y2; + + p1 = trian->points[t->edges[0]->p0]; + p2 = trian->points[t->edges[1]->p0]; + p3 = trian->points[t->edges[2]->p0]; + + VectorCopy (p1->totallight, base); + VectorSubtract (p2->totallight, base, d1); + VectorSubtract (p3->totallight, base, d2); + + x = DotProduct (point, t->edges[0]->normal) - t->edges[0]->dist; + y = DotProduct (point, t->edges[2]->normal) - t->edges[2]->dist; + + x1 = 0; + y1 = DotProduct (p2->origin, t->edges[2]->normal) - t->edges[2]->dist; + + x2 = DotProduct (p3->origin, t->edges[0]->normal) - t->edges[0]->dist; + y2 = 0; + + if (fabs(y1)edges[i]; + d = DotProduct (e->normal, point) - e->dist; + if (d < 0) + return false; // not inside + } + + return true; +} + +/* +=============== +SampleTriangulation +=============== +*/ +void SampleTriangulation (vec3_t point, triangulation_t *trian, vec3_t color) +{ + triangle_t *t; + triedge_t *e; + vec_t d, best; + patch_t *p0, *p1; + vec3_t v1, v2; + int i, j; + + if (trian->numpoints == 0) + { + VectorClear (color); + return; + } + if (trian->numpoints == 1) + { + VectorCopy (trian->points[0]->totallight, color); + return; + } + + // search for triangles + for (t = trian->tris, j=0 ; j < trian->numtris ; t++, j++) + { + if (!PointInTriangle (point, t)) + continue; + + // this is it + LerpTriangle (trian, t, point, color); + return; + } + + // search for exterior edge + for (e=trian->edges, j=0 ; j< trian->numedges ; e++, j++) + { + if (e->tri) + continue; // not an exterior edge + + d = DotProduct (point, e->normal) - e->dist; + if (d < 0) + continue; // not in front of edge + + p0 = trian->points[e->p0]; + p1 = trian->points[e->p1]; + + VectorSubtract (p1->origin, p0->origin, v1); + VectorNormalize (v1); + VectorSubtract (point, p0->origin, v2); + d = DotProduct (v2, v1); + if (d < 0) + continue; + if (d > 1) + continue; + for (i=0 ; i<3 ; i++) + color[i] = p0->totallight[i] + d * (p1->totallight[i] - p0->totallight[i]); + return; + } + + // search for nearest point + best = 99999; + p1 = NULL; + for (j=0 ; jnumpoints ; j++) + { + p0 = trian->points[j]; + VectorSubtract (point, p0->origin, v1); + d = VectorLength (v1); + if (d < best) + { + best = d; + p1 = p0; + } + } + + if (!p1) + Sys_Error ("SampleTriangulation: no points"); + + VectorCopy (p1->totallight, color); +} + +/* +================================================================= + + LIGHTMAP SAMPLE GENERATION + +================================================================= +*/ + + +#define SINGLEMAP (128*128*4) + +typedef struct +{ + vec_t facedist; + vec3_t facenormal; + + int numsurfpt; + vec3_t surfpt[SINGLEMAP]; + + vec3_t modelorg; // for origined bmodels + + vec3_t texorg; + vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0] + vec3_t textoworld[2]; // world = texorg + s * textoworld[0] + + vec_t exactmins[2], exactmaxs[2]; + + int texmins[2], texsize[2]; + int surfnum; + dsurface_t *face; +} lightinfo_t; + + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents (lightinfo_t *l) +{ + dsurface_t *s; + vec_t mins[2], maxs[2], val; + int i,j, e; + dvertex_t *v; + dtexinfo_t *tex; + vec3_t vt; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -99999; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + +// VectorAdd (v->point, l->modelorg, vt); + VectorCopy (v->point, vt); + + for (j=0 ; j<2 ; j++) + { + val = DotProduct (vt, tex->vecs[j]) + tex->vecs[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor(mins[i] / LM_SAMPLE_SIZE ); + maxs[i] = ceil( maxs[i] / LM_SAMPLE_SIZE ); + + l->texmins[i] = mins[i]; + l->texsize[i] = maxs[i] - mins[i]; + if (l->texsize[0] * l->texsize[1] > SINGLEMAP/4) // div 4 for extrasamples + Sys_Error ("Surface to large to map"); + } +} + +/* +================ +CalcFaceVectors + +Fills in texorg, worldtotex. and textoworld +================ +*/ +void CalcFaceVectors (lightinfo_t *l) +{ + dtexinfo_t *tex; + int i, j; + vec3_t texnormal; + vec_t distscale; + vec_t dist, len; + int w, h; + + tex = &texinfo[l->face->texinfo]; + + // convert from float to double + for (i=0 ; i<2 ; i++) + for (j=0 ; j<3 ; j++) + l->worldtotex[i][j] = tex->vecs[i][j]; + + // calculate a normal to the texture axis. points can be moved along this + // without changing their S/T + texnormal[0] = tex->vecs[1][1]*tex->vecs[0][2] - tex->vecs[1][2]*tex->vecs[0][1]; + texnormal[1] = tex->vecs[1][2]*tex->vecs[0][0] - tex->vecs[1][0]*tex->vecs[0][2]; + texnormal[2] = tex->vecs[1][0]*tex->vecs[0][1] - tex->vecs[1][1]*tex->vecs[0][0]; + VectorNormalize (texnormal); + + // flip it towards plane normal + distscale = DotProduct (texnormal, l->facenormal); + if (!distscale) + { + Msg("WARNING: Texture axis perpendicular to face\n"); + distscale = 1; + } + if (distscale < 0) + { + distscale = -distscale; + VectorSubtract (vec3_origin, texnormal, texnormal); + } + +// distscale is the ratio of the distance along the texture normal to +// the distance along the plane normal + distscale = 1/distscale; + + for (i=0 ; i<2 ; i++) + { + len = VectorLength (l->worldtotex[i]); + dist = DotProduct (l->worldtotex[i], l->facenormal); + dist *= distscale; + VectorMA (l->worldtotex[i], -dist, texnormal, l->textoworld[i]); + VectorScale (l->textoworld[i], (1/len)*(1/len), l->textoworld[i]); + } + + +// calculate texorg on the texture plane + for (i=0 ; i<3 ; i++) + l->texorg[i] = -tex->vecs[0][3]* l->textoworld[0][i] - tex->vecs[1][3] * l->textoworld[1][i]; + +// project back to the face plane + dist = DotProduct (l->texorg, l->facenormal) - l->facedist - 1; + dist *= distscale; + VectorMA (l->texorg, -dist, texnormal, l->texorg); + + // compensate for org'd bmodels + VectorAdd (l->texorg, l->modelorg, l->texorg); + + // total sample count + h = l->texsize[1]+1; + w = l->texsize[0]+1; + l->numsurfpt = w * h; +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +void CalcPoints (lightinfo_t *l, float sofs, float tofs) +{ + int i; + int s, t, j; + int w, h, step; + vec_t starts, startt, us, ut; + vec_t *surf; + vec_t mids, midt; + vec3_t facemid; + dleaf_t *leaf; + + surf = l->surfpt[0]; + mids = (l->exactmaxs[0] + l->exactmins[0])/2; + midt = (l->exactmaxs[1] + l->exactmins[1])/2; + + for (j=0 ; j<3 ; j++) + facemid[j] = l->texorg[j] + l->textoworld[0][j]*mids + l->textoworld[1][j]*midt; + + h = l->texsize[1]+1; + w = l->texsize[0]+1; + l->numsurfpt = w * h; + + starts = l->texmins[0] * LM_SAMPLE_SIZE; + startt = l->texmins[1] * LM_SAMPLE_SIZE; + step = LM_SAMPLE_SIZE; + + + for (t=0 ; ttexorg[j] + l->textoworld[0][j]*us + + l->textoworld[1][j]*ut; + + leaf = PointInLeaf (surf); + if (leaf->contents != CONTENTS_SOLID) + { + if (!(TestLine_r( 0, facemid, surf ) & CONTENTS_SOLID)) + break; // got it + } + + // nudge it + if (i & 1) + { + if (us > mids) + { + us -= LM_SAMPLE_SIZE >> 1; + if (us < mids) + us = mids; + } + else + { + us += LM_SAMPLE_SIZE >> 1; + if (us > mids) + us = mids; + } + } + else + { + if (ut > midt) + { + ut -= LM_SAMPLE_SIZE >> 1; + if (ut < midt) + ut = midt; + } + else + { + ut += LM_SAMPLE_SIZE >> 1; + if (ut > midt) + ut = midt; + } + } + } + } + } + +} + + +//============================================================== + + + +#define MAX_STYLES 32 +typedef struct +{ + int numsamples; + float *origins; + int numstyles; + int stylenums[MAX_STYLES]; + float *samples[MAX_STYLES]; +} facelight_t; + +directlight_t *directlights[MAX_MAP_LEAFS]; +facelight_t facelight[MAX_MAP_SURFACES]; +int numdlights; + +#define DIRECT_LIGHT 3 +#define LIGHT_SCALE 10 +#define LIGHT_FACTOR 10 + +/* +============= +CreateDirectLights +============= +*/ +void CreateDirectLights( void ) +{ + patch_t *p; + directlight_t *dl; + vec3_t dest; + dleaf_t *leaf; + float angle; + int i, cluster; + bsp_entity_t *e, *e2; + char *name, *target; + + // + // surfaces + // + if( bsp_parms & BSPLIB_MAKEQ2RAD ) + { + for( i = 0, p = patches; i < num_patches; i++, p++ ) + { + if( p->totallight[0] < DIRECT_LIGHT && p->totallight[1] < DIRECT_LIGHT + && p->totallight[2] < DIRECT_LIGHT ) + continue; + + dl = Malloc( sizeof( directlight_t )); + numdlights++; + + VectorCopy( p->origin, dl->origin ); + leaf = PointInLeaf( dl->origin ); + cluster = leaf->cluster; + dl->next = directlights[cluster]; + directlights[cluster] = dl; + + dl->type = emit_surface; + VectorCopy( p->plane->normal, dl->normal ); + + dl->lightscale = ColorNormalize( p->totallight, dl->color ); + dl->lightscale *= p->area * direct_scale; + VectorClear( p->totallight ); // all sent now + } + } + else if( bsp_parms & BSPLIB_MAKEHLRAD ) + { + for( i = 0, p = patches; i < num_patches; i++, p++ ) + { + if( VectorAvg(p->totallight) < 25.0f ) + continue; + + dl = Malloc( sizeof( directlight_t )); + numdlights++; + + VectorCopy( p->origin, dl->origin ); + leaf = PointInLeaf( dl->origin ); + cluster = leaf->cluster; + dl->next = directlights[cluster]; + directlights[cluster] = dl; + + dl->type = emit_surface; + VectorCopy( p->plane->normal, dl->normal ); + + VectorCopy( p->plane->normal, dl->normal ); + VectorCopy( p->totallight, dl->intensity ); + VectorScale( dl->intensity, p->area, dl->intensity ); + VectorScale( dl->intensity, direct_scale, dl->intensity ); + VectorClear( p->totallight ); // all sent now + } + } + + // + // entities + // + for( i = 0; i < num_entities; i++ ) + { + char *value; + int argCnt; + double vec[4]; + double col[3]; + float intensity; + bool monolight = false; + + e = &entities[i]; + name = ValueForKey( e, "classname" ); + if( com.strncmp( name, "light", 5 )) + continue; + + dl = Malloc( sizeof( directlight_t )); + numdlights++; + + GetVectorForKey( e, "origin", dl->origin ); + dl->style = FloatForKey( e, "_style" ); + if( !dl->style ) dl->style = FloatForKey( e, "style" ); + if( dl->style < 0 || dl->style >= MAX_LSTYLES ) + dl->style = 0; + + leaf = PointInLeaf( dl->origin ); + cluster = leaf->cluster; + + dl->next = directlights[cluster]; + directlights[cluster] = dl; + + value = ValueForKey( e, "light" ); + if( !value[0] ) value = ValueForKey( e, "_light" ); + + if( bsp_parms & BSPLIB_MAKEQ2RAD ) + { + // assume default light color + VectorSet( dl->color, 1.0f, 1.0f, 1.0f ); + intensity = 0.0f; + } + + if( value[0] ) + { + argCnt = sscanf( value, "%lf %lf %lf %lf", &vec[0], &vec[1], &vec[2], &vec[3] ); + switch( argCnt ) + { + case 4: // HalfLife light + VectorSet( dl->color, vec[0], vec[1], vec[2] ); + VectorDivide( dl->color, 255.0f, dl->color ); + if( bsp_parms & BSPLIB_MAKEHLRAD ) + VectorScale( dl->intensity, (float)vec[3], dl->intensity ); + intensity = vec[3]; + break; + case 3: // Half-Life light_environment + VectorSet( dl->intensity, vec[0], vec[1], vec[2] ); + if( bsp_parms & BSPLIB_MAKEQ2RAD ) + VectorDivide( dl->color, 255.0f, dl->color ); + break; + case 1: // Quake light + if( bsp_parms & BSPLIB_MAKEHLRAD ) + VectorSet( dl->intensity, vec[0], vec[0], vec[0] ); + intensity = vec[0]; + monolight = true; + break; + default: + MsgDev( D_WARN, "%s [%i]: '_light' key must be 1 (q1) or 3 or 4 (hl) numbers\n", name, i ); + break; + } + } + + if( monolight ) + { + value = ValueForKey( e, "color" ); + if( !value[0] ) value = ValueForKey( e, "_color" ); + if( value[0] ) + { + argCnt = sscanf( value, "%lf %lf %lf", &col[0], &col[1], &col[2] ); + if( argCnt != 3 ) + { + MsgDev( D_WARN, "light at %.0f %.0f %.0f:\ncolor must be given 3 values\n", + dl->origin[0], dl->origin[1], dl->origin[2] ); + } + else if( bsp_parms & BSPLIB_MAKEHLRAD ) + VectorScale( col, vec[0], dl->intensity ); // already in range 0-1 + else if( bsp_parms & BSPLIB_MAKEQ2RAD ) + VectorCopy( col, dl->color ); + } + } + + target = ValueForKey (e, "target"); + + if( !com.strcmp( name, "light_spot" ) || !com.strcmp( name, "light_environment" ) || target[0] ) + { + if( bsp_parms & BSPLIB_MAKEHLRAD && !VectorAvg( dl->intensity )) + VectorSet( dl->intensity, 500.0f, 500.0f, 500.0f ); + else if( bsp_parms & BSPLIB_MAKEQ2RAD && !intensity ) + intensity = 500.0f; + + dl->type = emit_spotlight; + dl->stopdot = FloatForKey( e, "_cone" ); + if( !dl->stopdot ) dl->stopdot = 10; + + if( bsp_parms & BSPLIB_MAKEQ2RAD && !com.strcmp( name, "light_environment" )) + dl->stopdot = 90; + + dl->stopdot2 = FloatForKey( e, "_cone2" ); + if( !dl->stopdot2 ) dl->stopdot2 = dl->stopdot; + + dl->stopdot = cos( dl->stopdot / 180 * M_PI ); + dl->stopdot2 = cos( dl->stopdot2 / 180 * M_PI ); + + if( target[0] ) + { + // point towards target + e2 = FindTargetEntity( target ); + if( !e2 ) + { + MsgDev( D_WARN, "light at (%i %i %i) has missing target\n", + (int)dl->origin[0], (int)dl->origin[1], (int)dl->origin[2]); + } + else + { + GetVectorForKey( e2, "origin", dest ); + VectorSubtract( dest, dl->origin, dl->normal ); + VectorNormalize( dl->normal ); + } + } + else + { // point down angle + vec3_t light_angles; + GetVectorForKey( e, "angles", light_angles ); + + angle = FloatForKey( e, "angle" ); + if( angle == ANGLE_UP ) + { + VectorSet( dl->normal, 0.0f, 0.0f, 1.0f ); + } + else if( angle == ANGLE_DOWN ) + { + VectorSet( dl->normal, 0.0f, 0.0f,-1.0f ); + } + else + { + // if we don't have a specific "angle" use the "angles" YAW + if( !angle ) angle = light_angles[1]; + + dl->normal[2] = 0; + dl->normal[0] = com.cos( angle / 180 * M_PI ); + dl->normal[1] = com.sin( angle / 180 * M_PI ); + } + + angle = FloatForKey( e, "pitch" ); + + // if we don't have a specific "pitch" use the "angles" PITCH + if( !angle ) angle = light_angles[0]; // don't forget about "Stupid Quake Bug" + dl->normal[2] = com.sin( angle / 180 * M_PI ); + dl->normal[0] *= com.cos( angle / 180 * M_PI ); + dl->normal[1] *= com.cos( angle / 180 * M_PI ); + } + + // qlight doesn't have a surface light environment - using spotlight instead + if( FloatForKey( e, "_sky" ) || !com.strcmp( name, "light_environment" )) + { + dl->stopdot2 = FloatForKey( e, "_sky" ); // hack stopdot2 to a sky key number + + if( bsp_parms & BSPLIB_MAKEHLRAD ) + dl->type = emit_skylight; + else dl->lightscale = 8000.0f; // default sun level + } + } + else + { + if( bsp_parms & BSPLIB_MAKEHLRAD && !VectorAvg( dl->intensity )) + VectorSet( dl->intensity, 300.0f, 300.0f, 300.0f ); + else if( bsp_parms & BSPLIB_MAKEQ2RAD && !intensity ) + intensity = 300.0f; + dl->type = emit_point; + } + + if( dl->type != emit_skylight ) + { + if( bsp_parms & BSPLIB_MAKEHLRAD ) + { + float l1 = VectorMax( dl->intensity ); + l1 = (l1 * l1) / LIGHT_SCALE; + VectorScale( dl->intensity, l1, dl->intensity ); + } + else + { + ColorNormalize( dl->color, dl->color ); + dl->lightscale = intensity * entity_scale; + } + } + } + Msg( "%i direct lights\n", numdlights ); +} + +/* +============= +GatherSampleLight + +Lightscale is the normalizer for multisampling +============= +*/ +void GatherSampleLight( vec3_t pos, vec3_t normal, float **styletable, int offset, int mapsize, float lightscale ) +{ + int i; + vec3_t delta; + float dot, dot2; + float dist; + float scale; + float *dest; + directlight_t *l, *sky_used = NULL; + + // get the PVS for the pos to limit the number of checks + Mem_Set( pvs, 0x00, sizeof( pvs )); + if( !PvsForOrigin( pos, pvs )) return; + + for( i = 0; i < dvis->numclusters; i++ ) + { + if(!( pvs[i>>3] & (1<<(i & 7)))) + continue; + + for( l = directlights[i]; l; l = l->next ) + { + VectorSubtract( l->origin, pos, delta ); + dist = VectorNormalizeLength( delta ); + dot = DotProduct( delta, normal ); + if( dot <= 0.001 ) continue; // behind sample surface + + switch( l->type ) + { + case emit_point: + // linear falloff + scale = (l->lightscale - dist) * dot; + break; + case emit_surface: + dot2 = -DotProduct (delta, l->normal); + if( dot2 <= 0.001 ) goto skipadd; // behind light surface + scale = (l->lightscale / (dist*dist)) * (dot * dot2); + break; + case emit_spotlight: + // linear falloff + dot2 = -DotProduct( delta, l->normal ); + if (dot2 <= l->stopdot) + goto skipadd; // outside light cone + scale = (l->lightscale - dist) * dot; + break; + default: + Sys_Error( "Bad l->type\n" ); + } + + if( scale <= 0 ) continue; + if( TestLine_r( 0, pos, l->origin ) & CONTENTS_SOLID ) + continue; // occluded + + // if this style doesn't have a table yet, allocate one + if( !styletable[l->style] ) + styletable[l->style] = Malloc (mapsize); + + dest = styletable[l->style] + offset; + // add some light to it + VectorMA( dest, scale * lightscale, l->color, dest ); +skipadd:; + } + } +} + +/* +============= +GatherSampleRad + +Lightscale is the normalizer for multisampling +============= +*/ +void GatherSampleRad( vec3_t pos, vec3_t normal, float **styletable, int offset, int mapsize, float lightscale ) +{ + int i; + vec3_t delta, add; + float dot, dot2; + float dist; + float ratio; + float *dest; + directlight_t *l, *sky_used = NULL; + + // get the PVS for the pos to limit the number of checks + Mem_Set( pvs, 0x00, sizeof( pvs )); + if( !PvsForOrigin( pos, pvs )) return; + + for( i = 0; i < dvis->numclusters; i++ ) + { + if(!( pvs[i>>3] & (1<<(i & 7)))) + continue; + + for( l = directlights[i]; l; l = l->next ) + { + // skylights work fundamentally differently than normal lights + if( l->type == emit_skylight ) + { + // only allow one of each sky type to hit any given point + if( sky_used ) continue; + sky_used = l; + + // make sure the angle is okay + dot = -DotProduct( normal, l->normal ); + if( dot <= ON_EPSILON / LIGHT_FACTOR ) continue; + + // search back to see if we can hit a sky brush + VectorScale( l->normal, -10000, delta ); + VectorAdd( pos, delta, delta ); + if(!(TestLine_r( 0, pos, delta ) & CONTENTS_SKY)) + continue; // occluded + VectorScale( l->intensity, dot, add ); + } + else + { + VectorSubtract( l->origin, pos, delta ); + dist = VectorNormalizeLength( delta ); + dot = DotProduct( delta, normal ); + if( dot <= ON_EPSILON / LIGHT_FACTOR ) continue; // behind sample surface + + switch( l->type ) + { + case emit_point: + ratio = dot / (dist * dist); + VectorScale( l->intensity, ratio * lightscale, add ); + break; + case emit_surface: + dot2 = -DotProduct( delta, l->normal ); + if( dot2 <= ON_EPSILON / LIGHT_FACTOR ) goto skipadd; // behind light surface + ratio = dot * dot2 / (dist * dist); + VectorScale( l->intensity, ratio * lightscale, add ); + break; + case emit_spotlight: + dot2 = -DotProduct( delta, l->normal ); + if( dot2 <= l->stopdot2 ) goto skipadd; // outside light cone + ratio = dot * dot2 / (dist * dist); + if( dot2 <= l->stopdot ) + ratio *= (dot2 - l->stopdot2) / (l->stopdot - l->stopdot2); + VectorScale( l->intensity, ratio * lightscale, add ); + break; + default: + Sys_Error( "Bad l->type\n" ); + } + + if( VectorMax( add ) > ( l->style ? 1.0f : 0.0f )) + { + if( l->type != emit_skylight && TestLine_r( 0, pos, l->origin ) & CONTENTS_SOLID ) + continue; // occluded + + // if this style doesn't have a table yet, allocate one + if( !styletable[l->style] ) + styletable[l->style] = Malloc( mapsize ); + + dest = styletable[l->style] + offset; + VectorAdd( dest, add, dest ); + } + } +skipadd:; + } + } + if( sky_used ) + { + vec3_t total; + vec3_t sky_intensity; + int j; + + VectorScale( sky_used->intensity, 1.0f/(NUMVERTEXNORMALS * 2), sky_intensity ); + + VectorClear( total ); + for( j = 0; j < NUMVERTEXNORMALS; j++ ) + { + // make sure the angle is okay + dot = -DotProduct( normal, r_avertexnormals[j] ); + if( dot <= ON_EPSILON / LIGHT_FACTOR ) continue; + + // search back to see if we can hit a sky brush + VectorScale( r_avertexnormals[j], -10000, delta ); + VectorAdd( pos, delta, delta ); + if(!(TestLine_r (0, pos, delta) & CONTENTS_SKY)) + continue; // occluded + + VectorScale( sky_intensity, dot, add ); + VectorAdd( total, add, total ); + } + if( VectorMax( total ) > 0 ) + { + // if this style doesn't have a table yet, allocate one + if( !styletable[sky_used->style] ) + styletable[sky_used->style] = Malloc( mapsize ); + + dest = styletable[sky_used->style] + offset; + VectorAdd( dest, total, dest ); + } + } +} + +/* +============= +AddSampleToPatch + +Take the sample's collected light and +add it back into the apropriate patch +for the radiosity pass. + +The sample is added to all patches that might include +any part of it. They are counted and averaged, so it +doesn't generate extra light. +============= +*/ +void AddSampleToPatch( vec3_t pos, vec3_t color, int facenum ) +{ + patch_t *patch; + vec3_t mins, maxs; + int i; + + if( numbounce == 0 ) return; + if( VectorAvg( color ) < 1.0f ) + return; + + for( patch = face_patches[facenum]; patch; patch = patch->next ) + { + // see if the point is in this patch (roughly) + WindingBounds( patch->winding, mins, maxs ); + for( i = 0; i < 3; i++ ) + { + if( mins[i] > pos[i] + LM_SAMPLE_SIZE ) + goto nextpatch; + if( maxs[i] < pos[i] - LM_SAMPLE_SIZE ) + goto nextpatch; + } + + // add the sample to the patch + patch->samples++; + VectorAdd( patch->samplelight, color, patch->samplelight ); +nextpatch:; + } + +} + + +/* +============= +BuildFacelights +============= +*/ +float sampleofs[5][2] = +{ {0,0}, {-0.25, -0.25}, {0.25, -0.25}, {0.25, 0.25}, {-0.25, 0.25} }; + +static lightinfo_t light_info[5]; + +void BuildFacelights( int facenum ) +{ + dsurface_t *f; + int i, j; + float *styletable[MAX_LSTYLES]; + float *spot; + patch_t *patch; + int numsamples; + int tablesize; + facelight_t *fl; + + f = &dsurfaces[facenum]; + + if( dshaders[texinfo[f->texinfo].shadernum].surfaceFlags & (SURF_WARP|SURF_SKY|SURF_3DSKY)) + return; // non-lit texture + + Mem_Set( styletable, 0, sizeof( styletable )); + + if( bsp_parms & BSPLIB_FULLCOMPILE ) + numsamples = 5; + else numsamples = 1; + + for( i = 0; i < numsamples; i++ ) + { + Mem_Set( &light_info[i], 0, sizeof( light_info[i] )); + light_info[i].surfnum = facenum; + light_info[i].face = f; + VectorCopy (dplanes[f->planenum].normal, light_info[i].facenormal); + light_info[i].facedist = dplanes[f->planenum].dist; + if( f->side ) + { + VectorSubtract( vec3_origin, light_info[i].facenormal, light_info[i].facenormal ); + light_info[i].facedist = -light_info[i].facedist; + } + + // get the origin offset for rotating bmodels + VectorCopy( face_offset[facenum], light_info[i].modelorg ); + + CalcFaceVectors( &light_info[i] ); + CalcFaceExtents( &light_info[i] ); + CalcPoints( &light_info[i], sampleofs[i][0], sampleofs[i][1] ); + } + + tablesize = light_info[0].numsurfpt * sizeof( vec3_t ); + styletable[0] = Malloc( tablesize ); + + fl = &facelight[facenum]; + fl->numsamples = light_info[0].numsurfpt; + fl->origins = Malloc( tablesize ); + Mem_Copy( fl->origins, light_info[0].surfpt, tablesize ); + + for( i = 0; i < light_info[0].numsurfpt; i++ ) + { + for( j = 0; j < numsamples; j++ ) + { + if( bsp_parms & BSPLIB_MAKEHLRAD ) + GatherSampleRad( light_info[j].surfpt[i], light_info[0].facenormal, styletable, i*3, tablesize, 1.0f / numsamples ); + else GatherSampleLight( light_info[j].surfpt[i], light_info[0].facenormal, styletable, i*3, tablesize, 1.0f / numsamples ); + } + + // contribute the sample to one or more patches + AddSampleToPatch( light_info[0].surfpt[i], styletable[0]+i*3, facenum ); + } + + // average up the direct light on each patch for radiosity + for (patch = face_patches[facenum] ; patch ; patch=patch->next) + { + if (patch->samples) + { + VectorScale (patch->samplelight, 1.0/patch->samples, patch->samplelight); + } + } + + for (i=0 ; inumstyles == MAX_STYLES) + break; + fl->samples[fl->numstyles] = styletable[i]; + fl->stylenums[fl->numstyles] = i; + fl->numstyles++; + } + + // the light from DIRECT_LIGHTS is sent out, but the + // texture itself should still be full bright + + if (face_patches[facenum]->baselight[0] >= DIRECT_LIGHT || + face_patches[facenum]->baselight[1] >= DIRECT_LIGHT || + face_patches[facenum]->baselight[2] >= DIRECT_LIGHT + ) + { + spot = fl->samples[0]; + for (i=0 ; ibaselight, spot); + } + } +} + + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +void FinalLightFace (int facenum) +{ + dsurface_t *f; + int i, j, k, st; + vec3_t lb; + patch_t *patch; + triangulation_t *trian; + facelight_t *fl; + float minlight; + float max, newmax; + byte *dest; + int pfacenum; + vec3_t facemins, facemaxs; + + f = &dsurfaces[facenum]; + fl = &facelight[facenum]; + + if( dshaders[texinfo[f->texinfo].shadernum].surfaceFlags & ( SURF_WARP|SURF_SKY|SURF_3DSKY )) + return; // non-lit texture + + ThreadLock (); + f->lightofs = lightdatasize; + lightdatasize += fl->numstyles*(fl->numsamples*3); + +// add green sentinals between lightmaps +#if 0 +lightdatasize += 64*3; +for (i=0 ; i<64 ; i++) +dlightdata[lightdatasize-(i+1)*3 + 1] = 255; +#endif + + if (lightdatasize > MAX_MAP_LIGHTING) + Sys_Error ("MAX_MAP_LIGHTING"); + ThreadUnlock (); + + f->styles[0] = 0; + f->styles[1] = f->styles[2] = f->styles[3] = 0xff; + + // + // set up the triangulation + // + if (numbounce > 0) + { + ClearBounds (facemins, facemaxs); + for (i=0 ; inumedges ; i++) + { + int ednum; + + ednum = dsurfedges[f->firstedge+i]; + if (ednum >= 0) + AddPointToBounds (dvertexes[dedges[ednum].v[0]].point, + facemins, facemaxs); + else + AddPointToBounds (dvertexes[dedges[-ednum].v[1]].point, + facemins, facemaxs); + } + + trian = AllocTriangulation (&dplanes[f->planenum]); + + // for all faces on the plane, add the nearby patches + // to the triangulation + for (pfacenum = planelinks[f->side][f->planenum] + ; pfacenum ; pfacenum = facelinks[pfacenum]) + { + for (patch = face_patches[pfacenum] ; patch ; patch=patch->next) + { + for (i=0 ; i < 3 ; i++) + { + if (facemins[i] - patch->origin[i] > subdiv*2) + break; + if (patch->origin[i] - facemaxs[i] > subdiv*2) + break; + } + if (i != 3) + continue; // not needed for this face + AddPointToTriangulation (patch, trian); + } + } + for (i=0 ; inumpoints ; i++) + memset (trian->edgematrix[i], 0, trian->numpoints*sizeof(trian->edgematrix[0][0]) ); + TriangulatePoints (trian); + } + + // + // sample the triangulation + // + + // _minlight allows models that have faces that would not be + // illuminated to receive a mottled light pattern instead of + // black + minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; + + dest = &dlightdata[f->lightofs]; + + if (fl->numstyles > LM_STYLES ) + { + fl->numstyles = LM_STYLES; + Msg( "face with too many lightstyles: (%f %f %f)\n", face_patches[facenum]->origin[0], face_patches[facenum]->origin[1], face_patches[facenum]->origin[2] ); + } + + for (st=0 ; stnumstyles ; st++) + { + f->styles[st] = fl->stylenums[st]; + for (j=0 ; jnumsamples ; j++) + { + VectorCopy ( (fl->samples[st]+j*3), lb); + if (numbounce > 0 && st == 0) + { + vec3_t add; + + SampleTriangulation (fl->origins + j*3, trian, add); + VectorAdd (lb, add, lb); + } + // add an ambient term if desired + lb[0] += ambient; + lb[1] += ambient; + lb[2] += ambient; + + VectorScale (lb, lightscale, lb); + + // we need to clamp without allowing hue to change + for (k=0 ; k<3 ; k++) + if (lb[k] < 1) + lb[k] = 1; + max = lb[0]; + if (lb[1] > max) + max = lb[1]; + if (lb[2] > max) + max = lb[2]; + newmax = max; + if (newmax < 0) + newmax = 0; // roundoff problems + if (newmax < minlight) + { + newmax = minlight + (rand()%48); + } + if (newmax > maxlight) + newmax = maxlight; + + for (k=0 ; k<3 ; k++) + { + *dest++ = lb[k]*newmax/max; + } + } + } + + if (numbounce > 0) + FreeTriangulation (trian); +} diff --git a/xtools/bsplib/map.c b/xtools/bsplib/map.c new file mode 100644 index 00000000..ca4bbc3f --- /dev/null +++ b/xtools/bsplib/map.c @@ -0,0 +1,1065 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// map.c - parse map script +//======================================================================= + +#include "bsplib.h" +#include "const.h" + +extern bool onlyents; + +#define PLANE_HASHES 1024 +#define MAPTYPE() Msg( "map type: %s\n", g_sMapType[g_brushtype] ) +#define NORMAL_EPSILON 0.00001 +#define DIST_EPSILON 0.01 + +script_t *mapfile; +int nummapbrushes; +mapbrush_t mapbrushes[MAX_MAP_BRUSHES]; +int nummapbrushsides; +side_t brushsides[MAX_MAP_SIDES]; +brush_texture_t side_brushtextures[MAX_MAP_SIDES]; +int nummapplanes; +plane_t mapplanes[MAX_MAP_PLANES]; +plane_t *planehash[PLANE_HASHES]; +vec3_t map_mins, map_maxs, map_size; +int g_mapversion; +int g_brushtype; + +int c_boxbevels; +int c_edgebevels; +int c_areaportals; +int c_clipbrushes; + +const char *g_sMapType[BRUSH_COUNT] = +{ +"unknown", +"Worldcraft 2.1", +"Valve Hammer 3.4", +"Radiant", +"QuArK" +}; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal( vec3_t normal ) +{ + // NOTE: should these have an epsilon around 1.0? + if(fabs(normal[0]) == 1.0) return PLANE_X; + if(fabs(normal[1]) == 1.0) return PLANE_Y; + if(fabs(normal[2]) == 1.0) return PLANE_Z; + + return 3; +} + +/* +================ +PlaneEqual +================ +*/ +bool PlaneEqual( plane_t *p, vec3_t normal, vec_t dist ) +{ + float ne, de; + + ne = NORMAL_EPSILON; + de = DIST_EPSILON; + + if( fabs(p->normal[0] - normal[0]) < ne && fabs(p->normal[1] - normal[1]) < ne + && fabs(p->normal[2] - normal[2]) < ne && fabs(p->dist - dist) < de ) + return true; + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void AddPlaneToHash( plane_t *p ) +{ + uint hash; + + hash = (PLANE_HASHES - 1) & (int)fabs( p->dist ); + p->hash_chain = planehash[hash]; + planehash[hash] = p; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CreateNewFloatPlane( vec3_t normal, vec_t dist ) +{ + plane_t *p, temp; + + if( VectorLength(normal) < 0.5 ) + { + MsgDev( D_ERROR, "CreateNewFloatPlane: bad normal\n"); + return -1; + } + + // create a new plane + if( nummapplanes + 2 > MAX_MAP_PLANES ) + Sys_Error( "MAX_MAP_PLANES limit exceeded\n" ); + + p = &mapplanes[nummapplanes]; + VectorCopy( normal, p->normal ); + p->dist = dist; + p->type = (p+1)->type = PlaneTypeForNormal( p->normal ); + + VectorSubtract( vec3_origin, normal, (p+1)->normal ); + (p+1)->dist = -dist; + + nummapplanes += 2; + + // always put axial planes facing positive first + if( p->type < 3 ) + { + if( p->normal[0] < 0 || p->normal[1] < 0 || p->normal[2] < 0 ) + { + // flip order + temp = *p; + *p = *(p+1); + *(p+1) = temp; + + AddPlaneToHash( p+0 ); + AddPlaneToHash( p+1 ); + return nummapplanes - 1; + } + } + + AddPlaneToHash( p+0 ); + AddPlaneToHash( p+1 ); + return nummapplanes - 2; +} + +/* +============== +SnapVector +============== +*/ +void SnapVector( vec3_t normal ) +{ + int i; + + for( i = 0; i < 3; i++ ) + { + if( fabs(normal[i] - 1) < NORMAL_EPSILON ) + { + VectorClear( normal ); + normal[i] = 1; + break; + } + if( fabs(normal[i] - -1) < NORMAL_EPSILON ) + { + VectorClear( normal ); + normal[i] = -1; + break; + } + } +} + +/* +============== +SnapPlane +============== +*/ +void SnapPlane( vec3_t normal, float *dist ) +{ + SnapVector( normal ); + + if( fabs( *dist - floor( *dist + 0.5 )) < DIST_EPSILON ) + *dist = floor( *dist + 0.5 ); +} + +/* +============= +FindFloatPlane + +============= +*/ +int FindFloatPlane( vec3_t normal, float dist ) +{ + int i, h; + uint hash; + plane_t *p; + + SnapPlane( normal, &dist ); + hash = (PLANE_HASHES - 1) & (int)fabs( dist ); + + // search the border bins as well + for( i = -1; i <= 1; i++ ) + { + h = (hash+i)&(PLANE_HASHES-1); + for( p = planehash[h]; p; p = p->hash_chain ) + { + if( PlaneEqual( p, normal, dist )) + return p - mapplanes; + } + } + return CreateNewFloatPlane( normal, dist ); +} + +/* +================ +PlaneFromPoints +================ +*/ +int PlaneFromPoints( vec_t *p0, vec_t *p1, vec_t *p2 ) +{ + vec3_t t1, t2, normal; + vec_t dist; + + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal ); + dist = DotProduct( p0, normal ); + + return FindFloatPlane( normal, dist ); +} + +void CntString( int cnt, char *contents ) +{ + contents[0] = '\0'; + + if( cnt & CONTENTS_SOLID ) com.strcat( contents, "solid " ); + if( cnt & CONTENTS_WINDOW) com.strcat( contents, "window " ); + if( cnt & CONTENTS_AUX ) com.strcat( contents, "aux " ); + if( cnt & CONTENTS_LAVA) com.strcat( contents, "lava " ); + if( cnt & CONTENTS_SLIME) com.strcat( contents, "slime " ); + if( cnt & CONTENTS_WATER) com.strcat( contents, "water " ); + if( cnt & CONTENTS_SKY) com.strcat( contents, "sky " ); + + if( cnt & CONTENTS_MIST) com.strcat( contents, "mist " ); + if( cnt & CONTENTS_FOG) com.strcat( contents, "fog " ); + if( cnt & CONTENTS_AREAPORTAL) com.strcat( contents, "areaportal " ); + if( cnt & CONTENTS_PLAYERCLIP) com.strcat( contents, "playerclip " ); + if( cnt & CONTENTS_MONSTERCLIP) com.strcat( contents, " monsterclip " ); + if( cnt & CONTENTS_CLIP) com.strcat( contents, "clip " ); + if( cnt & CONTENTS_ORIGIN) com.strcat( contents, " origin" ); + if( cnt & CONTENTS_BODY) Sys_Error("\nCONTENTS_BODY detected\n" ); + if( cnt & CONTENTS_CORPSE) Sys_Error("\nCONTENTS_CORPSE detected\n" ); + if( cnt & CONTENTS_DETAIL) com.strcat( contents, " detail " ); + if( cnt & CONTENTS_TRANSLUCENT) com.strcat( contents, "translucent " ); + if( cnt & CONTENTS_LADDER) com.strcat( contents, "ladder " ); + if( cnt & CONTENTS_TRIGGER) com.strcat( contents, "trigger " ); +} + +//==================================================================== +/* +=========== +BrushContents +=========== +*/ +int BrushContents( mapbrush_t *b ) +{ + int contents; + int i, trans; + string cnt1, cnt2; + side_t *s; + + s = &b->original_sides[0]; + b->shadernum = texinfo[s->texinfo].shadernum; + trans = dshaders[texinfo[s->texinfo].shadernum].surfaceFlags; + contents = s->contents; + + for( i = 1; i < b->numsides; i++, s++ ) + { + s = &b->original_sides[i]; + trans |= dshaders[texinfo[s->texinfo].shadernum].surfaceFlags; + if(( s->contents != contents ) && !( trans & (SURF_NODRAW|SURF_SKY|SURF_3DSKY))) + { + CntString( s->contents, cnt1 ); + CntString( contents, cnt2 ); + + // nodraw textures are ignored + MsgDev( D_WARN, "Entity %i, Brush %i: mixed face contents ( %s) and ( %s)\n", b->entitynum, b->brushnum, cnt1, cnt2 ); + break; + } + } + + // if any side is translucent, mark the contents + // and change solid to window + if( trans & (SURF_TRANS|SURF_BLEND)) + { + contents |= CONTENTS_TRANSLUCENT; + if( contents & CONTENTS_SOLID ) + { + contents &= ~CONTENTS_SOLID; + contents |= CONTENTS_WINDOW; + } + } + return contents; +} + + +//============================================================================ + +/* +================= +AddBrushBevels + +Adds any additional planes necessary to allow the brush to be expanded +against axial bounding boxes +================= +*/ +void AddBrushBevels( mapbrush_t *b ) +{ + int axis, dir; + int i, j, k, l, order; + side_t sidetemp; + brush_texture_t tdtemp; + side_t *s, *s2; + vec3_t normal; + float dist; + winding_t *w, *w2; + vec3_t vec, vec2; + float d; + + // add the axial planes + for( axis = order = 0; axis < 3; axis++ ) + { + for( dir = -1; dir <= 1; dir += 2, order++ ) + { + // see if the plane is allready present + for( i = 0, s = b->original_sides; i < b->numsides; i++, s++ ) + { + if( mapplanes[s->planenum].normal[axis] == dir ) + break; + } + + if( i == b->numsides ) + { + // add a new side + if( nummapbrushsides == MAX_MAP_BRUSHSIDES ) + Sys_Error( "MAX_MAP_BRUSHSIDES limit exceeded\n" ); + nummapbrushsides++; + b->numsides++; + VectorClear( normal ); + normal[axis] = dir; + if( dir == 1 ) dist = b->maxs[axis]; + else dist = -b->mins[axis]; + s->planenum = FindFloatPlane( normal, dist ); + s->texinfo = b->original_sides[0].texinfo; + s->contents = b->original_sides[0].contents; + s->bevel = true; + c_boxbevels++; + } + + // if the plane is not in it canonical order, swap it + if( i != order ) + { + sidetemp = b->original_sides[order]; + b->original_sides[order] = b->original_sides[i]; + b->original_sides[i] = sidetemp; + + j = b->original_sides - brushsides; + tdtemp = side_brushtextures[j+order]; + side_brushtextures[j+order] = side_brushtextures[j+i]; + side_brushtextures[j+i] = tdtemp; + } + } + } + + // add the edge bevels + if( b->numsides == 6 ) return; // pure axial + + // test the non-axial plane edges + for( i = 6; i < b->numsides; i++ ) + { + s = b->original_sides + i; + w = s->winding; + if( !w ) continue; + + for( j = 0; j < w->numpoints; j++ ) + { + k = (j+1) % w->numpoints; + VectorSubtract( w->p[j], w->p[k], vec ); + if( VectorNormalizeLength( vec ) < 0.5f ) continue; + SnapVector( vec ); + for( k = 0; k < 3; k++ ) + if( vec[k] == -1 || vec[k] == 1 ) + break; // axial + if( k != 3 ) continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for( axis = 0; axis < 3; axis++ ) + { + for( dir = -1; dir <= 1; dir += 2 ) + { + // construct a plane + VectorClear( vec2 ); + vec2[axis] = dir; + CrossProduct( vec, vec2, normal ); + if( VectorNormalizeLength( normal ) < 0.5f ) continue; + dist = DotProduct( w->p[j], normal ); + + // if all the points on all the sides are + // behind this plane, it is a proper edge bevel + for( k = 0; k < b->numsides; k++ ) + { + // if this plane has allready been used, skip it + if( PlaneEqual( &mapplanes[b->original_sides[k].planenum], normal, dist )) + break; + + w2 = b->original_sides[k].winding; + if( !w2 ) continue; + + for( l = 0; l < w2->numpoints; l++ ) + { + d = DotProduct( w2->p[l], normal ) - dist; + if( d > 0.1f ) break; // point in front + } + if( l != w2->numpoints ) break; + } + + if( k != b->numsides ) continue; // wasn't part of the outer hull + // add this plane + if( nummapbrushsides == MAX_MAP_BRUSHSIDES ) + Sys_Error( "MAX_MAP_BRUSHSIDES limit exceeded\n" ); + nummapbrushsides++; + s2 = &b->original_sides[b->numsides]; + s2->planenum = FindFloatPlane( normal, dist ); + s2->texinfo = b->original_sides[0].texinfo; + s2->contents = b->original_sides[0].contents; + s2->bevel = true; + c_edgebevels++; + b->numsides++; + } + } + } + } +} + + +/* +================ +MakeBrushWindings + +makes basewindigs for sides and mins / maxs for the brush +================ +*/ +bool MakeBrushWindings( mapbrush_t *ob ) +{ + int i, j; + winding_t *w; + side_t *side; + plane_t *plane; + + ClearBounds( ob->mins, ob->maxs ); + + for( i = 0; i < ob->numsides; i++ ) + { + plane = &mapplanes[ob->original_sides[i].planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + for( j = 0; j < ob->numsides && w; j++ ) + { + if( i == j ) continue; + if( ob->original_sides[j].bevel ) continue; + plane = &mapplanes[ob->original_sides[j].planenum^1]; + ChopWindingInPlace( &w, plane->normal, plane->dist, 0 ); + } + + side = &ob->original_sides[i]; + side->winding = w; + if( w ) + { + side->visible = true; + for( j = 0; j < w->numpoints; j++ ) + AddPointToBounds( w->p[j], ob->mins, ob->maxs ); + } + } + + for( i = 0; i < 3; i++ ) + { + if( ob->mins[i] < MIN_WORLD_COORD || ob->maxs[i] > MAX_WORLD_COORD ) + return false; + if( ob->mins[i] >= ob->maxs[i] ) + return false; + } + return true; +} + + +/* +================= +ParseBrush +================= +*/ +void ParseBrush( bsp_entity_t *mapent ) +{ + mapbrush_t *b; + int i,j, k; + int mt; + side_t *side, *s2; + int planenum; + vec_t planepts[3][3]; // quark used float coords + token_t token; + bsp_shader_t *si; + brush_texture_t td; + + if( nummapbrushes == MAX_MAP_BRUSHES ) Sys_Break( "MAX_MAP_BRUSHES limit exceeded\n"); + if( g_brushtype == BRUSH_RADIANT ) Com_CheckToken( mapfile, "{" ); + + b = &mapbrushes[nummapbrushes]; + b->original_sides = &brushsides[nummapbrushsides]; + b->entitynum = num_entities-1; + b->brushnum = nummapbrushes - mapent->firstbrush; + + while( 1 ) + { + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES|SC_COMMENT_SEMICOLON, &token )) + break; + if( !com.stricmp( token.string, "}" )) break; + if( g_brushtype == BRUSH_RADIANT ) + { + while( 1 ) + { + if( com.strcmp( token.string, "(" )) + Com_ReadToken( mapfile, 0, &token ); + else break; + Com_ReadToken( mapfile, SC_ALLOW_NEWLINES, &token ); + } + } + + if( nummapbrushsides == MAX_MAP_BRUSHSIDES ) + Sys_Break( "MAX_MAP_BRUSHSIDES limit exceeded\n" ); + Com_SaveToken( mapfile, &token ); + side = &brushsides[nummapbrushsides]; + + // read the three point plane definition + Com_Parse1DMatrix( mapfile, 3, planepts[0] ); + Com_Parse1DMatrix( mapfile, 3, planepts[1] ); + Com_Parse1DMatrix( mapfile, 3, planepts[2] ); + + if( g_brushtype == BRUSH_RADIANT ) + Com_Parse2DMatrix( mapfile, 2, 3, (float *)td.vects.radiant.matrix ); + + // read the texturedef + Com_ReadToken( mapfile, SC_ALLOW_PATHNAMES|SC_PARSE_GENERIC, &token ); + com.strncpy( td.name, token.string, sizeof( td.name )); + + if( g_brushtype == BRUSH_WORLDCRAFT_22 ) // Worldcraft 2.2+ + { + // texture U axis + Com_ReadToken( mapfile, 0, &token ); + if( com.strcmp( token.string, "[")) Sys_Break( "missing '[' in texturedef (U)\n" ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.UAxis[0] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.UAxis[1] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.UAxis[2] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.shift[0] ); + Com_ReadToken( mapfile, 0, &token ); + if( com.strcmp( token.string, "]")) Sys_Break( "missing ']' in texturedef (U)\n" ); + + // texture V axis + Com_ReadToken( mapfile, 0, &token ); + if( com.strcmp( token.string, "[")) Sys_Break( "missing '[' in texturedef (V)\n" ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.VAxis[0] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.VAxis[1] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.VAxis[2] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.shift[1] ); + Com_ReadToken( mapfile, 0, &token ); + if( com.strcmp( token.string, "]")) Sys_Break( "missing ']' in texturedef (V)\n"); + + // texture rotation is implicit in U/V axes. + Com_ReadToken( mapfile, 0, &token ); + td.vects.hammer.rotate = 0; + + // texure scale + // texure scale + Com_ReadFloat( mapfile, false, &td.vects.hammer.scale[0] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.scale[1] ); + } + else if( g_brushtype == BRUSH_WORLDCRAFT_21 || g_brushtype == BRUSH_QUARK ) + { + // worldcraft 2.1-, old Radiant, QuArK + Com_ReadFloat( mapfile, false, &td.vects.hammer.shift[0] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.shift[1] ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.rotate ); + Com_ReadFloat( mapfile, false, &td.vects.hammer.scale[0] ); + Com_ReadFloat( mapfile, SC_COMMENT_SEMICOLON, &td.vects.hammer.scale[1] ); + } + + // hidden q2/q3 legacy, but can be used + if( g_brushtype != BRUSH_QUARK && Com_ReadToken( mapfile, SC_COMMENT_SEMICOLON, &token )) + { + // overwrite shader values directly from .map file + Com_SaveToken( mapfile, &token ); + Com_ReadLong( mapfile, false, &td.contents ); + Com_ReadLong( mapfile, false, &td.flags ); + Com_ReadLong( mapfile, false, &td.value ); + } + + if( mapfile->TXcommand == '1' || mapfile->TXcommand == '2' ) + { + // we are QuArK mode and need to translate some numbers to align textures its way + // from QuArK, the texture vectors are given directly from the three points + vec3_t TexPt[2]; + float dot22, dot23, dot33, mdet, aa, bb, dd; + int k; + + g_brushtype = BRUSH_QUARK; + k = mapfile->TXcommand - '0'; + for( j = 0; j < 3; j++ ) + { + TexPt[1][j] = (planepts[k][j] - planepts[0][j]) * (1.0/128.0); + } + + k = 3 - k; + for( j = 0; j < 3; j++ ) + { + TexPt[0][j] = (planepts[k][j] - planepts[0][j]) * (1.0/128.0); + } + + dot22 = DotProduct( TexPt[0], TexPt[0] ); + dot23 = DotProduct( TexPt[0], TexPt[1] ); + dot33 = DotProduct( TexPt[1], TexPt[1] ); + mdet = dot22 * dot33 - dot23 * dot23; + if( mdet < 1E-6 && mdet > -1E-6 ) + { + aa = bb = dd = 0; + MsgDev( D_WARN, "Degenerate QuArK-style brush texture : Entity %i, Brush %i\n", b->entitynum, b->brushnum ); + } + else + { + mdet = 1.0 / mdet; + aa = dot33 * mdet; + bb = -dot23 * mdet; + dd = dot22 * mdet; + } + for( j = 0; j < 3; j++ ) + { + td.vects.quark.vecs[0][j] = aa * TexPt[0][j] + bb * TexPt[1][j]; + td.vects.quark.vecs[1][j] = -(bb * TexPt[0][j] + dd * TexPt[1][j]); + } + td.vects.quark.vecs[0][3] = -DotProduct( td.vects.quark.vecs[0], planepts[0] ); + td.vects.quark.vecs[1][3] = -DotProduct( td.vects.quark.vecs[1], planepts[0] ); + } + + td.brush_type = g_brushtype; // member map type + td.flags = td.contents = td.value = 0; // reset all values before setting + side->contents = side->surf = 0; + + // get size from miptex info + mt = FindMiptex( td.name ); + td.size[0] = dshaders[mt].size[0]; + td.size[1] = dshaders[mt].size[1]; + + // get flags and contents from shader + // FIXME: rewote this relationship + si = FindShader( td.name ); + if( si ) + { + side->contents = td.contents = si->contents; + side->surf = td.flags = si->surfaceFlags; + td.value = si->intensity; + } + else + { + side->contents = td.contents; + side->surf = td.flags; + } + + // translucent objects are automatically classified as detail + if( side->surf & ( SURF_TRANS|SURF_BLEND )) + { + side->contents |= CONTENTS_DETAIL; + td.contents |= CONTENTS_DETAIL; + } + if( side->contents & CONTENTS_CLIP ) + { + side->contents |= CONTENTS_DETAIL; + td.contents |= CONTENTS_DETAIL; + } + if(!(side->contents & ((LAST_VISIBLE_CONTENTS - 1)|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_MIST))) + { + side->contents |= CONTENTS_SOLID; + td.contents |= CONTENTS_SOLID; + } + // hints and skips are never detail, and have no content + if( side->surf & ( SURF_HINT|SURF_SKIP )) + { + side->contents = td.contents = 0; + side->surf &= ~CONTENTS_DETAIL; + td.flags &= ~CONTENTS_DETAIL; + } + + // Msg( "flags %p, value %d, contents %p\n", td.flags, td.value, side->contents ); + + // find the plane number + planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] ); + if( planenum == -1 ) + { + MsgDev( D_ERROR, "Entity %i, Brush %i: plane with no normal\n", b->entitynum, b->brushnum ); + continue; + } + + if( g_brushtype == BRUSH_RADIANT ) + { + float m[2][3], vecs[2][4]; + float a, ac, as, bc, bs; + plane_t *plane = mapplanes + planenum; + + Mem_Copy( m, td.vects.radiant.matrix, sizeof (m )); // save outside + + // calculate proper texture vectors from GTKRadiant/Doom3 brushprimitives matrix + a = -com.atan2( plane->normal[2], com.sqrt( plane->normal[0] * plane->normal[0] + plane->normal[1] * plane->normal[1] )); + ac = com.cos( a ); + as = com.sin( a ); + a = com.atan2( plane->normal[1], plane->normal[0] ); + bc = com.cos( a ); + bs = com.sin( a ); + + vecs[0][0] = -bs; + vecs[0][1] = bc; + vecs[0][2] = 0.0f; + vecs[0][3] = 0; // FIXME: set to 1.0f ? + vecs[1][0] = -as*bc; + vecs[1][1] = -as*bs; + vecs[1][2] = -ac; + vecs[1][3] = 0; // FIXME: set to 1.0f ? + + td.vects.quark.vecs[0][0] = m[0][0] * vecs[0][0] + m[0][1] * vecs[1][0]; + td.vects.quark.vecs[0][1] = m[0][0] * vecs[0][1] + m[0][1] * vecs[1][1]; + td.vects.quark.vecs[0][2] = m[0][0] * vecs[0][2] + m[0][1] * vecs[1][2]; + td.vects.quark.vecs[0][3] = m[0][0] * vecs[0][3] + m[0][1] * vecs[1][3] + m[0][2]; + td.vects.quark.vecs[1][0] = m[1][0] * vecs[0][0] + m[1][1] * vecs[1][0]; + td.vects.quark.vecs[1][1] = m[1][0] * vecs[0][1] + m[1][1] * vecs[1][1]; + td.vects.quark.vecs[1][2] = m[1][0] * vecs[0][2] + m[1][1] * vecs[1][2]; + td.vects.quark.vecs[1][3] = m[1][0] * vecs[0][3] + m[1][1] * vecs[1][3] + m[1][2]; + } + + // see if the plane has been used already + for( k = 0; k < b->numsides; k++ ) + { + s2 = b->original_sides + k; + if( s2->planenum == planenum ) + { + Msg( "Entity %i, Brush %i: duplicate plane\n", b->entitynum, b->brushnum ); + break; + } + if( s2->planenum == ( planenum^1 )) + { + Msg( "Entity %i, Brush %i: mirrored plane\n", b->entitynum, b->brushnum ); + break; + } + } + if( k != b->numsides ) continue; // duplicated + + // keep this side + side = b->original_sides + b->numsides; + side->planenum = planenum; + side->texinfo = TexinfoForBrushTexture( &mapplanes[planenum], &td, vec3_origin ); + // save the td off in case there is an origin brush and we + // have to recalculate the texinfo + side_brushtextures[nummapbrushsides] = td; + + nummapbrushsides++; + b->numsides++; + } + + if( g_brushtype == BRUSH_RADIANT ) + { + Com_SaveToken( mapfile, &token ); + Com_CheckToken( mapfile, "}" ); + Com_CheckToken( mapfile, "}" ); + } + + // get the content for the entire brush + b->contents = BrushContents( b ); + + // create windings for sides and bounds for brush + if( !MakeBrushWindings( b )) + { + // brush outside of the world, remove + b->numsides = 0; + return; + } + + // brushes that will not be visible at all will never be + // used as bsp splitters + if( b->contents & CONTENTS_CLIP ) + { + c_clipbrushes++; + for( i = 0; i < b->numsides; i++ ) + b->original_sides[i].texinfo = TEXINFO_NODE; + } + + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity. After the entire entity is parsed, + // the planenums and texinfos will be adjusted for + // the origin brush + if( b->contents & CONTENTS_ORIGIN ) + { + vec3_t size, movedir, origin; + char string[32]; + + if( num_entities == 1 ) + { + // g-cont. rotating world it's a interesting idea, hmm..... + MsgDev( D_WARN, "Entity %i, Brush %i: origin brushes not allowed in world", b->entitynum, b->brushnum ); + return; + } + + // calcualte movedir (Xash 0.4 style) + VectorAverage( b->mins, b->maxs, origin ); + VectorSubtract( b->maxs, b->mins, size ); + + if( size[2] > size[0] && size[2] > size[1] ) + VectorSet( movedir, 0, 1, 0 ); // x-rotate + else if( size[1] > size[2] && size[1] > size[0] ) + VectorSet( movedir, 1, 0, 0 ); // y-rotate + else if( size[0] > size[2] && size[0] > size[1] ) + VectorSet( movedir, 0, 0, 1 ); // z-rotate + else VectorClear( movedir ); // custom movedir + + if(!VectorIsNull( movedir )) + { + com.sprintf( string, "%i %i %i", (int)movedir[0], (int)movedir[1], (int)movedir[2] ); + SetKeyValue( &entities[b->entitynum], "movedir", string ); + } + if(!VectorIsNull( origin )) + { + com.sprintf( string, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2] ); + SetKeyValue( &entities[b->entitynum], "origin", string ); + VectorCopy( origin, entities[num_entities - 1].origin ); + } + + // don't keep this brush + b->numsides = 0; + return; + } + + AddBrushBevels( b ); + + nummapbrushes++; + mapent->numbrushes++; +} + +/* +================ +MoveBrushesToWorld + +Takes all of the brushes from the current entity and +adds them to the world's brush list. + +Used by func_group and func_areaportal +================ +*/ +void MoveBrushesToWorld( bsp_entity_t *mapent ) +{ + int newbrushes; + int worldbrushes; + mapbrush_t *temp; + int i; + + // this is pretty gross, because the brushes are expected to be + // in linear order for each entity + + newbrushes = mapent->numbrushes; + worldbrushes = entities[0].numbrushes; + if( newbrushes == 0 ) return; + + temp = Malloc( newbrushes * sizeof( mapbrush_t )); + Mem_Copy( temp, mapbrushes + mapent->firstbrush, newbrushes * sizeof( mapbrush_t )); + + // make space to move the brushes (overlapped copy) + memmove( mapbrushes + worldbrushes + newbrushes, mapbrushes + worldbrushes, + sizeof( mapbrush_t ) * ( nummapbrushes - worldbrushes - newbrushes )); + + // copy the new brushes down + Mem_Copy( mapbrushes + worldbrushes, temp, sizeof( mapbrush_t ) * newbrushes ); + + // fix up indexes + entities[0].numbrushes += newbrushes; + for( i = 1; i < num_entities; i++ ) + entities[i].firstbrush += newbrushes; + Mem_Free( temp ); + + mapent->numbrushes = 0; +} + +/* +================ +ParseMapEntity +================ +*/ +bool ParseMapEntity( void ) +{ + bsp_entity_t *mapent; + token_t token; + epair_t *e; + side_t *s; + int i, j; + int startbrush, startsides; + float newdist; + mapbrush_t *b; + + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES|SC_COMMENT_SEMICOLON, &token )) + return false; // end of .map file + if( com.stricmp( token.string, "{" )) Sys_Break( "ParseEntity: found %s instead {\n", token.string ); + if( num_entities == MAX_MAP_ENTITIES ) Sys_Break( "MAX_MAP_ENTITIES limit exceeded\n"); + + startbrush = nummapbrushes; + startsides = nummapbrushsides; + + mapent = &entities[num_entities]; + num_entities++; + Mem_Set( mapent, 0, sizeof( *mapent )); + mapent->firstbrush = nummapbrushes; + mapent->numbrushes = 0; + + while( 1 ) + { + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES|SC_COMMENT_SEMICOLON, &token )) + Sys_Break( "ParseEntity: EOF without closing brace\n" ); + if( !com.stricmp( token.string, "}" )) break; + if( !com.stricmp( token.string, "{" )) + { + // parse a brush or patch + if( !Com_ReadToken( mapfile, SC_ALLOW_NEWLINES, &token )) break; + if( !com.stricmp( token.string, "patchDef2" )) + { + g_brushtype = BRUSH_RADIANT; + Com_SkipBracedSection( mapfile, 0 ); + } + else if( !com.stricmp( token.string, "terrainDef" )) + { + g_brushtype = BRUSH_RADIANT; + Com_SkipBracedSection( mapfile, 0 ); + } + else if( !com.stricmp( token.string, "brushDef" )) + { + g_brushtype = BRUSH_RADIANT; + ParseBrush( mapent ); // parse brush primitive + } + else + { + // predict state + if( g_brushtype == BRUSH_UNKNOWN ) + g_brushtype = BRUSH_WORLDCRAFT_21; + // QuArK or WorldCraft map + Com_SaveToken( mapfile, &token ); + ParseBrush( mapent ); + } + } + else + { + // parse a key / value pair + e = ParseEpair( mapfile, &token ); + if( !com.strcmp( e->key, "mapversion" )) + { + if( com.atoi( e->value ) == VALVE_FORMAT ) + g_brushtype = BRUSH_WORLDCRAFT_22; + else g_brushtype = BRUSH_WORLDCRAFT_21; + } + e->next = mapent->epairs; + mapent->epairs = e; + } + } + + GetVectorForKey( mapent, "origin", mapent->origin ); + + // if there was an origin brush, offset all of the planes and texinfo + if( mapent->origin[0] || mapent->origin[1] || mapent->origin[2] ) + { + for( i = 0; i < mapent->numbrushes; i++ ) + { + b = &mapbrushes[mapent->firstbrush + i]; + for( j = 0; j < b->numsides; j++ ) + { + s = &b->original_sides[j]; + newdist = mapplanes[s->planenum].dist - DotProduct (mapplanes[s->planenum].normal, mapent->origin); + s->planenum = FindFloatPlane( mapplanes[s->planenum].normal, newdist ); + s->texinfo = TexinfoForBrushTexture( &mapplanes[s->planenum], &side_brushtextures[s-brushsides], mapent->origin ); + } + MakeBrushWindings( b ); + } + } + + // group entities are just for editor convenience + // toss all brushes into the world entity + if( !com.strcmp( "func_detail", ValueForKey( mapent, "classname" ))) + { + for( i = 0; i < mapent->numbrushes; i++ ) + { + b = &mapbrushes[mapent->firstbrush + i]; + b->contents |= CONTENTS_DETAIL; + } + MoveBrushesToWorld( mapent ); + mapent->numbrushes = 0; + mapent->epairs = NULL; // not a leak, mempool will freeing this memory at shutdown + return true; + } + + // areaportal entities move their brushes, but don't eliminate the entity + if( !com.strcmp( "func_areaportal", ValueForKey( mapent, "classname" ))) + { + char str[128]; + + if (mapent->numbrushes != 1) + Sys_Error ("Entity %i: func_areaportal can only be a single brush", num_entities-1); + + b = &mapbrushes[nummapbrushes-1]; + b->contents = CONTENTS_AREAPORTAL; + c_areaportals++; + mapent->areaportalnum = c_areaportals; + // set the portal number as "skin" + com.sprintf( str, "%i", c_areaportals ); + SetKeyValue( mapent, "skin", str ); + MoveBrushesToWorld( mapent ); + return true; + } + + return true; +} + +//=================================================================== + +/* +================ +LoadMapFile +================ +*/ +void LoadMapFile( void ) +{ + int i; + + g_brushtype = BRUSH_UNKNOWN; + nummapbrushsides = 0; + num_entities = 0; + + mapfile = Com_OpenScript( va( "maps/%s.map", gs_filename ), NULL, 0 ); + if( !mapfile ) Sys_Break( "can't loading map file %s.map\n", gs_filename ); + + while(ParseMapEntity( )); + + Com_CloseScript( mapfile ); + ClearBounds( map_mins, map_maxs ); + for( i = 0; i < entities[0].numbrushes; i++ ) + { + AddPointToBounds( mapbrushes[i].mins, map_mins, map_maxs ); + AddPointToBounds( mapbrushes[i].maxs, map_mins, map_maxs ); + } + VectorSubtract( map_maxs, map_mins, map_size ); + MsgDev( D_INFO, "%5i brushes\n", nummapbrushes ); + MsgDev( D_INFO, "%5i clipbrushes\n", c_clipbrushes ); + MsgDev( D_INFO, "%5i total sides\n", nummapbrushsides ); + MsgDev( D_INFO, "%5i entities\n", num_entities ); + MsgDev( D_INFO, "%5i planes\n", nummapplanes ); + MsgDev( D_INFO, "%5i areaportals\n", c_areaportals ); + MsgDev( D_INFO, "world size %5.0f %5.0f %5.0f\n", map_size[0], map_size[1], map_size[2] ); +} \ No newline at end of file diff --git a/xtools/bsplib/patches.c b/xtools/bsplib/patches.c new file mode 100644 index 00000000..020527c3 --- /dev/null +++ b/xtools/bsplib/patches.c @@ -0,0 +1,474 @@ +#include "bsplib.h" +#include "const.h" + +vec3_t texture_reflectivity[MAX_MAP_SHADERS]; + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ + +/* +====================== +CalcTextureReflectivity +====================== +*/ +void CalcTextureReflectivity( void ) +{ + int j, i; + rgbdata_t *pic; + bsp_shader_t *si; + + // allways set index 0 even if no textures + texture_reflectivity[0][0] = 0.5; + texture_reflectivity[0][1] = 0.5; + texture_reflectivity[0][2] = 0.5; + + for( i = 0; i < numtexinfo; i++ ) + { + // see if an earlier texinfo allready got the value + for (j = 0; j < i; j++) + { + if( texinfo[i].shadernum == texinfo[j].shadernum ) + { + VectorCopy( texture_reflectivity[j], texture_reflectivity[i] ); + break; + } + } + if( j != i ) continue; + + // try get direct values from shader + if( si = FindShader( dshaders[texinfo[i].shadernum].name )) + { + if( !VectorIsNull( si->color )) + { + VectorDivide( si->color, 255.0f, texture_reflectivity[i] ); + texinfo[i].value = si->intensity; + continue; + } + } + + pic = FS_LoadImage( dshaders[texinfo[i].shadernum].name, NULL, 0 ); + + if( pic ) + { + // create default and average colors + int k, texels = pic->width * pic->height; + float r, scale; + vec3_t color; + + VectorClear( color ); + Com_Assert( pic->type != PF_RGBA_32 ); + + for( k = 0; k < texels; k++ ) + { + color[0] += pic->buffer[k*4+0]; + color[1] += pic->buffer[k*4+1]; + color[2] += pic->buffer[k*4+2]; + } + + for( k = 0; k < 3; k++ ) + { + r = color[k] / texels; + color[k] = r; + } + + // scale the reflectivity up, because the textures are so dim + scale = ColorNormalize( color, texture_reflectivity[i] ); + texinfo[i].value = (texels * 255.0) / scale; // basic intensity value + FS_FreeImage( pic ); // don't forget free image + } + else VectorSet( texture_reflectivity[i], 0.5, 0.5, 0.5 ); // no texture, no shader... + } +} + +/* +======================================================================= + +MAKE FACES + +======================================================================= +*/ + +/* +============= +WindingFromFace +============= +*/ +winding_t *WindingFromFace( dsurface_t *f ) +{ + int i; + int se; + dvertex_t *dv; + int v; + winding_t *w; + + w = AllocWinding( f->numedges ); + w->numpoints = f->numedges; + + for( i = 0; i < f->numedges; i++ ) + { + se = dsurfedges[f->firstedge + i]; + if (se < 0) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dv = &dvertexes[v]; + VectorCopy (dv->point, w->p[i]); + } + + RemoveColinearPoints (w); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace( dsurface_t *f, vec3_t color ) +{ + dshader_t *si; + dtexinfo_t *tx; + + // + // check for light emited by texture + // + tx = &texinfo[f->texinfo]; + si = &dshaders[tx->shadernum]; + if(!(si->surfaceFlags & SURF_LIGHT ) || tx->value == 0 ) + { + VectorClear( color ); + return; + } + VectorScale( texture_reflectivity[f->texinfo], tx->value, color ); +} + +bool IsSky( dsurface_t *f ) +{ + dshader_t *tx; + + tx = &dshaders[texinfo[f->texinfo].shadernum]; + if( tx->surfaceFlags & SURF_SKY|SURF_3DSKY ) + return true; + return false; +} + +/* +============= +MakePatchForFace +============= +*/ +float totalarea; +void MakePatchForFace( int fn, winding_t *w ) +{ + dsurface_t *f; + float area; + patch_t *patch; + dplane_t *pl; + int i; + vec3_t color; + dleaf_t *leaf; + + f = &dsurfaces[fn]; + + area = WindingArea( w ); + totalarea += area; + + patch = &patches[num_patches]; + if( num_patches == MAX_PATCHES ) + Sys_Error( "MAX_PATCHES limit exceeded\n" ); + patch->next = face_patches[fn]; + face_patches[fn] = patch; + + patch->winding = w; + + if( f->side ) patch->plane = &backplanes[f->planenum]; + else patch->plane = &dplanes[f->planenum]; + + if( face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) + { + // origin offset faces must create new planes + if( numplanes + fakeplanes >= MAX_MAP_PLANES ) + Sys_Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); + pl = &dplanes[numplanes + fakeplanes]; + fakeplanes++; + + *pl = *(patch->plane); + pl->dist += DotProduct (face_offset[fn], pl->normal); + patch->plane = pl; + } + + WindingCenter (w, patch->origin); + VectorAdd (patch->origin, patch->plane->normal, patch->origin); + leaf = PointInLeaf(patch->origin); + patch->cluster = leaf->cluster; + + patch->area = area; + if (patch->area <= 1) + patch->area = 1; + patch->sky = IsSky (f); + + VectorCopy (texture_reflectivity[f->texinfo], patch->reflectivity); + + // non-bmodel patches can emit light + if (fn < dmodels[0].numsurfaces) + { + BaseLightForFace (f, patch->baselight); + + ColorNormalize (patch->reflectivity, color); + + for (i=0 ; i<3 ; i++) + patch->baselight[i] *= color[i]; + + VectorCopy (patch->baselight, patch->totallight); + } + num_patches++; +} + + +bsp_entity_t *EntityForModel( int modnum ) +{ + char *s, name[16]; + int i; + + com.sprintf( name, "*%i", modnum ); + + // search the entities for one using modnum + for( i = 0; i < num_entities; i++ ) + { + s = ValueForKey( &entities[i], "model" ); + if( !com.strcmp( s, name )) + return &entities[i]; + } + return &entities[0]; +} + +/* +============= +MakePatches +============= +*/ +void MakePatches( void ) +{ + int i, j, k; + dsurface_t *f; + int fn; + winding_t *w; + dmodel_t *mod; + vec3_t origin; + bsp_entity_t *ent; + + MsgDev( D_INFO, "%i faces\n", numsurfaces ); + + for( i = 0; i < nummodels; i++ ) + { + mod = &dmodels[i]; + ent = EntityForModel (i); + + // bmodels with origin brushes need to be offset into their in-use position + GetVectorForKey( ent, "origin", origin ); + + for( j = 0; j < mod->numsurfaces; j++ ) + { + fn = mod->firstsurface + j; + face_entity[fn] = ent; + VectorCopy( origin, face_offset[fn] ); + f = &dsurfaces[fn]; + w = WindingFromFace( f ); + for( k = 0; k < w->numpoints; k++ ) + VectorAdd( w->p[k], origin, w->p[k] ); + MakePatchForFace( fn, w ); + } + } + + MsgDev( D_INFO, "%i square feet\n", (int)( totalarea/64 )); +} + +/* +======================================================================= + +SUBDIVIDE + +======================================================================= +*/ + +void FinishSplit (patch_t *patch, patch_t *newp) +{ + dleaf_t *leaf; + + VectorCopy (patch->baselight, newp->baselight); + VectorCopy (patch->totallight, newp->totallight); + VectorCopy (patch->reflectivity, newp->reflectivity); + newp->plane = patch->plane; + newp->sky = patch->sky; + + patch->area = WindingArea (patch->winding); + newp->area = WindingArea (newp->winding); + + if (patch->area <= 1) + patch->area = 1; + if (newp->area <= 1) + newp->area = 1; + + WindingCenter (patch->winding, patch->origin); + VectorAdd (patch->origin, patch->plane->normal, patch->origin); + leaf = PointInLeaf(patch->origin); + patch->cluster = leaf->cluster; + + WindingCenter (newp->winding, newp->origin); + VectorAdd (newp->origin, newp->plane->normal, newp->origin); + leaf = PointInLeaf(newp->origin); + newp->cluster = leaf->cluster; +} + +/* +============= +SubdividePatch + +Chops the patch only if its local bounds exceed the max size +============= +*/ +void SubdividePatch (patch_t *patch) +{ + winding_t *w, *o1, *o2; + vec3_t mins, maxs, total; + vec3_t split; + vec_t dist; + int i, j; + vec_t v; + patch_t *newp; + + w = patch->winding; + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } + VectorSubtract (maxs, mins, total); + for (i=0 ; i<3 ; i++) + if (total[i] > (subdiv+1) ) + break; + if (i == 3) + { + // no splitting needed + return; + } + + // + // split the winding + // + VectorCopy (vec3_origin, split); + split[i] = 1; + dist = (mins[i] + maxs[i])*0.5; + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // + // create a new patch + // + if (num_patches == MAX_PATCHES) + Sys_Error ("MAX_PATCHES"); + newp = &patches[num_patches]; + num_patches++; + + newp->next = patch->next; + patch->next = newp; + + patch->winding = o1; + newp->winding = o2; + + FinishSplit (patch, newp); + + SubdividePatch (patch); + SubdividePatch (newp); +} + + +/* +============= +DicePatch + +Chops the patch by a global grid +============= +*/ +void DicePatch (patch_t *patch) +{ + winding_t *w, *o1, *o2; + vec3_t mins, maxs; + vec3_t split; + vec_t dist; + int i; + patch_t *newp; + + w = patch->winding; + WindingBounds (w, mins, maxs); + for (i=0 ; i<3 ; i++) + if (floor((mins[i]+1)/subdiv) < floor((maxs[i]-1)/subdiv)) + break; + if (i == 3) + { + // no splitting needed + return; + } + + // + // split the winding + // + VectorCopy (vec3_origin, split); + split[i] = 1; + dist = subdiv*(1+floor((mins[i]+1)/subdiv)); + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // + // create a new patch + // + if (num_patches == MAX_PATCHES) + Sys_Error ("MAX_PATCHES"); + newp = &patches[num_patches]; + num_patches++; + + newp->next = patch->next; + patch->next = newp; + + patch->winding = o1; + newp->winding = o2; + + FinishSplit (patch, newp); + + DicePatch (patch); + DicePatch (newp); +} + + +/* +============= +SubdividePatches +============= +*/ +void SubdividePatches (void) +{ + int i, num; + + if (subdiv < 1) + return; + + num = num_patches; // because the list will grow + for (i=0 ; i c_peak_portals) + c_peak_portals = c_active_portals; + + p = Malloc (sizeof(portal_t)); + + return p; +} + +void FreePortal (portal_t *p) +{ + if (p->winding) + FreeWinding (p->winding); + if (GetNumThreads() == 1) + c_active_portals--; + Mem_Free (p); +} + +//============================================================== + +/* +============== +VisibleContents + +Returns the single content bit of the +strongest visible content present +============== +*/ +int VisibleContents (int contents) +{ + int i; + + for (i=1 ; i<=LAST_VISIBLE_CONTENTS ; i<<=1) + if (contents & i ) + return i; + + return 0; +} + + +/* +=============== +ClusterContents +=============== +*/ +int ClusterContents (node_t *node) +{ + int c1, c2, c; + + if (node->planenum == PLANENUM_LEAF) + return node->contents; + + c1 = ClusterContents(node->children[0]); + c2 = ClusterContents(node->children[1]); + c = c1|c2; + + // a cluster may include some solid detail areas, but + // still be seen into + if ( ! (c1&CONTENTS_SOLID) || ! (c2&CONTENTS_SOLID) ) + c &= ~CONTENTS_SOLID; + return c; +} + +/* +============= +Portal_VisFlood + +Returns true if the portal is empty or translucent, allowing +the PVS calculation to see through it. +The nodes on either side of the portal may actually be clusters, +not leafs, so all contents should be ored together +============= +*/ +bool Portal_VisFlood (portal_t *p) +{ + int c1, c2; + + if (!p->onnode) + return false; // to global outsideleaf + + c1 = ClusterContents(p->nodes[0]); + c2 = ClusterContents(p->nodes[1]); + + if (!VisibleContents (c1^c2)) + return true; + + if (c1 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c1 = 0; + if (c2 & (CONTENTS_TRANSLUCENT|CONTENTS_DETAIL)) + c2 = 0; + + if ( (c1|c2) & CONTENTS_SOLID ) + return false; // can't see through solid + + if (! (c1 ^ c2)) + return true; // identical on both sides + + if (!VisibleContents (c1^c2)) + return true; + return false; +} + + +/* +=============== +Portal_EntityFlood + +The entity flood determines which areas are +"outside" on the map, which are then filled in. +Flowing from side s to side !s +=============== +*/ +bool Portal_EntityFlood (portal_t *p, int s) +{ + if (p->nodes[0]->planenum != PLANENUM_LEAF + || p->nodes[1]->planenum != PLANENUM_LEAF) + Sys_Error ("Portal_EntityFlood: not a leaf"); + + // can never cross to a solid + if ( (p->nodes[0]->contents & CONTENTS_SOLID) + || (p->nodes[1]->contents & CONTENTS_SOLID) ) + return false; + + // can flood through everything else + return true; +} + + +//============================================================================= + +int c_tinyportals; + +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes (portal_t *p, node_t *front, node_t *back) +{ + if (p->nodes[0] || p->nodes[1]) + Sys_Error ("AddPortalToNode: allready included"); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode (portal_t *portal, node_t *l) +{ + portal_t **pp, *t; + +// remove reference to the current portal + pp = &l->portals; + while (1) + { + t = *pp; + if (!t) + Sys_Error ("RemovePortalFromNode: portal not in leaf"); + + if ( t == portal ) + break; + + if (t->nodes[0] == l) + pp = &t->next[0]; + else if (t->nodes[1] == l) + pp = &t->next[1]; + else + Sys_Error ("RemovePortalFromNode: portal not bounding leaf"); + } + + if (portal->nodes[0] == l) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if (portal->nodes[1] == l) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal (portal_t *p) +{ + int i; + winding_t *w; + + w = p->winding; + for (i=0 ; inumpoints ; i++) + Msg ("(%5.0f,%5.0f,%5.0f)\n",w->p[i][0], w->p[i][1], w->p[i][2]); +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +#define SIDESPACE 8 +void MakeHeadnodePortals (tree_t *tree) +{ + vec3_t bounds[2]; + int i, j, n; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + node_t *node; + + node = tree->headnode; + +// pad with some space so there will never be null volume leafs + for (i=0 ; i<3 ; i++) + { + bounds[0][i] = tree->mins[i] - SIDESPACE; + bounds[1][i] = tree->maxs[i] + SIDESPACE; + } + + tree->outside_node.planenum = PLANENUM_LEAF; + tree->outside_node.brushlist = NULL; + tree->outside_node.portals = NULL; + tree->outside_node.contents = 0; + + for (i=0 ; i<3 ; i++) + for (j=0 ; j<2 ; j++) + { + n = j*3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset (pl, 0, sizeof(*pl)); + if (j) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + p->plane = *pl; + p->winding = BaseWindingForPlane (pl->normal, pl->dist); + AddPortalToNodes (p, node, &tree->outside_node); + } + +// clip the basewindings by all the other planes + for (i=0 ; i<6 ; i++) + { + for (j=0 ; j<6 ; j++) + { + if (j == i) + continue; + ChopWindingInPlace (&portals[i]->winding, bplanes[j].normal, bplanes[j].dist, ON_EPSILON); + } + } +} + +//=================================================== + + +/* +================ +BaseWindingForNode +================ +*/ +winding_t *BaseWindingForNode (node_t *node) +{ + winding_t *w; + node_t *n; + plane_t *plane; + vec3_t normal; + vec_t dist; + + w = BaseWindingForPlane (mapplanes[node->planenum].normal, mapplanes[node->planenum].dist); + + // clip by all the parents + for (n=node->parent ; n && w ; ) + { + plane = &mapplanes[n->planenum]; + + if (n->children[0] == node) + { // take front + ChopWindingInPlace (&w, plane->normal, plane->dist, EQUAL_EPSILON); + } + else + { // take back + VectorSubtract (vec3_origin, plane->normal, normal); + dist = -plane->dist; + ChopWindingInPlace (&w, normal, dist, EQUAL_EPSILON); + } + node = n; + n = n->parent; + } + + return w; +} + +//============================================================ + +bool WindingIsTiny (winding_t *w); + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of parents of this node +================== +*/ +void MakeNodePortal (node_t *node) +{ + portal_t *new_portal, *p; + winding_t *w; + vec3_t normal; + float dist; + int side; + + w = BaseWindingForNode (node); + + // clip the portal by all the other portals in the node + for (p = node->portals ; p && w; p = p->next[side]) + { + if (p->nodes[0] == node) + { + side = 0; + VectorCopy (p->plane.normal, normal); + dist = p->plane.dist; + } + else if (p->nodes[1] == node) + { + side = 1; + VectorSubtract (vec3_origin, p->plane.normal, normal); + dist = -p->plane.dist; + } + else + Sys_Error ("CutNodePortals_r: mislinked portal"); + + ChopWindingInPlace (&w, normal, dist, 0.1f); + } + + if (!w) + { + return; + } + + if (WindingIsTiny (w)) + { + c_tinyportals++; + FreeWinding (w); + return; + } + + + new_portal = AllocPortal (); + new_portal->plane = mapplanes[node->planenum]; + new_portal->onnode = node; + new_portal->winding = w; + AddPortalToNodes (new_portal, node->children[0], node->children[1]); +} + + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals (node_t *node) +{ + portal_t *p, *next_portal, *new_portal; + node_t *f, *b, *other_node; + int side; + plane_t *plane; + winding_t *frontwinding, *backwinding; + + plane = &mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for (p = node->portals ; p ; p = next_portal) + { + if (p->nodes[0] == node) + side = 0; + else if (p->nodes[1] == node) + side = 1; + else + Sys_Error ("CutNodePortals_r: mislinked portal"); + next_portal = p->next[side]; + + other_node = p->nodes[!side]; + RemovePortalFromNode (p, p->nodes[0]); + RemovePortalFromNode (p, p->nodes[1]); + +// +// cut the portal into two portals, one on each side of the cut plane +// + ClipWindingEpsilon (p->winding, plane->normal, plane->dist, + EQUAL_EPSILON, &frontwinding, &backwinding); + + if (frontwinding && WindingIsTiny(frontwinding)) + { + FreeWinding (frontwinding); + frontwinding = NULL; + c_tinyportals++; + } + + if (backwinding && WindingIsTiny(backwinding)) + { + FreeWinding (backwinding); + backwinding = NULL; + c_tinyportals++; + } + + if (!frontwinding && !backwinding) + { // tiny windings on both sides + continue; + } + + if (!frontwinding) + { + FreeWinding (backwinding); + if (side == 0) + AddPortalToNodes (p, b, other_node); + else + AddPortalToNodes (p, other_node, b); + continue; + } + if (!backwinding) + { + FreeWinding (frontwinding); + if (side == 0) + AddPortalToNodes (p, f, other_node); + else + AddPortalToNodes (p, other_node, f); + continue; + } + + // the winding is split + new_portal = AllocPortal (); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding (p->winding); + p->winding = frontwinding; + + if (side == 0) + { + AddPortalToNodes (p, f, other_node); + AddPortalToNodes (new_portal, b, other_node); + } + else + { + AddPortalToNodes (p, other_node, f); + AddPortalToNodes (new_portal, other_node, b); + } + } + + node->portals = NULL; +} + + +/* +================ +CalcNodeBounds +================ +*/ +void CalcNodeBounds (node_t *node) +{ + portal_t *p; + int s; + int i; + + // calc mins/maxs for both leafs and nodes + ClearBounds (node->mins, node->maxs); + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + for (i=0 ; iwinding->numpoints ; i++) + AddPointToBounds (p->winding->p[i], node->mins, node->maxs); + } +} + + +/* +================== +MakeTreePortals_r +================== +*/ +void MakeTreePortals_r (node_t *node) +{ + int i; + + CalcNodeBounds (node); + if (node->mins[0] >= node->maxs[0]) + { + Msg("WARNING: node without a volume\n"); + } + + for( i = 0; i < 3; i++ ) + { + if (node->mins[i] < -BOGUS_RANGE || node->maxs[i] > BOGUS_RANGE) + { + Msg("WARNING: node with unbounded volume\n"); + break; + } + } + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + MakeTreePortals_r (node->children[0]); + MakeTreePortals_r (node->children[1]); +} + +/* +================== +MakeTreePortals +================== +*/ +void MakeTreePortals (tree_t *tree) +{ + MakeHeadnodePortals (tree); + MakeTreePortals_r (tree->headnode); +} + +/* +========================================================= + +FLOOD ENTITIES + +========================================================= +*/ + +/* +============= +FloodPortals_r +============= +*/ +void FloodPortals_r (node_t *node, int dist) +{ + portal_t *p; + int s; + + node->occupied = dist; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + + if (p->nodes[!s]->occupied) + continue; + + if (!Portal_EntityFlood (p, s)) + continue; + + FloodPortals_r (p->nodes[!s], dist+1); + } +} + +/* +============= +PlaceOccupant +============= +*/ +bool PlaceOccupant (node_t *headnode, vec3_t origin, bsp_entity_t *occupant) +{ + node_t *node; + vec_t d; + plane_t *plane; + + // find the leaf to start in + node = headnode; + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + if (node->contents == CONTENTS_SOLID) + return false; + node->occupant = occupant; + + FloodPortals_r (node, 1); + + return true; +} + +/* +============= +FloodEntities + +Marks all nodes that can be reached by entites +============= +*/ +bool FloodEntities (tree_t *tree) +{ + int i; + vec3_t origin; + char *cl; + bool inside; + node_t *headnode; + + headnode = tree->headnode; + inside = false; + tree->outside_node.occupied = 0; + + for (i=1 ; ioutside_node.occupied); +} + +/* +========================================================= + +FLOOD AREAS + +========================================================= +*/ + +int c_areas; + +/* +============= +FloodAreas_r +============= +*/ +void FloodAreas_r (node_t *node) +{ + portal_t *p; + int s; + bspbrush_t *b; + bsp_entity_t *e; + + if (node->contents == CONTENTS_AREAPORTAL) + { + // this node is part of an area portal + b = node->brushlist; + e = &entities[b->original->entitynum]; + + // if the current area has allready touched this + // portal, we are done + if (e->portalareas[0] == c_areas || e->portalareas[1] == c_areas) + return; + + // note the current area as bounding the portal + if (e->portalareas[1]) + { + Msg("WARNING: areaportal entity %i touches > 2 areas\n", b->original->entitynum); + return; + } + if (e->portalareas[0]) + e->portalareas[1] = c_areas; + else + e->portalareas[0] = c_areas; + + return; + } + + if (node->area) + return; // allready got it + node->area = c_areas; + + for (p=node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); +#if 0 + if (p->nodes[!s]->occupied) + continue; +#endif + if (!Portal_EntityFlood (p, s)) + continue; + + FloodAreas_r (p->nodes[!s]); + } +} + +/* +============= +FindAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void FindAreas_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FindAreas_r (node->children[0]); + FindAreas_r (node->children[1]); + return; + } + + if (node->area) + return; // allready got it + + if (node->contents & CONTENTS_SOLID) + return; + + if (!node->occupied) + return; // not reachable by entities + + // area portals are allways only flooded into, never + // out of + if (node->contents == CONTENTS_AREAPORTAL) + return; + + c_areas++; + FloodAreas_r (node); +} + +/* +============= +SetAreaPortalAreas_r + +Just decend the tree, and for each node that hasn't had an +area set, flood fill out from there +============= +*/ +void SetAreaPortalAreas_r (node_t *node) +{ + bspbrush_t *b; + bsp_entity_t *e; + + if (node->planenum != PLANENUM_LEAF) + { + SetAreaPortalAreas_r (node->children[0]); + SetAreaPortalAreas_r (node->children[1]); + return; + } + + if (node->contents == CONTENTS_AREAPORTAL) + { + if (node->area) + return; // allready set + + b = node->brushlist; + e = &entities[b->original->entitynum]; + node->area = e->portalareas[0]; + if (!e->portalareas[1]) + { + Msg("WARNING: areaportal entity %i doesn't touch two areas\n", b->original->entitynum); + return; + } + } +} + +/* +============= +EmitAreaPortals + +============= +*/ +void EmitAreaPortals (node_t *headnode) +{ + int i, j; + bsp_entity_t *e; + dareaportal_t *dp; + + if (c_areas > MAX_MAP_AREAS) + Sys_Error ("MAX_MAP_AREAS"); + numareas = c_areas+1; + numareaportals = 1; // leave 0 as an error + + for (i=1 ; i<=c_areas ; i++) + { + dareas[i].firstareaportal = numareaportals; + for (j=0 ; jareaportalnum) + continue; + dp = &dareaportals[numareaportals]; + if (e->portalareas[0] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[1]; + numareaportals++; + } + else if (e->portalareas[1] == i) + { + dp->portalnum = e->areaportalnum; + dp->otherarea = e->portalareas[0]; + numareaportals++; + } + } + dareas[i].numareaportals = numareaportals - dareas[i].firstareaportal; + } +} + +/* +============= +FloodAreas + +Mark each leaf with an area, bounded by CONTENTS_AREAPORTAL +============= +*/ +void FloodAreas (tree_t *tree) +{ + FindAreas_r (tree->headnode); + SetAreaPortalAreas_r (tree->headnode); +} + +//====================================================== + +int c_outside; +int c_inside; +int c_solid; + +void FillOutside_r (node_t *node) +{ + if (node->planenum != PLANENUM_LEAF) + { + FillOutside_r (node->children[0]); + FillOutside_r (node->children[1]); + return; + } + + // anything not reachable by an entity + // can be filled away + if (!node->occupied) + { + if (node->contents != CONTENTS_SOLID) + { + c_outside++; + node->contents = CONTENTS_SOLID; + } + else + c_solid++; + } + else + c_inside++; + +} + +/* +============= +FillOutside + +Fill all nodes that can't be reached by entities +============= +*/ +void FillOutside (node_t *headnode) +{ + c_outside = 0; + c_inside = 0; + c_solid = 0; + FillOutside_r (headnode); +} + + +//============================================================== + +/* +============ +FindPortalSide + +Finds a brush side to use for texturing the given portal +============ +*/ +void FindPortalSide (portal_t *p) +{ + int viscontents; + bspbrush_t *bb; + mapbrush_t *brush; + node_t *n; + int i,j; + int planenum; + side_t *side, *bestside; + float dot, bestdot; + plane_t *p1, *p2; + + // decide which content change is strongest + // solid > lava > water, etc + viscontents = VisibleContents (p->nodes[0]->contents ^ p->nodes[1]->contents); + if (!viscontents) + return; + + planenum = p->onnode->planenum; + bestside = NULL; + bestdot = 0; + + for (j=0 ; j<2 ; j++) + { + n = p->nodes[j]; + p1 = &mapplanes[p->onnode->planenum]; + for (bb=n->brushlist ; bb ; bb=bb->next) + { + brush = bb->original; + if ( !(brush->contents & viscontents) ) + continue; + for (i=0 ; inumsides ; i++) + { + side = &brush->original_sides[i]; + if (side->bevel) + continue; + if (side->texinfo == TEXINFO_NODE) + continue; // non-visible + if ((side->planenum&~1) == planenum) + { // exact match + bestside = &brush->original_sides[i]; + goto gotit; + } + // see how close the match is + p2 = &mapplanes[side->planenum&~1]; + dot = DotProduct (p1->normal, p2->normal); + if (dot > bestdot) + { + bestdot = dot; + bestside = side; + } + } + } + } + +gotit: + if( !bestside ) MsgDev( D_WARN, "side not found for portal\n"); + + p->sidefound = true; + p->side = bestside; +} + + +/* +=============== +MarkVisibleSides_r + +=============== +*/ +void MarkVisibleSides_r (node_t *node) +{ + portal_t *p; + int s; + + if (node->planenum != PLANENUM_LEAF) + { + MarkVisibleSides_r (node->children[0]); + MarkVisibleSides_r (node->children[1]); + return; + } + + // empty leafs are never boundary leafs + if (!node->contents) + return; + + // see if there is a visible face + for (p=node->portals ; p ; p = p->next[!s]) + { + s = (p->nodes[0] == node); + if (!p->onnode) + continue; // edge of world + if (!p->sidefound) + FindPortalSide (p); + if (p->side) + p->side->visible = true; + } + +} + +/* +============= +MarkVisibleSides + +============= +*/ +void MarkVisibleSides (tree_t *tree, int startbrush, int endbrush) +{ + int i, j; + mapbrush_t *mb; + int numsides; + + // clear all the visible flags + for (i=startbrush ; inumsides; + for (j=0 ; joriginal_sides[j].visible = false; + } + + // set visible flags on the sides that are used by portals + MarkVisibleSides_r (tree->headnode); +} + diff --git a/xtools/bsplib/prtfile.c b/xtools/bsplib/prtfile.c new file mode 100644 index 00000000..73e43385 --- /dev/null +++ b/xtools/bsplib/prtfile.c @@ -0,0 +1,260 @@ + +#include "bsplib.h" +#include "const.h" + +/* +============================================================================== + +Portal file generation + +Save out name.prt for qvis to read +============================================================================== +*/ + + +#define PORTALFILE "PRT1" + +file_t *pf; +int num_visclusters; // clusters the player can be in +int num_visportals; + +void WriteFloat (file_t *f, vec_t v) +{ + if ( fabs(v - floor(v + 0.5)) < 0.001 ) + FS_Printf(f,"%i ",(int)floor(v + 0.5)); + else FS_Printf(f,"%f ",v); +} + +/* +================= +WritePortalFile_r +================= +*/ +void WritePortalFile_r (node_t *node) +{ + int i, s; + portal_t *p; + winding_t *w; + vec3_t normal; + vec_t dist; + + // decision node + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { + WritePortalFile_r (node->children[0]); + WritePortalFile_r (node->children[1]); + return; + } + + if (node->contents & CONTENTS_SOLID) + return; + + for (p = node->portals ; p ; p=p->next[s]) + { + w = p->winding; + s = (p->nodes[1] == node); + if (w && p->nodes[0] == node) + { + if (!Portal_VisFlood (p)) continue; + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + // FIXME: is this still relevent? + WindingPlane (w, normal, &dist); + if ( DotProduct (p->plane.normal, normal) < 0.99 ) + { // backwards... + FS_Printf(pf,"%i %i %i ",w->numpoints, p->nodes[1]->cluster, p->nodes[0]->cluster); + } + else + FS_Printf(pf,"%i %i %i ",w->numpoints, p->nodes[0]->cluster, p->nodes[1]->cluster); + for (i=0 ; inumpoints ; i++) + { + FS_Printf (pf,"( "); + WriteFloat (pf, w->p[i][0]); + WriteFloat (pf, w->p[i][1]); + WriteFloat (pf, w->p[i][2]); + FS_Printf (pf,") "); + } + FS_Printf(pf,"\n"); + } + } + +} + +/* +================ +FillLeafNumbers_r + +All of the leafs under node will have the same cluster +================ +*/ +void FillLeafNumbers_r (node_t *node, int num) +{ + if (node->planenum == PLANENUM_LEAF) + { + if (node->contents & CONTENTS_SOLID) + node->cluster = -1; + else + node->cluster = num; + return; + } + node->cluster = num; + FillLeafNumbers_r (node->children[0], num); + FillLeafNumbers_r (node->children[1], num); +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r (node_t *node) +{ + portal_t *p; + + if (node->planenum != PLANENUM_LEAF && !node->detail_seperator) + { // decision node + node->cluster = -99; + NumberLeafs_r (node->children[0]); + NumberLeafs_r (node->children[1]); + return; + } + + // either a leaf or a detail cluster + + if ( node->contents & CONTENTS_SOLID ) + { // solid block, viewpoint never inside + node->cluster = -1; + return; + } + + FillLeafNumbers_r (node, num_visclusters); + num_visclusters++; + + // count the portals + for (p = node->portals ; p ; ) + { + if (p->nodes[0] == node) // only write out from first leaf + { + if (Portal_VisFlood (p)) + num_visportals++; + p = p->next[0]; + } + else + p = p->next[1]; + } + +} + + +/* +================ +CreateVisPortals_r +================ +*/ +void CreateVisPortals_r (node_t *node) +{ + // stop as soon as we get to a detail_seperator, which + // means that everything below is in a single cluster + if (node->planenum == PLANENUM_LEAF || node->detail_seperator ) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + CreateVisPortals_r (node->children[0]); + CreateVisPortals_r (node->children[1]); +} + +/* +================ +FinishVisPortals_r +================ +*/ +void FinishVisPortals2_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + MakeNodePortal (node); + SplitNodePortals (node); + + FinishVisPortals2_r (node->children[0]); + FinishVisPortals2_r (node->children[1]); +} + +void FinishVisPortals_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + return; + + if (node->detail_seperator) + { + FinishVisPortals2_r (node); + return; + } + + FinishVisPortals_r (node->children[0]); + FinishVisPortals_r (node->children[1]); +} + + +int clusterleaf; +void SaveClusters_r (node_t *node) +{ + if (node->planenum == PLANENUM_LEAF) + { + dleafs[clusterleaf++].cluster = node->cluster; + return; + } + SaveClusters_r (node->children[0]); + SaveClusters_r (node->children[1]); +} + +/* +================ +WritePortalFile +================ +*/ +void WritePortalFile (tree_t *tree) +{ + char path[MAX_SYSPATH]; + node_t *headnode; + + Msg("--- WritePortalFile ---\n"); + + headnode = tree->headnode; + num_visclusters = 0; + num_visportals = 0; + + FreeTreePortals_r (headnode); + MakeHeadnodePortals (tree); + CreateVisPortals_r (headnode); + + // set the cluster field in every leaf and count the total number of portals + NumberLeafs_r (headnode); + + // write the file + com.sprintf (path, "maps/%s.prt", gs_filename ); + pf = FS_Open(path, "w" ); + if (!pf) Sys_Error ("Error opening %s", path); + + FS_Printf (pf, "%s\n", PORTALFILE); + FS_Printf (pf, "%i\n", num_visclusters); + FS_Printf (pf, "%i\n", num_visportals); + + MsgDev( D_INFO, "%5i visclusters\n", num_visclusters ); + MsgDev( D_INFO, "%5i visportals\n", num_visportals ); + + WritePortalFile_r( headnode ); + + FS_Close( pf ); + + // we need to store the clusters out now because ordering + // issues made us do this after writebsp... + clusterleaf = 1; + SaveClusters_r (headnode); +} + diff --git a/xtools/bsplib/qbsp3.c b/xtools/bsplib/qbsp3.c new file mode 100644 index 00000000..59a112cb --- /dev/null +++ b/xtools/bsplib/qbsp3.c @@ -0,0 +1,302 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// qbsp.c - emit bsp nodes and leafs +//======================================================================= + +#include "bsplib.h" +#include "const.h" + +#define BLOCK_SIZE 8192 // test + +int block_xl = -8, block_xh = 7, block_yl = -8, block_yh = 7; +int entity_num; +node_t *block_nodes[10][10]; + +/* +============ +BlockTree + +============ +*/ +node_t *BlockTree (int xl, int yl, int xh, int yh) +{ + node_t *node; + vec3_t normal; + float dist; + int mid; + + if (xl == xh && yl == yh) + { + node = block_nodes[xl+5][yl+5]; + if (!node) + { // return an empty leaf + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = 0; //CONTENTS_SOLID; + return node; + } + return node; + } + + // create a seperator along the largest axis + node = AllocNode (); + + if (xh - xl > yh - yl) + { // split x axis + mid = xl + (xh-xl)/2 + 1; + normal[0] = 1; + normal[1] = 0; + normal[2] = 0; + dist = mid*BLOCK_SIZE; + node->planenum = FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( mid, yl, xh, yh); + node->children[1] = BlockTree ( xl, yl, mid-1, yh); + } + else + { + mid = yl + (yh-yl)/2 + 1; + normal[0] = 0; + normal[1] = 1; + normal[2] = 0; + dist = mid*BLOCK_SIZE; + node->planenum = FindFloatPlane (normal, dist); + node->children[0] = BlockTree ( xl, mid, xh, yh); + node->children[1] = BlockTree ( xl, yl, xh, mid-1); + } + + return node; +} + +/* +============ +ProcessBlock_Thread + +============ +*/ +int brush_start, brush_end; + +void ProcessBlock_Thread (int blocknum) +{ + int xblock, yblock; + vec3_t mins, maxs; + bspbrush_t *brushes; + tree_t *tree; + node_t *node; + + yblock = block_yl + blocknum / (block_xh-block_xl+1); + xblock = block_xl + blocknum % (block_xh-block_xl+1); + + mins[0] = xblock*BLOCK_SIZE; + mins[1] = yblock*BLOCK_SIZE; + mins[2] = MIN_WORLD_COORD; + maxs[0] = (xblock+1)*BLOCK_SIZE; + maxs[1] = (yblock+1)*BLOCK_SIZE; + maxs[2] = MAX_WORLD_COORD; + + // the makelist and chopbrushes could be cached between the passes... + brushes = MakeBspBrushList (brush_start, brush_end, mins, maxs); + if (!brushes) + { + node = AllocNode (); + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + block_nodes[xblock+5][yblock+5] = node; + return; + } + +// brushes = ChopBrushes (brushes); + tree = BrushBSP (brushes, mins, maxs); + + block_nodes[xblock+5][yblock+5] = tree->headnode; + + Mem_Free( tree ); +} + +/* +============ +ProcessWorldModel + +============ +*/ +void ProcessWorldModel( void ) +{ + bsp_entity_t *e; + tree_t *tree; + bool leaked; + bool optimize; + + e = &entities[entity_num]; + + brush_start = e->firstbrush; + brush_end = brush_start + e->numbrushes; + leaked = false; + + // + // perform per-block operations + // + if (block_xh * BLOCK_SIZE > map_maxs[0]) + block_xh = floor(map_maxs[0]/BLOCK_SIZE); + if ( (block_xl+1) * BLOCK_SIZE < map_mins[0]) + block_xl = floor(map_mins[0]/BLOCK_SIZE); + if (block_yh * BLOCK_SIZE > map_maxs[1]) + block_yh = floor(map_maxs[1]/BLOCK_SIZE); + if ( (block_yl+1) * BLOCK_SIZE < map_mins[1]) + block_yl = floor(map_mins[1]/BLOCK_SIZE); + + if (block_xl <-4) block_xl = -4; + if (block_yl <-4) block_yl = -4; + if (block_xh > 3) block_xh = 3; + if (block_yh > 3) block_yh = 3; + + for (optimize = false; optimize <= true; optimize++) + { + RunThreadsOnIndividual ((block_xh-block_xl+1)*(block_yh-block_yl+1), true, ProcessBlock_Thread); + + // build the division tree + // oversizing the blocks guarantees that all the boundaries + // will also get nodes. + + tree = AllocTree (); + tree->headnode = BlockTree (block_xl-1, block_yl-1, block_xh+1, block_yh+1); + + tree->mins[0] = (block_xl)*BLOCK_SIZE; + tree->mins[1] = (block_yl)*BLOCK_SIZE; + tree->mins[2] = map_mins[2] - 8; + + tree->maxs[0] = (block_xh+1)*BLOCK_SIZE; + tree->maxs[1] = (block_yh+1)*BLOCK_SIZE; + tree->maxs[2] = map_maxs[2] + 8; + + // + // perform the global operations + // + MakeTreePortals (tree); + + if (FloodEntities (tree)) + { + FillOutside (tree->headnode); + } + else + { + Msg("**** leaked ****\n"); + leaked = true; + LeakFile (tree); + } + + MarkVisibleSides (tree, brush_start, brush_end); + if (leaked) break; + if (!optimize) + { + FreeTree (tree); + } + } + + FloodAreas(tree); + MakeFaces(tree->headnode); + FixTjuncs(tree->headnode); + PruneNodes(tree->headnode); + + WriteBSP(tree->headnode); + + if(!leaked) WritePortalFile( tree ); + + FreeTree( tree ); +} + +/* +============ +ProcessSubModel + +============ +*/ +void ProcessSubModel (void) +{ + bsp_entity_t *e; + int start, end; + tree_t *tree; + bspbrush_t *list; + vec3_t mins, maxs; + + e = &entities[entity_num]; + + start = e->firstbrush; + end = start + e->numbrushes; + + mins[0] = mins[1] = mins[2] = MIN_WORLD_COORD; + maxs[0] = maxs[1] = maxs[2] = MAX_WORLD_COORD; + list = MakeBspBrushList (start, end, mins, maxs); + list = ChopBrushes (list); + tree = BrushBSP (list, mins, maxs); + MakeTreePortals (tree); + MarkVisibleSides (tree, start, end); + MakeFaces (tree->headnode); + FixTjuncs (tree->headnode); + WriteBSP (tree->headnode); + FreeTree (tree); +} + +/* +============ +ProcessModels +============ +*/ +void ProcessModels (void) +{ + BeginBSPFile (); + + for (entity_num = 0; entity_num < num_entities; entity_num++) + { + if (!entities[entity_num].numbrushes) + continue; + + BeginModel (); + if (entity_num == 0) ProcessWorldModel (); + else ProcessSubModel (); + EndModel (); + } + + EndBSPFile (); +} + +/* +============ +WbspMain +============ +*/ +void WbspMain( void ) +{ + Msg( "\n---- bsp ---- [%s]\n", (bsp_parms & BSPLIB_ONLYENTS) ? "onlyents" : "normal" ); + + if(!( bsp_parms & BSPLIB_ONLYENTS )) + { + // delete portal and line files + com.sprintf( path, "%s/maps/%s.prt", com.GameInfo->gamedir, gs_filename ); + FS_Delete( path ); + com.sprintf( path, "%s/maps/%s.lin", com.GameInfo->gamedir, gs_filename ); + FS_Delete( path ); + } + + // if onlyents, just grab the entites and resave + if( bsp_parms & BSPLIB_ONLYENTS ) + { + LoadBSPFile(); + num_entities = 0; + + LoadMapFile(); + SetModelNumbers(); + SetLightStyles(); + UnparseEntities(); + WriteBSPFile(); + } + else + { + // start from scratch + LoadMapFile(); + SetModelNumbers(); + SetLightStyles(); + ProcessModels(); + LoadBSPFile(); + ProcessCollisionTree(); + WriteBSPFile(); + } +} \ No newline at end of file diff --git a/xtools/bsplib/qrad3.c b/xtools/bsplib/qrad3.c new file mode 100644 index 00000000..db8f0fdc --- /dev/null +++ b/xtools/bsplib/qrad3.c @@ -0,0 +1,429 @@ +// qrad.c + +#include "bsplib.h" +#include "const.h" + +/* +NOTES +----- +every surface must be divided into at least two patches each axis +*/ + +patch_t *face_patches[MAX_MAP_SURFACES]; +bsp_entity_t *face_entity[MAX_MAP_SURFACES]; +patch_t patches[MAX_PATCHES]; +uint num_patches; +vec3_t radiosity[MAX_PATCHES]; // light leaving a patch +vec3_t illumination[MAX_PATCHES]; // light arriving at a patch +vec3_t face_offset[MAX_MAP_SURFACES]; // for rotating bmodels +dplane_t backplanes[MAX_MAP_PLANES]; + +int fakeplanes; // created planes for origin offset +float subdiv = 64; // ??? + + +void BuildLightmaps (void); +int TestLine (vec3_t start, vec3_t stop); + +int junk; + +int numbounce = 0; +float ambient = 0; +float maxlight = 196; +float lightscale = 1.0f; +float direct_scale = 0.4f; +float entity_scale = 1.0f; + +/* +=================================================================== + +MISC + +=================================================================== +*/ + + +/* +============= +MakeBackplanes +============= +*/ +void MakeBackplanes (void) +{ + int i; + + for (i=0 ; ichildren[i]; + if (j < 0) leafparents[-j - 1] = nodenum; + else MakeParents( j, nodenum ); + } +} + + +/* +=================================================================== + +TRANSFER SCALES + +=================================================================== +*/ +/* +============= +MakeTransfers + +============= +*/ +int total_transfer; + +void MakeTransfers (int i) +{ + int j; + vec3_t delta; + vec_t dist, scale; + float trans; + int itrans; + patch_t *patch, *patch2; + float total; + dplane_t plane; + vec3_t origin; + float transfers[MAX_PATCHES], *all_transfers; + int s; + int itotal; + byte pvs[(MAX_MAP_LEAFS+7)/8]; + int cluster; + + patch = patches + i; + total = 0; + + VectorCopy (patch->origin, origin); + plane = *patch->plane; + + if (!PvsForOrigin (patch->origin, pvs)) + return; + + // find out which patch2s will collect light + // from patch + + all_transfers = transfers; + patch->numtransfers = 0; + for (j=0, patch2 = patches ; jcluster; + if (cluster == -1) continue; + if (!( pvs[cluster>>3] & (1<<(cluster&7)) ) ) + continue; // not in pvs + + // calculate vector + VectorSubtract (patch2->origin, origin, delta); + dist = VectorNormalizeLength( delta ); + if( !dist ) continue; // should never happen + + // reletive angles + scale = DotProduct (delta, plane.normal); + scale *= -DotProduct (delta, patch2->plane->normal); + if (scale <= 0) + continue; + + // check exact transfer + if( TestLine_r( 0, patch->origin, patch2->origin ) & CONTENTS_SOLID ) + continue; + + trans = scale * patch2->area / (dist*dist); + + if (trans < 0) + trans = 0; // rounding errors... + + transfers[j] = trans; + if (trans > 0) + { + total += trans; + patch->numtransfers++; + } + } + + // copy the transfers out and normalize + // total should be somewhere near PI if everything went right + // because partial occlusion isn't accounted for, and nearby + // patches have underestimated form factors, it will usually + // be higher than PI + if (patch->numtransfers) + { + transfer_t *t; + + if (patch->numtransfers < 0 || patch->numtransfers > MAX_PATCHES) + Sys_Error ("Weird numtransfers"); + s = patch->numtransfers * sizeof(transfer_t); + patch->transfers = Malloc (s); + if (!patch->transfers) + Sys_Error ("Memory allocation failure"); + + // + // normalize all transfers so all of the light + // is transfered to the surroundings + // + t = patch->transfers; + itotal = 0; + for (j=0 ; jtransfer = itrans; + t->patch = j; + t++; + } + } + + // don't bother locking around this. not that important. + total_transfer += patch->numtransfers; +} + + +/* +============= +FreeTransfers +============= +*/ +void FreeTransfers (void) +{ + int i; + + for (i = 0; i < num_patches; i++) + { + if(patches[i].transfers) + Mem_Free (patches[i].transfers); + patches[i].transfers = NULL; + } +} + +//============================================================== + +/* +============= +CollectLight +============= +*/ +float CollectLight (void) +{ + int i, j; + patch_t *patch; + vec_t total; + + total = 0; + + for (i=0, patch=patches ; isky) + { + VectorClear (radiosity[i]); + VectorClear (illumination[i]); + continue; + } + + for (j=0 ; j<3 ; j++) + { + patch->totallight[j] += illumination[i][j] / patch->area; + radiosity[i][j] = illumination[i][j] * patch->reflectivity[j]; + } + + total += radiosity[i][0] + radiosity[i][1] + radiosity[i][2]; + VectorClear (illumination[i]); + } + + return total; +} + + +/* +============= +ShootLight + +Send light out to other patches + Run multi-threaded +============= +*/ +void ShootLight (int patchnum) +{ + int k, l; + transfer_t *trans; + int num; + patch_t *patch; + vec3_t send; + + // this is the amount of light we are distributing + // prescale it so that multiplying by the 16 bit + // transfer values gives a proper output value + for (k=0 ; k<3 ; k++) + send[k] = radiosity[patchnum][k] / 0x10000; + patch = &patches[patchnum]; + + trans = patch->transfers; + num = patch->numtransfers; + + for (k=0 ; kpatch][l] += send[l]*trans->transfer; + } +} + +/* +============= +BounceLight +============= +*/ +void BounceLight (void) +{ + int i, j; + float added; + patch_t *p; + + for (i=0 ; isamplelight[j] * p->reflectivity[j] * p->area; + } + } + + for (i = 0; i < numbounce; i++) + { + RunThreadsOnIndividual (num_patches, false, ShootLight); + added = CollectLight (); + } +} + + + +//============================================================== + +void CheckPatches (void) +{ + int i; + patch_t *patch; + + for (i=0 ; itotallight[0] < 0 || patch->totallight[1] < 0 || patch->totallight[2] < 0) + Sys_Error ("negative patch totallight\n"); + } +} + +/* +============= +RadWorld +============= +*/ +void RadWorld (void) +{ + if( numnodes == 0 || numsurfaces == 0 ) + Sys_Break( "Empty map %s.bsp\n", gs_filename ); + MakeBackplanes (); + MakeParents (0, -1); + MakeTnodes (&dmodels[0]); + + // turn each face into a single patch + MakePatches (); + + // subdivide patches to a maximum dimension + SubdividePatches (); + + // create directlights out of patches and lights + CreateDirectLights (); + + // build initial facelights + RunThreadsOnIndividual( numsurfaces, true, BuildFacelights ); + + if (numbounce > 0) + { + // build transfer lists + RunThreadsOnIndividual (num_patches, true, MakeTransfers); + Msg("Make transfer lists: %5.1f megs\n", (float)total_transfer * sizeof(transfer_t) / (1024*1024)); + + // spread light around + BounceLight(); + FreeTransfers(); + CheckPatches(); + } + + // blend bounced light into direct light and save + PairEdges (); + LinkPlaneFaces (); + + lightdatasize = 0; + RunThreadsOnIndividual( numsurfaces, true, FinalLightFace ); +} + +void WradMain( void ) +{ + string cmdparm; + + if(!LoadBSPFile( )) + { + // map not exist, create it + WbspMain(); + LoadBSPFile(); + } + + if( bsp_parms & BSPLIB_MAKEHLRAD ) + { + Msg( "\n---- hlrad ---- [%s]\n", (bsp_parms & BSPLIB_FULLCOMPILE) ? "extra" : "normal" ); + direct_scale = 1.4f; // extrapolated light is too dim + } + else Msg( "\n---- qrad ---- [%s]\n", (bsp_parms & BSPLIB_FULLCOMPILE) ? "extra" : "normal" ); + + if( FS_GetParmFromCmdLine( "-ambient", cmdparm )) + ambient = com.atof( cmdparm ); + ambient = bound( 0, ambient, 512 ); + + if( FS_GetParmFromCmdLine( "-bounce", cmdparm )) + numbounce = com.atoi( cmdparm ); + numbounce = bound( 0, numbounce, 32 ); + + ParseEntities(); + CalcTextureReflectivity(); + + if( !visdatasize ) + { + Msg( "No vis information, direct lighting only.\n" ); + numbounce = 0; + ambient = 0.1f; + } + + RadWorld(); + WriteBSPFile(); +} diff --git a/xtools/bsplib/qvis3.c b/xtools/bsplib/qvis3.c new file mode 100644 index 00000000..d8013adc --- /dev/null +++ b/xtools/bsplib/qvis3.c @@ -0,0 +1,617 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// vis.c - map visibility +//======================================================================= + +#include "bsplib.h" + +int numportals; +int portalclusters; +visportal_t *portals; +leaf_t *leafs; +int c_portaltest, c_portalpass, c_portalcheck; +byte *uncompressedvis; +byte *vismap, *vismap_p, *vismap_end; // past visfile +int originalvismapsize; +int leafbytes; // (portalclusters+63)>>3 +int leaflongs; +int portalbytes, portallongs; +int totalvis; +int totalphs; +visportal_t *sorted_portals[MAX_MAP_PORTALS*2]; +float maxdist; + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis( byte *vis, byte *dest ) +{ + int j, rep, visrow; + byte *dest_p; + + dest_p = dest; + visrow = (dvis->numclusters + 7)>>3; + + for( j = 0; j < visrow; j++ ) + { + *dest_p++ = vis[j]; + if( vis[j] ) continue; + + rep = 1; + for( j++; j < visrow; j++ ) + if( vis[j] || rep == 255 ) + break; + else rep++; + *dest_p++ = rep; + j--; + } + return dest_p - dest; +} + + +/* +=================== +DecompressVis +=================== +*/ +void DecompressVis( byte *in, byte *decompressed ) +{ + int c; + byte *out; + int row; + + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if( *in ) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if( !c ) Sys_Error( "DecompressVis: 0 repeat\n" ); + in += 2; + while( c ) + { + *out++ = 0; + c--; + } + } while( out - decompressed < row ); +} + +int PointInLeafnum ( vec3_t point ) +{ + float dist; + dnode_t *node; + dplane_t *plane; + int nodenum = 0; + + while( nodenum >= 0 ) + { + node = &dnodes[nodenum]; + plane = &dplanes[node->planenum]; + dist = DotProduct( point, plane->normal ) - plane->dist; + if( dist > 0 ) nodenum = node->children[0]; + else nodenum = node->children[1]; + } + return -nodenum - 1; +} + +dleaf_t *PointInLeaf( vec3_t point ) +{ + int num; + + num = PointInLeafnum( point ); + return &dleafs[num]; +} + +bool PvsForOrigin( vec3_t org, byte *pvs ) +{ + dleaf_t *leaf; + + if( !visdatasize ) + { + Mem_Set( pvs, 0xFF, (numleafs+7)/8 ); + return true; + } + + leaf = PointInLeaf( org ); + if( leaf->cluster == -1 ) + return false; // in solid leaf + + DecompressVis( dvisdata + dvis->bitofs[leaf->cluster][DVIS_PVS], pvs ); + return true; +} + +byte *PhsForCluster( int cluster ) +{ + static byte phs[(MAX_MAP_LEAFS+7)/8]; + + Mem_Set( phs, 0x00, (numleafs+7)/8 ); + DecompressVis( dvisdata + dvis->bitofs[cluster][DVIS_PHS], phs ); + + return phs; +} + +//============================================================================= + +void PlaneFromWinding( viswinding_t *w, visplane_t *plane ) +{ + vec3_t v1, v2; + + // calc plane + VectorSubtract (w->points[2], w->points[1], v1); + VectorSubtract (w->points[0], w->points[1], v2); + CrossProduct (v2, v1, plane->normal); + VectorNormalize (plane->normal); + plane->dist = DotProduct (w->points[0], plane->normal); +} + +/* +================== +NewVisWinding +================== +*/ +viswinding_t *NewVisWinding( int points ) +{ + viswinding_t *w; + int size; + + if( points > MAX_POINTS_ON_WINDING ) + Sys_Error ("NewVisWinding: %i points", points); + + size = (int)((viswinding_t *)0)->points[points]; + w = malloc( size ); + memset( w, 0, size ); + + return w; +} + + + +static void pw(viswinding_t *w) +{ + int i; + for (i=0 ; inumpoints ; i++) + Msg("(%5.1f, %5.1f, %5.1f)\n",w->points[i][0], w->points[i][1],w->points[i][2]); +} + +static void prl(leaf_t *l) +{ + int i; + visportal_t *p; + visplane_t pl; + + for (i=0 ; inumportals ; i++) + { + p = l->portals[i]; + pl = p->plane; + Msg ("portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n",(int)(p-portals),p->leaf,pl.dist, pl.normal[0], pl.normal[1], pl.normal[2]); + } +} + + +//============================================================================= + +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +int PComp (const void *a, const void *b) +{ + if ( (*(visportal_t **)a)->nummightsee == (*(visportal_t **)b)->nummightsee) + return 0; + if ( (*(visportal_t **)a)->nummightsee < (*(visportal_t **)b)->nummightsee) + return -1; + return 1; +} +void SortPortals (void) +{ + int i; + + for (i=0 ; i>3] & (1<<(i&7)) ) + { + p = portals+i; + leafbits[p->leaf>>3] |= (1<<(p->leaf&7)); + } + } + + c_leafs = CountBits (leafbits, portalclusters); + + return c_leafs; +} + + +/* +=============== +ClusterMerge + +Merges the portal visibility for a leaf +=============== +*/ +void ClusterMerge (int leafnum) +{ + leaf_t *leaf; + byte portalvector[MAX_PORTALS/8]; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + int i, j; + int numvis; + byte *dest; + visportal_t *p; + int pnum; + + // OR together all the portalvis bits + + memset (portalvector, 0, portalbytes); + leaf = &leafs[leafnum]; + for (i = 0; i < leaf->numportals; i++) + { + p = leaf->portals[i]; + if(p->status != stat_done) + Sys_Error ("portal not done"); + for(j = 0; j < portallongs; j++) + ((long *)portalvector)[j] |= ((long *)p->portalvis)[j]; + pnum = p - portals; + portalvector[pnum>>3] |= 1<<(pnum&7); + } + + // convert portal bits to leaf bits + numvis = LeafVectorFromPortalVector(portalvector, uncompressed); + + if (uncompressed[leafnum>>3] & (1<<(leafnum&7))) + MsgDev( D_WARN, "Leaf portals saw into leaf\n"); + + uncompressed[leafnum>>3] |= (1<<(leafnum&7)); + numvis++; // count the leaf itself + + // save uncompressed for PHS calculation + Mem_Copy(uncompressedvis + leafnum*leafbytes, uncompressed, leafbytes); + + // compress the bit string + totalvis += numvis; + + i = CompressVis (uncompressed, compressed); + + dest = vismap_p; + vismap_p += i; + + if (vismap_p > vismap_end) + Sys_Error ("Vismap expansion overflow"); + + dvis->bitofs[leafnum][DVIS_PVS] = dest - vismap; + Mem_Copy(dest, compressed, i); +} + + +/* +================== +CalcPortalVis +================== +*/ +void CalcPortalVis( void ) +{ + if( bsp_parms & BSPLIB_FULLCOMPILE ) + { + RunThreadsOnIndividual( numportals * 2, true, PortalFlow ); + } + else + { + int i; + + // fastvis just uses mightsee for a very loose bound + for( i = 0; i < numportals * 2; i++ ) + { + portals[i].portalvis = portals[i].portalflood; + portals[i].status = stat_done; + } + } +} + + +/* +================== +CalcPVS +================== +*/ +void CalcPVS( void ) +{ + int i; + + Msg( "Building PVS...\n" ); + + RunThreadsOnIndividual( numportals * 2, true, BasePortalVis ); + SortPortals(); + CalcPortalVis(); + + // assemble the leaf vis lists by oring and compressing the portal lists + for( i = 0; i < portalclusters; i++ ) + ClusterMerge (i); + + Msg( "Average clusters visible: %i\n", totalvis / portalclusters ); +} + + +void SetPortalSphere (visportal_t *p) +{ + int i; + vec3_t total, dist; + viswinding_t *w; + float r, bestr; + + w = p->winding; + VectorCopy (vec3_origin, total); + for (i=0 ; inumpoints ; i++) + { + VectorAdd (total, w->points[i], total); + } + + for (i=0 ; i<3 ; i++) + total[i] /= w->numpoints; + + bestr = 0; + for (i=0 ; inumpoints ; i++) + { + VectorSubtract (w->points[i], total, dist); + r = VectorLength (dist); + if (r > bestr) + bestr = r; + } + VectorCopy (total, p->origin); + p->radius = bestr; +} + +/* +============ +LoadPortals +============ +*/ +void LoadPortals( void ) +{ + visportal_t *p; + leaf_t *l; + char magic[80]; + char path[MAX_SYSPATH]; + int numpoints; + script_t *prtfile; + viswinding_t *w; + int i, j, leafnums[2]; + visplane_t plane; + + com.sprintf( path, "maps/%s.prt", gs_filename ); + prtfile = Com_OpenScript( path, NULL, 0 ); + if( !prtfile ) Sys_Break( "LoadPortals: couldn't read %s\n", path ); + MsgDev( D_NOTE, "reading %s\n", path ); + + Com_ReadString( prtfile, true, magic ); + Com_ReadLong( prtfile, true, &portalclusters ); + Com_ReadLong( prtfile, true, &numportals ); + + if( !portalclusters && !numportals ) Sys_Break( "LoadPortals: failed to read header\n" ); + if( com.strcmp( magic, PORTALFILE )) Sys_Break( "LoadPortals: not a portal file\n" ); + + Msg( "%4i portalclusters\n", portalclusters ); + Msg( "%4i numportals\n", numportals ); + + // these counts should take advantage of 64 bit systems automatically + leafbytes = ((portalclusters+63)&~63)>>3; + leaflongs = leafbytes/sizeof(long); + + portalbytes = ((numportals*2+63)&~63)>>3; + portallongs = portalbytes/sizeof(long); + + // each file portal is split into two memory portals + portals = Malloc(2 * numportals * sizeof(visportal_t)); + + leafs = Malloc(portalclusters*sizeof(leaf_t)); + + originalvismapsize = portalclusters*leafbytes; + uncompressedvis = Malloc(originalvismapsize); + + vismap = vismap_p = dvisdata; + dvis->numclusters = portalclusters; + vismap_p = (byte *)&dvis->bitofs[portalclusters]; + vismap_end = vismap + MAX_MAP_VISIBILITY; + + for (i = 0, p = portals; i < numportals; i++) + { + Com_ReadLong( prtfile, true, &numpoints ); + Com_ReadLong( prtfile, false, &leafnums[0] ); + Com_ReadLong( prtfile, false, &leafnums[1] ); + + if( numpoints > MAX_POINTS_ON_WINDING ) + Sys_Break( "LoadPortals: portal %i has too many points\n", i ); + if(( uint )leafnums[0] > portalclusters || (uint)leafnums[1] > portalclusters ) + Sys_Break( "LoadPortals: reading portal %i\n", i ); + + w = p->winding = NewVisWinding( numpoints ); + w->original = true; + w->numpoints = numpoints; + + for( j = 0; j < numpoints; j++ ) + { + token_t token; + vec3_t v; + int k; + + // scanf into double, then assign to float + // so we don't care what size float is + Com_ReadToken( prtfile, 0, &token ); // get '(' symbol + if( com.stricmp( token.string, "(" )) + Sys_Break( "LoadPortals: not found ( reading portal %i\n", i ); + + Com_ReadFloat( prtfile, false, &v[0] ); + Com_ReadFloat( prtfile, false, &v[1] ); + Com_ReadFloat( prtfile, false, &v[2] ); + + Com_ReadToken( prtfile, 0, &token ); + if( com.stricmp( token.string, ")" )) + Sys_Error( "LoadPortals: not found ) reading portal %i", i ); + + for (k = 0; k < 3; k++) w->points[j][k] = v[k]; + } + + // calc plane + PlaneFromWinding (w, &plane); + + // create forward portal + l = &leafs[leafnums[0]]; + if (l->numportals == MAX_PORTALS_ON_LEAF) Sys_Error ("Leaf with too many portals"); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = w; + VectorSubtract (vec3_origin, plane.normal, p->plane.normal); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + SetPortalSphere (p); + p++; + + // create backwards portal + l = &leafs[leafnums[1]]; + if (l->numportals == MAX_PORTALS_ON_LEAF) Sys_Error ("Leaf with too many portals"); + l->portals[l->numportals] = p; + l->numportals++; + + p->winding = NewVisWinding(w->numpoints); + p->winding->numpoints = w->numpoints; + for (j=0 ; jnumpoints ; j++) + { + VectorCopy (w->points[w->numpoints-1-j], p->winding->points[j]); + } + + p->plane = plane; + p->leaf = leafnums[0]; + SetPortalSphere (p); + p++; + + } + Com_CloseScript( prtfile ); +} + +void ClusterPHS( int clusternum ) +{ + int j, k, l, index; + int bitbyte; + long *dest, *src; + byte *scan; + byte uncompressed[MAX_MAP_LEAFS/8]; + byte compressed[MAX_MAP_LEAFS/8]; + + scan = uncompressedvis + clusternum * leafbytes; + Mem_Copy( uncompressed, scan, leafbytes ); + for (j = 0; j < leafbytes; j++) + { + bitbyte = scan[j]; + if (!bitbyte) continue; + for (k = 0; k < 8; k++) + { + if (! (bitbyte & (1<= portalclusters) + Sys_Error ("Bad bit in PVS"); // pad bits should be 0 + src = (long *)(uncompressedvis + index*leafbytes); + dest = (long *)uncompressed; + for (l = 0; l < leaflongs; l++) ((long *)uncompressed)[l] |= src[l]; + } + } + for (j = 0; j < portalclusters; j++) + if (uncompressed[j>>3] & (1<<(j&7)) ) + totalphs++; + + // compress the bit string + j = CompressVis( uncompressed, compressed ); + + dest = (long *)vismap_p; + vismap_p += j; + + if( vismap_p > vismap_end ) + Sys_Error( "Vismap expansion overflow\n" ); + + dvis->bitofs[clusternum][DVIS_PHS] = (byte *)dest - vismap; + Mem_Copy( dest, compressed, j ); +} + +/* +================ +CalcPHS + +Calculate the PHS (Potentially Hearable Set) +by ORing together all the PVS visible from a leaf +================ +*/ +void CalcPHS( void ) +{ + Msg( "Building PHS...\n" ); + RunThreadsOnIndividual( portalclusters, true, ClusterPHS ); + Msg( "Average clusters hearable: %i\n", totalphs / portalclusters ); +} + +/* +=========== +main +=========== +*/ +void WvisMain( void ) +{ + if( !LoadBSPFile( )) + { + // map not exist, create it + WbspMain(); + LoadBSPFile(); + } + if( numnodes == 0 || numsurfaces == 0 ) + Sys_Break( "Empty map %s.bsp\n", gs_filename ); + + if( bsp_parms & BSPLIB_MAKEVIS ) + { + Msg( "\n---- vis ---- [%s]\n", (bsp_parms & BSPLIB_FULLCOMPILE) ? "full" : "fast" ); + + LoadPortals(); + CalcPVS(); + CalcPHS(); + + visdatasize = vismap_p - dvisdata; + MsgDev( D_INFO, "visdatasize:%i compressed from %i\n", visdatasize, originalvismapsize * 2 ); + } + + if( bsp_parms & BSPLIB_MAKESOUND ) + { + CalcAmbientSounds(); + } + WriteBSPFile(); +} \ No newline at end of file diff --git a/xtools/bsplib/shaders.c b/xtools/bsplib/shaders.c new file mode 100644 index 00000000..2a77ea2b --- /dev/null +++ b/xtools/bsplib/shaders.c @@ -0,0 +1,214 @@ +#include "bsplib.h" +#include "const.h" + +#define MAX_SHADER_FILES 64 +#define MAX_SURFACE_INFO 4096 +int numShaderInfo = 0; + +bsp_shader_t shaderInfo[MAX_SURFACE_INFO]; + +typedef struct +{ + const char *name; + int surfaceFlags; + int contents; + bool clearSolid; +} infoParm_t; + +// NOTE: this table must match with same table in render\r_shader.c +infoParm_t infoParms[] = +{ + // server relevant contents + {"window", SURF_NONE, CONTENTS_WINDOW, 0}, + {"aux", SURF_NONE, CONTENTS_AUX, 0}, + {"warp", SURF_WARP, CONTENTS_NONE, 0}, + {"water", SURF_NONE, CONTENTS_WATER, 1}, + {"slime", SURF_NONE, CONTENTS_SLIME, 1}, // mildly damaging + {"lava", SURF_NONE, CONTENTS_LAVA, 1}, // very damaging + {"playerclip", SURF_NODRAW, CONTENTS_PLAYERCLIP, 1}, + {"monsterclip", SURF_NODRAW, CONTENTS_MONSTERCLIP, 1}, + {"clip", SURF_NODRAW, CONTENTS_CLIP, 1}, + {"notsolid", SURF_NONE, CONTENTS_NONE, 1}, // just clear solid flag + {"trigger", SURF_NODRAW, CONTENTS_TRIGGER, 1}, // trigger volume + + // utility relevant attributes + {"origin", SURF_NODRAW, CONTENTS_ORIGIN, 1}, // center of rotating brushes + {"nolightmap", SURF_NOLIGHTMAP, CONTENTS_NONE, 0}, // don't generate a lightmap + {"translucent", SURF_NONE, CONTENTS_TRANSLUCENT, 0}, // don't eat contained surfaces + {"detail", SURF_NONE, CONTENTS_DETAIL, 0}, // don't include in structural bsp + {"fog", SURF_NOLIGHTMAP, CONTENTS_FOG, 0}, // carves surfaces entering + {"sky", SURF_SKY, CONTENTS_SKY, 0}, // emit light from environment map + {"3dsky", SURF_3DSKY, CONTENTS_SKY, 0}, // emit light from environment map + {"hint", SURF_HINT, CONTENTS_NONE, 0}, // use as a primary splitter + {"skip", SURF_SKIP, CONTENTS_NONE, 0}, // use as a secondary splitter + {"null", SURF_NODRAW, CONTENTS_NONE, 0}, // nodraw texture + {"mirror", SURF_MIRROR, CONTENTS_NONE, 0}, + + // server attributes + {"slick", SURF_SLICK, CONTENTS_NONE, 0}, + {"light", SURF_LIGHT, CONTENTS_NONE, 0}, + {"ladder", SURF_NONE, CONTENTS_LADDER, 0}, +}; + +/* +=============== +FindShader +=============== +*/ +bsp_shader_t *FindShader( const char *texture ) +{ + bsp_shader_t *texshader; + string shader; + int i; + + // convert to lower case + com.strlwr( texture, shader ); + + // look for it + for( i = 0, texshader = shaderInfo; i < numShaderInfo; i++, texshader++) + { + if(!com.stricmp( shader, texshader->name )) + return texshader; + } + return NULL; //no shaders for this texture +} + +/* +=============== +AllocShaderInfo +=============== +*/ +static bsp_shader_t *AllocShaderInfo( void ) +{ + bsp_shader_t *si; + + if ( numShaderInfo == MAX_SURFACE_INFO ) + Sys_Error( "MAX_SURFACE_INFO limit exceeded\n" ); + + si = &shaderInfo[numShaderInfo]; + numShaderInfo++; + + // set defaults + si->contents = CONTENTS_SOLID; + si->color[0] = 0; + si->color[1] = 0; + si->color[2] = 0; + si->surfaceFlags = 0; + si->intensity = 0; + + return si; +} + +/* +=============== +ParseShaderFile +=============== +*/ +static void ParseShaderFile( const char *filename ) +{ + int i, numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]); + script_t *shader; + token_t token; + string name; + bsp_shader_t *si; + + shader = Com_OpenScript( filename, NULL, 0 ); + + if( shader ) + { + FS_FileBase( filename, name ); + MsgDev( D_LOAD, "Adding: %s.shader\n", name ); + } + + while( shader ) + { + if( !Com_ReadToken( shader, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + break; + + si = AllocShaderInfo(); + com.strncpy( si->name, token.string, sizeof( si->name )); + Com_ReadToken( shader, SC_ALLOW_NEWLINES, &token ); + + if( com.strcmp( token.string, "{" )) + { + Msg( "ParseShaderFile: shader %s without opening brace!\n", si->name ); + continue; + } + + while ( 1 ) + { + if ( !Com_ReadToken( shader, SC_ALLOW_NEWLINES, &token )) break; + if ( !com.strcmp( token.string, "}" )) break; + + // skip internal braced sections + if ( !com.strcmp( token.string, "{" )) + { + si->hasPasses = true; + Com_SkipBracedSection( shader, 1 ); + continue; + } + + if( !com.stricmp( token.string, "surfaceParm" )) + { + Com_ReadToken( shader, 0, &token ); + for( i = 0; i < numInfoParms; i++ ) + { + if( !com.stricmp( token.string, infoParms[i].name )) + { + si->surfaceFlags |= infoParms[i].surfaceFlags; + si->contents |= infoParms[i].contents; + if( infoParms[i].clearSolid ) + si->contents &= ~CONTENTS_SOLID; + break; + } + } + continue; + } + + // light color + if( !com.stricmp( token.string, "rad_color" )) + { + Com_ReadFloat( shader, false, &si->color[0] ); + Com_ReadFloat( shader, false, &si->color[1] ); + Com_ReadFloat( shader, false, &si->color[2] ); + continue; + } + + // light intensity + if( !com.stricmp( token.string, "rad_intensity" )) + { + Com_ReadLong( shader, false, &si->intensity ); + continue; + } + + // ignore all other com_tokens on the line + Com_SkipRestOfLine( shader ); + } + } + if( shader ) Com_CloseScript( shader ); +} + + +/* +=============== +LoadShaderInfo +=============== +*/ +int LoadShaderInfo( void ) +{ + search_t *search; + int i, numShaderFiles; + + numShaderFiles = 0; + search = FS_Search( "scripts/*.shader", true ); + if( !search ) return 0; + + for( i = 0; i < search->numfilenames; i++ ) + { + ParseShaderFile( search->filenames[i] ); + numShaderFiles++; + } + Mem_Free( search ); + + return numShaderInfo; +} \ No newline at end of file diff --git a/xtools/bsplib/textures.c b/xtools/bsplib/textures.c new file mode 100644 index 00000000..87258659 --- /dev/null +++ b/xtools/bsplib/textures.c @@ -0,0 +1,214 @@ + +#include "bsplib.h" +#include "byteorder.h" + +//========================================================================== +int FindMiptex( const char *name ) +{ + rgbdata_t *image; + int i; + + if(!com.strlen( name )) return 0; + + for( i = 0; i < numshaders; i++ ) + { + if( !com.stricmp( dshaders[i].name, name )) + return i; + } + + // allocate a new texture + if( numshaders == MAX_MAP_SHADERS ) + Sys_Break( "MAX_MAP_SHADERS limit exceeded\n" ); + + // alloc new texture + com.strncpy( dshaders[i].name, name, sizeof( dshaders[i].name )); + + image = FS_LoadImage( name, NULL, 0 ); + if( image ) + { + // save texture dimensions that can be used + // for replacing original textures with HQ images + dshaders[i].size[0] = image->width; + dshaders[i].size[1] = image->height; + FS_FreeImage( image ); + } + else dshaders[i].size[0] = dshaders[i].size[1] = -1; // technically an error + + return numshaders++; +} + +/* +================== +textureAxisFromPlane +================== +*/ +vec3_t baseaxis[18] = +{ +{0,0,1}, {1,0,0}, {0,-1,0}, // floor +{0,0,-1}, {1,0,0}, {0,-1,0}, // ceiling +{1,0,0}, {0,1,0}, {0,0,-1}, // west wall +{-1,0,0}, {0,1,0}, {0,0,-1}, // east wall +{0,1,0}, {1,0,0}, {0,0,-1}, // south wall +{0,-1,0}, {1,0,0}, {0,0,-1}, // north wall +}; + +void TextureAxisFromPlane( plane_t *plane, vec3_t xv, vec3_t yv ) +{ + int bestaxis = 0; + vec_t dot, best = 0; + int i; + + for( i = 0; i < 6; i++ ) + { + dot = DotProduct( plane->normal, baseaxis[i*3] ); + if (dot > best) + { + best = dot; + bestaxis = i; + } + } + + VectorCopy( baseaxis[bestaxis*3+1], xv ); + VectorCopy( baseaxis[bestaxis*3+2], yv ); +} + +int TexinfoForBrushTexture( plane_t *plane, brush_texture_t *bt, vec3_t origin ) +{ + vec_t ang, sinv, cosv; + vec3_t vecs[2]; + int i, j, k; + dtexinfo_t tx, *tc; + int sv, tv; + vec_t ns, nt; + + if( !bt->name[0] ) return 0; + + Mem_Set( &tx, 0, sizeof( tx )); + tx.shadernum = FindMiptex( bt->name ); + + if( bt->brush_type == BRUSH_QUARK ) + { + Mem_Copy( tx.vecs, bt->vects.quark.vecs, sizeof( tx.vecs )); + + if(!VectorIsNull( origin )) + { + tx.vecs[0][3] += DotProduct( origin, tx.vecs[0] ); + tx.vecs[1][3] += DotProduct( origin, tx.vecs[1] ); + } + + dshaders[tx.shadernum].contentFlags = bt->contents; + dshaders[tx.shadernum].surfaceFlags = bt->flags; + tx.value = bt->value; + } + else if( bt->brush_type != BRUSH_RADIANT ) + { + if( bt->brush_type == BRUSH_WORLDCRAFT_21 ) + TextureAxisFromPlane( plane, vecs[0], vecs[1] ); + if( !bt->vects.hammer.scale[0] ) bt->vects.hammer.scale[0] = 1.0f; + if( !bt->vects.hammer.scale[1] ) bt->vects.hammer.scale[1] = 1.0f; + + if( bt->brush_type == BRUSH_WORLDCRAFT_21 ) + { + // rotate axis + if( bt->vects.hammer.rotate == 0 ) + { + sinv = 0; + cosv = 1; + } + else if( bt->vects.hammer.rotate == 90 ) + { + sinv = 1; + cosv = 0; + } + else if( bt->vects.hammer.rotate == 180 ) + { + sinv = 0; + cosv = -1; + } + else if( bt->vects.hammer.rotate == 270 ) + { + sinv = -1; + cosv = 0; + } + else + { + ang = bt->vects.hammer.rotate / 180 * M_PI; + sinv = com.sin( ang ); + cosv = com.cos( ang ); + } + if( vecs[0][0] ) sv = 0; + else if( vecs[0][1] ) sv = 1; + else sv = 2; + + if( vecs[1][0] ) tv = 0; + else if( vecs[1][1] ) tv = 1; + else tv = 2; + + for( i = 0; i < 2; i++ ) + { + ns = cosv * vecs[i][sv] - sinv * vecs[i][tv]; + nt = sinv * vecs[i][sv] + cosv * vecs[i][tv]; + vecs[i][sv] = ns; + vecs[i][tv] = nt; + } + + for( i = 0; i < 2; i++ ) + for( j = 0; j < 3; j++ ) + tx.vecs[i][j] = vecs[i][j] / bt->vects.hammer.scale[i]; + } + else if( bt->brush_type == BRUSH_WORLDCRAFT_22 ) + { + vec_t scale; + + scale = 1 / bt->vects.hammer.scale[0]; + VectorScale( bt->vects.hammer.UAxis, scale, tx.vecs[0] ); + scale = 1 / bt->vects.hammer.scale[1]; + VectorScale( bt->vects.hammer.VAxis, scale, tx.vecs[1] ); + } + + tx.vecs[0][3] = bt->vects.hammer.shift[0] + DotProduct( origin, tx.vecs[0] ); + tx.vecs[1][3] = bt->vects.hammer.shift[1] + DotProduct( origin, tx.vecs[1] ); + } + else if( bt->brush_type == BRUSH_RADIANT ) + { + VectorCopy( bt->vects.radiant.matrix[0], tx.vecs[0] ); + VectorCopy( bt->vects.radiant.matrix[1], tx.vecs[1] ); + + if(!VectorIsNull( origin )) + { + tx.vecs[0][3] += DotProduct( origin, tx.vecs[0] ); + tx.vecs[1][3] += DotProduct( origin, tx.vecs[1] ); + } + + dshaders[tx.shadernum].contentFlags = bt->contents; + dshaders[tx.shadernum].surfaceFlags = bt->flags; + tx.value = bt->value; + } + + dshaders[tx.shadernum].contentFlags = bt->contents; + dshaders[tx.shadernum].surfaceFlags = bt->flags; + tx.value = bt->value; + + // find the texinfo + tc = texinfo; + for( i = 0; i < numtexinfo; i++, tc++ ) + { + if( tc->value != tx.value ) continue; + for( j = 0; j < 2; j++ ) + { + if( tc->shadernum != tx.shadernum ) + goto skip; + for( k = 0; k < 4; k++ ) + { + if( tc->vecs[j][k] != tx.vecs[j][k] ) + goto skip; + } + } + return i; +skip:; + } + + // register new texinfo + *tc = tx; + return numtexinfo++; +} \ No newline at end of file diff --git a/xtools/bsplib/trace.c b/xtools/bsplib/trace.c new file mode 100644 index 00000000..4cd0ea48 --- /dev/null +++ b/xtools/bsplib/trace.c @@ -0,0 +1,284 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// rad_trace.c - ray casting +//======================================================================= + +#include "bsplib.h" +#include "const.h" + +tnode_t *tnodes, *tnode_p; + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode (int nodenum) +{ + tnode_t *t; + dplane_t *plane; + int i; + dnode_t *node; + + t = tnode_p++; + + node = dnodes + nodenum; + plane = dplanes + node->planenum; + + t->type = PlaneTypeForNormal( plane->normal ); + VectorCopy (plane->normal, t->normal); + t->dist = plane->dist; + + for (i=0 ; i<2 ; i++) + { + if( node->children[i] < 0 ) + { + t->children[i] = (-node->children[i] - 1)|(1<<31); + } + else + { + t->children[i] = tnode_p - tnodes; + MakeTnode (node->children[i]); + } + } + +} + + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +void MakeTnodes (dmodel_t *bm) +{ + // 32 byte align the structs + tnodes = Malloc( (numnodes+1) * sizeof(tnode_t)); + tnodes = (tnode_t *)(((int)tnodes + 31)&~31); + tnode_p = tnodes; + + MakeTnode (0); +} + + +//========================================================== + + +int TestLine_r (int node, vec3_t start, vec3_t stop) +{ + tnode_t *tnode; + float front, back; + vec3_t mid; + float frac; + int side; + int r; + + if (node & (1<<31)) + { + int leafnum = node & ~(1<<31); + int i, sky_found = 0; + dleafface_t *lface = &dleafsurfaces[dleafs[leafnum].firstleafsurface]; + + for( i = 0; i < dleafs[leafnum].numleafsurfaces; i++, lface++ ) + { + dsurface_t *face = &dsurfaces[*lface]; + if( dshaders[texinfo[face->texinfo].shadernum].contentFlags & CONTENTS_SKY ) + sky_found = true; + if( sky_found ) break; + } + + if( sky_found ) + return dleafs[leafnum].contents|CONTENTS_SKY; + return dleafs[leafnum].contents; + } + + tnode = &tnodes[node]; + switch (tnode->type) + { + case PLANE_X: + front = start[0] - tnode->dist; + back = stop[0] - tnode->dist; + break; + case PLANE_Y: + front = start[1] - tnode->dist; + back = stop[1] - tnode->dist; + break; + case PLANE_Z: + front = start[2] - tnode->dist; + back = stop[2] - tnode->dist; + break; + default: + front = (start[0]*tnode->normal[0] + start[1]*tnode->normal[1] + start[2]*tnode->normal[2]) - tnode->dist; + back = (stop[0]*tnode->normal[0] + stop[1]*tnode->normal[1] + stop[2]*tnode->normal[2]) - tnode->dist; + break; + } + + if (front >= -ON_EPSILON && back >= -ON_EPSILON) + return TestLine_r (tnode->children[0], start, stop); + + if (front < ON_EPSILON && back < ON_EPSILON) + return TestLine_r (tnode->children[1], start, stop); + + side = front < 0; + + frac = front / (front-back); + + mid[0] = start[0] + (stop[0] - start[0])*frac; + mid[1] = start[1] + (stop[1] - start[1])*frac; + mid[2] = start[2] + (stop[2] - start[2])*frac; + + r = TestLine_r (tnode->children[side], start, mid); + if( r & (CONTENTS_SOLID|CONTENTS_SKY)) + return r; + return TestLine_r (tnode->children[!side], mid, stop); +} + +int TestLine (vec3_t start, vec3_t stop) +{ + return TestLine_r (0, start, stop); +} + +/* +============================================================================== + +LINE TRACING + +The major lighting operation is a point to point visibility test, performed +by recursive subdivision of the line by the BSP tree. + +============================================================================== +*/ + +typedef struct +{ + vec3_t backpt; + int side; + int node; +} tracestack_t; + + +/* +============== +TestLine +============== +*/ +bool _TestLine (vec3_t start, vec3_t stop) +{ + int node; + float front, back; + tracestack_t *tstack_p; + int side; + float frontx,fronty, frontz, backx, backy, backz; + tracestack_t tracestack[64]; + tnode_t *tnode; + + frontx = start[0]; + fronty = start[1]; + frontz = start[2]; + backx = stop[0]; + backy = stop[1]; + backz = stop[2]; + + tstack_p = tracestack; + node = 0; + + while (1) + { + if (node == CONTENTS_SOLID) + { +#if 0 + float d1, d2, d3; + + d1 = backx - frontx; + d2 = backy - fronty; + d3 = backz - frontz; + + if (d1*d1 + d2*d2 + d3*d3 > 1) +#endif + return false; // DONE! + } + + while (node < 0) + { + // pop up the stack for a back side + tstack_p--; + if (tstack_p < tracestack) + return true; + node = tstack_p->node; + + // set the hit point for this plane + + frontx = backx; + fronty = backy; + frontz = backz; + + // go down the back side + + backx = tstack_p->backpt[0]; + backy = tstack_p->backpt[1]; + backz = tstack_p->backpt[2]; + + node = tnodes[tstack_p->node].children[!tstack_p->side]; + } + + tnode = &tnodes[node]; + + switch (tnode->type) + { + case PLANE_X: + front = frontx - tnode->dist; + back = backx - tnode->dist; + break; + case PLANE_Y: + front = fronty - tnode->dist; + back = backy - tnode->dist; + break; + case PLANE_Z: + front = frontz - tnode->dist; + back = backz - tnode->dist; + break; + default: + front = (frontx*tnode->normal[0] + fronty*tnode->normal[1] + frontz*tnode->normal[2]) - tnode->dist; + back = (backx*tnode->normal[0] + backy*tnode->normal[1] + backz*tnode->normal[2]) - tnode->dist; + break; + } + + if (front > -ON_EPSILON && back > -ON_EPSILON) +// if (front > 0 && back > 0) + { + node = tnode->children[0]; + continue; + } + + if (front < ON_EPSILON && back < ON_EPSILON) +// if (front <= 0 && back <= 0) + { + node = tnode->children[1]; + continue; + } + + side = front < 0; + + front = front / (front-back); + + tstack_p->node = node; + tstack_p->side = side; + tstack_p->backpt[0] = backx; + tstack_p->backpt[1] = backy; + tstack_p->backpt[2] = backz; + + tstack_p++; + + backx = frontx + front*(backx-frontx); + backy = fronty + front*(backy-fronty); + backz = frontz + front*(backz-frontz); + + node = tnode->children[side]; + } +} + + diff --git a/xtools/bsplib/tree.c b/xtools/bsplib/tree.c new file mode 100644 index 00000000..8b752977 --- /dev/null +++ b/xtools/bsplib/tree.c @@ -0,0 +1,198 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// bsp_tree.c - node bsp tree +//======================================================================= + +#include "bsplib.h" +#include "const.h" + +extern int c_nodes; + +void RemovePortalFromNode (portal_t *portal, node_t *l); + +node_t *NodeForPoint (node_t *node, vec3_t origin) +{ + plane_t *plane; + vec_t d; + + while (node->planenum != PLANENUM_LEAF) + { + plane = &mapplanes[node->planenum]; + d = DotProduct (origin, plane->normal) - plane->dist; + if (d >= 0) + node = node->children[0]; + else + node = node->children[1]; + } + + return node; +} + + + +/* +============= +FreeTreePortals_r +============= +*/ +void FreeTreePortals_r (node_t *node) +{ + portal_t *p, *nextp; + int s; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTreePortals_r (node->children[0]); + FreeTreePortals_r (node->children[1]); + } + + // free portals + for (p=node->portals ; p ; p=nextp) + { + s = (p->nodes[1] == node); + nextp = p->next[s]; + + RemovePortalFromNode (p, p->nodes[!s]); + FreePortal (p); + } + node->portals = NULL; +} + +/* +============= +FreeTree_r +============= +*/ +void FreeTree_r (node_t *node) +{ + face_t *f, *nextf; + + // free children + if (node->planenum != PLANENUM_LEAF) + { + FreeTree_r (node->children[0]); + FreeTree_r (node->children[1]); + } + + // free bspbrushes + FreeBrushList (node->brushlist); + + // free faces + for (f=node->faces ; f ; f=nextf) + { + nextf = f->next; + FreeFace (f); + } + + // free the node + if (node->volume) + FreeBrush (node->volume); + + if (GetNumThreads() == 1) + c_nodes--; + Mem_Free (node); +} + + +/* +============= +FreeTree +============= +*/ +void FreeTree (tree_t *tree) +{ + FreeTreePortals_r (tree->headnode); + FreeTree_r (tree->headnode); + Mem_Free (tree); +} + +//=============================================================== + +void PrintTree_r (node_t *node, int depth) +{ + int i; + plane_t *plane; + bspbrush_t *bb; + + for (i=0 ; iplanenum == PLANENUM_LEAF) + { + if (!node->brushlist) Msg ("NULL\n"); + else + { + for (bb=node->brushlist ; bb ; bb=bb->next) + Msg ("%i ", bb->original->brushnum); + Msg ("\n"); + } + return; + } + + plane = &mapplanes[node->planenum]; + Msg ("#%i (%5.2f %5.2f %5.2f):%5.2f\n", node->planenum, + plane->normal[0], plane->normal[1], plane->normal[2], + plane->dist); + PrintTree_r (node->children[0], depth+1); + PrintTree_r (node->children[1], depth+1); +} + +/* +========================================================= + +NODES THAT DON'T SEPERATE DIFFERENT CONTENTS CAN BE PRUNED + +========================================================= +*/ + +int c_pruned; + +/* +============ +PruneNodes_r +============ +*/ +void PruneNodes_r (node_t *node) +{ + bspbrush_t *b, *next; + + if (node->planenum == PLANENUM_LEAF) + return; + PruneNodes_r (node->children[0]); + PruneNodes_r (node->children[1]); + + if ( (node->children[0]->contents & CONTENTS_SOLID) + && (node->children[1]->contents & CONTENTS_SOLID) ) + { + if (node->faces) + Sys_Error ("node->faces seperating CONTENTS_SOLID"); + if (node->children[0]->faces || node->children[1]->faces) + Sys_Error ("!node->faces with children"); + + // FIXME: free stuff + node->planenum = PLANENUM_LEAF; + node->contents = CONTENTS_SOLID; + node->detail_seperator = false; + + if (node->brushlist) + Sys_Error ("PruneNodes: node->brushlist"); + + // combine brush lists + node->brushlist = node->children[1]->brushlist; + + for (b=node->children[0]->brushlist ; b ; b=next) + { + next = b->next; + b->next = node->brushlist; + node->brushlist = b; + } + + c_pruned++; + } +} + + +void PruneNodes (node_t *node) +{ + c_pruned = 0; + PruneNodes_r (node); +} \ No newline at end of file diff --git a/xtools/bsplib/winding.c b/xtools/bsplib/winding.c new file mode 100644 index 00000000..611175b0 --- /dev/null +++ b/xtools/bsplib/winding.c @@ -0,0 +1,543 @@ + +#include "bsplib.h" + +// counters are only bumped when running single threaded, +// because they are an awefull coherence problem +int c_active_windings; +int c_peak_windings; +int c_winding_allocs; +int c_winding_points; + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding (int points) +{ + winding_t *w; + int s; + + if (GetNumThreads() == 1) + { + c_winding_allocs++; + c_winding_points += points; + c_active_windings++; + if (c_active_windings > c_peak_windings) + c_peak_windings = c_active_windings; + } + s = sizeof(vec_t)*3*points + sizeof(int); + w = malloc (s); + memset (w, 0, s); + return w; +} + +void FreeWinding (winding_t *w) +{ + if (*(unsigned *)w == 0xdeaddead) + Sys_Error ("FreeWinding: freed a freed winding"); + *(unsigned *)w = 0xdeaddead; + + if (GetNumThreads() == 1) + c_active_windings--; + free (w); +} + + +/* +============ +RemoveColinearPoints +============ +*/ +int c_removed; + +void RemoveColinearPoints (winding_t *w) +{ + int i, j, k; + vec3_t v1, v2; + int nump; + vec3_t p[MAX_POINTS_ON_WINDING]; + + nump = 0; + for (i=0 ; inumpoints ; i++) + { + j = (i+1)%w->numpoints; + k = (i+w->numpoints-1)%w->numpoints; + VectorSubtract (w->p[j], w->p[i], v1); + VectorSubtract (w->p[i], w->p[k], v2); + VectorNormalize(v1); + VectorNormalize(v2); + if (DotProduct(v1, v2) < 0.999) + { + VectorCopy (w->p[i], p[nump]); + nump++; + } + } + + if (nump == w->numpoints) + return; + + if (GetNumThreads() == 1) + c_removed += w->numpoints - nump; + w->numpoints = nump; + memcpy (w->p, p, nump*sizeof(p[0])); +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist) +{ + vec3_t v1, v2; + + VectorSubtract (w->p[1], w->p[0], v1); + VectorSubtract (w->p[2], w->p[0], v2); + CrossProduct (v2, v1, normal); + VectorNormalize (normal); + *dist = DotProduct (w->p[0], normal); + +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea (winding_t *w) +{ + int i; + vec3_t d1, d2, cross; + vec_t total; + + total = 0; + for (i=2 ; inumpoints ; i++) + { + VectorSubtract (w->p[i-1], w->p[0], d1); + VectorSubtract (w->p[i], w->p[0], d2); + CrossProduct (d1, d2, cross); + total += 0.5 * VectorLength ( cross ); + } + return total; +} + +void WindingBounds (winding_t *w, vec3_t mins, vec3_t maxs) +{ + vec_t v; + int i,j; + + mins[0] = mins[1] = mins[2] = 99999; + maxs[0] = maxs[1] = maxs[2] = -99999; + + for (i=0 ; inumpoints ; i++) + { + for (j=0 ; j<3 ; j++) + { + v = w->p[i][j]; + if (v < mins[j]) + mins[j] = v; + if (v > maxs[j]) + maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter (winding_t *w, vec3_t center) +{ + int i; + float scale; + + VectorCopy (vec3_origin, center); + for (i=0 ; inumpoints ; i++) + VectorAdd (w->p[i], center, center); + + scale = 1.0/w->numpoints; + VectorScale (center, scale, center); +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane (vec3_t normal, vec_t dist) +{ + int i, x; + vec_t max, v; + vec3_t org, vright, vup; + winding_t *w; + + // find the major axis + max = -BOGUS_RANGE; + x = -1; + for( i = 0; i < 3; i++ ) + { + v = fabs( normal[i] ); + if( v > max ) + { + x = i; + max = v; + } + } + if( x == -1 ) Sys_Error ("BaseWindingForPlane: no axis found"); + + VectorClear( vup ); + switch( x ) + { + case 0: + case 1: + vup[2] = 1; + break; + case 2: + vup[0] = 1; + break; + } + + v = DotProduct( vup, normal ); + VectorMA( vup, -v, normal, vup ); + VectorNormalize( vup ); + VectorScale( normal, dist, org ); + CrossProduct( vup, normal, vright ); + VectorScale( vup, BOGUS_RANGE, vup ); + VectorScale( vright, BOGUS_RANGE, vright ); + + // project a really big axis aligned box onto the plane + w = AllocWinding( 4 ); + + VectorSubtract( org, vright, w->p[0] ); + VectorAdd( w->p[0], vup, w->p[0] ); + + VectorAdd( org, vright, w->p[1] ); + VectorAdd( w->p[1], vup, w->p[1] ); + + VectorAdd( org, vright, w->p[2] ); + VectorSubtract( w->p[2], vup, w->p[2] ); + + VectorSubtract( org, vright, w->p[3] ); + VectorSubtract( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding (winding_t *w) +{ + int size; + winding_t *c; + + c = AllocWinding (w->numpoints); + size = (int)((winding_t *)0)->p[w->numpoints]; + memcpy (c, w, size); + return c; +} + +/* +================== +ReverseWinding +================== +*/ +winding_t *ReverseWinding (winding_t *w) +{ + int i; + winding_t *c; + + c = AllocWinding (w->numpoints); + for (i=0 ; inumpoints ; i++) + { + VectorCopy (w->p[w->numpoints-1-i], c->p[i]); + } + c->numpoints = w->numpoints; + return c; +} + + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon (winding_t *in, vec3_t normal, vec_t dist, + vec_t epsilon, winding_t **front, winding_t **back) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + +// determine sides for each point + for (i=0 ; inumpoints ; i++) + { + dot = DotProduct (in->p[i], normal); + dot -= dist; + dists[i] = dot; + if (dot > epsilon) + { + sides[i] = SIDE_FRONT; + } + else if (dot < -epsilon) + { + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if (!counts[0]) + { + *back = CopyWinding (in); + return; + } + if (!counts[1]) + { + *front = CopyWinding (in); + return; + } + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + *front = f = AllocWinding (maxpts); + *back = b = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + if (sides[i] == SIDE_BACK) + { + VectorCopy (p1, b->p[b->numpoints]); + b->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + VectorCopy (mid, b->p[b->numpoints]); + b->numpoints++; + } + + if (f->numpoints > maxpts || b->numpoints > maxpts) + Sys_Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING || b->numpoints > MAX_POINTS_ON_WINDING) + Sys_Error ("ClipWinding: MAX_POINTS_ON_WINDING"); +} + + +/* +============= +ChopWindingInPlace +============= +*/ +void ChopWindingInPlace( winding_t **inout, vec3_t normal, vec_t dist, vec_t epsilon ) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + static vec_t dot; // VC 4.2 optimizer bug if not static + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for( i = 0; i < in->numpoints; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + if( dot > epsilon ) + sides[i] = SIDE_FRONT; + else if (dot < -epsilon) + sides[i] = SIDE_BACK; + else + { + sides[i] = SIDE_ON; + } + counts[sides[i]]++; + } + sides[i] = sides[0]; + dists[i] = dists[0]; + + if( !counts[0] ) + { + FreeWinding (in); + *inout = NULL; + return; + } + if( !counts[1] ) return; // inout stays the same + + maxpts = in->numpoints+4; // cant use counts[0]+2 because + // of fp grouping errors + + f = AllocWinding (maxpts); + + for (i=0 ; inumpoints ; i++) + { + p1 = in->p[i]; + + if (sides[i] == SIDE_ON) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + continue; + } + + if (sides[i] == SIDE_FRONT) + { + VectorCopy (p1, f->p[f->numpoints]); + f->numpoints++; + } + + if (sides[i+1] == SIDE_ON || sides[i+1] == sides[i]) + continue; + + // generate a split point + p2 = in->p[(i+1)%in->numpoints]; + + dot = dists[i] / (dists[i]-dists[i+1]); + for (j=0 ; j<3 ; j++) + { // avoid round off error when possible + if (normal[j] == 1) + mid[j] = dist; + else if (normal[j] == -1) + mid[j] = -dist; + else + mid[j] = p1[j] + dot*(p2[j]-p1[j]); + } + + VectorCopy (mid, f->p[f->numpoints]); + f->numpoints++; + } + + if (f->numpoints > maxpts) + Sys_Error ("ClipWinding: points exceeded estimate"); + if (f->numpoints > MAX_POINTS_ON_WINDING) + Sys_Error ("ClipWinding: MAX_POINTS_ON_WINDING"); + + FreeWinding (in); + *inout = f; +} + +/* +================= +CheckWinding + +================= +*/ +void CheckWinding (winding_t *w) +{ + int i, j; + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal, facenormal; + vec_t area; + vec_t facedist; + + if (w->numpoints < 3) + Sys_Error ("CheckWinding: %i points",w->numpoints); + + area = WindingArea(w); + if (area < 1) + Sys_Error ("CheckWinding: %f area", area); + + WindingPlane (w, facenormal, &facedist); + + for (i=0 ; inumpoints ; i++) + { + p1 = w->p[i]; + + for (j=0 ; j<3 ; j++) + if (p1[j] > BOGUS_RANGE || p1[j] < -BOGUS_RANGE) + Sys_Error ("CheckFace: BUGUS_RANGE: %f",p1[j]); + + j = i+1 == w->numpoints ? 0 : i+1; + + // check the point is on the face plane + d = DotProduct (p1, facenormal) - facedist; + if (d < -ON_EPSILON || d > ON_EPSILON) + Sys_Error ("CheckWinding: point off plane"); + + // check the edge isnt degenerate + p2 = w->p[j]; + VectorSubtract (p2, p1, dir); + + if (VectorLength (dir) < ON_EPSILON) + Sys_Error ("CheckWinding: degenerate edge"); + + CrossProduct (facenormal, dir, edgenormal); + VectorNormalize (edgenormal); + edgedist = DotProduct (p1, edgenormal); + edgedist += ON_EPSILON; + + // all other points must be on front side + for (j=0 ; jnumpoints ; j++) + { + if (j == i) + continue; + d = DotProduct (w->p[j], edgenormal); + if (d > edgedist) + Sys_Error ("CheckWinding: non-convex"); + } + } +} \ No newline at end of file diff --git a/xtools/bsplib/writebsp.c b/xtools/bsplib/writebsp.c new file mode 100644 index 00000000..3fb1b6ee --- /dev/null +++ b/xtools/bsplib/writebsp.c @@ -0,0 +1,540 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// bsp_emit.c - emit bsp nodes and leafs +//======================================================================= + +#include "bsplib.h" +#include "const.h" + +int c_nofaces; +int c_facenodes; + +/* +============ +EmitPlanes + +There is no oportunity to discard planes, because all of the original +brushes will be saved in the map. +============ +*/ +void EmitPlanes( void ) +{ + int i; + dplane_t *dp; + plane_t *mp; + + mp = mapplanes; + for( i = 0; i < nummapplanes; i++, mp++ ) + { + dp = &dplanes[numplanes]; + VectorCopy( mp->normal, dp->normal ); + dp->dist = mp->dist; + numplanes++; + } +} + + +//======================================================== + +void EmitMarkFace (dleaf_t *leaf_p, face_t *f) +{ + int i; + int facenum; + + while (f->merged) + f = f->merged; + + if (f->split[0]) + { + EmitMarkFace (leaf_p, f->split[0]); + EmitMarkFace (leaf_p, f->split[1]); + return; + } + + facenum = f->outputnumber; + if (facenum == -1) + return; // degenerate face + + if( facenum < 0 || facenum >= numsurfaces) + Sys_Error ("Bad leafface"); + for (i=leaf_p->firstleafsurface ; i= MAX_MAP_LEAFFACES ) + Sys_Error( "MAX_MAP_LEAFFACES limit exceeded\n" ); + + dleafsurfaces[numleafsurfaces] = facenum; + numleafsurfaces++; + } + +} + + +/* +================== +EmitLeaf +================== +*/ +void EmitLeaf( node_t *node ) +{ + dleaf_t *leaf_p; + portal_t *p; + int s; + face_t *f; + bspbrush_t *b; + int i, brushnum; + + // emit a leaf + if( numleafs >= MAX_MAP_LEAFS ) + Sys_Error( "MAX_MAP_LEAFS limit exceeded\n" ); + + leaf_p = &dleafs[numleafs]; + numleafs++; + + leaf_p->contents = node->contents; + leaf_p->cluster = node->cluster; + leaf_p->area = node->area; + + // + // write bounding box info + // + VectorCopy (node->mins, leaf_p->mins); + VectorCopy (node->maxs, leaf_p->maxs); + + // + // write the leafbrushes + // + leaf_p->firstleafbrush = numleafbrushes; + for( b = node->brushlist; b; b = b->next ) + { + if( numleafbrushes >= MAX_MAP_LEAFBRUSHES ) + Sys_Error( "MAX_MAP_LEAFBRUSHES limit exceeded\n" ); + + brushnum = b->original - mapbrushes; + for( i = leaf_p->firstleafbrush; i < numleafbrushes; i++ ) + if( dleafbrushes[i] == brushnum ) break; + + if( i == numleafbrushes ) + { + dleafbrushes[numleafbrushes] = brushnum; + numleafbrushes++; + } + } + leaf_p->numleafbrushes = numleafbrushes - leaf_p->firstleafbrush; + + // write the leaffaces + if( leaf_p->contents & CONTENTS_SOLID ) + return; // no leaffaces in solids + + leaf_p->firstleafsurface = numleafsurfaces; + + for (p = node->portals ; p ; p = p->next[s]) + { + s = (p->nodes[1] == node); + f = p->face[s]; + if (!f) + continue; // not a visible portal + + EmitMarkFace (leaf_p, f); + } + + leaf_p->numleafsurfaces = numleafsurfaces - leaf_p->firstleafsurface; +} + + +/* +================== +EmitFace +================== +*/ +void EmitFace( face_t *f ) +{ + dsurface_t *df; + int i; + int e; + + f->outputnumber = -1; + + if( f->numpoints < 3 ) + { + return; // degenerated + } + if( f->merged || f->split[0] || f->split[1] ) + { + return; // not a final face + } + + // save output number so leaffaces can use + f->outputnumber = numsurfaces; + + if( numsurfaces >= MAX_MAP_SURFACES ) + Sys_Error( "MAX_MAP_SURFACES limit exceeded\n" ); + df = &dsurfaces[numsurfaces]; + numsurfaces++; + + // planenum is used by qlight, but not quake + df->planenum = f->planenum & (~1); + df->side = f->planenum & 1; + + df->firstedge = numsurfedges; + df->numedges = f->numpoints; + df->texinfo = f->texinfo; + for( i = 0; i < f->numpoints; i++ ) + { + e = GetEdge2( f->vertexnums[i], f->vertexnums[(i+1)%f->numpoints], f ); + if( numsurfedges >= MAX_MAP_SURFEDGES ) + Sys_Error( "MAX_MAP_SURFEDGES limit exceeded\n" ); + dsurfedges[numsurfedges] = e; + numsurfedges++; + } +} + +/* +============ +EmitDrawingNode_r +============ +*/ +int EmitDrawNode_r (node_t *node) +{ + dnode_t *n; + face_t *f; + int i; + + if (node->planenum == PLANENUM_LEAF) + { + EmitLeaf (node); + return -numleafs; + } + + // emit a node + if( numnodes == MAX_MAP_NODES ) + Sys_Error( "MAX_MAP_NODES limit exceeded\n" ); + n = &dnodes[numnodes]; + numnodes++; + + VectorCopy( node->mins, n->mins ); + VectorCopy( node->maxs, n->maxs ); + + if( node->planenum & 1 ) + Sys_Error ("WriteDrawNodes_r: odd planenum"); + n->planenum = node->planenum; + n->firstsurface = numsurfaces; + + if (!node->faces) + c_nofaces++; + else + c_facenodes++; + + for( f = node->faces; f; f = f->next ) + EmitFace (f); + + n->numsurfaces = numsurfaces - n->firstsurface; + + // recursively output the other nodes + for( i = 0; i < 2; i++ ) + { + if( node->children[i]->planenum == PLANENUM_LEAF ) + { + n->children[i] = -(numleafs + 1); + EmitLeaf( node->children[i] ); + } + else + { + n->children[i] = numnodes; + EmitDrawNode_r( node->children[i] ); + } + } + return n - dnodes; +} + +//========================================================= + + +/* +============ +WriteBSP + +Wirte Binary Spacing Partition Tree +============ +*/ +void WriteBSP( node_t *headnode ) +{ + int oldsurfaces; + + c_nofaces = 0; + c_facenodes = 0; + + oldsurfaces = numsurfaces; + EmitDrawNode_r( headnode ); + EmitAreaPortals( headnode ); +} + +//=========================================================== + +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers( void ) +{ + int i, models = 1; + + for( i = 1; i < num_entities; i++ ) + { + if( entities[i].numbrushes ) + { + SetKeyValue( &entities[i], "model", va( "*%i", models )); + models++; + } + } +} + +/* +============ +SetLightStyles +============ +*/ +void SetLightStyles( void ) +{ + char *t; + bsp_entity_t *e; + int i, j, k, style, stylenum = 0; + char lightTargets[MAX_SWITCHED_LIGHTS][64]; + int lightStyles[MAX_SWITCHED_LIGHTS]; + + // any light that is controlled (has a targetname) + // must have a unique style number generated for it + for( i = 1; i < num_entities; i++ ) + { + e = &entities[i]; + + t = ValueForKey( e, "classname" ); + if( com.strncmp( t, "light", 5 )) + { + if(!com.strncmp( t, "func_light", 10 )) + { + // may create func_light family + // e.g. func_light_fluoro, func_light_broken etc + k = com.atoi(ValueForKey( e, "spawnflags" )); + if( k & SF_START_ON ) t = "-2"; // initially on + else t = "-1"; // initially off + } + else t = ValueForKey( e, "style" ); + + switch( com.atoi( t )) + { + case 0: continue; // not a light, no style, generally pretty boring + case -1: // normal switchable texlight (start off) + SetKeyValue( e, "style", va( "%i", 32 + stylenum )); + stylenum++; + continue; + case -2: // backwards switchable texlight (start on) + SetKeyValue(e, "style", va( "%i", -(32 + stylenum ))); + stylenum++; + continue; + default: continue; // nothing to set + } + } + t = ValueForKey( e, "targetname" ); + if( !t[0] ) continue; + + // get existing style + style = IntForKey( e, "style" ); + if( style < LS_NORMAL || style > LS_NONE ) + Sys_Break( "Invalid lightstyle (%d) on entity %d\n", style, i ); + + // find this targetname + for( j = 0; j < stylenum; j++ ) + { + if( lightStyles[j] == style && !com.strcmp( lightTargets[j], t )) + break; + } + if( j == stylenum ) + { + if( stylenum == MAX_SWITCHED_LIGHTS ) + { + MsgDev( D_WARN, "switchable lightstyles limit (%i) exceeded at entity #%i\n", 64, i ); + break; // nothing to process + } + com.strncpy( lightTargets[j], t, MAX_SHADERPATH ); + lightStyles[j] = style; + stylenum++; + } + SetKeyValue( e, "style", va( "%i", 32 + j )); + } +} + +//=========================================================== + +/* +============ +EmitBrushes +============ +*/ +void EmitBrushes( void ) +{ + int i, j, bnum, s, x; + dbrush_t *db; + mapbrush_t *b; + dbrushside_t *cp; + vec3_t normal; + vec_t dist; + int planenum; + + numbrushsides = 0; + numbrushes = nummapbrushes; + + for( bnum = 0; bnum < nummapbrushes; bnum++ ) + { + b = &mapbrushes[bnum]; + db = &dbrushes[bnum]; + + db->shadernum = b->shadernum; + db->firstside = numbrushsides; + db->numsides = b->numsides; + for( j = 0; j < b->numsides; j++ ) + { + if( numbrushsides == MAX_MAP_BRUSHSIDES ) + Sys_Error( "MAX_MAP_BRUSHSIDES limit exceeded\n" ); + cp = &dbrushsides[numbrushsides]; + numbrushsides++; + cp->planenum = b->original_sides[j].planenum; + cp->texinfo = b->original_sides[j].texinfo; + } + + // add any axis planes not contained in the brush to bevel off corners + for( x = 0; x < 3; x++ ) + for (s=-1 ; s<=1 ; s+=2) + { + // add the plane + VectorCopy (vec3_origin, normal); + normal[x] = s; + if (s == -1) + dist = -b->mins[x]; + else + dist = b->maxs[x]; + planenum = FindFloatPlane (normal, dist); + for( i = 0; i < b->numsides; i++ ) + if( b->original_sides[i].planenum == planenum ) + break; + if( i == b->numsides ) + { + if( numbrushsides >= MAX_MAP_BRUSHSIDES ) + Sys_Error( "MAX_MAP_BRUSHSIDES limit exceeded\n" ); + + dbrushsides[numbrushsides].planenum = planenum; + dbrushsides[numbrushsides].texinfo = dbrushsides[numbrushsides-1].texinfo; + numbrushsides++; + db->numsides++; + } + } + + } + +} + +//=========================================================== + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile( void ) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + + nummodels = 0; + numsurfaces = 0; + numnodes = 0; + numbrushsides = 0; + numvertexes = 0; + numleafsurfaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + numedges = 1; // edge 0 is not used, because 0 can't be negated + numvertexes = 1; // leave vertex 0 as an error + numleafs = 1; // leave leaf 0 as an error + + dleafs[0].contents = CONTENTS_SOLID; +} + + +/* +============ +EndBSPFile +============ +*/ +void EndBSPFile( void ) +{ + EmitBrushes (); + EmitPlanes (); + UnparseEntities (); + + // write the map + WriteBSPFile(); +} + + +/* +================== +BeginModel +================== +*/ +int firstmodleaf; +extern int firstmodeledge; +extern int firstmodelface; + +void BeginModel( void ) +{ + dmodel_t *mod; + mapbrush_t *b; + bsp_entity_t *e; + int j, start, end; + vec3_t mins, maxs; + + if( nummodels == MAX_MAP_MODELS ) + Sys_Error( "MAX_MAP_MODELS limit exceeded\n" ); + mod = &dmodels[nummodels]; + + mod->firstsurface = numsurfaces; + firstmodleaf = numleafs; + firstmodeledge = numedges; + firstmodelface = numsurfaces; + + // bound the brushes + e = &entities[entity_num]; + mod->firstbrush = start = e->firstbrush; + mod->numbrushes = e->numbrushes; + end = start + e->numbrushes; + ClearBounds (mins, maxs); + + for( j = start; j < end; j++ ) + { + b = &mapbrushes[j]; + if (!b->numsides) continue; // not a real brush (origin brush) + AddPointToBounds( b->mins, mins, maxs ); + AddPointToBounds( b->maxs, mins, maxs ); + } + VectorCopy( mins, mod->mins ); + VectorCopy( maxs, mod->maxs ); +} + + +/* +================== +EndModel +================== +*/ +void EndModel( void ) +{ + dmodel_t *mod; + + mod = &dmodels[nummodels]; + mod->numsurfaces = numsurfaces - mod->firstsurface; + nummodels++; +} \ No newline at end of file diff --git a/xtools/dpvencoder.c b/xtools/dpvencoder.c new file mode 100644 index 00000000..d16ddacc --- /dev/null +++ b/xtools/dpvencoder.c @@ -0,0 +1,22 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// dpvencoder.c - DP video encoder +//======================================================================= + +#include "xtools.h" +#include "utils.h" + +byte *dpvpool; + +bool CompileDPVideo( byte *mempool, const char *name, byte parms ) +{ + if( mempool ) dpvpool = mempool; + else + { + Msg( "DPV Encoder: can't allocate memory pool.\nAbort compilation\n" ); + return false; + } + + // FIXME: implement + return false; +} \ No newline at end of file diff --git a/xtools/mdllib.h b/xtools/mdllib.h new file mode 100644 index 00000000..c5461cb5 --- /dev/null +++ b/xtools/mdllib.h @@ -0,0 +1,347 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// studiomdl.h - generates a studio .mdl file from a .qc script +//======================================================================= +#ifndef STUDIOMDL_H +#define STUDIOMDL_H + +#include "xtools.h" +#include "utils.h" +#include "mathlib.h" + +#define FILEBUFFER (1024 * 1024 * 16) // 16 megs filebuffer +#define FLOAT_START 99999.0 +#define FLOAT_END -99999.0 +#define MAGIC 123322 +#define Kalloc(size) Mem_Alloc(studiopool, size) +#define Realloc(ptr, size) Mem_Realloc(studiopool, ptr, size) + +typedef struct +{ + int vertindex; + int normindex; // index into normal array + int s,t; + float u,v; +} s_trianglevert_t; + +typedef struct +{ + int bone; // bone transformation index + vec3_t org; // original position +} s_vertex_t; + +typedef struct +{ + int skinref; + int bone; // bone transformation index + vec3_t org; // original position +} s_normal_t; + +typedef struct +{ + vec3_t worldorg; + matrix4x4 m; + matrix4x4 im; + float length; +} s_bonefixup_t; + +typedef struct +{ + vec3_t verts[3]; +} triangle_t; + +typedef struct +{ + char name[32]; // bone name for symbolic links + int parent; // parent bone + int bonecontroller; // -1 == 0 + int flags; // X, Y, Z, XR, YR, ZR + vec3_t pos; // default pos + vec3_t posscale; // pos values scale + vec3_t rot; // default pos + vec3_t rotscale; // rotation values scale + int group; // hitgroup + vec3_t bmin, bmax; // bounding box +} s_bonetable_t; + +typedef struct +{ + char from[32]; + char to[32]; +} s_renamebone_t; + +typedef struct +{ + char name[32]; // bone name + int bone; + int group; // hitgroup + int model; + vec3_t bmin, bmax; // bounding box +} s_bbox_t; + +typedef struct +{ + int models; + int group; + char name[32]; // bone name +} s_hitgroup_t; + +typedef struct +{ + char name[32]; + int bone; + int type; + int index; + float start; + float end; +} s_bonecontroller_t; + +typedef struct +{ + char name[32]; + char bonename[32]; + int index; + int bone; + int type; + vec3_t org; +} s_attachment_t; + +typedef struct +{ + char name[64]; + int parent; + int mirrored; +} s_node_t; + +typedef struct +{ + char name[64]; + int startframe; + int endframe; + int flags; + int numbones; + s_node_t node[MAXSTUDIOSRCBONES]; + int bonemap[MAXSTUDIOSRCBONES]; + int boneimap[MAXSTUDIOSRCBONES]; + vec3_t *pos[MAXSTUDIOSRCBONES]; + vec3_t *rot[MAXSTUDIOSRCBONES]; + int numanim[MAXSTUDIOSRCBONES][6]; + dstudioanimvalue_t *anim[MAXSTUDIOSRCBONES][6]; +} s_animation_t; + +typedef struct +{ + int event; + int frame; + char options[64]; +} s_event_t; + +typedef struct +{ + int index; + vec3_t org; + int start; + int end; +} s_pivot_t; + +typedef struct +{ + int motiontype; + vec3_t linearmovement; + + char name[64]; + int flags; + float fps; + int numframes; + + int activity; + int actweight; + + int frameoffset; // used to adjust frame numbers + + int numevents; + s_event_t event[MAXSTUDIOEVENTS]; + + int numpivots; + s_pivot_t pivot[MAXSTUDIOPIVOTS]; + + int numblends; + s_animation_t *panim[MAXSTUDIOGROUPS]; + float blendtype[2]; + float blendstart[2]; + float blendend[2]; + + vec3_t automovepos[MAXSTUDIOANIMATIONS]; + vec3_t automoveangle[MAXSTUDIOANIMATIONS]; + + int seqgroup; + int animindex; + + vec3_t bmin; + vec3_t bmax; + int entrynode; + int exitnode; + int nodeflags; +} s_sequence_t; + +typedef struct +{ + char label[32]; + char name[64]; +} s_sequencegroup_t; + +typedef struct +{ + char name[64]; + int flags; + int srcwidth; + int srcheight; + byte *ppicture; + rgb_t *ppal; + float max_s; + float min_s; + float max_t; + float min_t; + int skintop; + int skinleft; + int skinwidth; + int skinheight; + float fskintop; + float fskinleft; + float fskinwidth; + float fskinheight; + int size; + void *pdata; + int parent; +} s_texture_t; + +typedef struct +{ + int numtris; + int alloctris; + s_trianglevert_t (*triangle)[3]; + + int skinref; + int numnorms; +} s_mesh_t; + +typedef struct +{ + vec3_t pos; + vec3_t rot; +} s_bone_t; + +typedef struct s_model_s +{ + char name[64]; + + int numbones; + s_node_t node[MAXSTUDIOSRCBONES]; + s_bone_t skeleton[MAXSTUDIOSRCBONES]; + int boneref[MAXSTUDIOSRCBONES]; // is local bone (or child) referenced with a vertex + int bonemap[MAXSTUDIOSRCBONES]; // local bone to world bone mapping + int boneimap[MAXSTUDIOSRCBONES]; // world bone to local bone mapping + + vec3_t boundingbox[MAXSTUDIOSRCBONES][2]; + + s_mesh_t *trimesh[MAXSTUDIOTRIANGLES]; + int trimap[MAXSTUDIOTRIANGLES]; + + int numverts; + s_vertex_t vert[MAXSTUDIOVERTS]; + + int numnorms; + s_normal_t normal[MAXSTUDIOVERTS]; + + int nummesh; + s_mesh_t *pmesh[MAXSTUDIOMESHES]; + + float boundingradius; + + int numframes; + float interval; + struct s_model_s *next; +} s_model_t; + +typedef struct +{ + char name[32]; + int nummodels; + int base; + s_model_t *pmodel[MAXSTUDIOMODELS]; +} s_bodypart_t; + +typedef struct +{ + vec3_t n; // normal + vec3_t p; // point + vec3_t c; // color + float u; // u + float v; // v +} aliaspoint_t; + +typedef struct +{ + aliaspoint_t pt[3]; +} tf_triangle; + +extern int numseq; +extern int numani; +extern int numbones; +extern int numxnodes; +extern int numhitboxes; +extern int nummirrored; +extern int numseqgroups; +extern int numhitgroups; +extern int numattachments; +extern int numrenamedbones; +extern int numcommandnodes; +extern int numtextures; +extern int numbonecontrollers; +extern int numcommands; +extern int stripcount; +extern int clip_texcoords; +extern int xnode[100][100]; +extern byte *studiopool; + +extern s_bbox_t hitbox[MAXSTUDIOSRCBONES]; +extern s_texture_t *texture[MAXSTUDIOSKINS]; +extern string mirrored[MAXSTUDIOSRCBONES]; +extern s_hitgroup_t hitgroup[MAXSTUDIOSRCBONES]; +extern s_sequence_t *sequence[MAXSTUDIOSEQUENCES]; +extern s_bonefixup_t bonefixup[MAXSTUDIOSRCBONES]; +extern s_bonetable_t bonetable[MAXSTUDIOSRCBONES]; +extern s_attachment_t attachment[MAXSTUDIOSRCBONES]; +extern s_renamebone_t renamedbone[MAXSTUDIOSRCBONES]; +extern s_animation_t *panimation[MAXSTUDIOSEQUENCES*MAXSTUDIOBLENDS]; +extern s_bonecontroller_t bonecontroller[MAXSTUDIOSRCBONES]; + +extern float normal_blend; +extern float scale_up; +extern float gamma; +extern vec3_t adjust; + +extern byte *studiopool; +extern s_mesh_t *pmesh; + +void LoadTriangleList (char *filename, triangle_t **pptri, int *numtriangles); +int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata ); +void ResetTextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ); +void TextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ); +s_trianglevert_t *lookup_triangle( s_mesh_t *pmesh, int index ); +s_mesh_t *lookup_mesh( s_model_t *pmodel, char *texturename ); +int lookup_normal( s_model_t *pmodel, s_normal_t *pnormal ); +int lookup_vertex( s_model_t *pmodel, s_vertex_t *pv ); +void ResizeTexture( s_texture_t *ptexture ); +int lookup_texture( char *texturename ); +int lookupActivity( char *szActivity ); +void clip_rotations( vec3_t rot ); +void scale_vertex( float *org ); +void adjust_vertex( float *org ); +int lookupControl( char *string ); +void OptimizeAnimations(void); +void MakeTransitions( void ); +int findNode( char *name ); +void ExtractMotion( void ); + +#endif//STUDIOMDL_H \ No newline at end of file diff --git a/xtools/ripper/conv_bsplumps.c b/xtools/ripper/conv_bsplumps.c new file mode 100644 index 00000000..b6651f6b --- /dev/null +++ b/xtools/ripper/conv_bsplumps.c @@ -0,0 +1,266 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// conv_bsplumps.c - convert bsp lumps +//======================================================================= + +#include "ripper.h" +#include "byteorder.h" + +#define VDBSPMODHEADER (('P'<<24)+('S'<<16)+('B'<<8)+'V') // little-endian "VBSP" hl2 bsp's +#define IDIWADHEADER (('D'<<24)+('A'<<16)+('W'<<8)+'I') // little-endian "IWAD" doom1 game wad +#define IDPWADHEADER (('D'<<24)+('A'<<16)+('W'<<8)+'P') // little-endian "PWAD" doom1 game wad +#define IDWAD2HEADER (('2'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD2" quake1 gfx.wad +#define IDWAD3HEADER (('3'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD3" half-life wads + +typedef struct +{ + int ident; // should be IWAD, WAD2 or WAD3 + int numlumps; // num files + int infotableofs; +} dwadheader_t; + +typedef struct +{ + int fileofs; + int filelen; +} dlump_t; + +typedef struct +{ + int version; + dlump_t lumps[15]; +} dbspheader_t; + +typedef struct +{ + int ident; + int version; +} gbsphdr_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +typedef struct +{ + char name[64]; +} dmiptexname_t; + +byte* bsp_base; +bool bsp_halflife = false; +dmiptexname_t *mipnames = NULL; +int mip_count = 0; + +bool MipExist( const char *name ) +{ + int i; + + if( !name ) return false; + for( i = 0; i < mip_count; i++ ) + { + if( !com.stricmp( name, mipnames[i].name )) + return true; + } + return FS_FileExists( name ); +} + +void Conv_BspTextures( const char *name, dlump_t *l, const char *ext ) +{ + dmiptexlump_t *m; + string genericname; + mip_t *mip; + int i, k; + int *dofs, size; + byte *buffer; + + if( !l->filelen ) return; // no textures stored + FS_FileBase( name, genericname ); + + m = (dmiptexlump_t *)(bsp_base + l->fileofs); + m->nummiptex = LittleLong( m->nummiptex ); + dofs = m->dataofs; + + mipnames = Mem_Realloc( basepool, mipnames, m->nummiptex * sizeof( dmiptexname_t )); + mip_count = 0; + + // first pass: store all names into linear array + for( i = 0; i < m->nummiptex; i++ ) + { + dofs[i] = LittleLong( dofs[i] ); + if( dofs[i] == -1 ) continue; + + // needs to simulate directly loading + mip = (mip_t *)((byte *)m + dofs[i]); + if( !LittleLong( mip->offsets[0] )) continue; // not in bsp + com.strnlwr( mip->name, mip->name, sizeof( mip->name )); // name + + // check for '*' symbol issues + k = com.strlen( com.strrchr( mip->name, '*' )); + if( k ) mip->name[com.strlen(mip->name)-k] = '!'; // quake1 issues + // some Q1 mods contains blank names (e.g. "after the fall") + if( !com.strlen( mip->name )) com.snprintf( mip->name, 16, "%s_%d", genericname, i ); + com.snprintf( mipnames[mip_count].name, 64, "%s/%s.mip", genericname, mip->name ); // build name + mip_count++; + } + + // second pass: convert lumps + for( i = 0; i < m->nummiptex; i++ ) + { + dofs[i] = LittleLong( dofs[i] ); + if( dofs[i] == -1 ) continue; + + // needs to simulate direct loading + mip = (mip_t *)((byte *)m + dofs[i]); + if( !LittleLong( mip->offsets[0] )) continue; // not in bsp + + buffer = ((byte *)m + dofs[i]); // buffer + size = (int)sizeof(mip_t) + (((mip->width * mip->height) * 85)>>6); + if( bsp_halflife ) size += sizeof(short) + 768; // palette + + if( !FS_FileExists( va( "%s/%s/%s.%s", gs_gamedir, name, mip->name, ext ))) + ConvMIP( va("%s/%s", name, mip->name ), buffer, size, ext ); // convert it + } + mip_count = 0; // freed and invaliadte +} + +/* +============ +ConvBSP +============ +*/ +bool ConvBSP( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + dbspheader_t *header = (dbspheader_t *)buffer; + int i = LittleLong( header->version ); + + switch( i ) + { + case 28: + case 29: + bsp_halflife = false; + break; + case 30: + bsp_halflife = true; + break; + default: + return false; // another bsp version + } + bsp_base = (byte*)buffer; + + for( i = 0; i < 15; i++ ) + { + header->lumps[i].fileofs = LittleLong(header->lumps[i].fileofs); + header->lumps[i].filelen = LittleLong(header->lumps[i].filelen); + } + Conv_BspTextures( name, &header->lumps[2], ext ); // LUMP_TEXTURES + + return true; +} + +bool Conv_CheckMap( const char *mapname ) +{ + file_t *f = FS_Open( mapname, "rb" ); + gbsphdr_t hdr; // generic header + + if( !f ) return false; + + if(FS_Read( f, &hdr, sizeof(gbsphdr_t)) != sizeof(gbsphdr_t)) + { + FS_Close( f ); // very strange file with size smaller than 8 bytes and ext .bsp + return false; + } + // detect game type + switch( LittleLong( hdr.ident )) + { + case 28: // quake 1 beta + case 29: // quake 1 release + game_family = GAME_QUAKE1; + FS_Close( f ); + return true; + case 30: + game_family = GAME_HALFLIFE; + FS_Close( f ); + return true; + case IDBSPMODHEADER: // continue checking + switch( LittleLong( hdr.version )) + { + case 38: + game_family = GAME_QUAKE2; + FS_Close( f ); + return true; + case 46: + game_family = GAME_QUAKE3; + FS_Close( f ); + return true; + case 47: + game_family = GAME_RTCW; + FS_Close( f ); + return true; + } + case VDBSPMODHEADER: // continue checking + switch( LittleLong( hdr.version )) + { + case 18: + game_family = GAME_HALFLIFE2_BETA; + FS_Close( f ); + return true; + case 19: + case 20: + game_family = GAME_HALFLIFE2; + FS_Close( f ); + return true; + } + case IDWAD3HEADER: + game_family = GAME_XASH3D; + FS_Close( f ); + return true; + default: + game_family = GAME_GENERIC; + FS_Close( f ); + return false; + } +} + +bool Conv_CheckWad( const char *wadname ) +{ + file_t *f = FS_Open( wadname, "rb" ); + dwadheader_t hdr; // generic header + + if( !f ) return false; + + if(FS_Read( f, &hdr, sizeof(dwadheader_t)) != sizeof(dwadheader_t)) + { + FS_Close( f ); // very strange file with size smaller than 12 bytes and ext .wad + return false; + } + + // detect game type + switch( hdr.ident ) + { + case IDIWADHEADER: + case IDPWADHEADER: + game_family = GAME_DOOM1; + FS_Close( f ); + break; + case IDWAD2HEADER: + game_family = GAME_QUAKE1; + FS_Close( f ); + break; + case IDWAD3HEADER: + game_family = GAME_HALFLIFE; + FS_Close( f ); + break; + default: + game_family = GAME_GENERIC; + break; + } + + // and check wadnames in case + if(!com.stricmp( wadname, "tnt.wad" ) && game_family == GAME_DOOM1 ) + { + Msg("Wow! Doom2 na TNT!\n" ); + } + return (game_family != GAME_GENERIC); +} \ No newline at end of file diff --git a/xtools/ripper/conv_doom.c b/xtools/ripper/conv_doom.c new file mode 100644 index 00000000..d964857f --- /dev/null +++ b/xtools/ripper/conv_doom.c @@ -0,0 +1,702 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// conv_doom.c - convert doom1\2 resources +//======================================================================= + +#include "ripper.h" +#include +#include + +// doom .mus files +#define MUSIDHEADER ((0x1A<<24)+('S'<<16)+('U'<<8)+'M') // little-endian "MUS " +#define MIDIDHEADER "MThd\000\000\000\006\000\001" +#define TRACKMAGIC1 "\000\377\003\035" +#define TRACKMAGIC2 "\000\377\057\000" +#define TRACKMAGIC3 "\000\377\002\026" +#define TRACKMAGIC4 "\000\377\131\002\000\000" +#define TRACKMAGIC5 "\000\377\121\003\011\243\032" +#define TRACKMAGIC6 "\000\377\057\000" + +#define MIDBUFFER 0x20000 +#define last(e) ((byte)(e & 0x80)) +#define event_type(e) ((byte)((e & 0x7F)>>4)) +#define channel(e) ((byte)(e & 0x0F)) +#define mid_write1 VFS_Write + +typedef struct +{ + int ident; + word ScoreLength; + word ScoreStart; + word channels; // count of primary channels + word SecChannels; // count of secondary channels + word InstrCnt; + word dummy; + word *instruments; +} mus_t; + +struct track_s +{ + dword current; + char vel; + long DeltaTime; + byte LastEvent; + char *data; // primary data +}; + +// doom spritemodel_qc +typedef struct angled_s +{ + char name[10]; // copy of skin name + + int width; // lumpwidth + int height; // lumpheight + int origin[2]; // monster origin + byte xmirrored; // swap left and right +} angled_t; + +struct angledframe_s +{ + angled_t frame[8]; // angled group or single frame + int bounds[2]; // group or frame maxsizes + byte angledframes; // count of angled frames max == 8 + byte normalframes; // count of anim frames max == 1 + byte mirrorframes; // animation mirror stored + + char membername[8]; // current model name, four characsters + char animation; // current animation number + bool in_progress; // current state + file_t *f; // skin script +} flat; + +static size_t mid_write2( vfile_t *file, const uint *ptr, size_t size ) +{ + uint i, rev = 0; + + for( i = 0; (size_t)i < size; i++ ) + rev = (rev << 8) + (((*ptr) >>(i*8)) & 0xFF) ; + + return VFS_Write( file, &rev, size ); +} + +static void Conv_WriteMIDheader( vfile_t *file, uint ntrks, uint division ) +{ + mid_write1( file, MIDIDHEADER, 10 ); + mid_write2( file, &ntrks, 2 ); + mid_write2( file, &division, 2 ); +} + +static void Conv_WriteTrack( vfile_t *file, int tracknum, struct track_s track[] ) +{ + uint size; + size_t quot, rem; + + // do we risk overflow here ? + size = (uint)track[tracknum].current + 4; + mid_write1( file, "MTrk", 4 ); + if( !tracknum ) size += 33; + + mid_write2( file, &size, 4 ); + if( !tracknum) mid_write1( file, TRACKMAGIC1 "written by Xash MusLib Ripper", 33 ); + quot = (size_t)(track[tracknum].current / 4096); + rem = (size_t)(track[tracknum].current - quot * 4096); + mid_write1( file, track[tracknum].data, 4096 * quot ); + mid_write1( file, ((const byte *)track[tracknum].data) + 4096 * quot, rem ); + mid_write1( file, TRACKMAGIC2, 4 ); +} + +static void Conv_WriteFirstTrack( vfile_t *file ) +{ + uint size = 43; + + mid_write1( file, "MTrk", 4); + mid_write2( file, &size, 4 ); + mid_write1( file, TRACKMAGIC3, 4 ); + mid_write1( file, "by XashXT Group 2008 ©", 22 ); + mid_write1( file, TRACKMAGIC4, 6 ); + mid_write1( file, TRACKMAGIC5, 7 ); + mid_write1( file, TRACKMAGIC6, 4 ); +} + +static bool Conv_ReadMusHeader( vfile_t *f, mus_t *hdr ) +{ + bool result = true; + + VFS_Read( f, &hdr->ident, 4 ); + if( hdr->ident != MUSIDHEADER ) + return false; + + VFS_Read(f, &(hdr->ScoreLength), sizeof(word)); + VFS_Read(f, &(hdr->ScoreStart), sizeof(word)); + VFS_Read(f, &(hdr->channels), sizeof(word)); + VFS_Read(f, &(hdr->SecChannels), sizeof(word)); + VFS_Read(f, &(hdr->InstrCnt), sizeof(word)); + VFS_Read(f, &(hdr->dummy), sizeof(word)); + hdr->instruments = (word *)Mem_Alloc( basepool, hdr->InstrCnt * sizeof(word)); + + if(VFS_Read( f, hdr->instruments, hdr->InstrCnt * sizeof(word)) != hdr->InstrCnt * sizeof(word)) + result = false; + Mem_Free( hdr->instruments ); + return result; +} + +static char Conv_GetChannel( signed char MUS2MIDchannel[] ) +{ + signed char old15 = MUS2MIDchannel[15], max = -1; + int i; + + MUS2MIDchannel[15] = -1; + + for( i = 0; i < 16; i++ ) + { + if( MUS2MIDchannel[i] > max ) + max = MUS2MIDchannel[i]; + } + MUS2MIDchannel[15] = old15; + return (max == 8 ? 10 : max + 1); +} + +static void Conv_FreeTracks( struct track_s track[] ) +{ + int i ; + + for( i = 0; i < 16; i++ ) + { + if( track[i].data ) Mem_Free( track[i].data ) ; + } +} + +static uint Conv_ReadTime( vfile_t *file ) +{ + register uint time = 0; + int newbyte; + + do + { + VFS_Read( file, &newbyte, 1 ); + if( newbyte != EOF ) time = (time << 7) + (newbyte & 0x7F); + } while((newbyte != EOF) && (newbyte & 0x80)); + return time ; +} + +static void Conv_WriteByte( char MIDItrack, char byte, struct track_s track[] ) +{ + uint pos; + + pos = track[MIDItrack].current; + if( pos < MIDBUFFER ) + { + // need to reallocte ? + track[MIDItrack].data[pos] = byte; + } + else + { + Conv_FreeTracks( track ); + Sys_Break( "not enough memory\n" ); + } + track[MIDItrack].current++; +} + +static void Conv_WriteVarLen( int tracknum, register uint value, struct track_s track[] ) +{ + register uint buffer; + + buffer = value & 0x7f; + while((value >>= 7)) + { + buffer<<= 8; + buffer |= 0x80; + buffer += (value & 0x7f); + } + while( 1 ) + { + Conv_WriteByte( tracknum, buffer, track ); + if( buffer & 0x80 ) buffer >>= 8; + else break; + } +} + +static bool Conv_Mus2Mid( const char *musicname, byte *buffer, int bufsize ) +{ + struct track_s track[16]; + word TrackCnt = 0; + word division = 90; + byte et, MUSchannel, MIDIchannel, MIDItrack, NewEvent; + uint i, DeltaTime, TotalTime = 0, n = 0; + char event, data, ouch = 0; + signed char MUS2MIDchannel[16]; + vfile_t *file_mid, *file_mus = VFS_Create( buffer, bufsize ); + file_t *f; + static mus_t MUSh; + byte MUS2MIDcontrol[15] = + { + 0, // program change - not a MIDI control change + 0x00, // bank select + 0x01, // modulation pot + 0x07, // volume + 0x0A, // pan pot + 0x0B, // expression pot + 0x5B, // reverb depth + 0x5D, // chorus depth + 0x40, // sustain pedal + 0x43, // soft pedal + 0x78, // all sounds off + 0x7B, // all notes off + 0x7E, // mono + 0x7F, // poly + 0x79 // reset all controllers + }, MIDIchan2track[16]; + + if(!Conv_ReadMusHeader( file_mus, &MUSh )) + { + VFS_Close( file_mus ); + MsgDev(D_ERROR, "Conv_Mus2Mid: can't read mus header\n" ); + return false; + } + + if( VFS_Seek( file_mus, MUSh.ScoreStart, SEEK_SET )) + { + VFS_Close( file_mus ); + MsgDev(D_ERROR,"Conv_Mus2Mid: can't seek scores\n" ); + return false; + } + + if( MUSh.channels > 15 ) + { + VFS_Close( file_mus ); + MsgDev(D_ERROR,"Conv_Mus2Mid: too many channels\n" ); + return false; + } + + for( i = 0; i < 16; i++ ) + { + MUS2MIDchannel[i] = -1; + track[i].current = 0; + track[i].vel = 64; + track[i].DeltaTime = 0; + track[i].LastEvent = 0; + track[i].data = NULL; + } + + VFS_Read( file_mus, &event, 1 ); + et = event_type( event ); + MUSchannel = channel( event ); + + while((et != 6) && !VFS_Eof( file_mus ) && (event != EOF)) + { + if( MUS2MIDchannel[MUSchannel] == -1 ) + { + MIDIchannel = MUS2MIDchannel[MUSchannel] = (MUSchannel == 15 ? 9:Conv_GetChannel(MUS2MIDchannel)); + MIDItrack = MIDIchan2track[MIDIchannel] = (byte)(TrackCnt++); + track[MIDItrack].data = (char *)Mem_Alloc( basepool, MIDBUFFER ); + } + else + { + MIDIchannel = MUS2MIDchannel[MUSchannel]; + MIDItrack = MIDIchan2track [MIDIchannel]; + } + Conv_WriteVarLen( MIDItrack, track[MIDItrack].DeltaTime, track ); + track[MIDItrack].DeltaTime = 0; + + switch( et ) + { + case 0: // release note + NewEvent = 0x90 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + VFS_Read( file_mus, &data, 1 ); + Conv_WriteByte( MIDItrack, data, track ); + Conv_WriteByte( MIDItrack, 0, track ); + break; + case 1: + NewEvent = 0x90 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + VFS_Read( file_mus, &data, 1 ); + Conv_WriteByte( MIDItrack, data & 0x7F, track ); + if( data & 0x80 ) VFS_Read( file_mus, &track[MIDItrack].vel, 1 ); + Conv_WriteByte( MIDItrack, track[MIDItrack].vel, track ); + break; + case 2: + NewEvent = 0xE0 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + VFS_Read( file_mus, &data, 1 ); + Conv_WriteByte( MIDItrack, (data & 1) << 6, track ); + Conv_WriteByte( MIDItrack, data >> 1, track ); + break; + case 3: + NewEvent = 0xB0 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + VFS_Read( file_mus, &data, 1 ); + Conv_WriteByte( MIDItrack, MUS2MIDcontrol[data], track ); + if( data == 12 ) Conv_WriteByte( MIDItrack, MUSh.channels + 1, track ); + else Conv_WriteByte( MIDItrack, 0, track ); + break; + case 4: + VFS_Read( file_mus, &data, 1 ); + if( data ) + { + NewEvent = 0xB0 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + Conv_WriteByte( MIDItrack, MUS2MIDcontrol[data], track ); + } + else + { + NewEvent = 0xC0 | MIDIchannel; + if( NewEvent != track[MIDItrack].LastEvent ) + { + Conv_WriteByte( MIDItrack, NewEvent, track ); + track[MIDItrack].LastEvent = NewEvent; + } + else n++; + } + VFS_Read( file_mus, &data, 1 ); + Conv_WriteByte( MIDItrack, data, track ); + break; + case 5: + case 7: + Conv_FreeTracks( track ); + MsgDev( D_ERROR, "Conv_Mus2Mid: bad event\n" ); + return false; + default: + break; + } + + if(last( event )) + { + DeltaTime = Conv_ReadTime( file_mus ); + TotalTime += DeltaTime; + for( i = 0; i < (int)TrackCnt; i++ ) + track[i].DeltaTime += DeltaTime; + } + VFS_Read( file_mus, &event, 1 ); + if( event != EOF ) + { + et = event_type( event ); + MUSchannel = channel( event ); + } + else ouch = 1; + } + if( ouch ) MsgDev(D_WARN, "Conv_Mus2Mid: %s.mus - end of file probably corrupted\n", musicname ); + + f = FS_Open(va( "%s/%s.mid", gs_gamedir, musicname ), "wb" ); + file_mid = VFS_Open( f, "w" ); + + Conv_WriteMIDheader( file_mid, TrackCnt + 1, division ); + Conv_WriteFirstTrack( file_mid ); + + for( i = 0; i < (int)TrackCnt; i++ ) + Conv_WriteTrack( file_mid, i, track ); + Conv_FreeTracks( track ); + FS_Close( VFS_Close( file_mid )); + VFS_Close( file_mus ); + + return true; +} + +static void Skin_RoundDimensions( int *scaled_width, int *scaled_height ) +{ + int width, height; + + for( width = 1; width < *scaled_width; width <<= 1 ); + for( height = 1; height < *scaled_height; height <<= 1 ); + + *scaled_width = bound( 1, width, 512 ); + *scaled_height = bound( 1, height, 512 ); +} + +void Skin_WriteSequence( void ) +{ + int i; + + Skin_RoundDimensions( &flat.bounds[0], &flat.bounds[1] ); + + // time to dump frames :) + if( flat.angledframes == 8 ) + { + // angled group is full, dump it! + FS_Print( flat.f, "\n$angled\n{\n" ); + FS_Printf( flat.f, "\t// frame '%c'\n", flat.frame[0].name[4] ); + FS_Printf( flat.f, "\t$resample\t\t%d %d\n", flat.bounds[0], flat.bounds[1] ); + for( i = 0; i < 8; i++) + { + FS_Printf( flat.f,"\t$load\t\t%s.bmp", flat.frame[i].name ); + if( flat.frame[i].xmirrored ) FS_Print( flat.f," flip_x\n"); + else FS_Print( flat.f, "\n" ); + FS_Printf( flat.f,"\t$frame\t\t0 0 %d %d", flat.frame[i].width, flat.frame[i].height ); + FS_Printf( flat.f, " 0.1 %d %d\n", flat.frame[i].origin[0], flat.frame[i].origin[1] ); + } + FS_Print( flat.f, "}\n" ); + } + else if( flat.normalframes == 1 ) + { + // single frame stored + FS_Print( flat.f, "\n" ); + FS_Printf( flat.f, "// frame '%c'\n", flat.frame[0].name[4] ); + FS_Printf( flat.f,"$resample\t\t%d %d\n", flat.bounds[0], flat.bounds[1] ); + FS_Printf( flat.f,"$load\t\t%s.bmp\n", flat.frame[0].name ); + FS_Printf( flat.f,"$frame\t\t0 0 %d %d", flat.frame[0].width, flat.frame[0].height ); + FS_Printf( flat.f, " 0.1 %d %d\n", flat.frame[0].origin[0], flat.frame[0].origin[1]); + } + + // drop mirror frames too + if( flat.mirrorframes == 8 ) + { + // mirrored group is always flipped + FS_Print( flat.f, "\n$angled\n{\n" ); + FS_Printf( flat.f, "\t//frame '%c' (mirror '%c')\n", flat.frame[0].name[6], flat.frame[0].name[4] ); + FS_Printf( flat.f, "\t$resample\t\t%d %d\n", flat.bounds[0], flat.bounds[1] ); + for( i = 2; i > -1; i--) + { + FS_Printf( flat.f,"\t$load\t\t%s.bmp flip_x\n", flat.frame[i].name ); + FS_Printf( flat.f,"\t$frame\t\t0 0 %d %d", flat.frame[i].width, flat.frame[i].height ); + FS_Printf( flat.f, " 0.1 %d %d\n", flat.frame[i].origin[0], flat.frame[i].origin[1] ); + } + for( i = 7; i > 2; i--) + { + FS_Printf( flat.f,"\t$load\t\t%s.bmp flip_x\n", flat.frame[i].name ); + FS_Printf( flat.f,"\t$frame\t\t0 0 %d %d", flat.frame[i].width, flat.frame[i].height ); + FS_Printf( flat.f, " 0.1 %d %d\n", flat.frame[i].origin[0], flat.frame[i].origin[1] ); + } + FS_Print( flat.f, "}\n" ); + } + + flat.bounds[0] = flat.bounds[1] = 0; + Mem_Set( &flat.frame, 0, sizeof( flat.frame )); + flat.angledframes = flat.normalframes = flat.mirrorframes = 0; // clear all +} + +void Skin_FindSequence( const char *name, rgbdata_t *pic ) +{ + uint headlen; + char num, header[10]; + + // create header from flat name + com.strncpy( header, name, 10 ); + headlen = com.strlen( name ); + + if( flat.animation != header[4] ) + { + // write animation + Skin_WriteSequence(); + flat.animation = header[4]; + } + + if( flat.animation == header[4] ) + { + // update bounds + if( flat.bounds[0] < pic->width ) flat.bounds[0] = pic->width; + if( flat.bounds[1] < pic->height) flat.bounds[1] = pic->height; + + // continue collect frames + if( headlen == 6 ) + { + num = header[5] - '0'; + if(num == 0) flat.normalframes++; // animation frame + if(num == 8) num = 0; // merge + flat.angledframes++; // angleframe stored + com.strncpy( flat.frame[num].name, header, 9 ); + flat.frame[num].width = pic->width; + flat.frame[num].height = pic->height; + flat.frame[num].origin[0] = pic->width>>1; // center + flat.frame[num].origin[1] = pic->height; // floor + flat.frame[num].xmirrored = false; + } + else if( headlen == 8 ) + { + // normal image + num = header[5] - '0'; + if(num == 8) num = 0; // merge + com.strncpy( flat.frame[num].name, header, 9 ); + flat.frame[num].width = pic->width; + flat.frame[num].height = pic->height; + flat.frame[num].origin[0] = pic->width>>1; // center + flat.frame[num].origin[1] = pic->height; // floor + flat.frame[num].xmirrored = false; + flat.angledframes++; // frame stored + + if( header[4] != header[6] ) + { + // mirrored groups + flat.mirrorframes++; + return; + } + + // mirrored image + num = header[7] - '0'; // angle it's a direct acess to group + if(num == 8) num = 0; // merge + com.strncpy( flat.frame[num].name, header, 9 ); + flat.frame[num].width = pic->width; + flat.frame[num].height = pic->height; + flat.frame[num].origin[0] = pic->width>>1; // center + flat.frame[num].origin[1] = pic->height; // floor + flat.frame[num].xmirrored = true; // it's mirror frame + flat.angledframes++; // frame stored + } + else Sys_Break( "Skin_CreateScript: invalid name %s\n", name ); // this never happens + } +} + +void Skin_ProcessScript( const char *wad, const char *name ) +{ + if( flat.in_progress ) + { + // finish script + Skin_WriteSequence(); + FS_Close( flat.f ); + flat.in_progress = false; + } + if( !flat.in_progress ) + { + // start from scratch + com.strncpy( flat.membername, name, 5 ); + flat.f = FS_Open( va( "%s/%s/%s.qc", gs_gamedir, wad, flat.membername ), "w" ); + flat.in_progress = true; + flat.bounds[0] = flat.bounds[1] = 0; + + // write description + FS_Print( flat.f,"//=======================================================================\n"); + FS_Printf( flat.f,"//\t\t\tCopyright XashXT Group %s ©\n", timestamp( TIME_YEAR_ONLY )); + FS_Print( flat.f,"//\t\t\twritten by Xash Miptex Decompiler\n"); + FS_Print( flat.f,"//=======================================================================\n"); + + // write sprite header + FS_Printf( flat.f, "\n$spritename\t%s.spr\n", flat.membername ); + FS_Print( flat.f, "$type\t\tfacing_upright\n" ); // constant + FS_Print( flat.f, "$texture\t\talphatest\n"); + FS_Print( flat.f, "$noresample\n" ); // comment this command by taste + } +} + +// close sequence for unexpected concessions +void Skin_FinalizeScript( void ) +{ + if( !flat.in_progress ) return; + + // finish script + Skin_WriteSequence(); + FS_Close( flat.f ); + flat.in_progress = false; +} + +void Skin_CreateScript( const char *name, rgbdata_t *pic ) +{ + string skinname, wadname; + + FS_ExtractFilePath( name, wadname ); // wad name + FS_FileBase( name, skinname ); // skinname + + if(com.strnicmp( skinname, flat.membername, 4 )) + Skin_ProcessScript( wadname, skinname ); + + if( flat.in_progress ) + Skin_FindSequence( skinname, pic ); +} + +/* +============ +ConvSKN +============ +*/ +bool ConvSKN( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.flt", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Skin_CreateScript( name, pic ); + Msg( "%s.flat\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvFLP +============ +*/ +bool ConvFLP( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.flt", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage(va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Msg( "%s.flat\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvFLT +============ +*/ +bool ConvFLT( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.flt", name ), buffer, filesize ); + + if( pic ) + { + string savedname, tempname, path; + + if( pic->flags & IMAGE_HAS_ALPHA ) + { + // insert '{' symbol for transparency textures + FS_ExtractFilePath( name, path ); + FS_FileBase( name, tempname ); + com.snprintf( savedname, MAX_STRING, "%s/{%s", path, tempname ); + } + else com.strncpy( savedname, name, MAX_STRING ); + + FS_SaveImage( va("%s/%s.%s", gs_gamedir, savedname, ext ), pic ); + Conv_CreateShader( savedname, pic, "flt", NULL, 0, 0 ); + Msg( "%s.flat\n", savedname ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvMID +============ +*/ +bool ConvMID( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + if(Conv_Mus2Mid( name, buffer, filesize )) + { + Msg( "%s.mus\n", name ); // echo to console + return true; + } + return false; +} \ No newline at end of file diff --git a/xtools/ripper/conv_image.c b/xtools/ripper/conv_image.c new file mode 100644 index 00000000..8052c597 --- /dev/null +++ b/xtools/ripper/conv_image.c @@ -0,0 +1,183 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// conv_image.c - convert various image type +//======================================================================= + +#include "ripper.h" + +/* +======================================================================== + +.WAL image format (Wally textures) + +======================================================================== +*/ +typedef struct wal_s +{ + char name[32]; + uint width, height; + uint offsets[4]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} wal_t; + +/* +============ +ConvWAL +============ +*/ +bool ConvWAL( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.wal", name ), buffer, filesize ); + + if( pic ) + { + wal_t *wal = (wal_t *)buffer; + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Conv_CreateShader( name, pic, ext, wal->animname, wal->flags, wal->contents ); + Msg("%s.wal\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============= +ConvJPG +============= +*/ +bool ConvJPG( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.jpg", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Conv_CreateShader( name, pic, ext, NULL, 0, 0 ); + Msg( "%s.jpg\n", name, ext ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============= +ConvBMP +============= +*/ +bool ConvBMP( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.bmp", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Conv_CreateShader( name, pic, ext, NULL, 0, 0 ); + Msg( "%s.bmp\n", name, ext ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============= +ConvPCX + +this also uses by SP2_ConvertFrame +============= +*/ +bool ConvPCX( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.pcx", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + // pcx images not required shader because it hud pics or sprite frames + Msg( "%s.pcx\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============= +ConvVTF +============= +*/ +bool ConvVTF( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.vtf", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Conv_CreateShader( name, pic, ext, NULL, 0, 0 ); + Msg( "%s.vtf\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvMIP +============ +*/ +bool ConvMIP( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.mip", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage( va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Conv_CreateShader( name, pic, ext, NULL, 0, 0 ); + Msg( "%s.mip\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvLMP +============ +*/ +bool ConvLMP( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + rgbdata_t *pic = FS_LoadImage( va( "#%s.lmp", name ), buffer, filesize ); + + if( pic ) + { + FS_SaveImage(va("%s/%s.%s", gs_gamedir, name, ext ), pic ); + Msg("%s.lmp\n", name ); // echo to console + FS_FreeImage( pic ); + return true; + } + return false; +} + +/* +============ +ConvRAW + +write to disk without conversions +============ +*/ +bool ConvRAW( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + if( FS_WriteFile( va("%s/%s.%s", gs_gamedir, name, ext ), buffer, filesize )) + { + Msg( "%s.%s\n", name, ext ); // echo to console + return true; + } + return false; +} \ No newline at end of file diff --git a/xtools/ripper/conv_main.c b/xtools/ripper/conv_main.c new file mode 100644 index 00000000..e8a69759 --- /dev/null +++ b/xtools/ripper/conv_main.c @@ -0,0 +1,355 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// conv_main.c - resource converter +//======================================================================= + +#include "ripper.h" + +bool write_qscsript; +int game_family; + +typedef struct convformat_s +{ + const char *path; + const char *ext; + bool (*convfunc)( const char *name, byte *buffer, size_t filesize, const char *ext ); + const char *ext2; // save format +} convformat_t; + +convformat_t convert_formats[] = +{ + {"%s.%s", "spr", ConvSPR, "bmp" }, // quake1/half-life sprite + {"%s.%s","spr32",ConvSPR, "tga" }, // spr32 sprite + {"%s.%s", "sp2", ConvSPR, "bmp" }, // quake2 sprite + {"%s.%s", "jpg", ConvJPG, "tga" }, // quake3 textures + {"%s.%s", "pcx", ConvPCX, "bmp" }, // quake2 pics + {"%s.%s", "flt", ConvFLT, "bmp" }, // doom1 textures + {"%s.%s", "flp", ConvFLP, "bmp" }, // doom1 menu pics + {"%s.%s", "mip", ConvMIP, "bmp" }, // Quake1/Half-Life textures + {"%s.%s", "lmp", ConvLMP, "bmp" }, // Quake1/Half-Life gfx + {"%s.%s", "wal", ConvWAL, "bmp" }, // Quake2 textures + {"%s.%s", "vtf", ConvVTF, "dds" }, // Half-Life 2 materials + {"%s.%s", "skn", ConvSKN, "bmp" }, // doom1 sprite models + {"%s.%s", "bsp", ConvBSP, "bmp" }, // Quake1\Half-Life map textures + {"%s.%s", "mus", ConvMID, "mid" }, // doom1\2 music files + {"%s.%s", "txt", ConvRAW, "txt" }, // (hidden) Xash-extract scripts + {"%s.%s", "dat", ConvRAW, "dat" }, // (hidden) Xash-extract progs + {NULL, NULL, NULL, NULL } // list terminator +}; + +convformat_t convert_formats32[] = +{ + {"%s.%s", "spr", ConvSPR, "tga" }, // quake1/half-life sprite + {"%s.%s","spr32",ConvSPR, "dds" }, // spr32 sprite + {"%s.%s", "sp2", ConvSPR, "dds" }, // quake2 sprite + {"%s.%s", "jpg", ConvJPG, "png" }, // quake3 textures + {"%s.%s", "bmp", ConvBMP, "dds" }, // 8-bit maps with alpha-cnahnel + {"%s.%s", "pcx", ConvPCX, "png" }, // quake2 pics + {"%s.%s", "flt", ConvFLT, "png" }, // doom1 textures + {"%s.%s", "flp", ConvFLP, "png" }, // doom1 menu pics + {"%s.%s", "mip", ConvMIP, "png" }, // Quake1/Half-Life textures + {"%s.%s", "lmp", ConvLMP, "png" }, // Quake1/Half-Life gfx + {"%s.%s", "wal", ConvWAL, "png" }, // Quake2 textures + {"%s.%s", "vtf", ConvVTF, "dds" }, // Half-Life 2 materials + {"%s.%s", "skn", ConvSKN, "png" }, // doom1 sprite models + {"%s.%s", "bsp", ConvBSP, "png" }, // Quake1\Half-Life map textures + {"%s.%s", "mus", ConvMID, "mid" }, // doom1\2 music files + {"%s.%s", "txt", ConvRAW, "txt" }, // (hidden) Xash-extract scripts + {"%s.%s", "dat", ConvRAW, "dat" }, // (hidden) Xash-extract progs + {NULL, NULL, NULL, NULL } // list terminator +}; + +bool CheckForExist( const char *path, const char *ext ) +{ + if( game_family == GAME_DOOM1 ) + { + string name, wad; + + // HACK: additional check for { symbol + FS_ExtractFilePath( path, wad ); + FS_FileBase( path, name ); + if(FS_FileExists( va( "%s/%s/{%s.%s", gs_gamedir, wad, name, ext ))) + return true; + // fallback to normal checking + } + return FS_FileExists( va( "%s/%s.%s", gs_gamedir, path, ext )); +} + +bool ConvertResource( byte *mempool, const char *filename, byte parms ) +{ + convformat_t *format; + const char *ext = FS_FileExtension( filename ); + bool anyformat = !com.stricmp(ext, "") ? true : false; + string path, convname, basename; + int filesize = 0; + byte *buffer = NULL; + + com.strncpy( convname, filename, sizeof( convname )); + FS_StripExtension( convname ); // remove extension if needed + + if( FS_CheckParm( "-force32" )) + format = convert_formats32; + else format = convert_formats; + + // now try all the formats in the selected list + for( ; format->convfunc; format++ ) + { + if( anyformat || !com.stricmp( ext, format->ext )) + { + if( CheckForExist( convname, format->ext2 )) return true; // already exist + com.sprintf( path, format->path, convname, format->ext ); + buffer = FS_LoadFile( path, &filesize ); + if( buffer && filesize > 0 ) + { + // this path may contains wadname: wadfile/lumpname + if( format->convfunc( convname, buffer, filesize, format->ext2 )) + { + Mem_Free( buffer ); // release buffer + return true; // converted ok + } + } + if( buffer ) Mem_Free( buffer ); // release buffer + } + } + FS_FileBase( convname, basename ); + MsgDev( D_WARN, "ConvertResource: couldn't load \"%s\"\n", basename ); + return false; +} + +void Conv_DetectGameType( void ) +{ + int i; + search_t *search; + + game_family = GAME_GENERIC; + + // first, searching for map format + AddMask( "maps/*.bsp" ); + + // search by mask + search = FS_Search( searchmask[0], true ); + for( i = 0; search && i < search->numfilenames; i++ ) + { + if(Conv_CheckMap( search->filenames[i] )) + break; + } + if( search ) Mem_Free( search ); + ClrMask(); + + // game family sucessfully determined + if( game_family != GAME_GENERIC ) return; + + AddMask( "*.wad" ); + + // search by mask + search = FS_Search( searchmask[0], true ); + for( i = 0; search && i < search->numfilenames; i++ ) + { + if(Conv_CheckWad( search->filenames[i] )) + break; + } + if( search ) Mem_Free( search ); + ClrMask(); +} + +void Conv_RunSearch( void ) +{ + search_t *search; + int i, j, k, imageflags; + + Conv_DetectGameType(); + + if( game_family ) Msg( "Game: %s family\n", game_names[game_family] ); + imageflags = IL_USE_LERPING; + write_qscsript = false; + + switch( game_family ) + { + case GAME_DOOM1: + search = FS_Search("*.wad", true ); + if( search ) + { + // make sure, that we stored all files from all wads + for( i = 0; i < search->numfilenames; i++ ) + { + AddMask(va("%s/*.flt", search->filenames[i])); + AddMask(va("%s/*.flp", search->filenames[i])); + AddMask(va("%s/*.skn", search->filenames[i])); + AddMask(va("%s/*.mus", search->filenames[i])); + } + Mem_Free( search ); + } + else + { + // just use global mask + AddMask( "*.skn" ); // Doom1 sprite models + AddMask( "*.flp" ); // Doom1 pics + AddMask( "*.flt" ); // Doom1 textures + AddMask( "*.mus" ); // Doom1 music + } + if( !FS_CheckParm( "-force32" )) + { + imageflags |= IL_KEEP_8BIT; + write_qscsript = true; + } + Image_Init( "Doom1", imageflags ); + break; + case GAME_HEXEN2: + case GAME_QUAKE1: + search = FS_Search("*.wad", true ); + // make sure, that we stored all files from all wads + for( i = 0; search && i < search->numfilenames; i++ ) + AddMask(va("%s/*.mip", search->filenames[i])); + if( search ) Mem_Free( search ); + else AddMask( "*.mip" ); + AddMask( "maps/*.bsp" ); // Quake1 textures from bsp + AddMask( "sprites/*.spr" ); // Quake1 sprites + AddMask( "sprites/*.spr32" ); // QW 32bit sprites + AddMask( "progs/*.spr32" ); // FTE, Darkplaces new sprites + AddMask( "progs/*.spr32" ); + AddMask( "progs/*.spr" ); + AddMask( "env/it/*.lmp" ); // Nehahra issues + AddMask( "gfx/*.mip" ); // Quake1 gfx/conchars + AddMask( "gfx/*.lmp" ); // Quake1 pics + AddMask( "*.sp32"); + AddMask( "*.spr" ); + if( !FS_CheckParm( "-force32" )) + { + imageflags |= IL_KEEP_8BIT; + write_qscsript = true; + } + Image_Init( "Quake1", imageflags ); + break; + case GAME_QUAKE2: + search = FS_Search("textures/*", true ); + + // find subdirectories + for( i = 0; search && i < search->numfilenames; i++ ) + { + if( com.strstr( search->filenames[i], "/." )) continue; + if( com.strstr( search->filenames[i], "/.." )) continue; + if( com.stricmp(FS_FileExtension( search->filenames[i] ), "" )) + continue; + AddMask( va("%s/*.wal", search->filenames[i])); + } + if( search ) Mem_Free( search ); + else AddMask( "*.wal" ); // Quake2 textures + AddMask( "*.sp2" ); // Quake2 sprites + AddMask( "*.pcx" ); // Quake2 sprites + AddMask( "sprites/*.sp2" ); // Quake2 sprites + AddMask( "pics/*.pcx"); // Quake2 pics + AddMask( "env/*.pcx" ); // Quake2 skyboxes + if( !FS_CheckParm( "-force32" )) + { + imageflags |= IL_KEEP_8BIT; + write_qscsript = true; + } + Image_Init( "Quake2", imageflags ); + break; + case GAME_RTCW: + case GAME_QUAKE3: + search = FS_Search("textures/*", true ); + // find subdirectories + for( i = 0; search && i < search->numfilenames; i++ ) + { + if( com.strstr( search->filenames[i], "/." )) continue; + if( com.strstr( search->filenames[i], "/.." )) continue; + if( com.stricmp(FS_FileExtension( search->filenames[i] ), "" )) + continue; + AddMask(va("%s/*.jpg", search->filenames[i])); + } + if( search ) Mem_Free( search ); + else AddMask( "*.jpg" ); // Quake3 textures + search = FS_Search("gfx/*", true ); + if( search ) + { + // find subdirectories + for( i = 0; i < search->numfilenames; i++ ) + AddMask(va("%s/*.jpg", search->filenames[i])); + Mem_Free( search ); + } + else AddMask( "gfx/*.jpg" ); // Quake3 hud pics + AddMask( "sprites/*.jpg" ); // Quake3 sprite frames + Image_Init( "Quake3", imageflags ); + break; + case GAME_HALFLIFE2: + case GAME_HALFLIFE2_BETA: + search = FS_Search("materials/*", true ); + + // hl2 like using multiple included sub-dirs + for( i = 0; search && i < search->numfilenames; i++ ) + { + search_t *search2; + if( com.strstr( search->filenames[i], "/." )) continue; + if( com.strstr( search->filenames[i], "/.." )) continue; + if( com.stricmp(FS_FileExtension( search->filenames[i] ), "" )) + continue; + search2 = FS_Search( va("%s/*", search->filenames[i] ), true ); + AddMask(va("%s/*.vtf", search->filenames[i])); + for( j = 0; search2 && j < search2->numfilenames; j++ ) + { + search_t *search3; + if( com.strstr( search2->filenames[j], "/." )) continue; + if( com.strstr( search2->filenames[j], "/.." )) continue; + if( com.stricmp(FS_FileExtension( search2->filenames[j] ), "" )) + continue; + search3 = FS_Search( va("%s/*", search2->filenames[j] ), true ); + AddMask(va("%s/*.vtf", search2->filenames[j])); + for( k = 0; search3 && k < search3->numfilenames; k++ ) + { + if( com.strstr( search3->filenames[k], "/." )) continue; + if( com.strstr( search3->filenames[k], "/.." )) continue; + if( com.stricmp(FS_FileExtension( search3->filenames[k] ), "" )) + continue; + AddMask(va("%s/*.vtf", search3->filenames[k])); + } + if( search3 ) Mem_Free( search3 ); + } + if( search2 ) Mem_Free( search2 ); + } + if( search ) Mem_Free( search ); + else AddMask( "*.jpg" ); // Quake3 textures + imageflags |= IL_DDS_HARDWARE; // because we want save textures into original DXT format + Image_Init( "Half-Life 2", imageflags ); + break; + case GAME_QUAKE4: + // i'm lazy today and i'm forget textures representation of IdTech4 :) + Sys_Break( "Sorry, nothing to decompile (not implemeneted yet)\n" ); + Image_Init( "Quake4", imageflags ); + break; + case GAME_HALFLIFE: + search = FS_Search( "*.wad", true ); + if( search ) + { + // find subdirectories + for( i = 0; i < search->numfilenames; i++ ) + { + AddMask(va("%s/*.mip", search->filenames[i])); + AddMask(va("%s/*.lmp", search->filenames[i])); + } + Mem_Free( search ); + } + else + { + // try to use generic mask + AddMask( "*.mip" ); + AddMask( "*.lmp" ); + } + AddMask( "maps/*.bsp" ); // textures from bsp + AddMask( "sprites/*.spr" ); // Half-Life sprites + if( !FS_CheckParm( "-force32" )) + { + imageflags |= IL_KEEP_8BIT; + write_qscsript = true; + } + Image_Init( "Half-Life", imageflags ); + break; + case GAME_XASH3D: + Sys_Break("Sorry, but i'm can't decompile himself\n" ); + Image_Init( "Xash3D", imageflags ); + break; + default: + Image_Init( "Generic", imageflags ); // everything else + break; + } +} \ No newline at end of file diff --git a/xtools/ripper/conv_shader.c b/xtools/ripper/conv_shader.c new file mode 100644 index 00000000..ad177e5e --- /dev/null +++ b/xtools/ripper/conv_shader.c @@ -0,0 +1,517 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// conv_shader.c - analyze and write texture shader +//======================================================================= + +#include "ripper.h" +#include "mathlib.h" +#include "utils.h" + +// q2 wal contents +#define CONTENTS_SOLID 0x00000001 // an eye is never valid in a solid +#define CONTENTS_WINDOW 0x00000002 // translucent, but not watery +#define CONTENTS_AUX 0x00000004 +#define CONTENTS_LAVA 0x00000008 +#define CONTENTS_SLIME 0x00000010 +#define CONTENTS_WATER 0x00000020 +#define CONTENTS_MIST 0x00000040 + +// remaining contents are non-visible, and don't eat brushes +#define CONTENTS_AREAPORTAL 0x00008000 +#define CONTENTS_PLAYERCLIP 0x00010000 +#define CONTENTS_MONSTERCLIP 0x00020000 +#define CONTENTS_CLIP (CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP) + +// currents can be added to any other contents, and may be mixed +#define CONTENTS_CURRENT_0 0x00040000 +#define CONTENTS_CURRENT_90 0x00080000 +#define CONTENTS_CURRENT_180 0x00100000 +#define CONTENTS_CURRENT_270 0x00200000 +#define CONTENTS_CURRENT_UP 0x00400000 +#define CONTENTS_CURRENT_DOWN 0x00800000 + +#define CONTENTS_ORIGIN 0x01000000 // removed before BSP'ing an entity + +#define CONTENTS_MONSTER 0x02000000 // should never be on a brush, only in game +#define CONTENTS_DEADMONSTER 0x04000000 +#define CONTENTS_DETAIL 0x08000000 // brushes to be added after vis leafs +#define CONTENTS_TRANSLUCENT 0x10000000 // auto set if any surface has trans +#define CONTENTS_LADDER 0x20000000 +#define CONTENTS_TRIGGER 0x40000000 // trigger + +#define SURF_LIGHT 0x00000001 // value will hold the light strength +#define SURF_SLICK 0x00000002 // effects game physics +#define SURF_SKY 0x00000004 // don't draw, but add to skybox +#define SURF_WARP 0x00000008 // turbulent water warp +#define SURF_TRANS33 0x00000010 // 33% opacity +#define SURF_TRANS66 0x00000020 // 66% opacity +#define SURF_FLOWING 0x00000040 // scroll towards angle +#define SURF_NODRAW 0x00000080 // don't bother referencing the texture +#define SURF_HINT 0x00000100 // make a primary bsp splitter +#define SURF_SKIP 0x00000200 // completely ignore, allowing non-closed brushes + +// xash 0.45 surfaces replacement table +#define SURF_MIRROR 0x00010000 // mirror surface +#define SURF_PORTAL 0x00020000 // portal surface +#define SURF_ALPHATEST 0x00040000 // alpha surface + +string animmap[256]; // should be enoguh +int animcount; // process counter +int num_anims; // shader total count +file_t *f; + +bool Conv_WriteShader( const char *shaderpath, const char *imagepath, rgbdata_t *p, float *rad, float scale, int flags, int contents ) +{ + file_t *f = NULL; + string qcname, qcpath; + string temp, lumpname; + string wadname, shadername; + int i, lightmap_stage = false; + + // write also wadlist.qc for xwad compiler + FS_ExtractFilePath( imagepath, temp ); + FS_FileBase( imagepath, lumpname ); + FS_FileBase( temp, qcname ); + FS_FileBase( temp, wadname ); + com.snprintf( shadername, MAX_STRING, "%s/%s", qcname, lumpname ); + FS_DefaultExtension( qcname, ".qc" ); + FS_DefaultExtension( wadname, ".wad" ); // check for wad later + com.snprintf( qcpath, MAX_STRING, "%s/%s/$%s", gs_gamedir, temp, qcname ); + + if( write_qscsript ) + { + if(FS_FileExists( qcpath )) + { + script_t *test; + token_t token; + + // already exist, search for current name + test = Com_OpenScript( qcpath, NULL, 0 ); + while( Com_ReadToken( test, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + { + if( !com.stricmp( token.string, "$mipmap" )) + { + Com_ReadToken( test, SC_PARSE_GENERIC, &token ); + if( !com.stricmp( token.string, lumpname )) + { + Com_CloseScript( test ); + goto check_shader; // already exist + } + } + Com_SkipRestOfLine( test ); + } + Com_CloseScript( test ); + f = FS_Open( qcpath, "a" ); // append + } + else + { + FS_StripExtension( qcname ); // no need anymore + f = FS_Open( qcpath, "w" ); // new file + // write description + FS_Print(f,"//=======================================================================\n"); + FS_Printf(f,"//\t\t\tCopyright XashXT Group %s ©\n", timestamp( TIME_YEAR_ONLY )); + FS_Print(f,"//\t\t\twritten by Xash Miptex Decompiler\n"); + FS_Print(f,"//=======================================================================\n"); + FS_Printf(f,"$wadname\t%s.wad\n\n", qcname ); + } + } + + if( f && p ) + { + FS_Printf( f,"$mipmap\t%s\t0 0 %d %d\n", lumpname, p->width, p->height ); + if( p->flags & IMAGE_HAS_LUMA ) // also add luma image if present + FS_Printf( f,"$mipmap\t%s_luma\t0 0 %d %d\n", lumpname, p->width, p->height ); + FS_Close( f ); // all done + } + +check_shader: + // invalid animation counter, kill it + if( num_anims == 1 ) animcount = num_anims = 0; + + // nothing to write + if( !flags && !contents && !num_anims && !(p->flags & IMAGE_HAS_LUMA)) + return false; + + if(FS_FileExists( shaderpath )) + { + script_t *test; + token_t token; + + // already exist, search for current shader + test = Com_OpenScript( shaderpath, NULL, 0 ); + while( Com_ReadToken( test, SC_ALLOW_NEWLINES|SC_ALLOW_PATHNAMES2, &token )) + { + if( !com.stricmp( token.string, shadername )) + { + Com_CloseScript( test ); + return false; // already exist + } + Com_SkipRestOfLine( test ); + } + Com_CloseScript( test ); + f = FS_Open( shaderpath, "a" ); // append + } + else + { + f = FS_Open( shaderpath, "w" ); // new file + // write description + FS_Print(f,"//=======================================================================\n"); + FS_Printf(f,"//\t\t\tCopyright XashXT Group %s ©\n", timestamp( TIME_YEAR_ONLY )); + FS_Print(f,"//\t\t\twritten by Xash Miptex Decompiler\n"); + FS_Print(f,"//=======================================================================\n"); + } + + FS_Printf( f, "\n%s\n{\n", shadername ); // write shadername + + if( contents & CONTENTS_CLIP && contents && CONTENTS_PLAYERCLIP ) + FS_Print( f, "\tsurfaceparm\tclip\n" ); + else if( contents & CONTENTS_MONSTERCLIP ) FS_Print( f, "\tsurfaceparm\tmonsterclip\n" ); + else if( contents & CONTENTS_PLAYERCLIP ) FS_Print( f, "\tsurfaceparm\tplayerclip\n" ); + else if( contents & CONTENTS_WINDOW ) FS_Print( f, "\tsurfaceparm\twindow\n" ); + else if( contents & CONTENTS_ORIGIN ) FS_Print( f, "\tsurfaceparm\torigin\n" ); + else if( contents & CONTENTS_TRANSLUCENT ) FS_Print( f, "\tsurfaceparm\ttranslucent\n" ); + else if( contents & CONTENTS_AREAPORTAL ) FS_Print( f, "\tsurfaceparm\tareaportal\n" ); + else if( contents & CONTENTS_TRIGGER ) FS_Print( f, "\tsurfaceparm\ttrigger\n" ); + else if( contents & CONTENTS_DETAIL ) FS_Print( f, "\tsurfaceparm\tdetail\n" ); + + if( flags & SURF_LIGHT ) + { + FS_Print( f, "\tsurfaceparm\tlight\n" ); + if(!VectorIsNull( rad )) FS_Printf(f, "\trad_color\t\t%.f %.f %.f\n", rad[0], rad[1], rad[2] ); + if( scale ) FS_Printf(f, "\trad_intensity\t%.f\n", scale ); + if( !num_anims ) + { + FS_Printf( f, "\t{\n\t\tmap\t%s\n\t}\n", shadername ); + lightmap_stage = true; + } + } + + if( flags & SURF_WARP ) + { + FS_Print( f, "\tsurfaceparm\tnoLightMap\n" ); + FS_Print( f, "\ttessSize\t\t64\n\n" ); + + // server relevant contents + if(contents & CONTENTS_WATER) + FS_Print( f, "\tsurfaceparm\twater\n" ); + else if(contents & CONTENTS_SLIME) + FS_Print( f, "\tsurfaceparm\tslime\n" ); + else if(contents & CONTENTS_LAVA) + FS_Print( f, "\tsurfaceparm\tlava\n" ); + else FS_Print( f, "\tsurfaceparm\twater\n" ); + FS_Print( f, "\tsurfaceparm\twarp\n" ); + + FS_Printf( f, "\t{\n\t\tmap\t%s\n", shadername ); // save basemap + if( flags & (SURF_TRANS33|SURF_TRANS66)) + { + FS_Print( f, "\t\tblendFunc\tGL_SRC_ALPHA\tGL_ONE_MINUS_SRC_ALPHA\n" ); + FS_Print( f, "\t\tAlphaGen\t\tvertex\n" ); + } + FS_Print( f, "\t\ttcGen\twarp\n\t}\n" ); // warp + lightmap_stage = false; + } + else if( flags & SURF_SKY ) FS_Print( f, "\tsurfaceparm\tsky\n" ); + else if( flags & SURF_HINT ) FS_Print( f, "\tsurfaceparm\thint\n" ); + else if( flags & SURF_SKIP ) FS_Print( f, "\tsurfaceparm\tskip\n" ); + else if( flags & SURF_MIRROR ) FS_Print( f, "\tsurfaceparm\tmirror\n" ); + else if( flags & (SURF_TRANS33|SURF_TRANS66)) + { + FS_Printf( f, "\t{\n\t\tmap\t%s\n\n", shadername ); // save basemap + FS_Print( f, "\t\tblendFunc\tGL_SRC_ALPHA\tGL_ONE_MINUS_SRC_ALPHA\n" ); + FS_Print( f, "\t\tAlphaGen\t\tentity\n\t}\n" ); + lightmap_stage = true; + } + else if( flags & SURF_ALPHATEST ) + { + FS_Printf( f, "\t{\n\t\tmap\t%s\n\n", shadername ); // save basemap + FS_Print( f, "\t\talphaFunc\tGL_GREATER 0.666f\t// id Software magic value\n" ); + FS_Print( f, "\t\tAlphaGen\tidentity\n\t}\n" ); + lightmap_stage = true; + } + else if( flags & SURF_NODRAW ) FS_Print( f, "\tsurfaceparm\tnull\n" ); + + if( num_anims ) + { + FS_Printf( f, "\t{\n\t\tAnimFrequency\t%i\n", animcount ); // #frames per second + for( i = 0; i < num_anims; i++ ) + FS_Printf( f, "\t\tmap\t\t%s\n", animmap[i] ); + FS_Printf( f, "\t}\n" ); // close section + lightmap_stage = true; + } + else if( p->flags & IMAGE_HAS_LUMA && !( flags & SURF_WARP )) + { + FS_Printf( f, "\t{\n\t\tmap\t%s\n\t}\n", shadername ); + lightmap_stage = true; + } + + if( lightmap_stage ) + { + FS_Print( f, "\t{\n\t\tmap\t$lightmap\n" ); // lightmap stage + FS_Print( f, "\t\tblendFunc\tfilter\n" ); + FS_Print( f, "\t}\n" ); + } + + if( p->flags & IMAGE_HAS_LUMA ) + { + if( num_anims ) + { + FS_Printf( f, "\t{\n\t\tAnimFrequency\t%i\n", animcount ); // #frames per second + for( i = 0; i < num_anims; i++ ) + FS_Printf( f, "\t\tmap\t\t%s_luma\n", animmap[i] ); // anim luma stage + FS_Printf( f, "\t\tblendFunc\t\tadd\n" ); + FS_Printf( f, "\t}\n" ); // close section + animcount = num_anims = 0; // done + } + else + { + FS_Printf( f, "\t{\n\t\tmap\t%s_luma\n", shadername ); // save luma + FS_Printf( f, "\t\tblendFunc\tadd\n" ); + if( flags & SURF_WARP ) FS_Print( f, "\t\ttcGen\twarp\n" ); + FS_Printf( f, "\t}\n" ); // close section + } + } + + FS_Print( f, "}\n" ); // close shader + FS_Close( f ); + + return true; +} + +/* +============= +generic flags extractor +============= +*/ +void Conv_ShaderGetFlags( const char *imagename, const char *shadername, const char *ext, int *flags, int *contents, const char *anim ) +{ + if( game_family == GAME_DOOM1 ) + { + num_anims = animcount = 0; // valid onlt for current frame so reset it + + if( !com.strnicmp( imagename, "sky", 3 )) *flags |= SURF_SKY; + if( com.stristr( imagename, "lit" )) *flags |= SURF_LIGHT; + + if( !com.strnicmp( "sw", imagename, 2 ) || !com.strnicmp( "{sw", imagename, 3 )) + { + // wallbuttons anim support + string temp1; + char c1; + int i, j; + + if( imagename[0] == '{' && imagename[4] == '_' ) + c1 = imagename[5], j = 5; + else if( imagename[3] == 's' ) + c1 = imagename[4], j = 4; + else if( imagename[4] == '_' ) + c1 = imagename[5], j = 5; + + if( c1 >= '0' && c1 <= '9' ); + else return; + + com.strncpy( temp1, imagename, MAX_STRING ); + for( i = 0; i < 10; i++ ) // Doom1 anim: 0 - 9 + { + if( !FS_FileExists( va( "%s/%s.flt", shadername, temp1 ))) break; + com.snprintf( animmap[animcount++], MAX_STRING, "%s/%s", shadername, temp1 ); + temp1[j]++; // increase symbol + } + num_anims = animcount; // can be dump now + } + } + else if( game_family == GAME_QUAKE1 || game_family == GAME_HALFLIFE ) + { + num_anims = animcount = 0; // valid onlt for current frame so reset it + + if( com.stristr( imagename, "water" )) + { + *contents |= CONTENTS_WATER; + *flags |= SURF_WARP; // liquids + } + else if( com.stristr( imagename, "slime" )) + { + *contents |= CONTENTS_SLIME; + *flags |= SURF_WARP; // liquids + } + else if( com.stristr( imagename, "lava" )) + { + *contents |= CONTENTS_LAVA; + *flags |= SURF_WARP; // liquids + } + + // search for keywords + if( !com.strnicmp( imagename, "sky", 3 )) *flags |= SURF_SKY; + else if( !com.strnicmp( imagename, "origin",6)) *contents |= CONTENTS_ORIGIN; + else if( !com.strnicmp( imagename, "clip", 4 )) *contents |= CONTENTS_CLIP; + else if( !com.strnicmp( imagename, "hint", 4 )) *flags |= SURF_HINT; + else if( !com.strnicmp( imagename, "skip", 4 )) *flags |= SURF_SKIP; + else if( !com.strnicmp( imagename, "null", 4 )) *flags |= SURF_NODRAW; + else if( !com.strnicmp( imagename, "translucent", 11 )) *contents |= CONTENTS_TRANSLUCENT; + else if( !com.strnicmp( imagename, "glass", 5 )) *flags |= SURF_TRANS66; + else if( !com.strnicmp( imagename, "mirror", 6 )) *flags |= SURF_MIRROR; + else if( !com.strnicmp( imagename, "portal", 6 )) *flags |= SURF_PORTAL; + else if( com.stristr( imagename, "trigger" )) *contents |= CONTENTS_TRIGGER; + else if( com.stristr( imagename, "lite" )) *flags |= SURF_LIGHT; + + // try to exctract contents and flags directly form mip-name + if( imagename[0] == '!' || imagename[0] == '*' ) *flags |= SURF_WARP; // liquids + else if( imagename[0] == '{' ) + { + *flags |= SURF_ALPHATEST; // grates + *contents |= CONTENTS_TRANSLUCENT; + } + else if( imagename[0] == '~' ) *flags |= SURF_LIGHT; // light definition + else if( imagename[0] == '+' ) + { + char c1 = imagename[1]; + string temp1; + int i; + + // HL: first map is off second map is on + if( c1 != '0' && c1 != 'a' && c1 != 'A' ) + { + return; + } + com.strncpy( temp1, imagename, MAX_STRING ); + for( i = 0; i < 10; i++ ) // Quake anim: 0 - 9 + { + if( !MipExist( va("%s/%s.mip", shadername, temp1 ))) break; + com.snprintf( animmap[animcount++], MAX_STRING, "%s/%s", shadername, temp1 ); + temp1[1]++; // increase symbol + } + if( i > 1 ) + { + num_anims = animcount; // can be dump now + return; + } + + if( c1 == 'a' || c1 == 'A' ) temp1[1] = '0'; + else if( c1 == '0' ) temp1[1] = 'A'; + + for( i = 0; i < 10; i++ ) // Quake anim A - K + { + if( !MipExist( va("%s/%s.mip", shadername, temp1 ))) break; + com.snprintf( animmap[animcount++], MAX_STRING, "%s/%s", shadername, temp1 ); + temp1[1]++; // increase symbol + } + num_anims = animcount; // can be dump now + } + } + else if( game_family == GAME_QUAKE2 ) + { + if( animcount && !com.strlen( anim )) + { + // end of chain, dump now + num_anims = animcount; + return; + } + + if( anim && com.strlen( anim )) + { + int i; + + if( animcount == 0 ) // add himself first + com.snprintf( animmap[animcount++], MAX_STRING, "%s/%s", shadername, imagename ); + + for( i = 0; i < animcount; i++ ) + { + if( !com.stricmp( animmap[i], anim )) + { + // chain is looped, dump now + num_anims = animcount; + return; + } + } + + // add next frame + if( animcount == i ) com.strncpy( animmap[animcount++], anim, MAX_STRING ); + } + // UNDONE: remove some flags + } +} + +bool Conv_CreateShader( const char *name, rgbdata_t *pic, const char *ext, const char *anim, int surf, int cnt ) +{ + string shadername, imagename; + string shaderpath, imagepath; + vec3_t radiocity = {0,0,0}; + float intencity = 0; + int flags = 0, contents = 0; + + // extract fodler name from path + FS_ExtractFilePath( name, shadername ); + FS_FileBase( shadername, shadername ); // remove "textures" from path + FS_FileBase( name, imagename ); + com.snprintf( shaderpath, MAX_STRING, "%s/scripts/%s.shader", gs_gamedir, shadername ); + com.strncpy( imagepath, name, MAX_STRING ); // full path + + flags |= surf; // .wal can transmit flags here + contents |= cnt; + + Conv_ShaderGetFlags( imagename, shadername, ext, &flags, &contents, anim ); + if( animcount >= 256 ) Sys_Break( "Conv_CreateShader: too many animations in shader\n" ); + + if( pic->flags & IMAGE_HAS_LUMA ) + { + // write luma image silently + Image_Process( &pic, 0, 0, IMAGE_MAKE_LUMA ); + FS_SaveImage( va("%s/%s_luma.%s", gs_gamedir, name, ext ), pic ); + } + if( flags & SURF_LIGHT ) + { + int j, texels; + byte *pal, *fin; + float scale; + + texels = pic->width * pic->height; + fin = pic->buffer; + pal = pic->palette; + + switch( pic->type ) + { + case PF_RGBA_32: + for( j = 0; j < texels; j++, fin += 4 ) + { + radiocity[0] += fin[0]; + radiocity[1] += fin[1]; + radiocity[2] += fin[2]; + } + break; + case PF_BGRA_32: + case PF_ABGR_64: + for( j = 0; j < texels; j++, fin += 4 ) + { + radiocity[0] += fin[2]; + radiocity[1] += fin[1]; + radiocity[2] += fin[0]; + } + break; + case PF_RGB_24: + for( j = 0; j < texels; j++, fin += 3 ) + { + radiocity[0] += fin[0]; + radiocity[1] += fin[1]; + radiocity[2] += fin[2]; + } + break; + case PF_INDEXED_24: + case PF_INDEXED_32: + for( j = 0; j < texels; j++ ) + { + radiocity[0] += pal[fin[j]+0]; + radiocity[1] += pal[fin[j]+1]; + radiocity[2] += pal[fin[j]+2]; + } + break; + default: + MsgDev( D_WARN, "Conv_CreateShader: %s can't calculate reflectivity\n", name ); + return Conv_WriteShader( shaderpath, imagepath, pic, vec3_origin, 0.0f, flags, contents ); + } + + for( j = 0; j < 3; j++ ) + radiocity[j] /= texels; + scale = ColorNormalize( radiocity, radiocity ); + VectorScale( radiocity, 255.0f, radiocity ); + intencity = texels * 255.0 / scale; // basic intensity value + } + return Conv_WriteShader( shaderpath, imagepath, pic, radiocity, intencity, flags, contents ); +} \ No newline at end of file diff --git a/xtools/ripper/conv_sprite.c b/xtools/ripper/conv_sprite.c new file mode 100644 index 00000000..e51cdd5c --- /dev/null +++ b/xtools/ripper/conv_sprite.c @@ -0,0 +1,454 @@ +//======================================================================= +// Copyright XashXT Group 2008 © +// conv_sprite.c - convert q1\q2\hl\spr32 sprites +//======================================================================= + +#include "ripper.h" +#include "byteorder.h" + +// sprite_decompiler.c +#define SPR_VP_PARALLEL_UPRIGHT 0 +#define SPR_FACING_UPRIGHT 1 +#define SPR_VP_PARALLEL 2 +#define SPR_ORIENTED 3 // all axis are valid +#define SPR_VP_PARALLEL_ORIENTED 4 + +#define SPR_NORMAL 0 // solid sprite +#define SPR_ADDITIVE 1 +#define SPR_INDEXALPHA 2 +#define SPR_ALPHTEST 3 +#define SPR_ADDGLOW 4 // same as additive, but without depthtest + +typedef struct frame_s +{ + char name[64]; // framename + + int width; // lumpwidth + int height; // lumpheight + int origin[2]; // monster origin +} frame_t; + +typedef struct group_s +{ + frame_t frame[64]; // max groupframes + float interval[64]; // frame intervals + int numframes; // num group frames; +} group_t; + +struct qcsprite_s +{ + group_t group[8]; // 64 frames for each group + frame_t frame[512]; // or 512 ungroupped frames + + int type; // rendering type + int texFormat; // half-life texture + bool truecolor; // spr32 + byte palette[256][4]; // shared palette + + int numgroup; // groups counter + int totalframes; // including group frames +} spr; + +/* +======================================================================== + +.SPR sprite file format + +======================================================================== +*/ +#define IDSPRQ1HEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSP" + +#define SPRITEQ1_VERSION 1 +#define SPRITEHL_VERSION 2 +#define SPRITE32_VERSION 32 + +typedef struct +{ + int ident; + int version; + int type; + int boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dspriteq1_t; + +typedef struct +{ + int ident; + int version; + int type; + int texFormat; // Half-Life stuff only + float boundingradius; // software rendering stuff + int width; + int height; + int numframes; + float beamlength; // software rendering stuff + synctype_t synctype; +} dspritehl_t; + +/* +======================================================================== + +.SP2 sprite file format + +======================================================================== +*/ +#define IDSPRQ2HEADER (('2'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDS2" + +#define SPRITEQ2_VERSION 2 + +typedef struct +{ + int width; + int height; + int origin_x; + int origin_y; // raster coordinates inside pic + char name[64]; // name of pcx file +} dsprframeq2_t; + +typedef struct +{ + int ident; + int version; + int numframes; + dsprframeq2_t frames[1]; // variable sized +} dspriteq2_t; + +_inline const char *SPR_RenderMode( void ) +{ + switch( spr.texFormat ) + { + case SPR_ADDGLOW: return "glow"; + case SPR_ALPHTEST: return "alphatest"; + case SPR_INDEXALPHA: return "indexalpha"; + case SPR_ADDITIVE: return "additive"; + case SPR_NORMAL: return "solid"; + default: return "normal"; + } +} + +_inline const char *SPR_RenderType( void ) +{ + switch( spr.type ) + { + case SPR_ORIENTED: return "oriented"; + case SPR_VP_PARALLEL: return "vp_parallel"; + case SPR_FACING_UPRIGHT: return "facing_upright"; + case SPR_VP_PARALLEL_UPRIGHT: return "vp_parallel_upright"; + case SPR_VP_PARALLEL_ORIENTED: return "vp_parallel_oriented"; + default: return "oriented"; + } +} + +void *SPR_ConvertFrame( const char *name, const char *ext, void *pin, int framenum, int groupframenum ) +{ + rgbdata_t *pix; + dspriteframe_t *pinframe; + string framename; + byte *fin, *fout; + int i, pixels, width, height; + + pinframe = (dspriteframe_t *)pin; + width = LittleLong( pinframe->width ); + height = LittleLong( pinframe->height ); + fin = (byte *)(pinframe + 1); + if( width <= 0 || height <= 0 ) + { + // NOTE: in Q1 existing sprites with blank frames + spr.totalframes--; + return (void *)((byte *)(pinframe + 1)); + } + + // extract sprite name from path + FS_FileBase( name, framename ); + com.strcat( framename, va("_%i", framenum )); + pixels = width * height; + if( spr.truecolor ) pixels *= 4; + + // frame exist, go next + if( FS_FileExists( va("%s/sprites/%s.%s", gs_gamedir, framename, ext ))) + return (void *)((byte *)(pinframe + 1) + pixels ); + + if( spr.truecolor ) + { + // HACKHACK: manually create rgbdata_t + pix = Mem_Alloc( basepool, sizeof( *pix )); + fout = Mem_Alloc( basepool, pixels ); + Mem_Copy( fout, fin, pixels ); + if( spr.texFormat >= SPR_INDEXALPHA ) + pix->flags |= IMAGE_HAS_ALPHA; + pix->type = PF_RGBA_32; + pix->width = width; + pix->height = height; + pix->size = pixels; + pix->depth = 1; + pix->numMips = 1; + pix->buffer = fout; + } + else + { + pix = FS_LoadImage( va( "#%s.spr", framename ), pin, pixels ); + Image_Process( &pix, 0, 0, IMAGE_PALTO24 ); + } + + if( groupframenum ) + { + i = groupframenum - 1; + com.strncpy( spr.group[spr.numgroup].frame[i].name, framename, MAX_STRING ); + spr.group[spr.numgroup].frame[i].origin[0] = -(float)LittleLong(pinframe->origin[0]); + spr.group[spr.numgroup].frame[i].origin[1] = (float)LittleLong(pinframe->origin[1]); + spr.group[spr.numgroup].frame[i].width = (float)LittleLong(pinframe->width); + spr.group[spr.numgroup].frame[i].height = (float)LittleLong(pinframe->height); + } + else + { + com.strncpy( spr.frame[framenum].name, framename, MAX_STRING ); + spr.frame[framenum].origin[0] = -(float)LittleLong(pinframe->origin[0]); + spr.frame[framenum].origin[1] = (float)LittleLong(pinframe->origin[1]); + spr.frame[framenum].width = (float)LittleLong(pinframe->width); + spr.frame[framenum].height = (float)LittleLong(pinframe->height); + } + + if( FS_CheckParm( "-force32" ) && spr.texFormat == SPR_INDEXALPHA ) + pix->flags &= ~IMAGE_HAS_ALPHA; + + FS_SaveImage( va("%s/sprites/%s.%s", gs_gamedir, framename, ext ), pix ); + FS_FreeImage( pix ); // free image + + // jump to next frame + return (void *)((byte *)(pinframe + 1) + pixels ); // no mipmap stored +} + +bool SP2_ConvertFrame( const char *name, const char *ext, int framenum ) +{ + byte *fin; + size_t filesize; + string barename; + + // store framename + FS_FileBase( name, spr.frame[framenum].name ); + com.strncpy( barename, name, MAX_STRING ); + FS_StripExtension( barename ); + + if( FS_FileExists( va("%s/%s.%s", gs_gamedir, barename, ext ))) + return true; + + fin = FS_LoadFile( name, &filesize ); + if( fin ) + { + ConvPCX( barename, fin, filesize, ext ); + Mem_Free( fin ); + return true; + } + return false; +} + +void *SPR_ConvertGroup( const char *name, const char *ext, void *pin, int framenum ) +{ + dspritegroup_t *pingroup; + int i, numframes; + dspriteinterval_t *pin_intervals; + void *ptemp; + + pingroup = (dspritegroup_t *)pin; + numframes = LittleLong( pingroup->numframes ); + pin_intervals = (dspriteinterval_t *)(pingroup + 1); + + for (i = 0; i < numframes; i++) + { + spr.group[spr.numgroup].interval[i] = LittleLong( pin_intervals->interval ); + pin_intervals++; + } + + // update global numframes + spr.group[spr.numgroup].numframes = numframes - 1; + ptemp = (void *)pin_intervals; + + for (i = 0; i < numframes; i++ ) + { + ptemp = SPR_ConvertFrame( name, ext, ptemp, framenum + i, i + 1 ); + } + spr.numgroup++; + return ptemp; +} + +bool SPR_WriteScript( const char *name, const char *ext ) +{ + string shortname; + int i, j; + file_t *f; + + FS_FileBase( name ,shortname ); + if( FS_FileExists( va("%s/sprites/%s.qc", gs_gamedir, shortname ))) + return true; + f = FS_Open( va("%s/sprites/%s.qc", gs_gamedir, shortname ), "w" ); + + if( !f ) + { + Msg( "Can't create qc-script \"%s.qc\"\n", shortname ); + return false; + } + + // description + FS_Printf(f,"//=======================================================================\n"); + FS_Printf(f,"//\t\t\tCopyright XashXT Group %s ©\n", timestamp( TIME_YEAR_ONLY )); + FS_Printf(f,"//\t\t\twritten by Xash Miptex Decompiler\n", name ); + FS_Printf(f,"//=======================================================================\n"); + + // sprite header + FS_Printf(f, "\n$spritename\t%s.spr\n", name ); + FS_Printf(f, "$type\t\t%s\n", SPR_RenderType()); + FS_Printf(f, "$texture\t\t%s\n\n", SPR_RenderMode()); + + // frames description + for( i = 0; i < spr.totalframes - spr.numgroup; i++) + { + FS_Printf(f,"$load\t\t%s.%s\n", spr.frame[i].name, ext ); + FS_Printf(f,"$frame\t\t0 0 %d %d", spr.frame[i].width, spr.frame[i].height ); + if(!spr.frame[i].origin[0] && !spr.frame[i].origin[1]) FS_Print(f, "\n" ); + else FS_Printf(f, " %.1f %d %d\n", 0.1f, spr.frame[i].origin[0],spr.frame[i].origin[1]); + } + + for( i = 0; i < spr.numgroup; i++ ) + { + FS_Print(f, "$group\n{\n" ); + for( j = 0; j < spr.group[i].numframes; j++) + { + FS_Printf(f,"\t$load\t\t%s.%s\n", spr.group[i].frame[j].name, ext ); + FS_Printf(f,"\t$frame\t\t0 0 %d %d", spr.group[i].frame[j].width, spr.group[i].frame[j].height ); + if( spr.group[i].interval[j] ) FS_Printf(f, " %g", spr.group[i].interval[j] ); + if(!spr.group[i].frame[j].origin[0] && !spr.group[i].frame[j].origin[1]) FS_Print(f, "\n" ); + else FS_Printf(f, " %d %d\n", spr.group[i].frame[j].origin[0],spr.group[i].frame[j].origin[1]); + } + FS_Print(f, "}\n" ); + } + + FS_Print(f,"\n" ); + FS_Close( f ); + Msg( "%s.spr\n", name ); // echo to console + return true; +} + +/* +============ +ConvSPR +============ +*/ +bool ConvSPR( const char *name, byte *buffer, size_t filesize, const char *ext ) +{ + dframetype_t *pframetype = NULL; + dspriteq2_t *pinq2; + dspritehl_t *pinhl; + dspriteq1_t *pin; + short *numi; + int i; + + // defaulting to q1 + pin = (dspriteq1_t *)buffer; + Mem_Set( &spr, 0, sizeof( spr )); + + switch( LittleLong( pin->ident )) + { + case IDSPRQ1HEADER: + switch( LittleLong( pin->version )) + { + case SPRITEQ1_VERSION: + spr.totalframes = LittleLong( pin->numframes ); + spr.texFormat = SPR_ALPHTEST; // constant + spr.type = LittleLong( pin->type ); + pframetype = (dframetype_t *)(pin + 1); + spr.truecolor = false; + break; + case SPRITE32_VERSION: + spr.totalframes = LittleLong( pin->numframes ); + spr.texFormat = SPR_ADDITIVE; // constant + spr.type = LittleLong( pin->type ); + + pframetype = (dframetype_t *)(pin + 1); + spr.truecolor = true; + break; + case SPRITEHL_VERSION: + pinhl = (dspritehl_t *)buffer; // reorganize header + spr.totalframes = LittleLong( pinhl->numframes ); + spr.texFormat = LittleLong( pinhl->texFormat ); + spr.type = LittleLong( pinhl->type ); + numi = (short *)(pinhl + 1); + spr.truecolor = false; + + if( LittleShort( *numi ) == 256 ) + { + byte *src = (byte *)(numi + 1); + rgbdata_t *pal = NULL; + + // install palette + switch( spr.texFormat ) + { + case SPR_INDEXALPHA: + case SPR_ALPHTEST: + pal = FS_LoadImage( "#transparent.pal", src, 768 ); + break; + case SPR_NORMAL: + case SPR_ADDGLOW: + case SPR_ADDITIVE: + default: + pal = FS_LoadImage( "#normal.pal", src, 768 ); + break; + } + + // get frametype for first frame + pframetype = (dframetype_t *)(src + 768); + //FS_FreeImage( pal ); // palette installed, no reason to keep this data + } + else + { + Msg("\"%s.spr\" have invalid palette size\n", name ); + return false; + } + break; + default: + Msg("\"%s.%s\" unknown version %i\n", name, "spr", LittleLong( pin->version )); + return false; + } + break; + case IDSPRQ2HEADER: + switch( LittleLong( pin->version )) + { + case SPRITEQ2_VERSION: + pinq2 = (dspriteq2_t *)buffer; + spr.totalframes = LittleLong( pinq2->numframes ); + spr.texFormat = SPR_ALPHTEST; // constants + spr.type = SPR_FWD_PARALLEL; + spr.truecolor = false; + for( i = 0; i < spr.totalframes; i++ ) + { + spr.frame[i].width = LittleLong( pinq2->frames[i].width ); + spr.frame[i].height = LittleLong( pinq2->frames[i].height ); + spr.frame[i].origin[0] = LittleLong( pinq2->frames[i].origin_x ); + spr.frame[i].origin[1] = LittleLong( pinq2->frames[i].origin_y ); + SP2_ConvertFrame( pinq2->frames[i].name, ext, i ); + } + break; + default: + Msg("\"%s.%s\" unknown version %i\n", name, "sp2", LittleLong( pin->version )); + return false; + } + break; + default: + Msg("\"%s\" have invalid header\n", name ); + return false; + } + + // .SPR save frames as normal images + for( i = 0; pframetype && i < spr.totalframes; i++ ) + { + frametype_t frametype = LittleLong( pframetype->type ); + + if( frametype == SPR_SINGLE ) + pframetype = (dframetype_t *)SPR_ConvertFrame( name, ext, (pframetype + 1), i, 0 ); + else pframetype = (dframetype_t *)SPR_ConvertGroup( name, ext, (pframetype + 1), i ); + } + return SPR_WriteScript( name, ext ); // write script file and done +} \ No newline at end of file diff --git a/xtools/ripper/ripper.h b/xtools/ripper/ripper.h new file mode 100644 index 00000000..52b70c63 --- /dev/null +++ b/xtools/ripper/ripper.h @@ -0,0 +1,78 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// ripper.h - Xash Miptex Decompiler +//======================================================================= +#ifndef BASECONVERTOR_H +#define BASECONVERTOR_H + +#include "xtools.h" + +extern stdlib_api_t com; +extern byte *basepool; +extern string gs_gamedir; +#define Sys_Error com.error +extern uint app_name; +extern bool write_qscsript; +extern int game_family; + +// common tools +bool Conv_CreateShader( const char *name, rgbdata_t *pic, const char *ext, const char *anim, int surf, int cnt ); +bool Conv_CheckMap( const char *mapname ); // for detect gametype +bool Conv_CheckWad( const char *wadname ); // for detect gametype +bool MipExist( const char *name ); + +typedef enum +{ + GAME_GENERIC = 0, + GAME_DOOM1, + GAME_DOOM3, + GAME_QUAKE1, + GAME_QUAKE2, + GAME_QUAKE3, + GAME_QUAKE4, + GAME_HALFLIFE, + GAME_HALFLIFE2, + GAME_HALFLIFE2_BETA, + GAME_HEXEN2, + GAME_XASH3D, + GAME_RTCW, +} game_family_t; + +static const char *game_names[] = +{ + "Unknown", + "Doom1\\Doom2", + "Doom3\\Quake4", + "Quake1", + "Quake2", + "Quake3", + "Quake4", + "Half-Life", + "Half-Life 2", + "Half-Life 2 Beta", + "Nexen 2", + "Xash 3D", + "Return To Castle Wolfenstein" +}; + +//===================================== +// convertor modules +//===================================== +bool ConvSPR( const char *name, byte *buffer, size_t filesize, const char *ext );// q1, q2, half-life and spr32 sprites +bool ConvPCX( const char *name, byte *buffer, size_t filesize, const char *ext );// pcx images (can use custom palette) +bool ConvFLT( const char *name, byte *buffer, size_t filesize, const char *ext );// Doom1 flat images (textures) +bool ConvFLP( const char *name, byte *buffer, size_t filesize, const char *ext );// Doom1 flat images (menu pics) +bool ConvJPG( const char *name, byte *buffer, size_t filesize, const char *ext );// Quake3 textures +bool ConvBMP( const char *name, byte *buffer, size_t filesize, const char *ext );// 8-bit maps with alpha-channel +bool ConvMIP( const char *name, byte *buffer, size_t filesize, const char *ext );// Quake1, Half-Life wad textures +bool ConvLMP( const char *name, byte *buffer, size_t filesize, const char *ext );// Quake1, Half-Life lump images +bool ConvFNT( const char *name, byte *buffer, size_t filesize, const char *ext );// Half-Life system fonts +bool ConvWAL( const char *name, byte *buffer, size_t filesize, const char *ext );// Quake2 textures +bool ConvVTF( const char *name, byte *buffer, size_t filesize, const char *ext );// Quake2 textures +bool ConvSKN( const char *name, byte *buffer, size_t filesize, const char *ext );// Doom1 sprite models +bool ConvBSP( const char *name, byte *buffer, size_t filesize, const char *ext );// Extract textures from bsp (q1\hl) +bool ConvMID( const char *name, byte *buffer, size_t filesize, const char *ext );// Doom1 music files (midi) +bool ConvRAW( const char *name, byte *buffer, size_t filesize, const char *ext );// write file without converting +bool ConvDAT( const char *name, byte *buffer, size_t filesize, const char *ext );// quakec progs into source.qc + +#endif//BASECONVERTOR_H \ No newline at end of file diff --git a/xtools/spritegen.c b/xtools/spritegen.c new file mode 100644 index 00000000..50dc8462 --- /dev/null +++ b/xtools/spritegen.c @@ -0,0 +1,641 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// sprlib.c - sprite generator +//======================================================================= + +#include "xtools.h" +#include "byteorder.h" +#include "utils.h" +#include "mathlib.h" + +#define MAX_FRAMES 256 +#define MAX_FRAME_DIM 512 +#define MIN_INTERVAL 0.001f +#define MAX_INTERVAL 64.0f + +dsprite_t sprite; +byte *spritepool; +byte *sprite_pal; +rgbdata_t *frame = NULL; +script_t *spriteqc = NULL; +string spriteoutname; +float frameinterval; +int framecount; +int origin_x; +int origin_y; +bool need_resample; +bool ignore_resample; +int resample_w; +int resample_h; + +struct +{ + frametype_t type; // single frame or group of frames + void *pdata; // either a dspriteframe_t or group info + float interval; // only used for frames in groups + int numgroupframes; // only used by group headers +} frames[MAX_FRAMES]; + +/* +============ +WriteFrame +============ +*/ +void WriteFrame( file_t *f, int framenum ) +{ + dspriteframe_t *pframe; + + pframe = (dspriteframe_t *)frames[framenum].pdata; + pframe->origin[0] = LittleLong( pframe->origin[0] ); + pframe->origin[1] = LittleLong( pframe->origin[1] ); + pframe->width = LittleLong (pframe->width); + pframe->height = LittleLong (pframe->height); + + // write frame as 32-bit indexed image + FS_Write(f, pframe, sizeof(*pframe)); + FS_Write(f, (byte *)(pframe + 1), pframe->height * pframe->width ); +} + +/* +============ +WriteSprite +============ +*/ +void WriteSprite( file_t *f ) +{ + int i; + short cnt = 256; + int curframe = 0; + int groupframe = 0; + + // calculate bounding radius + sprite.boundingradius = sqrt(((sprite.bounds[0]>>1) * (sprite.bounds[0]>>1)) + + ((sprite.bounds[1]>>1) * (sprite.bounds[1]>>1))); + + // write out the sprite header + SwapBlock((int *)&sprite, sizeof(dsprite_t)); + FS_Write( f, &sprite, sizeof(sprite)); + + // write out palette (768 bytes) + FS_Write( f, (void *)&cnt, sizeof(cnt)); + FS_Write( f, sprite_pal, cnt * 3 ); + + for (i = 0; i < sprite.numframes; i++) + { + FS_Write( f, &frames[curframe].type, sizeof(frames[curframe].type)); + if( frames[curframe].type == SPR_SINGLE ) + { + // single (non-grouped) frame + WriteFrame( f, curframe ); + curframe++; + } + else // angled or sequence + { + int j, numframes; + dspritegroup_t dsgroup; + float totinterval; + + groupframe = curframe; + curframe++; + numframes = frames[groupframe].numgroupframes; + + // set and write the group header + dsgroup.numframes = LittleLong( numframes ); + FS_Write( f, &dsgroup, sizeof(dsgroup)); + totinterval = 0.0f; // write the interval array + + for( j = 0; j < numframes; j++ ) + { + dspriteinterval_t temp; + + totinterval += frames[groupframe+1+j].interval; + temp.interval = LittleFloat(totinterval); + FS_Write(f, &temp, sizeof(temp)); + } + for( j = 0; j < numframes; j++ ) + { + WriteFrame(f, curframe); + curframe++; + } + } + } +} + +/* +============== +WriteSPRFile +============== +*/ +bool WriteSPRFile( void ) +{ + file_t *f; + uint i, groups = 0, grpframes = 0, sngframes = framecount; + + Com_CloseScript( spriteqc ); + if( sprite.numframes == 0 ) + { + MsgDev(D_WARN, "WriteSPRFile: ignoring blank sprite %s\n", spriteoutname ); + return false; + } + + f = FS_Open( spriteoutname, "wb" ); + Msg( "writing %s\n", spriteoutname ); + WriteSprite( f ); + FS_Close( f ); + + // release framebuffer + for( i = 0; i < framecount; i++) + { + if( frames[i].pdata ) Mem_Free( frames[i].pdata ); + if( frames[i].numgroupframes ) + { + groups++; + sngframes -= frames[i].numgroupframes; + grpframes += frames[i].numgroupframes; + } + } + + // display info about current sprite + if( groups ) + { + Msg("%d group%s,", groups, groups > 1 ? "s":"" ); + Msg(" contain %d frame%s\n", grpframes, grpframes > 1 ? "s":"" ); + } + if( sngframes - groups ) + Msg("%d ungrouped frame%s\n", sngframes - groups, (sngframes - groups) > 1 ? "s" : "" ); + return true; +} + +/* +=============== +Cmd_Type + +syntax: "$type preset" +=============== +*/ +void Cmd_Type( void ) +{ + token_t token; + + Com_ReadToken( spriteqc, 0, &token ); + + if( !com.stricmp( token.string, "vp_parallel_upright" )) sprite.type = SPR_FWD_PARALLEL_UPRIGHT; + else if( !com.stricmp( token.string, "facing_upright" )) sprite.type = SPR_FACING_UPRIGHT; + else if( !com.stricmp( token.string, "vp_parallel" )) sprite.type = SPR_FWD_PARALLEL; + else if( !com.stricmp( token.string, "oriented" )) sprite.type = SPR_ORIENTED; + else if( !com.stricmp( token.string, "vp_parallel_oriented")) sprite.type = SPR_FWD_PARALLEL_ORIENTED; + else sprite.type = SPR_FWD_PARALLEL; // default +} + +/* +=============== +Cmd_RenderMode + +syntax: "$rendermode preset" +=============== +*/ +void Cmd_RenderMode( void ) +{ + token_t token; + + Com_ReadToken( spriteqc, 0, &token ); + + if( !com.stricmp( token.string, "additive")) sprite.texFormat = SPR_ADDITIVE; + else if( !com.stricmp( token.string, "normal")) sprite.texFormat = SPR_NORMAL; + else if( !com.stricmp( token.string, "indexalpha")) sprite.texFormat = SPR_INDEXALPHA; + else if( !com.stricmp( token.string, "alphatest")) sprite.texFormat = SPR_ALPHTEST; + else if( !com.stricmp( token.string, "glow")) sprite.texFormat = SPR_ADDGLOW; + else sprite.texFormat = SPR_ADDITIVE; // default +} + +/* +============== +Cmd_FaceType + +syntax: "$facetype" +============== +*/ +void Cmd_FaceType( void ) +{ + token_t token; + + Com_ReadToken( spriteqc, 0, &token ); + + if( !com.stricmp( token.string, "normal")) sprite.facetype = SPR_SINGLE_FACE; + else if( !com.stricmp( token.string, "twoside")) sprite.facetype = SPR_DOUBLE_FACE; + else if( !com.stricmp( token.string, "xcross")) sprite.facetype = SPR_XCROSS_FACE; + else sprite.facetype = SPR_SINGLE_FACE; // default +} + + +/* +=============== +Cmd_Framerate + +syntax: "$framerate value" +=============== +*/ +void Cmd_Framerate( void ) +{ + float framerate; + + Com_ReadFloat( spriteqc, false, &framerate ); + if( framerate <= 0.0f ) return; // negative framerate not allowed + frameinterval = bound( MIN_INTERVAL, (1.0f/framerate), MAX_INTERVAL ); +} + +/* +=============== +Cmd_Resample + +syntax: "$resample " +=============== +*/ +void Cmd_Resample( void ) +{ + if(Com_ReadUlong( spriteqc, false, &resample_w )) + Com_ReadUlong( spriteqc, false, &resample_h ); + else resample_w = resample_h = 0; // Image_Resample will be found nearest pow2 + if( !ignore_resample ) need_resample = true; +} + +/* +=============== +Cmd_NoResample + +syntax: "$noresample" +=============== +*/ +void Cmd_NoResample( void ) +{ + ignore_resample = true; +} + +/* +=============== +Cmd_Load + +syntax "$load fire01.bmp" +=============== +*/ +void Cmd_Load( void ) +{ + string framename; + static byte base_pal[256*3]; + int flags = 0; + token_t token; + + Com_ReadString( spriteqc, SC_PARSE_GENERIC, framename ); + + if( frame ) FS_FreeImage( frame ); + frame = FS_LoadImage( framename, error_bmp, error_bmp_size ); + if( !frame ) Sys_Break( "unable to load %s\n", framename ); // no error.bmp, missing frame... + if( frame->type != PF_INDEXED_24 && frame->type != PF_INDEXED_32 ) + Sys_Break( "%s it's a not indexed image!\n", framename ); + Image_Process( &frame, 0, 0, IMAGE_PALTO24 ); + if( sprite.numframes == 0 ) Mem_Copy( base_pal, frame->palette, sizeof( base_pal )); + else if( memcmp( base_pal, frame->palette, sizeof( base_pal ))) + MsgDev( D_WARN, "Cmd_Load: %s doesn't share a pallette with the previous frame\n", framename ); + sprite_pal = (byte *)(&base_pal[0]); + + Msg( "grabbing %s\n", framename ); + while( Com_ReadToken( spriteqc, 0, &token )) + { + if( !com.stricmp(token.string, "flip_diagonal")) flags |= IMAGE_ROT_90; + else if( !com.stricmp( token.string, "flip_y" )) flags |= IMAGE_FLIP_Y; + else if( !com.stricmp( token.string, "flip_x" )) flags |= IMAGE_FLIP_X; + } + + // apply changes + Image_Process( &frame, 0, 0, flags ); +} + +/* +=============== +Cmd_Frame + +syntax "$frame xoffset yoffset width height " +=============== +*/ +void Cmd_Frame( void ) +{ + int x, y, xl, yl, xh, yh, w, h; + int org_x, org_y; + int pixels, linedelta; + bool resampled = false; + dspriteframe_t *pframe; + byte *fin, *plump; + + if( !frame || !frame->buffer ) Sys_Break( "frame not loaded\n" ); + if( framecount >= MAX_FRAMES ) Sys_Break( "too many frames in package\n" ); + pixels = frame->width * frame->height; + + Com_ReadLong( spriteqc, false, &xl ); + Com_ReadLong( spriteqc, false, &yl ); + Com_ReadLong( spriteqc, false, &w ); + Com_ReadLong( spriteqc, false, &h ); + + // merge bounds + if( xl <= 0 || xl > frame->width ) xl = 0; + if( yl <= 0 || yl > frame->width ) yl = 0; + if( w <= 0 || w > frame->width ) w = frame->width; + if( h <= 0 || h > frame->height ) h = frame->height; + + if((xl & 0x07)||(yl & 0x07)||(w & 0x07)||(h & 0x07) || need_resample ) + { + resampled = Image_Process( &frame, resample_w, resample_h, IMAGE_RESAMPLE ); + if( !resampled ) MsgDev( D_NOTE, "frame dimensions not multiples of 8\n" ); + } + if((w > MAX_FRAME_DIM) || (h > MAX_FRAME_DIM)) + Sys_Break( "sprite has a dimension longer than %d\n", MAX_FRAME_DIM ); + + // get interval + if( Com_ReadFloat( spriteqc, false, &frames[framecount].interval )) + { + frames[framecount].interval = bound( MIN_INTERVAL, frames[framecount].interval, MAX_INTERVAL ); + + } + else if( frameinterval != 0 ) + { + frames[framecount].interval = frameinterval; + } + else + { + // use default interval + frames[framecount].interval = (float)0.05f; + } + + if(Com_ReadLong( spriteqc, false, &org_x )) + { + org_x = -org_x; // inverse x coord + Com_ReadLong( spriteqc, false, &org_y ); + } + else if((origin_x != 0) && (origin_y != 0)) + { + // write shared origin + org_x = -origin_x; + org_y = origin_y; + } + else + { + // use center of image + org_x = -(w>>1); + org_y = h>>1; + } + + // merge all sprite info + if( need_resample && resampled ) + { + // check for org[n] == size[n] and org[n] == size[n]/2 + // another cases will be not changed + if( org_x == -w ) org_x = -frame->width; + else if( org_x == -(w>>1)) org_x = -frame->width>>1; + if( org_y == h ) org_y = frame->height; + else if( org_y == (h>>1)) org_y = frame->height>>1; + w = frame->width; + h = frame->height; + } + + xh = xl + w; + yh = yl + h; + + plump = (byte *)Mem_Alloc( spritepool, sizeof(dspriteframe_t) + (w * h)); + pframe = (dspriteframe_t *)plump; + frames[framecount].pdata = plump; + frames[framecount].type = SPR_SINGLE; + + pframe->origin[0] = org_x; + pframe->origin[1] = org_y; + pframe->width = w; + pframe->height = h; + + // adjust maxsize + if(w > sprite.bounds[0]) sprite.bounds[0] = w; + if(h > sprite.bounds[1]) sprite.bounds[1] = h; + + plump = (byte *)(pframe + 1); // move pointer + fin = frame->buffer + yl * frame->width + xl; + linedelta = frame->width - w; + + // apply scissor to source + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + *plump++ = *fin++; + fin += linedelta; + } + framecount++; +} + +/* +============== +Cmd_SpriteUnknown + +syntax: "blabla" +============== +*/ +void Cmd_SpriteUnknown( const char *token ) +{ + MsgDev( D_WARN, "Cmd_SpriteUnknown: bad command %s\n", token ); + Com_SkipRestOfLine( spriteqc ); +} + +/* +=============== +Cmd_Group + +syntax: +$group or $angled +{ + $load fire01.bmp + $frame xoffset yoffset width height + $load fire02.bmp + $frame xoffset yoffset width height " + $load fire03.bmp + $frame xoffset yoffset width height +} +=============== +*/ +void Cmd_Group( bool angled ) +{ + int groupframe; + int is_started = 0; + token_t token; + + groupframe = framecount++; + + frames[groupframe].type = angled ? SPR_ANGLED : SPR_GROUP; + need_resample = resample_w = resample_h = 0; // invalidate resample for group + frames[groupframe].numgroupframes = 0; + + while( 1 ) + { + if( !Com_ReadToken( spriteqc, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + { + if( is_started ) Sys_Break( "missing }\n" ); + break; + } + + if( !com.stricmp( token.string, "{" )) is_started = 1; + else if( !com.stricmp( token.string, "groupstart" )) is_started = 1; + else if( !com.stricmp( token.string, "groupend" )) break; // end of group + else if( !com.stricmp( token.string, "}" )) break; // end of group + else if( !com.stricmp( token.string, "$framerate" )) Cmd_Framerate(); + else if( !com.stricmp( token.string, "$resample" )) Cmd_Resample(); + else if( !com.stricmp( token.string, "$frame" )) + { + Cmd_Frame(); + frames[groupframe].numgroupframes++; + } + else if( !com.stricmp( token.string, "$load" )) Cmd_Load(); + else if( is_started ) Sys_Break( "missing }\n" ); + else Cmd_SpriteUnknown( token.string ); // skip unknown commands + } + if( frames[groupframe].numgroupframes == 0 ) + { + // don't create blank groups, rewind frames + framecount--, sprite.numframes--; + MsgDev( D_WARN, "Cmd_Group: remove blank group\n" ); + } + else if( angled && frames[groupframe].numgroupframes != 8 ) + { + // don't create blank groups, rewind frames + framecount--, sprite.numframes--; + MsgDev(D_WARN, "Cmd_Group: Remove angled group with invalid framecount\n" ); + } + + // back to single frames, invalidate resample + need_resample = resample_w = resample_h = 0; +} + +/* +=============== +Cmd_Origin + +syntax: $origin "x_pos y_pos" +=============== +*/ +static void Cmd_Origin( void ) +{ + Com_ReadLong( spriteqc, false, &origin_x ); + Com_ReadLong( spriteqc, false, &origin_y ); +} + + +/* +=============== +Cmd_Rand + +syntax: $rand +=============== +*/ +static void Cmd_Rand( void ) +{ + sprite.synctype = ST_RAND; +} + +/* +============== +Cmd_Spritename + +syntax: "$spritename outname" +============== +*/ +void Cmd_Spritename( void ) +{ + Com_ReadString( spriteqc, false, spriteoutname ); + FS_DefaultExtension( spriteoutname, ".spr" ); +} + +void ResetSpriteInfo( void ) +{ + // set default sprite parms + spriteoutname[0] = 0; + FS_FileBase( gs_filename, spriteoutname ); + FS_DefaultExtension( spriteoutname, ".spr" ); + + Mem_Set( &sprite, 0, sizeof( sprite )); + Mem_Set( frames, 0, sizeof( frames )); + framecount = origin_x = origin_y = 0; + frameinterval = 0.0f; + + sprite.bounds[0] = -9999; + sprite.bounds[1] = -9999; + sprite.ident = IDSPRITEHEADER; + sprite.version = SPRITE_VERSION; + sprite.type = SPR_FWD_PARALLEL; + sprite.facetype = SPR_SINGLE_FACE; + sprite.synctype = ST_SYNC; +} + +/* +=============== +ParseScript +=============== +*/ +bool ParseSpriteScript( void ) +{ + token_t token; + + ResetSpriteInfo(); + + while( 1 ) + { + if( !Com_ReadToken( spriteqc, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) break; + if( !com.stricmp( token.string, "$spritename" )) Cmd_Spritename(); + else if( !com.stricmp( token.string, "$noresample" )) Cmd_NoResample(); + else if( !com.stricmp( token.string, "$resample" )) Cmd_Resample(); + else if( !com.stricmp( token.string, "$texture" )) Cmd_RenderMode(); + else if( !com.stricmp( token.string, "$facetype" )) Cmd_FaceType(); + else if( !com.stricmp( token.string, "$origin" )) Cmd_Origin(); + else if( !com.stricmp( token.string, "$rand" )) Cmd_Rand(); + else if( !com.stricmp( token.string, "$load" )) Cmd_Load(); + else if( !com.stricmp( token.string, "$type" )) Cmd_Type(); + else if( !com.stricmp( token.string, "$frame" )) + { + Cmd_Frame(); + sprite.numframes++; + } + else if( !com.stricmp( token.string, "$group" )) + { + Cmd_Group( false ); + sprite.numframes++; + } + else if( !com.stricmp( token.string, "$angled" )) + { + Cmd_Group( true ); + sprite.numframes++; + } + else if( !Com_ValidScript( token.string, QC_SPRITEGEN )) return false; + else Cmd_SpriteUnknown( token.string ); + } + return true; +} + +bool CompileCurrentSprite( const char *name ) +{ + if( name ) com.strcpy( gs_filename, name ); + FS_DefaultExtension( gs_filename, ".qc" ); + spriteqc = Com_OpenScript( gs_filename, NULL, 0 ); + + if( spriteqc ) + { + if(!ParseSpriteScript()) + return false; + return WriteSPRFile(); + } + + Msg( "%s not found\n", gs_filename ); + return false; +} + +bool CompileSpriteModel( byte *mempool, const char *name, byte parms ) +{ + if( mempool ) spritepool = mempool; + else + { + MsgDev( D_ERROR, "can't allocate memory pool.\nAbort compilation\n"); + return false; + } + return CompileCurrentSprite( name ); +} \ No newline at end of file diff --git a/xtools/studio.c b/xtools/studio.c new file mode 100644 index 00000000..809e447c --- /dev/null +++ b/xtools/studio.c @@ -0,0 +1,2641 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// studio.c - half-life model compiler +//======================================================================= + +#include "mdllib.h" +#include "matrix_lib.h" + +bool cdset; +bool ignore_errors; + +byte *studiopool; +byte *pData; +byte *pStart; +string modeloutname; + +int numrep; +int flip_triangles; +int dump_hboxes; +int gflags; + +int cdtextureset; +int clip_texcoords; +int split_textures; +int numseq; +int nummirrored; +int numani; +int numxnodes; +int numrenamedbones; +int totalframes = 0; +int xnode[100][100]; +int numbones; +int numhitboxes; +int numhitgroups; +int numattachments; +int numtextures; +int numskinref; +int numskinfamilies; +int numbonecontrollers; +int used[MAXSTUDIOTRIANGLES]; +int skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index +int striptris[MAXSTUDIOTRIANGLES+2]; +int stripverts[MAXSTUDIOTRIANGLES+2]; +short commands[MAXSTUDIOTRIANGLES * 13]; +int neighbortri[MAXSTUDIOTRIANGLES][3]; +int neighboredge[MAXSTUDIOTRIANGLES][3]; +int numtexturegroups; +int numtexturelayers[32]; +int numtexturereps[32]; +int texturegroup[32][32][32]; +int numcommandnodes; +int nummodels; +int numbodyparts; +int stripcount; +int numcommands; +int allverts; +int alltris; + +float totalseconds = 0; +float default_scale; +float scale_up; +float defaultzrotation; +float normal_blend; +float zrotation; +float gamma; + +vec3_t adjust; +vec3_t bbox[2]; +vec3_t cbox[2]; +vec3_t eyeposition; +vec3_t defaultadjust; + +char filename[MAX_SYSPATH]; +char line[MAX_SYSPATH]; +string pivotname[64]; // names of the pivot points +string defaulttexture[64]; +string sourcetexture[64]; +string mirrored[MAXSTUDIOSRCBONES]; + +s_mesh_t *pmesh; +token_t token; +dstudiohdr_t *phdr; +dstudioseqhdr_t *pseqhdr; +script_t *studioqc; +s_sequencegroup_t sequencegroup; +s_trianglevert_t (*triangles)[3]; +s_model_t *model[MAXSTUDIOMODELS]; +s_bbox_t hitbox[MAXSTUDIOSRCBONES]; +s_texture_t *texture[MAXSTUDIOSKINS]; +s_hitgroup_t hitgroup[MAXSTUDIOSRCBONES]; +s_sequence_t *sequence[MAXSTUDIOSEQUENCES]; +s_bodypart_t bodypart[MAXSTUDIOBODYPARTS]; +s_bonefixup_t bonefixup[MAXSTUDIOSRCBONES]; +s_bonetable_t bonetable[MAXSTUDIOSRCBONES]; +s_attachment_t attachment[MAXSTUDIOSRCBONES]; +s_renamebone_t renamedbone[MAXSTUDIOSRCBONES]; +s_animation_t *panimation[MAXSTUDIOSEQUENCES*MAXSTUDIOBLENDS]; // each sequence can have 16 blends +s_bonecontroller_t bonecontroller[MAXSTUDIOSRCBONES]; + +/* +============ +WriteBoneInfo +============ +*/ +void WriteBoneInfo( void ) +{ + int i, j; + dstudiobone_t *pbone; + dstudiobonecontroller_t *pbonecontroller; + dstudioattachment_t *pattachment; + dstudiobbox_t *pbbox; + + // save bone info + pbone = (dstudiobone_t *)pData; + phdr->numbones = numbones; + phdr->boneindex = (pData - pStart); + + for (i = 0; i < numbones; i++) + { + com.strncpy( pbone[i].name, bonetable[i].name, 32 ); + pbone[i].parent = bonetable[i].parent; + pbone[i].flags = bonetable[i].flags; + pbone[i].value[0] = bonetable[i].pos[0]; + pbone[i].value[1] = bonetable[i].pos[1]; + pbone[i].value[2] = bonetable[i].pos[2]; + pbone[i].value[3] = bonetable[i].rot[0]; + pbone[i].value[4] = bonetable[i].rot[1]; + pbone[i].value[5] = bonetable[i].rot[2]; + pbone[i].scale[0] = bonetable[i].posscale[0]; + pbone[i].scale[1] = bonetable[i].posscale[1]; + pbone[i].scale[2] = bonetable[i].posscale[2]; + pbone[i].scale[3] = bonetable[i].rotscale[0]; + pbone[i].scale[4] = bonetable[i].rotscale[1]; + pbone[i].scale[5] = bonetable[i].rotscale[2]; + } + + pData += numbones * sizeof( dstudiobone_t ); + ALIGN( pData ); + + // map bonecontroller to bones + for (i = 0; i < numbones; i++) + for (j = 0; j < 6; j++) + pbone[i].bonecontroller[j] = -1; + + for (i = 0; i < numbonecontrollers; i++) + { + j = bonecontroller[i].bone; + + switch( bonecontroller[i].type & STUDIO_TYPES ) + { + case STUDIO_X: + pbone[j].bonecontroller[0] = i; + break; + case STUDIO_Y: + pbone[j].bonecontroller[1] = i; + break; + case STUDIO_Z: + pbone[j].bonecontroller[2] = i; + break; + case STUDIO_XR: + pbone[j].bonecontroller[3] = i; + break; + case STUDIO_YR: + pbone[j].bonecontroller[4] = i; + break; + case STUDIO_ZR: + pbone[j].bonecontroller[5] = i; + break; + default: + MsgDev( D_WARN, "unknown bonecontroller type %i\n", bonecontroller[i].type ); + pbone[j].bonecontroller[0] = i; //default + break; + } + } + + // save bonecontroller info + pbonecontroller = (dstudiobonecontroller_t *)pData; + phdr->numbonecontrollers = numbonecontrollers; + phdr->bonecontrollerindex = (pData - pStart); + + for (i = 0; i < numbonecontrollers; i++) + { + pbonecontroller[i].bone = bonecontroller[i].bone; + pbonecontroller[i].index = bonecontroller[i].index; + pbonecontroller[i].type = bonecontroller[i].type; + pbonecontroller[i].start = bonecontroller[i].start; + pbonecontroller[i].end = bonecontroller[i].end; + } + + pData += numbonecontrollers * sizeof( dstudiobonecontroller_t ); + ALIGN( pData ); + + // save attachment info + pattachment = (dstudioattachment_t *)pData; + phdr->numattachments = numattachments; + phdr->attachmentindex = (pData - pStart); + + for (i = 0; i < numattachments; i++) + { + pattachment[i].bone = attachment[i].bone; + VectorCopy( attachment[i].org, pattachment[i].org ); + } + + pData += numattachments * sizeof( dstudioattachment_t ); + ALIGN( pData ); + + // save bbox info + pbbox = (dstudiobbox_t *)pData; + phdr->numhitboxes = numhitboxes; + phdr->hitboxindex = (pData - pStart); + + for (i = 0; i < numhitboxes; i++) + { + pbbox[i].bone = hitbox[i].bone; + pbbox[i].group = hitbox[i].group; + VectorCopy( hitbox[i].bmin, pbbox[i].bbmin ); + VectorCopy( hitbox[i].bmax, pbbox[i].bbmax ); + } + + pData += numhitboxes * sizeof( dstudiobbox_t ); + ALIGN( pData ); +} + +/* +============ +WriteSequenceInfo +============ +*/ +void WriteSequenceInfo( void ) +{ + int i, j; + + dstudioseqgroup_t *pseqgroup; + dstudioseqdesc_t *pseqdesc; + dstudioseqdesc_t *pbaseseqdesc; + dstudioevent_t *pevent; + dstudiopivot_t *ppivot; + byte *ptransition; + + + // save sequence info + pseqdesc = (dstudioseqdesc_t *)pData; + pbaseseqdesc = pseqdesc; + phdr->numseq = numseq; + phdr->seqindex = (pData - pStart); + pData += numseq * sizeof( dstudioseqdesc_t ); + + for (i = 0; i < numseq; i++, pseqdesc++) + { + com.strncpy( pseqdesc->label, sequence[i]->name, 32 ); + pseqdesc->numframes = sequence[i]->numframes; + pseqdesc->fps = sequence[i]->fps; + pseqdesc->flags = sequence[i]->flags; + + pseqdesc->numblends = sequence[i]->numblends; + pseqdesc->blendtype[0] = sequence[i]->blendtype[0]; + pseqdesc->blendtype[1] = sequence[i]->blendtype[1]; + pseqdesc->blendstart[0] = sequence[i]->blendstart[0]; + pseqdesc->blendend[0] = sequence[i]->blendend[0]; + pseqdesc->blendstart[1] = sequence[i]->blendstart[1]; + pseqdesc->blendend[1] = sequence[i]->blendend[1]; + + pseqdesc->motiontype = sequence[i]->motiontype; + pseqdesc->motionbone = 0; + VectorCopy( sequence[i]->linearmovement, pseqdesc->linearmovement ); + + pseqdesc->seqgroup = sequence[i]->seqgroup; + pseqdesc->animindex = sequence[i]->animindex; + pseqdesc->activity = sequence[i]->activity; + pseqdesc->actweight = sequence[i]->actweight; + + VectorCopy( sequence[i]->bmin, pseqdesc->bbmin ); + VectorCopy( sequence[i]->bmax, pseqdesc->bbmax ); + + pseqdesc->entrynode = sequence[i]->entrynode; + pseqdesc->exitnode = sequence[i]->exitnode; + pseqdesc->nodeflags = sequence[i]->nodeflags; + + totalframes += sequence[i]->numframes; + totalseconds += sequence[i]->numframes / sequence[i]->fps; + + // save events + pevent = (dstudioevent_t *)pData; + pseqdesc->numevents = sequence[i]->numevents; + pseqdesc->eventindex = (pData - pStart); + pData += pseqdesc->numevents * sizeof( dstudioevent_t ); + + for (j = 0; j < sequence[i]->numevents; j++) + { + pevent[j].frame = sequence[i]->event[j].frame - sequence[i]->frameoffset; + pevent[j].event = sequence[i]->event[j].event; + Mem_Copy( pevent[j].options, sequence[i]->event[j].options, sizeof( pevent[j].options )); + } + + ALIGN( pData ); + + // save pivots + ppivot = (dstudiopivot_t *)pData; + pseqdesc->numpivots = sequence[i]->numpivots; + pseqdesc->pivotindex = (pData - pStart); + pData += pseqdesc->numpivots * sizeof( dstudiopivot_t ); + + for (j = 0; j < sequence[i]->numpivots; j++) + { + VectorCopy( sequence[i]->pivot[j].org, ppivot[j].org ); + ppivot[j].start = sequence[i]->pivot[j].start - sequence[i]->frameoffset; + ppivot[j].end = sequence[i]->pivot[j].end - sequence[i]->frameoffset; + } + + ALIGN( pData ); + } + + // save sequence group info + pseqgroup = (dstudioseqgroup_t *)pData; + phdr->numseqgroups = 1; + phdr->seqgroupindex = (pData - pStart); + pData += sizeof( dstudioseqgroup_t ); + + ALIGN( pData ); + + com.strncpy( pseqgroup->label, sequencegroup.label, 32 ); + com.strncpy( pseqgroup->name, sequencegroup.name, 64 ); + + // save transition graph + ptransition = (byte *)pData; + phdr->numtransitions = numxnodes; + phdr->transitionindex = (pData - pStart); + pData += numxnodes * numxnodes * sizeof( byte ); + ALIGN( pData ); + for (i = 0; i < numxnodes; i++) + for(j = 0; j < numxnodes; j++) + *ptransition++ = xnode[i][j]; + +} + +/* +============ +WriteAnimations +============ +*/ +byte *WriteAnimations( byte *pData, byte *pStart, int group ) +{ + int i, j, k, q, n; + + dstudioanim_t *panim; + dstudioanimvalue_t *panimvalue; + + for (i = 0; i < numseq; i++) + { + if (sequence[i]->seqgroup == group) + { + // save animations + panim = (dstudioanim_t *)pData; + sequence[i]->animindex = (pData - pStart); + pData += sequence[i]->numblends * numbones * sizeof( dstudioanim_t ); + ALIGN( pData ); + + panimvalue = (dstudioanimvalue_t *)pData; + for (q = 0; q < sequence[i]->numblends; q++) + { + // save animation value info + for (j = 0; j < numbones; j++) + { + for (k = 0; k < 6; k++) + { + if (sequence[i]->panim[q]->numanim[j][k] == 0) + { + panim->offset[k] = 0; + } + else + { + panim->offset[k] = ((byte *)panimvalue - (byte *)panim); + for (n = 0; n < sequence[i]->panim[q]->numanim[j][k]; n++) + { + panimvalue->value = sequence[i]->panim[q]->anim[j][k][n].value; + panimvalue++; + } + } + } + if (((byte *)panimvalue - (byte *)panim) > 0xffff) + Sys_Break("sequence \"%s\" is greater than 64K\n", sequence[i]->name ); + panim++; + } + } + + pData = (byte *)panimvalue; + ALIGN( pData ); + } + } + return pData; +} + +/* +============ +WriteTextures +============ +*/ +void WriteTextures( void ) +{ + dstudiotexture_t *ptexture; + short *pref; + int i, j; + + // save bone info + ptexture = (dstudiotexture_t *)pData; + phdr->numtextures = numtextures; + phdr->textureindex = (pData - pStart); + pData += numtextures * sizeof( dstudiotexture_t ); + ALIGN( pData ); + + phdr->skinindex = (pData - pStart); + phdr->numskinref = numskinref; + phdr->numskinfamilies = numskinfamilies; + pref = (short *)pData; + + for (i = 0; i < phdr->numskinfamilies; i++) + { + for (j = 0; j < phdr->numskinref; j++) + { + *pref = skinref[i][j]; + pref++; + } + } + + pData = (byte *)pref; + ALIGN( pData ); + + phdr->texturedataindex = (pData - pStart); // must be the end of the file! + + for (i = 0; i < numtextures; i++) + { + com.strncpy( ptexture[i].name, texture[i]->name, 64 ); + ptexture[i].flags = texture[i]->flags; + ptexture[i].width = texture[i]->skinwidth; + ptexture[i].height = texture[i]->skinheight; + ptexture[i].index = (pData - pStart); + Mem_Copy( pData, texture[i]->pdata, texture[i]->size ); + pData += texture[i]->size; + } + + ALIGN( pData ); +} + +/* +============ +WriteModel +============ +*/ +void WriteModel( void ) +{ + dstudiobodyparts_t *pbodypart; + dstudiomodel_t *pmodel; + byte *pbone; + vec3_t *pvert; + vec3_t *pnorm; + dstudiomesh_t *pmesh; + s_trianglevert_t *psrctri; + int i, j, k, cur; + int total_tris = 0; + int total_strips = 0; + + pbodypart = (dstudiobodyparts_t *)pData; + phdr->numbodyparts = numbodyparts; + phdr->bodypartindex = (pData - pStart); + pData += numbodyparts * sizeof( dstudiobodyparts_t ); + + pmodel = (dstudiomodel_t *)pData; + pData += nummodels * sizeof( dstudiomodel_t ); + + for (i = 0, j = 0; i < numbodyparts; i++) + { + com.strncpy( pbodypart[i].name, bodypart[i].name, 64 ); + pbodypart[i].nummodels = bodypart[i].nummodels; + pbodypart[i].base = bodypart[i].base; + pbodypart[i].modelindex = ((byte *)&pmodel[j]) - pStart; + j += bodypart[i].nummodels; + } + + ALIGN( pData ); + cur = (int)pData; + + for( i = 0; i < nummodels; i++ ) + { + int normmap[MAXSTUDIOVERTS]; + int normimap[MAXSTUDIOVERTS]; + int n = 0; + + com.strncpy( pmodel[i].name, model[i]->name, 64 ); + + // remap normals to be sorted by skin reference + for (j = 0; j < model[i]->nummesh; j++) + { + for (k = 0; k < model[i]->numnorms; k++) + { + if (model[i]->normal[k].skinref == model[i]->pmesh[j]->skinref) + { + normmap[k] = n; + normimap[n] = k; + model[i]->pmesh[j]->numnorms++; + n++; + } + } + } + + // save vertice bones + pbone = pData; + pmodel[i].numverts = model[i]->numverts; + pmodel[i].vertinfoindex = (pData - pStart); + for (j = 0; j < pmodel[i].numverts; j++) *pbone++ = model[i]->vert[j].bone; + + ALIGN( pbone ); + + // save normal bones + pmodel[i].numnorms = model[i]->numnorms; + pmodel[i].norminfoindex = ((byte *)pbone - pStart); + for (j = 0; j < pmodel[i].numnorms; j++) *pbone++ = model[i]->normal[normimap[j]].bone; + + ALIGN( pbone ); + pData = pbone; + + // save group info + pvert = (vec3_t *)pData; + pData += model[i]->numverts * sizeof( vec3_t ); + pmodel[i].vertindex = ((byte *)pvert - pStart); + + ALIGN( pData ); + pnorm = (vec3_t *)pData; + + pData += model[i]->numnorms * sizeof( vec3_t ); + pmodel[i].normindex = ((byte *)pnorm - pStart); + ALIGN( pData ); + + for (j = 0; j < model[i]->numverts; j++) + { + VectorCopy( model[i]->vert[j].org, pvert[j] ); + } + + for (j = 0; j < model[i]->numnorms; j++) + { + VectorCopy( model[i]->normal[normimap[j]].org, pnorm[j] ); + } + + Msg( "vertices %6s (%d vertices, %d normals)\n", memprint( pData - (char *)cur ), model[i]->numverts, model[i]->numnorms); + cur = (int)pData; + + // save mesh info + pmesh = (dstudiomesh_t *)pData; + pmodel[i].nummesh = model[i]->nummesh; + pmodel[i].meshindex = (pData - pStart); + pData += pmodel[i].nummesh * sizeof( dstudiomesh_t ); + + ALIGN( pData ); + + total_tris = 0; + total_strips = 0; + + for (j = 0; j < model[i]->nummesh; j++) + { + int numCmdBytes; + byte *pCmdSrc; + + pmesh[j].numtris = model[i]->pmesh[j]->numtris; + pmesh[j].skinref = model[i]->pmesh[j]->skinref; + pmesh[j].numnorms = model[i]->pmesh[j]->numnorms; + + psrctri = (s_trianglevert_t *)(model[i]->pmesh[j]->triangle); + for (k = 0; k < pmesh[j].numtris * 3; k++) + { + psrctri->normindex = normmap[psrctri->normindex]; + psrctri++; + } + numCmdBytes = BuildTris( model[i]->pmesh[j]->triangle, model[i]->pmesh[j], &pCmdSrc ); + + pmesh[j].triindex = (pData - pStart); + Mem_Copy( pData, pCmdSrc, numCmdBytes ); + pData += numCmdBytes; + + ALIGN( pData ); + total_tris += pmesh[j].numtris; + total_strips += numcommandnodes; + } + Msg("mesh %6s (%d tris, %d strips)\n", memprint( pData - ( char *)cur ), total_tris, total_strips ); + cur = (int)pData; + } +} + + +/* +============ +WriteMDLFile +============ +*/ +void WriteMDLFile( void ) +{ + int total = 0; + + pStart = Kalloc( FILEBUFFER ); + FS_StripExtension( modeloutname ); + + if( split_textures ) + { + // write textures out to a separate file + string texname; + + com.snprintf( texname, MAX_STRING, "%sT.mdl", modeloutname ); + Msg( "writing %s:\n", texname ); + + phdr = (dstudiohdr_t *)pStart; + phdr->ident = IDSTUDIOHEADER; + phdr->version = STUDIO_VERSION; + + pData = (byte *)phdr + sizeof( dstudiohdr_t ); + + WriteTextures( ); + + phdr->length = pData - pStart; + Msg( "textures %6s\n", memprint( phdr->length )); + + FS_WriteFile( texname, pStart, phdr->length ); + + Mem_Set( pStart, 0, phdr->length ); + pData = pStart; + } + + // write the model output file + FS_DefaultExtension( modeloutname, ".mdl" ); + + Msg ("---------------------\n"); + Msg ("writing %s:\n", modeloutname); + + phdr = (dstudiohdr_t *)pStart; + phdr->ident = IDSTUDIOHEADER; + phdr->version = STUDIO_VERSION; + com.strncpy( phdr->name, modeloutname, 64 ); + + VectorCopy( eyeposition, phdr->eyeposition ); + VectorCopy( bbox[0], phdr->min ); + VectorCopy( bbox[1], phdr->max ); + VectorCopy( cbox[0], phdr->bbmin ); + VectorCopy( cbox[1], phdr->bbmax ); + + phdr->flags = gflags; + pData = (byte *)phdr + sizeof( dstudiohdr_t ); + + WriteBoneInfo(); + Msg("bones %6s (%d)\n", memprint( pData - pStart - total ), numbones ); + total = pData - pStart; + pData = WriteAnimations( pData, pStart, 0 ); + + WriteSequenceInfo( ); + Msg("sequences %6s (%d frames) [%d:%02d]\n", memprint( pData - pStart - total ), totalframes, (int)totalseconds / 60, (int)totalseconds % 60 ); + total = pData - pStart; + + WriteModel(); + Msg("models %6s\n", memprint( pData - pStart - total )); + total = pData - pStart; + + if( !split_textures ) + { + WriteTextures(); + Msg("textures %6s\n", memprint( pData - pStart - total )); + } + + phdr->length = pData - pStart; + FS_WriteFile( modeloutname, pStart, phdr->length ); + Msg("total %6s\n", memprint( phdr->length )); +} + +/* +============ +SimplifyModel +============ +*/ +void SimplifyModel( void ) +{ + int i, j, k; + int n, m, q; + vec3_t *defaultpos[MAXSTUDIOSRCBONES]; + vec3_t *defaultrot[MAXSTUDIOSRCBONES]; + int iError = 0; + + OptimizeAnimations(); + ExtractMotion(); + MakeTransitions(); + + // find used bones + for (i = 0; i < nummodels; i++) + { + for (k = 0; k < MAXSTUDIOSRCBONES; k++) model[i]->boneref[k] = 0; + for (j = 0; j < model[i]->numverts; j++) model[i]->boneref[model[i]->vert[j].bone] = 1; + for (k = 0; k < MAXSTUDIOSRCBONES; k++) + { + // tag parent bones as used if child has been used + if (model[i]->boneref[k]) + { + n = model[i]->node[k].parent; + while (n != -1 && !model[i]->boneref[n]) + { + model[i]->boneref[n] = 1; + n = model[i]->node[n].parent; + } + } + } + } + + // rename model bones if needed + for( i = 0; i < nummodels; i++ ) + { + for (j = 0; j < model[i]->numbones; j++) + { + for (k = 0; k < numrenamedbones; k++) + { + if (!com.strcmp( model[i]->node[j].name, renamedbone[k].from)) + { + com.strncpy( model[i]->node[j].name, renamedbone[k].to, 64 ); + break; + } + } + } + } + + // union of all used bones + numbones = 0; + for( i = 0; i < nummodels; i++ ) + { + for( k = 0; k < MAXSTUDIOSRCBONES; k++ ) model[i]->boneimap[k] = -1; + for( j = 0; j < model[i]->numbones; j++ ) + { + if (model[i]->boneref[j]) + { + k = findNode( model[i]->node[j].name ); + if( k == -1 ) + { + // create new bone + k = numbones; + com.strncpy( bonetable[k].name, model[i]->node[j].name, sizeof(bonetable[k].name)); + if ((n = model[i]->node[j].parent) != -1) + bonetable[k].parent = findNode( model[i]->node[n].name ); + else bonetable[k].parent = -1; + bonetable[k].bonecontroller = 0; + bonetable[k].flags = 0; + + // set defaults + defaultpos[k] = Kalloc( MAXSTUDIOANIMATIONS * sizeof( vec3_t )); + defaultrot[k] = Kalloc( MAXSTUDIOANIMATIONS * sizeof( vec3_t )); + + for( n = 0; n < MAXSTUDIOANIMATIONS; n++ ) + { + VectorCopy( model[i]->skeleton[j].pos, defaultpos[k][n] ); + VectorCopy( model[i]->skeleton[j].rot, defaultrot[k][n] ); + } + + VectorCopy( model[i]->skeleton[j].pos, bonetable[k].pos ); + VectorCopy( model[i]->skeleton[j].rot, bonetable[k].rot ); + numbones++; + } + else + { + // double check parent assignments + n = model[i]->node[j].parent; + if (n != -1) n = findNode( model[i]->node[n].name ); + m = bonetable[k].parent; + + if (n != m) + { + MsgDev( D_ERROR, "SimplifyModel: illegal parent bone replacement in model \"%s\"\n\t\"%s\" has \"%s\", previously was \"%s\"\n", model[i]->name, model[i]->node[j].name, (n != -1) ? bonetable[n].name : "ROOT", (m != -1) ? bonetable[m].name : "ROOT" ); + iError++; + } + } + + model[i]->bonemap[j] = k; + model[i]->boneimap[k] = j; + } + } + } + + // handle errors + if( iError && !ignore_errors ) Sys_Break("Unexpected errors, compilation aborted\n"); + if( numbones >= MAXSTUDIOBONES ) Sys_Break( "Too many bones in model: used %d, max %d\n", numbones, MAXSTUDIOBONES ); + + // rename sequence bones if needed + for( i = 0; i < numseq; i++ ) + { + for( j = 0; j < sequence[i]->panim[0]->numbones; j++ ) + { + for (k = 0; k < numrenamedbones; k++) + { + if (!com.strcmp( sequence[i]->panim[0]->node[j].name, renamedbone[k].from)) + { + com.strncpy( sequence[i]->panim[0]->node[j].name, renamedbone[k].to, 64 ); + break; + } + } + } + } + + // map each sequences bone list to master list + for( i = 0; i < numseq; i++ ) + { + for (k = 0; k < MAXSTUDIOSRCBONES; k++) sequence[i]->panim[0]->boneimap[k] = -1; + for (j = 0; j < sequence[i]->panim[0]->numbones; j++) + { + k = findNode( sequence[i]->panim[0]->node[j].name ); + + if (k == -1) sequence[i]->panim[0]->bonemap[j] = -1; + else + { + char *szAnim = "ROOT"; + char *szNode = "ROOT"; + + // whoa, check parent connections! + if (sequence[i]->panim[0]->node[j].parent != -1) szAnim = sequence[i]->panim[0]->node[sequence[i]->panim[0]->node[j].parent].name; + if (bonetable[k].parent != -1) szNode = bonetable[bonetable[k].parent].name; + + if (strcmp(szAnim, szNode)) + { + MsgDev( D_ERROR, "SimplifyModel: illegal parent bone replacement in sequence \"%s\"\n\t\"%s\" has \"%s\", reference has \"%s\"\n", sequence[i]->name, sequence[i]->panim[0]->node[j].name, szAnim, szNode ); + iError++; + } + sequence[i]->panim[0]->bonemap[j] = k; + sequence[i]->panim[0]->boneimap[k] = j; + } + } + } + + //handle errors + if( iError && !ignore_errors ) Sys_Break("unexpected errors, compilation aborted\n" ); + + // link bonecontrollers + for (i = 0; i < numbonecontrollers; i++) + { + for (j = 0; j < numbones; j++) + { + if (com.stricmp( bonecontroller[i].name, bonetable[j].name) == 0) + break; + } + if (j >= numbones) + { + MsgDev( D_WARN, "SimplifyModel: unknown bonecontroller link '%s'\n", bonecontroller[i].name ); + j = numbones - 1; + } + bonecontroller[i].bone = j; + } + + // link attachments + for (i = 0; i < numattachments; i++) + { + for (j = 0; j < numbones; j++) + { + if (stricmp( attachment[i].bonename, bonetable[j].name) == 0) + break; + } + if (j >= numbones) + { + MsgDev( D_WARN, "SimplifyModel: unknown attachment link '%s'\n", attachment[i].bonename ); + j = numbones - 1; + } + attachment[i].bone = j; + } + + // relink model + for (i = 0; i < nummodels; i++) + { + for (j = 0; j < model[i]->numverts; j++) model[i]->vert[j].bone = model[i]->bonemap[model[i]->vert[j].bone]; + for (j = 0; j < model[i]->numnorms; j++) model[i]->normal[j].bone = model[i]->bonemap[model[i]->normal[j].bone]; + } + + // set hitgroups + for (k = 0; k < numbones; k++) bonetable[k].group = -9999; + for (j = 0; j < numhitgroups; j++) + { + for (k = 0; k < numbones; k++) + { + if (strcmpi( bonetable[k].name, hitgroup[j].name) == 0) + { + bonetable[k].group = hitgroup[j].group; + break; + } + } + if (k >= numbones) + { + MsgDev( D_WARN, "SimplifyModel: cannot find bone %s for hitgroup %d\n", hitgroup[j].name, hitgroup[j].group ); + continue; + } + } + + for (k = 0; k < numbones; k++) + { + if (bonetable[k].group == -9999) + { + if (bonetable[k].parent != -1) + bonetable[k].group = bonetable[bonetable[k].parent].group; + else bonetable[k].group = 0; + } + } + + if (numhitboxes == 0) + { + // find intersection box volume for each bone + for (k = 0; k < numbones; k++) + { + for (j = 0; j < 3; j++) + { + bonetable[k].bmin[j] = 0.0; + bonetable[k].bmax[j] = 0.0; + } + } + // try all the connect vertices + for (i = 0; i < nummodels; i++) + { + vec3_t p; + for (j = 0; j < model[i]->numverts; j++) + { + VectorCopy( model[i]->vert[j].org, p ); + k = model[i]->vert[j].bone; + + if (p[0] < bonetable[k].bmin[0]) bonetable[k].bmin[0] = p[0]; + if (p[1] < bonetable[k].bmin[1]) bonetable[k].bmin[1] = p[1]; + if (p[2] < bonetable[k].bmin[2]) bonetable[k].bmin[2] = p[2]; + if (p[0] > bonetable[k].bmax[0]) bonetable[k].bmax[0] = p[0]; + if (p[1] > bonetable[k].bmax[1]) bonetable[k].bmax[1] = p[1]; + if (p[2] > bonetable[k].bmax[2]) bonetable[k].bmax[2] = p[2]; + } + } + + // add in all your children as well + for (k = 0; k < numbones; k++) + { + if ((j = bonetable[k].parent) != -1) + { + if (bonetable[k].pos[0] < bonetable[j].bmin[0]) bonetable[j].bmin[0] = bonetable[k].pos[0]; + if (bonetable[k].pos[1] < bonetable[j].bmin[1]) bonetable[j].bmin[1] = bonetable[k].pos[1]; + if (bonetable[k].pos[2] < bonetable[j].bmin[2]) bonetable[j].bmin[2] = bonetable[k].pos[2]; + if (bonetable[k].pos[0] > bonetable[j].bmax[0]) bonetable[j].bmax[0] = bonetable[k].pos[0]; + if (bonetable[k].pos[1] > bonetable[j].bmax[1]) bonetable[j].bmax[1] = bonetable[k].pos[1]; + if (bonetable[k].pos[2] > bonetable[j].bmax[2]) bonetable[j].bmax[2] = bonetable[k].pos[2]; + } + } + + for (k = 0; k < numbones; k++) + { + if (bonetable[k].bmin[0] < bonetable[k].bmax[0] - 1 && bonetable[k].bmin[1] < bonetable[k].bmax[1] - 1 && bonetable[k].bmin[2] < bonetable[k].bmax[2] - 1) + { + hitbox[numhitboxes].bone = k; + hitbox[numhitboxes].group = bonetable[k].group; + VectorCopy( bonetable[k].bmin, hitbox[numhitboxes].bmin ); + VectorCopy( bonetable[k].bmax, hitbox[numhitboxes].bmax ); + + if( dump_hboxes ) // remove this?? + { + Msg( "$hbox %d \"%s\" %.2f %.2f %.2f %.2f %.2f %.2f\n", hitbox[numhitboxes].group, bonetable[hitbox[numhitboxes].bone].name, hitbox[numhitboxes].bmin[0], hitbox[numhitboxes].bmin[1], hitbox[numhitboxes].bmin[2], hitbox[numhitboxes].bmax[0], hitbox[numhitboxes].bmax[1], hitbox[numhitboxes].bmax[2] ); + } + numhitboxes++; + } + } + } + else + { + for (j = 0; j < numhitboxes; j++) + { + for (k = 0; k < numbones; k++) + { + if (strcmpi( bonetable[k].name, hitbox[j].name) == 0) + { + hitbox[j].bone = k; + break; + } + } + if (k >= numbones) + { + MsgDev( D_WARN, "SimplifyModel: cannot find bone %s for bbox\n", hitbox[j].name ); + continue; + } + } + } + + // relink animations + for (i = 0; i < numseq; i++) + { + vec3_t *origpos[MAXSTUDIOSRCBONES]; + vec3_t *origrot[MAXSTUDIOSRCBONES]; + + for (q = 0; q < sequence[i]->numblends; q++) + { + // save pointers to original animations + for (j = 0; j < sequence[i]->panim[q]->numbones; j++) + { + origpos[j] = sequence[i]->panim[q]->pos[j]; + origrot[j] = sequence[i]->panim[q]->rot[j]; + } + + for (j = 0; j < numbones; j++) + { + if ((k = sequence[i]->panim[0]->boneimap[j]) >= 0) + { + // link to original animations + sequence[i]->panim[q]->pos[j] = origpos[k]; + sequence[i]->panim[q]->rot[j] = origrot[k]; + } + else + { + // link to dummy animations + sequence[i]->panim[q]->pos[j] = defaultpos[j]; + sequence[i]->panim[q]->rot[j] = defaultrot[j]; + } + } + } + } + + // find scales for all bones + for (j = 0; j < numbones; j++) + { + for (k = 0; k < 6; k++) + { + float minv, maxv, scale; + + if (k < 3) + { + minv = -128.0f; + maxv = 128.0f; + } + else + { + minv = -M_PI / 8.0f; + maxv = M_PI / 8.0f; + } + + for( i = 0; i < numseq; i++ ) + { + for( q = 0; q < sequence[i]->numblends; q++ ) + { + for( n = 0; n < sequence[i]->numframes; n++ ) + { + float v; + switch( k ) + { + case 0: + case 1: + case 2: + v = ( sequence[i]->panim[q]->pos[j][n][k] - bonetable[j].pos[k] ); + break; + case 3: + case 4: + case 5: + v = ( sequence[i]->panim[q]->rot[j][n][k-3] - bonetable[j].rot[k-3] ); + if (v >= M_PI) v -= M_PI * 2; + if (v < -M_PI) v += M_PI * 2; + break; + } + if (v < minv) minv = v; + if (v > maxv) maxv = v; + } + } + } + + if (minv < maxv) + { + if (-minv> maxv) scale = minv / -32768.0; + else scale = maxv / 32767; + } + else scale = 1.0 / 32.0; + + switch(k) + { + case 0: + case 1: + case 2: + bonetable[j].posscale[k] = scale; + break; + case 3: + case 4: + case 5: + bonetable[j].rotscale[k-3] = scale; + break; + } + } + } + + // find bounding box for each sequence + for (i = 0; i < numseq; i++) + { + vec3_t bmin, bmax; + + // find intersection box volume for each bone + for (j = 0; j < 3; j++) + { + bmin[j] = 9999.0; + bmax[j] = -9999.0; + } + + for (q = 0; q < sequence[i]->numblends; q++) + { + for (n = 0; n < sequence[i]->numframes; n++) + { + matrix4x4 bonetransform[MAXSTUDIOBONES];// bone transformation matrix + matrix4x4 bonematrix; // local transformation matrix + vec3_t pos; + + for (j = 0; j < numbones; j++) + { + vec3_t origin, angle; + + // convert to degrees + angle[0] = sequence[i]->panim[q]->rot[j][n][0] * (180.0 / M_PI); + angle[1] = sequence[i]->panim[q]->rot[j][n][1] * (180.0 / M_PI); + angle[2] = sequence[i]->panim[q]->rot[j][n][2] * (180.0 / M_PI); + origin[0] = sequence[i]->panim[q]->pos[j][n][0]; + origin[1] = sequence[i]->panim[q]->pos[j][n][1]; + origin[2] = sequence[i]->panim[q]->pos[j][n][2]; + + Matrix4x4_CreateFromEntity( bonematrix, origin[0], origin[1], origin[2], angle[YAW], angle[ROLL], angle[PITCH], 1.0 ); + + if( bonetable[j].parent == -1 ) Matrix4x4_Copy( bonetransform[j], bonematrix ); + else Matrix4x4_ConcatTransforms( bonetransform[j], bonetransform[bonetable[j].parent], bonematrix ); + } + + for( k = 0; k < nummodels; k++ ) + { + for( j = 0; j < model[k]->numverts; j++ ) + { + Matrix4x4_Transform( bonetransform[model[k]->vert[j].bone], model[k]->vert[j].org, pos ); + + if (pos[0] < bmin[0]) bmin[0] = pos[0]; + if (pos[1] < bmin[1]) bmin[1] = pos[1]; + if (pos[2] < bmin[2]) bmin[2] = pos[2]; + if (pos[0] > bmax[0]) bmax[0] = pos[0]; + if (pos[1] > bmax[1]) bmax[1] = pos[1]; + if (pos[2] > bmax[2]) bmax[2] = pos[2]; + } + } + } + } + + VectorCopy( bmin, sequence[i]->bmin ); + VectorCopy( bmax, sequence[i]->bmax ); + } + + // reduce animations + { + int total = 0; + int changes = 0; + int p; + + for (i = 0; i < numseq; i++) + { + for (q = 0; q < sequence[i]->numblends; q++) + { + for (j = 0; j < numbones; j++) + { + for (k = 0; k < 6; k++) + { + dstudioanimvalue_t *pcount, *pvalue; + short value[MAXSTUDIOANIMATIONS]; + dstudioanimvalue_t data[MAXSTUDIOANIMATIONS]; + float v; + + for (n = 0; n < sequence[i]->numframes; n++) + { + switch(k) + { + case 0: + case 1: + case 2: + value[n] = ( sequence[i]->panim[q]->pos[j][n][k] - bonetable[j].pos[k] ) / bonetable[j].posscale[k]; + break; + case 3: + case 4: + case 5: + v = ( sequence[i]->panim[q]->rot[j][n][k-3] - bonetable[j].rot[k-3] ); + if (v >= M_PI) v -= M_PI * 2; + if (v < -M_PI) v += M_PI * 2; + value[n] = v / bonetable[j].rotscale[k-3]; + break; + } + } + if( n == 0 ) Sys_Break("no animation frames: \"%s\"\n", sequence[i]->name ); + + sequence[i]->panim[q]->numanim[j][k] = 0; + Mem_Set( data, 0, sizeof( data ) ); + pcount = data; + pvalue = pcount + 1; + + pcount->num.valid = 1; + pcount->num.total = 1; + pvalue->value = value[0]; + pvalue++; + + for (m = 1, p = 0; m < n; m++) + { + if (abs(value[p] - value[m]) > 1600) + { + changes++; + p = m; + } + } + + // this compression algorithm needs work + for( m = 1; m < n; m++ ) + { + if( pcount->num.total == 255 ) + { + // too many, force a new entry + pcount = pvalue; + pvalue = pcount + 1; + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + } + + // insert value if they're not equal, + // or if we're not on a run and the run is less than 3 units + else if ((value[m] != value[m-1]) || ((pcount->num.total == pcount->num.valid) && ((m < n - 1) && value[m] != value[m+1]))) + { + total++; + if (pcount->num.total != pcount->num.valid) + { + pcount = pvalue; + pvalue = pcount + 1; + } + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + } + pcount->num.total++; + } + + sequence[i]->panim[q]->numanim[j][k] = pvalue - data; + if( sequence[i]->panim[q]->numanim[j][k] == 2 && value[0] == 0) + { + sequence[i]->panim[q]->numanim[j][k] = 0; + } + else + { + sequence[i]->panim[q]->anim[j][k] = Kalloc( (pvalue - data) * sizeof( dstudioanimvalue_t )); + memmove( sequence[i]->panim[q]->anim[j][k], data, (pvalue - data) * sizeof( dstudioanimvalue_t )); + } + } + } + } + } + } +} + +void Grab_Skin( s_texture_t *ptexture ) +{ + string filename; + rgbdata_t *pic; + + com.strncpy( filename, ptexture->name, MAX_STRING ); + + pic = FS_LoadImage( filename, error_bmp, error_bmp_size ); + if( !pic ) Sys_Break( "unable to load %s\n", filename ); // no error.bmp, missing texture... + Image_Process( &pic, 0, 0, IMAGE_PALTO24 ); + + ptexture->ppal = Kalloc( 768 ); + Mem_Copy( ptexture->ppal, pic->palette, sizeof(rgb_t) * 256 ); + ptexture->ppicture = Kalloc( pic->size ); + Mem_Copy( ptexture->ppicture, pic->buffer, pic->size ); + ptexture->srcwidth = pic->width; + ptexture->srcheight = pic->height; + FS_FreeImage( pic ); // free old image +} + +void SetSkinValues( void ) +{ + int i, j; + int index; + + for (i = 0; i < numtextures; i++) + { + Grab_Skin ( texture[i] ); + texture[i]->max_s = -9999999; + texture[i]->min_s = 9999999; + texture[i]->max_t = -9999999; + texture[i]->min_t = 9999999; + } + + for (i = 0; i < nummodels; i++) + for (j = 0; j < model[i]->nummesh; j++) + TextureCoordRanges( model[i]->pmesh[j], texture[model[i]->pmesh[j]->skinref] ); + + for (i = 0; i < numtextures; i++) + { + if (texture[i]->max_s < texture[i]->min_s ) + { + // must be a replacement texture + if (texture[i]->flags & STUDIO_NF_CHROME) + { + texture[i]->max_s = 63; + texture[i]->min_s = 0; + texture[i]->max_t = 63; + texture[i]->min_t = 0; + } + else + { + texture[i]->max_s = texture[texture[i]->parent]->max_s; + texture[i]->min_s = texture[texture[i]->parent]->min_s; + texture[i]->max_t = texture[texture[i]->parent]->max_t; + texture[i]->min_t = texture[texture[i]->parent]->min_t; + } + } + ResizeTexture( texture[i] ); + } + + for (i = 0; i < nummodels; i++) + for (j = 0; j < model[i]->nummesh; j++) + ResetTextureCoordRanges( model[i]->pmesh[j], texture[model[i]->pmesh[j]->skinref] ); + + // build texture groups + for (i = 0; i < MAXSTUDIOSKINS; i++) for (j = 0; j < MAXSTUDIOSKINS; j++) skinref[i][j] = j; + + index = 0; + for (i = 0; i < numtexturelayers[0]; i++) + { + for (j = 0; j < numtexturereps[0]; j++) + { + skinref[i][texturegroup[0][0][j]] = texturegroup[0][i][j]; + } + } + + if (i != 0) numskinfamilies = i; + else + { + numskinfamilies = 1; + numskinref = numtextures; + } +} + +void Build_Reference( s_model_t *pmodel) +{ + int i, parent; + float angle[3]; + + for (i = 0; i < pmodel->numbones; i++) + { + matrix4x4 m; + vec3_t p; + + // convert to degrees + angle[0] = pmodel->skeleton[i].rot[0] * (180.0 / M_PI); + angle[1] = pmodel->skeleton[i].rot[1] * (180.0 / M_PI); + angle[2] = pmodel->skeleton[i].rot[2] * (180.0 / M_PI); + + parent = pmodel->node[i].parent; + if( parent == -1 ) + { + // scale the done pos. + // calc rotational matrices + Matrix4x4_CreateFromEntity( bonefixup[i].m, 0, 0, 0, angle[YAW], angle[ROLL], angle[PITCH], 1.0 ); + Matrix4x4_Transpose( bonefixup[i].im, bonefixup[i].m ); + VectorCopy( pmodel->skeleton[i].pos, bonefixup[i].worldorg ); + } + else + { + // calc compound rotational matrices + Matrix4x4_CreateFromEntity( m, 0, 0, 0, angle[YAW], angle[ROLL], angle[PITCH], 1.0 ); + Matrix4x4_ConcatTransforms( bonefixup[i].m, bonefixup[parent].m, m ); + Matrix4x4_Transpose( bonefixup[i].im, bonefixup[i].m ); + + // calc true world coord. + Matrix4x4_Transform( bonefixup[parent].m, pmodel->skeleton[i].pos, p ); + VectorAdd( p, bonefixup[parent].worldorg, bonefixup[i].worldorg ); + } + } +} + +void Grab_Triangles( script_t *script, s_model_t *pmodel ) +{ + int i, j; + int tcount = 0; + vec3_t vmin, vmax; + + vmin[0] = vmin[1] = vmin[2] = 99999; + vmax[0] = vmax[1] = vmax[2] = -99999; + + Build_Reference( pmodel ); + + // load the base triangles + while ( 1 ) + { + if( Com_ReadToken( script, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC|SC_PARSE_LINE, &token )) + { + s_mesh_t *pmesh; + char texturename[64]; + s_trianglevert_t *ptriv; + int bone; + vec3_t vert[3]; + vec3_t norm[3]; + + // strip off trailing smag + com.strncpy( texturename, token.string, 64 ); + for( i = com.strlen( texturename ) - 1; i >= 0 && !isgraph( texturename[i] ); i-- ); + texturename[i+1] = '\0'; + + if( !com.stricmp( texturename, "end" )) return; // triangles end + + // funky texture overrides + for( i = 0; i < numrep; i++ ) + { + if( sourcetexture[i][0] == '\0' ) + { + com.strncpy( texturename, defaulttexture[i], 64 ); + break; + } + if( !com.stricmp( texturename, sourcetexture[i] )) + { + com.strncpy( texturename, defaulttexture[i], 64 ); + break; + } + } + if( texturename[0] == '\0' ) // invalid name + { + // weird model problem, skip them + MsgDev( D_ERROR, "Grab_Triangles: triangle with invalid texname\n" ); + Com_ReadToken( script, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC|SC_PARSE_LINE, &token ); + Com_ReadToken( script, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC|SC_PARSE_LINE, &token ); + Com_ReadToken( script, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC|SC_PARSE_LINE, &token ); + continue; + } + + pmesh = lookup_mesh( pmodel, texturename ); + + for( j = 0; j < 3; j++ ) + { + s_vertex_t p; + vec3_t tmp; + s_normal_t normal; + + if( flip_triangles ) ptriv = lookup_triangle( pmesh, pmesh->numtris ) + 2 - j; + else ptriv = lookup_triangle( pmesh, pmesh->numtris ) + j; + + // grab triangle info + Com_ReadLong( script, true, &bone ); + Com_ReadFloat( script, false, &p.org[0] ); + Com_ReadFloat( script, false, &p.org[1] ); + Com_ReadFloat( script, false, &p.org[2] ); + Com_ReadFloat( script, false, &normal.org[0] ); + Com_ReadFloat( script, false, &normal.org[1] ); + Com_ReadFloat( script, false, &normal.org[2] ); + Com_ReadFloat( script, false, &ptriv->u ); + Com_ReadFloat( script, false, &ptriv->v ); + + // skip MilkShape additional info + Com_SkipRestOfLine( script ); + + // translate triangles + if( bone < 0 || bone >= pmodel->numbones ) + Sys_Break( "bogus bone index %d \n%s line %d", bone, filename, token.line ); + VectorCopy( p.org, vert[j] ); + VectorCopy( normal.org, norm[j] ); + + p.bone = bone; + normal.bone = bone; + normal.skinref = pmesh->skinref; + + if( p.org[2] < vmin[2] ) vmin[2] = p.org[2]; + + adjust_vertex( p.org ); + scale_vertex( p.org ); + + // move vertex position to object space. + VectorSubtract( p.org, bonefixup[p.bone].worldorg, tmp ); + Matrix4x4_Transform( bonefixup[p.bone].im, tmp, p.org ); + + // move normal to object space. + VectorCopy( normal.org, tmp ); + Matrix4x4_Transform( bonefixup[p.bone].im, tmp, normal.org ); + VectorNormalize( normal.org ); + + ptriv->normindex = lookup_normal( pmodel, &normal ); + ptriv->vertindex = lookup_vertex( pmodel, &p ); + + // tag bone as being used + // pmodel->bone[bone].ref = 1; + } + pmodel->trimesh[tcount] = pmesh; + pmodel->trimap[tcount] = pmesh->numtris++; + tcount++; + } + else break; + } + if( vmin[2] != 0.0 ) MsgDev( D_NOTE, "Grab_Triangles: lowest vector at %f\n", vmin[2] ); +} + +void Grab_Skeleton( script_t *script, s_node_t *pnodes, s_bone_t *pbones ) +{ + int index; + int time = 0; + + while ( 1 ) + { + if( !Com_ReadToken( script, SC_ALLOW_NEWLINES, &token )) + break; + + if( !com.stricmp( token.string, "end" )) return; // skeleton end + else if( !com.stricmp( token.string, "time" )) + { + // check time + Com_ReadToken( script, 0, &token ); + time += token.integerValue; + if( time > 0 ) MsgDev( D_WARN, "Grab_Skeleton: Warning! An animation file is probably used as a reference\n" ); + continue; + } + else + { + // grab skeleton info + index = com.atoi( token.string ); + Com_ReadFloat( script, false, &pbones[index].pos[0] ); + Com_ReadFloat( script, false, &pbones[index].pos[1] ); + Com_ReadFloat( script, false, &pbones[index].pos[2] ); + + scale_vertex( pbones[index].pos ); + if( pnodes[index].mirrored ) VectorScale( pbones[index].pos, -1.0, pbones[index].pos ); + + Com_ReadFloat( script, false, &pbones[index].rot[0] ); + Com_ReadFloat( script, false, &pbones[index].rot[1] ); + Com_ReadFloat( script, false, &pbones[index].rot[2] ); + + clip_rotations( pbones[index].rot ); + } + } + Sys_Break( "Unexpected EOF %s at line %d\n", filename, token.line ); +} + +int Grab_Nodes( script_t *script, s_node_t *pnodes ) +{ + int i, index; + int parent; + int numbones = 0; + string name; + + while( 1 ) + { + if(!Com_ReadToken( script, SC_ALLOW_NEWLINES, &token )) break; + + // end of nodes description + if( !com.stricmp( token.string, "end" )) return numbones + 1; + + index = com.atoi( token.string ); // read bone index (we already have filled token) + Com_ReadString( script, false, name ); + Com_ReadLong( script, false, &parent); // read bone parent + + com.strncpy( pnodes[index].name, name, sizeof(pnodes[index].name)); + pnodes[index].parent = parent; + numbones = index; + + // check for mirrored bones; + for( i = 0; i < nummirrored; i++ ) + { + if( !com.strcmp( name, mirrored[i] )) + pnodes[index].mirrored = 1; + } + if((!pnodes[index].mirrored) && parent != -1 ) + pnodes[index].mirrored = pnodes[pnodes[index].parent].mirrored; + } + + Sys_Break( "Unexpected EOF %s at line %d\n", filename, token.line ); + return 0; +} + +void Grab_Studio( s_model_t *pmodel ) +{ + script_t *studio; + + com.strncpy( filename, pmodel->name, sizeof( filename )); + + FS_DefaultExtension( filename, ".smd" ); + studio = Com_OpenScript( filename, NULL, 0 ); + if( !studio ) Sys_Break( "unable to open %s\n", filename ); + Msg( "grabbing %s\n", filename ); + + while( 1 ) + { + if( !Com_ReadToken( studio, SC_ALLOW_NEWLINES, &token )) break; + + if( !com.stricmp( token.string, "version" )) + { + int option; + bool result = Com_ReadLong( studio, false, &option ); + if( option != 1 ) Sys_Break( "Grab_Studio: %s bad version file %i, %i\n", filename, option, result ); + } + else if( !com.stricmp( token.string, "nodes" )) + { + pmodel->numbones = Grab_Nodes( studio, pmodel->node ); + } + else if( !com.stricmp( token.string, "skeleton" )) + { + Grab_Skeleton( studio, pmodel->node, pmodel->skeleton ); + } + else if( !com.stricmp( token.string, "triangles" )) + { + Grab_Triangles( studio, pmodel ); + } + else MsgDev( D_WARN, "Grab_Studio: unknown studio command %s at line %d\n", token.string, token.line ); + } + Com_CloseScript( studio ); +} + +/* +============== +Cmd_Eyeposition + +syntax: $eyeposition +============== +*/ +void Cmd_Eyeposition( void ) +{ + // rotate points into frame of reference so model points down the positive x axis + Com_ReadFloat( studioqc, false, &eyeposition[1] ); + Com_ReadFloat( studioqc, false, &eyeposition[0] ); + Com_ReadFloat( studioqc, false, &eyeposition[2] ); + + eyeposition[0] = -eyeposition[0]; // stupid Quake bug +} + +/* +============== +Cmd_Modelname + +syntax: $modelname "outname" +============== +*/ +void Cmd_Modelname( void ) +{ + Com_ReadString( studioqc, false, modeloutname ); +} + +void Option_Studio( void ) +{ + if(!Com_ReadToken( studioqc, 0, &token )) return; + + model[nummodels] = Kalloc( sizeof( s_model_t )); + bodypart[numbodyparts].pmodel[bodypart[numbodyparts].nummodels] = model[nummodels]; + com.strncpy( model[nummodels]->name, token.string, sizeof(model[nummodels]->name)); + + flip_triangles = 1; + scale_up = default_scale; + + while(Com_ReadToken( studioqc, 0, &token )) + { + if( !com.stricmp( token.string, "reverse" )) + flip_triangles = 0; + else if( !com.stricmp( token.string, "scale" )) + Com_ReadFloat( studioqc, false, &scale_up ); + } + + Grab_Studio( model[nummodels] ); + + scale_up = default_scale; // reset to default + bodypart[numbodyparts].nummodels++; + nummodels++; +} + + +int Option_Blank( void ) +{ + model[nummodels] = Kalloc( sizeof( s_model_t )); + bodypart[numbodyparts].pmodel[bodypart[numbodyparts].nummodels] = model[nummodels]; + + com.strncpy( model[nummodels]->name, "blank", sizeof(model[nummodels]->name)); + + bodypart[numbodyparts].nummodels++; + nummodels++; + + return 0; +} + +/* +============== +Cmd_Bodygroup + +syntax: $bodygroup "name" +{ + studio "bodyref.smd" + blank +} +============== +*/ +void Cmd_Bodygroup( void ) +{ + int is_started = 0; + + if(!Com_ReadToken( studioqc, 0, &token )) return; + + if (numbodyparts == 0) bodypart[numbodyparts].base = 1; + else bodypart[numbodyparts].base = bodypart[numbodyparts-1].base * bodypart[numbodyparts-1].nummodels; + com.strncpy( bodypart[numbodyparts].name, token.string, sizeof( bodypart[numbodyparts].name )); + + while( 1 ) + { + if( !Com_ReadToken( studioqc, SC_ALLOW_NEWLINES, &token )) return; + if( !com.stricmp( token.string, "{" )) is_started = 1; + else if( !com.stricmp( token.string, "}" )) break; + else if( !com.stricmp( token.string, "studio" )) Option_Studio(); + else if( !com.stricmp( token.string, "blank" )) Option_Blank(); + } + numbodyparts++; +} + +/* +============== +Cmd_Modelname + +syntax: $body "name" "mainref.smd" +============== +*/ +void Cmd_Body( void ) +{ + int is_started = 0; + + if( !Com_ReadToken( studioqc, 0, &token )) return; + if( numbodyparts == 0 ) bodypart[numbodyparts].base = 1; + else bodypart[numbodyparts].base = bodypart[numbodyparts-1].base * bodypart[numbodyparts-1].nummodels; + com.strncpy( bodypart[numbodyparts].name, token.string, sizeof( bodypart[numbodyparts].name )); + + Option_Studio(); + numbodyparts++; +} + +void Grab_Animation( script_t *script, s_animation_t *panim ) +{ + vec3_t pos; + vec3_t rot; + int index; + int t = -99999999; + int start = 99999; + float cz, sz; + int end = 0; + + for( index = 0; index < panim->numbones; index++ ) + { + panim->pos[index] = Kalloc( MAXSTUDIOANIMATIONS * sizeof( vec3_t )); + panim->rot[index] = Kalloc( MAXSTUDIOANIMATIONS * sizeof( vec3_t )); + } + + cz = com.cos( zrotation ); + sz = com.sin( zrotation ); + + while( 1 ) + { + if(!Com_ReadToken( script, SC_ALLOW_NEWLINES, &token )) + break; + + if( !com.stricmp( token.string, "end" )) + { + panim->startframe = start; + panim->endframe = end; + return; + } + else if( !com.stricmp( token.string, "time" )) + { + Com_ReadLong( script, false, &t ); + } + else + { + index = com.atoi( token.string ); + Com_ReadFloat( script, false, &pos[0] ); + Com_ReadFloat( script, false, &pos[1] ); + Com_ReadFloat( script, false, &pos[2] ); + Com_ReadFloat( script, false, &rot[0] ); + Com_ReadFloat( script, false, &rot[1] ); + Com_ReadFloat( script, false, &rot[2] ); + + if( t >= panim->startframe && t <= panim->endframe ) + { + if (panim->node[index].parent == -1) + { + adjust_vertex( pos ); + panim->pos[index][t][0] = cz * pos[0] - sz * pos[1]; + panim->pos[index][t][1] = sz * pos[0] + cz * pos[1]; + panim->pos[index][t][2] = pos[2]; + rot[2] += zrotation; // rotate model + } + else VectorCopy( pos, panim->pos[index][t] ); + + if( t > end ) end = t; + if( t < start ) start = t; + + if( panim->node[index].mirrored ) + VectorScale( panim->pos[index][t], -1.0, panim->pos[index][t] ); + + scale_vertex( panim->pos[index][t] ); + clip_rotations( rot ); + VectorCopy( rot, panim->rot[index][t] ); + } + } + } + Sys_Break( "unexpected EOF: %s.smd line %i\n", panim->name, token.line ); +} + +void Shift_Animation( s_animation_t *panim ) +{ + int j, size; + + size = (panim->endframe - panim->startframe + 1) * sizeof( vec3_t ); + + // shift + for( j = 0; j < panim->numbones; j++ ) + { + vec3_t *ppos; + vec3_t *prot; + + ppos = Kalloc( size ); + prot = Kalloc( size ); + Mem_Move( studiopool, (void *)&ppos, &panim->pos[j][panim->startframe], size ); + Mem_Move( studiopool, (void *)&prot, &panim->rot[j][panim->startframe], size ); + panim->pos[j] = ppos; + panim->rot[j] = prot; + } +} + +void Option_Animation( const char *name, s_animation_t *panim ) +{ + script_t *anim; + + com.strncpy( panim->name, name, sizeof( panim->name )); + com.strncpy( filename, panim->name, sizeof( filename )); + + FS_DefaultExtension( filename, ".smd" ); + anim = Com_OpenScript( filename, NULL, 0 ); + if( !anim ) Sys_Break( "unable to open %s\n", filename ); + + while ( 1 ) + { + if( !Com_ReadToken( anim, SC_ALLOW_NEWLINES, &token )) + break; + + if( !com.stricmp( token.string, "end" )) break; + else if( !com.stricmp( token.string, "version" )) + { + int option; + Com_ReadLong( anim, false, &option ); + if( option != 1 ) MsgDev( D_ERROR, "Grab_Animation: %s bad version file\n", filename ); + } + else if( !com.stricmp( token.string, "nodes" )) + { + panim->numbones = Grab_Nodes( anim, panim->node ); + } + else if( !com.stricmp( token.string, "skeleton" )) + { + Grab_Animation( anim, panim ); + Shift_Animation( panim ); + } + else + { + MsgDev( D_WARN, "Option_Animation: unknown studio command : %s\n", token.string ); + Com_SkipRestOfLine( anim ); // skip other tokens at line + } + } + Com_CloseScript( anim ); +} + +int Option_Motion( s_sequence_t *psequence ) +{ + string motion; + + while( Com_ReadString( studioqc, false, motion )) + psequence->motiontype |= lookupControl( motion ); + return 0; +} + +int Option_Event( s_sequence_t *psequence ) +{ + if( psequence->numevents + 1 >= MAXSTUDIOEVENTS ) + { + MsgDev( D_ERROR, "Option_Event: MAXSTUDIOEVENTS limit excedeed.\n"); + return 0; + } + + Com_ReadLong( studioqc, false, &psequence->event[psequence->numevents].event ); + Com_ReadLong( studioqc, false, &psequence->event[psequence->numevents].frame ); + psequence->numevents++; + + // option token + if( Com_ReadToken( studioqc, 0, &token )) + { + if( !com.stricmp( token.string, "}" )) return 1; // opps, hit the end + + // found an option + com.strncpy( psequence->event[psequence->numevents-1].options, token.string, 64 ); + } + return 0; +} + +int Option_Fps( s_sequence_t *psequence ) +{ + Com_ReadFloat( studioqc, false, &psequence->fps ); + return 0; +} + +int Option_AddPivot( s_sequence_t *psequence ) +{ + if( psequence->numpivots + 1 >= MAXSTUDIOPIVOTS ) + { + MsgDev( D_ERROR, "MAXSTUDIOPIVOTS limit excedeed.\n" ); + return 0; + } + + Com_ReadLong( studioqc, false, &psequence->pivot[psequence->numpivots].index ); + Com_ReadLong( studioqc, false, &psequence->pivot[psequence->numpivots].start ); + Com_ReadLong( studioqc, false, &psequence->pivot[psequence->numpivots].end ); + psequence->numpivots++; + + return 0; +} + +/* +============== +Cmd_Origin + +syntax: $origin (z rotate) +============== +*/ +void Cmd_Origin( void ) +{ + Com_ReadFloat( studioqc, false, &defaultadjust[0] ); + Com_ReadFloat( studioqc, false, &defaultadjust[1] ); + Com_ReadFloat( studioqc, false, &defaultadjust[2] ); + + if( Com_ReadToken( studioqc, 0, &token )) + defaultzrotation = DEG2RAD( com.atof( token.string ) + 90 ); +} + + +void Option_Origin( void ) +{ + Com_ReadFloat( studioqc, false, &adjust[0] ); + Com_ReadFloat( studioqc, false, &adjust[1] ); + Com_ReadFloat( studioqc, false, &adjust[2] ); +} + +void Option_Rotate( void ) +{ + Com_ReadToken( studioqc, 0, &token ); + zrotation = DEG2RAD( com.atof( token.string ) + 90 ); +} + +/* +============== +Cmd_ScaleUp + +syntax: $scale +============== +*/ +void Cmd_ScaleUp( void ) +{ + Com_ReadFloat( studioqc, false, &scale_up ); + default_scale = scale_up; +} + +/* +============== +Cmd_Rotate + +syntax: $rotate +============== +*/ +void Cmd_Rotate( void ) +{ + if(!Com_ReadToken( studioqc, 0, &token )) return; + zrotation = DEG2RAD( com.atof( token.string ) + 90 ); +} + +void Option_ScaleUp (void) +{ + Com_ReadFloat( studioqc, false, &scale_up ); +} + +/* +============== +Cmd_Sequence + +syntax: $sequence "seqname" +{ + (animation) "blend1.smd" "blend2.smd" // blendings (one smdfile - normal animation) + event { ("option") } // event num, frame num, option string (optionally, heh...) + pivot // xyz pivot point + fps // 30.0 frames per second is default value + origin // xyz anim offset + rotate // z-axis rotation value 0 - 360 deg + scale // 1.0 is default size + loop // loop this animation (flag) + frame // use range(start, end) from smd file + blend "type" // blend description types: XR, YR e.t.c and range(min-max) + node // link animation with node + transition // move from start to end node + rtransition // rotate from start to end node + LX // movement flag (may be X, Y, Z, LX, LY, LZ, XR, YR, etc ) + ACT_WALK | ACT_32 (actweight) // act name or act number and actweight (optionally) +} +============== +*/ +static int Cmd_Sequence( void ) +{ + int i, depth = 0; + string smdfilename[MAXSTUDIOBLENDS]; + int end = MAXSTUDIOANIMATIONS - 1; + int numblends = 0; + int start = 0; + + if( !Com_ReadToken( studioqc, 0, &token )) return 0; + + // allocate new sequence + if( numseq >= MAXSTUDIOSEQUENCES ) Sys_Break( "Too many sequences in model\n" ); + sequence[numseq] = Mem_Alloc( studiopool, sizeof(s_sequence_t)); + com.strncpy( sequence[numseq]->name, token.string, sizeof( sequence[numseq]->name )); + + VectorCopy( defaultadjust, adjust ); + scale_up = default_scale; + + // set default values + zrotation = defaultzrotation; + sequence[numseq]->fps = 30.0; + sequence[numseq]->seqgroup = 0; + sequence[numseq]->blendstart[0] = 0.0; + sequence[numseq]->blendend[0] = 1.0f; + + while( 1 ) + { + if( depth > 0 ) + { + if(!Com_ReadToken( studioqc, SC_ALLOW_NEWLINES|SC_ALLOW_PATHNAMES, &token )) + break; + } + else if(!Com_ReadToken( studioqc, SC_ALLOW_PATHNAMES, &token )) break; + + if( !com.stricmp( token.string, "{" )) depth++; + else if( !com.stricmp( token.string, "}" )) depth--; + else if( !com.stricmp( token.string, "event" )) depth -= Option_Event( sequence[numseq] ); + else if( !com.stricmp( token.string, "pivot" )) Option_AddPivot( sequence[numseq] ); + else if( !com.stricmp( token.string, "fps" )) Option_Fps( sequence[numseq] ); + else if( !com.stricmp( token.string, "origin" )) Option_Origin(); + else if( !com.stricmp( token.string, "rotate" )) Option_Rotate(); + else if( !com.stricmp( token.string, "scale" )) Option_ScaleUp(); + else if( !com.stricmp( token.string, "loop" )) sequence[numseq]->flags |= STUDIO_LOOPING; + else if( !com.stricmp( token.string, "frame" )) + { + Com_ReadLong( studioqc, false, &start ); + Com_ReadLong( studioqc, false, &end ); + } + else if( !com.stricmp( token.string, "blend" )) + { + Com_ReadToken( studioqc, 0, &token ); + sequence[numseq]->blendtype[0] = lookupControl( token.string ); + Com_ReadFloat( studioqc, false, &sequence[numseq]->blendstart[0] ); + Com_ReadFloat( studioqc, false, &sequence[numseq]->blendend[0] ); + } + else if( !com.stricmp( token.string, "node" )) + { + Com_ReadLong( studioqc, false, &sequence[numseq]->entrynode ); + sequence[numseq]->exitnode = sequence[numseq]->entrynode; + } + else if( !com.stricmp( token.string, "transition" )) + { + Com_ReadLong( studioqc, false, &sequence[numseq]->entrynode ); + Com_ReadLong( studioqc, false, &sequence[numseq]->exitnode ); + } + else if( !com.stricmp( token.string, "rtransition" )) + { + Com_ReadLong( studioqc, false, &sequence[numseq]->entrynode ); + Com_ReadLong( studioqc, false, &sequence[numseq]->exitnode ); + sequence[numseq]->nodeflags |= 1; + } + else if( lookupControl( token.string ) != -1 ) + { + sequence[numseq]->motiontype |= lookupControl( token.string ); + } + else if( !com.stricmp( token.string, "animation" )) + { + Com_ReadString( studioqc, false, smdfilename[numblends] ); + numblends++; + } + else if( i = lookupActivity( token.string )) + { + sequence[numseq]->activity = i; + sequence[numseq]->actweight = 1; // default weight + Com_ReadLong( studioqc, false, &sequence[numseq]->actweight ); + } + else + { + com.strncpy( smdfilename[numblends], token.string, sizeof( smdfilename[numblends] )); + numblends++; + } + } + + if( numblends == 0 ) + { + Mem_Free( sequence[numseq] ); // no needs more + MsgDev( D_INFO, "Cmd_Sequence: \"%s\" has no animations. skipped...\n", sequence[numseq]->name ); + return 0; + } + for( i = 0; i < numblends; i++ ) + { + if( numani >= ( MAXSTUDIOSEQUENCES * MAXSTUDIOBLENDS )) + Sys_Break( "Too many blends in model\n" ); + panimation[numani] = Kalloc( sizeof( s_animation_t )); + sequence[numseq]->panim[i] = panimation[numani]; + sequence[numseq]->panim[i]->startframe = start; + sequence[numseq]->panim[i]->endframe = end; + sequence[numseq]->panim[i]->flags = 0; + if( i == 0 ) Msg( "grabbing %s\n", filename ); + else MsgDev( D_INFO, "adding %s[%i]\n", filename, numani ); + Option_Animation( smdfilename[i], panimation[numani] ); + numani++; + } + sequence[numseq]->numblends = numblends; + numseq++; + + return 1; +} + +/* +============== +Cmd_Root + +syntax: $root "pivotname" +============== +*/ +int Cmd_Root( void ) +{ + if( Com_ReadToken( studioqc, 0, &token )) + { + com.strncpy( pivotname[0], token.string, sizeof( pivotname )); + return 0; + } + return 1; +} + +/* +============== +Cmd_Pivot + +syntax: $pivot () ("pivotname") +============== +*/ +int Cmd_Pivot( void ) +{ + int index; + + if( Com_ReadLong( studioqc, false, &index )) + { + if( Com_ReadToken( studioqc, 0, &token )) + { + com.strncpy( pivotname[index], token.string, sizeof( pivotname[index] )); + return 0; + } + } + return 1; +} + +/* +============== +Cmd_Controller + +syntax: $controller "mouth"| ("name") +============== +*/ +int Cmd_Controller( void ) +{ + if( Com_ReadToken( studioqc, 0, &token )) + { + // mouth is hardcoded at four controller + if( !com.stricmp( token.string, "mouth" )) + bonecontroller[numbonecontrollers].index = 4; + else bonecontroller[numbonecontrollers].index = com.atoi( token.string ); + + if( Com_ReadToken( studioqc, 0, &token )) + { + com.strncpy( bonecontroller[numbonecontrollers].name, token.string, sizeof(bonecontroller[numbonecontrollers].name )); + + Com_ReadToken( studioqc, 0, &token ); + if((bonecontroller[numbonecontrollers].type = lookupControl( token.string )) == -1 ) + { + MsgDev( D_ERROR, "Cmd_Controller: unknown bonecontroller type '%s'\n", token.string ); + Com_SkipRestOfLine( studioqc ); + return 0; + } + + Com_ReadFloat( studioqc, false, &bonecontroller[numbonecontrollers].start ); + Com_ReadFloat( studioqc, false, &bonecontroller[numbonecontrollers].end ); + + if( bonecontroller[numbonecontrollers].type & (STUDIO_XR|STUDIO_YR|STUDIO_ZR)) + { + // check for infinity loop + if(((int)(bonecontroller[numbonecontrollers].start + 360) % 360) == ((int)(bonecontroller[numbonecontrollers].end + 360) % 360)) + bonecontroller[numbonecontrollers].type |= STUDIO_RLOOP; + } + numbonecontrollers++; + } + } + return 1; +} + +/* +============== +Cmd_BBox + +syntax: $bbox +============== +*/ +void Cmd_BBox( void ) +{ + Com_ReadFloat( studioqc, false, &bbox[0][0] ); + Com_ReadFloat( studioqc, false, &bbox[0][1] ); + Com_ReadFloat( studioqc, false, &bbox[0][2] ); + Com_ReadFloat( studioqc, false, &bbox[1][0] ); + Com_ReadFloat( studioqc, false, &bbox[1][1] ); + Com_ReadFloat( studioqc, false, &bbox[1][2] ); +} + +/* +============== +Cmd_CBox + +syntax: $cbox +============== +*/ +void Cmd_CBox( void ) +{ + Com_ReadFloat( studioqc, false, &cbox[0][0] ); + Com_ReadFloat( studioqc, false, &cbox[0][1] ); + Com_ReadFloat( studioqc, false, &cbox[0][2] ); + Com_ReadFloat( studioqc, false, &cbox[1][0] ); + Com_ReadFloat( studioqc, false, &cbox[1][1] ); + Com_ReadFloat( studioqc, false, &cbox[1][2] ); +} + +/* +============== +Cmd_Mirror + +syntax: $mirrorbone "name" +============== +*/ +void Cmd_Mirror( void ) +{ + Com_ReadString( studioqc, false, mirrored[nummirrored] ); + nummirrored++; +} + +/* +============== +Cmd_Gamma + +syntax: $gamma +============== +*/ +void Cmd_Gamma( void ) +{ + Com_ReadFloat( studioqc, false, &gamma ); +} + +/* +============== +Cmd_TextureGroup + +syntax: $texturegroup +{ +{ texture1.bmp } +{ texture2.bmp } +{ texture3.bmp } +} +============== +*/ +int Cmd_TextureGroup( void ) +{ + int i, depth = 0; + int index = 0, group = 0; + + if( numtextures == 0 ) + { + MsgDev( D_ERROR, "Cmd_TextureGroup: texturegroups must follow model loading\n"); + return 0; + } + + if( !Com_ReadToken( studioqc, 0, &token )) return 0; + if( numskinref == 0 ) numskinref = numtextures; + + while( 1 ) + { + if( !Com_ReadToken( studioqc, SC_ALLOW_NEWLINES, &token )) + { + if( depth ) MsgDev( D_ERROR, "missing }\n" ); + break; + } + + if( !com.stricmp( token.string, "{" )) depth++; + else if( !com.stricmp( token.string, "}" )) + { + if( --depth == 0 ) break; + group++; + index = 0; + } + else if( depth == 2 ) + { + i = lookup_texture( token.string ); + texturegroup[numtexturegroups][group][index] = i; + if( group != 0 ) texture[i]->parent = texturegroup[numtexturegroups][0][index]; + index++; + numtexturereps[numtexturegroups] = index; + numtexturelayers[numtexturegroups] = group + 1; + } + } + numtexturegroups++; + return 0; +} + +/* +============== +Cmd_Hitgroup + +syntax: $hitgroup +============== +*/ +int Cmd_Hitgroup( void ) +{ + Com_ReadLong( studioqc, false, &hitgroup[numhitgroups].group ); + Com_ReadString( studioqc, false, hitgroup[numhitgroups].name ); + numhitgroups++; + + return 0; +} + +/* +============== +Cmd_Hitbox + +syntax: $hbox +============== +*/ +int Cmd_Hitbox( void ) +{ + Com_ReadLong( studioqc, false, &hitbox[numhitboxes].group ); + Com_ReadString( studioqc, false, hitbox[numhitboxes].name ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmin[0] ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmin[1] ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmin[2] ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmax[0] ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmax[1] ); + Com_ReadFloat( studioqc, false, &hitbox[numhitboxes].bmax[2] ); + numhitboxes++; + + return 0; +} + +/* +============== +Cmd_Attachment + +syntax: $attachment (old stuff) +============== +*/ +int Cmd_Attachment( void ) +{ + Com_ReadLong( studioqc, false, &attachment[numattachments].index ); // index + Com_ReadString( studioqc, false, attachment[numattachments].bonename ); // bone name + Com_ReadFloat( studioqc, false, &attachment[numattachments].org[0] ); // position + Com_ReadFloat( studioqc, false, &attachment[numattachments].org[1] ); + Com_ReadFloat( studioqc, false, &attachment[numattachments].org[2] ); + + // skip old stuff + Com_SkipRestOfLine( studioqc ); + + numattachments++; + return 0; +} + +/* +============== +Cmd_Renamebone + +syntax: $renamebone +============== +*/ +void Cmd_Renamebone( void ) +{ + Com_ReadString( studioqc, false, renamedbone[numrenamedbones].from ); + Com_ReadString( studioqc, false, renamedbone[numrenamedbones].to ); + numrenamedbones++; +} + + +/* +============== +Cmd_TexRenderMode + +syntax: $texrendermode "texture.bmp" "rendermode" +============== +*/ +void Cmd_TexRenderMode( void ) +{ + char tex_name[64]; + + Com_ReadString( studioqc, false, tex_name ); + Com_ReadToken( studioqc, 0, &token ); + + if( !com.stricmp( token.string, "additive" )) + texture[lookup_texture(tex_name)]->flags |= STUDIO_NF_ADDITIVE; + else if( !com.stricmp( token.string, "masked" )) + texture[lookup_texture(tex_name)]->flags |= STUDIO_NF_TRANSPARENT; + else if( !com.stricmp( token.string, "blended" )) + texture[lookup_texture(tex_name)]->flags |= STUDIO_NF_BLENDED; + else MsgDev( D_WARN, "Cmd_TexRenderMode: texture '%s' have unknown render mode '%s'!\n", tex_name, token.string ); + +} + +/* +============== +Cmd_Replace + +syntax: $replacetexture "oldname.bmp" "newname.bmp" +============== +*/ +void Cmd_Replace( void ) +{ + Com_ReadString( studioqc, false, sourcetexture[numrep] ); + Com_ReadString( studioqc, false, defaulttexture[numrep]); + numrep++; +} + +/* +============== +Cmd_CdSet + +syntax: $cd "path" +============== +*/ +void Cmd_CdSet( void ) +{ + if( !cdset ) + { + string path; + + cdset = true; + Com_ReadString( studioqc, false, path ); + FS_AddGameHierarchy( path ); + } + else MsgDev( D_WARN, "$cd already set\n" ); +} + +/* +============== +Cmd_CdSet + +syntax: $cdtexture "path" +============== +*/ +void Cmd_CdTextureSet( void ) +{ + if( cdtextureset < 16 ) + { + string path; + + cdtextureset++; + Com_ReadString( studioqc, false, path ); + FS_AddGameHierarchy( path ); + } + else MsgDev( D_WARN, "$cdtexture already set\n" ); +} + +/* +============== +Cmd_DebugBuild + +syntax: $debug +============== +*/ +void Cmd_DebugBuild( void ) +{ + ignore_errors = true; +} + +void ResetModelInfo( void ) +{ + default_scale = 1.0; + defaultzrotation = M_PI / 2; + + numrep = 0; + gamma = 1.8f; + flip_triangles = 1; + normal_blend = com.cos( DEG2RAD( 2.0f )); + dump_hboxes = 0; // FIXME: make an option + + com.strcpy( gs_filename, "model" ); + com.strcpy( sequencegroup.label, "default" ); + FS_ClearSearchPath( ); // clear all $cd and $cdtexture + + // set default model parms + FS_FileBase( gs_filename, modeloutname ); // kill path and ext + FS_DefaultExtension( modeloutname, ".mdl" ); // set new ext +} + +/* +============== +Cmd_StudioUnknown + +syntax: "blabla" +============== +*/ +void Cmd_StudioUnknown( const char *token ) +{ + MsgDev( D_WARN, "Cmd_StudioUnknown: skip command \"%s\"\n", token ); + Com_SkipRestOfLine( studioqc ); +} + +bool ParseModelScript( void ) +{ + ResetModelInfo(); + + while( 1 ) + { + if( !Com_ReadToken( studioqc, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + break; + + if( !com.stricmp( token.string, "$modelname" )) Cmd_Modelname (); + else if( !com.stricmp( token.string, "$cd")) Cmd_CdSet(); + else if( !com.stricmp( token.string, "$debug")) Cmd_DebugBuild(); + else if( !com.stricmp( token.string, "$cdtexture")) Cmd_CdTextureSet(); + else if( !com.stricmp( token.string, "$scale")) Cmd_ScaleUp (); + else if( !com.stricmp( token.string, "$rotate")) Cmd_Rotate(); + else if( !com.stricmp( token.string, "$root")) Cmd_Root (); + else if( !com.stricmp( token.string, "$pivot")) Cmd_Pivot (); + else if( !com.stricmp( token.string, "$controller")) Cmd_Controller (); + else if( !com.stricmp( token.string, "$body")) Cmd_Body(); + else if( !com.stricmp( token.string, "$bodygroup")) Cmd_Bodygroup(); + else if( !com.stricmp( token.string, "$sequence")) Cmd_Sequence (); + else if( !com.stricmp( token.string, "$eyeposition")) Cmd_Eyeposition (); + else if( !com.stricmp( token.string, "$origin")) Cmd_Origin (); + else if( !com.stricmp( token.string, "$bbox")) Cmd_BBox (); + else if( !com.stricmp( token.string, "$cbox")) Cmd_CBox (); + else if( !com.stricmp( token.string, "$mirrorbone")) Cmd_Mirror (); + else if( !com.stricmp( token.string, "$gamma")) Cmd_Gamma (); + else if( !com.stricmp( token.string, "$texturegroup")) Cmd_TextureGroup (); + else if( !com.stricmp( token.string, "$hgroup")) Cmd_Hitgroup (); + else if( !com.stricmp( token.string, "$hbox")) Cmd_Hitbox (); + else if( !com.stricmp( token.string, "$attachment")) Cmd_Attachment (); + else if( !com.stricmp( token.string, "$externaltextures")) split_textures = 1; + else if( !com.stricmp( token.string, "$cliptotextures")) clip_texcoords = 1; + else if( !com.stricmp( token.string, "$renamebone")) Cmd_Renamebone (); + else if( !com.stricmp( token.string, "$texrendermode")) Cmd_TexRenderMode(); + else if( !com.stricmp( token.string, "$replacetexture")) Cmd_Replace(); + else if( !Com_ValidScript( token.string, QC_STUDIOMDL )) return false; + else Cmd_StudioUnknown( token.string ); + } + + Com_CloseScript( studioqc ); + + // process model + SetSkinValues(); + SimplifyModel(); + + return true; +} + +void CreateModelScript( void ) +{ + file_t *f = FS_Open( "model.qc", "w" ); + search_t *t = FS_Search( "*.smd", false ); + int i; + + // header + FS_Printf( f, "\n$modelname \"model.mdl\"" ); + FS_Printf( f, "\n$cd \".\\\"" ); + FS_Printf( f, "\n$cdtexture \".\\\"" ); + FS_Printf( f, "\n$scale 1.0" ); + FS_Printf( f, "\n$cliptotextures\n\n" ); + + // body FIXME: parse for real reference + FS_Printf( f, "\n//reference mesh(es)" ); + FS_Printf( f, "\n$body \"studio\" \"reference\"" ); + + // write animations + for( i = 0; t && i < t->numfilenames; i++ ) + { + // FIXME: make cases for "attack_%" + FS_Printf( f, "$sequence \"t->filenames[i]\" \"t->filenames[i]\"" ); + if(com.stristr(t->filenames[i], "walk" )) FS_Printf( f, "LX fps 30 loop ACT_WALK 1\n" ); + else if(com.stristr(t->filenames[i], "run" )) FS_Printf( f, "LX fps 30 loop ACT_RUN 1\n" ); + else if(com.stristr(t->filenames[i], "idle")) FS_Printf( f, "fps 16 loop ACT_IDLE 50\n" ); + else FS_Printf( f, "fps 16\n" ); + } + FS_Printf(f, "\n" ); //finished + FS_Close( f ); +} + +void ClearModel( void ) +{ + cdset = false; + cdtextureset = 0; + ignore_errors = false; + + numrep = gflags = numseq = nummirrored = 0; + numxnodes = numrenamedbones = totalframes = numbones = numhitboxes = 0; + numhitgroups = numattachments = numtextures = numskinref = numskinfamilies = 0; + numbonecontrollers = numtexturegroups = numcommandnodes = numbodyparts = 0; + stripcount = numcommands = numani = allverts = alltris = totalseconds = 0; + nummodels = split_textures = 0; + + Mem_Set( numtexturelayers, 0, sizeof(int) * 32 ); + Mem_Set( numtexturereps, 0, sizeof(int) * 32 ); + Mem_Set( bodypart, 0, sizeof(s_bodypart_t) * MAXSTUDIOBODYPARTS ); + Mem_EmptyPool( studiopool ); // free all memory +} + +bool CompileCurrentModel( const char *name ) +{ + cdset = false; + cdtextureset = 0; + + if( name ) com.strcpy( gs_filename, name ); + FS_DefaultExtension( gs_filename, ".qc" ); + if( !FS_FileExists( gs_filename )) + { + // try to create qc-file + CreateModelScript(); + } + + studioqc = Com_OpenScript( gs_filename, NULL, 0 ); + if( studioqc ) + { + if( !ParseModelScript()) + return false; + WriteMDLFile(); + ClearModel(); + return true; + } + + Msg("%s not found\n", gs_filename ); + return false; +} + +bool CompileStudioModel ( byte *mempool, const char *name, byte parms ) +{ + if(mempool) studiopool = mempool; + else + { + Msg("Studiomdl: can't allocate memory pool.\nAbort compilation\n"); + return false; + } + return CompileCurrentModel( name ); +} \ No newline at end of file diff --git a/xtools/studio_utils.c b/xtools/studio_utils.c new file mode 100644 index 00000000..205d021e --- /dev/null +++ b/xtools/studio_utils.c @@ -0,0 +1,1100 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// studio_utils.c - common compiler funcs +//======================================================================= + +#include "mdllib.h" +typedef struct { int type; char *name; } activity_map_t; // studio activity map conversion +int used[MAXSTUDIOTRIANGLES]; // the command list holds counts and s/t values +short commands[MAXSTUDIOTRIANGLES * 13]; // that are valid for every frame +int stripverts[MAXSTUDIOTRIANGLES+2]; +int striptris[MAXSTUDIOTRIANGLES+2]; +int neighbortri[MAXSTUDIOTRIANGLES][3]; +int neighboredge[MAXSTUDIOTRIANGLES][3]; +s_trianglevert_t (*triangles)[3]; + +enum ai_activity +{ + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever + ACT_SPECIAL_ATTACK1,// very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, + ACT_VM_NONE, // weapon viewmodel animations + ACT_VM_DEPLOY, // deploy + ACT_VM_DEPLOY_EMPTY,// deploy empty weapon + ACT_VM_HOLSTER, // holster empty weapon + ACT_VM_HOLSTER_EMPTY, + ACT_VM_IDLE1, + ACT_VM_IDLE2, + ACT_VM_IDLE3, + ACT_VM_RANGE_ATTACK1, + ACT_VM_RANGE_ATTACK2, + ACT_VM_RANGE_ATTACK3, + ACT_VM_MELEE_ATTACK1, + ACT_VM_MELEE_ATTACK2, + ACT_VM_MELEE_ATTACK3, + ACT_VM_SHOOT_EMPTY, + ACT_VM_START_RELOAD, + ACT_VM_RELOAD, + ACT_VM_RELOAD_EMPTY, + ACT_VM_TURNON, + ACT_VM_TURNOFF, + ACT_VM_PUMP, // pumping gun + ACT_VM_PUMP_EMPTY, + ACT_VM_START_CHARGE, + ACT_VM_CHARGE, + ACT_VM_OVERLOAD, + ACT_VM_IDLE_EMPTY, +}; + +static activity_map_t activity_map[] = +{ +{ACT_IDLE, "ACT_IDLE" }, +{ACT_GUARD, "ACT_GUARD" }, +{ACT_WALK, "ACT_WALK" }, +{ACT_RUN, "ACT_RUN" }, +{ACT_FLY, "ACT_FLY" }, +{ACT_SWIM, "ACT_SWIM", }, +{ACT_HOP, "ACT_HOP", }, +{ACT_LEAP, "ACT_LEAP" }, +{ACT_FALL, "ACT_FALL" }, +{ACT_LAND, "ACT_LAND" }, +{ACT_STRAFE_LEFT, "ACT_STRAFE_LEFT" }, +{ACT_STRAFE_RIGHT, "ACT_STRAFE_RIGHT" }, +{ACT_ROLL_LEFT, "ACT_ROLL_LEFT" }, +{ACT_ROLL_RIGHT, "ACT_ROLL_RIGHT" }, +{ACT_TURN_LEFT, "ACT_TURN_LEFT" }, +{ACT_TURN_RIGHT, "ACT_TURN_RIGHT" }, +{ACT_CROUCH, "ACT_CROUCH" }, +{ACT_CROUCHIDLE, "ACT_CROUCHIDLE" }, +{ACT_STAND, "ACT_STAND" }, +{ACT_USE, "ACT_USE" }, +{ACT_SIGNAL1, "ACT_SIGNAL1" }, +{ACT_SIGNAL2, "ACT_SIGNAL2" }, +{ACT_SIGNAL3, "ACT_SIGNAL3" }, +{ACT_TWITCH, "ACT_TWITCH" }, +{ACT_COWER, "ACT_COWER" }, +{ACT_SMALL_FLINCH, "ACT_SMALL_FLINCH" }, +{ACT_BIG_FLINCH, "ACT_BIG_FLINCH" }, +{ACT_RANGE_ATTACK1, "ACT_RANGE_ATTACK1" }, +{ACT_RANGE_ATTACK2, "ACT_RANGE_ATTACK2" }, +{ACT_MELEE_ATTACK1, "ACT_MELEE_ATTACK1" }, +{ACT_MELEE_ATTACK2, "ACT_MELEE_ATTACK2" }, +{ACT_RELOAD, "ACT_RELOAD" }, +{ACT_ARM, "ACT_ARM" }, +{ACT_DISARM, "ACT_DISARM" }, +{ACT_EAT, "ACT_EAT" }, +{ACT_DIESIMPLE, "ACT_DIESIMPLE" }, +{ACT_DIEBACKWARD, "ACT_DIEBACKWARD" }, +{ACT_DIEFORWARD, "ACT_DIEFORWARD" }, +{ACT_DIEVIOLENT, "ACT_DIEVIOLENT" }, +{ACT_BARNACLE_HIT, "ACT_BARNACLE_HIT" }, +{ACT_BARNACLE_PULL, "ACT_BARNACLE_PULL" }, +{ACT_BARNACLE_CHOMP, "ACT_BARNACLE_CHOMP" }, +{ACT_BARNACLE_CHEW, "ACT_BARNACLE_CHEW" }, +{ACT_SLEEP, "ACT_SLEEP" }, +{ACT_INSPECT_FLOOR, "ACT_INSPECT_FLOOR" }, +{ACT_INSPECT_WALL, "ACT_INSPECT_WALL" }, +{ACT_IDLE_ANGRY, "ACT_IDLE_ANGRY" }, +{ACT_WALK_HURT, "ACT_WALK_HURT" }, +{ACT_RUN_HURT, "ACT_RUN_HURT" }, +{ACT_HOVER, "ACT_HOVER" }, +{ACT_GLIDE, "ACT_GLIDE" }, +{ACT_FLY_LEFT, "ACT_FLY_LEFT" }, +{ACT_FLY_RIGHT, "ACT_FLY_RIGHT" }, +{ACT_DETECT_SCENT, "ACT_DETECT_SCENT" }, +{ACT_SNIFF, "ACT_SNIFF" }, +{ACT_BITE, "ACT_BITE" }, +{ACT_THREAT_DISPLAY, "ACT_THREAT_DISPLAY" }, +{ACT_FEAR_DISPLAY, "ACT_FEAR_DISPLAY" }, +{ACT_EXCITED, "ACT_EXCITED" }, +{ACT_SPECIAL_ATTACK1, "ACT_SPECIAL_ATTACK1" }, +{ACT_SPECIAL_ATTACK2, "ACT_SPECIAL_ATTACK2" }, +{ACT_COMBAT_IDLE, "ACT_COMBAT_IDLE" }, +{ACT_WALK_SCARED, "ACT_WALK_SCARED" }, +{ACT_RUN_SCARED, "ACT_RUN_SCARED" }, +{ACT_VICTORY_DANCE, "ACT_VICTORY_DANCE" }, +{ACT_DIE_HEADSHOT, "ACT_DIE_HEADSHOT" }, +{ACT_DIE_CHESTSHOT, "ACT_DIE_CHESTSHOT" }, +{ACT_DIE_GUTSHOT, "ACT_DIE_GUTSHOT" }, +{ACT_DIE_BACKSHOT, "ACT_DIE_BACKSHOT" }, +{ACT_FLINCH_HEAD, "ACT_FLINCH_HEAD" }, +{ACT_FLINCH_CHEST, "ACT_FLINCH_CHEST" }, +{ACT_FLINCH_STOMACH, "ACT_FLINCH_STOMACH" }, +{ACT_FLINCH_LEFTARM, "ACT_FLINCH_LEFTARM" }, +{ACT_FLINCH_RIGHTARM, "ACT_FLINCH_RIGHTARM" }, +{ACT_FLINCH_LEFTLEG, "ACT_FLINCH_LEFTLEG" }, +{ACT_FLINCH_RIGHTLEG, "ACT_FLINCH_RIGHTLEG" }, +{ACT_VM_NONE, "ACT_VM_NONE" }, // invalid animation +{ACT_VM_DEPLOY, "ACT_VM_DEPLOY" }, // deploy +{ACT_VM_DEPLOY_EMPTY, "ACT_VM_DEPLOY_EMPTY" }, // deploy empty weapon +{ACT_VM_HOLSTER, "ACT_VM_HOLSTER" }, // holster empty weapon +{ACT_VM_HOLSTER_EMPTY, "ACT_VM_HOLSTER_EMPTY" }, +{ACT_VM_IDLE1, "ACT_VM_IDLE1" }, +{ACT_VM_IDLE2, "ACT_VM_IDLE2" }, +{ACT_VM_IDLE3, "ACT_VM_IDLE3" }, +{ACT_VM_RANGE_ATTACK1, "ACT_VM_RANGE_ATTACK1" }, +{ACT_VM_RANGE_ATTACK2, "ACT_VM_RANGE_ATTACK2" }, +{ACT_VM_RANGE_ATTACK3, "ACT_VM_RANGE_ATTACK3" }, +{ACT_VM_MELEE_ATTACK1, "ACT_VM_MELEE_ATTACK1" }, +{ACT_VM_MELEE_ATTACK2, "ACT_VM_MELEE_ATTACK2" }, +{ACT_VM_MELEE_ATTACK3, "ACT_VM_MELEE_ATTACK3" }, +{ACT_VM_SHOOT_EMPTY, "ACT_VM_SHOOT_EMPTY" }, +{ACT_VM_START_RELOAD, "ACT_VM_START_RELOAD" }, +{ACT_VM_RELOAD, "ACT_VM_RELOAD" }, +{ACT_VM_RELOAD_EMPTY, "ACT_VM_RELOAD_EMPTY" }, +{ACT_VM_TURNON, "ACT_VM_TURNON" }, +{ACT_VM_TURNOFF, "ACT_VM_TURNOFF" }, +{ACT_VM_PUMP, "ACT_VM_PUMP" }, // user animations +{ACT_VM_PUMP_EMPTY, "ACT_VM_PUMP_EMPTY" }, +{ACT_VM_START_CHARGE, "ACT_VM_START_CHARGE" }, +{ACT_VM_CHARGE, "ACT_VM_CHARGE" }, +{ACT_VM_OVERLOAD, "ACT_VM_OVERLOAD" }, +{ACT_VM_IDLE_EMPTY, "ACT_VM_IDLE_EMPTY" }, +{0, NULL }, +}; + +void OptimizeAnimations( void ) +{ + int i, j; + int n, m; + int type; + int q; + int iError = 0; + + // optimize animations + for (i = 0; i < numseq; i++) + { + sequence[i]->numframes = sequence[i]->panim[0]->endframe - sequence[i]->panim[0]->startframe + 1; + + // force looping animations to be looping + if( sequence[i]->flags & STUDIO_LOOPING ) + { + for( j = 0; j < sequence[i]->panim[0]->numbones; j++ ) + { + for( q = 0; q < sequence[i]->numblends; q++ ) + { + vec3_t *ppos = sequence[i]->panim[q]->pos[j]; + vec3_t *prot = sequence[i]->panim[q]->rot[j]; + + n = 0; // sequence[i]->panim[q]->startframe; + m = sequence[i]->numframes - 1; + + type = sequence[i]->motiontype; + if (!(type & STUDIO_LX)) ppos[m][0] = ppos[n][0]; + if (!(type & STUDIO_LY)) ppos[m][1] = ppos[n][1]; + if (!(type & STUDIO_LZ)) ppos[m][2] = ppos[n][2]; + + prot[m][0] = prot[n][0]; + prot[m][1] = prot[n][1]; + prot[m][2] = prot[n][2]; + } + } + } + + for( j = 0; j < sequence[i]->numevents; j++ ) + { + if( sequence[i]->event[j].frame < sequence[i]->panim[0]->startframe ) + { + Msg( "sequence %s has event (%d) before first frame (%d)\n", sequence[i]->name, sequence[i]->event[j].frame, sequence[i]->panim[0]->startframe ); + sequence[i]->event[j].frame = sequence[i]->panim[0]->startframe; + iError++; + } + if( sequence[i]->event[j].frame > sequence[i]->panim[0]->endframe ) + { + Msg( "sequence %s has event (%d) after last frame (%d)\n", sequence[i]->name, sequence[i]->event[j].frame, sequence[i]->panim[0]->endframe ); + sequence[i]->event[j].frame = sequence[i]->panim[0]->endframe; + iError++; + } + } + + sequence[i]->frameoffset = sequence[i]->panim[0]->startframe; + } +} + +void FindNeighbor( int starttri, int startv ) +{ + s_trianglevert_t m1, m2; + s_trianglevert_t *last, *check; + int j, k; + + // used[starttri] |= (1 << startv); + + last = &triangles[starttri][0]; + + m1 = last[(startv+1)%3]; + m2 = last[(startv+0)%3]; + + for (j = starttri + 1, check = &triangles[starttri + 1][0]; j < pmesh->numtris; j++, check += 3) + { + if (used[j] == 7) continue; + for (k = 0; k < 3; k++) + { + if (memcmp(&check[k],&m1,sizeof(m1))) continue; + if (memcmp(&check[ (k+1)%3 ],&m2,sizeof(m2))) continue; + + neighbortri[starttri][startv] = j; + neighboredge[starttri][startv] = k; + + neighbortri[j][k] = starttri; + neighboredge[j][k] = startv; + + used[starttri] |= (1 << startv); + used[j] |= (1 << k); + return; + } + } +} + +int StripLength( int starttri, int startv ) +{ + int j, k; + + used[starttri] = 2; + + stripverts[0] = (startv)%3; + stripverts[1] = (startv+1)%3; + stripverts[2] = (startv+2)%3; + + striptris[0] = starttri; + striptris[1] = starttri; + striptris[2] = starttri; + stripcount = 3; + + while( 1 ) + { + if (stripcount & 1) + { + j = neighbortri[starttri][(startv+1)%3]; + k = neighboredge[starttri][(startv+1)%3]; + } + else + { + j = neighbortri[starttri][(startv+2)%3]; + k = neighboredge[starttri][(startv+2)%3]; + } + if (j == -1 || used[j]) goto done; + + stripverts[stripcount] = (k+2)%3; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + starttri = j; + startv = k; + } + +done: + + // clear the temp used flags + for (j = 0; j < pmesh->numtris; j++) + if (used[j] == 2) used[j] = 0; + + return stripcount; +} + +int FanLength (int starttri, int startv) +{ + int j, k; + + used[starttri] = 2; + + stripverts[0] = (startv)%3; + stripverts[1] = (startv+1)%3; + stripverts[2] = (startv+2)%3; + + striptris[0] = starttri; + striptris[1] = starttri; + striptris[2] = starttri; + stripcount = 3; + + while( 1 ) + { + j = neighbortri[starttri][(startv+2)%3]; + k = neighboredge[starttri][(startv+2)%3]; + + if (j == -1 || used[j]) goto done; + + stripverts[stripcount] = (k+2)%3; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + starttri = j; + startv = k; + } + +done: + // clear the temp used flags + for (j=0 ; jnumtris ; j++) + if (used[j] == 2) used[j] = 0; + + return stripcount; +} + +int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata ) +{ + int i, j, k, m; + int startv; + int len, bestlen, besttype; + int bestverts[MAXSTUDIOTRIANGLES]; + int besttris[MAXSTUDIOTRIANGLES]; + int peak[MAXSTUDIOTRIANGLES]; + int type; + int total = 0; + long t; + int maxlen; + + triangles = x; + pmesh = y; + + + t = time( NULL ); + + for (i = 0; i < pmesh->numtris; i++) + { + neighbortri[i][0] = neighbortri[i][1] = neighbortri[i][2] = -1; + used[i] = 0; + peak[i] = pmesh->numtris; + } + + for (i = 0; i < pmesh->numtris; i++) + { + for (k = 0; k < 3; k++) + { + if (used[i] & (1 << k)) continue; + FindNeighbor( i, k ); + } + } + + // build tristrips + numcommandnodes = 0; + numcommands = 0; + Mem_Set (used, 0, sizeof(used)); + + for (i=0 ; inumtris ;) + { + // pick an unused triangle and start the trifan + if (used[i]) + { + i++; + continue; + } + + maxlen = 9999; + bestlen = 0; + m = 0; + for (k = i; k < pmesh->numtris && bestlen < 127; k++) + { + int localpeak = 0; + + if (used[k]) continue; + if (peak[k] <= bestlen) continue; + + m++; + for (type = 0 ; type < 2 ; type++) + { + for (startv =0 ; startv < 3 ; startv++) + { + if (type == 1) len = FanLength (k, startv); + else len = StripLength (k, startv); + + if (len > 127) + { + // skip these, they are too long to encode + } + else if (len > bestlen) + { + besttype = type; + bestlen = len; + for (j = 0; j < bestlen; j++) + { + besttris[j] = striptris[j]; + bestverts[j] = stripverts[j]; + } + } + if (len > localpeak) localpeak = len; + } + } + + peak[k] = localpeak; + if (localpeak == maxlen) break; + } + + total += (bestlen - 2); + maxlen = bestlen; + + // mark the tris on the best strip as used + for (j = 0; j < bestlen; j++) + used[besttris[j]] = 1; + + if (besttype == 1) commands[numcommands++] = -bestlen; + else commands[numcommands++] = bestlen; + + for (j = 0; j < bestlen; j++) + { + s_trianglevert_t *tri; + tri = &triangles[besttris[j]][bestverts[j]]; + + commands[numcommands++] = tri->vertindex; + commands[numcommands++] = tri->normindex; + commands[numcommands++] = tri->s; + commands[numcommands++] = tri->t; + } + numcommandnodes++; + + if (t != time(NULL)) + { + Msg("%2d%%\r", (total * 100) / pmesh->numtris ); + t = time(NULL); + } + } + + commands[numcommands++] = 0;// end of list marker + *ppdata = (byte *)commands; + + return numcommands * sizeof( short ); +} + +void ExtractMotion( void ) +{ + int i, j, k, q; + + // extract linear motion + for (i = 0; i < numseq; i++) + { + if (sequence[i]->numframes > 1) + { + // assume 0 for now. + int type; + vec3_t *ppos; + vec3_t motion = {0,0,0}; + + type = sequence[i]->motiontype; + ppos = sequence[i]->panim[0]->pos[0]; + k = sequence[i]->numframes - 1; + + if (type & STUDIO_LX) motion[0] = ppos[k][0] - ppos[0][0]; + if (type & STUDIO_LY) motion[1] = ppos[k][1] - ppos[0][1]; + if (type & STUDIO_LZ) motion[2] = ppos[k][2] - ppos[0][2]; + + for (j = 0; j < sequence[i]->numframes; j++) + { + vec3_t adj; + for (k = 0; k < sequence[i]->panim[0]->numbones; k++) + { + if (sequence[i]->panim[0]->node[k].parent == -1) + { + ppos = sequence[i]->panim[0]->pos[k]; + + VectorScale( motion, j * 1.0 / (sequence[i]->numframes - 1), adj ); + for (q = 0; q < sequence[i]->numblends; q++) + { + VectorSubtract( sequence[i]->panim[q]->pos[k][j], adj, sequence[i]->panim[q]->pos[k][j] ); + } + } + } + } + VectorCopy( motion, sequence[i]->linearmovement ); + } + else + { + VectorSubtract( sequence[i]->linearmovement, sequence[i]->linearmovement, sequence[i]->linearmovement ); + } + } + + // extract unused motion + for (i = 0; i < numseq; i++) + { + int type = sequence[i]->motiontype; + + for (k = 0; k < sequence[i]->panim[0]->numbones; k++) + { + if (sequence[i]->panim[0]->node[k].parent == -1) + { + for (q = 0; q < sequence[i]->numblends; q++) + { + float motion[6]; + motion[0] = sequence[i]->panim[q]->pos[k][0][0]; + motion[1] = sequence[i]->panim[q]->pos[k][0][1]; + motion[2] = sequence[i]->panim[q]->pos[k][0][2]; + motion[3] = sequence[i]->panim[q]->rot[k][0][0]; + motion[4] = sequence[i]->panim[q]->rot[k][0][1]; + motion[5] = sequence[i]->panim[q]->rot[k][0][2]; + + for (j = 0; j < sequence[i]->numframes; j++) + { + //if (type & STUDIO_X) sequence[i]->panim[q]->pos[k][j][0] = motion[0]; + //if (type & STUDIO_Y) sequence[i]->panim[q]->pos[k][j][1] = motion[1]; + //if (type & STUDIO_Z) sequence[i]->panim[q]->pos[k][j][2] = motion[2]; + if (type & STUDIO_XR) sequence[i]->panim[q]->rot[k][j][0] = motion[3]; + if (type & STUDIO_YR) sequence[i]->panim[q]->rot[k][j][1] = motion[4]; + if (type & STUDIO_ZR) sequence[i]->panim[q]->rot[k][j][2] = motion[5]; + } + } + } + } + } + + // extract auto motion + for (i = 0; i < numseq; i++) + { + // assume 0 for now. + int type; + vec3_t *ppos; + vec3_t *prot; + vec3_t motion = {0,0,0}; + vec3_t angles = {0,0,0}; + + type = sequence[i]->motiontype; + + for (j = 0; j < sequence[i]->numframes; j++) + { + ppos = sequence[i]->panim[0]->pos[0]; + prot = sequence[i]->panim[0]->rot[0]; + + if (type & STUDIO_AX) motion[0] = ppos[j][0] - ppos[0][0]; + if (type & STUDIO_AY) motion[1] = ppos[j][1] - ppos[0][1]; + if (type & STUDIO_AZ) motion[2] = ppos[j][2] - ppos[0][2]; + if (type & STUDIO_AXR) angles[0] = prot[j][0] - prot[0][0]; + if (type & STUDIO_AYR) angles[1] = prot[j][1] - prot[0][1]; + if (type & STUDIO_AZR) angles[2] = prot[j][2] - prot[0][2]; + + VectorCopy( motion, sequence[i]->automovepos[j] ); + VectorCopy( angles, sequence[i]->automoveangle[j] ); + + for (k = 0; k < sequence[i]->panim[0]->numbones; k++) + { + if (sequence[i]->panim[0]->node[k].parent == -1) + { + for (q = 0; q < sequence[i]->numblends; q++) + { + // VectorSubtract( sequence[i]->panim[q]->pos[k][j], motion, sequence[i]->panim[q]->pos[k][j] ); + // VectorSubtract( sequence[i]->panim[q]->rot[k][j], angles, sequence[i]->panim[q]->pos[k][j] ); + } + } + } + } + } +} + + +int findNode( char *name ) +{ + int k; + + for (k = 0; k < numbones; k++) + { + if(!com.strcmp( bonetable[k].name, name )) + return k; + } + return -1; +} + + +void MakeTransitions( void ) +{ + int i, j, k; + int iHit; + + // add in direct node transitions + for (i = 0; i < numseq; i++) + { + if (sequence[i]->entrynode != sequence[i]->exitnode) + { + xnode[sequence[i]->entrynode-1][sequence[i]->exitnode-1] = sequence[i]->exitnode; + if (sequence[i]->nodeflags) + xnode[sequence[i]->exitnode-1][sequence[i]->entrynode-1] = sequence[i]->entrynode; + } + if (sequence[i]->entrynode > numxnodes) numxnodes = sequence[i]->entrynode; + } + + // add multi-stage transitions + do + { + iHit = 0; + for (i = 1; i <= numxnodes; i++) + { + for (j = 1; j <= numxnodes; j++) + { + // if I can't go there directly + if (i != j && xnode[i-1][j-1] == 0) + { + for (k = 1; k < numxnodes; k++) + { + // but I found someone who knows how that I can get to + if (xnode[k-1][j-1] > 0 && xnode[i-1][k-1] > 0) + { + // then go to them + xnode[i-1][j-1] = -xnode[i-1][k-1]; + iHit = 1; + break; + } + } + } + } + } + // reset previous pass so the links can be used in the next pass + for (i = 1; i <= numxnodes; i++) + for (j = 1; j <= numxnodes; j++) + xnode[i-1][j-1] = abs( xnode[i-1][j-1] ); + } + while(iHit); +} + +int lookup_texture( char *texturename ) +{ + int i; + + for( i = 0; i < numtextures; i++ ) + { + if(!com.stricmp( texture[i]->name, texturename )) + return i; + } + + if( i >= MAXSTUDIOSKINS ) Sys_Break( "too many textures in model\n" ); + + // allocate a new one + texture[i] = Mem_Alloc( studiopool, sizeof(s_texture_t)); + com.strncpy( texture[i]->name, texturename, sizeof(texture[i]->name)); + + if(com.stristr( texturename, "chrome" ) != NULL) + texture[i]->flags = STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME; + else if(com.stristr( texturename, "bright" ) != NULL) + texture[i]->flags = STUDIO_NF_FLATSHADE | STUDIO_NF_FULLBRIGHT; + else texture[i]->flags = 0; + + numtextures++; + return i; +} + +s_mesh_t *lookup_mesh( s_model_t *pmodel, char *texturename ) +{ + int i, j; + + j = lookup_texture( texturename ); + + for( i = 0; i < pmodel->nummesh; i++ ) + { + if (pmodel->pmesh[i]->skinref == j) + return pmodel->pmesh[i]; + } + + if (i >= MAXSTUDIOMESHES) Sys_Break( "too many meshes in model: \"%s\"\n", pmodel->name ); + + pmodel->nummesh = i + 1; + pmodel->pmesh[i] = Kalloc( sizeof( s_mesh_t ) ); + pmodel->pmesh[i]->skinref = j; + + return pmodel->pmesh[i]; +} + +s_trianglevert_t *lookup_triangle( s_mesh_t *pmesh, int index ) +{ + if( index >= pmesh->alloctris ) + { + int start = pmesh->alloctris; + pmesh->alloctris = index + 256; + pmesh->triangle = Realloc( pmesh->triangle, pmesh->alloctris * sizeof( *pmesh->triangle )); + } + return pmesh->triangle[index]; +} + +int lookup_normal( s_model_t *pmodel, s_normal_t *pnormal ) +{ + int i; + + for (i = 0; i < pmodel->numnorms; i++) + { + if (DotProduct( pmodel->normal[i].org, pnormal->org ) > normal_blend && pmodel->normal[i].bone == pnormal->bone && pmodel->normal[i].skinref == pnormal->skinref) + return i; + } + + if (i >= MAXSTUDIOVERTS) Sys_Break( "too many normals in model: \"%s\"\n", pmodel->name); + + VectorCopy( pnormal->org, pmodel->normal[i].org ); + pmodel->normal[i].bone = pnormal->bone; + pmodel->normal[i].skinref = pnormal->skinref; + pmodel->numnorms = i + 1; + return i; +} + +int lookup_vertex( s_model_t *pmodel, s_vertex_t *pv ) +{ + int i; + + // assume 2 digits of accuracy + pv->org[0] = (int)(pv->org[0] * 100) / 100.0; + pv->org[1] = (int)(pv->org[1] * 100) / 100.0; + pv->org[2] = (int)(pv->org[2] * 100) / 100.0; + + for (i = 0; i < pmodel->numverts; i++) + { + if (VectorCompare( pmodel->vert[i].org, pv->org ) && pmodel->vert[i].bone == pv->bone) + return i; + } + + if (i >= MAXSTUDIOVERTS) Sys_Break( "too many vertices in model: \"%s\"\n", pmodel->name); + + VectorCopy( pv->org, pmodel->vert[i].org ); + pmodel->vert[i].bone = pv->bone; + pmodel->numverts = i + 1; + return i; +} + +int lookupControl( char *string ) +{ + if (stricmp(string,"X")==0) return STUDIO_X; + if (stricmp(string,"Y")==0) return STUDIO_Y; + if (stricmp(string,"Z")==0) return STUDIO_Z; + if (stricmp(string,"XR")==0) return STUDIO_XR; + if (stricmp(string,"YR")==0) return STUDIO_YR; + if (stricmp(string,"ZR")==0) return STUDIO_ZR; + if (stricmp(string,"LX")==0) return STUDIO_LX; + if (stricmp(string,"LY")==0) return STUDIO_LY; + if (stricmp(string,"LZ")==0) return STUDIO_LZ; + if (stricmp(string,"AX")==0) return STUDIO_AX; + if (stricmp(string,"AY")==0) return STUDIO_AY; + if (stricmp(string,"AZ")==0) return STUDIO_AZ; + if (stricmp(string,"AXR")==0) return STUDIO_AXR; + if (stricmp(string,"AYR")==0) return STUDIO_AYR; + if (stricmp(string,"AZR")==0) return STUDIO_AZR; + return -1; +} + +void adjust_vertex( float *org ) +{ + org[0] = (org[0] - adjust[0]); + org[1] = (org[1] - adjust[1]); + org[2] = (org[2] - adjust[2]); +} + +void scale_vertex( float *org ) +{ + float tmp = org[0]; + org[0] = org[0] * scale_up; + org[1] = org[1] * scale_up; + org[2] = org[2] * scale_up; +} + +void clip_rotations( vec3_t rot ) +{ + int j;// clip everything to : -M_PI <= x < M_PI + + for (j = 0; j < 3; j++) + { + while (rot[j] >= M_PI) rot[j] -= M_PI*2; + while (rot[j] < -M_PI) rot[j] += M_PI*2; + } +} + +int lookupActivity( char *szActivity ) +{ + int i; + + for (i = 0; activity_map[i].name; i++) + { + if (!com.stricmp( szActivity, activity_map[i].name )) + return activity_map[i].type; + } + + // match ACT_# + if (!com.strnicmp( szActivity, "ACT_", 4 )) + return com.atoi( &szActivity[4] ); + return 0; +} + +void TextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ) +{ + int i, j; + + if (ptexture->flags & STUDIO_NF_CHROME) + { + ptexture->skintop = 0; + ptexture->skinleft = 0; + ptexture->skinwidth = (ptexture->srcwidth + 3) & ~3; + ptexture->skinheight = ptexture->srcheight; + + for (i = 0; i < pmesh->numtris; i++) + { + for (j = 0; j < 3; j++) + { + pmesh->triangle[i][j].s = 0; + pmesh->triangle[i][j].t = 0; + } + ptexture->max_s = 63; + ptexture->min_s = 0; + ptexture->max_t = 63; + ptexture->min_t = 0; + } + return; + } + + for (i = 0; i < pmesh->numtris; i++) + { + for (j = 0; j < 3; j++) + { + if (pmesh->triangle[i][j].u > 2.0) pmesh->triangle[i][j].u = 2.0; + if (pmesh->triangle[i][j].u < -1.0) pmesh->triangle[i][j].u = -1.0; + if (pmesh->triangle[i][j].v > 2.0) pmesh->triangle[i][j].v = 2.0; + if (pmesh->triangle[i][j].v < -1.0) pmesh->triangle[i][j].v = -1.0; + } + } + + // pack texture coords + if (!clip_texcoords) + { + int k, n; + + do + { + float min_u = 10; + float max_u = -10; + float k_max_u, n_min_u; + k = -1; + n = -1; + + for (i = 0; i < pmesh->numtris; i++) + { + float local_min, local_max; + local_min = min( pmesh->triangle[i][0].u, min( pmesh->triangle[i][1].u, pmesh->triangle[i][2].u )); + local_max = max( pmesh->triangle[i][0].u, max( pmesh->triangle[i][1].u, pmesh->triangle[i][2].u )); + if (local_min < min_u) { min_u = local_min; k = i; k_max_u = local_max; } + if (local_max > max_u) { max_u = local_max; n = i; n_min_u = local_min; } + } + + if (k_max_u + 1.0 < max_u) + { + for (j = 0; j < 3; j++) pmesh->triangle[k][j].u += 1.0; + } + else if (n_min_u - 1.0 > min_u) + { + for (j = 0; j < 3; j++) pmesh->triangle[n][j].u -= 1.0; + } + else break; + } while (1); + + do + { + float min_v = 10; + float max_v = -10; + float k_max_v, n_min_v; + k = -1; + n = -1; + + for (i = 0; i < pmesh->numtris ; i++) + { + float local_min, local_max; + local_min = min( pmesh->triangle[i][0].v, min( pmesh->triangle[i][1].v, pmesh->triangle[i][2].v )); + local_max = max( pmesh->triangle[i][0].v, max( pmesh->triangle[i][1].v, pmesh->triangle[i][2].v )); + if (local_min < min_v) { min_v = local_min; k = i; k_max_v = local_max; } + if (local_max > max_v) { max_v = local_max; n = i; n_min_v = local_min; } + } + + if (k_max_v + 1.0 < max_v) + { + for (j = 0; j < 3; j++) pmesh->triangle[k][j].v += 1.0; + } + else if (n_min_v - 1.0 > min_v) + { + for (j = 0; j < 3; j++) pmesh->triangle[n][j].v -= 1.0; + } + else break; + } while (1); + } + else + { + for (i = 0; i < pmesh->numtris; i++) + { + for (j = 0; j < 3; j++) + { + if (pmesh->triangle[i][j].u < 0) pmesh->triangle[i][j].u = 0; + if (pmesh->triangle[i][j].u > 1) pmesh->triangle[i][j].u = 1; + if (pmesh->triangle[i][j].v < 0) pmesh->triangle[i][j].v = 0; + if (pmesh->triangle[i][j].v > 1) pmesh->triangle[i][j].v = 1; + } + } + } + + // convert to pixel coordinates + for (i = 0; i < pmesh->numtris ; i++) + { + for (j = 0; j < 3; j++) + { + // FIXME: losing texture coord resultion! + pmesh->triangle[i][j].s = pmesh->triangle[i][j].u * (ptexture->srcwidth - 1); + pmesh->triangle[i][j].t = pmesh->triangle[i][j].v * (ptexture->srcheight - 1); + } + } + + // find the range + if (!clip_texcoords) + { + for (i = 0; i < pmesh->numtris; i++) + { + for (j = 0; j < 3; j++) + { + ptexture->max_s = max( pmesh->triangle[i][j].s, ptexture->max_s ); + ptexture->min_s = min( pmesh->triangle[i][j].s, ptexture->min_s ); + ptexture->max_t = max( pmesh->triangle[i][j].t, ptexture->max_t ); + ptexture->min_t = min( pmesh->triangle[i][j].t, ptexture->min_t ); + } + } + } + else + { + ptexture->max_s = ptexture->srcwidth-1; + ptexture->min_s = 0; + ptexture->max_t = ptexture->srcheight-1; + ptexture->min_t = 0; + } +} + +void ResetTextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ) +{ + int i, j; + + // adjust top, left edge + for (i=0 ; inumtris ; i++) + { + for (j = 0; j < 3; j++) + { + pmesh->triangle[i][j].s -= ptexture->min_s; + pmesh->triangle[i][j].t = (ptexture->max_t - ptexture->min_t) - (pmesh->triangle[i][j].t - ptexture->min_t); + } + } +} + +void ResizeTexture( s_texture_t *ptexture ) +{ + int i, j, s, t; + byte *pdest; + int srcadjwidth; + float percent; + + // make the width a multiple of 4; some hardware requires this, and it ensures + // dword alignment for each scan + ptexture->skintop = ptexture->min_t; + ptexture->skinleft = ptexture->min_s; + ptexture->skinwidth = (int)((ptexture->max_s - ptexture->min_s) + 1 + 3) & ~3; + ptexture->skinheight = (int)(ptexture->max_t - ptexture->min_t) + 1; + ptexture->size = ptexture->skinwidth * ptexture->skinheight + 256 * 3; + percent = ((ptexture->skinwidth * ptexture->skinheight) / (float)(ptexture->srcwidth * ptexture->srcheight)) * 100.0f; + + Msg("BMP %s [%d %d] (%.0f%%) %6s\n", ptexture->name, ptexture->skinwidth, ptexture->skinheight, percent, memprint( ptexture->size )); + + if( ptexture->size > 1536 * 1536) + { + Msg("%g %g %g %g\n", ptexture->min_s, ptexture->max_s, ptexture->min_t, ptexture->max_t ); + Sys_Break("texture too large\n"); + } + + pdest = Kalloc( ptexture->size ); + ptexture->pdata = pdest; + + // data is saved as a multiple of 4 + srcadjwidth = (ptexture->srcwidth + 3) & ~3; + + // move the picture data to the model area, replicating missing data, deleting unused data. + for (i = 0, t = ptexture->srcheight - ptexture->skinheight - ptexture->skintop + 10 * ptexture->srcheight; i < ptexture->skinheight; i++, t++) + { + while (t >= ptexture->srcheight) t -= ptexture->srcheight; + while (t < 0) t += ptexture->srcheight; + + for (j = 0, s = ptexture->skinleft + 10 * ptexture->srcwidth; j < ptexture->skinwidth; j++, s++) + { + while (s >= ptexture->srcwidth) s -= ptexture->srcwidth; + *(pdest++) = *(ptexture->ppicture + s + t * srcadjwidth); + } + } + + // TODO: process the texture and flag it if fullbright or transparent are used. + // TODO: only save as many palette entries as are actually used. + + if( gamma != 1.8 ) + { + // gamma correct the monster textures to a gamma of 1.8 + float g; + byte *psrc = (byte *)ptexture->ppal; + g = gamma / 1.8; + + for (i = 0; i < 768; i++) pdest[i] = pow( psrc[i] / 255.0, g ) * 255; + } + else Mem_Copy( pdest, ptexture->ppal, 256 * sizeof( rgb_t )); + + Mem_Free( ptexture->ppicture ); + Mem_Free( ptexture->ppal ); +} \ No newline at end of file diff --git a/xtools/utils.c b/xtools/utils.c new file mode 100644 index 00000000..bdcbec9e --- /dev/null +++ b/xtools/utils.c @@ -0,0 +1,54 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// utils.c - platform utils +//======================================================================= + +#include "xtools.h" +#include "byteorder.h" +#include "utils.h" +#include "mdllib.h" + +string gs_basedir; // initial dir before loading gameinfo.txt (used for compilers too) +string gs_filename; // used for compilers only + +/* +================ +Com_ValidScript + +validate qc-script for unexcpected keywords +================ +*/ +bool Com_ValidScript( const char *token, qctype_t scripttype ) +{ + if( !com.stricmp( token, "$spritename") && scripttype != QC_SPRITEGEN ) + { + Msg( "%s probably spritegen qc.script, skipping...\n", gs_filename ); + return false; + } + else if( !com.stricmp( token, "$resample" ) && scripttype != QC_SPRITEGEN ) + { + Msg( "%s probably spritegen qc.script, skipping...\n", gs_filename ); + return false; + } + else if( !com.stricmp( token, "$modelname" ) && scripttype != QC_STUDIOMDL ) + { + Msg( "%s probably studio qc.script, skipping...\n", gs_filename ); + return false; + } + else if( !com.stricmp( token, "$body" ) && scripttype != QC_STUDIOMDL ) + { + Msg( "%s probably studio qc.script, skipping...\n", gs_filename ); + return false; + } + else if( !com.stricmp( token, "$wadname" ) && scripttype != QC_WADLIB ) + { + Msg( "%s probably wadlib qc.script, skipping...\n", gs_filename ); + return false; + } + else if( !com.stricmp( token, "$mipmap" ) && scripttype != QC_WADLIB ) + { + Msg("%s probably wadlib qc.script, skipping...\n", gs_filename ); + return false; + } + return true; +}; diff --git a/xtools/utils.h b/xtools/utils.h new file mode 100644 index 00000000..aef765d6 --- /dev/null +++ b/xtools/utils.h @@ -0,0 +1,44 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// utils.h - shared utilities +//======================================================================= +#ifndef UTILS_H +#define UTILS_H + +#include + +// bsplib compile flags +#define ALIGN( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) + +extern byte *basepool; +extern byte *zonepool; +extern bool enable_log; +extern stdlib_api_t com; + +#define Sys_Error com.error +#define Malloc(size) Mem_Alloc( basepool, size ) + +extern string gs_filename; +extern string gs_basedir; +extern byte *error_bmp; +extern size_t error_bmp_size; + +typedef enum +{ + QC_SPRITEGEN = 1, + QC_STUDIOMDL, + QC_ROQLIB, + QC_WADLIB +} qctype_t; + +bool Com_ValidScript( const char *token, qctype_t script_type ); + +// misc +bool CompileStudioModel( byte *mempool, const char *name, byte parms ); +bool CompileSpriteModel( byte *mempool, const char *name, byte parms ); +bool CompileWad3Archive( byte *mempool, const char *name, byte parms ); +bool CompileDPVideo( byte *mempool, const char *name, byte parms ); +bool PrepareBSPModel( const char *dir, const char *name ); +bool CompileBSPModel( void ); + +#endif//UTILS_H \ No newline at end of file diff --git a/xtools/wadlib.c b/xtools/wadlib.c new file mode 100644 index 00000000..0100af2b --- /dev/null +++ b/xtools/wadlib.c @@ -0,0 +1,569 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// wadlib.c.c - wad archive compiler +//======================================================================= + +#include "xtools.h" +#include "byteorder.h" +#include "const.h" +#include "utils.h" +#include "mathlib.h" + +string wadoutname; +bool wad_append = false; +script_t *wadqc = NULL; +wfile_t *handle = NULL; +string lumpname; +byte *wadpool; + +float linearpalette[256][3]; +int color_used[256]; +float maxdistortion; +int colors_used; +byte pixdata[256]; +rgbdata_t *image = NULL; +vec3_t d_color; + +byte Pal_AddColor( float r, float g, float b ) +{ + int i; + + for( i = 0; i < 255; i++ ) + { + if( !color_used[i] ) + { + linearpalette[i][0] = r; + linearpalette[i][1] = g; + linearpalette[i][2] = b; + if( r < 0.0 ) r = 0.0; + if( r > 1.0 ) r = 1.0; + image->palette[i*3+0] = pow( r, 1.0 / 2.2) * 255; + if( g < 0.0 ) g = 0.0; + if( g > 1.0 ) g = 1.0; + image->palette[i*3+1] = pow( g, 1.0 / 2.2) * 255; + if( b < 0.0 ) b = 0.0; + if( b > 1.0 ) b = 1.0; + image->palette[i*3+2] = pow( b, 1.0 / 2.2) * 255; + color_used[i] = 1; + colors_used++; + return i; + } + } + return 0; +} + +/* +============= +Mip_AveragePixels + +FIXME: share this code by imglib someday +============= +*/ +byte Mip_AveragePixels( int count ) +{ + float r = 0, g = 0, b = 0; + int i, pix, vis = 0; + float bestdistortion, distortion; + int bestcolor; + vec3_t color; + + for( i = 0; i < count; i++ ) + { + pix = pixdata[i]; + r += linearpalette[pix][0]; + g += linearpalette[pix][1]; + b += linearpalette[pix][2]; + } + + r /= count; + g /= count; + b /= count; + + r += d_color[0]; + g += d_color[1]; + b += d_color[2]; + + // find the best color + bestdistortion = 3.0; + bestcolor = -1; + + for( i = 0; i < 255; i++ ) + { + if( color_used[i] ) + { + pix = i; + + VectorSet( color, r-linearpalette[i][0], g-linearpalette[i][1], b-linearpalette[i][2]); + distortion = DotProduct( color, color ); + if( distortion < bestdistortion ) + { + if( !distortion ) + { + VectorClear( d_color ); // no distortion yet + return pix; // perfect match + } + bestdistortion = distortion; + bestcolor = pix; + } + } + } + + if( bestdistortion > 0.001 && colors_used < 255 ) + { + bestcolor = Pal_AddColor( r, g, b ); + VectorClear( d_color ); + bestdistortion = 0; + } + else + { + // error diffusion + d_color[0] = r - linearpalette[bestcolor][0]; + d_color[1] = g - linearpalette[bestcolor][1]; + d_color[2] = b - linearpalette[bestcolor][2]; + } + + if( bestdistortion > maxdistortion ) + maxdistortion = bestdistortion; + + // index in palette (new or completely matched) + return bestcolor; +} + +void Wad3_NewWad( void ) +{ + if( wad_append ) + handle = WAD_Open( wadoutname, "a+" ); + else handle = WAD_Open( wadoutname, "wb" ); + if( !handle ) Sys_Break( "Wad3_NewWad: can't create %s\n", wadoutname ); +} + +/* +=============== +AddLump +=============== +*/ +void Wad3_AddLump( const byte *buffer, size_t lumpsize, int lump_type, bool compress ) +{ + int result; + if( !handle ) Wad3_NewWad(); // create wad file + result = WAD_Write( handle, lumpname, buffer, lumpsize, lump_type, ( compress ? CMP_ZLIB : CMP_NONE )); + if( result != -1 ) Msg("Add %s\t#%3i\n", lumpname, result ); // FIXME: align message +} + +/* +============== +Cmd_GrabMip + +$mipmap filename x y width height +============== +*/ +void Cmd_GrabMip( void ) +{ + int i, j, x, y, xl, yl, xh, yh, w, h; + byte *plump, *plump_end, *screen_p, *source; + int miplevel, mipstep, flags = 0; + byte *lump, testpixel; + int xx, yy, count; + int linedelta; + size_t plump_size; + mip_t *mip; + + Com_ReadString( wadqc, SC_PARSE_GENERIC, lumpname ); + + // load mip image + image = FS_LoadImage( lumpname, NULL, 0 ); + if( !image ) + { + Com_SkipRestOfLine( wadqc ); + // no fatal error, just ignore this image for adding into wad-archive + MsgDev( D_ERROR, "Cmd_LoadMip: unable to loading %s\n", lumpname ); + return; + } + + flags |= IMAGE_PALTO24; + + if(Com_ReadUlong( wadqc, false, &xl )) + { + Com_ReadUlong( wadqc, false, &yl); + Com_ReadUlong( wadqc, false, &w ); + Com_ReadUlong( wadqc, false, &h ); + } + else + { + xl = yl = 0; + w = image->width; + h = image->height; + } + + // reflood image with new size + if(( w & 15) || (h & 15)) flags |= IMAGE_ROUNDFILLER; + Image_Process( &image, 0, 0, flags ); + + if( flags & IMAGE_ROUNDFILLER ) + { + // updates image size + w = image->width; + h = image->height; + } + + xh = xl + w; + yh = yl + h; + + // mip_t + mipmap0[w>>0*h>>0] + mipmap1[w>>1*h>>1] + mipmap2[w>>2*h>>2] + mipmap3[w>>3*h>>3] + // + numolors[short] + palette[768]; + plump_size = (int)sizeof(*mip) + ((w * h * 85)>>6) + sizeof(short) + 768; + plump = lump = (byte *)Mem_Alloc( wadpool, plump_size ); + plump_end = plump + plump_size; // sentinel + + mip = (mip_t *)plump; + mip->width = LittleLong( w ); + mip->height = LittleLong( h ); + com.strncpy( mip->name, lumpname, sizeof(mip->name)); + plump = (byte *)&mip->offsets[4]; + + screen_p = image->buffer + yl * image->width + xl; + linedelta = image->width - w; + + source = plump; + mip->offsets[0] = LittleLong( plump - (byte *)mip ); + + // apply scissor to source + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + *plump++ = *screen_p++; + screen_p += linedelta; + } + + // calculate gamma corrected linear palette + for( i = 0; i < 256; i++ ) + { + for( j = 0; j < 3; j++ ) + { + // assume textures are done at 2.2, we want to remap them at 1.0 + float f = image->palette[i*3+j] / 255.0; + linearpalette[i][j] = pow( f, 2.2 ); + } + } + + maxdistortion = 0; + if(!(image->flags & IMAGE_HAS_ALPHA )) + { + // figure out what palette entries are actually used + colors_used = 0; + Mem_Set( color_used, 0, sizeof(int) * 256 ); + + for( x = 0; x < w; x++ ) + { + for( y = 0; y < h; y++ ) + { + if(!color_used[source[y * w + x]]) + { + color_used[source[y * w + x]] = 1; + colors_used++; + } + } + } + } + else + { + // assume palette full if it's a transparent texture + colors_used = 256; + Mem_Set( color_used, 1, sizeof(int) * 256 ); + } + + // subsample for greater mip levels + for( miplevel = 1; miplevel < 4; miplevel++ ) + { + int pixTest; + + VectorClear( d_color ); // no distortion yet + mip->offsets[miplevel] = LittleLong(plump - (byte *)mip); + + mipstep = 1<flags & IMAGE_HAS_ALPHA ) || testpixel != 255) + { + pixdata[count] = testpixel; + count++; + } + } + } + // solid pixels account for < 40% of this pixel, make it transparent + if( count <= pixTest ) *plump++ = 255; + else *plump++ = Mip_AveragePixels( count ); + } + } + } + + *(word*)plump = 256; // palette size + plump += sizeof( short ); + + // bounds checker + if( plump + 768 == plump_end ) + { + Mem_Copy( plump, image->palette, 768 ); + plump += 768; + + // write out and release intermediate buffers + Wad3_AddLump( lump, plump_size, TYPE_MIPTEX, false ); + } + else MsgDev( D_WARN, "lump %s have invalid size, ignore\n", lumpname ); + FS_FreeImage( image ); + Mem_Free( lump ); +} + +/* +============== +Cmd_GrabPic + +$gfxpic filename x y width height +============== +*/ +void Cmd_GrabPic( void ) +{ + int x, y, xl, yl, xh, yh; + byte *plump, *lump; + size_t plump_size; + lmp_t *pic; + + Com_ReadString( wadqc, SC_PARSE_GENERIC, lumpname ); + + // load lmp image + image = FS_LoadImage( lumpname, NULL, 0 ); + if( !image ) + { + Com_SkipRestOfLine( wadqc ); + // no fatal error, just ignore this image for adding into wad-archive + MsgDev( D_ERROR, "Cmd_LoadPic: unable to loading %s\n", lumpname ); + return; + } + + Image_Process( &image, 0, 0, IMAGE_PALTO24 ); // turn into 24-bit mode + + if(Com_ReadUlong( wadqc, false, &xl )) + { + Com_ReadUlong( wadqc, false, &yl); + Com_ReadUlong( wadqc, false, &x ); + Com_ReadUlong( wadqc, false, &y ); + xh = xl + x; + yh = yl + y; + } + else + { + xl = yl = 0; + xh = image->width; + yh = image->height; + } + + if( xh < xl || yh < yl || xl < 0 || yl < 0 ) + { + xl = yl = 0; + xh = image->width; + yh = image->height; + } + + // lmp_t + picture[w*h] + numolors[short] + palette[768]; + plump_size = (int)sizeof(*pic) + (xh * yh) + sizeof(short) + 768; + plump = lump = (byte *)Mem_Alloc( wadpool, plump_size ); + pic = (lmp_t *)plump; + pic->width = LittleLong( xh - xl ); + pic->height = LittleLong( yh - yl ); + + // apply scissor to source + plump = (byte *)(pic + 1); + for( y = yl; y < yh; y++ ) + for( x = xl; x < xh; x++ ) + *plump++ = (*(image->buffer + (y) * image->width + x)); + + *(word*)plump = 256; // palette size + plump += sizeof(short); + Mem_Copy( plump, image->palette, 768 ); + plump += 768; + + // write out and release intermediate buffers + Wad3_AddLump( lump, plump_size, TYPE_QPIC, false ); + FS_FreeImage( image ); + Mem_Free( lump ); +} + +/* +============== +Cmd_GrabScript + +$wadqc filename +============== +*/ +void Cmd_GrabScript( void ) +{ + byte *lump; + size_t plump_size; + + Com_ReadString( wadqc, SC_PARSE_GENERIC, lumpname ); + + // load mip image or replaced with error.bmp + lump = FS_LoadFile( lumpname, &plump_size ); + + if( !lump || !plump_size ) + { + Com_SkipRestOfLine( wadqc ); + // no fatal error, just ignore this image for adding into wad-archive + MsgDev( D_ERROR, "Cmd_LoadScript: unable to loading %s\n", lumpname ); + return; + } + + // write out and release intermediate buffers + Wad3_AddLump( lump, plump_size, TYPE_SCRIPT, true ); // always compress text files + Mem_Free( lump ); +} + +/* +============== +Cmd_GrabProgs + +$vprogs filename +============== +*/ +void Cmd_GrabProgs( void ) +{ + byte *lump; + size_t plump_size; + dprograms_t *hdr; + + Com_ReadString( wadqc, SC_PARSE_GENERIC, lumpname ); + + // load mip image or replaced with error.bmp + lump = FS_LoadFile( lumpname, &plump_size ); + + if( !lump || !plump_size || plump_size < sizeof(dprograms_t)) + { + Com_SkipRestOfLine( wadqc ); + // no fatal error, just ignore this image for adding into wad-archive + MsgDev( D_ERROR, "Cmd_LoadProgs: unable to loading %s\n", lumpname ); + return; + } + // validate progs + hdr = (dprograms_t *)lump; + + if( hdr->ident != VPROGSHEADER32 || hdr->version != VPROGS_VERSION ) + { + // no fatal error, just ignore this image for adding into wad-archive + MsgDev( D_ERROR, "Cmd_LoadProgs: %s invalid progs version, ignore\n", lumpname ); + Mem_Free( lump ); + return; + } + + // write out and release intermediate buffers + Wad3_AddLump( lump, plump_size, TYPE_VPROGS, !hdr->flags ); // release progs may be already packed + Mem_Free( lump ); +} + +void Cmd_WadName( void ) +{ + string parm; + + Com_ReadString( wadqc, SC_ALLOW_PATHNAMES2, wadoutname ); + + FS_StripExtension( wadoutname ); + FS_DefaultExtension( wadoutname, ".wad" ); + + if( Com_ReadString( wadqc, SC_ALLOW_PATHNAMES2, parm )) + if( !com.stricmp( parm, "append" )) wad_append = true; +} + +/* +============== +Cmd_WadUnknown + +syntax: "blabla" +============== +*/ +void Cmd_WadUnknown( const char *token ) +{ + MsgDev( D_WARN, "Cmd_WadUnknown: skip command \"%s\"\n", token ); + Com_SkipRestOfLine( wadqc ); +} + +void ResetWADInfo( void ) +{ + FS_FileBase( gs_filename, wadoutname ); // kill path and ext + FS_DefaultExtension( wadoutname, ".wad" ); // set new ext + handle = NULL; +} + +/* +=============== +ParseScript +=============== +*/ +bool ParseWADfileScript( void ) +{ + token_t token; + + ResetWADInfo(); + + while( 1 ) + { + if(!Com_ReadToken( wadqc, SC_ALLOW_NEWLINES|SC_PARSE_GENERIC, &token )) + break; + + if( !com.stricmp( token.string, "$wadname" )) Cmd_WadName(); + else if( !com.stricmp( token.string, "$mipmap" )) Cmd_GrabMip(); + else if( !com.stricmp( token.string, "$gfxpic" )) Cmd_GrabPic(); + else if( !com.stricmp( token.string, "$wadqc" )) Cmd_GrabScript(); + else if( !com.stricmp( token.string, "$vprogs" )) Cmd_GrabProgs(); + else if( !Com_ValidScript( token.string, QC_WADLIB )) return false; + else Cmd_WadUnknown( token.string ); + } + return true; +} + +bool WriteWADFile( void ) +{ + if( !handle ) return false; + WAD_Close( handle ); + Com_CloseScript( wadqc ); + return true; +} + +bool BuildCurrentWAD( const char *name ) +{ + if( name ) com.strncpy( gs_filename, name, sizeof( gs_filename )); + FS_DefaultExtension( gs_filename, ".qc" ); + wadqc = Com_OpenScript( gs_filename, NULL, 0 ); + + if( wadqc ) + { + if(!ParseWADfileScript()) + return false; + return WriteWADFile(); + } + + Msg( "%s not found\n", gs_filename ); + return false; +} + +bool CompileWad3Archive( byte *mempool, const char *name, byte parms ) +{ + if( mempool ) wadpool = mempool; + else + { + Msg( "Wadlib: can't allocate memory pool.\nAbort compilation\n" ); + return false; + } + return BuildCurrentWAD( name ); +} \ No newline at end of file diff --git a/xtools/xtools.c b/xtools/xtools.c new file mode 100644 index 00000000..73365b80 --- /dev/null +++ b/xtools/xtools.c @@ -0,0 +1,240 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// platform.c - tools common dll +//======================================================================= + +#include "xtools.h" +#include "utils.h" +#include "mdllib.h" +#include "vprogs_api.h" +#include "xtools.h" +#include "engine_api.h" +#include "mathlib.h" + +dll_info_t vprogs_dll = { "vprogs.dll", NULL, "CreateAPI", NULL, NULL, true, sizeof(vprogs_exp_t) }; +vprogs_exp_t *PRVM; +stdlib_api_t com; + +#define MAX_SEARCHMASK 256 +string searchmask[MAX_SEARCHMASK]; +int num_searchmask = 0; +string gs_searchmask; +string gs_gamedir; +byte *basepool; +byte *zonepool; +byte *error_bmp; +size_t error_bmp_size; +static double start, end; +uint app_name = HOST_OFFLINE; +bool enable_log = false; +file_t *bsplog = NULL; + +void ClrMask( void ) +{ + num_searchmask = 0; + Mem_Set( searchmask, 0, MAX_STRING * MAX_SEARCHMASK ); +} + +void AddMask( const char *mask ) +{ + if( num_searchmask >= MAX_SEARCHMASK ) + { + MsgDev( D_WARN, "AddMask: searchlist is full\n" ); + return; + } + com.strncpy( searchmask[num_searchmask], mask, MAX_STRING ); + num_searchmask++; +} + +/* +================== +CommonInit + +platform.dll needs for some setup operations +so do it manually +================== +*/ +void InitCommon( int argc, char **argv ) +{ + int imageflags = 0; + launch_t CreateVprogs; + + basepool = Mem_AllocPool( "Common Pool" ); + app_name = g_Instance; + enable_log = false; + + switch( app_name ) + { + case HOST_BSPLIB: + if( !FS_GetParmFromCmdLine( "-game", gs_basedir )) + com.strncpy( gs_basedir, Cvar_VariableString( "fs_defaultdir" ), sizeof( gs_basedir )); + if( !FS_GetParmFromCmdLine( "+map", gs_filename )) + com.strncpy( gs_filename, "newmap", sizeof( gs_filename )); + + + // initialize ImageLibrary + start = Sys_DoubleTime(); + //PrepareBSPModel( gs_basedir, gs_filename ); + break; + case HOST_QCCLIB: + Sys_LoadLibrary( &vprogs_dll ); // load qcclib + CreateVprogs = (void *)vprogs_dll.main; + PRVM = CreateVprogs( &com, NULL ); // second interface not allowed + + PRVM->Init( argc, argv ); + + if( !FS_GetParmFromCmdLine( "-dir", gs_basedir )) + com.strncpy( gs_basedir, ".", sizeof( gs_basedir )); + if( !FS_GetParmFromCmdLine( "+src", gs_filename )) + com.strncpy( gs_filename, "progs.src", sizeof( gs_filename )); + + start = Sys_DoubleTime(); + PRVM->PrepareDAT( gs_basedir, gs_filename ); + break; + case HOST_SPRITE: + case HOST_STUDIO: + case HOST_WADLIB: + imageflags |= IL_KEEP_8BIT; + case HOST_DPVENC: + // initialize ImageLibrary + Image_Init( NULL, imageflags ); + case HOST_RIPPER: + // blamk image for missed resources + error_bmp = FS_LoadInternal( "blank.bmp", &error_bmp_size ); + FS_InitRootDir("."); + + start = Sys_DoubleTime(); + Msg( "\n\n" ); // tabulation + break; + case HOST_OFFLINE: + break; + } +} + +void CommonMain( void ) +{ + search_t *search; + bool (*CompileMod)( byte *mempool, const char *name, byte parms ) = NULL; + cvar_t *fs_defaultdir = Cvar_Get( "fs_defaultdir", "tmpQuArK", CVAR_SYSTEMINFO, NULL ); + byte parms = 0; // future expansion + int i, j, numCompiledMods = 0; + string errorstring; + + // directory to extract + com.strncpy( gs_gamedir, fs_defaultdir->string, sizeof( gs_gamedir )); + Mem_Set( errorstring, 0, MAX_STRING ); + ClrMask(); + + switch( app_name ) + { + case HOST_SPRITE: + CompileMod = CompileSpriteModel; + AddMask( "*.qc" ); + break; + case HOST_STUDIO: + CompileMod = CompileStudioModel; + AddMask( "*.qc" ); + break; + case HOST_BSPLIB: + AddMask( "*.map" ); + //CompileBSPModel(); + break; + case HOST_WADLIB: + CompileMod = CompileWad3Archive; + AddMask( "*.qc" ); + break; + case HOST_DPVENC: + CompileMod = CompileDPVideo; + AddMask( "*.qc" ); + break; + case HOST_RIPPER: + CompileMod = ConvertResource; + Conv_RunSearch(); + break; + case HOST_QCCLIB: + AddMask( "*.src" ); + PRVM->CompileDAT(); + break; + case HOST_OFFLINE: + break; + } + if( !CompileMod ) goto elapced_time; // jump to shutdown + + // using custom mask + if(FS_GetParmFromCmdLine( "-file", gs_searchmask )) + { + ClrMask(); // clear all previous masks + AddMask( gs_searchmask ); // custom mask + } + zonepool = Mem_AllocPool( "Zone Pool" ); + Msg( "Converting ...\n\n" ); + + // search by mask + for( i = 0; i < num_searchmask; i++ ) + { + // skip blank mask + if( !com.strlen( searchmask[i] )) continue; + search = FS_Search( searchmask[i], true ); + if( !search ) continue; // try next mask + + for( j = 0; j < search->numfilenames; j++ ) + { + if( CompileMod( zonepool, search->filenames[j], parms )) + numCompiledMods++; + } + Mem_Free( search ); + } + if( numCompiledMods == 0 ) + { + if( !num_searchmask ) com.strncpy( errorstring, "files", MAX_STRING ); + for( j = 0; j < num_searchmask; j++ ) + { + if( !com.strlen( searchmask[j] )) continue; + com.strncat( errorstring, va("%s ", searchmask[j]), MAX_STRING ); + } + Sys_Break( "no %s found in this folder!\n", errorstring ); + } +elapced_time: + end = Sys_DoubleTime(); + Msg( "%5.3f seconds elapsed\n", end - start ); + if( numCompiledMods > 1) Msg( "total %d files proceed\n", numCompiledMods ); +} + +void FreeCommon( void ) +{ + if( app_name == HOST_QCCLIB ) + { + PRVM->Free(); + Sys_FreeLibrary( &vprogs_dll ); // free qcclib + } + else if( app_name == HOST_RIPPER ) + { + // finalize qc-script + Skin_FinalizeScript(); + } + else if( app_name == HOST_BSPLIB ) + { + if( bsplog ) FS_Close( bsplog ); + } + + Mem_Check(); // check for leaks + Mem_FreePool( &basepool ); + Mem_FreePool( &zonepool ); +} + +launch_exp_t DLLEXPORT *CreateAPI( stdlib_api_t *input, void *unused ) +{ + static launch_exp_t Com; + + com = *input; + + // generic functions + Com.api_size = sizeof(launch_exp_t); + + Com.Init = InitCommon; + Com.Main = CommonMain; + Com.Free = FreeCommon; + Com.CPrint = NULL; + + return &Com; +} \ No newline at end of file diff --git a/xtools/xtools.dsp b/xtools/xtools.dsp new file mode 100644 index 00000000..0c428ed6 --- /dev/null +++ b/xtools/xtools.dsp @@ -0,0 +1,189 @@ +# Microsoft Developer Studio Project File - Name="xtools" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=xtools - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "xtools.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "xtools.mak" CFG="xtools - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "xtools - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "xtools - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "xtools - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\xtools\!release" +# PROP Intermediate_Dir "..\temp\xtools\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "./" /I "../public" /I "bsplib" /I "ripper" /I "../common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib /nologo /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /opt:nowin98 +# SUBTRACT LINK32 /profile +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\xtools\!release +InputPath=\Xash3D\src_main\temp\xtools\!release\xtools.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\xtools.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\xtools.dll "D:\Xash3D\bin\xtools.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "xtools - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\temp\xtools\!debug" +# PROP Intermediate_Dir "..\temp\xtools\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "./" /I "../public" /I "bsplib" /I "ripper" /I "../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 msvcrtd.lib user32.lib /nologo /dll /debug /machine:I386 /nodefaultlib:"libc.lib" /pdbtype:sept +# SUBTRACT LINK32 /incremental:no /nodefaultlib +# Begin Custom Build +TargetDir=\Xash3D\src_main\temp\xtools\!debug +InputPath=\Xash3D\src_main\temp\xtools\!debug\xtools.dll +SOURCE="$(InputPath)" + +"D:\Xash3D\bin\xtools.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\xtools.dll "D:\Xash3D\bin\xtools.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "xtools - Win32 Release" +# Name "xtools - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\ripper\conv_bsplumps.c +# End Source File +# Begin Source File + +SOURCE=.\ripper\conv_doom.c +# End Source File +# Begin Source File + +SOURCE=.\ripper\conv_image.c +# End Source File +# Begin Source File + +SOURCE=.\ripper\conv_main.c +# End Source File +# Begin Source File + +SOURCE=.\ripper\conv_shader.c +# End Source File +# Begin Source File + +SOURCE=.\ripper\conv_sprite.c +# End Source File +# Begin Source File + +SOURCE=.\dpvencoder.c +# End Source File +# Begin Source File + +SOURCE=.\spritegen.c +# End Source File +# Begin Source File + +SOURCE=.\studio.c +# End Source File +# Begin Source File + +SOURCE=.\studio_utils.c +# End Source File +# Begin Source File + +SOURCE=.\utils.c +# End Source File +# Begin Source File + +SOURCE=.\wadlib.c +# End Source File +# Begin Source File + +SOURCE=.\xtools.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\mdllib.h +# End Source File +# Begin Source File + +SOURCE=.\ripper\ripper.h +# End Source File +# Begin Source File + +SOURCE=.\utils.h +# End Source File +# Begin Source File + +SOURCE=.\xtools.h +# End Source File +# End Group +# End Target +# End Project diff --git a/xtools/xtools.h b/xtools/xtools.h new file mode 100644 index 00000000..0bbd7494 --- /dev/null +++ b/xtools/xtools.h @@ -0,0 +1,33 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// xtools.h - Xash Game Tools +//======================================================================= +#ifndef XTOOLS_H +#define XTOOLS_H + +#include +#include "launch_api.h" +#include "qfiles_ref.h" + +//===================================== +// platform export +//===================================== + +void InitPlatform ( int argc, char **argv ); // init host +void RunPlatform ( void ); // host frame +void ClosePlatform ( void ); // close host + +//===================================== +// extragen export +//===================================== +bool ConvertResource( byte *mempool, const char *filename, byte parms ); +void Skin_FinalizeScript( void ); +void Conv_RunSearch( void ); + +// shared tools +void ClrMask( void ); +void AddMask( const char *mask ); +extern string searchmask[]; +extern int num_searchmask; + +#endif//XTOOLS_H \ No newline at end of file